@happyvertical/smrt-app-mcp 0.37.2 → 0.37.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,13 @@
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
@@ -0,0 +1 @@
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"}
package/dist/index.js CHANGED
@@ -1,90 +1,78 @@
1
- import { M as McpAccessError } from "./chunks/errors-W_2Xu9nk.js";
1
+ import { t as McpAccessError } from "./chunks/errors-DQqDD-im.js";
2
2
  import { MCPGenerator } from "@happyvertical/smrt-core/generators/mcp";
3
+ //#region src/tools.ts
3
4
  function matchesToolPattern(toolName, pattern) {
4
- if (!pattern) return false;
5
- if (pattern === "*") return true;
6
- const parts = pattern.split("*");
7
- if (parts.length === 1) return toolName === pattern;
8
- let cursor = 0;
9
- if (parts[0] && !toolName.startsWith(parts[0])) return false;
10
- for (const part of parts) {
11
- if (!part) continue;
12
- const index = toolName.indexOf(part, cursor);
13
- if (index < 0) return false;
14
- cursor = index + part.length;
15
- }
16
- const last = parts.at(-1);
17
- return !last || toolName.endsWith(last);
5
+ if (!pattern) return false;
6
+ if (pattern === "*") return true;
7
+ const parts = pattern.split("*");
8
+ if (parts.length === 1) return toolName === pattern;
9
+ let cursor = 0;
10
+ if (parts[0] && !toolName.startsWith(parts[0])) return false;
11
+ for (const part of parts) {
12
+ if (!part) continue;
13
+ const index = toolName.indexOf(part, cursor);
14
+ if (index < 0) return false;
15
+ cursor = index + part.length;
16
+ }
17
+ const last = parts.at(-1);
18
+ return !last || toolName.endsWith(last);
18
19
  }
19
20
  function isReadOnlyToolName(toolName) {
20
- return toolName.endsWith("_list") || toolName.endsWith("_get");
21
+ return toolName.endsWith("_list") || toolName.endsWith("_get");
21
22
  }
22
23
  function isPublicToolName(toolName, patterns) {
23
- return isReadOnlyToolName(toolName) && patterns.some((pattern) => matchesToolPattern(toolName, pattern));
24
+ return isReadOnlyToolName(toolName) && patterns.some((pattern) => matchesToolPattern(toolName, pattern));
24
25
  }
25
26
  function classNamePrefixes(classNames) {
26
- return new Set(classNames.map((className) => `${className.toLowerCase()}_`));
27
+ return new Set(classNames.map((className) => `${className.toLowerCase()}_`));
27
28
  }
28
29
  function isAllowedCoreTool(toolName, prefixes) {
29
- for (const prefix of prefixes) {
30
- if (toolName.startsWith(prefix)) return true;
31
- }
32
- return false;
30
+ for (const prefix of prefixes) if (toolName.startsWith(prefix)) return true;
31
+ return false;
33
32
  }
