feat(fable-mcp): build wheel in Docker image, serve download, add admin log tool, and Settings install UI
- Dockerfile: build fable-mcp wheel into /app/dist/ during image build - routes/fable_mcp_dist.py: GET /api/fable-mcp/info + /download endpoints - app.py: register fable_mcp_dist_bp - fable_mcp/tools/admin.py: get_app_logs() hitting /api/admin/logs - fable_mcp/server.py: fable_get_app_logs MCP tool - SettingsView: "Fable MCP" section in API Keys tab with download button and install instructions - client.ts: getFableMcpInfo() helper - ci.yml: add fable-mcp/** to trigger paths Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -3,7 +3,7 @@ import { ref, watch, onMounted } from "vue";
|
||||
import { useSettingsStore } from "@/stores/settings";
|
||||
import { useAuthStore } from "@/stores/auth";
|
||||
import { useToastStore } from "@/stores/toast";
|
||||
import { apiGet, apiPost, apiPut, apiDelete, listGroups, createGroup, deleteGroup, listGroupMembers, addGroupMember, removeGroupMember, searchUsers, getBriefingConfig, saveBriefingConfig, getBriefingFeeds, createBriefingFeed, deleteBriefingFeed, refreshBriefingFeeds, geocodeAddress, type GroupEntry, type GroupMember, type UserSearchResult, type BriefingConfig, type BriefingFeed } from "@/api/client";
|
||||
import { apiGet, apiPost, apiPut, apiDelete, listGroups, createGroup, deleteGroup, listGroupMembers, addGroupMember, removeGroupMember, searchUsers, getBriefingConfig, saveBriefingConfig, getBriefingFeeds, createBriefingFeed, deleteBriefingFeed, refreshBriefingFeeds, geocodeAddress, getFableMcpInfo, type GroupEntry, type GroupMember, type UserSearchResult, type BriefingConfig, type BriefingFeed } from "@/api/client";
|
||||
import { usePushStore } from "@/stores/push";
|
||||
import type { User } from "@/types/auth";
|
||||
import PaginationBar from "@/components/PaginationBar.vue";
|
||||
@@ -48,7 +48,7 @@ watch(activeTab, (v) => {
|
||||
if (v === "logs" && authStore.isAdmin) loadLogsPanel();
|
||||
if (v === "groups" && authStore.isAdmin) loadGroupsPanel();
|
||||
if (v === "briefing") loadBriefingTab();
|
||||
if (v === "apikeys") fetchApiKeys();
|
||||
if (v === "apikeys") { fetchApiKeys(); loadMcpInfo(); }
|
||||
});
|
||||
|
||||
// API Keys
|
||||
@@ -59,6 +59,33 @@ const newKeyValue = ref('');
|
||||
const apiKeyCopied = ref(false);
|
||||
const creatingApiKey = ref(false);
|
||||
const revokeConfirmId = ref<number | null>(null);
|
||||
const mcpInfo = ref<{ available: boolean; filename: string | null } | null>(null);
|
||||
const mcpInfoLoading = ref(false);
|
||||
|
||||
const origin = window.location.origin;
|
||||
const mcpConfigSnippet = JSON.stringify({
|
||||
mcpServers: {
|
||||
fable: {
|
||||
command: "fable-mcp",
|
||||
env: {
|
||||
FABLE_URL: window.location.origin,
|
||||
FABLE_API_KEY: "<your-api-key>",
|
||||
},
|
||||
},
|
||||
},
|
||||
}, null, 2);
|
||||
|
||||
async function loadMcpInfo() {
|
||||
if (mcpInfo.value !== null) return;
|
||||
mcpInfoLoading.value = true;
|
||||
try {
|
||||
mcpInfo.value = await getFableMcpInfo();
|
||||
} catch {
|
||||
mcpInfo.value = { available: false, filename: null };
|
||||
} finally {
|
||||
mcpInfoLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchApiKeys() {
|
||||
const res = await fetch('/api/api-keys', { credentials: 'include' });
|
||||
@@ -1818,6 +1845,47 @@ function formatUserDate(iso: string): string {
|
||||
</table>
|
||||
<p v-else-if="apiKeys.length === 0 && activeTab === 'apikeys'" class="settings-empty">No API keys yet.</p>
|
||||
</section>
|
||||
|
||||
<!-- Fable MCP install section -->
|
||||
<section class="settings-section full-width">
|
||||
<h2>Fable MCP</h2>
|
||||
<p class="settings-description">
|
||||
The Fable MCP server lets Claude (and other MCP clients) read and write your notes, tasks, and projects.
|
||||
Install it locally, then point it at this Fable instance with an API key above.
|
||||
</p>
|
||||
|
||||
<div v-if="mcpInfoLoading" class="mcp-status">Checking package availability…</div>
|
||||
<template v-else-if="mcpInfo">
|
||||
<div v-if="mcpInfo.available" class="mcp-available">
|
||||
<div class="mcp-pkg-row">
|
||||
<span class="mcp-pkg-name">{{ mcpInfo.filename }}</span>
|
||||
<a href="/api/fable-mcp/download" class="btn btn-primary btn-sm" download>Download</a>
|
||||
</div>
|
||||
|
||||
<div class="mcp-install-steps">
|
||||
<h3>Installation</h3>
|
||||
<ol>
|
||||
<li>
|
||||
Download the wheel above and install it:
|
||||
<pre class="mcp-code">pip install {{ mcpInfo.filename }}</pre>
|
||||
</li>
|
||||
<li>
|
||||
Create a <code>.env</code> file (or set environment variables):
|
||||
<pre class="mcp-code">FABLE_URL={{ origin }}
|
||||
FABLE_API_KEY=<your-api-key></pre>
|
||||
</li>
|
||||
<li>
|
||||
Add to your Claude MCP config (<code>~/.claude.json</code> or the Claude Desktop config):
|
||||
<pre class="mcp-code">{{ mcpConfigSnippet }}</pre>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="mcp-unavailable">
|
||||
<p>The Fable MCP package is not bundled in this image build. It will be available after the next image rebuild.</p>
|
||||
</div>
|
||||
</template>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<!-- ── Admin ── -->
|
||||
@@ -3411,4 +3479,34 @@ function formatUserDate(iso: string): string {
|
||||
.scope-badge.write { background: color-mix(in srgb, #10b981 15%, transparent); color: #10b981; }
|
||||
.settings-empty { opacity: 0.5; margin-top: 1rem; }
|
||||
.settings-description { opacity: 0.7; margin-bottom: 1rem; line-height: 1.5; }
|
||||
|
||||
/* Fable MCP section */
|
||||
.mcp-status { opacity: 0.6; font-size: 0.9rem; }
|
||||
.mcp-unavailable p { opacity: 0.7; }
|
||||
.mcp-available { display: flex; flex-direction: column; gap: 1.25rem; }
|
||||
.mcp-pkg-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
padding: 0.65rem 0.9rem;
|
||||
background: color-mix(in srgb, var(--color-primary) 8%, transparent);
|
||||
border: 1px solid color-mix(in srgb, var(--color-primary) 25%, transparent);
|
||||
border-radius: 8px;
|
||||
}
|
||||
.mcp-pkg-name { font-family: monospace; font-size: 0.9rem; flex: 1; }
|
||||
.mcp-install-steps h3 { font-size: 0.95rem; font-weight: 600; margin-bottom: 0.75rem; }
|
||||
.mcp-install-steps ol { padding-left: 1.25rem; display: flex; flex-direction: column; gap: 0.75rem; }
|
||||
.mcp-install-steps li { line-height: 1.6; font-size: 0.9rem; }
|
||||
.mcp-code {
|
||||
margin-top: 0.4rem;
|
||||
padding: 0.55rem 0.75rem;
|
||||
background: color-mix(in srgb, var(--color-text) 6%, transparent);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 6px;
|
||||
font-size: 0.82rem;
|
||||
font-family: monospace;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
overflow-x: auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user