@aprovan/mcp-app-server 0.1.0-dev.c1ac1dc

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/.turbo/turbo-build.log +20 -0
  2. package/E2E_TESTING.md +198 -0
  3. package/LICENSE +373 -0
  4. package/README.md +134 -0
  5. package/dist/index.d.ts +108 -0
  6. package/dist/index.js +1592 -0
  7. package/dist/index.js.map +1 -0
  8. package/dist/server.d.ts +2 -0
  9. package/dist/server.js +1841 -0
  10. package/dist/server.js.map +1 -0
  11. package/package.json +52 -0
  12. package/src/__tests__/cache.test.ts +119 -0
  13. package/src/__tests__/cdn-plugin.test.ts +86 -0
  14. package/src/__tests__/compile.test.ts +93 -0
  15. package/src/__tests__/e2e-pipeline.test.ts +417 -0
  16. package/src/__tests__/live-update.test.ts +158 -0
  17. package/src/__tests__/local-backend.test.ts +100 -0
  18. package/src/__tests__/memory-backend.ts +144 -0
  19. package/src/__tests__/registry-backend.test.ts +256 -0
  20. package/src/__tests__/services.test.ts +153 -0
  21. package/src/__tests__/shim.test.ts +132 -0
  22. package/src/__tests__/widget-store.test.ts +183 -0
  23. package/src/compiler/cache.ts +68 -0
  24. package/src/compiler/cdn-plugin.ts +197 -0
  25. package/src/compiler/compile.ts +306 -0
  26. package/src/compiler/index.ts +3 -0
  27. package/src/hello-world.html +79 -0
  28. package/src/html.d.ts +4 -0
  29. package/src/index.ts +641 -0
  30. package/src/live-update.ts +149 -0
  31. package/src/reference-widgets/live-dashboard.ts +162 -0
  32. package/src/registry-backend.ts +304 -0
  33. package/src/server.ts +178 -0
  34. package/src/services.ts +251 -0
  35. package/src/shim.ts +247 -0
  36. package/src/widget-store/index.ts +10 -0
  37. package/src/widget-store/local-backend.ts +77 -0
  38. package/src/widget-store/store.ts +198 -0
  39. package/src/widget-store/types.ts +50 -0
  40. package/tsconfig.json +10 -0
  41. package/tsup.config.ts +14 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/server.ts","../src/live-update.ts","../src/registry-backend.ts","../src/index.ts","../src/compiler/compile.ts","../src/shim.ts","../src/compiler/cache.ts","../src/compiler/cdn-plugin.ts","../src/hello-world.html","../src/services.ts","../src/widget-store/store.ts","../src/widget-store/local-backend.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\nimport { createMcpExpressApp } from '@modelcontextprotocol/sdk/server/express.js';\nimport { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';\nimport cors from 'cors';\nimport { registerSession, unregisterSession } from './live-update.js';\nimport { createRegistryBackend, type RegistryBackend } from './registry-backend.js';\nimport { createMcpAppServer, type McpAppServerOptions } from './index.js';\n\nconst PORT = Number(process.env['PORT'] ?? 3000);\nconst HOST = process.env['HOST'] ?? '0.0.0.0';\n\nasync function startServer(): Promise<void> {\n const serverOptions: McpAppServerOptions = {};\n\n // ---------------------------------------------------------------------------\n // Registry auto-configuration\n //\n // Set REGISTRY_PROVIDERS to a comma-separated list of @utdk providers\n // (e.g. \"github,slack,stripe\") to automatically spawn the Registry MCP server\n // and make all its tools available to widgets.\n //\n // The Registry process inherits all current environment variables, so provider\n // credentials (GITHUB_TOKEN, STRIPE_SECRET_KEY, etc.) do not need to be\n // duplicated — just set them once in this process's environment.\n //\n // Optional overrides:\n // REGISTRY_COMMAND Executable to run (default: \"npx\")\n // REGISTRY_ARGS Space-separated extra args appended after \"@utdk/mcp-server\"\n // ---------------------------------------------------------------------------\n const REGISTRY_PROVIDERS = process.env['REGISTRY_PROVIDERS'];\n\n let registryBackend: RegistryBackend | null = null;\n\n if (REGISTRY_PROVIDERS) {\n const command = process.env['REGISTRY_COMMAND'] ?? 'npx';\n const extraArgs = process.env['REGISTRY_ARGS']?.split(' ').filter(Boolean) ?? [];\n const args = ['@utdk/mcp-server', ...extraArgs];\n\n console.log(\n `[mcp-app-server] Connecting to Registry MCP server (providers: ${REGISTRY_PROVIDERS})...`,\n );\n\n try {\n registryBackend = await createRegistryBackend({\n command,\n args,\n providers: REGISTRY_PROVIDERS,\n });\n\n const toolInfos = registryBackend.getToolInfos();\n serverOptions.services = {\n backend: registryBackend,\n tools: toolInfos,\n };\n\n const namespaces = [...new Set(toolInfos.map((t) => t.namespace))];\n console.log(\n `[mcp-app-server] Registry ready: ${toolInfos.length} tools across namespaces: ${namespaces.join(', ')}`,\n );\n } catch (err) {\n console.error('[mcp-app-server] Failed to connect to Registry MCP server:', err);\n console.error('[mcp-app-server] Starting without Registry service backend.');\n }\n }\n\n const app = createMcpExpressApp({ host: HOST });\n\n // Allow cross-origin requests so Claude web (behind cloudflared) can reach the server\n app.use(cors());\n\n /**\n * Session store for stateful MCP connections.\n *\n * Each MCP session gets its own McpServer + StreamableHTTPServerTransport pair.\n * The session ID is minted on initialization and echoed in the Mcp-Session-Id\n * response header so the host can route subsequent requests and the standalone\n * GET SSE stream back to the correct server instance.\n *\n * Stateful sessions are required for server-initiated push: calling\n * `mcpServer.server.notification(...)` on a live session delivers the\n * message through the session's SSE stream to the host, which forwards it to\n * the widget iframe.\n */\n const sessionStore = new Map<\n string,\n { server: ReturnType<typeof createMcpAppServer>; transport: StreamableHTTPServerTransport }\n >();\n\n /**\n * MCP endpoint — stateful session mode.\n *\n * First request (no Mcp-Session-Id header): creates a new session.\n * Subsequent requests carry the session ID and are routed to the existing\n * server + transport pair.\n */\n app.all('/mcp', async (req, res) => {\n const sessionId = req.headers['mcp-session-id'] as string | undefined;\n\n if (sessionId) {\n const existing = sessionStore.get(sessionId);\n if (existing) {\n try {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-argument\n await existing.transport.handleRequest(req, res, req.body);\n } catch (err) {\n console.error('[mcp] session request error', err);\n if (!res.headersSent) {\n res.status(500).json({ error: 'Internal server error' });\n }\n }\n return;\n }\n // Unknown session ID — fall through to create a fresh session.\n }\n\n // New session\n const mcpServer = createMcpAppServer(serverOptions);\n const transport = new StreamableHTTPServerTransport({\n sessionIdGenerator: () => randomUUID(),\n onsessioninitialized: (id) => {\n sessionStore.set(id, { server: mcpServer, transport });\n registerSession(id, mcpServer);\n },\n onsessionclosed: (id) => {\n sessionStore.delete(id);\n unregisterSession(id);\n },\n });\n\n // Clean up when the response finishes (handles SSE streams that close early)\n res.on('close', () => {\n const id = transport.sessionId;\n if (id && !sessionStore.has(id)) {\n void mcpServer.close();\n }\n });\n\n try {\n await mcpServer.connect(transport);\n // eslint-disable-next-line @typescript-eslint/no-unsafe-argument\n await transport.handleRequest(req, res, req.body);\n } catch (err) {\n console.error('[mcp] new session error', err);\n if (!res.headersSent) {\n res.status(500).json({ error: 'Internal server error' });\n }\n }\n });\n\n /** Health-check endpoint — useful for cloudflared and load-balancer probes. */\n app.get('/health', (_req, res) => {\n res.json({ status: 'ok', service: 'patchwork-mcp-app-server' });\n });\n\n app.listen(PORT, HOST, () => {\n console.log(`MCP App Server listening on http://${HOST}:${PORT}`);\n console.log(` POST /mcp — MCP Streamable HTTP endpoint (stateful sessions)`);\n console.log(` GET /health — health check`);\n console.log();\n console.log('To expose locally via cloudflared:');\n console.log(` cloudflared tunnel --url http://localhost:${PORT}`);\n });\n\n // Clean up the Registry child process on shutdown.\n const shutdown = () => {\n if (registryBackend) {\n registryBackend.close().catch(() => {/* ignore close errors on exit */});\n }\n process.exit(0);\n };\n process.on('SIGTERM', shutdown);\n process.on('SIGINT', shutdown);\n}\n\nstartServer().catch((err: unknown) => {\n console.error('[mcp-app-server] Fatal startup error:', err);\n process.exit(1);\n});\n","import { type McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\n\n/**\n * A single buffered event for a data stream.\n */\nexport interface StreamEvent {\n seq: number;\n data: unknown;\n timestamp: number;\n}\n\nconst RING_BUFFER_MAX = 100;\n\n/**\n * Process-level ring buffer for each named stream.\n * Shared across all sessions so any request can read from it.\n */\nconst streamBuffers = new Map<string, StreamEvent[]>();\nlet globalSeq = 0;\n\nfunction getOrCreateBuffer(stream: string): StreamEvent[] {\n let buf = streamBuffers.get(stream);\n if (!buf) {\n buf = [];\n streamBuffers.set(stream, buf);\n }\n return buf;\n}\n\n/**\n * Retrieve buffered events for a stream with sequence numbers > afterSeq.\n */\nexport function getEvents(stream: string, afterSeq: number): StreamEvent[] {\n const buf = streamBuffers.get(stream);\n if (!buf) return [];\n return buf.filter((e) => e.seq > afterSeq);\n}\n\n/**\n * Write an event into the ring buffer for a stream.\n * Evicts the oldest entry when the buffer is full.\n */\nexport function appendEvent(stream: string, data: unknown): StreamEvent {\n const buf = getOrCreateBuffer(stream);\n const event: StreamEvent = {\n seq: ++globalSeq,\n data,\n timestamp: Date.now(),\n };\n buf.push(event);\n if (buf.length > RING_BUFFER_MAX) {\n buf.shift();\n }\n return event;\n}\n\n/**\n * Per-session subscription tracking.\n * A session is keyed by the MCP session ID; its McpServer is used to send\n * `notifications/tools/list_changed` as a push signal when stream events arrive.\n */\ninterface SessionEntry {\n server: McpServer;\n streams: Set<string>;\n}\n\nconst sessions = new Map<string, SessionEntry>();\n\n/**\n * Register a live MCP session so it can receive push notifications.\n */\nexport function registerSession(sessionId: string, server: McpServer): void {\n if (!sessions.has(sessionId)) {\n sessions.set(sessionId, { server, streams: new Set() });\n }\n}\n\n/**\n * Unregister a session (e.g. on transport close or DELETE).\n */\nexport function unregisterSession(sessionId: string): void {\n sessions.delete(sessionId);\n}\n\n/**\n * Subscribe a session to a named stream.\n */\nexport function subscribeSession(sessionId: string, stream: string): void {\n const entry = sessions.get(sessionId);\n if (entry) {\n entry.streams.add(stream);\n }\n}\n\n/**\n * Unsubscribe a session from a named stream.\n */\nexport function unsubscribeSession(sessionId: string, stream: string): void {\n const entry = sessions.get(sessionId);\n if (entry) {\n entry.streams.delete(stream);\n }\n}\n\n/**\n * Push a data update to all sessions that have subscribed to `stream`.\n *\n * The event is appended to the ring buffer. Each subscribed session's McpServer\n * then sends a `notifications/tools/list_changed` notification to the host.\n * The host forwards this to the widget, which uses it as a signal to call\n * `poll_updates` and retrieve the buffered data.\n *\n * @returns The new sequence number assigned to this event.\n */\nexport async function pushStreamUpdate(\n stream: string,\n data: unknown,\n): Promise<number> {\n const event = appendEvent(stream, data);\n\n const pushPromises: Promise<void>[] = [];\n for (const [, entry] of sessions) {\n if (entry.streams.has(stream)) {\n const notifyPromise = entry.server.server\n .notification({ method: \"notifications/tools/list_changed\" })\n .catch((err: unknown) => {\n console.warn(\"[live-update] Failed to notify session:\", err);\n });\n pushPromises.push(notifyPromise);\n }\n }\n\n await Promise.all(pushPromises);\n return event.seq;\n}\n\n/**\n * Return the current highest sequence number (useful for initial subscribe).\n */\nexport function currentSeq(): number {\n return globalSeq;\n}\n\n/** Clears all buffers and sessions — for testing only. */\nexport function _resetForTests(): void {\n streamBuffers.clear();\n sessions.clear();\n globalSeq = 0;\n}\n","/**\n * Registry MCP backend for the Patchwork MCP App Server.\n *\n * Spawns (or connects to) the Aprovan Registry MCP server via stdio and bridges\n * its tools into the Patchwork ServiceBridge. Widgets can then call any Registry-\n * backed service (e.g. `github.repos_list()`, `stripe.charges_list()`) via the\n * standard Patchwork service proxy shim.\n *\n * Usage (in server.ts or programmatically):\n *\n * const backend = await createRegistryBackend({\n * command: \"npx\",\n * args: [\"@utdk/mcp-server\"],\n * providers: \"github,slack,stripe\",\n * });\n *\n * const bridge = new ServiceBridge({\n * backend,\n * tools: backend.getToolInfos(),\n * });\n */\n\nimport { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport { StdioClientTransport } from \"@modelcontextprotocol/sdk/client/stdio.js\";\nimport type { ServiceBackend, ServiceToolInfo } from \"./services.js\";\n\n// ---------------------------------------------------------------------------\n// Minimal type alias for MCP callTool responses\n// ---------------------------------------------------------------------------\n\n/** Subset of the MCP CallToolResult used in this module. */\ninterface McpToolResult {\n isError?: boolean;\n content: Array<{ type: string; text?: string; [k: string]: unknown }>;\n}\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\nexport interface RegistryBackendOptions {\n /** Executable to start the Registry MCP server (e.g. \"npx\" or an absolute path). */\n command: string;\n /** Arguments passed to the command (e.g. [\"@utdk/mcp-server\"]). */\n args?: string[];\n /**\n * Additional environment variables for the Registry process.\n * Merged on top of the current process.env, so provider credentials already\n * present in the environment are forwarded automatically.\n */\n env?: Record<string, string>;\n /**\n * Comma-separated list of `@utdk` providers to load, e.g. \"github,slack,stripe\".\n * Passed as UTDK_PROVIDERS to the Registry server.\n */\n providers: string;\n}\n\nexport interface RegistryBackend extends ServiceBackend {\n /** All service tool infos loaded from the Registry, ready for ServiceBridgeConfig. */\n getToolInfos(): ServiceToolInfo[];\n /** Close the underlying MCP client / Registry child process. */\n close(): Promise<void>;\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Split a Registry MCP tool name (e.g. \"github__repos_list\") into its\n * namespace (\"github\") and procedure (\"repos_list\") parts.\n *\n * The Registry convention is:\n * <provider>__<operation> (double underscore as separator)\n *\n * Everything before the first \"__\" is the namespace; the remainder is the\n * procedure (which may itself contain single underscores).\n */\nexport function parseRegistryToolName(mcpName: string): {\n namespace: string;\n procedure: string;\n} {\n const idx = mcpName.indexOf(\"__\");\n if (idx === -1) {\n // No separator — treat the whole name as a single-part namespace\n return { namespace: mcpName, procedure: \"call\" };\n }\n return {\n namespace: mcpName.slice(0, idx),\n procedure: mcpName.slice(idx + 2),\n };\n}\n\n/**\n * Fetch all tool metadata from the Registry via its `list_tools` / `tool_info`\n * meta-tools and convert to the `ServiceToolInfo` shape expected by ServiceBridge.\n */\nasync function loadRegistryToolInfos(client: Client): Promise<ServiceToolInfo[]> {\n // list_tools returns a flat JSON array of MCP tool names when no group_by is given.\n const listResult = (await client.callTool({\n name: \"list_tools\",\n arguments: {},\n })) as McpToolResult;\n\n const listText = listResult.content\n .filter((c): c is { type: \"text\"; text: string } => c.type === \"text\")\n .map((c) => c.text)\n .join(\"\");\n\n let toolNames: unknown;\n try {\n toolNames = JSON.parse(listText);\n } catch {\n console.warn(\"[registry-backend] Failed to parse tool list response from Registry\");\n return [];\n }\n\n if (!Array.isArray(toolNames)) {\n console.warn(\"[registry-backend] Unexpected tool list format from Registry (expected array)\");\n return [];\n }\n\n const mcpNames = toolNames.filter((n): n is string => typeof n === \"string\");\n\n // Fetch full schema for each tool in parallel, chunked to avoid flooding the process.\n const CHUNK_SIZE = 20;\n const toolInfos: ServiceToolInfo[] = [];\n\n for (let i = 0; i < mcpNames.length; i += CHUNK_SIZE) {\n const chunk = mcpNames.slice(i, i + CHUNK_SIZE);\n const results = await Promise.all(\n chunk.map(async (mcpName): Promise<ServiceToolInfo | null> => {\n try {\n const infoResult = (await client.callTool({\n name: \"tool_info\",\n arguments: { tool_name: mcpName },\n })) as McpToolResult;\n\n const infoText = infoResult.content\n .filter((c): c is { type: \"text\"; text: string } => c.type === \"text\")\n .map((c) => c.text)\n .join(\"\");\n\n const raw = JSON.parse(infoText) as {\n name?: string;\n description?: string;\n inputSchema?: Record<string, unknown>;\n };\n\n const { namespace, procedure } = parseRegistryToolName(mcpName);\n\n return {\n name: `${namespace}.${procedure}`,\n namespace,\n procedure,\n description: raw.description ?? `Call ${namespace}.${procedure}`,\n parameters: raw.inputSchema as Record<string, unknown> | undefined,\n };\n } catch (err) {\n console.warn(`[registry-backend] Failed to fetch tool info for '${mcpName}': ${err}`);\n return null;\n }\n }),\n );\n\n for (const info of results) {\n if (info !== null) {\n toolInfos.push(info);\n }\n }\n }\n\n const providerCount = new Set(toolInfos.map((t) => t.namespace)).size;\n console.log(\n `[registry-backend] Loaded ${toolInfos.length} tools from ${providerCount} provider(s)`,\n );\n\n return toolInfos;\n}\n\n// ---------------------------------------------------------------------------\n// Public factory\n// ---------------------------------------------------------------------------\n\n/**\n * Create a `RegistryBackend` that routes widget service calls through the\n * Aprovan Registry MCP server.\n *\n * The Registry server is spawned as a child process via stdio. All current\n * `process.env` variables (including provider credentials such as `GITHUB_TOKEN`)\n * are forwarded to the child process, with `UTDK_PROVIDERS` set to the given\n * providers list. Additional overrides can be passed via `options.env`.\n *\n * @example\n * ```ts\n * const backend = await createRegistryBackend({\n * command: \"npx\",\n * args: [\"@utdk/mcp-server\"],\n * providers: \"github,stripe\",\n * });\n *\n * const bridge = new ServiceBridge({\n * backend,\n * tools: backend.getToolInfos(),\n * });\n * ```\n */\nexport async function createRegistryBackend(\n options: RegistryBackendOptions,\n): Promise<RegistryBackend> {\n // Forward the current environment so provider API keys are available in the\n // spawned process. UTDK_PROVIDERS is the only variable that must be set here.\n const inheritedEnv: Record<string, string> = {};\n for (const [k, v] of Object.entries(process.env)) {\n if (v !== undefined) inheritedEnv[k] = v;\n }\n\n const env: Record<string, string> = {\n ...inheritedEnv,\n UTDK_PROVIDERS: options.providers,\n ...options.env,\n };\n\n const transport = new StdioClientTransport({\n command: options.command,\n args: options.args ?? [],\n env,\n // Route Registry stderr to the parent so operators can see provider logs.\n stderr: \"inherit\",\n });\n\n const client = new Client({\n name: \"patchwork-mcp-app-server\",\n version: \"0.1.0\",\n });\n\n await client.connect(transport);\n\n // Pre-load all tool metadata so ServiceBridge can be constructed synchronously.\n const toolInfos = await loadRegistryToolInfos(client);\n\n const backend: RegistryBackend = {\n /**\n * Execute a Registry-backed service tool.\n *\n * The call is forwarded as a Registry `call_tool` meta-tool invocation:\n * namespace + \"__\" + procedure → Registry MCP tool name\n *\n * The first element of `args` is treated as the tool's argument object.\n */\n async call(\n namespace: string,\n procedure: string,\n args: unknown[],\n ): Promise<unknown> {\n const toolName = `${namespace}__${procedure}`;\n const toolArgs = (typeof args[0] === \"object\" && args[0] !== null ? args[0] : {}) as Record<\n string,\n unknown\n >;\n\n const result = (await client.callTool({\n name: \"call_tool\",\n arguments: {\n tool_name: toolName,\n arguments: toolArgs,\n },\n })) as McpToolResult;\n\n if (result.isError) {\n const message = result.content\n .filter((c): c is { type: \"text\"; text: string } => c.type === \"text\")\n .map((c) => c.text)\n .join(\"\\n\");\n throw new Error(message || `Registry call failed for '${toolName}'`);\n }\n\n // Return parsed JSON when the response looks like JSON, else return raw text.\n const textContent = result.content.find(\n (c): c is { type: \"text\"; text: string } => c.type === \"text\",\n );\n if (textContent) {\n try {\n return JSON.parse(textContent.text) as unknown;\n } catch {\n return textContent.text;\n }\n }\n\n return result;\n },\n\n getToolInfos(): ServiceToolInfo[] {\n return toolInfos;\n },\n\n async close(): Promise<void> {\n await client.close();\n },\n };\n\n return backend;\n}\n","import {\n createProjectFromFiles,\n type Manifest,\n type VirtualFile,\n type VirtualProject,\n} from \"@aprovan/patchwork-compiler\";\nimport {\n registerAppTool,\n registerAppResource,\n RESOURCE_MIME_TYPE,\n} from \"@modelcontextprotocol/ext-apps/server\";\nimport { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { z } from \"zod\";\nimport {\n compileWidget,\n allEntries,\n type CompileWidgetResult,\n} from \"./compiler/index.js\";\nimport HELLO_WORLD_HTML from \"./hello-world.html\";\nimport {\n subscribeSession,\n unsubscribeSession as _unsubscribeSession,\n getEvents,\n pushStreamUpdate,\n currentSeq,\n type StreamEvent,\n} from \"./live-update.js\";\nimport {\n ServiceBridge,\n type ServiceBackend,\n type ServiceToolInfo,\n type ServiceBridgeConfig,\n} from \"./services.js\";\nimport {\n type WidgetStore,\n getWidgetStore,\n} from \"./widget-store/index.js\";\n\nexport type { ServiceBackend, ServiceToolInfo, ServiceBridgeConfig };\nexport type { StreamEvent };\nexport { pushStreamUpdate };\n\nconst HELLO_WORLD_RESOURCE_URI = \"ui://hello-world/view.html\";\n\nconst MANIFEST_DEFAULTS: Manifest = {\n name: \"widget\",\n version: \"0.1.0\",\n platform: \"browser\",\n image: \"@aprovan/patchwork-image-shadcn\",\n};\n\nfunction buildManifest(input?: Record<string, unknown>): Manifest {\n return {\n name: (input?.[\"name\"] as string) ?? MANIFEST_DEFAULTS.name,\n version: (input?.[\"version\"] as string) ?? MANIFEST_DEFAULTS.version,\n platform: \"browser\",\n image:\n (input?.[\"image\"] as string) ?? MANIFEST_DEFAULTS.image,\n services: (input?.[\"services\"] as string[] | undefined),\n };\n}\n\nfunction buildVirtualProject(\n source?: string,\n files?: Array<{ path: string; content: string }>,\n entry?: string,\n): string | VirtualProject {\n if (files && files.length > 0) {\n const virtualFiles: VirtualFile[] = files.map((f) => ({\n path: f.path,\n content: f.content,\n }));\n return createProjectFromFiles(virtualFiles, entry);\n }\n return source ?? 'export default function Widget() { return <div>Hello Patchwork</div>; }';\n}\n\nasync function registerStoredWidgetResources(\n server: McpServer,\n store: WidgetStore,\n): Promise<void> {\n const widgets = await store.loadAll();\n for (const widget of widgets) {\n registerAppResource(\n server,\n `Widget ${widget.manifest.name}`,\n widget.resourceUri,\n {\n description: widget.manifest.description ?? `Persisted widget: ${widget.manifest.name}`,\n },\n async () => ({\n contents: [\n {\n uri: widget.resourceUri,\n mimeType: RESOURCE_MIME_TYPE,\n text: widget.html,\n },\n ],\n }),\n );\n }\n}\n\nfunction registerCachedWidgetResources(server: McpServer): void {\n for (const [, entry] of allEntries()) {\n registerAppResource(\n server,\n `Widget ${entry.manifest.name}`,\n entry.resourceUri,\n {\n description: entry.manifest.description ?? `Compiled widget: ${entry.manifest.name}`,\n },\n async () => ({\n contents: [\n {\n uri: entry.resourceUri,\n mimeType: RESOURCE_MIME_TYPE,\n text: entry.html,\n },\n ],\n }),\n );\n }\n}\n\nexport interface McpAppServerOptions {\n services?: ServiceBridgeConfig;\n}\n\nexport function createMcpAppServer(options: McpAppServerOptions = {}): McpServer {\n const server = new McpServer({\n name: \"patchwork-mcp-app-server\",\n version: \"0.1.0\",\n });\n\n const serviceBridge = options.services\n ? new ServiceBridge(options.services)\n : null;\n\n const store = getWidgetStore();\n\n registerAppTool(\n server,\n \"hello_world\",\n {\n description:\n \"Display a hello-world widget inline in the conversation. \" +\n \"Returns a static greeting card rendered as an MCP App.\",\n _meta: { ui: { resourceUri: HELLO_WORLD_RESOURCE_URI } },\n },\n async () => ({\n content: [\n {\n type: \"text\" as const,\n text: \"Hello, world! The widget is rendered inline above.\",\n },\n ],\n }),\n );\n\n registerAppResource(\n server,\n \"Hello World View\",\n HELLO_WORLD_RESOURCE_URI,\n {\n description:\n \"Hello-world HTML widget for the Patchwork MCP App Server demo.\",\n },\n async () => ({\n contents: [\n {\n uri: HELLO_WORLD_RESOURCE_URI,\n mimeType: RESOURCE_MIME_TYPE,\n text: HELLO_WORLD_HTML,\n },\n ],\n }),\n );\n\n registerAppTool(\n server,\n \"compile_widget\",\n {\n description:\n \"Compile a JSX/TSX widget into a self-contained HTML page served as an MCP App resource. \" +\n \"Pass source code for a single-file widget, or a files array for a multi-file project. \" +\n \"The compiled widget is cached in memory and persisted to the VFS widget store.\",\n inputSchema: {\n source: z\n .string()\n .optional()\n .describe(\n \"JSX/TSX source code for a single-file widget. Must export a default React component.\",\n ),\n files: z\n .array(\n z.object({\n path: z.string().describe(\"File path relative to project root (e.g. 'main.tsx')\"),\n content: z.string().describe(\"File contents\"),\n }),\n )\n .optional()\n .describe(\n \"Array of files for a multi-file widget project. At least one file should be the entry point (main.tsx or index.tsx).\",\n ),\n entry: z\n .string()\n .optional()\n .describe(\"Entry point file path. Defaults to auto-detection (main.tsx, index.tsx).\"),\n name: z\n .string()\n .optional()\n .describe(\"Widget name for the manifest. Defaults to 'widget'.\"),\n image: z\n .string()\n .optional()\n .describe(\n \"Patchwork image package to use. Defaults to '@aprovan/patchwork-image-shadcn'.\",\n ),\n services: z\n .array(z.string())\n .optional()\n .describe(\n \"Service namespaces the widget calls (e.g., ['weather', 'stripe']). \" +\n \"A proxy shim is injected so widget code can call namespace.procedure(args) directly.\",\n ),\n },\n _meta: {\n ui: { resourceUri: \"ui://widget/{hash}/view.html\" },\n },\n },\n async (args) => {\n const source = args?.[\"source\"] as string | undefined;\n const files = args?.[\"files\"] as\n | Array<{ path: string; content: string }>\n | undefined;\n const entry = args?.[\"entry\"] as string | undefined;\n const requestedServices = args?.[\"services\"] as string[] | undefined;\n\n const manifestInput: Record<string, unknown> = {};\n if (args?.[\"name\"]) manifestInput[\"name\"] = args[\"name\"];\n if (args?.[\"image\"]) manifestInput[\"image\"] = args[\"image\"];\n if (requestedServices) manifestInput[\"services\"] = requestedServices;\n\n const manifest = buildManifest(manifestInput);\n const project = buildVirtualProject(source, files, entry);\n\n let compileServices: string[] | undefined;\n if (requestedServices && requestedServices.length > 0 && serviceBridge) {\n const availableNamespaces = serviceBridge.getNamespaces();\n compileServices = requestedServices.filter((ns) =>\n availableNamespaces.includes(ns),\n );\n const unavailable = requestedServices.filter(\n (ns) => !availableNamespaces.includes(ns),\n );\n if (unavailable.length > 0) {\n console.warn(\n `[mcp-app-server] Requested services not available: ${unavailable.join(\", \")}. Available: ${availableNamespaces.join(\", \")}`,\n );\n }\n }\n\n try {\n const result: CompileWidgetResult = await compileWidget(\n project,\n manifest,\n compileServices ? { services: compileServices } : {},\n );\n\n const entryPath = typeof project === \"string\" ? undefined : project.entry;\n await store.saveWidget(result.hash, result.html, manifest, entryPath);\n\n const storedUri = store.resourceUriFor(manifest.name, result.hash);\n registerAppResource(\n server,\n `Widget ${manifest.name}`,\n storedUri,\n {\n description: manifest.description ?? `Persisted widget: ${manifest.name}`,\n },\n async () => ({\n contents: [\n {\n uri: storedUri,\n mimeType: RESOURCE_MIME_TYPE,\n text: result.html,\n },\n ],\n }),\n );\n\n registerAppResource(\n server,\n `Widget ${manifest.name} (cached)`,\n result.resourceUri,\n {\n description: `Compiled widget: ${manifest.name}`,\n },\n async () => ({\n contents: [\n {\n uri: result.resourceUri,\n mimeType: RESOURCE_MIME_TYPE,\n text: result.html,\n },\n ],\n }),\n );\n\n return {\n content: [\n {\n type: \"text\" as const,\n text: `Widget compiled and persisted. Resource URI: ${storedUri}`,\n },\n {\n type: \"text\" as const,\n text: `Cache key: ${result.hash}`,\n },\n ],\n _meta: {\n ui: { resourceUri: result.resourceUri },\n },\n };\n } catch (error) {\n const message =\n error instanceof Error ? error.message : String(error);\n return {\n content: [\n {\n type: \"text\" as const,\n text: `Compilation failed: ${message}`,\n },\n ],\n isError: true,\n };\n }\n },\n );\n\n if (serviceBridge) {\n serviceBridge.registerTools(server);\n serviceBridge.registerSearchServices(server);\n }\n\n registerAppTool(\n server,\n \"list_widgets\",\n {\n description:\n \"List all persisted widgets in the VFS widget store. \" +\n \"Returns each widget's name, version, description, path, and resource URI.\",\n _meta: { ui: { resourceUri: \"ui://widgets/list\" } },\n },\n async () => {\n const widgets = await store.listWidgets();\n\n if (widgets.length === 0) {\n return {\n content: [\n {\n type: \"text\" as const,\n text: \"No widgets stored in the VFS widget store.\",\n },\n ],\n };\n }\n\n const lines = widgets.map((w) => {\n const parts = [`- **${w.name}** (v${w.version})`];\n if (w.description) parts.push(` ${w.description}`);\n parts.push(` Path: ${w.path}`);\n parts.push(` URI: ${w.resourceUri}`);\n if (w.entry) parts.push(` Entry: ${w.entry}`);\n if (w.services && w.services.length > 0) parts.push(` Services: ${w.services.join(\", \")}`);\n return parts.join(\"\\n\");\n });\n\n return {\n content: [\n {\n type: \"text\" as const,\n text: `Stored widgets (${widgets.length}):\\n\\n${lines.join(\"\\n\\n\")}`,\n },\n ],\n };\n },\n );\n\n registerAppTool(\n server,\n \"render_widget\",\n {\n description:\n \"Render a persisted widget by its name and hash. \" +\n \"Serves the compiled widget as an MCP App resource rendered inline in the conversation.\",\n inputSchema: {\n name: z\n .string()\n .describe(\"Widget name (as stored in the VFS widget store).\"),\n hash: z\n .string()\n .optional()\n .describe(\"Widget content hash. If omitted, renders the most recent version of the named widget.\"),\n },\n _meta: {\n ui: { resourceUri: \"ui://widgets/{name}/{hash}/view.html\" },\n },\n },\n async (args) => {\n const name = args?.[\"name\"] as string;\n const hashInput = args?.[\"hash\"] as string | undefined;\n\n if (!name) {\n return {\n content: [\n {\n type: \"text\" as const,\n text: \"Widget name is required.\",\n },\n ],\n isError: true,\n };\n }\n\n let hash = hashInput;\n if (!hash) {\n const widgets = await store.listWidgets();\n const match = widgets.find((w) => w.name === name);\n if (!match) {\n return {\n content: [\n {\n type: \"text\" as const,\n text: `No stored widget found with name \"${name}\".`,\n },\n ],\n isError: true,\n };\n }\n const resourcePath = match.resourceUri.replace(\"ui://widgets/\", \"\").replace(\"/view.html\", \"\");\n const parts = resourcePath.split(\"/\");\n hash = parts[1] ?? parts[0] ?? \"\";\n }\n\n if (!hash) {\n return {\n content: [\n {\n type: \"text\" as const,\n text: `Could not determine hash for widget \"${name}\".`,\n },\n ],\n isError: true,\n };\n }\n\n const widget = await store.getWidget(name, hash);\n if (!widget) {\n return {\n content: [\n {\n type: \"text\" as const,\n text: `Widget \"${name}\" with hash \"${hash}\" not found in the VFS store.`,\n },\n ],\n isError: true,\n };\n }\n\n registerAppResource(\n server,\n `Widget ${widget.manifest.name}`,\n widget.resourceUri,\n {\n description: widget.manifest.description ?? `Persisted widget: ${widget.manifest.name}`,\n },\n async () => ({\n contents: [\n {\n uri: widget.resourceUri,\n mimeType: RESOURCE_MIME_TYPE,\n text: widget.html,\n },\n ],\n }),\n );\n\n return {\n content: [\n {\n type: \"text\" as const,\n text: `Rendering widget \"${name}\" (hash: ${hash}).`,\n },\n ],\n _meta: {\n ui: { resourceUri: widget.resourceUri },\n },\n };\n },\n );\n\n registerCachedWidgetResources(server);\n void registerStoredWidgetResources(server, store);\n registerLiveUpdateTools(server);\n\n return server;\n}\n\nexport { WidgetStore, getWidgetStore, resetWidgetStore } from \"./widget-store/index.js\";\nexport type { StoredWidget, StoredWidgetInfo, WidgetStoreOptions } from \"./widget-store/types.js\";\n\n// ---------------------------------------------------------------------------\n// Live-update tools\n// ---------------------------------------------------------------------------\n\n/**\n * Register the three tools that power the live-update channel:\n *\n * - `subscribe_stream` — widget declares interest in a named data stream.\n * - `poll_updates` — widget fetches buffered events since a given seq.\n * - `push_update` — backend/LLM pushes new data onto a stream.\n *\n * The session ID is extracted from the MCP request's `_meta` extra or from the\n * internal `RequestHandlerExtra`. Tools that need the session ID use the\n * `extra.meta?.sessionId` field that the SDK populates from the transport.\n */\nfunction registerLiveUpdateTools(server: McpServer): void {\n // subscribe_stream — widget registers interest in a named data stream.\n server.registerTool(\n \"subscribe_stream\",\n {\n description:\n \"Subscribe this widget session to a named data stream. \" +\n \"The server will send `notifications/tools/list_changed` whenever new \" +\n \"events arrive; the widget should then call `poll_updates` to fetch them. \" +\n \"Returns the current sequence number so the widget knows where to start polling.\",\n inputSchema: {\n stream: z.string().describe(\"Name of the data stream to subscribe to.\"),\n session_id: z\n .string()\n .optional()\n .describe(\n \"MCP session ID. Widgets should pass the value returned in the \" +\n \"Mcp-Session-Id response header during initialization.\",\n ),\n },\n },\n (args, extra) => {\n const stream = (args as Record<string, unknown>)[\"stream\"] as string;\n // Prefer an explicit session_id arg; fall back to the transport session\n const sessionId =\n ((args as Record<string, unknown>)[\"session_id\"] as string | undefined) ??\n (extra as Record<string, unknown>)[\"sessionId\"] as string | undefined;\n\n if (sessionId) {\n subscribeSession(sessionId, stream);\n }\n\n const seq = currentSeq();\n return {\n content: [\n {\n type: \"text\" as const,\n text: JSON.stringify({ success: true, stream, seq }),\n },\n ],\n };\n },\n );\n\n // poll_updates — returns buffered events for a stream since afterSeq.\n server.registerTool(\n \"poll_updates\",\n {\n description:\n \"Fetch buffered events for a data stream that arrived after the given \" +\n \"sequence number. Call this after receiving a `notifications/tools/list_changed` \" +\n \"notification (which the server sends when new data is available). \" +\n \"Pass the highest `seq` value from the last successful poll to avoid duplicates.\",\n inputSchema: {\n stream: z.string().describe(\"Name of the data stream to poll.\"),\n after_seq: z\n .number()\n .int()\n .default(0)\n .describe(\n \"Return only events with seq > after_seq. Pass 0 to retrieve all buffered events.\",\n ),\n },\n },\n (args) => {\n const stream = (args as Record<string, unknown>)[\"stream\"] as string;\n const afterSeq = ((args as Record<string, unknown>)[\"after_seq\"] as number) ?? 0;\n\n const events = getEvents(stream, afterSeq);\n return {\n content: [\n {\n type: \"text\" as const,\n text: JSON.stringify({ success: true, stream, events }),\n },\n ],\n };\n },\n );\n\n // push_update — backend or LLM pushes new data onto a stream.\n server.registerTool(\n \"push_update\",\n {\n description:\n \"Push a data update onto a named stream, broadcasting it to all \" +\n \"subscribed widget sessions. Subscribing widgets will receive a \" +\n \"`notifications/tools/list_changed` signal and then call `poll_updates` \" +\n \"to retrieve the new data. Use this tool from server-side code or as an \" +\n \"LLM tool to drive real-time widget updates.\",\n inputSchema: {\n stream: z.string().describe(\"Name of the data stream to push to.\"),\n data: z\n .record(z.unknown())\n .describe(\"Arbitrary JSON-serialisable payload to push to subscribers.\"),\n },\n },\n async (args) => {\n const stream = (args as Record<string, unknown>)[\"stream\"] as string;\n const data = (args as Record<string, unknown>)[\"data\"];\n\n const seq = await pushStreamUpdate(stream, data);\n return {\n content: [\n {\n type: \"text\" as const,\n text: JSON.stringify({ success: true, stream, seq }),\n },\n ],\n };\n },\n );\n}\n","import { randomUUID } from \"node:crypto\";\nimport { mkdir, writeFile, rm, readFile } from \"node:fs/promises\";\nimport { join, resolve, dirname } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport {\n createCompiler,\n loadImage,\n type Manifest,\n type VirtualProject,\n type ImageConfig,\n type LoadedImage,\n} from \"@aprovan/patchwork-compiler\";\nimport react from \"@vitejs/plugin-react\";\nimport { build, type InlineConfig } from \"vite\";\nimport { viteSingleFile } from \"vite-plugin-singlefile\";\nimport { generateServiceShim, generateLiveUpdateShim } from \"../shim.js\";\nimport {\n computeCacheKey,\n set as cacheSet,\n has as cacheHas,\n get as cacheGet,\n type CachedWidget,\n} from \"./cache.js\";\nimport { patchworkCdnPlugin, getPreloadScripts } from \"./cdn-plugin.js\";\n\nconst WIDGET_RESOURCE_PREFIX = \"ui://widget/\";\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\nconst COMPILE_TMP_DIR = resolve(__dirname, \"..\", \".compile-tmp\");\n\nconst FALLBACK_IMAGE_CONFIG: ImageConfig = {\n platform: \"browser\",\n esbuild: { target: \"es2020\", format: \"esm\", jsx: \"automatic\" },\n framework: {\n globals: { react: \"React\", \"react-dom\": \"ReactDOM\" },\n preload: [\n \"https://esm.sh/react@18\",\n \"https://esm.sh/react-dom@18/client\",\n ],\n deps: { react: \"18\", \"react-dom\": \"18\" },\n },\n};\n\nlet loadedImage: LoadedImage | null = null;\nlet imageLoadPromise: Promise<LoadedImage> | null = null;\n\nasync function getImageConfig(): Promise<ImageConfig> {\n if (loadedImage) return loadedImage.config;\n if (imageLoadPromise) return imageLoadPromise.then((img) => img.config);\n\n imageLoadPromise = (async () => {\n try {\n loadedImage = await loadImage(\"@aprovan/patchwork-image-shadcn\");\n return loadedImage;\n } catch {\n const compiler = await createCompiler({\n image: \"@aprovan/patchwork-image-shadcn\",\n proxyUrl: \"\",\n });\n await compiler.preloadImage(\"@aprovan/patchwork-image-shadcn\");\n const registry = (compiler as unknown as Record<string, unknown>)\n .registry as Map<string, LoadedImage> | undefined;\n const img = registry?.get(\"@aprovan/patchwork-image-shadcn\");\n if (img) {\n loadedImage = img;\n return img;\n }\n return { config: FALLBACK_IMAGE_CONFIG } as LoadedImage;\n }\n })();\n\n return imageLoadPromise.then((img) => img.config);\n}\n\nfunction generateHtmlEntry(preloads: string[], cssVars: string): string {\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n ${preloads.join(\"\\n \")}\n <script src=\"https://cdn.tailwindcss.com\"></script>\n <script>\n window.tailwind = window.tailwind || {};\n window.tailwind.config = {\n darkMode: \"class\",\n theme: {\n extend: {\n colors: {\n border: \"hsl(var(--border))\",\n input: \"hsl(var(--input))\",\n ring: \"hsl(var(--ring))\",\n background: \"hsl(var(--background))\",\n foreground: \"hsl(var(--foreground))\",\n primary: { DEFAULT: \"hsl(var(--primary))\", foreground: \"hsl(var(--primary-foreground))\" },\n secondary: { DEFAULT: \"hsl(var(--secondary))\", foreground: \"hsl(var(--secondary-foreground))\" },\n destructive: { DEFAULT: \"hsl(var(--destructive))\", foreground: \"hsl(var(--destructive-foreground))\" },\n muted: { DEFAULT: \"hsl(var(--muted))\", foreground: \"hsl(var(--muted-foreground))\" },\n accent: { DEFAULT: \"hsl(var(--accent))\", foreground: \"hsl(var(--accent-foreground))\" },\n popover: { DEFAULT: \"hsl(var(--popover))\", foreground: \"hsl(var(--popover-foreground))\" },\n card: { DEFAULT: \"hsl(var(--card))\", foreground: \"hsl(var(--card-foreground))\" },\n },\n borderRadius: { lg: \"var(--radius)\", md: \"calc(var(--radius) - 2px)\", sm: \"calc(var(--radius) - 4px)\" },\n },\n },\n };\n </script>\n <style>\n :root {\n ${cssVars}\n }\n </style>\n</head>\n<body>\n <div id=\"root\"></div>\n <script type=\"module\" src=\"/src/_app.tsx\"></script>\n</body>\n</html>`;\n}\n\nconst SHADCN_CSS_VARS = `\n --background: 0 0% 100%;\n --foreground: 222.2 84% 4.9%;\n --card: 0 0% 100%;\n --card-foreground: 222.2 84% 4.9%;\n --popover: 0 0% 100%;\n --popover-foreground: 222.2 84% 4.9%;\n --primary: 222.2 47.4% 11.2%;\n --primary-foreground: 210 40% 98%;\n --secondary: 210 40% 96.1%;\n --secondary-foreground: 222.2 47.4% 11.2%;\n --muted: 210 40% 96.1%;\n --muted-foreground: 215.4 16.3% 46.9%;\n --accent: 210 40% 96.1%;\n --accent-foreground: 222.2 47.4% 11.2%;\n --destructive: 0 84.2% 60.2%;\n --destructive-foreground: 210 40% 98%;\n --border: 214.3 31.8% 91.4%;\n --input: 214.3 31.8% 91.4%;\n --ring: 222.2 84% 4.9%;\n --radius: 0.5rem;\n`.trim();\n\nfunction generateMainTsx(entryModule: string): string {\n return `import React from 'react';\nimport { createRoot } from 'react-dom/client';\nimport Widget from './${entryModule}';\n\nconst rootEl = document.getElementById('root');\nif (rootEl) {\n const root = createRoot(rootEl);\n root.render(React.createElement(Widget));\n}\n`;\n}\n\nasync function writeProjectFiles(\n projectDir: string,\n source: string | VirtualProject,\n): Promise<string> {\n const srcDir = join(projectDir, \"src\");\n await mkdir(srcDir, { recursive: true });\n\n if (typeof source === \"string\") {\n await writeFile(join(srcDir, \"widget.tsx\"), source, \"utf-8\");\n return \"widget\";\n }\n\n for (const [filePath, file] of source.files) {\n const fullPath = filePath.startsWith(\"src/\")\n ? join(projectDir, filePath)\n : join(srcDir, filePath);\n const dir = resolve(fullPath, \"..\");\n await mkdir(dir, { recursive: true });\n await writeFile(fullPath, file.content, \"utf-8\");\n }\n\n const entry = source.entry;\n const entryName = entry.replace(/\\.(tsx|ts|jsx|js)$/, \"\");\n return entryName.startsWith(\"src/\")\n ? entryName.slice(4)\n : entryName;\n}\n\nfunction injectShimIntoHtml(html: string, shimScript: string): string {\n const shimTag = `<script type=\"module\">\\n${shimScript}\\n</script>`;\n const bodyCloseIndex = html.lastIndexOf(\"</body>\");\n if (bodyCloseIndex === -1) return html;\n return html.slice(0, bodyCloseIndex) + shimTag + \"\\n\" + html.slice(bodyCloseIndex);\n}\n\nexport interface CompileWidgetResult {\n html: string;\n hash: string;\n resourceUri: string;\n}\n\nexport interface CompileWidgetOptions {\n services?: string[];\n /** Inject the live-update shim (window.patchwork) into the compiled widget. Defaults to true. */\n liveUpdates?: boolean;\n}\n\nexport async function compileWidget(\n source: string | VirtualProject,\n manifest: Manifest,\n options: CompileWidgetOptions = {},\n): Promise<CompileWidgetResult> {\n const liveUpdates = options.liveUpdates ?? true;\n\n const baseCacheKey = computeCacheKey(source, manifest);\n let cacheKey = baseCacheKey;\n if (options.services?.length) {\n cacheKey += `:svc:${options.services.sort().join(\",\")}`;\n }\n if (liveUpdates) {\n cacheKey += `:live`;\n }\n if (cacheHas(cacheKey)) {\n const cached = cacheGet(cacheKey)!;\n return {\n html: cached.html,\n hash: cacheKey,\n resourceUri: cached.resourceUri,\n };\n }\n\n const imageConfig = await getImageConfig();\n\n await mkdir(COMPILE_TMP_DIR, { recursive: true });\n const projectDir = join(COMPILE_TMP_DIR, `build-${randomUUID()}`);\n try {\n await mkdir(projectDir, { recursive: true });\n\n const entryModule = await writeProjectFiles(projectDir, source);\n await writeFile(\n join(projectDir, \"src\", \"_app.tsx\"),\n generateMainTsx(entryModule),\n \"utf-8\",\n );\n\n const preloads = getPreloadScripts(imageConfig);\n const htmlContent = generateHtmlEntry(preloads, SHADCN_CSS_VARS);\n await writeFile(join(projectDir, \"index.html\"), htmlContent, \"utf-8\");\n\n const viteConfig: InlineConfig = {\n root: projectDir,\n base: \"./\",\n plugins: [\n patchworkCdnPlugin({ imageConfig }),\n react({\n jsxRuntime: \"classic\",\n }),\n viteSingleFile({ useRecommendedBuildConfig: true }),\n ],\n build: {\n outDir: \"dist\",\n emptyOutDir: true,\n minify: false,\n rollupOptions: {\n input: resolve(projectDir, \"index.html\"),\n },\n },\n logLevel: \"silent\",\n };\n\n await build(viteConfig);\n\n const outputHtml = await readFile(\n join(projectDir, \"dist\", \"index.html\"),\n \"utf-8\",\n );\n\n // Build up the shim scripts. The live-update shim is injected first so\n // __patchwork_app is available when the service shim runs. If both are\n // present the live-update shim guards against double-initialisation.\n const shimParts: string[] = [];\n if (liveUpdates) {\n shimParts.push(generateLiveUpdateShim());\n }\n if (options.services?.length) {\n shimParts.push(generateServiceShim({ namespaces: options.services }));\n }\n\n let finalHtml = outputHtml;\n for (const script of shimParts) {\n finalHtml = injectShimIntoHtml(finalHtml, script);\n }\n\n const resourceUri = `${WIDGET_RESOURCE_PREFIX}${cacheKey}/view.html`;\n\n const cacheEntry: CachedWidget = {\n html: finalHtml,\n manifest,\n resourceUri,\n createdAt: Date.now(),\n };\n cacheSet(cacheKey, cacheEntry);\n\n return { html: finalHtml, hash: cacheKey, resourceUri };\n } finally {\n await rm(projectDir, { recursive: true, force: true });\n }\n}\n\nexport { cacheGet, cacheHas, computeCacheKey };\n","export interface ShimOptions {\n namespaces: string[];\n extAppsVersion?: string;\n}\n\nexport interface LiveUpdateShimOptions {\n /** @default \"^1.7.3\" */\n extAppsVersion?: string;\n}\n\nconst DEFAULT_EXT_APPS_VERSION = \"^1.7.3\";\n\n/**\n * Generate the service-proxy shim that maps `namespace.procedure(args)` calls\n * to `app.callServerTool({ name: \"namespace__procedure\", arguments: args })`.\n *\n * Returns an empty string when `namespaces` is empty (nothing to proxy).\n */\nexport function generateServiceShim(options: ShimOptions): string {\n const {\n namespaces,\n extAppsVersion = DEFAULT_EXT_APPS_VERSION,\n } = options;\n\n if (namespaces.length === 0) return \"\";\n\n const namespacesJson = JSON.stringify(namespaces);\n const importUrl = `https://esm.sh/@modelcontextprotocol/ext-apps@${extAppsVersion}`;\n\n return `\nimport { App } from '${importUrl}';\n\nconst __patchwork_app = new App({ name: 'patchwork-widget', version: '0.1.0' });\nconst __patchwork_ready = __patchwork_app.connect().catch(function(err) {\n console.error('[patchwork] Failed to connect to host:', err);\n});\n\nfunction __patchwork_createNamespaceProxy(namespace) {\n return new Proxy({}, {\n get: function(target, prop) {\n if (typeof prop !== 'string') return undefined;\n return function __patchwork_serviceCall(args) {\n var toolName = namespace + '__' + prop;\n return __patchwork_ready.then(function() {\n return __patchwork_app.callServerTool({ name: toolName, arguments: args || {} });\n }).then(function(result) {\n if (result.isError) {\n var errorMsg = result.content && result.content[0] && result.content[0].text\n ? result.content[0].text\n : 'Service call failed: ' + namespace + '.' + prop;\n throw new Error(errorMsg);\n }\n var textContent = result.content && result.content.find(function(c) { return c.type === 'text'; });\n if (textContent) {\n try { return JSON.parse(textContent.text); }\n catch (e) { return textContent.text; }\n }\n return result;\n });\n };\n }\n });\n}\n\nvar __patchwork_namespaces = ${namespacesJson};\nfor (var __i = 0; __i < __patchwork_namespaces.length; __i++) {\n var __ns = __patchwork_namespaces[__i];\n window[__ns] = __patchwork_createNamespaceProxy(__ns);\n}\n`;\n}\n\n/**\n * Generate the live-update shim that wires up `window.patchwork`:\n *\n * - `patchwork.subscribe(stream, callback)` — subscribe to a named data\n * stream. Calls `subscribe_stream` on connect, then calls `callback(data)`\n * each time new events arrive via `poll_updates`.\n * - `patchwork.updateContext(content)` — push widget state back to the model\n * via `app.updateModelContext()`.\n * - `patchwork.fireEvent(toolName, args)` — convenience wrapper around\n * `app.callServerTool()` for user interaction events.\n *\n * The shim uses a single shared `App` instance (exposed as\n * `window.__patchwork_app`) so it composes correctly with the service shim.\n * If the service shim is also injected it must use the same variable name —\n * both shims are concatenated in the compiler, and the first one to run\n * initialises the App; the second one skips the initialisation guard.\n */\nexport function generateLiveUpdateShim(\n options: LiveUpdateShimOptions = {},\n): string {\n const { extAppsVersion = DEFAULT_EXT_APPS_VERSION } = options;\n const importUrl = `https://esm.sh/@modelcontextprotocol/ext-apps@${extAppsVersion}`;\n\n return `\nimport { App } from '${importUrl}';\n\n// Shared App instance — the service shim may have already created this.\nif (!window.__patchwork_app) {\n window.__patchwork_app = new App({ name: 'patchwork-widget', version: '0.1.0' });\n window.__patchwork_ready = window.__patchwork_app.connect().catch(function(err) {\n console.error('[patchwork] Failed to connect to host:', err);\n });\n}\n\n(function() {\n var __app = window.__patchwork_app;\n var __ready = window.__patchwork_ready;\n\n // Per-stream state: { seq, callbacks }\n var __streams = {};\n // MCP session ID captured after connect\n var __sessionId = null;\n\n function __parseResult(result) {\n if (!result || !result.content) return result;\n var textContent = result.content.find(function(c) { return c.type === 'text'; });\n if (textContent) {\n try { return JSON.parse(textContent.text); }\n catch (e) { return textContent.text; }\n }\n return result;\n }\n\n function __pollStream(stream) {\n var state = __streams[stream];\n if (!state) return;\n var afterSeq = state.seq;\n __app.callServerTool({ name: 'poll_updates', arguments: { stream: stream, after_seq: afterSeq } })\n .then(function(result) {\n var parsed = __parseResult(result);\n if (!parsed || !parsed.success || !parsed.events || !parsed.events.length) return;\n var events = parsed.events;\n for (var i = 0; i < events.length; i++) {\n var ev = events[i];\n if (ev.seq > state.seq) {\n state.seq = ev.seq;\n }\n for (var j = 0; j < state.callbacks.length; j++) {\n try { state.callbacks[j](ev.data, ev.seq, stream); }\n catch (cbErr) { console.error('[patchwork] subscribe callback error:', cbErr); }\n }\n }\n })\n .catch(function(err) {\n console.warn('[patchwork] poll_updates failed for stream ' + stream + ':', err);\n });\n }\n\n // When tools/list_changed fires, poll all subscribed streams.\n __app.setNotificationHandler(\n { method: 'notifications/tools/list_changed', params: {} },\n function() {\n var streamNames = Object.keys(__streams);\n for (var i = 0; i < streamNames.length; i++) {\n __pollStream(streamNames[i]);\n }\n return Promise.resolve();\n }\n );\n\n // Expose the public patchwork API on window.\n window.patchwork = {\n /**\n * Subscribe to a named data stream.\n *\n * @param {string} stream - Stream name (e.g. \"price_feed\").\n * @param {function} callback - Called with (data, seq, stream) for each event.\n * @returns {function} Unsubscribe function.\n */\n subscribe: function(stream, callback) {\n if (!__streams[stream]) {\n __streams[stream] = { seq: 0, callbacks: [] };\n // Tell the server about this subscription once the App is connected.\n __ready.then(function() {\n var args = { stream: stream };\n if (__sessionId) args.session_id = __sessionId;\n return __app.callServerTool({ name: 'subscribe_stream', arguments: args });\n }).then(function(result) {\n var parsed = __parseResult(result);\n if (parsed && typeof parsed.seq === 'number') {\n // Start polling from the server's current seq.\n if (__streams[stream]) {\n __streams[stream].seq = parsed.seq;\n }\n }\n }).catch(function(err) {\n console.warn('[patchwork] subscribe_stream failed:', err);\n });\n }\n __streams[stream].callbacks.push(callback);\n return function unsubscribe() {\n if (!__streams[stream]) return;\n var idx = __streams[stream].callbacks.indexOf(callback);\n if (idx !== -1) __streams[stream].callbacks.splice(idx, 1);\n };\n },\n\n /**\n * Push the widget's current state into the conversation context.\n * The model will include this in its next response.\n *\n * @param {string|Array} content - Text string or array of MCP ContentBlock objects.\n */\n updateContext: function(content) {\n return __ready.then(function() {\n var params;\n if (typeof content === 'string') {\n params = { content: [{ type: 'text', text: content }] };\n } else if (Array.isArray(content)) {\n params = { content: content };\n } else if (content && typeof content === 'object') {\n params = { structuredContent: content };\n } else {\n params = { content: [{ type: 'text', text: String(content) }] };\n }\n return __app.request({ method: 'ui/update-model-context', params: params }, {});\n });\n },\n\n /**\n * Fire a client event as an MCP tool call. Use this to surface user\n * interactions so the LLM can observe and react to them.\n *\n * @param {string} toolName - MCP tool name on the server.\n * @param {object} args - Tool arguments.\n */\n fireEvent: function(toolName, args) {\n return __ready.then(function() {\n return __app.callServerTool({ name: toolName, arguments: args || {} });\n }).then(function(result) {\n return __parseResult(result);\n });\n },\n };\n\n // Capture the session ID from the host context on connect.\n __ready.then(function() {\n // The host context is available via getHostContext(); we can't get the\n // session ID directly, but it's passed in subscribe_stream args from\n // user code. Expose a setter so widgets can supply it explicitly.\n window.patchwork._setSessionId = function(id) { __sessionId = id; };\n }).catch(function() {});\n})();\n`;\n}\n","import { createHash } from \"node:crypto\";\nimport type { Manifest, VirtualProject } from \"@aprovan/patchwork-compiler\";\n\nexport interface CachedWidget {\n html: string;\n manifest: Manifest;\n resourceUri: string;\n createdAt: number;\n}\n\nconst MAX_CACHE_SIZE = 256;\n\nconst cache = new Map<string, CachedWidget>();\n\nexport function computeCacheKey(\n source: string | VirtualProject,\n manifest: Manifest,\n): string {\n const parts: string[] = [];\n\n if (typeof source === \"string\") {\n parts.push(source);\n } else {\n const sortedFiles = Array.from(source.files.entries()).sort(([a], [b]) =>\n a.localeCompare(b),\n );\n for (const [path, file] of sortedFiles) {\n parts.push(`${path}::${file.content}`);\n }\n parts.push(`entry:${source.entry}`);\n }\n\n parts.push(JSON.stringify(manifest));\n\n return createHash(\"sha256\").update(parts.join(\"\\n\")).digest(\"hex\").slice(0, 16);\n}\n\nexport function get(hash: string): CachedWidget | undefined {\n return cache.get(hash);\n}\n\nexport function set(hash: string, entry: CachedWidget): void {\n if (cache.size >= MAX_CACHE_SIZE) {\n const entries = Array.from(cache.entries());\n entries.sort((a, b) => a[1].createdAt - b[1].createdAt);\n const toRemove = entries.slice(0, Math.floor(MAX_CACHE_SIZE / 4));\n for (const [key] of toRemove) {\n cache.delete(key);\n }\n }\n cache.set(hash, entry);\n}\n\nexport function has(hash: string): boolean {\n return cache.has(hash);\n}\n\nexport function clear(): void {\n cache.clear();\n}\n\nexport function size(): number {\n return cache.size;\n}\n\nexport function allEntries(): Array<[string, CachedWidget]> {\n return Array.from(cache.entries());\n}\n","import type { ImageConfig } from \"@aprovan/patchwork-compiler\";\nimport type { Plugin } from \"vite\";\n\nconst ESM_SH_BASE = \"https://esm.sh\";\n\nconst REACT_EXPORTS = [\n \"useState\",\n \"useEffect\",\n \"useCallback\",\n \"useMemo\",\n \"useRef\",\n \"useContext\",\n \"useReducer\",\n \"useLayoutEffect\",\n \"useId\",\n \"createContext\",\n \"createElement\",\n \"cloneElement\",\n \"createRef\",\n \"forwardRef\",\n \"lazy\",\n \"memo\",\n \"Fragment\",\n \"Suspense\",\n \"StrictMode\",\n \"Component\",\n \"PureComponent\",\n \"Children\",\n \"isValidElement\",\n];\n\nconst REACT_DOM_EXPORTS = [\n \"createPortal\",\n \"flushSync\",\n \"render\",\n \"hydrate\",\n \"unmountComponentAtNode\",\n];\n\nfunction toEsmShUrl(\n packageName: string,\n version?: string,\n subpath?: string,\n deps?: Record<string, string>,\n): string {\n let url: string = `${ESM_SH_BASE}/${packageName}`;\n if (version) url += `@${version}`;\n if (subpath) url += `/${subpath}`;\n if (deps && Object.keys(deps).length > 0) {\n const depsStr = Object.entries(deps)\n .map(([name, ver]) => `${name}@${ver}`)\n .join(\",\");\n url += `?deps=${depsStr}`;\n }\n return url;\n}\n\nfunction parseImportPath(importPath: string): {\n packageName: string;\n subpath?: string;\n} {\n if (importPath.startsWith(\"@\")) {\n const parts = importPath.split(\"/\");\n if (parts.length >= 2) {\n const packageName = `${parts[0]}/${parts[1]}`;\n const subpath = parts.slice(2).join(\"/\");\n return { packageName, subpath: subpath || undefined };\n }\n }\n const parts = importPath.split(\"/\");\n return { packageName: parts[0] ?? \"\", subpath: parts.slice(1).join(\"/\") || undefined };\n}\n\nfunction isBareImport(path: string): boolean {\n return !(\n path.startsWith(\".\") ||\n path.startsWith(\"/\") ||\n path.startsWith(\"http://\") ||\n path.startsWith(\"https://\")\n );\n}\n\nfunction matchAlias(\n importPath: string,\n aliases: Record<string, string>,\n): string | null {\n for (const [pattern, target] of Object.entries(aliases)) {\n if (pattern.endsWith(\"/*\")) {\n const prefix = pattern.slice(0, -2);\n if (importPath === prefix || importPath.startsWith(prefix + \"/\")) {\n return target;\n }\n }\n if (importPath === pattern) {\n return target;\n }\n }\n return null;\n}\n\nexport interface CdnPluginOptions {\n imageConfig: ImageConfig;\n packages?: Record<string, string>;\n}\n\nexport function patchworkCdnPlugin(options: CdnPluginOptions): Plugin {\n const { imageConfig } = options;\n const globals = imageConfig.framework?.globals ?? {};\n const deps = imageConfig.framework?.deps ?? {};\n const aliases = imageConfig.aliases ?? {};\n const packages = options.packages ?? {};\n const globalsSet = new Set(Object.keys(globals));\n\n return {\n name: \"patchwork-cdn\",\n enforce: \"pre\",\n\n resolveId(source) {\n if (!isBareImport(source)) return null;\n\n const aliasTarget = matchAlias(source, aliases);\n if (aliasTarget) {\n const { packageName, subpath } = parseImportPath(aliasTarget);\n\n if (globalsSet.has(packageName)) {\n return `${packageName}${subpath ? `/${subpath}` : \"\"}`;\n }\n\n const version = packages[packageName];\n const url = toEsmShUrl(\n packageName,\n version,\n subpath,\n Object.keys(deps).length > 0 ? deps : undefined,\n );\n return { id: url, external: true };\n }\n\n const { packageName, subpath } = parseImportPath(source);\n\n if (globalsSet.has(packageName)) {\n return source;\n }\n\n const version = packages[packageName];\n const url = toEsmShUrl(\n packageName,\n version,\n subpath,\n Object.keys(deps).length > 0 ? deps : undefined,\n );\n return { id: url, external: true };\n },\n\n load(id) {\n const { packageName, subpath } = parseImportPath(id);\n const globalName = globals[packageName];\n if (!globalName) return null;\n\n if (subpath) {\n const url = toEsmShUrl(\n packageName,\n packages[packageName],\n subpath,\n Object.keys(deps).length > 0 ? deps : undefined,\n );\n return `export * from '${url}'; export { default } from '${url}';`;\n }\n\n const commonExports =\n packageName === \"react\"\n ? REACT_EXPORTS\n : packageName === \"react-dom\"\n ? REACT_DOM_EXPORTS\n : [];\n\n const lines = [\n `const mod = window.${globalName};`,\n `export default mod;`,\n ];\n\n if (commonExports.length > 0) {\n lines.push(\n `const { ${commonExports.join(\", \")} } = mod;`,\n `export { ${commonExports.join(\", \")} };`,\n );\n }\n\n return lines.join(\"\\n\");\n },\n };\n}\n\nexport function getPreloadScripts(imageConfig: ImageConfig): string[] {\n const preload = imageConfig.framework?.preload ?? [];\n return preload.map((url) => `<script src=\"${url}\"></script>`);\n}\n","<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n <title>Hello World — Patchwork MCP App</title>\n <style>\n *,\n *::before,\n *::after {\n box-sizing: border-box;\n }\n\n body {\n margin: 0;\n font-family: system-ui, -apple-system, sans-serif;\n background: #f0f4ff;\n display: flex;\n align-items: center;\n justify-content: center;\n min-height: 100vh;\n }\n\n .card {\n background: #fff;\n border-radius: 1rem;\n box-shadow: 0 4px 24px rgba(0, 0, 0, 0.08);\n padding: 2.5rem 3rem;\n text-align: center;\n max-width: 420px;\n width: 100%;\n }\n\n .wave {\n font-size: 3rem;\n line-height: 1;\n margin-bottom: 1rem;\n }\n\n h1 {\n margin: 0 0 0.75rem;\n font-size: 1.75rem;\n color: #111827;\n }\n\n p {\n margin: 0;\n color: #6b7280;\n font-size: 0.95rem;\n line-height: 1.6;\n }\n\n .badge {\n display: inline-block;\n margin-top: 1.5rem;\n background: #eff6ff;\n color: #3b82f6;\n font-size: 0.75rem;\n font-weight: 600;\n letter-spacing: 0.05em;\n text-transform: uppercase;\n padding: 0.25rem 0.75rem;\n border-radius: 999px;\n }\n </style>\n </head>\n <body>\n <div class=\"card\">\n <div class=\"wave\">👋</div>\n <h1>Hello, world!</h1>\n <p>\n This widget is served by the <strong>Patchwork MCP App Server</strong>.\n It renders inline inside Claude Desktop and Claude web via a\n <code>StreamableHTTPServerTransport</code>.\n </p>\n <span class=\"badge\">MCP App</span>\n </div>\n </body>\n</html>\n","import { type McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { z } from \"zod\";\n\nexport interface ServiceBackend {\n call(namespace: string, procedure: string, args: unknown[]): Promise<unknown>;\n}\n\nexport interface ServiceToolInfo {\n name: string;\n namespace: string;\n procedure: string;\n description?: string;\n parameters?: Record<string, unknown>;\n}\n\nexport interface ServiceBridgeConfig {\n backend: ServiceBackend;\n tools: ServiceToolInfo[];\n}\n\nconst TOOL_SEPARATOR = \"__\";\n\nfunction toMcpToolName(namespace: string, procedure: string): string {\n return `${namespace}${TOOL_SEPARATOR}${procedure}`;\n}\n\nfunction jsonSchemaToZodShape(\n schema?: Record<string, unknown>,\n): Record<string, z.ZodTypeAny> {\n const properties = (schema?.[\"properties\"] ?? {}) as Record<\n string,\n { type?: string; description?: string }\n >;\n const required = new Set((schema?.[\"required\"] ?? []) as string[]);\n\n const shape: Record<string, z.ZodTypeAny> = {};\n for (const [key, prop] of Object.entries(properties)) {\n let field: z.ZodTypeAny;\n switch (prop.type) {\n case \"number\":\n case \"integer\":\n field = z.number();\n break;\n case \"boolean\":\n field = z.boolean();\n break;\n case \"array\":\n field = z.array(z.unknown());\n break;\n case \"object\":\n field = z.record(z.unknown());\n break;\n default:\n field = z.string();\n break;\n }\n if (prop.description) {\n field = field.describe(prop.description);\n }\n if (!required.has(key)) {\n field = field.optional();\n }\n shape[key] = field;\n }\n\n return shape;\n}\n\nexport class ServiceBridge {\n private backend: ServiceBackend;\n private tools: Map<string, ServiceToolInfo> = new Map();\n\n constructor(config: ServiceBridgeConfig) {\n this.backend = config.backend;\n for (const tool of config.tools) {\n this.tools.set(tool.name, tool);\n }\n }\n\n registerTools(server: McpServer): void {\n for (const [, info] of this.tools) {\n const mcpToolName = toMcpToolName(info.namespace, info.procedure);\n const inputShape = jsonSchemaToZodShape(info.parameters);\n\n server.registerTool(\n mcpToolName,\n {\n description:\n info.description ??\n `Call ${info.namespace}.${info.procedure}`,\n inputSchema: inputShape,\n },\n async (args) => {\n try {\n const result = await this.backend.call(\n info.namespace,\n info.procedure,\n [args ?? {}],\n );\n return {\n content: [\n {\n type: \"text\" as const,\n text:\n typeof result === \"string\"\n ? result\n : JSON.stringify(result),\n },\n ],\n };\n } catch (error) {\n const message =\n error instanceof Error ? error.message : String(error);\n return {\n content: [\n {\n type: \"text\" as const,\n text: `Service call failed: ${message}`,\n },\n ],\n isError: true,\n };\n }\n },\n );\n }\n }\n\n registerSearchServices(server: McpServer): void {\n server.registerTool(\n \"search_services\",\n {\n description:\n \"Search for available service tools that widgets can call. \" +\n \"Returns matching services with their namespaces, procedures, and parameter schemas.\",\n inputSchema: {\n query: z\n .string()\n .optional()\n .describe(\n 'Natural language description of what you want to do (e.g., \"get weather forecast\")',\n ),\n namespace: z\n .string()\n .optional()\n .describe(\n 'Filter results to a specific service namespace (e.g., \"weather\")',\n ),\n tool_name: z\n .string()\n .optional()\n .describe(\"Get detailed info about a specific tool by name\"),\n limit: z\n .number()\n .optional()\n .describe(\"Maximum number of results to return\"),\n },\n },\n async (args) => {\n const query = args?.[\"query\"] as string | undefined;\n const namespace = args?.[\"namespace\"] as string | undefined;\n const toolName = args?.[\"tool_name\"] as string | undefined;\n const limit = (args?.[\"limit\"] as number) ?? 10;\n\n if (toolName) {\n const dotName = toolName.replace(/__/g, \".\");\n const info =\n this.tools.get(toolName) ?? this.tools.get(dotName);\n if (!info) {\n return {\n content: [\n {\n type: \"text\" as const,\n text: JSON.stringify({\n success: false,\n error: `Tool '${toolName}' not found`,\n }),\n },\n ],\n };\n }\n return {\n content: [\n {\n type: \"text\" as const,\n text: JSON.stringify({ success: true, tool: info }),\n },\n ],\n };\n }\n\n let results = Array.from(this.tools.values());\n\n if (namespace) {\n results = results.filter(\n (info) => info.namespace === namespace,\n );\n }\n\n if (query) {\n const queryLower = query.toLowerCase();\n const keywords = queryLower.split(/\\s+/).filter(Boolean);\n results = results\n .map((info) => {\n const searchText =\n `${info.name} ${info.namespace} ${info.procedure} ${info.description ?? \"\"}`.toLowerCase();\n const matchCount = keywords.filter((kw) =>\n searchText.includes(kw),\n ).length;\n return { info, score: matchCount / keywords.length };\n })\n .filter(({ score }) => score > 0)\n .sort((a, b) => b.score - a.score)\n .map(({ info }) => info);\n }\n\n results = results.slice(0, limit);\n\n return {\n content: [\n {\n type: \"text\" as const,\n text: JSON.stringify({\n success: true,\n count: results.length,\n tools: results,\n namespaces: this.getNamespaces(),\n }),\n },\n ],\n };\n },\n );\n }\n\n getNamespaces(): string[] {\n const namespaces = new Set<string>();\n for (const info of this.tools.values()) {\n namespaces.add(info.namespace);\n }\n return Array.from(namespaces);\n }\n\n getToolInfos(): ServiceToolInfo[] {\n return Array.from(this.tools.values());\n }\n\n has(namespace: string, procedure: string): boolean {\n return this.tools.has(`${namespace}.${procedure}`);\n }\n}\n","import { join, resolve } from \"node:path\";\nimport { LocalFileBackend } from \"./local-backend.js\";\nimport type { FSProvider, StoredWidget, StoredWidgetInfo, WidgetStoreOptions } from \"./types.js\";\nimport type { Manifest } from \"@aprovan/patchwork-compiler\";\n\nconst WIDGETS_PREFIX = \"widgets\";\nconst RESOURCE_URI_PREFIX = \"ui://widgets/\";\nconst DEFAULT_STORAGE_DIR = \".widget-store\";\n\nexport class WidgetStore {\n private provider: FSProvider;\n private storageDir: string;\n\n constructor(options: WidgetStoreOptions = {}) {\n this.storageDir = resolve(options.storageDir ?? DEFAULT_STORAGE_DIR);\n this.provider = options.backend ?? new LocalFileBackend(this.storageDir);\n }\n\n private fullPath(virtualPath: string): string {\n return join(WIDGETS_PREFIX, virtualPath);\n }\n\n async saveWidget(\n hash: string,\n html: string,\n manifest: Manifest,\n entry?: string,\n ): Promise<StoredWidget> {\n const widgetDir = `${manifest.name}/${hash}`;\n const htmlVirtualPath = this.fullPath(`${widgetDir}/view.html`);\n const manifestVirtualPath = this.fullPath(`${widgetDir}/manifest.json`);\n\n await this.provider.writeFile(htmlVirtualPath, html);\n await this.provider.writeFile(\n manifestVirtualPath,\n JSON.stringify({\n ...manifest,\n hash,\n entry,\n createdAt: Date.now(),\n }),\n );\n\n return {\n path: htmlVirtualPath,\n resourceUri: `${RESOURCE_URI_PREFIX}${widgetDir}/view.html`,\n html,\n manifest,\n entry,\n createdAt: Date.now(),\n };\n }\n\n async getWidget(name: string, hash: string): Promise<StoredWidget | null> {\n const widgetDir = `${name}/${hash}`;\n const htmlVirtualPath = this.fullPath(`${widgetDir}/view.html`);\n const manifestVirtualPath = this.fullPath(`${widgetDir}/manifest.json`);\n\n const exists = await this.provider.exists(htmlVirtualPath);\n if (!exists) return null;\n\n const html = await this.provider.readFile(htmlVirtualPath);\n let manifest: Manifest;\n let entry: string | undefined;\n let createdAt = Date.now();\n\n try {\n const raw = await this.provider.readFile(manifestVirtualPath);\n const parsed = JSON.parse(raw) as Record<string, unknown>;\n manifest = {\n name: parsed[\"name\"] as string,\n version: parsed[\"version\"] as string,\n platform: (parsed[\"platform\"] as \"browser\" | \"cli\") ?? \"browser\",\n image: parsed[\"image\"] as string,\n description: parsed[\"description\"] as string | undefined,\n services: parsed[\"services\"] as string[] | undefined,\n };\n entry = parsed[\"entry\"] as string | undefined;\n createdAt = (parsed[\"createdAt\"] as number) ?? Date.now();\n } catch {\n manifest = {\n name,\n version: \"0.1.0\",\n platform: \"browser\",\n image: \"@aprovan/patchwork-image-shadcn\",\n };\n }\n\n return {\n path: htmlVirtualPath,\n resourceUri: `${RESOURCE_URI_PREFIX}${widgetDir}/view.html`,\n html,\n manifest,\n entry,\n createdAt,\n };\n }\n\n async listWidgets(): Promise<StoredWidgetInfo[]> {\n const results: StoredWidgetInfo[] = [];\n const rootPath = this.fullPath(\"\");\n\n try {\n const names = await this.provider.readdir(rootPath);\n for (const nameEntry of names) {\n if (!nameEntry.isDirectory()) continue;\n const name = nameEntry.name;\n\n try {\n const hashes = await this.provider.readdir(join(rootPath, name));\n for (const hashEntry of hashes) {\n if (!hashEntry.isDirectory()) continue;\n const hash = hashEntry.name;\n const manifestVirtualPath = this.fullPath(`${name}/${hash}/manifest.json`);\n\n let manifest: Partial<Manifest> & { entry?: string; createdAt?: number; hash?: string } = {};\n try {\n const raw = await this.provider.readFile(manifestVirtualPath);\n manifest = JSON.parse(raw) as Partial<Manifest> & {\n entry?: string;\n createdAt?: number;\n hash?: string;\n };\n } catch {\n continue;\n }\n\n results.push({\n path: this.fullPath(`${name}/${hash}/view.html`),\n resourceUri: `${RESOURCE_URI_PREFIX}${name}/${hash}/view.html`,\n name: manifest.name ?? name,\n version: manifest.version ?? \"0.1.0\",\n description: manifest.description,\n services: manifest.services,\n entry: manifest.entry,\n createdAt: manifest.createdAt ?? 0,\n });\n }\n } catch {\n continue;\n }\n }\n } catch {\n return results;\n }\n\n return results.sort((a, b) => b.createdAt - a.createdAt);\n }\n\n async deleteWidget(name: string, hash: string): Promise<boolean> {\n const widgetDir = this.fullPath(`${name}/${hash}`);\n const exists = await this.provider.exists(widgetDir);\n if (!exists) return false;\n\n await this.provider.rmdir(widgetDir, { recursive: true });\n return true;\n }\n\n async hasWidget(name: string, hash: string): Promise<boolean> {\n return this.provider.exists(this.fullPath(`${name}/${hash}/view.html`));\n }\n\n resourceUriFor(name: string, hash: string): string {\n return `${RESOURCE_URI_PREFIX}${name}/${hash}/view.html`;\n }\n\n async loadAll(): Promise<StoredWidget[]> {\n const infos = await this.listWidgets();\n const widgets: StoredWidget[] = [];\n\n for (const info of infos) {\n const uriPath = info.resourceUri.replace(RESOURCE_URI_PREFIX, \"\").replace(/\\/view\\.html$/, \"\");\n const parts = uriPath.split(\"/\");\n const name = parts[0];\n const hash = parts[1];\n\n if (!name || !hash) continue;\n\n const widget = await this.getWidget(name, hash);\n if (widget) widgets.push(widget);\n }\n\n return widgets;\n }\n}\n\nlet _instance: WidgetStore | null = null;\n\nexport function getWidgetStore(options?: WidgetStoreOptions): WidgetStore {\n if (!_instance) {\n _instance = new WidgetStore(options);\n }\n return _instance;\n}\n\nexport function resetWidgetStore(): void {\n _instance = null;\n}\n","import { readFile, writeFile, unlink, stat, mkdir, readdir, rm, access } from \"node:fs/promises\";\nimport { join, dirname, normalize } from \"node:path\";\nimport type { DirEntry, FileStats, FSProvider } from \"./types.js\";\n\nfunction createDirEntry(name: string, isDir: boolean): DirEntry {\n return {\n name,\n isFile: () => !isDir,\n isDirectory: () => isDir,\n };\n}\n\nfunction createFileStats(size: number, mtime: Date, isDir: boolean): FileStats {\n return {\n size,\n mtime,\n isFile: () => !isDir,\n isDirectory: () => isDir,\n };\n}\n\nexport class LocalFileBackend implements FSProvider {\n constructor(private basePath: string) {}\n\n private resolve(path: string): string {\n return join(this.basePath, normalize(path));\n }\n\n async readFile(path: string): Promise<string> {\n return readFile(this.resolve(path), \"utf-8\");\n }\n\n async writeFile(path: string, content: string): Promise<void> {\n const fullPath = this.resolve(path);\n await mkdir(dirname(fullPath), { recursive: true });\n await writeFile(fullPath, content, \"utf-8\");\n }\n\n async unlink(path: string): Promise<void> {\n await unlink(this.resolve(path));\n }\n\n async stat(path: string): Promise<FileStats> {\n const fullPath = this.resolve(path);\n const s = await stat(fullPath);\n return createFileStats(s.size, s.mtime, s.isDirectory());\n }\n\n async mkdir(path: string, options?: { recursive?: boolean }): Promise<void> {\n await mkdir(this.resolve(path), options);\n }\n\n async readdir(path: string): Promise<DirEntry[]> {\n const fullPath = this.resolve(path);\n const entries = await readdir(fullPath, { withFileTypes: true });\n return entries.map((e) =>\n createDirEntry(e.name, e.isDirectory()),\n );\n }\n\n async rmdir(path: string, options?: { recursive?: boolean }): Promise<void> {\n if (options?.recursive) {\n await rm(this.resolve(path), { recursive: true, force: true });\n } else {\n await rm(this.resolve(path), { force: true });\n }\n }\n\n async exists(path: string): Promise<boolean> {\n try {\n await access(this.resolve(path));\n return true;\n } catch {\n return false;\n }\n }\n}\n"],"mappings":"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","names":["randomUUID","z","parts","packageName","subpath","version","url","__dirname","join","resolve","readFile","writeFile","mkdir","rm","join","dirname","size","resolve","join","z","randomUUID"]}
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@aprovan/mcp-app-server",
3
+ "version": "0.1.0-dev.c1ac1dc",
4
+ "description": "MCP App Server — hosts MCP tools that render interactive UIs in Claude Desktop and Claude web.",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "dependencies": {
15
+ "@modelcontextprotocol/ext-apps": "^1.7.3",
16
+ "@modelcontextprotocol/sdk": "^1.29.0",
17
+ "@vitejs/plugin-react": "^4.5.0",
18
+ "autoprefixer": "^10.4.21",
19
+ "cors": "^2.8.5",
20
+ "express": "^5.1.0",
21
+ "postcss": "^8.5.4",
22
+ "react": "^18.3.1",
23
+ "react-dom": "^18.3.1",
24
+ "tailwindcss": "^3.4.17",
25
+ "vite": "^6.3.5",
26
+ "vite-plugin-singlefile": "^2.3.3",
27
+ "zod": "^3.23.0",
28
+ "@aprovan/patchwork-compiler": "0.1.2-dev.c1ac1dc",
29
+ "@aprovan/patchwork-image-shadcn": "0.1.0-dev.c1ac1dc"
30
+ },
31
+ "devDependencies": {
32
+ "@types/cors": "^2.8.17",
33
+ "@types/express": "^5.0.2",
34
+ "@types/node": "^22.10.5",
35
+ "@types/react": "^18.3.0",
36
+ "@types/react-dom": "^18.3.0",
37
+ "tsup": "^8.3.5",
38
+ "tsx": "^4.19.2",
39
+ "typescript": "^5.7.3"
40
+ },
41
+ "engines": {
42
+ "node": ">=20.0.0"
43
+ },
44
+ "scripts": {
45
+ "build": "tsup",
46
+ "check-types": "tsc --noEmit",
47
+ "dev": "tsx watch src/server.ts",
48
+ "start": "node dist/server.js",
49
+ "typecheck": "tsc --noEmit",
50
+ "test": "vitest run"
51
+ }
52
+ }
@@ -0,0 +1,119 @@
1
+ import { describe, it, expect, beforeEach } from "vitest";
2
+ import {
3
+ computeCacheKey,
4
+ get,
5
+ set,
6
+ has,
7
+ clear,
8
+ size,
9
+ allEntries,
10
+ type CachedWidget,
11
+ } from "../compiler/cache.js";
12
+ import type { Manifest, VirtualProject } from "@aprovan/patchwork-compiler";
13
+
14
+ const TEST_MANIFEST: Manifest = {
15
+ name: "test-widget",
16
+ version: "0.1.0",
17
+ platform: "browser",
18
+ image: "@aprovan/patchwork-image-shadcn",
19
+ };
20
+
21
+ function makeCachedWidget(hash: string): CachedWidget {
22
+ return {
23
+ html: `<html><body>${hash}</body></html>`,
24
+ manifest: TEST_MANIFEST,
25
+ resourceUri: `ui://widget/${hash}/view.html`,
26
+ createdAt: Date.now(),
27
+ };
28
+ }
29
+
30
+ describe("cache", () => {
31
+ beforeEach(() => {
32
+ clear();
33
+ });
34
+
35
+ describe("computeCacheKey", () => {
36
+ it("produces deterministic keys for the same string source", () => {
37
+ const key1 = computeCacheKey("const x = 1", TEST_MANIFEST);
38
+ const key2 = computeCacheKey("const x = 1", TEST_MANIFEST);
39
+ expect(key1).toBe(key2);
40
+ });
41
+
42
+ it("produces different keys for different sources", () => {
43
+ const key1 = computeCacheKey("const x = 1", TEST_MANIFEST);
44
+ const key2 = computeCacheKey("const x = 2", TEST_MANIFEST);
45
+ expect(key1).not.toBe(key2);
46
+ });
47
+
48
+ it("produces different keys for different manifests", () => {
49
+ const otherManifest: Manifest = { ...TEST_MANIFEST, name: "other" };
50
+ const key1 = computeCacheKey("const x = 1", TEST_MANIFEST);
51
+ const key2 = computeCacheKey("const x = 1", otherManifest);
52
+ expect(key1).not.toBe(key2);
53
+ });
54
+
55
+ it("handles VirtualProject input", () => {
56
+ const project: VirtualProject = {
57
+ id: "test",
58
+ entry: "main.tsx",
59
+ files: new Map([
60
+ ["main.tsx", { path: "main.tsx", content: "export default function App() {}" }],
61
+ ]),
62
+ };
63
+ const key = computeCacheKey(project, TEST_MANIFEST);
64
+ expect(key).toBeTruthy();
65
+ expect(key).toHaveLength(16);
66
+ });
67
+
68
+ it("produces same key for same VirtualProject", () => {
69
+ const project: VirtualProject = {
70
+ id: "test",
71
+ entry: "main.tsx",
72
+ files: new Map([
73
+ ["main.tsx", { path: "main.tsx", content: "export default function App() {}" }],
74
+ ]),
75
+ };
76
+ const key1 = computeCacheKey(project, TEST_MANIFEST);
77
+ const key2 = computeCacheKey(project, TEST_MANIFEST);
78
+ expect(key1).toBe(key2);
79
+ });
80
+ });
81
+
82
+ describe("get / set / has", () => {
83
+ it("returns undefined for missing keys", () => {
84
+ expect(get("nonexistent")).toBeUndefined();
85
+ expect(has("nonexistent")).toBe(false);
86
+ });
87
+
88
+ it("stores and retrieves entries", () => {
89
+ const entry = makeCachedWidget("abc123");
90
+ set("abc123", entry);
91
+ expect(has("abc123")).toBe(true);
92
+ expect(get("abc123")).toEqual(entry);
93
+ });
94
+ });
95
+
96
+ describe("allEntries", () => {
97
+ it("returns all cached entries", () => {
98
+ set("a", makeCachedWidget("a"));
99
+ set("b", makeCachedWidget("b"));
100
+ const entries = allEntries();
101
+ expect(entries).toHaveLength(2);
102
+ expect(entries.map((e) => e[0])).toContain("a");
103
+ expect(entries.map((e) => e[0])).toContain("b");
104
+ });
105
+ });
106
+
107
+ describe("eviction", () => {
108
+ it("evicts oldest entries when cache is full", () => {
109
+ const originalSize = 256;
110
+ for (let i = 0; i < originalSize + 10; i++) {
111
+ set(`key-${i}`, {
112
+ ...makeCachedWidget(`key-${i}`),
113
+ createdAt: i * 1000,
114
+ });
115
+ }
116
+ expect(size()).toBeLessThanOrEqual(originalSize);
117
+ });
118
+ });
119
+ });
@@ -0,0 +1,86 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { patchworkCdnPlugin, getPreloadScripts } from "../compiler/cdn-plugin.js";
3
+ import type { ImageConfig } from "@aprovan/patchwork-compiler";
4
+
5
+ const SHADCN_CONFIG: ImageConfig = {
6
+ platform: "browser",
7
+ esbuild: { target: "es2020", format: "esm", jsx: "transform" },
8
+ framework: {
9
+ globals: { react: "React", "react-dom": "ReactDOM" },
10
+ preload: [
11
+ "https://esm.sh/react@18",
12
+ "https://esm.sh/react-dom@18/client",
13
+ ],
14
+ deps: { react: "18", "react-dom": "18" },
15
+ },
16
+ aliases: {
17
+ "@/components/ui/*": "@packagedcn/react",
18
+ "@/components/*": "@packagedcn/react",
19
+ "@/lib/utils": "@packagedcn/react",
20
+ },
21
+ };
22
+
23
+ describe("cdn-plugin", () => {
24
+ describe("patchworkCdnPlugin", () => {
25
+ it("creates a Vite plugin with correct name", () => {
26
+ const plugin = patchworkCdnPlugin({ imageConfig: SHADCN_CONFIG });
27
+ expect(plugin.name).toBe("patchwork-cdn");
28
+ });
29
+
30
+ it("has enforce: pre", () => {
31
+ const plugin = patchworkCdnPlugin({ imageConfig: SHADCN_CONFIG });
32
+ expect(plugin.enforce).toBe("pre");
33
+ });
34
+
35
+ it("has resolveId hook", () => {
36
+ const plugin = patchworkCdnPlugin({ imageConfig: SHADCN_CONFIG });
37
+ expect(plugin.resolveId).toBeTruthy();
38
+ });
39
+
40
+ it("has load hook", () => {
41
+ const plugin = patchworkCdnPlugin({ imageConfig: SHADCN_CONFIG });
42
+ expect(plugin.load).toBeTruthy();
43
+ });
44
+
45
+ it("works with empty image config", () => {
46
+ const plugin = patchworkCdnPlugin({
47
+ imageConfig: { platform: "browser" },
48
+ });
49
+ expect(plugin.name).toBe("patchwork-cdn");
50
+ });
51
+
52
+ it("works with custom packages option", () => {
53
+ const plugin = patchworkCdnPlugin({
54
+ imageConfig: SHADCN_CONFIG,
55
+ packages: { "@packagedcn/react": "^1.0.0" },
56
+ });
57
+ expect(plugin.resolveId).toBeTruthy();
58
+ });
59
+ });
60
+
61
+ describe("getPreloadScripts", () => {
62
+ it("generates script tags from preload URLs", () => {
63
+ const scripts = getPreloadScripts(SHADCN_CONFIG);
64
+ expect(scripts).toHaveLength(2);
65
+ expect(scripts[0]).toBe('<script src="https://esm.sh/react@18"></script>');
66
+ expect(scripts[1]).toBe(
67
+ '<script src="https://esm.sh/react-dom@18/client"></script>',
68
+ );
69
+ });
70
+
71
+ it("returns empty array when no preloads", () => {
72
+ const config: ImageConfig = {
73
+ platform: "browser",
74
+ framework: {},
75
+ };
76
+ const scripts = getPreloadScripts(config);
77
+ expect(scripts).toHaveLength(0);
78
+ });
79
+
80
+ it("returns empty array when framework is undefined", () => {
81
+ const config: ImageConfig = { platform: "browser" };
82
+ const scripts = getPreloadScripts(config);
83
+ expect(scripts).toHaveLength(0);
84
+ });
85
+ });
86
+ });