UNPKG

@fusedashlabs/ui9000-workspace

Version:

UI9000 workspace MCP. One tool, show_workspace, draws a chart from your table. Set TYPESAFE_API_KEY to let Jev choose the chart.

93 lines (92 loc) • 3.88 kB
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { registerAppResource } from '@modelcontextprotocol/ext-apps/server'; import { z } from 'zod'; import { INTENTS } from '../spec/intent.js'; import { SHOW_WORKSPACE_DESCRIPTION, SHOW_WORKSPACE_INPUT_SCHEMA, SHOW_WORKSPACE_NAME, } from '../tool/show-workspace.js'; import { SERVER_INFO, WORKSPACE_INSTRUCTIONS, workspaceToolResult, } from './create-server.js'; /** Same closed enum and field text as SHOW_WORKSPACE_INPUT_SCHEMA. */ const ShowWorkspaceArgsSchema = z .object({ intent: z.enum(INTENTS).describe(schemaDescription('intent')), csv: z.string().optional().describe(schemaDescription('csv')), url: z.string().optional().describe(schemaDescription('url')), path: z.string().optional().describe(schemaDescription('path')), datasetId: z.string().optional().describe(schemaDescription('datasetId')), utterance: z.string().optional().describe(schemaDescription('utterance')), requestedChart: z.string().optional().describe(schemaDescription('requestedChart')), confirm: z.boolean().optional().describe(schemaDescription('confirm')), columns: z.array(z.string()).optional().describe(schemaDescription('columns')), }) .strict(); function schemaDescription(name) { return SHOW_WORKSPACE_INPUT_SCHEMA.properties[name].description; } export function createSdkWorkspaceServer(handler, info) { assertIntentSchema(info.inputSchema); const appResource = info.appResource; if (!appResource) { throw new Error('createSdkWorkspaceServer requires an MCP App resource.'); } const server = new McpServer(SERVER_INFO, { capabilities: { tools: {}, resources: {} }, instructions: WORKSPACE_INSTRUCTIONS, }); registerAppResource(server, appResource.name, appResource.uri, { description: appResource.description, _meta: { ui: appResource.ui }, }, async () => { const text = await appResource.loadHtml(); return { contents: [ { uri: appResource.uri, mimeType: appResource.mimeType, text, _meta: { ui: appResource.ui }, }, ], }; }); const resourceUri = appResource.uri; server.registerTool(SHOW_WORKSPACE_NAME, { title: 'Show workspace', description: info.description ?? SHOW_WORKSPACE_DESCRIPTION, inputSchema: ShowWorkspaceArgsSchema, annotations: { readOnlyHint: true, openWorldHint: false }, _meta: { ui: { resourceUri, visibility: ['model', 'app'], csp: appResource.ui.csp, }, 'ui/resourceUri': resourceUri, }, }, async (args) => { try { return workspaceToolResult(await handler(args), appResource); } catch (error) { const message = error instanceof Error ? error.message : 'Internal error'; return { content: [{ type: 'text', text: message }], isError: true }; } }); return server; } export async function connectSdkWorkspace(handler, info, streams) { const server = createSdkWorkspaceServer(handler, info); const transport = new StdioServerTransport(streams?.input, streams?.output); await server.connect(transport); return () => { void server.close(); }; } function assertIntentSchema(inputSchema) { if (!inputSchema) return; const listed = inputSchema; const fromJson = listed.properties?.intent?.enum; if (Array.isArray(fromJson) && fromJson.join() !== INTENTS.join()) { throw new Error('show_workspace SDK schema drifted from SHOW_WORKSPACE_INPUT_SCHEMA.'); } }