From deb8aacca4212ef191a0438a4a3303f8b02d12d4 Mon Sep 17 00:00:00 2001 From: dswbx Date: Fri, 15 Aug 2025 10:12:09 +0200 Subject: [PATCH] added mcp ui as tool --- app/src/data/api/DataController.ts | 6 +- app/src/modules/mcp/$object.ts | 4 + app/src/modules/mcp/$record.ts | 4 + app/src/modules/server/AdminController.tsx | 2 +- app/src/modules/server/SystemController.ts | 6 +- app/src/ui/layouts/AppShell/AppShell.tsx | 89 ++++--- app/src/ui/layouts/AppShell/Header.tsx | 17 +- app/src/ui/main.css | 9 +- app/src/ui/routes/index.tsx | 6 + app/src/ui/routes/media/_media.root.tsx | 2 - app/src/ui/routes/test/index.tsx | 2 - app/src/ui/routes/test/tests/mcp/tools.tsx | 171 -------------- app/src/ui/routes/tools/index.tsx | 16 ++ .../routes/tools/mcp/components/mcp-icon.tsx | 15 ++ .../mcp/mcp-test.tsx => tools/mcp/mcp.tsx} | 27 ++- .../routes/{test/tests => tools}/mcp/state.ts | 9 + app/src/ui/routes/tools/mcp/tools.tsx | 217 ++++++++++++++++++ .../routes/{test/tests => tools}/mcp/utils.ts | 0 app/src/ui/store/appshell.ts | 64 +++++- 19 files changed, 445 insertions(+), 221 deletions(-) delete mode 100644 app/src/ui/routes/test/tests/mcp/tools.tsx create mode 100644 app/src/ui/routes/tools/index.tsx create mode 100644 app/src/ui/routes/tools/mcp/components/mcp-icon.tsx rename app/src/ui/routes/{test/tests/mcp/mcp-test.tsx => tools/mcp/mcp.tsx} (58%) rename app/src/ui/routes/{test/tests => tools}/mcp/state.ts (56%) create mode 100644 app/src/ui/routes/tools/mcp/tools.tsx rename app/src/ui/routes/{test/tests => tools}/mcp/utils.ts (100%) diff --git a/app/src/data/api/DataController.ts b/app/src/data/api/DataController.ts index d7cbc24..ba79df7 100644 --- a/app/src/data/api/DataController.ts +++ b/app/src/data/api/DataController.ts @@ -62,7 +62,11 @@ export class DataController extends Controller { hono.get( "/sync", permission(DataPermissions.databaseSync), - mcpTool("data_sync"), + mcpTool("data_sync", { + annotations: { + destructiveHint: true, + }, + }), describeRoute({ summary: "Sync database schema", tags: ["data"], diff --git a/app/src/modules/mcp/$object.ts b/app/src/modules/mcp/$object.ts index f52c723..a57257b 100644 --- a/app/src/modules/mcp/$object.ts +++ b/app/src/modules/mcp/$object.ts @@ -53,6 +53,10 @@ export class ObjectToolSchema< }) .optional(), }), + annotations: { + readOnlyHint: true, + destructiveHint: false, + }, }, async (params, ctx: AppToolHandlerCtx) => { const configs = ctx.context.app.toJSON(params.secrets); diff --git a/app/src/modules/mcp/$record.ts b/app/src/modules/mcp/$record.ts index cbc1856..a10054a 100644 --- a/app/src/modules/mcp/$record.ts +++ b/app/src/modules/mcp/$record.ts @@ -69,6 +69,10 @@ export class RecordToolSchema< }) .optional(), }), + annotations: { + readOnlyHint: true, + destructiveHint: false, + }, }, async (params, ctx: AppToolHandlerCtx) => { const configs = ctx.context.app.toJSON(params.secrets); diff --git a/app/src/modules/server/AdminController.tsx b/app/src/modules/server/AdminController.tsx index d714098..d15eefe 100644 --- a/app/src/modules/server/AdminController.tsx +++ b/app/src/modules/server/AdminController.tsx @@ -91,7 +91,7 @@ export class AdminController extends Controller { logout: "/api/auth/logout", }; - const paths = ["/", "/data/*", "/auth/*", "/media/*", "/flows/*", "/settings/*"]; + const paths = ["/", "/data/*", "/auth/*", "/media/*", "/flows/*", "/settings/*", "/tools/*"]; if (isDebug()) { paths.push("/test/*"); } diff --git a/app/src/modules/server/SystemController.ts b/app/src/modules/server/SystemController.ts index 7660232..15914ae 100644 --- a/app/src/modules/server/SystemController.ts +++ b/app/src/modules/server/SystemController.ts @@ -131,7 +131,11 @@ export class SystemController extends Controller { summary: "Get the config for a module", tags: ["system"], }), - mcpTool("system_config"), // @todo: ":module" gets not removed + mcpTool("system_config", { + annotations: { + readOnlyHint: true, + }, + }), // @todo: ":module" gets not removed jsc("param", s.object({ module: s.string({ enum: MODULE_NAMES }).optional() })), jsc("query", s.object({ secrets: s.boolean().optional() })), async (c) => { diff --git a/app/src/ui/layouts/AppShell/AppShell.tsx b/app/src/ui/layouts/AppShell/AppShell.tsx index 9018e29..a161356 100644 --- a/app/src/ui/layouts/AppShell/AppShell.tsx +++ b/app/src/ui/layouts/AppShell/AppShell.tsx @@ -19,15 +19,9 @@ import { appShellStore } from "ui/store"; import { useLocation } from "wouter"; export function Root({ children }: { children: React.ReactNode }) { - const sidebarWidth = appShellStore((store) => store.sidebarWidth); return ( -
+
{children}
@@ -97,10 +91,24 @@ export function Main({ children }) { ); } -export function Sidebar({ children }) { - const open = appShellStore((store) => store.sidebarOpen); - const close = appShellStore((store) => store.closeSidebar); +export function Sidebar({ + children, + name = "default", + handle = "right", + minWidth, + maxWidth, +}: { + children: React.ReactNode; + name?: string; + handle?: "right" | "left"; + minWidth?: number; + maxWidth?: number; +}) { + const open = appShellStore((store) => store.sidebars[name]?.open); + const close = appShellStore((store) => store.closeSidebar(name)); + const width = appShellStore((store) => store.sidebars[name]?.width ?? 350); const ref = useClickOutside(close, ["mouseup", "touchend"]); //, [document.getElementById("header")]); + const sidebarRef = useRef(null!); const [location] = useLocation(); const closeHandler = () => { @@ -115,16 +123,35 @@ export function Sidebar({ children }) { return ( <> + {handle === "left" && ( + + )} - + {handle === "right" && ( + + )}