@happyvertical/smrt-app-mcp 0.40.63 → 0.40.64
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 +18 -1
- package/README.md +4 -4
- package/dist/index.js.map +1 -1
- package/dist/manifest.json +1 -1
- package/dist/smrt-knowledge.json +5 -5
- package/dist/sveltekit.d.ts +4 -4
- package/dist/sveltekit.js.map +1 -1
- package/package.json +3 -3
package/AGENTS.md
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
# @happyvertical/smrt-app-mcp
|
|
2
2
|
|
|
3
|
-
Application-scoped MCP server wrapper for SMRT apps.
|
|
3
|
+
Application-scoped MCP server wrapper for SMRT apps. The core owns generated
|
|
4
|
+
tool policy; `./sveltekit` owns the stateless Streamable HTTP transport.
|
|
4
5
|
|
|
5
6
|
## Purpose
|
|
6
7
|
|
|
7
8
|
- Wraps `@happyvertical/smrt-core` MCP generation with app-level tool allow-lists.
|
|
8
9
|
- Filters unauthenticated tool visibility to public read-only tools.
|
|
10
|
+
- Applies an optional principal-aware policy consistently to discovery and calls.
|
|
9
11
|
- Provides workflow assertions so app servers can reject unsafe or incomplete tool calls before dispatch.
|
|
12
|
+
- Exposes an opt-in durable Tasks extension through `@happyvertical/smrt-jobs`.
|
|
10
13
|
|
|
11
14
|
## Patterns
|
|
12
15
|
|
|
@@ -14,9 +17,23 @@ Application-scoped MCP server wrapper for SMRT apps.
|
|
|
14
17
|
- Treat list/get tools as read-only; mutating tools require authentication unless the app deliberately wraps them with stronger policy.
|
|
15
18
|
- Keep app policy outside generated tool schemas. The wrapper owns auth, allow-list, and workflow assertion behavior.
|
|
16
19
|
- Return 404 for tools outside the app allow-list so private generated tools are not enumerated by mistake.
|
|
20
|
+
- Trust only a principal supplied by the application adapter. This package does
|
|
21
|
+
not validate bearer tokens or implement an OAuth authorization server.
|
|
22
|
+
- Mount `mountMcpRoute` as one modern `POST` endpoint. It creates a fresh
|
|
23
|
+
protocol server per request, emits no session id, and refuses subscriptions.
|
|
24
|
+
- Keep tool catalogs private by default. Public caching requires an explicit
|
|
25
|
+
attestation and a global, unauthenticated, read-only, non-tenant catalog with
|
|
26
|
+
no principal-aware policy.
|
|
17
27
|
|
|
18
28
|
## Gotchas
|
|
19
29
|
|
|
20
30
|
- Public tool patterns are still constrained by read-only detection.
|
|
31
|
+
- Policy errors fail closed and expose only the shared safe denial envelope.
|
|
21
32
|
- Workflow assertions run before generated tool dispatch and should be deterministic.
|
|
33
|
+
- Tasks require a stable authenticated principal id, preserve its tenant boundary,
|
|
34
|
+
and require the deployment to run a `TaskRunner` for the `mcp-tasks` queue.
|
|
35
|
+
- This package ships no stdio executable. Use `@happyvertical/smrt-app-cli` to
|
|
36
|
+
bridge a deployed app endpoint to a local MCP client.
|
|
37
|
+
- `mountMcpToolsRoute` and `mountMcpCallRoute` are deprecated REST-shaped
|
|
38
|
+
compatibility aliases, not MCP transports.
|
|
22
39
|
- This package is runtime app infrastructure, not the development MCP. Use `@happyvertical/smrt-dev-mcp` for agentic development knowledge, review, and architecture tooling.
|
package/README.md
CHANGED
|
@@ -90,10 +90,10 @@ await client.callTool({
|
|
|
90
90
|
|
|
91
91
|
## Deprecated REST compatibility
|
|
92
92
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
93
|
+
Applications that have not moved their route path can retain the deprecated
|
|
94
|
+
handlers below while they migrate. They are REST-shaped compatibility aliases,
|
|
95
|
+
not an MCP transport. Direct calls to a tool outside the app allow-list
|
|
96
|
+
continue to receive the safe 404 behavior.
|
|
97
97
|
|
|
98
98
|
```ts
|
|
99
99
|
// src/routes/api/mcp/tools/+server.ts
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/server.ts"],"sourcesContent":["/**\n * `createMcpAppServer` returns the framework-agnostic core that backs an\n * app's HTTP MCP endpoints and stdio bridge. It wraps `MCPGenerator` from\n * `@happyvertical/smrt-core` with:\n *\n * - an allow-list of SMRT class names (so apps publish a subset of their\n * objects, not everything decorated with `@smrt()`),\n * - a public-tool policy for unauthenticated callers (read-only patterns\n * via `publicToolPatterns`),\n * - an optional principal-aware `toolPolicy`, used for both discovery and\n * direct calls,\n * - a pluggable `workflowAssertions` hook so apps can guard their own\n * domain-specific tool calls (e.g. \"approval requires an authenticated\n * user\") without that policy living in this package.\n *\n * @packageDocumentation\n */\n\nimport {\n isTenantScopedClassResolved,\n ObjectRegistry,\n} from '@happyvertical/smrt-core';\nimport type {\n MCPConfig,\n MCPResponse,\n MCPTool,\n} from '@happyvertical/smrt-core/generators/mcp';\nimport {\n MCP_STABLE_CATALOG_TTL_MS,\n MCPGenerator,\n} from '@happyvertical/smrt-core/generators/mcp';\nimport {\n type McpTask,\n McpTaskNotFoundError,\n McpTaskStore,\n} from '@happyvertical/smrt-jobs';\nimport type { DatabaseInterface } from '@happyvertical/sql';\nimport { MCP_TOOL_ACCESS_DENIED_CODE, McpAccessError } from './errors.js';\nimport {\n classNamePrefixes,\n compareMcpToolNames,\n isAllowedCoreTool,\n isPublicToolName,\n} from './tools.js';\n\n/**\n * Generic authenticated caller information available to app-MCP policy.\n *\n * `kind`, `roles`, and `scopes` are deliberately unconstrained so an app can\n * represent a human or a scoped service without this package encoding an\n * application's identity or capability model. A missing principal means the\n * request is unauthenticated.\n */\nexport interface McpAppPrincipal {\n id?: string;\n /** Tenant boundary for task ownership and generated tenant-scoped actions. */\n tenantId?: string;\n /** Trusted operator override for generated tenant-scoped actions. */\n allowCrossTenant?: boolean;\n kind?: string;\n roles?: string[];\n scopes?: string[];\n}\n\n/** Minimal legacy user shape used for generated tool-call attribution. */\nexport interface McpAppUser extends McpAppPrincipal {\n id: string;\n}\n\n/** Context supplied to the optional per-tool principal policy. */\nexport interface McpToolPolicyContext {\n principal: McpAppPrincipal | null;\n tool: MCPTool;\n}\n\n/**\n * Per-tool access policy. Return `true` to expose/allow the tool and `false`\n * to hide it from discovery and deny a direct call. A thrown error is treated\n * as a denial so policy implementation details cannot escape the app-MCP\n * boundary.\n */\nexport type McpToolPolicy = (\n context: McpToolPolicyContext,\n) => boolean | Promise<boolean>;\n\n/**\n * Workflow assertion hook signature. Throw `McpAccessError` to reject the\n * call. Implementations may mutate `args` in place to inject server-trusted\n * fields (e.g. clamping `approvedByUserId` to the authenticated user's id).\n */\nexport type McpWorkflowAssertion = (\n args: Record<string, unknown>,\n user: McpAppUser | null,\n) => void;\n\n/**\n * SMRT options thunk — returns the `{ db }` (and similar) bag to pass into\n * MCPGenerator's per-request context. A function is used so apps can lazily\n * resolve env vars at call time.\n */\nexport type McpSmrtOptionsThunk = () => Record<string, unknown>;\n\n/** Public-tool patterns thunk — same lazy-evaluation rationale. */\nexport type McpPublicToolPatternsThunk = () => readonly string[];\n\nexport interface McpToolListCacheHint {\n ttlMs: number;\n cacheScope: 'private' | 'public';\n}\n\nexport interface McpToolListCacheOptions {\n /** Cache lifetime in milliseconds. Defaults to one day for a deploy-static catalog. */\n ttlMs?: number;\n /** Requested cache visibility. Defaults to private. */\n cacheScope?: 'private' | 'public';\n /**\n * Explicit attestation that every allowed tool is global, unauthenticated,\n * and safe to share through an intermediary cache.\n */\n publicCatalog?: true;\n}\n\n/**\n * Options for `createMcpAppServer`.\n */\nexport interface CreateMcpAppServerOptions {\n /** SMRT context bag (db, etc.) passed to MCPGenerator per call. */\n smrtOptions: McpSmrtOptionsThunk;\n /** Server identity surfaced in the MCP protocol. */\n serverInfo: Required<Pick<MCPConfig, 'name' | 'version'>> &\n Pick<MCPConfig, 'description'>;\n /**\n * SMRT class names the app wants to publish. Tools whose name does not\n * start with any of these classes (lowercased + underscore) are filtered\n * out, even if SMRT generated them.\n */\n allowedClassNames: readonly string[];\n /**\n * Optional thunk returning glob-ish patterns for read-only tools that\n * unauthenticated callers are allowed to use. Defaults to an empty list\n * (everything requires auth).\n */\n publicToolPatterns?: McpPublicToolPatternsThunk;\n /**\n * Cache policy for the MCP tools/list result. Public caching is honored only\n * when this explicitly opts in and every allowed tool is a non-tenant,\n * unauthenticated read-only tool with no principal-aware policy.\n */\n toolListCache?: McpToolListCacheOptions;\n /**\n * Optional generic principal-aware tool policy. It is evaluated for every\n * tool that passes the app allow-list and base public/authenticated policy,\n * for both discovery and a direct call.\n */\n toolPolicy?: McpToolPolicy;\n /**\n * Optional per-tool guards. Keyed by tool name. The assertion runs after\n * tool resolution and before `MCPGenerator.handleToolCall`; throwing\n * `McpAccessError` aborts the call with the error's status. Implementations\n * may mutate `args` to inject trusted fields.\n */\n workflowAssertions?: Record<string, McpWorkflowAssertion>;\n}\n\n/** Tool listing inputs. */\nexport interface ListToolsInput {\n /** Caller used for public/authenticated and optional tool-policy checks. */\n principal?: McpAppPrincipal | null;\n /**\n * Backwards-compatible authenticated marker. New mounts should pass\n * `principal` so discovery and direct calls use the same identity.\n */\n authenticated?: boolean;\n}\n\n/** Tool call inputs. */\nexport interface CallToolInput {\n name: string;\n arguments?: Record<string, unknown>;\n /** Caller used for public/authenticated and optional tool-policy checks. */\n principal?: McpAppPrincipal | null;\n /**\n * Backwards-compatible user input. New callers should pass `principal`.\n * Null/undefined means unauthenticated.\n */\n user?: McpAppUser | null;\n}\n\n/** Shape returned by `createMcpAppServer`. */\nexport interface McpAppServer {\n listTools(input: ListToolsInput): Promise<MCPTool[]>;\n callTool(input: CallToolInput): Promise<MCPResponse>;\n /** Whether this app has any explicitly enabled Tasks extension action. */\n hasTaskSupport?(): Promise<boolean>;\n /** Whether a particular visible tool is task-enabled. */\n isTaskTool?(name: string): Promise<boolean>;\n /** Static declaration used by the protocol discovery capability surface. */\n readonly tasksEnabled?: boolean;\n /** Create a durable task after applying the same tool policy as tools/call. */\n callTask?(input: CallToolInput): Promise<MCPResponse>;\n /** Principal-scoped task lifecycle operations. */\n getTask?(input: {\n taskId: string;\n principal?: McpAppPrincipal | null;\n }): Promise<McpTask>;\n updateTask?(input: {\n taskId: string;\n inputResponses: Record<string, unknown>;\n principal?: McpAppPrincipal | null;\n }): Promise<void>;\n cancelTask?(input: {\n taskId: string;\n principal?: McpAppPrincipal | null;\n }): Promise<void>;\n /** Cache policy for protocol tools/list responses. */\n getToolsListCacheHint?(): Promise<McpToolListCacheHint>;\n /** Read-only view of the configured server identity. */\n readonly serverInfo: CreateMcpAppServerOptions['serverInfo'];\n}\n\nfunction configuredToolListCacheHint(\n options: McpToolListCacheOptions | undefined,\n): McpToolListCacheHint {\n const ttlMs = options?.ttlMs ?? MCP_STABLE_CATALOG_TTL_MS;\n if (!Number.isSafeInteger(ttlMs) || ttlMs < 0) {\n throw new RangeError(\n 'MCP tools/list cache ttlMs must be a non-negative safe integer.',\n );\n }\n if (\n options?.cacheScope !== undefined &&\n options.cacheScope !== 'private' &&\n options.cacheScope !== 'public'\n ) {\n throw new RangeError(\n \"MCP tools/list cacheScope must be 'private' or 'public'.\",\n );\n }\n return {\n ttlMs,\n cacheScope:\n options?.cacheScope === 'public' && options.publicCatalog === true\n ? 'public'\n : 'private',\n };\n}\n\nfunction isTenantScopedTool(tool: MCPTool): boolean {\n const separator = tool.name.indexOf('_');\n if (separator <= 0) return false;\n const objectName = tool.name.slice(0, separator).toLowerCase();\n for (const [key, classInfo] of ObjectRegistry.getAllClasses()) {\n const name = classInfo.name || key;\n if (name.toLowerCase() === objectName) {\n return (\n ObjectRegistry.isTenantScoped(name) || isTenantScopedClassResolved(name)\n );\n }\n }\n return false;\n}\n\n/**\n * Scope task lookup to both the authenticated principal and its tenant. The\n * opaque value is stored in the existing job row, so no separate task table\n * can accidentally bypass a tenant boundary.\n */\nfunction taskOwnerIdFor(principal: McpAppPrincipal): string {\n return JSON.stringify([principal.tenantId ?? null, principal.id]);\n}\n\n/**\n * Build the app-runtime MCP server core. The returned object is intentionally\n * framework-agnostic: HTTP wrappers live in `@happyvertical/smrt-app-mcp/sveltekit`,\n * this package's only other export.\n *\n * This package ships no stdio bridge. To pipe a deployed app's HTTP MCP surface\n * to a local stdio MCP client, use `@happyvertical/smrt-app-cli`, which provides\n * a generic `smrt-mcp-bridge` bin plus `runMcpStdioBridge` and\n * `createAppCli().startMcpBridge()` for branded entry points.\n */\nexport function createMcpAppServer(\n options: CreateMcpAppServerOptions,\n): McpAppServer {\n const allowedPrefixes = classNamePrefixes(options.allowedClassNames);\n const getPublicPatterns =\n options.publicToolPatterns ?? ((): readonly string[] => []);\n const toolPolicy = options.toolPolicy;\n const workflowAssertions = options.workflowAssertions ?? {};\n const requestedToolListCacheHint = configuredToolListCacheHint(\n options.toolListCache,\n );\n const tasksEnabled = options.allowedClassNames.some((className) => {\n const mcp = ObjectRegistry.getConfig(className).mcp;\n return (\n typeof mcp === 'object' &&\n (mcp.tasks === true || (Array.isArray(mcp.tasks) && mcp.tasks.length > 0))\n );\n });\n\n function userForGenerator(\n principal?: McpAppPrincipal | null,\n ): McpAppUser | undefined {\n if (!principal?.id) return undefined;\n return { id: principal.id, roles: principal.roles };\n }\n\n async function taskStoreFor(\n principal?: McpAppPrincipal | null,\n ): Promise<McpTaskStore> {\n if (!principal?.id) {\n throw new McpAccessError(\n 401,\n 'Authentication is required for MCP tasks.',\n );\n }\n const db = options.smrtOptions().db as DatabaseInterface | undefined;\n if (!db) {\n throw new Error('MCP Tasks requires smrtOptions() to provide a database');\n }\n return McpTaskStore.create(db, { ownerId: taskOwnerIdFor(principal) });\n }\n\n function makeGenerator(\n principal?: McpAppPrincipal | null,\n taskStore?: McpTaskStore,\n ): MCPGenerator {\n const user = userForGenerator(principal);\n return new MCPGenerator(options.serverInfo as MCPConfig, {\n ...options.smrtOptions(),\n user,\n tenantId: principal?.tenantId,\n allowCrossTenant: principal?.allowCrossTenant,\n ...(taskStore ? { taskStore } : {}),\n });\n }\n\n function principalForList(input: ListToolsInput): McpAppPrincipal | null {\n if (input.principal !== undefined) return input.principal;\n // Preserve callers of the original boolean API without inventing an id.\n return input.authenticated ? {} : null;\n }\n\n function principalForCall(input: CallToolInput): McpAppPrincipal | null {\n if (input.principal !== undefined) return input.principal;\n return input.user ?? null;\n }\n\n async function allowedTools(): Promise<MCPTool[]> {\n const tools = await makeGenerator().generateTools();\n return tools\n .filter((tool) =>\n isAllowedCoreTool(tool.name.toLowerCase(), allowedPrefixes),\n )\n .sort((left, right) => compareMcpToolNames(left.name, right.name));\n }\n\n function passesBasePolicy(\n tool: MCPTool,\n principal: McpAppPrincipal | null,\n publicPatterns?: readonly string[],\n ): boolean {\n if (principal) return true;\n return isPublicToolName(tool.name, publicPatterns ?? []);\n }\n\n async function passesToolPolicy(\n tool: MCPTool,\n principal: McpAppPrincipal | null,\n ): Promise<boolean> {\n if (!toolPolicy) return true;\n try {\n return Boolean(await toolPolicy({ principal, tool }));\n } catch {\n // A policy failure must fail closed and never leak implementation detail.\n return false;\n }\n }\n\n async function listTools(input: ListToolsInput): Promise<MCPTool[]> {\n const principal = principalForList(input);\n const tools = await allowedTools();\n // Keep the lazy thunk per request, not per tool. Besides avoiding repeated\n // work, this gives one consistent public surface when a thunk reads a\n // dynamic source such as an environment-backed configuration.\n const publicPatterns = principal ? undefined : getPublicPatterns();\n const visible = await Promise.all(\n tools.map(async (tool) => {\n if (!passesBasePolicy(tool, principal, publicPatterns)) return false;\n return passesToolPolicy(tool, principal);\n }),\n );\n return tools.filter((_, index) => visible[index]);\n }\n\n async function getToolsListCacheHint(): Promise<McpToolListCacheHint> {\n if (requestedToolListCacheHint.cacheScope !== 'public') {\n return requestedToolListCacheHint;\n }\n\n // A principal-aware policy can make one caller's catalog differ from\n // another's. Likewise, tenant-scoped reads must never be shared across\n // tenants. The requested public scope is therefore honored only for a\n // complete, unauthenticated, non-tenant read-only catalog.\n const tools = await allowedTools();\n const publicPatterns = getPublicPatterns();\n const isSafePublicCatalog =\n !toolPolicy &&\n tools.every(\n (tool) =>\n isPublicToolName(tool.name, publicPatterns) &&\n !isTenantScopedTool(tool),\n );\n\n return isSafePublicCatalog\n ? requestedToolListCacheHint\n : { ...requestedToolListCacheHint, cacheScope: 'private' };\n }\n\n async function callTool(input: CallToolInput): Promise<MCPResponse> {\n const args = input.arguments ?? {};\n const principal = principalForCall(input);\n const tools = await allowedTools();\n const tool = tools.find((candidate) => candidate.name === input.name);\n if (!tool) {\n throw new McpAccessError(404, 'Unknown MCP tool.');\n }\n\n const publicPatterns = principal ? undefined : getPublicPatterns();\n if (!passesBasePolicy(tool, principal, publicPatterns)) {\n throw new McpAccessError(\n 401,\n `Authentication is required for MCP tool: ${input.name}`,\n );\n }\n\n if (!(await passesToolPolicy(tool, principal))) {\n throw new McpAccessError(403, 'MCP tool access is not permitted.', {\n code: MCP_TOOL_ACCESS_DENIED_CODE,\n retryable: false,\n });\n }\n\n const assertion = workflowAssertions[input.name];\n if (assertion) {\n assertion(args, userForGenerator(principal) ?? null);\n }\n\n return makeGenerator(principal).handleToolCall({\n method: 'tools/call',\n params: { arguments: args, name: input.name },\n });\n }\n\n async function authorizeCall(input: CallToolInput): Promise<{\n args: Record<string, unknown>;\n principal: McpAppPrincipal | null;\n tool: MCPTool;\n }> {\n const args = input.arguments ?? {};\n const principal = principalForCall(input);\n const tools = await allowedTools();\n const tool = tools.find((candidate) => candidate.name === input.name);\n if (!tool) throw new McpAccessError(404, 'Unknown MCP tool.');\n const publicPatterns = principal ? undefined : getPublicPatterns();\n if (!passesBasePolicy(tool, principal, publicPatterns)) {\n throw new McpAccessError(\n 401,\n `Authentication is required for MCP tool: ${input.name}`,\n );\n }\n if (!(await passesToolPolicy(tool, principal))) {\n throw new McpAccessError(403, 'MCP tool access is not permitted.', {\n code: MCP_TOOL_ACCESS_DENIED_CODE,\n retryable: false,\n });\n }\n const assertion = workflowAssertions[input.name];\n if (assertion) assertion(args, userForGenerator(principal) ?? null);\n return { args, principal, tool };\n }\n\n async function hasTaskSupport(): Promise<boolean> {\n const tools = await allowedTools();\n const generator = makeGenerator();\n for (const tool of tools) {\n if (await generator.supportsTaskTool(tool.name)) return true;\n }\n return false;\n }\n\n async function isTaskTool(name: string): Promise<boolean> {\n const tools = await allowedTools();\n if (!tools.some((tool) => tool.name === name)) return false;\n return makeGenerator().supportsTaskTool(name);\n }\n\n async function callTask(input: CallToolInput): Promise<MCPResponse> {\n const { args, principal } = await authorizeCall(input);\n const taskStore = await taskStoreFor(principal);\n return makeGenerator(principal, taskStore).createTask({\n method: 'tools/call',\n params: { arguments: args, name: input.name },\n });\n }\n\n async function withTaskStore<T>(\n principal: McpAppPrincipal | null | undefined,\n operation: (store: McpTaskStore) => Promise<T>,\n ): Promise<T> {\n try {\n return await operation(await taskStoreFor(principal));\n } catch (error) {\n if (error instanceof McpTaskNotFoundError) {\n throw new McpAccessError(404, 'Unknown MCP task.');\n }\n throw error;\n }\n }\n\n async function getTask(input: {\n taskId: string;\n principal?: McpAppPrincipal | null;\n }): Promise<McpTask> {\n return withTaskStore(input.principal, (store) =>\n store.getTask(input.taskId),\n );\n }\n\n async function updateTask(input: {\n taskId: string;\n inputResponses: Record<string, unknown>;\n principal?: McpAppPrincipal | null;\n }): Promise<void> {\n await withTaskStore(input.principal, (store) =>\n store.updateTask(input.taskId, input.inputResponses),\n );\n }\n\n async function cancelTask(input: {\n taskId: string;\n principal?: McpAppPrincipal | null;\n }): Promise<void> {\n await withTaskStore(input.principal, (store) =>\n store.cancelTask(input.taskId),\n );\n }\n\n return {\n listTools,\n callTool,\n hasTaskSupport,\n isTaskTool,\n callTask,\n getTask,\n updateTask,\n cancelTask,\n tasksEnabled,\n getToolsListCacheHint,\n serverInfo: options.serverInfo,\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/server.ts"],"sourcesContent":["/**\n * `createMcpAppServer` returns the framework-agnostic core that backs an\n * app's HTTP MCP endpoints and transport-neutral protocol adapter. It wraps\n * `MCPGenerator` from `@happyvertical/smrt-core` with:\n *\n * - an allow-list of SMRT class names (so apps publish a subset of their\n * objects, not everything decorated with `@smrt()`),\n * - a public-tool policy for unauthenticated callers (read-only patterns\n * via `publicToolPatterns`),\n * - an optional principal-aware `toolPolicy`, used for both discovery and\n * direct calls,\n * - a pluggable `workflowAssertions` hook so apps can guard their own\n * domain-specific tool calls (e.g. \"approval requires an authenticated\n * user\") without that policy living in this package.\n *\n * @packageDocumentation\n */\n\nimport {\n isTenantScopedClassResolved,\n ObjectRegistry,\n} from '@happyvertical/smrt-core';\nimport type {\n MCPConfig,\n MCPResponse,\n MCPTool,\n} from '@happyvertical/smrt-core/generators/mcp';\nimport {\n MCP_STABLE_CATALOG_TTL_MS,\n MCPGenerator,\n} from '@happyvertical/smrt-core/generators/mcp';\nimport {\n type McpTask,\n McpTaskNotFoundError,\n McpTaskStore,\n} from '@happyvertical/smrt-jobs';\nimport type { DatabaseInterface } from '@happyvertical/sql';\nimport { MCP_TOOL_ACCESS_DENIED_CODE, McpAccessError } from './errors.js';\nimport {\n classNamePrefixes,\n compareMcpToolNames,\n isAllowedCoreTool,\n isPublicToolName,\n} from './tools.js';\n\n/**\n * Generic authenticated caller information available to app-MCP policy.\n *\n * `kind`, `roles`, and `scopes` are deliberately unconstrained so an app can\n * represent a human or a scoped service without this package encoding an\n * application's identity or capability model. A missing principal means the\n * request is unauthenticated.\n */\nexport interface McpAppPrincipal {\n id?: string;\n /** Tenant boundary for task ownership and generated tenant-scoped actions. */\n tenantId?: string;\n /** Trusted operator override for generated tenant-scoped actions. */\n allowCrossTenant?: boolean;\n kind?: string;\n roles?: string[];\n scopes?: string[];\n}\n\n/** Minimal legacy user shape used for generated tool-call attribution. */\nexport interface McpAppUser extends McpAppPrincipal {\n id: string;\n}\n\n/** Context supplied to the optional per-tool principal policy. */\nexport interface McpToolPolicyContext {\n principal: McpAppPrincipal | null;\n tool: MCPTool;\n}\n\n/**\n * Per-tool access policy. Return `true` to expose/allow the tool and `false`\n * to hide it from discovery and deny a direct call. A thrown error is treated\n * as a denial so policy implementation details cannot escape the app-MCP\n * boundary.\n */\nexport type McpToolPolicy = (\n context: McpToolPolicyContext,\n) => boolean | Promise<boolean>;\n\n/**\n * Workflow assertion hook signature. Throw `McpAccessError` to reject the\n * call. Implementations may mutate `args` in place to inject server-trusted\n * fields (e.g. clamping `approvedByUserId` to the authenticated user's id).\n */\nexport type McpWorkflowAssertion = (\n args: Record<string, unknown>,\n user: McpAppUser | null,\n) => void;\n\n/**\n * SMRT options thunk — returns the `{ db }` (and similar) bag to pass into\n * MCPGenerator's per-request context. A function is used so apps can lazily\n * resolve env vars at call time.\n */\nexport type McpSmrtOptionsThunk = () => Record<string, unknown>;\n\n/** Public-tool patterns thunk — same lazy-evaluation rationale. */\nexport type McpPublicToolPatternsThunk = () => readonly string[];\n\nexport interface McpToolListCacheHint {\n ttlMs: number;\n cacheScope: 'private' | 'public';\n}\n\nexport interface McpToolListCacheOptions {\n /** Cache lifetime in milliseconds. Defaults to one day for a deploy-static catalog. */\n ttlMs?: number;\n /** Requested cache visibility. Defaults to private. */\n cacheScope?: 'private' | 'public';\n /**\n * Explicit attestation that every allowed tool is global, unauthenticated,\n * and safe to share through an intermediary cache.\n */\n publicCatalog?: true;\n}\n\n/**\n * Options for `createMcpAppServer`.\n */\nexport interface CreateMcpAppServerOptions {\n /** SMRT context bag (db, etc.) passed to MCPGenerator per call. */\n smrtOptions: McpSmrtOptionsThunk;\n /** Server identity surfaced in the MCP protocol. */\n serverInfo: Required<Pick<MCPConfig, 'name' | 'version'>> &\n Pick<MCPConfig, 'description'>;\n /**\n * SMRT class names the app wants to publish. Tools whose name does not\n * start with any of these classes (lowercased + underscore) are filtered\n * out, even if SMRT generated them.\n */\n allowedClassNames: readonly string[];\n /**\n * Optional thunk returning glob-ish patterns for read-only tools that\n * unauthenticated callers are allowed to use. Defaults to an empty list\n * (everything requires auth).\n */\n publicToolPatterns?: McpPublicToolPatternsThunk;\n /**\n * Cache policy for the MCP tools/list result. Public caching is honored only\n * when this explicitly opts in and every allowed tool is a non-tenant,\n * unauthenticated read-only tool with no principal-aware policy.\n */\n toolListCache?: McpToolListCacheOptions;\n /**\n * Optional generic principal-aware tool policy. It is evaluated for every\n * tool that passes the app allow-list and base public/authenticated policy,\n * for both discovery and a direct call.\n */\n toolPolicy?: McpToolPolicy;\n /**\n * Optional per-tool guards. Keyed by tool name. The assertion runs after\n * tool resolution and before `MCPGenerator.handleToolCall`; throwing\n * `McpAccessError` aborts the call with the error's status. Implementations\n * may mutate `args` to inject trusted fields.\n */\n workflowAssertions?: Record<string, McpWorkflowAssertion>;\n}\n\n/** Tool listing inputs. */\nexport interface ListToolsInput {\n /** Caller used for public/authenticated and optional tool-policy checks. */\n principal?: McpAppPrincipal | null;\n /**\n * Backwards-compatible authenticated marker. New mounts should pass\n * `principal` so discovery and direct calls use the same identity.\n */\n authenticated?: boolean;\n}\n\n/** Tool call inputs. */\nexport interface CallToolInput {\n name: string;\n arguments?: Record<string, unknown>;\n /** Caller used for public/authenticated and optional tool-policy checks. */\n principal?: McpAppPrincipal | null;\n /**\n * Backwards-compatible user input. New callers should pass `principal`.\n * Null/undefined means unauthenticated.\n */\n user?: McpAppUser | null;\n}\n\n/** Shape returned by `createMcpAppServer`. */\nexport interface McpAppServer {\n listTools(input: ListToolsInput): Promise<MCPTool[]>;\n callTool(input: CallToolInput): Promise<MCPResponse>;\n /** Whether this app has any explicitly enabled Tasks extension action. */\n hasTaskSupport?(): Promise<boolean>;\n /** Whether a particular visible tool is task-enabled. */\n isTaskTool?(name: string): Promise<boolean>;\n /** Static declaration used by the protocol discovery capability surface. */\n readonly tasksEnabled?: boolean;\n /** Create a durable task after applying the same tool policy as tools/call. */\n callTask?(input: CallToolInput): Promise<MCPResponse>;\n /** Principal-scoped task lifecycle operations. */\n getTask?(input: {\n taskId: string;\n principal?: McpAppPrincipal | null;\n }): Promise<McpTask>;\n updateTask?(input: {\n taskId: string;\n inputResponses: Record<string, unknown>;\n principal?: McpAppPrincipal | null;\n }): Promise<void>;\n cancelTask?(input: {\n taskId: string;\n principal?: McpAppPrincipal | null;\n }): Promise<void>;\n /** Cache policy for protocol tools/list responses. */\n getToolsListCacheHint?(): Promise<McpToolListCacheHint>;\n /** Read-only view of the configured server identity. */\n readonly serverInfo: CreateMcpAppServerOptions['serverInfo'];\n}\n\nfunction configuredToolListCacheHint(\n options: McpToolListCacheOptions | undefined,\n): McpToolListCacheHint {\n const ttlMs = options?.ttlMs ?? MCP_STABLE_CATALOG_TTL_MS;\n if (!Number.isSafeInteger(ttlMs) || ttlMs < 0) {\n throw new RangeError(\n 'MCP tools/list cache ttlMs must be a non-negative safe integer.',\n );\n }\n if (\n options?.cacheScope !== undefined &&\n options.cacheScope !== 'private' &&\n options.cacheScope !== 'public'\n ) {\n throw new RangeError(\n \"MCP tools/list cacheScope must be 'private' or 'public'.\",\n );\n }\n return {\n ttlMs,\n cacheScope:\n options?.cacheScope === 'public' && options.publicCatalog === true\n ? 'public'\n : 'private',\n };\n}\n\nfunction isTenantScopedTool(tool: MCPTool): boolean {\n const separator = tool.name.indexOf('_');\n if (separator <= 0) return false;\n const objectName = tool.name.slice(0, separator).toLowerCase();\n for (const [key, classInfo] of ObjectRegistry.getAllClasses()) {\n const name = classInfo.name || key;\n if (name.toLowerCase() === objectName) {\n return (\n ObjectRegistry.isTenantScoped(name) || isTenantScopedClassResolved(name)\n );\n }\n }\n return false;\n}\n\n/**\n * Scope task lookup to both the authenticated principal and its tenant. The\n * opaque value is stored in the existing job row, so no separate task table\n * can accidentally bypass a tenant boundary.\n */\nfunction taskOwnerIdFor(principal: McpAppPrincipal): string {\n return JSON.stringify([principal.tenantId ?? null, principal.id]);\n}\n\n/**\n * Build the app-runtime MCP server core. The returned object is intentionally\n * framework-agnostic: HTTP wrappers live in `@happyvertical/smrt-app-mcp/sveltekit`,\n * this package's only other export.\n *\n * This package ships no stdio bridge. To pipe a deployed app's HTTP MCP surface\n * to a local stdio MCP client, use `@happyvertical/smrt-app-cli`, which provides\n * a generic `smrt-mcp-bridge` bin plus `runMcpStdioBridge` and\n * `createAppCli().startMcpBridge()` for branded entry points.\n */\nexport function createMcpAppServer(\n options: CreateMcpAppServerOptions,\n): McpAppServer {\n const allowedPrefixes = classNamePrefixes(options.allowedClassNames);\n const getPublicPatterns =\n options.publicToolPatterns ?? ((): readonly string[] => []);\n const toolPolicy = options.toolPolicy;\n const workflowAssertions = options.workflowAssertions ?? {};\n const requestedToolListCacheHint = configuredToolListCacheHint(\n options.toolListCache,\n );\n const tasksEnabled = options.allowedClassNames.some((className) => {\n const mcp = ObjectRegistry.getConfig(className).mcp;\n return (\n typeof mcp === 'object' &&\n (mcp.tasks === true || (Array.isArray(mcp.tasks) && mcp.tasks.length > 0))\n );\n });\n\n function userForGenerator(\n principal?: McpAppPrincipal | null,\n ): McpAppUser | undefined {\n if (!principal?.id) return undefined;\n return { id: principal.id, roles: principal.roles };\n }\n\n async function taskStoreFor(\n principal?: McpAppPrincipal | null,\n ): Promise<McpTaskStore> {\n if (!principal?.id) {\n throw new McpAccessError(\n 401,\n 'Authentication is required for MCP tasks.',\n );\n }\n const db = options.smrtOptions().db as DatabaseInterface | undefined;\n if (!db) {\n throw new Error('MCP Tasks requires smrtOptions() to provide a database');\n }\n return McpTaskStore.create(db, { ownerId: taskOwnerIdFor(principal) });\n }\n\n function makeGenerator(\n principal?: McpAppPrincipal | null,\n taskStore?: McpTaskStore,\n ): MCPGenerator {\n const user = userForGenerator(principal);\n return new MCPGenerator(options.serverInfo as MCPConfig, {\n ...options.smrtOptions(),\n user,\n tenantId: principal?.tenantId,\n allowCrossTenant: principal?.allowCrossTenant,\n ...(taskStore ? { taskStore } : {}),\n });\n }\n\n function principalForList(input: ListToolsInput): McpAppPrincipal | null {\n if (input.principal !== undefined) return input.principal;\n // Preserve callers of the original boolean API without inventing an id.\n return input.authenticated ? {} : null;\n }\n\n function principalForCall(input: CallToolInput): McpAppPrincipal | null {\n if (input.principal !== undefined) return input.principal;\n return input.user ?? null;\n }\n\n async function allowedTools(): Promise<MCPTool[]> {\n const tools = await makeGenerator().generateTools();\n return tools\n .filter((tool) =>\n isAllowedCoreTool(tool.name.toLowerCase(), allowedPrefixes),\n )\n .sort((left, right) => compareMcpToolNames(left.name, right.name));\n }\n\n function passesBasePolicy(\n tool: MCPTool,\n principal: McpAppPrincipal | null,\n publicPatterns?: readonly string[],\n ): boolean {\n if (principal) return true;\n return isPublicToolName(tool.name, publicPatterns ?? []);\n }\n\n async function passesToolPolicy(\n tool: MCPTool,\n principal: McpAppPrincipal | null,\n ): Promise<boolean> {\n if (!toolPolicy) return true;\n try {\n return Boolean(await toolPolicy({ principal, tool }));\n } catch {\n // A policy failure must fail closed and never leak implementation detail.\n return false;\n }\n }\n\n async function listTools(input: ListToolsInput): Promise<MCPTool[]> {\n const principal = principalForList(input);\n const tools = await allowedTools();\n // Keep the lazy thunk per request, not per tool. Besides avoiding repeated\n // work, this gives one consistent public surface when a thunk reads a\n // dynamic source such as an environment-backed configuration.\n const publicPatterns = principal ? undefined : getPublicPatterns();\n const visible = await Promise.all(\n tools.map(async (tool) => {\n if (!passesBasePolicy(tool, principal, publicPatterns)) return false;\n return passesToolPolicy(tool, principal);\n }),\n );\n return tools.filter((_, index) => visible[index]);\n }\n\n async function getToolsListCacheHint(): Promise<McpToolListCacheHint> {\n if (requestedToolListCacheHint.cacheScope !== 'public') {\n return requestedToolListCacheHint;\n }\n\n // A principal-aware policy can make one caller's catalog differ from\n // another's. Likewise, tenant-scoped reads must never be shared across\n // tenants. The requested public scope is therefore honored only for a\n // complete, unauthenticated, non-tenant read-only catalog.\n const tools = await allowedTools();\n const publicPatterns = getPublicPatterns();\n const isSafePublicCatalog =\n !toolPolicy &&\n tools.every(\n (tool) =>\n isPublicToolName(tool.name, publicPatterns) &&\n !isTenantScopedTool(tool),\n );\n\n return isSafePublicCatalog\n ? requestedToolListCacheHint\n : { ...requestedToolListCacheHint, cacheScope: 'private' };\n }\n\n async function callTool(input: CallToolInput): Promise<MCPResponse> {\n const args = input.arguments ?? {};\n const principal = principalForCall(input);\n const tools = await allowedTools();\n const tool = tools.find((candidate) => candidate.name === input.name);\n if (!tool) {\n throw new McpAccessError(404, 'Unknown MCP tool.');\n }\n\n const publicPatterns = principal ? undefined : getPublicPatterns();\n if (!passesBasePolicy(tool, principal, publicPatterns)) {\n throw new McpAccessError(\n 401,\n `Authentication is required for MCP tool: ${input.name}`,\n );\n }\n\n if (!(await passesToolPolicy(tool, principal))) {\n throw new McpAccessError(403, 'MCP tool access is not permitted.', {\n code: MCP_TOOL_ACCESS_DENIED_CODE,\n retryable: false,\n });\n }\n\n const assertion = workflowAssertions[input.name];\n if (assertion) {\n assertion(args, userForGenerator(principal) ?? null);\n }\n\n return makeGenerator(principal).handleToolCall({\n method: 'tools/call',\n params: { arguments: args, name: input.name },\n });\n }\n\n async function authorizeCall(input: CallToolInput): Promise<{\n args: Record<string, unknown>;\n principal: McpAppPrincipal | null;\n tool: MCPTool;\n }> {\n const args = input.arguments ?? {};\n const principal = principalForCall(input);\n const tools = await allowedTools();\n const tool = tools.find((candidate) => candidate.name === input.name);\n if (!tool) throw new McpAccessError(404, 'Unknown MCP tool.');\n const publicPatterns = principal ? undefined : getPublicPatterns();\n if (!passesBasePolicy(tool, principal, publicPatterns)) {\n throw new McpAccessError(\n 401,\n `Authentication is required for MCP tool: ${input.name}`,\n );\n }\n if (!(await passesToolPolicy(tool, principal))) {\n throw new McpAccessError(403, 'MCP tool access is not permitted.', {\n code: MCP_TOOL_ACCESS_DENIED_CODE,\n retryable: false,\n });\n }\n const assertion = workflowAssertions[input.name];\n if (assertion) assertion(args, userForGenerator(principal) ?? null);\n return { args, principal, tool };\n }\n\n async function hasTaskSupport(): Promise<boolean> {\n const tools = await allowedTools();\n const generator = makeGenerator();\n for (const tool of tools) {\n if (await generator.supportsTaskTool(tool.name)) return true;\n }\n return false;\n }\n\n async function isTaskTool(name: string): Promise<boolean> {\n const tools = await allowedTools();\n if (!tools.some((tool) => tool.name === name)) return false;\n return makeGenerator().supportsTaskTool(name);\n }\n\n async function callTask(input: CallToolInput): Promise<MCPResponse> {\n const { args, principal } = await authorizeCall(input);\n const taskStore = await taskStoreFor(principal);\n return makeGenerator(principal, taskStore).createTask({\n method: 'tools/call',\n params: { arguments: args, name: input.name },\n });\n }\n\n async function withTaskStore<T>(\n principal: McpAppPrincipal | null | undefined,\n operation: (store: McpTaskStore) => Promise<T>,\n ): Promise<T> {\n try {\n return await operation(await taskStoreFor(principal));\n } catch (error) {\n if (error instanceof McpTaskNotFoundError) {\n throw new McpAccessError(404, 'Unknown MCP task.');\n }\n throw error;\n }\n }\n\n async function getTask(input: {\n taskId: string;\n principal?: McpAppPrincipal | null;\n }): Promise<McpTask> {\n return withTaskStore(input.principal, (store) =>\n store.getTask(input.taskId),\n );\n }\n\n async function updateTask(input: {\n taskId: string;\n inputResponses: Record<string, unknown>;\n principal?: McpAppPrincipal | null;\n }): Promise<void> {\n await withTaskStore(input.principal, (store) =>\n store.updateTask(input.taskId, input.inputResponses),\n );\n }\n\n async function cancelTask(input: {\n taskId: string;\n principal?: McpAppPrincipal | null;\n }): Promise<void> {\n await withTaskStore(input.principal, (store) =>\n store.cancelTask(input.taskId),\n );\n }\n\n return {\n listTools,\n callTool,\n hasTaskSupport,\n isTaskTool,\n callTask,\n getTask,\n updateTask,\n cancelTask,\n tasksEnabled,\n getToolsListCacheHint,\n serverInfo: options.serverInfo,\n };\n}\n"],"mappings":"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"}
|
package/dist/manifest.json
CHANGED
package/dist/smrt-knowledge.json
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"generatedAt": "1970-01-01T00:00:00.000Z",
|
|
4
4
|
"packageName": "@happyvertical/smrt-app-mcp",
|
|
5
|
-
"packageVersion": "0.40.
|
|
5
|
+
"packageVersion": "0.40.64",
|
|
6
6
|
"sourceManifestPath": "dist/manifest.json",
|
|
7
7
|
"agentDocPath": "AGENTS.md",
|
|
8
8
|
"sourceHashes": {
|
|
9
|
-
"manifest": "
|
|
10
|
-
"packageJson": "
|
|
11
|
-
"agents": "
|
|
9
|
+
"manifest": "cdebd91d13ada6edf5636b1909a400e0004a712aa7923054bb5c41c76a5d6a21",
|
|
10
|
+
"packageJson": "1ba809606b37545040801e41393ac99d0fba11102d02692aa7a599f29e576233",
|
|
11
|
+
"agents": "b4f2b874e2c5c83b91b77ccf4aeaae3fd58ad9eb2aba2e5d1efd118564aa0730"
|
|
12
12
|
},
|
|
13
13
|
"exports": [
|
|
14
14
|
".",
|
|
@@ -47,5 +47,5 @@
|
|
|
47
47
|
"polymorphicAssociations": 0,
|
|
48
48
|
"uuidColumns": 0
|
|
49
49
|
},
|
|
50
|
-
"agentDoc": "# @happyvertical/smrt-app-mcp\n\nApplication-scoped MCP server wrapper for SMRT apps.\n\n## Purpose\n\n- Wraps `@happyvertical/smrt-core` MCP generation with app-level tool allow-lists.\n- Filters unauthenticated tool visibility to public read-only tools.\n- Provides workflow assertions so app servers can reject unsafe or incomplete tool calls before dispatch.\n\n## Patterns\n\n- Generate tools from core, then filter by allowed class-name prefixes.\n- Treat list/get tools as read-only; mutating tools require authentication unless the app deliberately wraps them with stronger policy.\n- Keep app policy outside generated tool schemas. The wrapper owns auth, allow-list, and workflow assertion behavior.\n- Return 404 for tools outside the app allow-list so private generated tools are not enumerated by mistake.\n\n## Gotchas\n\n- Public tool patterns are still constrained by read-only detection.\n- Workflow assertions run before generated tool dispatch and should be deterministic.\n- This package is runtime app infrastructure, not the development MCP. Use `@happyvertical/smrt-dev-mcp` for agentic development knowledge, review, and architecture tooling.\n"
|
|
50
|
+
"agentDoc": "# @happyvertical/smrt-app-mcp\n\nApplication-scoped MCP server wrapper for SMRT apps. The core owns generated\ntool policy; `./sveltekit` owns the stateless Streamable HTTP transport.\n\n## Purpose\n\n- Wraps `@happyvertical/smrt-core` MCP generation with app-level tool allow-lists.\n- Filters unauthenticated tool visibility to public read-only tools.\n- Applies an optional principal-aware policy consistently to discovery and calls.\n- Provides workflow assertions so app servers can reject unsafe or incomplete tool calls before dispatch.\n- Exposes an opt-in durable Tasks extension through `@happyvertical/smrt-jobs`.\n\n## Patterns\n\n- Generate tools from core, then filter by allowed class-name prefixes.\n- Treat list/get tools as read-only; mutating tools require authentication unless the app deliberately wraps them with stronger policy.\n- Keep app policy outside generated tool schemas. The wrapper owns auth, allow-list, and workflow assertion behavior.\n- Return 404 for tools outside the app allow-list so private generated tools are not enumerated by mistake.\n- Trust only a principal supplied by the application adapter. This package does\n not validate bearer tokens or implement an OAuth authorization server.\n- Mount `mountMcpRoute` as one modern `POST` endpoint. It creates a fresh\n protocol server per request, emits no session id, and refuses subscriptions.\n- Keep tool catalogs private by default. Public caching requires an explicit\n attestation and a global, unauthenticated, read-only, non-tenant catalog with\n no principal-aware policy.\n\n## Gotchas\n\n- Public tool patterns are still constrained by read-only detection.\n- Policy errors fail closed and expose only the shared safe denial envelope.\n- Workflow assertions run before generated tool dispatch and should be deterministic.\n- Tasks require a stable authenticated principal id, preserve its tenant boundary,\n and require the deployment to run a `TaskRunner` for the `mcp-tasks` queue.\n- This package ships no stdio executable. Use `@happyvertical/smrt-app-cli` to\n bridge a deployed app endpoint to a local MCP client.\n- `mountMcpToolsRoute` and `mountMcpCallRoute` are deprecated REST-shaped\n compatibility aliases, not MCP transports.\n- This package is runtime app infrastructure, not the development MCP. Use `@happyvertical/smrt-dev-mcp` for agentic development knowledge, review, and architecture tooling.\n"
|
|
51
51
|
}
|
package/dist/sveltekit.d.ts
CHANGED
|
@@ -206,8 +206,8 @@ declare type McpWorkflowAssertion = (args: Record<string, unknown>, user: McpApp
|
|
|
206
206
|
* Mount `server.callTool` as a `POST` handler that expects
|
|
207
207
|
* `{ name, arguments }` in the JSON body.
|
|
208
208
|
*
|
|
209
|
-
* @deprecated Use {@link mountMcpRoute}
|
|
210
|
-
*
|
|
209
|
+
* @deprecated Use {@link mountMcpRoute}. Existing REST-shaped mounts can use
|
|
210
|
+
* this compatibility alias while migrating without a coordinated cutover.
|
|
211
211
|
*/
|
|
212
212
|
export declare function mountMcpCallRoute(server: McpAppServer, options?: MountMcpRouteOptions): McpSvelteKitHandler;
|
|
213
213
|
|
|
@@ -242,8 +242,8 @@ export declare interface MountMcpRouteOptions {
|
|
|
242
242
|
* Mount `server.listTools` as a `GET` handler. Returns the tool list shape
|
|
243
243
|
* `{ tools }` for compatibility with the stock MCP bridge.
|
|
244
244
|
*
|
|
245
|
-
* @deprecated Use {@link mountMcpRoute}
|
|
246
|
-
*
|
|
245
|
+
* @deprecated Use {@link mountMcpRoute}. Existing REST-shaped mounts can use
|
|
246
|
+
* this compatibility alias while migrating without a coordinated cutover.
|
|
247
247
|
*/
|
|
248
248
|
export declare function mountMcpToolsRoute(server: McpAppServer, options?: MountMcpRouteOptions): McpSvelteKitHandler;
|
|
249
249
|
|
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 { createMcpProtocolServer, MCP_TASKS_EXTENSION } 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 /**\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 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 createMcpProtocolServer(protocolServerForRequest(server, resolved), {\n principal: resolved.principal,\n }),\n {\n // The legacy REST-shaped mounts below remain the migration path for\n // one release. This endpoint is deliberately 2026-07-28-only.\n legacy: 'reject',\n // The SDK validates every request before consulting its listen router.\n // Zero capacity keeps this tools-only 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}; retained for one release so existing\n * REST-shaped mounts can migrate 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}; retained for one release so existing\n * REST-shaped mounts can migrate 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\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 { createMcpProtocolServer, MCP_TASKS_EXTENSION } 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 /**\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 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 createMcpProtocolServer(protocolServerForRequest(server, resolved), {\n principal: resolved.principal,\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 tools-only 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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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@happyvertical/smrt-app-mcp",
|
|
3
|
-
"version": "0.40.
|
|
3
|
+
"version": "0.40.64",
|
|
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",
|
|
@@ -45,8 +45,8 @@
|
|
|
45
45
|
"dependencies": {
|
|
46
46
|
"@happyvertical/sql": "^0.86.1",
|
|
47
47
|
"@modelcontextprotocol/server": "2.0.0",
|
|
48
|
-
"@happyvertical/smrt-core": "0.40.
|
|
49
|
-
"@happyvertical/smrt-jobs": "0.40.
|
|
48
|
+
"@happyvertical/smrt-core": "0.40.64",
|
|
49
|
+
"@happyvertical/smrt-jobs": "0.40.64"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
52
|
"@modelcontextprotocol/client": "2.0.0",
|