@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
JavaScript
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.');
}
}