33
+ //#endregion
34
+ //#region src/server.ts
34
35
  function createMcpAppServer(options) {
35
- const allowedPrefixes = classNamePrefixes(options.allowedClassNames);
36
- const getPublicPatterns = options.publicToolPatterns ?? (() => []);
37
- const workflowAssertions = options.workflowAssertions ?? {};
38
- function makeGenerator(user) {
39
- return new MCPGenerator(options.serverInfo, {
40
- ...options.smrtOptions(),
41
- user: user?.id ? { id: user.id, roles: user.roles } : void 0
42
- });
43
- }
44
- async function listTools(input) {
45
- const tools = await makeGenerator().generateTools();
46
- const allowed = tools.filter(
47
- (tool) => isAllowedCoreTool(tool.name, allowedPrefixes)
48
- );
49
- if (input.authenticated) return allowed;
50
- const patterns = getPublicPatterns();
51
- return allowed.filter((tool) => isPublicToolName(tool.name, patterns));
52
- }
53
- async function callTool(input) {
54
- const args = input.arguments ?? {};
55
- const user = input.user ?? null;
56
- const tools = await listTools({ authenticated: true });
57
- if (!tools.some((tool) => tool.name === input.name)) {
58
- throw new McpAccessError(404, `Unknown MCP tool: ${input.name}`);
59
- }
60
- if (!user && !isPublicToolName(input.name, getPublicPatterns())) {
61
- throw new McpAccessError(
62
- 401,
63
- `Authentication is required for MCP tool: ${input.name}`
64
- );
65
- }
66
- const assertion = workflowAssertions[input.name];
67
- if (assertion) {
68
- assertion(args, user);
69
- }
70
- return makeGenerator(user).handleToolCall({
71
- method: "tools/call",
72
- params: { arguments: args, name: input.name }
73
- });
74
- }
75
- return {
76
- listTools,
77
- callTool,
78
- serverInfo: options.serverInfo
79
- };
36
+ const allowedPrefixes = classNamePrefixes(options.allowedClassNames);
37
+ const getPublicPatterns = options.publicToolPatterns ?? (() => []);
38
+ const workflowAssertions = options.workflowAssertions ?? {};
39
+ function makeGenerator(user) {
40
+ return new MCPGenerator(options.serverInfo, {
41
+ ...options.smrtOptions(),
42
+ user: user?.id ? {
43
+ id: user.id,
44
+ roles: user.roles
45
+ } : void 0
46
+ });
47
+ }
48
+ 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));
53
+ }
54
+ async function callTool(input) {
55
+ 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}`);
59
+ const assertion = workflowAssertions[input.name];
60
+ if (assertion) assertion(args, user);
61
+ return makeGenerator(user).handleToolCall({
62
+ method: "tools/call",
63
+ params: {
64
+ arguments: args,
65
+ name: input.name
66
+ }
67
+ });
68
+ }
69
+ return {
70
+ listTools,
71
+ callTool,
72
+ serverInfo: options.serverInfo
73
+ };
80
74
  }
81
- export {
82
- McpAccessError,
83
- classNamePrefixes,
84
- createMcpAppServer,
85
- isAllowedCoreTool,
86
- isPublicToolName,
87
- isReadOnlyToolName,
88
- matchesToolPattern
89
- };
90
- //# sourceMappingURL=index.js.map
75
+ //#endregion
76
+ export { McpAccessError, classNamePrefixes, createMcpAppServer, isAllowedCoreTool, isPublicToolName, isReadOnlyToolName, matchesToolPattern };
77
+
78
+ //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","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"],"names":[],"mappings":"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"}
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,8 +1,8 @@
1
1
  {
2
2
  "version": "1.0.0",
3
- "timestamp": 1782873325093,
3
+ "timestamp": 1782943573056,
4
4
  "packageName": "@happyvertical/smrt-app-mcp",
5
- "packageVersion": "0.37.2",
5
+ "packageVersion": "0.37.4",
6
6
  "objects": {},
7
7
  "moduleType": "smrt",
8
8
  "smrtDependencies": [
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "generatedAt": "2026-07-01T02:35:26.099Z",
3
+ "generatedAt": "2026-07-01T22:06:14.141Z",
4
4
  "packageName": "@happyvertical/smrt-app-mcp",
5
- "packageVersion": "0.37.2",
5
+ "packageVersion": "0.37.4",
6
6
  "sourceManifestPath": "dist/manifest.json",
7
7
  "agentDocPath": "AGENTS.md",
8
8
  "sourceHashes": {
9
- "manifest": "b91c799276379525c866520d110fa6a20ae8572069afa3537e77bd7fdd35f6c5",
10
- "packageJson": "27f23287dc53f4eae7ceb5a480760d264df7d9923e9c50670a0bd8a09ec5722c",
9
+ "manifest": "54636b4748f8bb2e921d300f570a7ff4e96dbe7c5fd81f480ea5f99fe990641c",
10
+ "packageJson": "eef53a40801d55ede4277c3dd43e3ba565bb2f2edf9f25d0224ae86b75e5e559",
11
11
  "agents": "fd1dc36d1530f81aae49e6efa2e2f5011a8a62d30816f25649d4cb597fc5662a"
12
12
  },
13
13
  "exports": [
@@ -17,10 +17,10 @@
17
17
  "dependencies": {
18
18
  "@happyvertical/smrt-core": "workspace:*",
19
19
  "@modelcontextprotocol/sdk": "^1.25.2",
20
- "@types/node": "25.0.9",
20
+ "@types/node": "24.13.2",
21
21
  "typescript": "^5.9.3",
22
- "vite": "^7.3.6",
23
- "vitest": "^4.0.17"
22
+ "vite": "^8.1.2",
23
+ "vitest": "^4.1.9"
24
24
  },
25
25
  "smrtDependencies": [
26
26
  "@happyvertical/smrt-core"
package/dist/sveltekit.js CHANGED
@@ -1,53 +1,43 @@
1
- import { M as McpAccessError } from "./chunks/errors-W_2Xu9nk.js";
2
- const defaultResolveUser = (event) => event.locals?.user ?? null;
3
- const defaultResolveAuthenticated = (event) => Boolean(event.locals?.user);
1
+ import { t as McpAccessError } from "./chunks/errors-DQqDD-im.js";
2
+ //#region src/sveltekit.ts
3
+ var defaultResolveUser = (event) => event.locals?.user ?? null;
4
+ var defaultResolveAuthenticated = (event) => Boolean(event.locals?.user);
4
5
  function mountMcpToolsRoute(server, options = {}) {
5
- const resolveAuthenticated = options.resolveAuthenticated ?? defaultResolveAuthenticated;
6
- return async (event) => {
7
- try {
8
- const tools = await server.listTools({
9
- authenticated: resolveAuthenticated(event)
10
- });
11
- return jsonResponse({ tools });
12
- } catch (error) {
13
- if (error instanceof McpAccessError) {
14
- return jsonResponse({ error: error.message }, error.status);
15
- }
16
- throw error;
17
- }
18
- };
6
+ const resolveAuthenticated = options.resolveAuthenticated ?? defaultResolveAuthenticated;
7
+ return async (event) => {
8
+ try {
9
+ return jsonResponse({ tools: await server.listTools({ authenticated: resolveAuthenticated(event) }) });
10
+ } catch (error) {
11
+ if (error instanceof McpAccessError) return jsonResponse({ error: error.message }, error.status);
12
+ throw error;
13
+ }
14
+ };
19
15
  }
20
16
  function mountMcpCallRoute(server, options = {}) {
21
- const resolveUser = options.resolveUser ?? defaultResolveUser;
22
- return async (event) => {
23
- const body = await event.request.json().catch(() => null);
24
- if (!body?.name) {
25
- return jsonResponse({ error: "name is required." }, 400);
26
- }
27
- const input = {
28
- arguments: body.arguments ?? {},
29
- name: body.name,
30
- user: resolveUser(event) ?? null
31
- };
32
- try {
33
- return jsonResponse(await server.callTool(input));
34
- } catch (error) {
35
- if (error instanceof McpAccessError) {
36
- return jsonResponse({ error: error.message }, error.status);
37
- }
38
- throw error;
39
- }
40
- };
17
+ const resolveUser = options.resolveUser ?? defaultResolveUser;
18
+ return async (event) => {
19
+ const body = await event.request.json().catch(() => null);
20
+ if (!body?.name) return jsonResponse({ error: "name is required." }, 400);
21
+ const input = {
22
+ arguments: body.arguments ?? {},
23
+ name: body.name,
24
+ user: resolveUser(event) ?? null
25
+ };
26
+ try {
27
+ return jsonResponse(await server.callTool(input));
28
+ } catch (error) {
29
+ if (error instanceof McpAccessError) return jsonResponse({ error: error.message }, error.status);
30
+ throw error;
31
+ }
32
+ };
41
33
  }
42
34
  function jsonResponse(body, status = 200) {
43
- return new Response(JSON.stringify(body), {
44
- status,
45
- headers: { "content-type": "application/json" }
46
- });
35
+ return new Response(JSON.stringify(body), {
36
+ status,
37
+ headers: { "content-type": "application/json" }
38
+ });
47
39
  }
48
- export {
49
- McpAccessError,
50
- mountMcpCallRoute,
51
- mountMcpToolsRoute
52
- };
53
- //# sourceMappingURL=sveltekit.js.map
40
+ //#endregion
41
+ export { McpAccessError, mountMcpCallRoute, mountMcpToolsRoute };
42
+
43
+ //# sourceMappingURL=sveltekit.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"sveltekit.js","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"],"names":[],"mappings":";AAiCA,MAAM,qBAAsC,CAAC,UAC1C,MAAM,QAAQ,QAAQ;AAEzB,MAAM,8BAA8B,CAAC,UACnC,QAAQ,MAAM,QAAQ,IAAI;AAoBrB,SAAS,mBACd,QACA,UAAgC,IACd;AAClB,QAAM,uBACJ,QAAQ,wBAAwB;AAElC,SAAO,OAAO,UAAU;AACtB,QAAI;AACF,YAAM,QAAQ,MAAM,OAAO,UAAU;AAAA,QACnC,eAAe,qBAAqB,KAAK;AAAA,MAAA,CAC1C;AACD,aAAO,aAAa,EAAE,OAAO;AAAA,IAC/B,SAAS,OAAO;AACd,UAAI,iBAAiB,gBAAgB;AACnC,eAAO,aAAa,EAAE,OAAO,MAAM,QAAA,GAAW,MAAM,MAAM;AAAA,MAC5D;AACA,YAAM;AAAA,IACR;AAAA,EACF;AACF;AAMO,SAAS,kBACd,QACA,UAAgC,IACd;AAClB,QAAM,cAAc,QAAQ,eAAe;AAE3C,SAAO,OAAO,UAAU;AACtB,UAAM,OAAQ,MAAM,MAAM,QAAQ,OAAO,MAAM,MAAM,IAAI;AAKzD,QAAI,CAAC,MAAM,MAAM;AACf,aAAO,aAAa,EAAE,OAAO,oBAAA,GAAuB,GAAG;AAAA,IACzD;AAEA,UAAM,QAAuB;AAAA,MAC3B,WAAW,KAAK,aAAa,CAAA;AAAA,MAC7B,MAAM,KAAK;AAAA,MACX,MAAM,YAAY,KAAK,KAAK;AAAA,IAAA;AAG9B,QAAI;AACF,aAAO,aAAa,MAAM,OAAO,SAAS,KAAK,CAAC;AAAA,IAClD,SAAS,OAAO;AACd,UAAI,iBAAiB,gBAAgB;AACnC,eAAO,aAAa,EAAE,OAAO,MAAM,QAAA,GAAW,MAAM,MAAM;AAAA,MAC5D;AACA,YAAM;AAAA,IACR;AAAA,EACF;AACF;AAEA,SAAS,aAAa,MAAe,SAAS,KAAe;AAC3D,SAAO,IAAI,SAAS,KAAK,UAAU,IAAI,GAAG;AAAA,IACxC;AAAA,IACA,SAAS,EAAE,gBAAgB,mBAAA;AAAA,EAAmB,CAC/C;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, 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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@happyvertical/smrt-app-mcp",
3
- "version": "0.37.2",
3
+ "version": "0.37.4",
4
4
  "description": "App-runtime MCP server scaffolding for SMRT apps — `createMcpAppServer` plus transport adapters (SvelteKit today) for exposing a SMRT app's MCP surface over HTTP.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -44,13 +44,13 @@
44
44
  "author": "HappyVertical",
45
45
  "dependencies": {
46
46
  "@modelcontextprotocol/sdk": "^1.25.2",
47
- "@happyvertical/smrt-core": "0.37.2"
47
+ "@happyvertical/smrt-core": "0.37.4"
48
48
  },
49
49
  "devDependencies": {
50
- "@types/node": "25.0.9",
50
+ "@types/node": "24.13.2",
51
51
  "typescript": "^5.9.3",
52
- "vite": "^7.3.6",
53
- "vitest": "^4.0.17"
52
+ "vite": "^8.1.2",
53
+ "vitest": "^4.1.9"
54
54
  },
55
55
  "scripts": {
56
56
  "build": "vite build --mode library",
@@ -1,12 +0,0 @@
1
- class McpAccessError extends Error {
2
- constructor(status, message) {
3
- super(message);
4
- this.status = status;
5
- this.name = "McpAccessError";
6
- }
7
- status;
8
- }
9
- export {
10
- McpAccessError as M
11
- };
12
- //# sourceMappingURL=errors-W_2Xu9nk.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"errors-W_2Xu9nk.js","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"],"names":[],"mappings":"AAKO,MAAM,uBAAuB,MAAM;AAAA,EACxC,YACW,QACT,SACA;AACA,UAAM,OAAO;AAHJ,SAAA,SAAA;AAIT,SAAK,OAAO;AAAA,EACd;AAAA,EALW;AAMb;"}