@happyvertical/smrt-app-mcp 0.54.2 → 0.54.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENTS.md CHANGED
@@ -13,7 +13,12 @@ tool policy; `./sveltekit` owns the stateless Streamable HTTP transport.
13
13
 
14
14
  ## Patterns
15
15
 
16
- - Generate tools from core, then filter by allowed class-name prefixes.
16
+ - Generate tools from core scoped to the allow-list (`MCPConfig.classNames`),
17
+ then filter by allowed class names: a duplicate tool name among registered
18
+ classes the app never publishes must not fail its catalog or calls (#3490).
19
+ `mountMcpAppRoute`/`createDefaultMcpAppServer` resolve each model
20
+ constructor to its registry-qualified name, so a same-named class elsewhere
21
+ never collides; a raw simple name two classes share fails closed.
17
22
  - Treat list/get tools as read-only; mutating tools require authentication unless the app deliberately wraps them with stronger policy.
18
23
  - Keep app policy outside generated tool schemas. The wrapper owns auth, allow-list, and workflow assertion behavior.
19
24
  - Return 404 for tools outside the app allow-list so private generated tools are not enumerated by mistake.
package/README.md CHANGED
@@ -378,7 +378,14 @@ re-resolves credentials per request, refuses redirects, and forwards resource
378
378
  and tool metadata without inventing UI or extension capabilities.
379
379
 
380
380
  Generated tool allow-lists use the generator-owned original class identity, even
381
- when the advertised name is a canonical alias. Guards keyed by either the alias
381
+ when the advertised name is a canonical alias. Core generates tools only for the
382
+ allow-listed classes, so a duplicate tool name among registered classes the app
383
+ does not publish (a consumed package's models, say) cannot fail `tools/list` or
384
+ `tools/call`; a duplicate within the allow-list still fails closed. The
385
+ model-based routes (`mountMcpAppRoute`, `createDefaultMcpAppServer`) allow-list
386
+ each constructor's registry-qualified name, so an app model that shares a
387
+ simple name with another registered class gets its own tools; a raw
388
+ `allowedClassNames` simple name that two classes share is refused. Guards keyed by either the alias
382
389
  or original tool name run before both direct and task dispatch. Authored workflows
383
390
  retain their explicit names and effect policy; catalogs containing authored
384
391
  workflows keep private cache scope because they have no generated tenant identity.
@@ -7141,12 +7141,21 @@ function configuredEffects(effects) {
7141
7141
  if (!Array.isArray(effects) || effects.some((effect) => !MCP_TOOL_EFFECTS.includes(effect))) throw new TypeError("MCP effects must be an array of 'read', 'write' or 'destructive'.");
7142
7142
  return new Set(effects);
7143
7143
  }
7144
+ function sameNamedClasses(simpleName) {
7145
+ const wanted = simpleName.toLowerCase();
7146
+ const seen = /* @__PURE__ */ new Set();
7147
+ const matches = [];
7148
+ for (const [key, info] of ObjectRegistry.getAllClasses()) {
7149
+ if (seen.has(info) || (info.name || key).toLowerCase() !== wanted) continue;
7150
+ seen.add(info);
7151
+ matches.push([key, info]);
7152
+ }
7153
+ return matches;
7154
+ }
7144
7155
  function isTenantScopedTool(identity) {
7156
+ if (identity.qualifiedName) return ObjectRegistry.isTenantScoped(identity.qualifiedName) || isTenantScopedClassResolved(identity.qualifiedName);
7145
7157
  const objectName = identity.objectName.toLowerCase();
7146
- for (const [key, classInfo] of ObjectRegistry.getAllClasses()) {
7147
- const name = classInfo.name || key;
7148
- if (name.toLowerCase() === objectName) return ObjectRegistry.isTenantScoped(name) || isTenantScopedClassResolved(name);
7149
- }
7158
+ for (const [key, classInfo] of ObjectRegistry.getAllClasses()) if ((classInfo.name || key).toLowerCase() === objectName && (ObjectRegistry.isTenantScoped(key) || isTenantScopedClassResolved(key))) return true;
7150
7159
  return false;
7151
7160
  }
7152
7161
  function taskOwnerIdFor(principal) {
@@ -7160,6 +7169,7 @@ function createMcpAppServer(options) {
7160
7169
  resources.set(resource.descriptor.uri, resource);
7161
7170
  }
7162
7171
  const allowedClassNames = new Set(options.allowedClassNames.map((name) => name.toLowerCase()));
7172
+ const generatorScope = Object.freeze([...allowedClassNames]);
7163
7173
  const getPublicPatterns = options.publicToolPatterns ?? (() => []);
7164
7174
  const toolPolicy = options.toolPolicy;
7165
7175
  const workflowAssertions = options.workflowAssertions ?? {};
@@ -7171,8 +7181,13 @@ function createMcpAppServer(options) {
7171
7181
  }
7172
7182
  const allowedEffects = configuredEffects(options.effects);
7173
7183
  const requestedToolListCacheHint = configuredToolListCacheHint(options.toolListCache);
7174
- const tasksEnabled = options.allowedClassNames.some((className) => {
7175
- const mcp = ObjectRegistry.getConfig(className).mcp;
7184
+ const tasksEnabled = [...ObjectRegistry.getAllClasses()].some(([key, classInfo]) => {
7185
+ if (![
7186
+ key,
7187
+ classInfo.qualifiedName,
7188
+ classInfo.name
7189
+ ].some((name) => !!name && allowedClassNames.has(name.toLowerCase()))) return false;
7190
+ const mcp = ObjectRegistry.getConfig(key).mcp;
7176
7191
  return typeof mcp === "object" && (mcp.tasks === true || Array.isArray(mcp.tasks) && mcp.tasks.length > 0);
7177
7192
  });
7178
7193
  function userForGenerator(principal) {
@@ -7194,7 +7209,10 @@ function createMcpAppServer(options) {
7194
7209
  }
7195
7210
  function makeGenerator(principal, taskStore) {
7196
7211
  const user = userForGenerator(principal);
7197
- return new MCPGenerator(options.serverInfo, {
7212
+ return new MCPGenerator({
7213
+ ...options.serverInfo,
7214
+ classNames: generatorScope
7215
+ }, {
7198
7216
  ...options.smrtOptions(),
7199
7217
  user,
7200
7218
  tenantId: principal?.tenantId,
@@ -7215,7 +7233,7 @@ function createMcpAppServer(options) {
7215
7233
  const coreTools = (await generator.generateTools()).map((tool) => ({
7216
7234
  tool,
7217
7235
  identity: generator.getToolIdentity(tool)
7218
- })).filter(({ identity }) => allowedClassNames.has(identity.objectName.toLowerCase()));
7236
+ })).filter(({ identity }) => allowedClassNames.has(identity.objectName.toLowerCase()) || !!identity.qualifiedName && allowedClassNames.has(identity.qualifiedName.toLowerCase()));
7219
7237
  const names = new Set(coreTools.map(({ tool }) => tool.name));
7220
7238
  for (const workflowTool of workflowTools) {
7221
7239
  if (names.has(workflowTool.tool.name)) throw new Error(`Duplicate MCP tool name: ${workflowTool.tool.name}`);
@@ -7397,12 +7415,16 @@ function createMcpAppServer(options) {
7397
7415
  }
7398
7416
  function isTaskOrigin(identity, origin) {
7399
7417
  if (identity.action.toLowerCase() !== origin.method.toLowerCase()) return false;
7400
- for (const [key, info] of ObjectRegistry.getAllClasses()) {
7401
- const name = info.name || key;
7402
- if (name.toLowerCase() !== identity.objectName.toLowerCase()) continue;
7403
- return origin.objectType === name || origin.objectType === info.qualifiedName;
7418
+ if (identity.qualifiedName) {
7419
+ if (origin.objectType === identity.qualifiedName) return true;
7420
+ const info2 = ObjectRegistry.getAllClasses().get(identity.qualifiedName);
7421
+ return !!info2?.name && origin.objectType === info2.name && sameNamedClasses(info2.name).length === 1;
7404
7422
  }
7405
- return false;
7423
+ const matches = sameNamedClasses(identity.objectName);
7424
+ if (matches.length !== 1) return false;
7425
+ const [[key, info]] = matches;
7426
+ const name = info.name || key;
7427
+ return origin.objectType === name || origin.objectType === info.qualifiedName;
7406
7428
  }
7407
7429
  async function assertTaskToolVisible(taskId, principal) {
7408
7430
  const origin = await taskOrigin(taskId, principal);
@@ -7473,6 +7495,18 @@ function mcpAllowedClassNames(models) {
7473
7495
  }
7474
7496
  return [...names];
7475
7497
  }
7498
+ function mcpAllowedRegistryNames(models) {
7499
+ if (!Array.isArray(models)) throw new TypeError("MCP models must be an explicit array.");
7500
+ const classes = ObjectRegistry.getAllClasses();
7501
+ const names = /* @__PURE__ */ new Set();
7502
+ for (const model of models) {
7503
+ const registered = typeof model === "function" ? ObjectRegistry.getClassByConstructor(model) : void 0;
7504
+ const key = registered ? registered.qualifiedName && classes.get(registered.qualifiedName) ? registered.qualifiedName : [...classes].find(([, info]) => info === registered)?.[0] : void 0;
7505
+ if (!registered || !key || registered.constructor !== model) throw new TypeError("MCP models must be registered @smrt() class constructors.");
7506
+ names.add(key);
7507
+ }
7508
+ return [...names];
7509
+ }
7476
7510
  var scopePattern = /^[\x21\x23-\x5b\x5d-\x7e]+$/u;
7477
7511
  function snapshotStrings(values, label) {
7478
7512
  if (!Array.isArray(values)) throw new TypeError(`MCP ${label} must be an explicit array.`);
@@ -7497,7 +7531,7 @@ function mcpPrincipalScopePolicy(options) {
7497
7531
  }
7498
7532
  function createDefaultMcpAppServer(options) {
7499
7533
  const { models, requiredScopes, principalKinds, serverInfo, toolPolicy: narrowTool, resourcePolicy: narrowResource, taskPrincipalPolicy: narrowTask, ...rest } = options;
7500
- const allowedClassNames = mcpAllowedClassNames(models);
7534
+ const allowedClassNames = mcpAllowedRegistryNames(models);
7501
7535
  const base = mcpPrincipalScopePolicy({
7502
7536
  requiredScopes,
7503
7537
  principalKinds
@@ -7644,4 +7678,4 @@ function assertExtensionMetadata(value) {
7644
7678
  //#endregion
7645
7679
  export { McpAccessError as C, MCP_TOOL_ACCESS_DENIED_CODE as S, MCP_APP_RESOURCE_MAX_BYTES as _, createDefaultMcpAppServer as a, createMcpWorkflowTool as b, createMcpAppServer as c, isAllowedCoreTool as d, isPublicMcpTool as f, mcpToolEffect as g, matchesToolPattern as h, DEFAULT_MCP_APP_SERVER_INFO as i, MCP_TOOL_EFFECTS as l, isReadOnlyToolName as m, createMcpProtocolServer as n, mcpAllowedClassNames as o, isPublicToolName as p, createMcpProtocolServerForRequest as r, mcpPrincipalScopePolicy as s, MCP_TASKS_EXTENSION as t, classNamePrefixes as u, MCP_APP_RESOURCE_MIME as v, MCP_ORIGIN_DENIED_CODE as x, prepareMcpAppResource as y };
7646
7680
 
7647
- //# sourceMappingURL=protocol-BlbEg3jn.js.map
7681
+ //# sourceMappingURL=protocol-CoCuR_w7.js.map