@happyvertical/smrt-app-mcp 0.53.3 → 0.53.4
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.
- package/AGENTS.md +12 -0
- package/README.md +135 -0
- package/dist/auth.d.ts +39 -0
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +34 -1
- package/dist/auth.js.map +1 -1
- package/dist/chunks/protocol-BlbEg3jn.js +7647 -0
- package/dist/chunks/protocol-BlbEg3jn.js.map +1 -0
- package/dist/defaults.d.ts +87 -0
- package/dist/defaults.d.ts.map +1 -0
- package/dist/errors.d.ts +2 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -7351
- package/dist/index.js.map +1 -1
- package/dist/manifest.json +1 -1
- package/dist/server.d.ts +28 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/smrt-knowledge.json +5 -5
- package/dist/sveltekit.d.ts +150 -1
- package/dist/sveltekit.d.ts.map +1 -1
- package/dist/sveltekit.js +157 -3
- package/dist/sveltekit.js.map +1 -1
- package/dist/tools.d.ts +18 -0
- package/dist/tools.d.ts.map +1 -1
- package/package.json +3 -3
- package/dist/chunks/protocol-Ce0U_XSp.js +0 -184
- package/dist/chunks/protocol-Ce0U_XSp.js.map +0 -1
package/dist/sveltekit.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sveltekit.js","names":[],"sources":["../src/sveltekit.ts"],"sourcesContent":["/**\n * SvelteKit route adapters for an `McpAppServer`. Mirrors the\n * `@happyvertical/smrt-users/sveltekit` pattern: minimal `HandleInput` type\n * so we never need `@sveltejs/kit` as a real dependency.\n *\n * @packageDocumentation\n *\n * @example\n * ```ts\n * // src/routes/api/mcp/+server.ts\n * import { mountMcpRoute } from '@happyvertical/smrt-app-mcp/sveltekit';\n * import { mcpServer } from '$lib/server/mcp';\n * export const POST = mountMcpRoute(mcpServer);\n * ```\n */\n\nimport {\n classifyInboundRequest,\n createMcpHandler,\n isJsonContentType,\n} from '@modelcontextprotocol/server';\nimport { McpAccessError } from './errors.js';\nimport {\n createMcpProtocolServerForRequest,\n MCP_TASKS_EXTENSION,\n type McpProtocolRequestOptions,\n} from './protocol.js';\nimport type { CallToolInput, McpAppPrincipal, McpAppServer } from './server.js';\n\n/** Minimal subset of a SvelteKit RequestEvent we actually touch. */\ntype SvelteKitRequestEvent = {\n locals?: Record<string, unknown>;\n request: Request;\n url: URL;\n};\n\n/** A SvelteKit `+server.ts` request handler with no SvelteKit dependency. */\nexport type McpSvelteKitHandler = (\n event: SvelteKitRequestEvent,\n) => Promise<Response>;\n\ntype ResolvedRequestPrincipal = {\n principal: McpAppPrincipal | null;\n /** Defined only for the legacy discovery-only authentication adapter. */\n legacyAuthenticated?: boolean;\n};\n\n/** Locals reader used to pull the request principal out of `event.locals`. */\nexport type McpPrincipalResolver = (\n event: SvelteKitRequestEvent,\n) => McpAppPrincipal | null | undefined;\n\n/** Backwards-compatible alias for callers that name the principal a user. */\nexport type McpUserResolver = McpPrincipalResolver;\n\nconst defaultResolvePrincipal: McpPrincipalResolver = (event) =>\n (event.locals?.user ?? null) as McpAppPrincipal | null;\n\nfunction resolveRequestPrincipal(\n event: SvelteKitRequestEvent,\n options: MountMcpRouteOptions,\n): ResolvedRequestPrincipal {\n const resolvePrincipal =\n options.resolvePrincipal ?? options.resolveUser ?? defaultResolvePrincipal;\n const principal = resolvePrincipal(event) ?? null;\n // Keep the legacy boolean gate for existing apps, but apply it to the same\n // principal that both routes receive. A new principal resolver supersedes it.\n if (\n !options.resolvePrincipal &&\n options.resolveAuthenticated &&\n !options.resolveAuthenticated(event)\n ) {\n return { principal: null, legacyAuthenticated: false };\n }\n return {\n principal,\n ...(options.resolvePrincipal || !options.resolveAuthenticated\n ? {}\n : { legacyAuthenticated: true }),\n };\n}\n\nfunction listToolsInput(resolved: ResolvedRequestPrincipal) {\n // Before principal-aware routes, `resolveAuthenticated` affected discovery\n // independently of `resolveUser`. Preserve a true legacy result only when\n // there is no principal to pass; new routes should use `resolvePrincipal`\n // for a single identity on both discovery and calls.\n if (!resolved.principal && resolved.legacyAuthenticated) {\n return { authenticated: true };\n }\n return { principal: resolved.principal };\n}\n\n/** Options shared by both route mounts. */\nexport interface MountMcpRouteOptions {\n /** Optional extension discovery projected from the request-authorized tool catalog. */\n extensions?: McpProtocolRequestOptions['extensions'];\n /**\n * Resolve the request principal once for both discovery and direct calls.\n * Defaults to `event.locals.user`.\n */\n resolvePrincipal?: McpPrincipalResolver;\n /**\n * Backwards-compatible alias for `resolvePrincipal`.\n */\n resolveUser?: McpUserResolver;\n /**\n * Deprecated legacy authentication gate. When `resolvePrincipal` is not\n * supplied, a false result makes the principal null for both routes.\n */\n resolveAuthenticated?: (event: SvelteKitRequestEvent) => boolean;\n}\n\nfunction protocolServerForRequest(\n server: McpAppServer,\n resolved: ResolvedRequestPrincipal,\n): McpAppServer {\n // Older applications sometimes used a boolean discovery-only adapter. Keep\n // its positive result intact for the deprecated resolver while new mounts\n // consistently use the principal on both MCP methods.\n if (!resolved.legacyAuthenticated || resolved.principal) return server;\n return {\n ...server,\n serverInfo: server.serverInfo,\n listTools: () => server.listTools({ authenticated: true }),\n callTool: (input) => server.callTool(input),\n };\n}\n\n/**\n * Mount a modern, stateless Streamable HTTP MCP endpoint as a SvelteKit\n * `POST` handler. The scoped SDK validates the 2026-07-28 envelope plus the\n * required `Mcp-Method` and `Mcp-Name` headers, returning `-32020` on a\n * mismatch. A fresh protocol server is created for each HTTP request, so this\n * route holds neither MCP sessions nor request principal state between nodes.\n */\nexport function mountMcpRoute(\n server: McpAppServer,\n options: MountMcpRouteOptions = {},\n): McpSvelteKitHandler {\n return async (event) => {\n const resolved = resolveRequestPrincipal(event, options);\n const taskResponse = await maybeHandleTaskRequest(\n server,\n resolved.principal,\n event.request,\n );\n if (taskResponse) return taskResponse;\n const handler = createMcpHandler(\n () =>\n createMcpProtocolServerForRequest(\n protocolServerForRequest(server, resolved),\n {\n principal: resolved.principal,\n extensions: options.extensions,\n },\n ),\n {\n // The legacy REST-shaped mounts below remain a deprecated migration\n // path. This endpoint accepts only the modern MCP protocol.\n legacy: 'reject',\n // The SDK validates every request before consulting its listen router.\n // Zero capacity keeps this mount stateless by refusing a\n // listen request before it can open an SSE response.\n maxSubscriptions: 0,\n },\n );\n return handler.fetch(event.request);\n };\n}\n\n/**\n * The installed MCP SDK validates tools/call against a pre-Tasks result codec.\n * Intercept the extension before the SDK handler so the regular stateless HTTP\n * protocol stays untouched for every other method.\n */\nasync function maybeHandleTaskRequest(\n server: McpAppServer,\n principal: McpAppPrincipal | null,\n request: Request,\n): Promise<Response | null> {\n if (\n !server.tasksEnabled ||\n !server.callTask ||\n !server.getTask ||\n !server.updateTask ||\n !server.cancelTask ||\n request.method !== 'POST'\n ) {\n return null;\n }\n const body = (await request\n .clone()\n .json()\n .catch(() => null)) as {\n id?: string | number | null;\n jsonrpc?: string;\n method?: string;\n params?: Record<string, unknown>;\n } | null;\n if (body?.jsonrpc !== '2.0' || body.id === undefined) return null;\n const params = body.params ?? {};\n const taskTool =\n body.method === 'tools/call' &&\n typeof params.name === 'string' &&\n (await server.isTaskTool?.(params.name)) === true;\n const taskMethod = ['tasks/get', 'tasks/update', 'tasks/cancel'].includes(\n body.method ?? '',\n );\n if (!taskTool && !taskMethod) return null;\n\n // Keep task requests on the SDK's protocol-validation path until their\n // 2026 request envelope is known-good. In particular, never enqueue durable\n // work for malformed envelopes, unsupported revisions, or non-JSON bodies.\n // The fallback handler owns its wire-exact error response for those cases.\n if (!isJsonContentType(request.headers.get('content-type'))) return null;\n const classification = classifyInboundRequest({\n httpMethod: request.method,\n protocolVersionHeader:\n request.headers.get('mcp-protocol-version') ?? undefined,\n mcpMethodHeader: request.headers.get('mcp-method') ?? undefined,\n mcpNameHeader: request.headers.get('mcp-name') ?? undefined,\n body: body as never,\n });\n if (classification.kind === 'reject') {\n return jsonRpcResponse(\n body.id,\n undefined,\n {\n code: classification.code,\n message: classification.message,\n ...(classification.data === undefined\n ? {}\n : { data: classification.data }),\n },\n classification.httpStatus,\n );\n }\n if (\n classification.kind !== 'modern' ||\n classification.classification.revision !== '2026-07-28'\n ) {\n return null;\n }\n\n // The SDK normally performs this modern HTTP routing validation before\n // dispatch. Task responses are intercepted ahead of that SDK handler, so\n // retain the same fail-closed header/body contract here.\n const headerMismatch = taskHeaderMismatch(request, body.method, params);\n if (headerMismatch) {\n return jsonRpcResponse(body.id, undefined, headerMismatch, 400);\n }\n\n const clientCapabilities = asRecord(params._meta)[\n 'io.modelcontextprotocol/clientCapabilities'\n ];\n const clientSupportsTasks = Object.hasOwn(\n asRecord(asRecord(clientCapabilities).extensions),\n MCP_TASKS_EXTENSION,\n );\n\n // A synchronous fallback exists for tools/call; lifecycle methods do not.\n if (!clientSupportsTasks && body.method === 'tools/call') return null;\n if (!clientSupportsTasks) {\n return jsonRpcResponse(\n body.id,\n undefined,\n {\n code: -32021,\n message: 'Missing required client capability',\n data: {\n requiredCapabilities: { extensions: { [MCP_TASKS_EXTENSION]: {} } },\n },\n },\n 400,\n );\n }\n\n try {\n if (body.method === 'tools/call') {\n const result = await server.callTask({\n name: params.name as string,\n arguments: (params.arguments as Record<string, unknown>) ?? {},\n principal,\n });\n return jsonRpcResponse(body.id, result);\n }\n if (typeof params.taskId !== 'string') {\n return jsonRpcResponse(body.id, undefined, {\n code: -32602,\n message: 'taskId is required',\n });\n }\n if (body.method === 'tasks/get') {\n const task = await server.getTask({ taskId: params.taskId, principal });\n return jsonRpcResponse(body.id, { resultType: 'complete', ...task });\n }\n if (body.method === 'tasks/update') {\n await server.updateTask({\n taskId: params.taskId,\n inputResponses:\n params.inputResponses && typeof params.inputResponses === 'object'\n ? (params.inputResponses as Record<string, unknown>)\n : {},\n principal,\n });\n return jsonRpcResponse(body.id, { resultType: 'complete' });\n }\n await server.cancelTask({ taskId: params.taskId, principal });\n return jsonRpcResponse(body.id, { resultType: 'complete' });\n } catch (error) {\n if (error instanceof McpAccessError) {\n const { code, retryable } = error.metadata;\n return jsonRpcResponse(body.id, undefined, {\n code: error.status === 404 ? -32602 : -32600,\n message: error.message,\n data: {\n ...(typeof code === 'string' ? { code } : {}),\n ...(typeof retryable === 'boolean' ? { retryable } : {}),\n },\n });\n }\n const message =\n error instanceof Error ? error.message : 'Task operation failed';\n return jsonRpcResponse(body.id, undefined, { code: -32602, message });\n }\n}\n\nfunction taskHeaderMismatch(\n request: Request,\n method: string | undefined,\n params: Record<string, unknown>,\n): { code: number; message: string } | undefined {\n if (\n !method ||\n normalizeHeaderValue(request.headers.get('mcp-method') ?? '') !== method\n ) {\n return {\n code: -32020,\n message: 'Mcp-Method header must match the JSON-RPC method.',\n };\n }\n const toolName =\n method === 'tools/call' && typeof params.name === 'string'\n ? params.name\n : undefined;\n const headerName = request.headers.get('mcp-name');\n if (\n toolName !== undefined &&\n (headerName === null || decodeMcpHeaderValue(headerName) !== toolName)\n ) {\n return {\n code: -32020,\n message: 'Mcp-Name header must match the tools/call name.',\n };\n }\n return undefined;\n}\n\n/** Match the SDK's RFC 9110 optional-whitespace handling for MCP headers. */\nfunction normalizeHeaderValue(value: string): string {\n return value.replace(/^[\\t ]+|[\\t ]+$/g, '');\n}\n\n/** Decode the SDK's canonical Base64 sentinel for an MCP header value. */\nfunction decodeMcpHeaderValue(value: string): string | undefined {\n const normalized = normalizeHeaderValue(value);\n const prefix = '=?base64?';\n if (!normalized.startsWith(prefix) || !normalized.endsWith('?=')) {\n return normalized;\n }\n const encoded = normalized.slice(prefix.length, -2);\n if (\n !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(\n encoded,\n )\n ) {\n return undefined;\n }\n try {\n return new TextDecoder('utf-8', { fatal: true }).decode(\n Uint8Array.from(atob(encoded), (character) => character.charCodeAt(0)),\n );\n } catch {\n return undefined;\n }\n}\n\nfunction jsonRpcResponse(\n id: string | number | null,\n result?: unknown,\n error?: { code: number; message: string; data?: unknown },\n status = 200,\n): Response {\n return new Response(\n JSON.stringify({ jsonrpc: '2.0', id, ...(error ? { error } : { result }) }),\n { status, headers: { 'content-type': 'application/json' } },\n );\n}\n\nfunction asRecord(value: unknown): Record<string, unknown> {\n return value !== null && typeof value === 'object' && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : {};\n}\n\n/**\n * Mount `server.listTools` as a `GET` handler. Returns the tool list shape\n * `{ tools }` for compatibility with the stock MCP bridge.\n *\n * @deprecated Use {@link mountMcpRoute}. Existing REST-shaped mounts can use\n * this compatibility alias while migrating without a coordinated cutover.\n */\nexport function mountMcpToolsRoute(\n server: McpAppServer,\n options: MountMcpRouteOptions = {},\n): McpSvelteKitHandler {\n return async (event) => {\n try {\n const tools = await server.listTools(\n listToolsInput(resolveRequestPrincipal(event, options)),\n );\n return jsonResponse({ tools });\n } catch (error) {\n if (error instanceof McpAccessError) {\n return jsonResponse(mcpAccessErrorBody(error), error.status);\n }\n throw error;\n }\n };\n}\n\n/**\n * Mount `server.callTool` as a `POST` handler that expects\n * `{ name, arguments }` in the JSON body.\n *\n * @deprecated Use {@link mountMcpRoute}. Existing REST-shaped mounts can use\n * this compatibility alias while migrating without a coordinated cutover.\n */\nexport function mountMcpCallRoute(\n server: McpAppServer,\n options: MountMcpRouteOptions = {},\n): McpSvelteKitHandler {\n return async (event) => {\n const body = (await event.request.json().catch(() => null)) as {\n arguments?: Record<string, unknown>;\n name?: string;\n } | null;\n\n if (!body?.name) {\n return jsonResponse({ error: 'name is required.' }, 400);\n }\n\n const input: CallToolInput = {\n arguments: body.arguments ?? {},\n name: body.name,\n principal: resolveRequestPrincipal(event, options).principal,\n };\n\n try {\n return jsonResponse(await server.callTool(input));\n } catch (error) {\n if (error instanceof McpAccessError) {\n return jsonResponse(mcpAccessErrorBody(error), error.status);\n }\n throw error;\n }\n };\n}\n\nfunction mcpAccessErrorBody(error: McpAccessError): unknown {\n const { code, retryable } = error.metadata;\n // Preserve the legacy shape unless an error deliberately opts into the\n // shared structured failure contract.\n if (!code) return { error: error.message };\n return {\n error: {\n ok: false,\n code,\n message: error.message,\n status: error.status,\n ...(retryable === undefined ? {} : { retryable }),\n },\n };\n}\n\nfunction jsonResponse(body: unknown, status = 200): Response {\n return new Response(JSON.stringify(body), {\n status,\n headers: { 'content-type': 'application/json' },\n });\n}\n\nexport { McpAccessError } from './errors.js';\nexport type { McpAppPrincipal, McpAppServer } from './server.js';\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"sveltekit.js","names":[],"sources":["../src/sveltekit.ts"],"sourcesContent":["/**\n * SvelteKit route adapters for an `McpAppServer`. Mirrors the\n * `@happyvertical/smrt-users/sveltekit` pattern: minimal `HandleInput` type\n * so we never need `@sveltejs/kit` as a real dependency.\n *\n * @packageDocumentation\n *\n * @example\n * ```ts\n * // src/routes/api/mcp/+server.ts — one call with the app defaults\n * import { mountMcpAppRoute } from '@happyvertical/smrt-app-mcp/sveltekit';\n * import { Item } from '$lib/objects/Item';\n * export const POST = mountMcpAppRoute({\n * models: [Item],\n * requiredScopes: ['items.read'],\n * smrtOptions: () => ({ db: getDatabaseConfig() }),\n * });\n * ```\n *\n * @example\n * ```ts\n * // src/routes/api/mcp/+server.ts — a custom server core\n * import { mountMcpRoute } from '@happyvertical/smrt-app-mcp/sveltekit';\n * import { mcpServer } from '$lib/server/mcp';\n * export const POST = mountMcpRoute(mcpServer);\n * ```\n */\n\nimport {\n classifyInboundRequest,\n createMcpHandler,\n isJsonContentType,\n} from '@modelcontextprotocol/server';\nimport type { McpResourceAuth } from './auth.js';\nimport {\n type CreateDefaultMcpAppServerOptions,\n createDefaultMcpAppServer,\n} from './defaults.js';\nimport {\n MCP_ORIGIN_DENIED_CODE,\n MCP_TOOL_ACCESS_DENIED_CODE,\n McpAccessError,\n} from './errors.js';\nimport {\n createMcpProtocolServerForRequest,\n MCP_TASKS_EXTENSION,\n type McpProtocolRequestOptions,\n} from './protocol.js';\nimport type { CallToolInput, McpAppPrincipal, McpAppServer } from './server.js';\n\n/** Minimal subset of a SvelteKit RequestEvent we actually touch. */\ntype SvelteKitRequestEvent = {\n locals?: Record<string, unknown>;\n request: Request;\n url: URL;\n};\n\n/** A SvelteKit `+server.ts` request handler with no SvelteKit dependency. */\nexport type McpSvelteKitHandler = (\n event: SvelteKitRequestEvent,\n) => Promise<Response>;\n\ntype ResolvedRequestPrincipal = {\n principal: McpAppPrincipal | null;\n /** Defined only for the legacy discovery-only authentication adapter. */\n legacyAuthenticated?: boolean;\n};\n\n/** Locals reader used to pull the request principal out of `event.locals`. */\nexport type McpPrincipalResolver = (\n event: SvelteKitRequestEvent,\n) => McpAppPrincipal | null | undefined;\n\n/** Backwards-compatible alias for callers that name the principal a user. */\nexport type McpUserResolver = McpPrincipalResolver;\n\nconst defaultResolvePrincipal: McpPrincipalResolver = (event) =>\n (event.locals?.user ?? null) as McpAppPrincipal | null;\n\nfunction resolveRequestPrincipal(\n event: SvelteKitRequestEvent,\n options: MountMcpRouteOptions,\n): ResolvedRequestPrincipal {\n const resolvePrincipal =\n options.resolvePrincipal ?? options.resolveUser ?? defaultResolvePrincipal;\n const principal = resolvePrincipal(event) ?? null;\n // Keep the legacy boolean gate for existing apps, but apply it to the same\n // principal that both routes receive. A new principal resolver supersedes it.\n if (\n !options.resolvePrincipal &&\n options.resolveAuthenticated &&\n !options.resolveAuthenticated(event)\n ) {\n return { principal: null, legacyAuthenticated: false };\n }\n return {\n principal,\n ...(options.resolvePrincipal || !options.resolveAuthenticated\n ? {}\n : { legacyAuthenticated: true }),\n };\n}\n\nfunction listToolsInput(resolved: ResolvedRequestPrincipal) {\n // Before principal-aware routes, `resolveAuthenticated` affected discovery\n // independently of `resolveUser`. Preserve a true legacy result only when\n // there is no principal to pass; new routes should use `resolvePrincipal`\n // for a single identity on both discovery and calls.\n if (!resolved.principal && resolved.legacyAuthenticated) {\n return { authenticated: true };\n }\n return { principal: resolved.principal };\n}\n\n/**\n * The bearer-authentication surface `mountMcpRoute` and the metadata route\n * need from `createMcpResourceAuth` (`./auth`). Typed structurally so this\n * entry does not load the JWT verifier.\n */\nexport type McpRouteResourceAuth = Pick<\n McpResourceAuth,\n 'metadataUrl' | 'metadataResponse' | 'authenticate'\n>;\n\n/**\n * A protected-resource adapter, or a thunk read on every request. A `null` or\n * `undefined` result (for example the `local` profile) means the route uses\n * its session principal resolver instead.\n */\nexport type McpRouteResourceAuthSource =\n | McpRouteResourceAuth\n | null\n | undefined\n | (() => McpRouteResourceAuth | null | undefined);\n\nfunction currentResourceAuth(\n source: McpRouteResourceAuthSource,\n): McpRouteResourceAuth | null {\n const value = typeof source === 'function' ? source() : source;\n return value ?? null;\n}\n\n/** Parse an origin to its canonical `scheme://host[:port]`, or `undefined`. */\nfunction canonicalOrigin(value: string): string | undefined {\n if (value === 'null') return undefined;\n let url: URL;\n try {\n url = new URL(value);\n } catch {\n return undefined;\n }\n if (url.protocol !== 'https:' && url.protocol !== 'http:') return undefined;\n if (url.username || url.password || url.search || url.hash) return undefined;\n if (url.pathname !== '/') return undefined;\n return url.origin;\n}\n\nfunction normalizeTrustedOrigins(\n values: readonly string[],\n): ReadonlySet<string> {\n if (!Array.isArray(values)) {\n throw new TypeError('MCP trustedOrigins must be an array of origins.');\n }\n const origins = new Set<string>();\n for (const value of values) {\n const origin =\n typeof value === 'string' ? canonicalOrigin(value) : undefined;\n if (!origin) throw new TypeError('Invalid MCP trusted origin.');\n origins.add(origin);\n }\n return origins;\n}\n\n/**\n * Same-origin is judged against `event.url`, which SvelteKit derives from the\n * adapter's configured origin (for example adapter-node `ORIGIN`, or its\n * `PROTOCOL_HEADER`/`HOST_HEADER` proxy settings). This package does not read\n * `X-Forwarded-*` itself.\n */\nfunction originPermitted(\n event: SvelteKitRequestEvent,\n trusted: ReadonlySet<string>,\n): boolean {\n const header = event.request.headers.get('origin');\n if (header !== null) {\n const origin = canonicalOrigin(header.trim());\n return (\n origin !== undefined &&\n (origin === event.url.origin || trusted.has(origin))\n );\n }\n // No Origin: a non-browser client, unless fetch metadata says otherwise.\n const site = event.request.headers\n .get('sec-fetch-site')\n ?.trim()\n .toLowerCase();\n return site !== 'cross-site' && site !== 'same-site';\n}\n\nfunction originDeniedResponse(): Response {\n return new Response(\n JSON.stringify({\n jsonrpc: '2.0',\n id: null,\n error: {\n code: -32600,\n message: 'MCP request origin is not permitted.',\n data: { code: MCP_ORIGIN_DENIED_CODE, retryable: false },\n },\n }),\n {\n status: 403,\n headers: {\n 'content-type': 'application/json',\n 'cache-control': 'no-store',\n },\n },\n );\n}\n\n/**\n * Project the session hook's request-local result onto an MCP principal.\n *\n * Reads only `locals.user.id`, the session-authorized `locals.tenantId`, and\n * `locals.permissions` as populated by `createSessionHandler` from\n * `@happyvertical/smrt-users/sveltekit`. Headers, URL-selected tenants\n * (`locals.selectedTenantId`), request bodies and route arguments are never\n * identity inputs. Any missing or malformed field yields `null`\n * (unauthenticated), which fails closed for every non-public tool.\n */\nexport function principalFromSessionLocals(event: {\n locals?: Record<string, unknown>;\n}): McpAppPrincipal | null {\n const locals = event.locals;\n if (!locals || typeof locals !== 'object') return null;\n const user = locals.user as { id?: unknown } | null | undefined;\n const tenantId = locals.tenantId;\n const permissions = locals.permissions;\n if (!user || typeof user !== 'object') return null;\n if (typeof user.id !== 'string' || user.id.length === 0) return null;\n if (typeof tenantId !== 'string' || tenantId.length === 0) return null;\n if (\n !Array.isArray(permissions) ||\n !permissions.every((value) => typeof value === 'string')\n )\n return null;\n return {\n id: user.id,\n tenantId,\n kind: 'human',\n scopes: [...permissions].sort(),\n };\n}\n\n/**\n * Run MCP dispatch for a bearer-authenticated principal inside an\n * application-owned context bound to that principal (for example\n * `runtime.runAsPrincipal` from `@happyvertical/smrt-app-runtime/sveltekit`,\n * which opens the principal's permission context and, under `database-rls`,\n * its RLS transaction). Must call `run` exactly once and return its result;\n * throwing before `run` (an unbindable principal) is a denial.\n *\n * Pass `run` the bound principal carrying its *effective* `scopes` (for\n * example the token scopes still granted by live membership permissions).\n * Dispatch then authorizes with those scopes, intersected with the token's:\n * a binder can only narrow authority, and changing the principal's `id` or\n * `tenantId` is a denial. Calling `run()` with no argument keeps the token\n * scopes.\n */\nexport type McpPrincipalBinder = <T>(\n principal: McpAppPrincipal & { id: string },\n run: (bound?: McpAppPrincipal & { id: string }) => Promise<T>,\n) => Promise<T>;\n\n/**\n * The principal dispatch authorizes after binding: the authenticated identity\n * with only the scopes both the token and the binder grant. `undefined` when\n * the binder changed the identity or returned malformed scopes.\n */\nfunction effectiveBoundPrincipal(\n authenticated: McpAppPrincipal & { id: string },\n bound: (McpAppPrincipal & { id: string }) | undefined,\n): (McpAppPrincipal & { id: string }) | undefined {\n if (bound === undefined) return authenticated;\n if (\n !bound ||\n typeof bound !== 'object' ||\n bound.id !== authenticated.id ||\n (bound.tenantId ?? null) !== (authenticated.tenantId ?? null) ||\n !Array.isArray(bound.scopes) ||\n !bound.scopes.every((scope) => typeof scope === 'string')\n ) {\n return undefined;\n }\n const granted = new Set(bound.scopes);\n return {\n ...authenticated,\n scopes: (authenticated.scopes ?? []).filter((scope) => granted.has(scope)),\n };\n}\n\n/** Options shared by both route mounts. */\nexport interface MountMcpRouteOptions {\n /**\n * Bind a bearer-authenticated principal around dispatch (see\n * {@link McpPrincipalBinder}). The `smrtOptions` database thunk, task\n * handling and tool execution all run inside the binding, and the response\n * is fully materialized before it returns. A binder that throws before\n * running dispatch, or returns without running it, yields HTTP 403 with the\n * safe `mcp_tool_access_denied` JSON-RPC error. Scopes the binder hands\n * back become the effective authority for the tool policy, so live\n * revocation applies even without a database-enforced policy. Used only when `auth`\n * authenticated the request; the session-locals path is unchanged.\n */\n bindPrincipal?: McpPrincipalBinder;\n /**\n * Refuse a modern-endpoint request whose `Origin` header is present but is\n * neither the request URL's own origin nor in `trustedOrigins` (and, when\n * `Origin` is absent, one whose `Sec-Fetch-Site` is `cross-site` or\n * `same-site`). The check runs before bearer authentication, principal\n * resolution and any task or tool dispatch, and answers 403 with the safe\n * `mcp_origin_denied` JSON-RPC error. Requests without browser origin\n * signals (server-side MCP clients, the `smrt-app-cli` stdio bridge) are\n * unaffected. Off unless set for `mountMcpRoute`; on by default for\n * `mountMcpAppRoute`. Ignored by the deprecated REST-shaped mounts.\n */\n checkOrigin?: boolean;\n /**\n * Additional exact origins (`scheme://host[:port]`) accepted by the origin\n * check, e.g. a browser-hosted MCP client, or the public origin when a\n * proxy makes `event.url` differ from what browsers send. `null`, wildcards,\n * paths and credentials are rejected at mount time.\n */\n trustedOrigins?: readonly string[];\n /**\n * Optional bearer authentication for the modern `mountMcpRoute` endpoint.\n * When the source yields an adapter, every request must carry a valid\n * bearer token: a failed check returns the adapter's challenge response\n * before any dispatch, and the adapter's mapped principal replaces the\n * session principal (`resolvePrincipal` is not consulted). When it yields\n * `null`, the route resolves its principal from the request as usual.\n * Ignored by the deprecated REST-shaped mounts.\n */\n auth?: McpRouteResourceAuthSource;\n /** Optional extension discovery projected from the request-authorized tool catalog. */\n extensions?: McpProtocolRequestOptions['extensions'];\n /**\n * Resolve the request principal once for both discovery and direct calls.\n * Defaults to `event.locals.user`.\n */\n resolvePrincipal?: McpPrincipalResolver;\n /**\n * Backwards-compatible alias for `resolvePrincipal`.\n */\n resolveUser?: McpUserResolver;\n /**\n * Deprecated legacy authentication gate. When `resolvePrincipal` is not\n * supplied, a false result makes the principal null for both routes.\n */\n resolveAuthenticated?: (event: SvelteKitRequestEvent) => boolean;\n}\n\nfunction protocolServerForRequest(\n server: McpAppServer,\n resolved: ResolvedRequestPrincipal,\n): McpAppServer {\n // Older applications sometimes used a boolean discovery-only adapter. Keep\n // its positive result intact for the deprecated resolver while new mounts\n // consistently use the principal on both MCP methods.\n if (!resolved.legacyAuthenticated || resolved.principal) return server;\n return {\n ...server,\n serverInfo: server.serverInfo,\n listTools: () => server.listTools({ authenticated: true }),\n callTool: (input) => server.callTool(input),\n };\n}\n\n/**\n * Mount a modern, stateless Streamable HTTP MCP endpoint as a SvelteKit\n * `POST` handler. The scoped SDK validates the 2026-07-28 envelope plus the\n * required `Mcp-Method` and `Mcp-Name` headers, returning `-32020` on a\n * mismatch. A fresh protocol server is created for each HTTP request, so this\n * route holds neither MCP sessions nor request principal state between nodes.\n */\nexport function mountMcpRoute(\n server: McpAppServer,\n options: MountMcpRouteOptions = {},\n): McpSvelteKitHandler {\n const trustedOrigins = options.checkOrigin\n ? normalizeTrustedOrigins(options.trustedOrigins ?? [])\n : undefined;\n return async (event) => {\n // Ambient browser credentials (session cookies) must not let another\n // origin drive this endpoint; refuse before any identity or dispatch work.\n if (trustedOrigins && !originPermitted(event, trustedOrigins)) {\n return originDeniedResponse();\n }\n const auth = currentResourceAuth(options.auth);\n if (!auth) return dispatch(event, resolveRequestPrincipal(event, options));\n const checked = await auth.authenticate(event.request);\n if (!checked.ok) return checked.response;\n if (!options.bindPrincipal) {\n return dispatch(event, { principal: checked.principal });\n }\n let entered = false;\n let response: Response;\n try {\n response = await options.bindPrincipal(\n checked.principal,\n async (bound) => {\n entered = true;\n const principal = effectiveBoundPrincipal(checked.principal, bound);\n if (!principal) return principalUnboundResponse();\n // Materialize the body inside the binding so no dispatch work can\n // outlive the principal's context (or its RLS transaction).\n const dispatched = await dispatch(event, { principal });\n return new Response(await dispatched.arrayBuffer(), {\n status: dispatched.status,\n statusText: dispatched.statusText,\n headers: dispatched.headers,\n });\n },\n );\n } catch (error) {\n if (entered) throw error;\n return principalUnboundResponse();\n }\n return entered ? response : principalUnboundResponse();\n };\n\n async function dispatch(\n event: SvelteKitRequestEvent,\n resolved: ResolvedRequestPrincipal,\n ): Promise<Response> {\n const taskResponse = await maybeHandleTaskRequest(\n server,\n resolved.principal,\n event.request,\n );\n if (taskResponse) return taskResponse;\n const handler = createMcpHandler(\n () =>\n createMcpProtocolServerForRequest(\n protocolServerForRequest(server, resolved),\n {\n principal: resolved.principal,\n extensions: options.extensions,\n },\n ),\n {\n // The legacy REST-shaped mounts below remain a deprecated migration\n // path. This endpoint accepts only the modern MCP protocol.\n legacy: 'reject',\n // The SDK validates every request before consulting its listen router.\n // Zero capacity keeps this mount stateless by refusing a\n // listen request before it can open an SSE response.\n maxSubscriptions: 0,\n },\n );\n return handler.fetch(event.request);\n }\n}\n\nfunction principalUnboundResponse(): Response {\n return new Response(\n JSON.stringify({\n jsonrpc: '2.0',\n id: null,\n error: {\n code: -32600,\n message: 'MCP tool access is not permitted.',\n data: { code: MCP_TOOL_ACCESS_DENIED_CODE, retryable: false },\n },\n }),\n {\n status: 403,\n headers: {\n 'content-type': 'application/json',\n 'cache-control': 'no-store',\n },\n },\n );\n}\n\n/** Options for {@link mountMcpAppRoute}. */\nexport interface MountMcpAppRouteOptions\n extends CreateDefaultMcpAppServerOptions,\n Pick<\n MountMcpRouteOptions,\n 'auth' | 'bindPrincipal' | 'extensions' | 'checkOrigin' | 'trustedOrigins'\n > {\n /**\n * Resolve the request principal when no bearer adapter is active. Defaults\n * to {@link principalFromSessionLocals}.\n */\n resolvePrincipal?: McpPrincipalResolver;\n}\n\n/** A mounted app route; `server` is the policy core it serves. */\nexport type McpAppSvelteKitHandler = McpSvelteKitHandler & {\n readonly server: McpAppServer;\n};\n\n/**\n * One-call application MCP endpoint: the app's declared `models`, the default\n * principal scope policy (see `createDefaultMcpAppServer`), principals from\n * the SvelteKit session locals, and optional bearer authentication, mounted\n * as the stateless {@link mountMcpRoute} `POST` endpoint. The origin check\n * (`checkOrigin`) is on by default because the default principal is an\n * ambient session cookie; set `trustedOrigins` for extra browser origins or\n * `checkOrigin: false` to opt out.\n *\n * @example\n * ```ts\n * // src/routes/api/mcp/+server.ts\n * import { mountMcpAppRoute } from '@happyvertical/smrt-app-mcp/sveltekit';\n * import { Item } from '$lib/objects/Item';\n * import { getDatabaseConfig } from '$lib/server/db';\n *\n * export const POST = mountMcpAppRoute({\n * models: [Item],\n * requiredScopes: ['items.read'],\n * smrtOptions: () => ({ db: getDatabaseConfig() }),\n * });\n * ```\n */\nexport function mountMcpAppRoute(\n options: MountMcpAppRouteOptions,\n): McpAppSvelteKitHandler {\n const {\n auth,\n bindPrincipal,\n extensions,\n resolvePrincipal,\n checkOrigin,\n trustedOrigins,\n ...serverOptions\n } = options;\n const server = createDefaultMcpAppServer(serverOptions);\n const handler = mountMcpRoute(server, {\n auth,\n bindPrincipal,\n extensions,\n // Default on: the default principal is an ambient session cookie.\n checkOrigin: checkOrigin ?? true,\n trustedOrigins,\n resolvePrincipal: resolvePrincipal ?? principalFromSessionLocals,\n });\n return Object.defineProperty(handler, 'server', {\n value: server,\n enumerable: true,\n }) as McpAppSvelteKitHandler;\n}\n\n/**\n * Mount the RFC 9728 protected-resource metadata document as a SvelteKit\n * `GET` handler, at the exact `metadataUrl` path the bearer challenge\n * advertises (for `/api/mcp`:\n * `src/routes/.well-known/oauth-protected-resource/api/mcp/+server.ts`).\n *\n * Returns 404 when the source yields no adapter (the `local` profile) or when\n * the request path is not the advertised metadata path. This handler does\n * not implement an OAuth authorization server; the metadata names the\n * operator-owned issuer.\n */\nexport function mountMcpProtectedResourceMetadataRoute(\n auth: McpRouteResourceAuthSource,\n): McpSvelteKitHandler {\n return async (event) => {\n const current = currentResourceAuth(auth);\n if (\n !current ||\n event.url.pathname !== new URL(current.metadataUrl).pathname\n )\n return new Response(null, {\n status: 404,\n headers: { 'Cache-Control': 'no-store' },\n });\n return current.metadataResponse();\n };\n}\n\n/**\n * The installed MCP SDK validates tools/call against a pre-Tasks result codec.\n * Intercept the extension before the SDK handler so the regular stateless HTTP\n * protocol stays untouched for every other method.\n */\nasync function maybeHandleTaskRequest(\n server: McpAppServer,\n principal: McpAppPrincipal | null,\n request: Request,\n): Promise<Response | null> {\n if (\n !server.tasksEnabled ||\n !server.callTask ||\n !server.getTask ||\n !server.updateTask ||\n !server.cancelTask ||\n request.method !== 'POST'\n ) {\n return null;\n }\n const body = (await request\n .clone()\n .json()\n .catch(() => null)) as {\n id?: string | number | null;\n jsonrpc?: string;\n method?: string;\n params?: Record<string, unknown>;\n } | null;\n if (body?.jsonrpc !== '2.0' || body.id === undefined) return null;\n const params = body.params ?? {};\n const taskTool =\n body.method === 'tools/call' &&\n typeof params.name === 'string' &&\n (await server.isTaskTool?.(params.name)) === true;\n const taskMethod = ['tasks/get', 'tasks/update', 'tasks/cancel'].includes(\n body.method ?? '',\n );\n if (!taskTool && !taskMethod) return null;\n\n // Keep task requests on the SDK's protocol-validation path until their\n // 2026 request envelope is known-good. In particular, never enqueue durable\n // work for malformed envelopes, unsupported revisions, or non-JSON bodies.\n // The fallback handler owns its wire-exact error response for those cases.\n if (!isJsonContentType(request.headers.get('content-type'))) return null;\n const classification = classifyInboundRequest({\n httpMethod: request.method,\n protocolVersionHeader:\n request.headers.get('mcp-protocol-version') ?? undefined,\n mcpMethodHeader: request.headers.get('mcp-method') ?? undefined,\n mcpNameHeader: request.headers.get('mcp-name') ?? undefined,\n body: body as never,\n });\n if (classification.kind === 'reject') {\n return jsonRpcResponse(\n body.id,\n undefined,\n {\n code: classification.code,\n message: classification.message,\n ...(classification.data === undefined\n ? {}\n : { data: classification.data }),\n },\n classification.httpStatus,\n );\n }\n if (\n classification.kind !== 'modern' ||\n classification.classification.revision !== '2026-07-28'\n ) {\n return null;\n }\n\n // The SDK normally performs this modern HTTP routing validation before\n // dispatch. Task responses are intercepted ahead of that SDK handler, so\n // retain the same fail-closed header/body contract here.\n const headerMismatch = taskHeaderMismatch(request, body.method, params);\n if (headerMismatch) {\n return jsonRpcResponse(body.id, undefined, headerMismatch, 400);\n }\n\n const clientCapabilities = asRecord(params._meta)[\n 'io.modelcontextprotocol/clientCapabilities'\n ];\n const clientSupportsTasks = Object.hasOwn(\n asRecord(asRecord(clientCapabilities).extensions),\n MCP_TASKS_EXTENSION,\n );\n\n // A synchronous fallback exists for tools/call; lifecycle methods do not.\n if (!clientSupportsTasks && body.method === 'tools/call') return null;\n if (!clientSupportsTasks) {\n return jsonRpcResponse(\n body.id,\n undefined,\n {\n code: -32021,\n message: 'Missing required client capability',\n data: {\n requiredCapabilities: { extensions: { [MCP_TASKS_EXTENSION]: {} } },\n },\n },\n 400,\n );\n }\n\n try {\n if (body.method === 'tools/call') {\n const result = await server.callTask({\n name: params.name as string,\n arguments: (params.arguments as Record<string, unknown>) ?? {},\n principal,\n });\n return jsonRpcResponse(body.id, result);\n }\n if (typeof params.taskId !== 'string') {\n return jsonRpcResponse(body.id, undefined, {\n code: -32602,\n message: 'taskId is required',\n });\n }\n if (body.method === 'tasks/get') {\n const task = await server.getTask({ taskId: params.taskId, principal });\n return jsonRpcResponse(body.id, { resultType: 'complete', ...task });\n }\n if (body.method === 'tasks/update') {\n await server.updateTask({\n taskId: params.taskId,\n inputResponses:\n params.inputResponses && typeof params.inputResponses === 'object'\n ? (params.inputResponses as Record<string, unknown>)\n : {},\n principal,\n });\n return jsonRpcResponse(body.id, { resultType: 'complete' });\n }\n await server.cancelTask({ taskId: params.taskId, principal });\n return jsonRpcResponse(body.id, { resultType: 'complete' });\n } catch (error) {\n if (error instanceof McpAccessError) {\n const { code, retryable } = error.metadata;\n return jsonRpcResponse(body.id, undefined, {\n code: error.status === 404 ? -32602 : -32600,\n message: error.message,\n data: {\n ...(typeof code === 'string' ? { code } : {}),\n ...(typeof retryable === 'boolean' ? { retryable } : {}),\n },\n });\n }\n const message =\n error instanceof Error ? error.message : 'Task operation failed';\n return jsonRpcResponse(body.id, undefined, { code: -32602, message });\n }\n}\n\nfunction taskHeaderMismatch(\n request: Request,\n method: string | undefined,\n params: Record<string, unknown>,\n): { code: number; message: string } | undefined {\n if (\n !method ||\n normalizeHeaderValue(request.headers.get('mcp-method') ?? '') !== method\n ) {\n return {\n code: -32020,\n message: 'Mcp-Method header must match the JSON-RPC method.',\n };\n }\n const toolName =\n method === 'tools/call' && typeof params.name === 'string'\n ? params.name\n : undefined;\n const headerName = request.headers.get('mcp-name');\n if (\n toolName !== undefined &&\n (headerName === null || decodeMcpHeaderValue(headerName) !== toolName)\n ) {\n return {\n code: -32020,\n message: 'Mcp-Name header must match the tools/call name.',\n };\n }\n return undefined;\n}\n\n/** Match the SDK's RFC 9110 optional-whitespace handling for MCP headers. */\nfunction normalizeHeaderValue(value: string): string {\n return value.replace(/^[\\t ]+|[\\t ]+$/g, '');\n}\n\n/** Decode the SDK's canonical Base64 sentinel for an MCP header value. */\nfunction decodeMcpHeaderValue(value: string): string | undefined {\n const normalized = normalizeHeaderValue(value);\n const prefix = '=?base64?';\n if (!normalized.startsWith(prefix) || !normalized.endsWith('?=')) {\n return normalized;\n }\n const encoded = normalized.slice(prefix.length, -2);\n if (\n !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(\n encoded,\n )\n ) {\n return undefined;\n }\n try {\n return new TextDecoder('utf-8', { fatal: true }).decode(\n Uint8Array.from(atob(encoded), (character) => character.charCodeAt(0)),\n );\n } catch {\n return undefined;\n }\n}\n\nfunction jsonRpcResponse(\n id: string | number | null,\n result?: unknown,\n error?: { code: number; message: string; data?: unknown },\n status = 200,\n): Response {\n return new Response(\n JSON.stringify({ jsonrpc: '2.0', id, ...(error ? { error } : { result }) }),\n { status, headers: { 'content-type': 'application/json' } },\n );\n}\n\nfunction asRecord(value: unknown): Record<string, unknown> {\n return value !== null && typeof value === 'object' && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : {};\n}\n\n/**\n * Mount `server.listTools` as a `GET` handler. Returns the tool list shape\n * `{ tools }` for compatibility with the stock MCP bridge.\n *\n * @deprecated Use {@link mountMcpRoute}. Existing REST-shaped mounts can use\n * this compatibility alias while migrating without a coordinated cutover.\n */\nexport function mountMcpToolsRoute(\n server: McpAppServer,\n options: MountMcpRouteOptions = {},\n): McpSvelteKitHandler {\n return async (event) => {\n try {\n const tools = await server.listTools(\n listToolsInput(resolveRequestPrincipal(event, options)),\n );\n return jsonResponse({ tools });\n } catch (error) {\n if (error instanceof McpAccessError) {\n return jsonResponse(mcpAccessErrorBody(error), error.status);\n }\n throw error;\n }\n };\n}\n\n/**\n * Mount `server.callTool` as a `POST` handler that expects\n * `{ name, arguments }` in the JSON body.\n *\n * @deprecated Use {@link mountMcpRoute}. Existing REST-shaped mounts can use\n * this compatibility alias while migrating without a coordinated cutover.\n */\nexport function mountMcpCallRoute(\n server: McpAppServer,\n options: MountMcpRouteOptions = {},\n): McpSvelteKitHandler {\n return async (event) => {\n const body = (await event.request.json().catch(() => null)) as {\n arguments?: Record<string, unknown>;\n name?: string;\n } | null;\n\n if (!body?.name) {\n return jsonResponse({ error: 'name is required.' }, 400);\n }\n\n const input: CallToolInput = {\n arguments: body.arguments ?? {},\n name: body.name,\n principal: resolveRequestPrincipal(event, options).principal,\n };\n\n try {\n return jsonResponse(await server.callTool(input));\n } catch (error) {\n if (error instanceof McpAccessError) {\n return jsonResponse(mcpAccessErrorBody(error), error.status);\n }\n throw error;\n }\n };\n}\n\nfunction mcpAccessErrorBody(error: McpAccessError): unknown {\n const { code, retryable } = error.metadata;\n // Preserve the legacy shape unless an error deliberately opts into the\n // shared structured failure contract.\n if (!code) return { error: error.message };\n return {\n error: {\n ok: false,\n code,\n message: error.message,\n status: error.status,\n ...(retryable === undefined ? {} : { retryable }),\n },\n };\n}\n\nfunction jsonResponse(body: unknown, status = 200): Response {\n return new Response(JSON.stringify(body), {\n status,\n headers: { 'content-type': 'application/json' },\n });\n}\n\nexport type {\n CreateDefaultMcpAppServerOptions,\n McpAppModel,\n} from './defaults.js';\nexport { McpAccessError } from './errors.js';\nexport type { McpAppPrincipal, McpAppServer } from './server.js';\n"],"mappings":"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"}
|
package/dist/tools.d.ts
CHANGED
|
@@ -40,6 +40,24 @@ export declare function isPublicMcpTool(tool: {
|
|
|
40
40
|
readOnlyHint?: boolean;
|
|
41
41
|
};
|
|
42
42
|
}, patterns: readonly string[]): boolean;
|
|
43
|
+
/** Canonical tool effect vocabulary, shared with WebMCP `effects`. */
|
|
44
|
+
export type McpToolEffect = 'read' | 'write' | 'destructive';
|
|
45
|
+
/** Every supported {@link McpToolEffect}. */
|
|
46
|
+
export declare const MCP_TOOL_EFFECTS: readonly McpToolEffect[];
|
|
47
|
+
/**
|
|
48
|
+
* Classify a descriptor's effect. `read` uses the same read-only detection as
|
|
49
|
+
* {@link isPublicMcpTool} (the canonical `readOnlyHint`, else the `_list`/`_get`
|
|
50
|
+
* name rule). Any other tool is `destructive` unless it explicitly declares
|
|
51
|
+
* `destructiveHint: false` (MCP's default for a non-read-only tool), so an
|
|
52
|
+
* undeclared tool never counts as a mere write.
|
|
53
|
+
*/
|
|
54
|
+
export declare function mcpToolEffect(tool: {
|
|
55
|
+
name: string;
|
|
56
|
+
annotations?: {
|
|
57
|
+
readOnlyHint?: boolean;
|
|
58
|
+
destructiveHint?: boolean;
|
|
59
|
+
};
|
|
60
|
+
}): McpToolEffect;
|
|
43
61
|
/**
|
|
44
62
|
* Lower-case `<class>_` prefixes the app considers "allowed core tools"
|
|
45
63
|
* given a list of SMRT class names. Used to build the allow-list for
|
package/dist/tools.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAkB7E;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAE5D;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,SAAS,MAAM,EAAE,GAC1B,OAAO,CAKT;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE;IACJ,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE;QAAE,YAAY,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;CAC1C,EACD,QAAQ,EAAE,SAAS,MAAM,EAAE,GAC1B,OAAO,CAOT;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,UAAU,EAAE,SAAS,MAAM,EAAE,GAC5B,WAAW,CAAC,MAAM,CAAC,CAErB;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,WAAW,CAAC,MAAM,CAAC,GAC5B,OAAO,CAKT;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAEvE"}
|
|
1
|
+
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAkB7E;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAE5D;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,SAAS,MAAM,EAAE,GAC1B,OAAO,CAKT;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE;IACJ,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE;QAAE,YAAY,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;CAC1C,EACD,QAAQ,EAAE,SAAS,MAAM,EAAE,GAC1B,OAAO,CAOT;AAED,sEAAsE;AACtE,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,OAAO,GAAG,aAAa,CAAC;AAE7D,6CAA6C;AAC7C,eAAO,MAAM,gBAAgB,EAAE,SAAS,aAAa,EAInD,CAAC;AAEH;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE;QAAE,YAAY,CAAC,EAAE,OAAO,CAAC;QAAC,eAAe,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;CACrE,GAAG,aAAa,CAKhB;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,UAAU,EAAE,SAAS,MAAM,EAAE,GAC5B,WAAW,CAAC,MAAM,CAAC,CAErB;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,WAAW,CAAC,MAAM,CAAC,GAC5B,OAAO,CAKT;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAEvE"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@happyvertical/smrt-app-mcp",
|
|
3
|
-
"version": "0.53.
|
|
3
|
+
"version": "0.53.4",
|
|
4
4
|
"description": "App-runtime MCP server scaffolding for SMRT apps — `createMcpAppServer` plus transport adapters (SvelteKit today) for exposing a SMRT app's MCP surface over HTTP.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -47,8 +47,8 @@
|
|
|
47
47
|
],
|
|
48
48
|
"author": "HappyVertical",
|
|
49
49
|
"dependencies": {
|
|
50
|
-
"@happyvertical/smrt-core": "0.53.
|
|
51
|
-
"@happyvertical/smrt-jobs": "0.53.
|
|
50
|
+
"@happyvertical/smrt-core": "0.53.4",
|
|
51
|
+
"@happyvertical/smrt-jobs": "0.53.4",
|
|
52
52
|
"@happyvertical/sql": "^0.98.0",
|
|
53
53
|
"@modelcontextprotocol/server": "2.0.0",
|
|
54
54
|
"jose": "^6.2.12",
|
|
@@ -1,184 +0,0 @@
|
|
|
1
|
-
import { ProtocolError, ProtocolErrorCode, Server } from "@modelcontextprotocol/server";
|
|
2
|
-
//#region src/errors.ts
|
|
3
|
-
var MCP_TOOL_ACCESS_DENIED_CODE = "mcp_tool_access_denied";
|
|
4
|
-
var McpAccessError = class extends Error {
|
|
5
|
-
constructor(status, message, metadata = {}) {
|
|
6
|
-
super(message);
|
|
7
|
-
this.status = status;
|
|
8
|
-
this.metadata = metadata;
|
|
9
|
-
this.name = "McpAccessError";
|
|
10
|
-
}
|
|
11
|
-
status;
|
|
12
|
-
metadata;
|
|
13
|
-
};
|
|
14
|
-
//#endregion
|
|
15
|
-
//#region src/tools.ts
|
|
16
|
-
function matchesToolPattern(toolName, pattern) {
|
|
17
|
-
if (!pattern) return false;
|
|
18
|
-
if (pattern === "*") return true;
|
|
19
|
-
const parts = pattern.split("*");
|
|
20
|
-
if (parts.length === 1) return toolName === pattern;
|
|
21
|
-
let cursor = 0;
|
|
22
|
-
if (parts[0] && !toolName.startsWith(parts[0])) return false;
|
|
23
|
-
for (const part of parts) {
|
|
24
|
-
if (!part) continue;
|
|
25
|
-
const index = toolName.indexOf(part, cursor);
|
|
26
|
-
if (index < 0) return false;
|
|
27
|
-
cursor = index + part.length;
|
|
28
|
-
}
|
|
29
|
-
const last = parts.at(-1);
|
|
30
|
-
return !last || toolName.endsWith(last);
|
|
31
|
-
}
|
|
32
|
-
function isReadOnlyToolName(toolName) {
|
|
33
|
-
return toolName.endsWith("_list") || toolName.endsWith("_get");
|
|
34
|
-
}
|
|
35
|
-
function isPublicToolName(toolName, patterns) {
|
|
36
|
-
return isReadOnlyToolName(toolName) && patterns.some((pattern) => matchesToolPattern(toolName, pattern));
|
|
37
|
-
}
|
|
38
|
-
function isPublicMcpTool(tool, patterns) {
|
|
39
|
-
return (tool.annotations?.readOnlyHint ?? isReadOnlyToolName(tool.name)) && patterns.some((pattern) => matchesToolPattern(tool.name, pattern));
|
|
40
|
-
}
|
|
41
|
-
function classNamePrefixes(classNames) {
|
|
42
|
-
return new Set(classNames.map((className) => `${className.toLowerCase()}_`));
|
|
43
|
-
}
|
|
44
|
-
function isAllowedCoreTool(toolName, prefixes) {
|
|
45
|
-
for (const prefix of prefixes) if (toolName.startsWith(prefix)) return true;
|
|
46
|
-
return false;
|
|
47
|
-
}
|
|
48
|
-
function compareMcpToolNames(left, right) {
|
|
49
|
-
return left < right ? -1 : left > right ? 1 : 0;
|
|
50
|
-
}
|
|
51
|
-
//#endregion
|
|
52
|
-
//#region src/protocol.ts
|
|
53
|
-
var MCP_TASKS_EXTENSION = "io.modelcontextprotocol/tasks";
|
|
54
|
-
var DEFAULT_TOOL_LIST_CACHE_HINT = {
|
|
55
|
-
ttlMs: 864e5,
|
|
56
|
-
cacheScope: "private"
|
|
57
|
-
};
|
|
58
|
-
async function createMcpProtocolServerForRequest(appServer, options = {}) {
|
|
59
|
-
const principal = options.principal ?? null;
|
|
60
|
-
let extensions = {};
|
|
61
|
-
if (options.extensions) try {
|
|
62
|
-
const tools = await appServer.listTools({ principal });
|
|
63
|
-
const projected = await options.extensions({
|
|
64
|
-
principal,
|
|
65
|
-
tools
|
|
66
|
-
});
|
|
67
|
-
assertExtensionMetadata(projected);
|
|
68
|
-
if (Object.hasOwn(projected, "io.modelcontextprotocol/tasks")) throw new TypeError("Reserved extension.");
|
|
69
|
-
extensions = structuredClone(projected);
|
|
70
|
-
} catch {
|
|
71
|
-
throw new ProtocolError(ProtocolErrorCode.InternalError, "Extension discovery unavailable.");
|
|
72
|
-
}
|
|
73
|
-
return protocolServer(appServer, { principal }, extensions);
|
|
74
|
-
}
|
|
75
|
-
async function resolvePrincipal(option, context) {
|
|
76
|
-
if (typeof option === "function") return await option(context) ?? null;
|
|
77
|
-
return option ?? null;
|
|
78
|
-
}
|
|
79
|
-
function createMcpProtocolServer(appServer, options = {}) {
|
|
80
|
-
if ("extensions" in options) throw new TypeError("Extension discovery requires createMcpProtocolServerForRequest.");
|
|
81
|
-
return protocolServer(appServer, options, {});
|
|
82
|
-
}
|
|
83
|
-
function protocolServer(appServer, options, extensions) {
|
|
84
|
-
const tasksEnabled = appServer.tasksEnabled && typeof options.principal !== "function" && Boolean(options.principal?.id);
|
|
85
|
-
const server = new Server(appServer.serverInfo, {
|
|
86
|
-
capabilities: {
|
|
87
|
-
tools: {},
|
|
88
|
-
...appServer.listResources && appServer.readResource ? { resources: {} } : {},
|
|
89
|
-
...tasksEnabled || Object.keys(extensions).length ? { extensions: {
|
|
90
|
-
...extensions,
|
|
91
|
-
...tasksEnabled ? { [MCP_TASKS_EXTENSION]: {} } : {}
|
|
92
|
-
} } : {}
|
|
93
|
-
},
|
|
94
|
-
cacheHints: { "tools/list": DEFAULT_TOOL_LIST_CACHE_HINT }
|
|
95
|
-
});
|
|
96
|
-
server.setRequestHandler("tools/list", async (_request, context) => {
|
|
97
|
-
const principal = await resolvePrincipal(options.principal, context);
|
|
98
|
-
const cacheHint = await appServer.getToolsListCacheHint?.() ?? DEFAULT_TOOL_LIST_CACHE_HINT;
|
|
99
|
-
return {
|
|
100
|
-
tools: [...await appServer.listTools({ principal })].sort((left, right) => compareMcpToolNames(left.name, right.name)),
|
|
101
|
-
...cacheHint
|
|
102
|
-
};
|
|
103
|
-
});
|
|
104
|
-
if (appServer.listResources && appServer.readResource) {
|
|
105
|
-
server.setRequestHandler("resources/list", async (_request, context) => ({
|
|
106
|
-
resources: await appServer.listResources({ principal: await resolvePrincipal(options.principal, context) }),
|
|
107
|
-
ttlMs: 0,
|
|
108
|
-
cacheScope: "private"
|
|
109
|
-
}));
|
|
110
|
-
server.setRequestHandler("resources/read", async (request, context) => {
|
|
111
|
-
try {
|
|
112
|
-
return {
|
|
113
|
-
contents: [await appServer.readResource({
|
|
114
|
-
uri: request.params.uri,
|
|
115
|
-
principal: await resolvePrincipal(options.principal, context)
|
|
116
|
-
})],
|
|
117
|
-
ttlMs: 0,
|
|
118
|
-
cacheScope: "private"
|
|
119
|
-
};
|
|
120
|
-
} catch (error) {
|
|
121
|
-
if (error instanceof McpAccessError) throw new ProtocolError(ProtocolErrorCode.InvalidParams, "MCP resource is not available.");
|
|
122
|
-
throw error;
|
|
123
|
-
}
|
|
124
|
-
});
|
|
125
|
-
}
|
|
126
|
-
server.setRequestHandler("tools/call", async (request, context) => {
|
|
127
|
-
try {
|
|
128
|
-
return await appServer.callTool({
|
|
129
|
-
name: request.params.name,
|
|
130
|
-
arguments: request.params.arguments,
|
|
131
|
-
principal: await resolvePrincipal(options.principal, context)
|
|
132
|
-
});
|
|
133
|
-
} catch (error) {
|
|
134
|
-
if (error instanceof McpAccessError) {
|
|
135
|
-
const { code, retryable } = error.metadata;
|
|
136
|
-
throw new ProtocolError(error.status === 404 ? ProtocolErrorCode.InvalidParams : ProtocolErrorCode.InvalidRequest, error.message, {
|
|
137
|
-
...typeof code === "string" ? { code } : {},
|
|
138
|
-
...typeof retryable === "boolean" ? { retryable } : {}
|
|
139
|
-
});
|
|
140
|
-
}
|
|
141
|
-
throw error;
|
|
142
|
-
}
|
|
143
|
-
});
|
|
144
|
-
return server;
|
|
145
|
-
}
|
|
146
|
-
function assertExtensionMetadata(value) {
|
|
147
|
-
let budget = 65536;
|
|
148
|
-
const visit = (v, depth) => {
|
|
149
|
-
if (--budget < 0 || depth > 16) throw new TypeError("Extension metadata exceeds limits.");
|
|
150
|
-
if (v === null || typeof v === "boolean") return;
|
|
151
|
-
if (typeof v === "number" && Number.isFinite(v)) return;
|
|
152
|
-
if (typeof v === "string") {
|
|
153
|
-
budget -= new TextEncoder().encode(v).length;
|
|
154
|
-
if (budget < 0) throw new TypeError("Extension metadata exceeds limits.");
|
|
155
|
-
return;
|
|
156
|
-
}
|
|
157
|
-
if (Array.isArray(v)) {
|
|
158
|
-
if (v.length > 1024) throw new TypeError("Extension array exceeds limits.");
|
|
159
|
-
for (const item of v) visit(item, depth + 1);
|
|
160
|
-
return;
|
|
161
|
-
}
|
|
162
|
-
if (!v || typeof v !== "object" || ![Object.prototype, null].includes(Object.getPrototypeOf(v))) throw new TypeError("Expected plain extension object.");
|
|
163
|
-
const names = Object.keys(v);
|
|
164
|
-
if (names.length > 256) throw new TypeError("Extension object exceeds limits.");
|
|
165
|
-
for (const name of names) {
|
|
166
|
-
if ([
|
|
167
|
-
"__proto__",
|
|
168
|
-
"constructor",
|
|
169
|
-
"prototype"
|
|
170
|
-
].includes(name)) throw new TypeError("Unsafe extension key.");
|
|
171
|
-
const descriptor = Object.getOwnPropertyDescriptor(v, name);
|
|
172
|
-
if (!descriptor || !("value" in descriptor)) throw new TypeError("Expected inert extension metadata.");
|
|
173
|
-
visit(name, depth + 1);
|
|
174
|
-
visit(descriptor.value, depth + 1);
|
|
175
|
-
}
|
|
176
|
-
};
|
|
177
|
-
visit(value, 0);
|
|
178
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) throw new TypeError("Expected extension map.");
|
|
179
|
-
for (const [name, entry] of Object.entries(value)) if (!name.trim() || !entry || typeof entry !== "object" || Array.isArray(entry)) throw new TypeError("Expected named extension object.");
|
|
180
|
-
}
|
|
181
|
-
//#endregion
|
|
182
|
-
export { compareMcpToolNames as a, isPublicToolName as c, MCP_TOOL_ACCESS_DENIED_CODE as d, McpAccessError as f, classNamePrefixes as i, isReadOnlyToolName as l, createMcpProtocolServer as n, isAllowedCoreTool as o, createMcpProtocolServerForRequest as r, isPublicMcpTool as s, MCP_TASKS_EXTENSION as t, matchesToolPattern as u };
|
|
183
|
-
|
|
184
|
-
//# sourceMappingURL=protocol-Ce0U_XSp.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"protocol-Ce0U_XSp.js","names":[],"sources":["../../src/errors.ts","../../src/tools.ts","../../src/protocol.ts"],"sourcesContent":["/** Machine-readable code for a principal policy denial. */\nexport const MCP_TOOL_ACCESS_DENIED_CODE = 'mcp_tool_access_denied';\n\n/**\n * Metadata that is safe to expose for an app-MCP access failure. Policy\n * implementations must not place principal, scope, tool, or internal-error\n * details here.\n */\nexport interface McpAccessErrorMetadata {\n code?: string;\n retryable?: boolean;\n}\n\n/**\n * Error returned by the MCP app server when a caller tries to use a tool\n * they are not allowed to access. The HTTP layer should map `status` onto\n * the response status code.\n */\nexport class McpAccessError extends Error {\n constructor(\n readonly status: number,\n message: string,\n readonly metadata: McpAccessErrorMetadata = {},\n ) {\n super(message);\n this.name = 'McpAccessError';\n }\n}\n","/**\n * Tool-name policy helpers — used by `McpAppServer` to filter the full set\n * of generated tools down to what the calling principal is allowed to see,\n * and to decide whether an unauthenticated tool call should be permitted.\n *\n * @packageDocumentation\n */\n\n/**\n * Match a tool name against a glob-ish pattern with `*` wildcards.\n *\n * - Empty pattern → never matches.\n * - `*` → matches everything.\n * - `prefix_*` → matches anything starting with `prefix_`.\n * - `*_suffix` → matches anything ending with `_suffix`.\n * - `a_*_b` → matches any name containing `a_`, then any text, then `_b`.\n *\n * No regex characters are special besides `*` — the input is treated as a\n * literal string with star wildcards.\n */\nexport function matchesToolPattern(toolName: string, pattern: string): boolean {\n if (!pattern) return false;\n if (pattern === '*') return true;\n\n const parts = pattern.split('*');\n if (parts.length === 1) return toolName === pattern;\n\n let cursor = 0;\n if (parts[0] && !toolName.startsWith(parts[0])) return false;\n for (const part of parts) {\n if (!part) continue;\n const index = toolName.indexOf(part, cursor);\n if (index < 0) return false;\n cursor = index + part.length;\n }\n\n const last = parts.at(-1);\n return !last || toolName.endsWith(last);\n}\n\n/**\n * Read-only tool detection. Generated SMRT MCP tools follow the naming\n * convention `<class>_<verb>`; we treat `_list` and `_get` as read-only.\n */\nexport function isReadOnlyToolName(toolName: string): boolean {\n return toolName.endsWith('_list') || toolName.endsWith('_get');\n}\n\n/**\n * Check whether a tool name is currently allowed for unauthenticated callers\n * given the configured public-tool patterns. Only read-only tools may ever\n * be public, regardless of pattern.\n */\nexport function isPublicToolName(\n toolName: string,\n patterns: readonly string[],\n): boolean {\n return (\n isReadOnlyToolName(toolName) &&\n patterns.some((pattern) => matchesToolPattern(toolName, pattern))\n );\n}\n\n/**\n * Check public eligibility using a descriptor's canonical read annotation when\n * available. The legacy name rule remains for generated catalogs predating\n * canonical annotations.\n */\nexport function isPublicMcpTool(\n tool: {\n name: string;\n annotations?: { readOnlyHint?: boolean };\n },\n patterns: readonly string[],\n): boolean {\n const readOnly =\n tool.annotations?.readOnlyHint ?? isReadOnlyToolName(tool.name);\n return (\n readOnly &&\n patterns.some((pattern) => matchesToolPattern(tool.name, pattern))\n );\n}\n\n/**\n * Lower-case `<class>_` prefixes the app considers \"allowed core tools\"\n * given a list of SMRT class names. Used to build the allow-list for\n * `McpAppServer.listTools`.\n */\nexport function classNamePrefixes(\n classNames: readonly string[],\n): ReadonlySet<string> {\n return new Set(classNames.map((className) => `${className.toLowerCase()}_`));\n}\n\n/**\n * Whether a given tool name starts with any of the configured class\n * prefixes.\n */\nexport function isAllowedCoreTool(\n toolName: string,\n prefixes: ReadonlySet<string>,\n): boolean {\n for (const prefix of prefixes) {\n if (toolName.startsWith(prefix)) return true;\n }\n return false;\n}\n\n/**\n * Compare tool names by Unicode code unit, rather than the host locale, so a\n * catalog has one byte-stable order across every runtime.\n */\nexport function compareMcpToolNames(left: string, right: string): number {\n return left < right ? -1 : left > right ? 1 : 0;\n}\n","/** MCP SDK v2 protocol adapter for the framework-neutral app server core. */\n\nimport type { MCPTool } from '@happyvertical/smrt-core/generators/mcp';\nimport {\n type CallToolResult,\n type JSONObject,\n ProtocolError,\n ProtocolErrorCode,\n Server,\n type ServerContext,\n type Tool,\n} from '@modelcontextprotocol/server';\nimport { McpAccessError } from './errors.js';\nimport type { McpAppPrincipal, McpAppServer } from './server.js';\nimport { compareMcpToolNames } from './tools.js';\n\nexport const MCP_TASKS_EXTENSION = 'io.modelcontextprotocol/tasks';\n\nconst DEFAULT_TOOL_LIST_CACHE_HINT = {\n ttlMs: 86_400_000,\n cacheScope: 'private' as const,\n};\n\nexport interface McpProtocolServerOptions {\n /** Resolve the authenticated application principal for each MCP request. */\n principal?:\n | McpAppPrincipal\n | null\n | ((\n context: ServerContext,\n ) => McpAppPrincipal | null | Promise<McpAppPrincipal | null>);\n}\n\n/** Request-bound options for asynchronous catalog extension projection. */\nexport interface McpProtocolRequestOptions {\n principal?: McpAppPrincipal | null;\n /** Optional inert extension metadata, recomputed from this request's authorized catalog. */\n extensions?: (context: {\n principal: McpAppPrincipal | null;\n tools: readonly MCPTool[];\n }) =>\n | Record<string, Record<string, unknown>>\n | Promise<Record<string, Record<string, unknown>>>;\n}\n\n/** Prepare native capabilities before the SDK HTTP factory installs discovery handlers. */\nexport async function createMcpProtocolServerForRequest(\n appServer: McpAppServer,\n options: McpProtocolRequestOptions = {},\n): Promise<Server> {\n const principal = options.principal ?? null;\n let extensions: Record<string, JSONObject> = {};\n if (options.extensions) {\n try {\n const tools = await appServer.listTools({ principal });\n const projected = await options.extensions({ principal, tools });\n assertExtensionMetadata(projected);\n if (Object.hasOwn(projected, MCP_TASKS_EXTENSION))\n throw new TypeError('Reserved extension.');\n extensions = structuredClone(projected);\n } catch {\n throw new ProtocolError(\n ProtocolErrorCode.InternalError,\n 'Extension discovery unavailable.',\n );\n }\n }\n return protocolServer(appServer, { principal }, extensions);\n}\n\nasync function resolvePrincipal(\n option: McpProtocolServerOptions['principal'],\n context: ServerContext,\n): Promise<McpAppPrincipal | null> {\n if (typeof option === 'function') return (await option(context)) ?? null;\n return option ?? null;\n}\n\n/**\n * Adapt an app MCP core to the SDK v2 low-level server protocol.\n *\n * Transport ownership remains with the caller. In particular, this does not\n * add a production HTTP endpoint; it is safe to compose with `serveStdio` or\n * `createMcpHandler` in a deployment that supplies its own authentication.\n */\nexport function createMcpProtocolServer(\n appServer: McpAppServer,\n options: McpProtocolServerOptions = {},\n): Server {\n if ('extensions' in options) {\n throw new TypeError(\n 'Extension discovery requires createMcpProtocolServerForRequest.',\n );\n }\n return protocolServer(appServer, options, {});\n}\n\nfunction protocolServer(\n appServer: McpAppServer,\n options: McpProtocolServerOptions,\n extensions: Record<string, JSONObject>,\n): Server {\n // Task lifecycle records are owner-scoped. Unlike ordinary public read-only\n // tools, they cannot be safely exposed without a stable principal id.\n const tasksEnabled =\n appServer.tasksEnabled &&\n typeof options.principal !== 'function' &&\n Boolean(options.principal?.id);\n const server = new Server(appServer.serverInfo, {\n capabilities: {\n tools: {},\n ...(appServer.listResources && appServer.readResource\n ? { resources: {} }\n : {}),\n ...(tasksEnabled || Object.keys(extensions).length\n ? {\n extensions: {\n ...extensions,\n ...(tasksEnabled ? { [MCP_TASKS_EXTENSION]: {} } : {}),\n },\n }\n : {}),\n } as never,\n cacheHints: { 'tools/list': DEFAULT_TOOL_LIST_CACHE_HINT },\n });\n\n server.setRequestHandler('tools/list', async (_request, context) => {\n const principal = await resolvePrincipal(options.principal, context);\n const cacheHint =\n (await appServer.getToolsListCacheHint?.()) ??\n DEFAULT_TOOL_LIST_CACHE_HINT;\n return {\n tools: [...(await appServer.listTools({ principal }))].sort(\n (left, right) => compareMcpToolNames(left.name, right.name),\n ) as Tool[],\n ...cacheHint,\n };\n });\n\n if (appServer.listResources && appServer.readResource) {\n server.setRequestHandler('resources/list', async (_request, context) => ({\n resources: await appServer.listResources!({\n principal: await resolvePrincipal(options.principal, context),\n }),\n ttlMs: 0,\n cacheScope: 'private',\n }));\n server.setRequestHandler('resources/read', async (request, context) => {\n try {\n return {\n contents: [\n await appServer.readResource!({\n uri: request.params.uri,\n principal: await resolvePrincipal(options.principal, context),\n }),\n ],\n ttlMs: 0,\n cacheScope: 'private',\n };\n } catch (error) {\n if (error instanceof McpAccessError)\n throw new ProtocolError(\n ProtocolErrorCode.InvalidParams,\n 'MCP resource is not available.',\n );\n throw error;\n }\n });\n }\n\n server.setRequestHandler('tools/call', async (request, context) => {\n try {\n return (await appServer.callTool({\n name: request.params.name,\n arguments: request.params.arguments,\n principal: await resolvePrincipal(options.principal, context),\n })) as CallToolResult;\n } catch (error) {\n if (error instanceof McpAccessError) {\n const { code, retryable } = error.metadata;\n throw new ProtocolError(\n error.status === 404\n ? ProtocolErrorCode.InvalidParams\n : ProtocolErrorCode.InvalidRequest,\n error.message,\n {\n ...(typeof code === 'string' ? { code } : {}),\n ...(typeof retryable === 'boolean' ? { retryable } : {}),\n },\n );\n }\n throw error;\n }\n });\n\n return server;\n}\n\n/** Bound plugin discovery metadata without allowing executable values or provider-specific types. */\nfunction assertExtensionMetadata(\n value: unknown,\n): asserts value is Record<string, JSONObject> {\n let budget = 65536;\n const visit = (v: unknown, depth: number): void => {\n if (--budget < 0 || depth > 16)\n throw new TypeError('Extension metadata exceeds limits.');\n if (v === null || typeof v === 'boolean') return;\n if (typeof v === 'number' && Number.isFinite(v)) return;\n if (typeof v === 'string') {\n budget -= new TextEncoder().encode(v).length;\n if (budget < 0) throw new TypeError('Extension metadata exceeds limits.');\n return;\n }\n if (Array.isArray(v)) {\n if (v.length > 1024)\n throw new TypeError('Extension array exceeds limits.');\n for (const item of v) visit(item, depth + 1);\n return;\n }\n if (\n !v ||\n typeof v !== 'object' ||\n ![Object.prototype, null].includes(Object.getPrototypeOf(v))\n )\n throw new TypeError('Expected plain extension object.');\n const names = Object.keys(v);\n if (names.length > 256)\n throw new TypeError('Extension object exceeds limits.');\n for (const name of names) {\n if (['__proto__', 'constructor', 'prototype'].includes(name))\n throw new TypeError('Unsafe extension key.');\n const descriptor = Object.getOwnPropertyDescriptor(v, name);\n if (!descriptor || !('value' in descriptor))\n throw new TypeError('Expected inert extension metadata.');\n visit(name, depth + 1);\n visit(descriptor.value, depth + 1);\n }\n };\n visit(value, 0);\n if (!value || typeof value !== 'object' || Array.isArray(value))\n throw new TypeError('Expected extension map.');\n for (const [name, entry] of Object.entries(value)) {\n if (\n !name.trim() ||\n !entry ||\n typeof entry !== 'object' ||\n Array.isArray(entry)\n )\n throw new TypeError('Expected named extension object.');\n }\n}\n"],"mappings":";;AACO,IAAM,8BAA8B;AAiBpC,IAAM,iBAAN,cAA6B,MAAM;CACxC,YACW,QACT,SACS,WAAmC,CAAC,GAC7C;EACA,MAAM,OAAO;EAJJ,KAAA,SAAA;EAEA,KAAA,WAAA;EAGT,KAAK,OAAO;CACd;CANW;CAEA;AAKb;;;ACPO,SAAS,mBAAmB,UAAkB,SAA0B;CAC7E,IAAI,CAAC,SAAS,OAAO;CACrB,IAAI,YAAY,KAAK,OAAO;CAE5B,MAAM,QAAQ,QAAQ,MAAM,GAAG;CAC/B,IAAI,MAAM,WAAW,GAAG,OAAO,aAAa;CAE5C,IAAI,SAAS;CACb,IAAI,MAAM,MAAM,CAAC,SAAS,WAAW,MAAM,EAAE,GAAG,OAAO;CACvD,KAAA,MAAW,QAAQ,OAAO;EACxB,IAAI,CAAC,MAAM;EACX,MAAM,QAAQ,SAAS,QAAQ,MAAM,MAAM;EAC3C,IAAI,QAAQ,GAAG,OAAO;EACtB,SAAS,QAAQ,KAAK;CACxB;CAEA,MAAM,OAAO,MAAM,GAAG,EAAE;CACxB,OAAO,CAAC,QAAQ,SAAS,SAAS,IAAI;AACxC;AAMO,SAAS,mBAAmB,UAA2B;CAC5D,OAAO,SAAS,SAAS,OAAO,KAAK,SAAS,SAAS,MAAM;AAC/D;AAOO,SAAS,iBACd,UACA,UACS;CACT,OACE,mBAAmB,QAAQ,KAC3B,SAAS,MAAM,YAAY,mBAAmB,UAAU,OAAO,CAAC;AAEpE;AAOO,SAAS,gBACd,MAIA,UACS;CAGT,QADE,KAAK,aAAa,gBAAgB,mBAAmB,KAAK,IAAI,MAG9D,SAAS,MAAM,YAAY,mBAAmB,KAAK,MAAM,OAAO,CAAC;AAErE;AAOO,SAAS,kBACd,YACqB;CACrB,OAAO,IAAI,IAAI,WAAW,KAAK,cAAc,GAAG,UAAU,YAAY,EAAC,EAAG,CAAC;AAC7E;AAMO,SAAS,kBACd,UACA,UACS;CACT,KAAA,MAAW,UAAU,UACnB,IAAI,SAAS,WAAW,MAAM,GAAG,OAAO;CAE1C,OAAO;AACT;AAMO,SAAS,oBAAoB,MAAc,OAAuB;CACvE,OAAO,OAAO,QAAQ,KAAK,OAAO,QAAQ,IAAI;AAChD;;;AClGO,IAAM,sBAAsB;AAEnC,IAAM,+BAA+B;CACnC,OAAO;CACP,YAAY;AACd;AAyBA,eAAsB,kCACpB,WACA,UAAqC,CAAC,GACrB;CACjB,MAAM,YAAY,QAAQ,aAAa;CACvC,IAAI,aAAyC,CAAC;CAC9C,IAAI,QAAQ,YACV,IAAI;EACF,MAAM,QAAQ,MAAM,UAAU,UAAU,EAAE,UAAU,CAAC;EACrD,MAAM,YAAY,MAAM,QAAQ,WAAW;GAAE;GAAW;EAAM,CAAC;EAC/D,wBAAwB,SAAS;EACjC,IAAI,OAAO,OAAO,WAAA,+BAA8B,GAC9C,MAAM,IAAI,UAAU,qBAAqB;EAC3C,aAAa,gBAAgB,SAAS;CACxC,QAAQ;EACN,MAAM,IAAI,cACR,kBAAkB,eAClB,kCACF;CACF;CAEF,OAAO,eAAe,WAAW,EAAE,UAAU,GAAG,UAAU;AAC5D;AAEA,eAAe,iBACb,QACA,SACiC;CACjC,IAAI,OAAO,WAAW,YAAY,OAAQ,MAAM,OAAO,OAAO,KAAM;CACpE,OAAO,UAAU;AACnB;AASO,SAAS,wBACd,WACA,UAAoC,CAAC,GAC7B;CACR,IAAI,gBAAgB,SAClB,MAAM,IAAI,UACR,iEACF;CAEF,OAAO,eAAe,WAAW,SAAS,CAAC,CAAC;AAC9C;AAEA,SAAS,eACP,WACA,SACA,YACQ;CAGR,MAAM,eACJ,UAAU,gBACV,OAAO,QAAQ,cAAc,cAC7B,QAAQ,QAAQ,WAAW,EAAE;CAC/B,MAAM,SAAS,IAAI,OAAO,UAAU,YAAY;EAC9C,cAAc;GACZ,OAAO,CAAC;GACR,GAAI,UAAU,iBAAiB,UAAU,eACrC,EAAE,WAAW,CAAC,EAAE,IAChB,CAAC;GACL,GAAI,gBAAgB,OAAO,KAAK,UAAU,CAAA,CAAE,SACxC,EACE,YAAY;IACV,GAAG;IACH,GAAI,eAAe,GAAG,sBAAsB,CAAC,EAAE,IAAI,CAAC;GACtD,EACF,IACA,CAAC;EACP;EACA,YAAY,EAAE,cAAc,6BAA6B;CAC3D,CAAC;CAED,OAAO,kBAAkB,cAAc,OAAO,UAAU,YAAY;EAClE,MAAM,YAAY,MAAM,iBAAiB,QAAQ,WAAW,OAAO;EACnE,MAAM,YACH,MAAM,UAAU,wBAAwB,KACzC;EACF,OAAO;GACL,OAAO,CAAC,GAAI,MAAM,UAAU,UAAU,EAAE,UAAU,CAAC,CAAE,CAAA,CAAE,MACpD,MAAM,UAAU,oBAAoB,KAAK,MAAM,MAAM,IAAI,CAC5D;GACA,GAAG;EACL;CACF,CAAC;CAED,IAAI,UAAU,iBAAiB,UAAU,cAAc;EACrD,OAAO,kBAAkB,kBAAkB,OAAO,UAAU,aAAa;GACvE,WAAW,MAAM,UAAU,cAAe,EACxC,WAAW,MAAM,iBAAiB,QAAQ,WAAW,OAAO,EAC9D,CAAC;GACD,OAAO;GACP,YAAY;EACd,EAAE;EACF,OAAO,kBAAkB,kBAAkB,OAAO,SAAS,YAAY;GACrE,IAAI;IACF,OAAO;KACL,UAAU,CACR,MAAM,UAAU,aAAc;MAC5B,KAAK,QAAQ,OAAO;MACpB,WAAW,MAAM,iBAAiB,QAAQ,WAAW,OAAO;KAC9D,CAAC,CACH;KACA,OAAO;KACP,YAAY;IACd;GACF,SAAS,OAAO;IACd,IAAI,iBAAiB,gBACnB,MAAM,IAAI,cACR,kBAAkB,eAClB,gCACF;IACF,MAAM;GACR;EACF,CAAC;CACH;CAEA,OAAO,kBAAkB,cAAc,OAAO,SAAS,YAAY;EACjE,IAAI;GACF,OAAQ,MAAM,UAAU,SAAS;IAC/B,MAAM,QAAQ,OAAO;IACrB,WAAW,QAAQ,OAAO;IAC1B,WAAW,MAAM,iBAAiB,QAAQ,WAAW,OAAO;GAC9D,CAAC;EACH,SAAS,OAAO;GACd,IAAI,iBAAiB,gBAAgB;IACnC,MAAM,EAAE,MAAM,cAAc,MAAM;IAClC,MAAM,IAAI,cACR,MAAM,WAAW,MACb,kBAAkB,gBAClB,kBAAkB,gBACtB,MAAM,SACN;KACE,GAAI,OAAO,SAAS,WAAW,EAAE,KAAK,IAAI,CAAC;KAC3C,GAAI,OAAO,cAAc,YAAY,EAAE,UAAU,IAAI,CAAC;IACxD,CACF;GACF;GACA,MAAM;EACR;CACF,CAAC;CAED,OAAO;AACT;AAGA,SAAS,wBACP,OAC6C;CAC7C,IAAI,SAAS;CACb,MAAM,SAAS,GAAY,UAAwB;EACjD,IAAI,EAAE,SAAS,KAAK,QAAQ,IAC1B,MAAM,IAAI,UAAU,oCAAoC;EAC1D,IAAI,MAAM,QAAQ,OAAO,MAAM,WAAW;EAC1C,IAAI,OAAO,MAAM,YAAY,OAAO,SAAS,CAAC,GAAG;EACjD,IAAI,OAAO,MAAM,UAAU;GACzB,UAAU,IAAI,YAAY,CAAA,CAAE,OAAO,CAAC,CAAA,CAAE;GACtC,IAAI,SAAS,GAAG,MAAM,IAAI,UAAU,oCAAoC;GACxE;EACF;EACA,IAAI,MAAM,QAAQ,CAAC,GAAG;GACpB,IAAI,EAAE,SAAS,MACb,MAAM,IAAI,UAAU,iCAAiC;GACvD,KAAA,MAAW,QAAQ,GAAG,MAAM,MAAM,QAAQ,CAAC;GAC3C;EACF;EACA,IACE,CAAC,KACD,OAAO,MAAM,YACb,CAAC,CAAC,OAAO,WAAW,IAAI,CAAA,CAAE,SAAS,OAAO,eAAe,CAAC,CAAC,GAE3D,MAAM,IAAI,UAAU,kCAAkC;EACxD,MAAM,QAAQ,OAAO,KAAK,CAAC;EAC3B,IAAI,MAAM,SAAS,KACjB,MAAM,IAAI,UAAU,kCAAkC;EACxD,KAAA,MAAW,QAAQ,OAAO;GACxB,IAAI;IAAC;IAAa;IAAe;GAAW,CAAA,CAAE,SAAS,IAAI,GACzD,MAAM,IAAI,UAAU,uBAAuB;GAC7C,MAAM,aAAa,OAAO,yBAAyB,GAAG,IAAI;GAC1D,IAAI,CAAC,cAAc,EAAE,WAAW,aAC9B,MAAM,IAAI,UAAU,oCAAoC;GAC1D,MAAM,MAAM,QAAQ,CAAC;GACrB,MAAM,WAAW,OAAO,QAAQ,CAAC;EACnC;CACF;CACA,MAAM,OAAO,CAAC;CACd,IAAI,CAAC,SAAS,OAAO,UAAU,YAAY,MAAM,QAAQ,KAAK,GAC5D,MAAM,IAAI,UAAU,yBAAyB;CAC/C,KAAA,MAAW,CAAC,MAAM,UAAU,OAAO,QAAQ,KAAK,GAC9C,IACE,CAAC,KAAK,KAAK,KACX,CAAC,SACD,OAAO,UAAU,YACjB,MAAM,QAAQ,KAAK,GAEnB,MAAM,IAAI,UAAU,kCAAkC;AAE5D"}
|