@happyvertical/smrt-app-mcp 0.40.44 → 0.40.45

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/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  App-runtime MCP server scaffolding for s-m-r-t apps. Provides:
4
4
 
5
- - **Core** — `createMcpAppServer({ smrtOptions, serverInfo, allowedClassNames, publicToolPatterns?, workflowAssertions? })` returning `{ listTools, callTool }` wired to `@happyvertical/smrt-core/generators/mcp`.
5
+ - **Core** — `createMcpAppServer({ smrtOptions, serverInfo, allowedClassNames, publicToolPatterns?, toolPolicy?, workflowAssertions? })` returning `{ listTools, callTool }` wired to `@happyvertical/smrt-core/generators/mcp`.
6
6
  - **SvelteKit adapters** (`./sveltekit`) — `mountMcpToolsRoute` / `mountMcpCallRoute` for `/api/mcp/{tools,call}/+server.ts`.
7
7
 
8
8
  For piping a deployed app's MCP surface to a local stdio MCP client, see `@happyvertical/smrt-app-cli` — the client-side runtime CLI exposes a `startMcpBridge()` default and a generic `smrt-mcp-bridge` bin.
@@ -22,6 +22,11 @@ export const mcpServer = createMcpAppServer({
22
22
  .split(',')
23
23
  .map((s) => s.trim())
24
24
  .filter(Boolean),
25
+ toolPolicy: ({ tool, principal }) => {
26
+ if (!principal) return tool.name === 'application_get';
27
+ if (principal.kind === 'human') return principal.roles?.includes('admin') ?? false;
28
+ return principal.kind === 'service' && principal.scopes?.includes('mcp:applications') === true;
29
+ },
25
30
  workflowAssertions: {
26
31
  application_update: (args, user) => {
27
32
  if (!user?.id) throw new McpAccessError(401, 'sign in first');
@@ -45,3 +50,21 @@ import { mcpServer } from '$lib/server/mcp';
45
50
  export const POST = mountMcpCallRoute(mcpServer);
46
51
  ```
47
52
 
53
+ `toolPolicy` is evaluated for every tool eligible under the allow-list and
54
+ base public/authenticated rule, on both discovery and a direct call. Return
55
+ `false` to hide the tool from discovery and deny a direct call with the safe,
56
+ non-retryable `mcp_tool_access_denied` code. Its HTTP response is the shared
57
+ structured failure envelope under `error` (`ok: false`, `code`, `message`,
58
+ `status`, `retryable`) and never includes tool, principal, scope, or
59
+ policy-error details. Policy errors also fail closed. The default remains unchanged:
60
+ unauthenticated callers only see/read tools selected by `publicToolPatterns`.
61
+
62
+ SvelteKit mounts resolve `event.locals.user` once as the principal for both
63
+ routes. Use `resolvePrincipal` when your app stores a human or scoped-service
64
+ principal elsewhere; `resolveUser` remains a legacy compatibility alias.
65
+ `resolveAuthenticated` is also retained as a deprecated legacy gate; when a
66
+ new `resolvePrincipal` is not supplied, it makes that same route principal
67
+ unauthenticated for both discovery and calls. If an older mount supplies only
68
+ `resolveAuthenticated: () => true` and no principal, discovery keeps its old
69
+ boolean behavior while calls remain user-less as before; migrate that mount to
70
+ `resolvePrincipal` for one identity across both routes.
@@ -0,0 +1,16 @@
1
+ //#region src/errors.ts
2
+ var MCP_TOOL_ACCESS_DENIED_CODE = "mcp_tool_access_denied";
3
+ var McpAccessError = class extends Error {
4
+ constructor(status, message, metadata = {}) {
5
+ super(message);
6
+ this.status = status;
7
+ this.metadata = metadata;
8
+ this.name = "McpAccessError";
9
+ }
10
+ status;
11
+ metadata;
12
+ };
13
+ //#endregion
14
+ export { McpAccessError as n, MCP_TOOL_ACCESS_DENIED_CODE as t };
15
+
16
+ //# sourceMappingURL=errors-CHYu0Vr2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors-CHYu0Vr2.js","names":[],"sources":["../../src/errors.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"],"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"}
package/dist/index.d.ts CHANGED
@@ -30,7 +30,12 @@ import { MCPTool } from '@happyvertical/smrt-core/generators/mcp';
30
30
  export declare interface CallToolInput {
31
31
  name: string;
32
32
  arguments?: Record<string, unknown>;
33
- /** Authenticated user; null/undefined means unauthenticated. */
33
+ /** Caller used for public/authenticated and optional tool-policy checks. */
34
+ principal?: McpAppPrincipal | null;
35
+ /**
36
+ * Backwards-compatible user input. New callers should pass `principal`.
37
+ * Null/undefined means unauthenticated.
38
+ */
34
39
  user?: McpAppUser | null;
35
40
  }
36
41
 
@@ -68,6 +73,12 @@ export declare interface CreateMcpAppServerOptions {
68
73
  * (everything requires auth).
69
74
  */
70
75
  publicToolPatterns?: McpPublicToolPatternsThunk;
76
+ /**
77
+ * Optional generic principal-aware tool policy. It is evaluated for every
78
+ * tool that passes the app allow-list and base public/authenticated policy,
79
+ * for both discovery and a direct call.
80
+ */
81
+ toolPolicy?: McpToolPolicy;
71
82
  /**
72
83
  * Optional per-tool guards. Keyed by tool name. The assertion runs after
73
84
  * tool resolution and before `MCPGenerator.handleToolCall`; throwing
@@ -98,8 +109,13 @@ export declare function isReadOnlyToolName(toolName: string): boolean;
98
109
 
99
110
  /** Tool listing inputs. */
100
111
  export declare interface ListToolsInput {
101
- /** Whether the calling principal is authenticated. */
102
- authenticated: boolean;
112
+ /** Caller used for public/authenticated and optional tool-policy checks. */
113
+ principal?: McpAppPrincipal | null;
114
+ /**
115
+ * Backwards-compatible authenticated marker. New mounts should pass
116
+ * `principal` so discovery and direct calls use the same identity.
117
+ */
118
+ authenticated?: boolean;
103
119
  }
104
120
 
105
121
  /**
@@ -116,6 +132,9 @@ export declare interface ListToolsInput {
116
132
  */
117
133
  export declare function matchesToolPattern(toolName: string, pattern: string): boolean;
118
134
 
135
+ /** Machine-readable code for a principal policy denial. */
136
+ export declare const MCP_TOOL_ACCESS_DENIED_CODE = "mcp_tool_access_denied";
137
+
119
138
  /**
120
139
  * Error returned by the MCP app server when a caller tries to use a tool
121
140
  * they are not allowed to access. The HTTP layer should map `status` onto
@@ -123,7 +142,33 @@ export declare function matchesToolPattern(toolName: string, pattern: string): b
123
142
  */
124
143
  export declare class McpAccessError extends Error {
125
144
  readonly status: number;
126
- constructor(status: number, message: string);
145
+ readonly metadata: McpAccessErrorMetadata;
146
+ constructor(status: number, message: string, metadata?: McpAccessErrorMetadata);
147
+ }
148
+
149
+ /**
150
+ * Metadata that is safe to expose for an app-MCP access failure. Policy
151
+ * implementations must not place principal, scope, tool, or internal-error
152
+ * details here.
153
+ */
154
+ export declare interface McpAccessErrorMetadata {
155
+ code?: string;
156
+ retryable?: boolean;
157
+ }
158
+
159
+ /**
160
+ * Generic authenticated caller information available to app-MCP policy.
161
+ *
162
+ * `kind`, `roles`, and `scopes` are deliberately unconstrained so an app can
163
+ * represent a human or a scoped service without this package encoding an
164
+ * application's identity or capability model. A missing principal means the
165
+ * request is unauthenticated.
166
+ */
167
+ export declare interface McpAppPrincipal {
168
+ id?: string;
169
+ kind?: string;
170
+ roles?: string[];
171
+ scopes?: string[];
127
172
  }
128
173
 
129
174
  /** Shape returned by `createMcpAppServer`. */
@@ -134,10 +179,9 @@ export declare interface McpAppServer {
134
179
  readonly serverInfo: CreateMcpAppServerOptions['serverInfo'];
135
180
  }
136
181
 
137
- /** Minimal user shape used for tool-call attribution. */
138
- export declare interface McpAppUser {
182
+ /** Minimal legacy user shape used for generated tool-call attribution. */
183
+ export declare interface McpAppUser extends McpAppPrincipal {
139
184
  id: string;
140
- roles?: string[];
141
185
  }
142
186
 
143
187
  /** Public-tool patterns thunk — same lazy-evaluation rationale. */
@@ -150,6 +194,20 @@ export declare type McpPublicToolPatternsThunk = () => readonly string[];
150
194
  */
151
195
  export declare type McpSmrtOptionsThunk = () => Record<string, unknown>;
152
196
 
197
+ /**
198
+ * Per-tool access policy. Return `true` to expose/allow the tool and `false`
199
+ * to hide it from discovery and deny a direct call. A thrown error is treated
200
+ * as a denial so policy implementation details cannot escape the app-MCP
201
+ * boundary.
202
+ */
203
+ export declare type McpToolPolicy = (context: McpToolPolicyContext) => boolean | Promise<boolean>;
204
+
205
+ /** Context supplied to the optional per-tool principal policy. */
206
+ export declare interface McpToolPolicyContext {
207
+ principal: McpAppPrincipal | null;
208
+ tool: MCPTool;
209
+ }
210
+
153
211
  /**
154
212
  * Workflow assertion hook signature. Throw `McpAccessError` to reject the
155
213
  * call. Implementations may mutate `args` in place to inject server-trusted
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { t as McpAccessError } from "./chunks/errors-DQqDD-im.js";
1
+ import { n as McpAccessError, t as MCP_TOOL_ACCESS_DENIED_CODE } from "./chunks/errors-CHYu0Vr2.js";
2
2
  import { MCPGenerator } from "@happyvertical/smrt-core/generators/mcp";
3
3
  //#region src/tools.ts
4
4
  function matchesToolPattern(toolName, pattern) {
@@ -35,30 +35,71 @@ function isAllowedCoreTool(toolName, prefixes) {
35
35
  function createMcpAppServer(options) {
36
36
  const allowedPrefixes = classNamePrefixes(options.allowedClassNames);
37
37
  const getPublicPatterns = options.publicToolPatterns ?? (() => []);
38
+ const toolPolicy = options.toolPolicy;
38
39
  const workflowAssertions = options.workflowAssertions ?? {};
39
- function makeGenerator(user) {
40
+ function userForGenerator(principal) {
41
+ if (!principal?.id) return void 0;
42
+ return {
43
+ id: principal.id,
44
+ roles: principal.roles
45
+ };
46
+ }
47
+ function makeGenerator(principal) {
48
+ const user = userForGenerator(principal);
40
49
  return new MCPGenerator(options.serverInfo, {
41
50
  ...options.smrtOptions(),
42
- user: user?.id ? {
43
- id: user.id,
44
- roles: user.roles
45
- } : void 0
51
+ user
46
52
  });
47
53
  }
54
+ function principalForList(input) {
55
+ if (input.principal !== void 0) return input.principal;
56
+ return input.authenticated ? {} : null;
57
+ }
58
+ function principalForCall(input) {
59
+ if (input.principal !== void 0) return input.principal;
60
+ return input.user ?? null;
61
+ }
62
+ async function allowedTools() {
63
+ return (await makeGenerator().generateTools()).filter((tool) => isAllowedCoreTool(tool.name, allowedPrefixes));
64
+ }
65
+ function passesBasePolicy(tool, principal, publicPatterns) {
66
+ if (principal) return true;
67
+ return isPublicToolName(tool.name, publicPatterns ?? []);
68
+ }
69
+ async function passesToolPolicy(tool, principal) {
70
+ if (!toolPolicy) return true;
71
+ try {
72
+ return Boolean(await toolPolicy({
73
+ principal,
74
+ tool
75
+ }));
76
+ } catch {
77
+ return false;
78
+ }
79
+ }
48
80
  async function listTools(input) {
49
- const allowed = (await makeGenerator().generateTools()).filter((tool) => isAllowedCoreTool(tool.name, allowedPrefixes));
50
- if (input.authenticated) return allowed;
51
- const patterns = getPublicPatterns();
52
- return allowed.filter((tool) => isPublicToolName(tool.name, patterns));
81
+ const principal = principalForList(input);
82
+ const tools = await allowedTools();
83
+ const publicPatterns = principal ? void 0 : getPublicPatterns();
84
+ const visible = await Promise.all(tools.map(async (tool) => {
85
+ if (!passesBasePolicy(tool, principal, publicPatterns)) return false;
86
+ return passesToolPolicy(tool, principal);
87
+ }));
88
+ return tools.filter((_, index) => visible[index]);
53
89
  }
54
90
  async function callTool(input) {
55
91
  const args = input.arguments ?? {};
56
- const user = input.user ?? null;
57
- if (!(await listTools({ authenticated: true })).some((tool) => tool.name === input.name)) throw new McpAccessError(404, `Unknown MCP tool: ${input.name}`);
58
- if (!user && !isPublicToolName(input.name, getPublicPatterns())) throw new McpAccessError(401, `Authentication is required for MCP tool: ${input.name}`);
92
+ const principal = principalForCall(input);
93
+ const tool = (await allowedTools()).find((candidate) => candidate.name === input.name);
94
+ if (!tool) throw new McpAccessError(404, `Unknown MCP tool: ${input.name}`);
95
+ if (!passesBasePolicy(tool, principal, principal ? void 0 : getPublicPatterns())) throw new McpAccessError(401, `Authentication is required for MCP tool: ${input.name}`);
96
+ if (!await passesToolPolicy(tool, principal)) throw new McpAccessError(403, "MCP tool access is not permitted.", {
97
+ code: MCP_TOOL_ACCESS_DENIED_CODE,
98
+ retryable: false
99
+ });
59
100
  const assertion = workflowAssertions[input.name];
60
- if (assertion) assertion(args, user);
61
- return makeGenerator(user).handleToolCall({
101
+ if (assertion) assertion(args, userForGenerator(principal) ?? null);
102
+ return makeGenerator(principal).handleToolCall({
62
103
  method: "tools/call",
63
104
  params: {
64
105
  arguments: args,
@@ -73,6 +114,6 @@ function createMcpAppServer(options) {
73
114
  };
74
115
  }
75
116
  //#endregion
76
- export { McpAccessError, classNamePrefixes, createMcpAppServer, isAllowedCoreTool, isPublicToolName, isReadOnlyToolName, matchesToolPattern };
117
+ export { MCP_TOOL_ACCESS_DENIED_CODE, McpAccessError, classNamePrefixes, createMcpAppServer, isAllowedCoreTool, isPublicToolName, isReadOnlyToolName, matchesToolPattern };
77
118
 
78
119
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/tools.ts","../src/server.ts"],"sourcesContent":["/**\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 * 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 * `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 * - 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 type {\n MCPConfig,\n MCPResponse,\n MCPTool,\n} from '@happyvertical/smrt-core/generators/mcp';\nimport { MCPGenerator } from '@happyvertical/smrt-core/generators/mcp';\nimport { McpAccessError } from './errors.js';\nimport {\n classNamePrefixes,\n isAllowedCoreTool,\n isPublicToolName,\n} from './tools.js';\n\n/** Minimal user shape used for tool-call attribution. */\nexport interface McpAppUser {\n id: string;\n roles?: string[];\n}\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\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 * 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 /** Whether the calling principal is authenticated. */\n authenticated: boolean;\n}\n\n/** Tool call inputs. */\nexport interface CallToolInput {\n name: string;\n arguments?: Record<string, unknown>;\n /** Authenticated user; null/undefined means unauthenticated. */\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 /** Read-only view of the configured server identity. */\n readonly serverInfo: CreateMcpAppServerOptions['serverInfo'];\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 * and a stdio bridge lives in `@happyvertical/smrt-app-mcp/bin/smrt-mcp-bridge`.\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 workflowAssertions = options.workflowAssertions ?? {};\n\n function makeGenerator(user?: McpAppUser | null): MCPGenerator {\n return new MCPGenerator(options.serverInfo as MCPConfig, {\n ...options.smrtOptions(),\n user: user?.id ? { id: user.id, roles: user.roles } : undefined,\n });\n }\n\n async function listTools(input: ListToolsInput): Promise<MCPTool[]> {\n const tools = await makeGenerator().generateTools();\n const allowed = tools.filter((tool) =>\n isAllowedCoreTool(tool.name, allowedPrefixes),\n );\n if (input.authenticated) return allowed;\n const patterns = getPublicPatterns();\n return allowed.filter((tool) => isPublicToolName(tool.name, patterns));\n }\n\n async function callTool(input: CallToolInput): Promise<MCPResponse> {\n const args = input.arguments ?? {};\n const user = input.user ?? null;\n const tools = await listTools({ authenticated: true });\n if (!tools.some((tool) => tool.name === input.name)) {\n throw new McpAccessError(404, `Unknown MCP tool: ${input.name}`);\n }\n\n if (!user && !isPublicToolName(input.name, getPublicPatterns())) {\n throw new McpAccessError(\n 401,\n `Authentication is required for MCP tool: ${input.name}`,\n );\n }\n\n const assertion = workflowAssertions[input.name];\n if (assertion) {\n assertion(args, user);\n }\n\n return makeGenerator(user).handleToolCall({\n method: 'tools/call',\n params: { arguments: args, name: input.name },\n });\n }\n\n return {\n listTools,\n callTool,\n serverInfo: options.serverInfo,\n };\n}\n"],"mappings":";;;AAoBO,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,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;;;AC0BO,SAAS,mBACd,SACc;CACd,MAAM,kBAAkB,kBAAkB,QAAQ,iBAAiB;CACnE,MAAM,oBACJ,QAAQ,6BAAgD,CAAC;CAC3D,MAAM,qBAAqB,QAAQ,sBAAsB,CAAC;CAE1D,SAAS,cAAc,MAAwC;EAC7D,OAAO,IAAI,aAAa,QAAQ,YAAyB;GACvD,GAAG,QAAQ,YAAY;GACvB,MAAM,MAAM,KAAK;IAAE,IAAI,KAAK;IAAI,OAAO,KAAK;GAAM,IAAI,KAAA;EACxD,CAAC;CACH;CAEA,eAAe,UAAU,OAA2C;EAElE,MAAM,WAAU,MADI,cAAc,CAAA,CAAE,cAAc,EAAA,CAC5B,QAAQ,SAC5B,kBAAkB,KAAK,MAAM,eAAe,CAC9C;EACA,IAAI,MAAM,eAAe,OAAO;EAChC,MAAM,WAAW,kBAAkB;EACnC,OAAO,QAAQ,QAAQ,SAAS,iBAAiB,KAAK,MAAM,QAAQ,CAAC;CACvE;CAEA,eAAe,SAAS,OAA4C;EAClE,MAAM,OAAO,MAAM,aAAa,CAAC;EACjC,MAAM,OAAO,MAAM,QAAQ;EAE3B,IAAI,EAAC,MADe,UAAU,EAAE,eAAe,KAAK,CAAC,EAAA,CAC1C,MAAM,SAAS,KAAK,SAAS,MAAM,IAAI,GAChD,MAAM,IAAI,eAAe,KAAK,qBAAqB,MAAM,MAAM;EAGjE,IAAI,CAAC,QAAQ,CAAC,iBAAiB,MAAM,MAAM,kBAAkB,CAAC,GAC5D,MAAM,IAAI,eACR,KACA,4CAA4C,MAAM,MACpD;EAGF,MAAM,YAAY,mBAAmB,MAAM;EAC3C,IAAI,WACF,UAAU,MAAM,IAAI;EAGtB,OAAO,cAAc,IAAI,CAAA,CAAE,eAAe;GACxC,QAAQ;GACR,QAAQ;IAAE,WAAW;IAAM,MAAM,MAAM;GAAK;EAC9C,CAAC;CACH;CAEA,OAAO;EACL;EACA;EACA,YAAY,QAAQ;CACtB;AACF"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/tools.ts","../src/server.ts"],"sourcesContent":["/**\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 * 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 * `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 type {\n MCPConfig,\n MCPResponse,\n MCPTool,\n} from '@happyvertical/smrt-core/generators/mcp';\nimport { MCPGenerator } from '@happyvertical/smrt-core/generators/mcp';\nimport { MCP_TOOL_ACCESS_DENIED_CODE, McpAccessError } from './errors.js';\nimport {\n classNamePrefixes,\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 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\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 * 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 /** Read-only view of the configured server identity. */\n readonly serverInfo: CreateMcpAppServerOptions['serverInfo'];\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 * and a stdio bridge lives in `@happyvertical/smrt-app-mcp/bin/smrt-mcp-bridge`.\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\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 function makeGenerator(principal?: McpAppPrincipal | null): MCPGenerator {\n const user = userForGenerator(principal);\n return new MCPGenerator(options.serverInfo as MCPConfig, {\n ...options.smrtOptions(),\n user,\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.filter((tool) =>\n isAllowedCoreTool(tool.name, allowedPrefixes),\n );\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 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: ${input.name}`);\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 return {\n listTools,\n callTool,\n serverInfo: options.serverInfo,\n };\n}\n"],"mappings":"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"}
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "version": "1.0.0",
3
- "timestamp": 1785638581371,
3
+ "timestamp": 1785650616026,
4
4
  "packageName": "@happyvertical/smrt-app-mcp",
5
- "packageVersion": "0.40.44",
5
+ "packageVersion": "0.40.45",
6
6
  "objects": {},
7
7
  "moduleType": "smrt",
8
8
  "smrtDependencies": [
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "generatedAt": "2026-08-02T02:43:08.058Z",
3
+ "generatedAt": "2026-08-02T06:03:55.660Z",
4
4
  "packageName": "@happyvertical/smrt-app-mcp",
5
- "packageVersion": "0.40.44",
5
+ "packageVersion": "0.40.45",
6
6
  "sourceManifestPath": "dist/manifest.json",
7
7
  "agentDocPath": "AGENTS.md",
8
8
  "sourceHashes": {
9
- "manifest": "596e5f49a31985d3e97ee3a817ebfb319fd3176a95badb8c08c12162db7c4942",
10
- "packageJson": "5effd8e6f90f0815cf2a426f448eeb63594b67572eebc9dac51ba925f334edbe",
9
+ "manifest": "efcac8e4a3939b4100f9c3315ff5cdea5187d2debbbf672c0585951bd58a2ba8",
10
+ "packageJson": "2d1968f081d16df4677c6da4dae61db7068ab5ba66c644fc0614cb7a4b71690c",
11
11
  "agents": "fd1dc36d1530f81aae49e6efa2e2f5011a8a62d30816f25649d4cb597fc5662a"
12
12
  },
13
13
  "exports": [
@@ -6,7 +6,12 @@ import { MCPTool } from '@happyvertical/smrt-core/generators/mcp';
6
6
  declare interface CallToolInput {
7
7
  name: string;
8
8
  arguments?: Record<string, unknown>;
9
- /** Authenticated user; null/undefined means unauthenticated. */
9
+ /** Caller used for public/authenticated and optional tool-policy checks. */
10
+ principal?: McpAppPrincipal | null;
11
+ /**
12
+ * Backwards-compatible user input. New callers should pass `principal`.
13
+ * Null/undefined means unauthenticated.
14
+ */
10
15
  user?: McpAppUser | null;
11
16
  }
12
17
 
@@ -30,6 +35,12 @@ declare interface CreateMcpAppServerOptions {
30
35
  * (everything requires auth).
31
36
  */
32
37
  publicToolPatterns?: McpPublicToolPatternsThunk;
38
+ /**
39
+ * Optional generic principal-aware tool policy. It is evaluated for every
40
+ * tool that passes the app allow-list and base public/authenticated policy,
41
+ * for both discovery and a direct call.
42
+ */
43
+ toolPolicy?: McpToolPolicy;
33
44
  /**
34
45
  * Optional per-tool guards. Keyed by tool name. The assertion runs after
35
46
  * tool resolution and before `MCPGenerator.handleToolCall`; throwing
@@ -41,8 +52,13 @@ declare interface CreateMcpAppServerOptions {
41
52
 
42
53
  /** Tool listing inputs. */
43
54
  declare interface ListToolsInput {
44
- /** Whether the calling principal is authenticated. */
45
- authenticated: boolean;
55
+ /** Caller used for public/authenticated and optional tool-policy checks. */
56
+ principal?: McpAppPrincipal | null;
57
+ /**
58
+ * Backwards-compatible authenticated marker. New mounts should pass
59
+ * `principal` so discovery and direct calls use the same identity.
60
+ */
61
+ authenticated?: boolean;
46
62
  }
47
63
 
48
64
  /**
@@ -52,7 +68,33 @@ declare interface ListToolsInput {
52
68
  */
53
69
  export declare class McpAccessError extends Error {
54
70
  readonly status: number;
55
- constructor(status: number, message: string);
71
+ readonly metadata: McpAccessErrorMetadata;
72
+ constructor(status: number, message: string, metadata?: McpAccessErrorMetadata);
73
+ }
74
+
75
+ /**
76
+ * Metadata that is safe to expose for an app-MCP access failure. Policy
77
+ * implementations must not place principal, scope, tool, or internal-error
78
+ * details here.
79
+ */
80
+ declare interface McpAccessErrorMetadata {
81
+ code?: string;
82
+ retryable?: boolean;
83
+ }
84
+
85
+ /**
86
+ * Generic authenticated caller information available to app-MCP policy.
87
+ *
88
+ * `kind`, `roles`, and `scopes` are deliberately unconstrained so an app can
89
+ * represent a human or a scoped service without this package encoding an
90
+ * application's identity or capability model. A missing principal means the
91
+ * request is unauthenticated.
92
+ */
93
+ export declare interface McpAppPrincipal {
94
+ id?: string;
95
+ kind?: string;
96
+ roles?: string[];
97
+ scopes?: string[];
56
98
  }
57
99
 
58
100
  /** Shape returned by `createMcpAppServer`. */
@@ -63,12 +105,14 @@ export declare interface McpAppServer {
63
105
  readonly serverInfo: CreateMcpAppServerOptions['serverInfo'];
64
106
  }
65
107
 
66
- /** Minimal user shape used for tool-call attribution. */
67
- declare interface McpAppUser {
108
+ /** Minimal legacy user shape used for generated tool-call attribution. */
109
+ declare interface McpAppUser extends McpAppPrincipal {
68
110
  id: string;
69
- roles?: string[];
70
111
  }
71
112
 
113
+ /** Locals reader used to pull the request principal out of `event.locals`. */
114
+ export declare type McpPrincipalResolver = (event: SvelteKitRequestEvent) => McpAppPrincipal | null | undefined;
115
+
72
116
  /** Public-tool patterns thunk — same lazy-evaluation rationale. */
73
117
  declare type McpPublicToolPatternsThunk = () => readonly string[];
74
118
 
@@ -79,8 +123,22 @@ declare type McpPublicToolPatternsThunk = () => readonly string[];
79
123
  */
80
124
  declare type McpSmrtOptionsThunk = () => Record<string, unknown>;
81
125
 
82
- /** Locals reader used to pull the authenticated user out of `event.locals`. */
83
- export declare type McpUserResolver = (event: SvelteKitRequestEvent) => McpAppUser | null | undefined;
126
+ /**
127
+ * Per-tool access policy. Return `true` to expose/allow the tool and `false`
128
+ * to hide it from discovery and deny a direct call. A thrown error is treated
129
+ * as a denial so policy implementation details cannot escape the app-MCP
130
+ * boundary.
131
+ */
132
+ declare type McpToolPolicy = (context: McpToolPolicyContext) => boolean | Promise<boolean>;
133
+
134
+ /** Context supplied to the optional per-tool principal policy. */
135
+ declare interface McpToolPolicyContext {
136
+ principal: McpAppPrincipal | null;
137
+ tool: MCPTool;
138
+ }
139
+
140
+ /** Backwards-compatible alias for callers that name the principal a user. */
141
+ export declare type McpUserResolver = McpPrincipalResolver;
84
142
 
85
143
  /**
86
144
  * Workflow assertion hook signature. Throw `McpAccessError` to reject the
@@ -98,13 +156,17 @@ export declare function mountMcpCallRoute(server: McpAppServer, options?: MountM
98
156
  /** Options shared by both route mounts. */
99
157
  export declare interface MountMcpRouteOptions {
100
158
  /**
101
- * Resolve the authenticated user from `event.locals`. Defaults to
102
- * `event.locals.user`.
159
+ * Resolve the request principal once for both discovery and direct calls.
160
+ * Defaults to `event.locals.user`.
161
+ */
162
+ resolvePrincipal?: McpPrincipalResolver;
163
+ /**
164
+ * Backwards-compatible alias for `resolvePrincipal`.
103
165
  */
104
166
  resolveUser?: McpUserResolver;
105
167
  /**
106
- * Resolve whether the request is authenticated. Defaults to
107
- * `Boolean(event.locals.user)`.
168
+ * Deprecated legacy authentication gate. When `resolvePrincipal` is not
169
+ * supplied, a false result makes the principal null for both routes.
108
170
  */
109
171
  resolveAuthenticated?: (event: SvelteKitRequestEvent) => boolean;
110
172
  }
package/dist/sveltekit.js CHANGED
@@ -1,36 +1,59 @@
1
- import { t as McpAccessError } from "./chunks/errors-DQqDD-im.js";
1
+ import { n as McpAccessError } from "./chunks/errors-CHYu0Vr2.js";
2
2
  //#region src/sveltekit.ts
3
- var defaultResolveUser = (event) => event.locals?.user ?? null;
4
- var defaultResolveAuthenticated = (event) => Boolean(event.locals?.user);
3
+ var defaultResolvePrincipal = (event) => event.locals?.user ?? null;
4
+ function resolveRequestPrincipal(event, options) {
5
+ const principal = (options.resolvePrincipal ?? options.resolveUser ?? defaultResolvePrincipal)(event) ?? null;
6
+ if (!options.resolvePrincipal && options.resolveAuthenticated && !options.resolveAuthenticated(event)) return {
7
+ principal: null,
8
+ legacyAuthenticated: false
9
+ };
10
+ return {
11
+ principal,
12
+ ...options.resolvePrincipal || !options.resolveAuthenticated ? {} : { legacyAuthenticated: true }
13
+ };
14
+ }
15
+ function listToolsInput(resolved) {
16
+ if (!resolved.principal && resolved.legacyAuthenticated) return { authenticated: true };
17
+ return { principal: resolved.principal };
18
+ }
5
19
  function mountMcpToolsRoute(server, options = {}) {
6
- const resolveAuthenticated = options.resolveAuthenticated ?? defaultResolveAuthenticated;
7
20
  return async (event) => {
8
21
  try {
9
- return jsonResponse({ tools: await server.listTools({ authenticated: resolveAuthenticated(event) }) });
22
+ return jsonResponse({ tools: await server.listTools(listToolsInput(resolveRequestPrincipal(event, options))) });
10
23
  } catch (error) {
11
- if (error instanceof McpAccessError) return jsonResponse({ error: error.message }, error.status);
24
+ if (error instanceof McpAccessError) return jsonResponse(mcpAccessErrorBody(error), error.status);
12
25
  throw error;
13
26
  }
14
27
  };
15
28
  }
16
29
  function mountMcpCallRoute(server, options = {}) {
17
- const resolveUser = options.resolveUser ?? defaultResolveUser;
18
30
  return async (event) => {
19
31
  const body = await event.request.json().catch(() => null);
20
32
  if (!body?.name) return jsonResponse({ error: "name is required." }, 400);
21
33
  const input = {
22
34
  arguments: body.arguments ?? {},
23
35
  name: body.name,
24
- user: resolveUser(event) ?? null
36
+ principal: resolveRequestPrincipal(event, options).principal
25
37
  };
26
38
  try {
27
39
  return jsonResponse(await server.callTool(input));
28
40
  } catch (error) {
29
- if (error instanceof McpAccessError) return jsonResponse({ error: error.message }, error.status);
41
+ if (error instanceof McpAccessError) return jsonResponse(mcpAccessErrorBody(error), error.status);
30
42
  throw error;
31
43
  }
32
44
  };
33
45
  }
46
+ function mcpAccessErrorBody(error) {
47
+ const { code, retryable } = error.metadata;
48
+ if (!code) return { error: error.message };
49
+ return { error: {
50
+ ok: false,
51
+ code,
52
+ message: error.message,
53
+ status: error.status,
54
+ ...retryable === void 0 ? {} : { retryable }
55
+ } };
56
+ }
34
57
  function jsonResponse(body, status = 200) {
35
58
  return new Response(JSON.stringify(body), {
36
59
  status,
@@ -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/tools/+server.ts\n * import { mountMcpToolsRoute } from '@happyvertical/smrt-app-mcp/sveltekit';\n * import { mcpServer } from '$lib/server/mcp';\n * export const GET = mountMcpToolsRoute(mcpServer);\n * ```\n */\n\nimport { McpAccessError } from './errors.js';\nimport type { CallToolInput, McpAppServer, McpAppUser } 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\ntype SvelteKitHandler = (event: SvelteKitRequestEvent) => Promise<Response>;\n\n/** Locals reader used to pull the authenticated user out of `event.locals`. */\nexport type McpUserResolver = (\n event: SvelteKitRequestEvent,\n) => McpAppUser | null | undefined;\n\nconst defaultResolveUser: McpUserResolver = (event) =>\n (event.locals?.user ?? null) as McpAppUser | null;\n\nconst defaultResolveAuthenticated = (event: SvelteKitRequestEvent): boolean =>\n Boolean(event.locals?.user);\n\n/** Options shared by both route mounts. */\nexport interface MountMcpRouteOptions {\n /**\n * Resolve the authenticated user from `event.locals`. Defaults to\n * `event.locals.user`.\n */\n resolveUser?: McpUserResolver;\n /**\n * Resolve whether the request is authenticated. Defaults to\n * `Boolean(event.locals.user)`.\n */\n resolveAuthenticated?: (event: SvelteKitRequestEvent) => boolean;\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 */\nexport function mountMcpToolsRoute(\n server: McpAppServer,\n options: MountMcpRouteOptions = {},\n): SvelteKitHandler {\n const resolveAuthenticated =\n options.resolveAuthenticated ?? defaultResolveAuthenticated;\n\n return async (event) => {\n try {\n const tools = await server.listTools({\n authenticated: resolveAuthenticated(event),\n });\n return jsonResponse({ tools });\n } catch (error) {\n if (error instanceof McpAccessError) {\n return jsonResponse({ error: error.message }, 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 */\nexport function mountMcpCallRoute(\n server: McpAppServer,\n options: MountMcpRouteOptions = {},\n): SvelteKitHandler {\n const resolveUser = options.resolveUser ?? defaultResolveUser;\n\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 user: resolveUser(event) ?? null,\n };\n\n try {\n return jsonResponse(await server.callTool(input));\n } catch (error) {\n if (error instanceof McpAccessError) {\n return jsonResponse({ error: error.message }, error.status);\n }\n throw error;\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 { McpAppServer } from './server.js';\n"],"mappings":";;AAiCA,IAAM,sBAAuC,UAC1C,MAAM,QAAQ,QAAQ;AAEzB,IAAM,+BAA+B,UACnC,QAAQ,MAAM,QAAQ,IAAI;AAoBrB,SAAS,mBACd,QACA,UAAgC,CAAC,GACf;CAClB,MAAM,uBACJ,QAAQ,wBAAwB;CAElC,OAAO,OAAO,UAAU;EACtB,IAAI;GAIF,OAAO,aAAa,EAAE,OAAA,MAHF,OAAO,UAAU,EACnC,eAAe,qBAAqB,KAAK,EAC3C,CAAC,EAC2B,CAAC;EAC/B,SAAS,OAAO;GACd,IAAI,iBAAiB,gBACnB,OAAO,aAAa,EAAE,OAAO,MAAM,QAAQ,GAAG,MAAM,MAAM;GAE5D,MAAM;EACR;CACF;AACF;AAMO,SAAS,kBACd,QACA,UAAgC,CAAC,GACf;CAClB,MAAM,cAAc,QAAQ,eAAe;CAE3C,OAAO,OAAO,UAAU;EACtB,MAAM,OAAQ,MAAM,MAAM,QAAQ,KAAK,CAAA,CAAE,YAAY,IAAI;EAKzD,IAAI,CAAC,MAAM,MACT,OAAO,aAAa,EAAE,OAAO,oBAAoB,GAAG,GAAG;EAGzD,MAAM,QAAuB;GAC3B,WAAW,KAAK,aAAa,CAAC;GAC9B,MAAM,KAAK;GACX,MAAM,YAAY,KAAK,KAAK;EAC9B;EAEA,IAAI;GACF,OAAO,aAAa,MAAM,OAAO,SAAS,KAAK,CAAC;EAClD,SAAS,OAAO;GACd,IAAI,iBAAiB,gBACnB,OAAO,aAAa,EAAE,OAAO,MAAM,QAAQ,GAAG,MAAM,MAAM;GAE5D,MAAM;EACR;CACF;AACF;AAEA,SAAS,aAAa,MAAe,SAAS,KAAe;CAC3D,OAAO,IAAI,SAAS,KAAK,UAAU,IAAI,GAAG;EACxC;EACA,SAAS,EAAE,gBAAgB,mBAAmB;CAChD,CAAC;AACH"}
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/tools/+server.ts\n * import { mountMcpToolsRoute } from '@happyvertical/smrt-app-mcp/sveltekit';\n * import { mcpServer } from '$lib/server/mcp';\n * export const GET = mountMcpToolsRoute(mcpServer);\n * ```\n */\n\nimport { McpAccessError } from './errors.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\ntype SvelteKitHandler = (event: SvelteKitRequestEvent) => 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\n/**\n * Mount `server.listTools` as a `GET` handler. Returns the tool list shape\n * `{ tools }` for compatibility with the stock MCP bridge.\n */\nexport function mountMcpToolsRoute(\n server: McpAppServer,\n options: MountMcpRouteOptions = {},\n): SvelteKitHandler {\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 */\nexport function mountMcpCallRoute(\n server: McpAppServer,\n options: MountMcpRouteOptions = {},\n): SvelteKitHandler {\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":";;AA0CA,IAAM,2BAAiD,UACpD,MAAM,QAAQ,QAAQ;AAEzB,SAAS,wBACP,OACA,SAC0B;CAG1B,MAAM,aADJ,QAAQ,oBAAoB,QAAQ,eAAe,wBAAA,CAClB,KAAK,KAAK;CAG7C,IACE,CAAC,QAAQ,oBACT,QAAQ,wBACR,CAAC,QAAQ,qBAAqB,KAAK,GAEnC,OAAO;EAAE,WAAW;EAAM,qBAAqB;CAAM;CAEvD,OAAO;EACL;EACA,GAAI,QAAQ,oBAAoB,CAAC,QAAQ,uBACrC,CAAC,IACD,EAAE,qBAAqB,KAAK;CAClC;AACF;AAEA,SAAS,eAAe,UAAoC;CAK1D,IAAI,CAAC,SAAS,aAAa,SAAS,qBAClC,OAAO,EAAE,eAAe,KAAK;CAE/B,OAAO,EAAE,WAAW,SAAS,UAAU;AACzC;AAwBO,SAAS,mBACd,QACA,UAAgC,CAAC,GACf;CAClB,OAAO,OAAO,UAAU;EACtB,IAAI;GAIF,OAAO,aAAa,EAAE,OAAA,MAHF,OAAO,UACzB,eAAe,wBAAwB,OAAO,OAAO,CAAC,CACxD,EAC4B,CAAC;EAC/B,SAAS,OAAO;GACd,IAAI,iBAAiB,gBACnB,OAAO,aAAa,mBAAmB,KAAK,GAAG,MAAM,MAAM;GAE7D,MAAM;EACR;CACF;AACF;AAMO,SAAS,kBACd,QACA,UAAgC,CAAC,GACf;CAClB,OAAO,OAAO,UAAU;EACtB,MAAM,OAAQ,MAAM,MAAM,QAAQ,KAAK,CAAA,CAAE,YAAY,IAAI;EAKzD,IAAI,CAAC,MAAM,MACT,OAAO,aAAa,EAAE,OAAO,oBAAoB,GAAG,GAAG;EAGzD,MAAM,QAAuB;GAC3B,WAAW,KAAK,aAAa,CAAC;GAC9B,MAAM,KAAK;GACX,WAAW,wBAAwB,OAAO,OAAO,CAAA,CAAE;EACrD;EAEA,IAAI;GACF,OAAO,aAAa,MAAM,OAAO,SAAS,KAAK,CAAC;EAClD,SAAS,OAAO;GACd,IAAI,iBAAiB,gBACnB,OAAO,aAAa,mBAAmB,KAAK,GAAG,MAAM,MAAM;GAE7D,MAAM;EACR;CACF;AACF;AAEA,SAAS,mBAAmB,OAAgC;CAC1D,MAAM,EAAE,MAAM,cAAc,MAAM;CAGlC,IAAI,CAAC,MAAM,OAAO,EAAE,OAAO,MAAM,QAAQ;CACzC,OAAO,EACL,OAAO;EACL,IAAI;EACJ;EACA,SAAS,MAAM;EACf,QAAQ,MAAM;EACd,GAAI,cAAc,KAAA,IAAY,CAAC,IAAI,EAAE,UAAU;CACjD,EACF;AACF;AAEA,SAAS,aAAa,MAAe,SAAS,KAAe;CAC3D,OAAO,IAAI,SAAS,KAAK,UAAU,IAAI,GAAG;EACxC;EACA,SAAS,EAAE,gBAAgB,mBAAmB;CAChD,CAAC;AACH"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@happyvertical/smrt-app-mcp",
3
- "version": "0.40.44",
3
+ "version": "0.40.45",
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",
@@ -44,7 +44,7 @@
44
44
  "author": "HappyVertical",
45
45
  "dependencies": {
46
46
  "@modelcontextprotocol/sdk": "^1.25.2",
47
- "@happyvertical/smrt-core": "0.40.44"
47
+ "@happyvertical/smrt-core": "0.40.45"
48
48
  },
49
49
  "devDependencies": {
50
50
  "@types/node": "24.13.2",
@@ -1,13 +0,0 @@
1
- //#region src/errors.ts
2
- var McpAccessError = class extends Error {
3
- constructor(status, message) {
4
- super(message);
5
- this.status = status;
6
- this.name = "McpAccessError";
7
- }
8
- status;
9
- };
10
- //#endregion
11
- export { McpAccessError as t };
12
-
13
- //# sourceMappingURL=errors-DQqDD-im.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"errors-DQqDD-im.js","names":[],"sources":["../../src/errors.ts"],"sourcesContent":["/**\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 ) {\n super(message);\n this.name = 'McpAccessError';\n }\n}\n"],"mappings":";AAKO,IAAM,iBAAN,cAA6B,MAAM;CACxC,YACW,QACT,SACA;EACA,MAAM,OAAO;EAHJ,KAAA,SAAA;EAIT,KAAK,OAAO;CACd;CALW;AAMb"}