@noodleseed/one 0.166.0 → 0.168.0

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.
Files changed (120) hide show
  1. package/dist/commands/author-loop.d.ts.map +1 -1
  2. package/dist/commands/author-loop.js +18 -3
  3. package/dist/commands/author-loop.js.map +1 -1
  4. package/dist/commands/deploy-ops.d.ts.map +1 -1
  5. package/dist/commands/deploy-ops.js +21 -2
  6. package/dist/commands/deploy-ops.js.map +1 -1
  7. package/dist/commands/deploy-status-ops.d.ts.map +1 -1
  8. package/dist/commands/deploy-status-ops.js +16 -0
  9. package/dist/commands/deploy-status-ops.js.map +1 -1
  10. package/dist/control-plane-request.d.ts +2 -0
  11. package/dist/control-plane-request.d.ts.map +1 -1
  12. package/dist/control-plane-request.js +18 -0
  13. package/dist/control-plane-request.js.map +1 -1
  14. package/dist/deploy.d.ts +2 -1
  15. package/dist/deploy.d.ts.map +1 -1
  16. package/dist/deploy.js +15 -0
  17. package/dist/deploy.js.map +1 -1
  18. package/dist/dev.d.ts +2 -0
  19. package/dist/dev.d.ts.map +1 -1
  20. package/dist/dev.js +12 -1
  21. package/dist/dev.js.map +1 -1
  22. package/dist/devtools-auth-session.d.ts +5 -0
  23. package/dist/devtools-auth-session.d.ts.map +1 -1
  24. package/dist/devtools-auth-session.js +41 -2
  25. package/dist/devtools-auth-session.js.map +1 -1
  26. package/dist/devtools-auth-types.d.ts +2 -1
  27. package/dist/devtools-auth-types.d.ts.map +1 -1
  28. package/dist/devtools-auth-types.js.map +1 -1
  29. package/dist/devtools-auth-ui.d.ts +1 -1
  30. package/dist/devtools-auth-ui.d.ts.map +1 -1
  31. package/dist/devtools-auth-ui.js +13 -5
  32. package/dist/devtools-auth-ui.js.map +1 -1
  33. package/dist/devtools-harness.d.ts +1 -0
  34. package/dist/devtools-harness.d.ts.map +1 -1
  35. package/dist/devtools-harness.js +12 -7
  36. package/dist/devtools-harness.js.map +1 -1
  37. package/dist/devtools-preview.d.ts +1 -0
  38. package/dist/devtools-preview.d.ts.map +1 -1
  39. package/dist/devtools-preview.js +21 -6
  40. package/dist/devtools-preview.js.map +1 -1
  41. package/dist/devtools-rpc-forwarder.d.ts +1 -0
  42. package/dist/devtools-rpc-forwarder.d.ts.map +1 -1
  43. package/dist/devtools-rpc-forwarder.js +2 -1
  44. package/dist/devtools-rpc-forwarder.js.map +1 -1
  45. package/dist/preview-session.d.ts +1 -0
  46. package/dist/preview-session.d.ts.map +1 -1
  47. package/dist/preview-session.js +1 -0
  48. package/dist/preview-session.js.map +1 -1
  49. package/node_modules/@noodle-borg/agent-kit/dist/behavior-skills.js +4 -2
  50. package/node_modules/@noodle-borg/agent-kit/dist/generated/example-files.js +3 -3
  51. package/node_modules/@noodle-borg/agent-kit/dist/plugin-bootstrap-skill.js +8 -1
  52. package/node_modules/@noodle-borg/agent-kit/dist/skill-authoring-auth-ref.js +5 -2
  53. package/node_modules/@noodle-borg/agent-kit/dist/skill-embedded-assistant-ref.js +20 -0
  54. package/node_modules/@noodle-borg/agent-kit/dist/skill-router.js +9 -2
  55. package/node_modules/@noodle-borg/agent-kit/package.json +1 -1
  56. package/node_modules/@noodle-borg/agent-packaging/dist/product-skill-types.d.ts +1 -0
  57. package/node_modules/@noodle-borg/agent-packaging/dist/product-skill-validation.js +4 -0
  58. package/node_modules/@noodle-borg/app-package/dist/schema.js +1 -0
  59. package/node_modules/@noodle-borg/app-package/dist/types.d.ts +1 -0
  60. package/node_modules/@noodle-borg/authoring/dist/server.d.ts +3 -1
  61. package/node_modules/@noodle-borg/authoring/dist/server.js +7 -6
  62. package/node_modules/@noodle-borg/capabilities/dist/index.js +7 -0
  63. package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-local-authoring.js +8 -0
  64. package/node_modules/@noodle-borg/compiler/dist/app-package/compile.js +9 -1
  65. package/node_modules/@noodle-borg/compiler/dist/artifact/catalog-emission.js +3 -0
  66. package/node_modules/@noodle-borg/compiler/dist/artifact/types.d.ts +2 -0
  67. package/node_modules/@noodle-borg/compiler/dist/artifact/version.d.ts +2 -1
  68. package/node_modules/@noodle-borg/compiler/dist/artifact/version.js +2 -1
  69. package/node_modules/@noodle-borg/compiler/dist/manifest/schema.d.ts +5 -0
  70. package/node_modules/@noodle-borg/compiler/dist/manifest/schema.js +12 -2
  71. package/node_modules/@noodle-borg/protocol/dist/handlers/tools.js +3 -0
  72. package/node_modules/@noodle-borg/protocol/dist/index.d.ts +1 -0
  73. package/node_modules/@noodle-borg/protocol/dist/index.js +1 -0
  74. package/node_modules/@noodle-borg/protocol/dist/mapping.d.ts +5 -1
  75. package/node_modules/@noodle-borg/protocol/dist/mapping.js +15 -7
  76. package/node_modules/@noodle-borg/protocol/dist/sdk-server.d.ts +2 -0
  77. package/node_modules/@noodle-borg/protocol/dist/tool-discovery.d.ts +14 -0
  78. package/node_modules/@noodle-borg/protocol/dist/tool-discovery.js +12 -0
  79. package/node_modules/@noodle-borg/protocol/dist/v2/server.js +3 -0
  80. package/node_modules/@noodle-borg/service/dist/build-info.js +4 -2
  81. package/node_modules/@noodle-borg/service/dist/deployment-access-mode.js +24 -0
  82. package/node_modules/@noodle-borg/service/dist/deployment-activation-precondition.js +27 -0
  83. package/node_modules/@noodle-borg/service/dist/deployment-authentication.js +2 -2
  84. package/node_modules/@noodle-borg/service/dist/deployment-execution.js +2 -2
  85. package/node_modules/@noodle-borg/service/dist/deployment-record-version.js +26 -2
  86. package/node_modules/@noodle-borg/service/dist/registry-access.js +33 -31
  87. package/node_modules/@noodle-borg/service/dist/registry-deploy-transaction.js +97 -22
  88. package/node_modules/@noodle-borg/service/dist/registry-rollback.js +13 -1
  89. package/node_modules/@noodle-borg/service/dist/registry-state.d.ts +3 -7
  90. package/node_modules/@noodle-borg/service/dist/registry-state.js +6 -5
  91. package/node_modules/@noodle-borg/service/dist/registry-status.js +3 -4
  92. package/node_modules/@noodle-borg/service/dist/registry-targets.js +35 -28
  93. package/node_modules/@noodle-borg/service/dist/registry-types.d.ts +3 -1
  94. package/node_modules/@noodle-borg/service/dist/registry.js +40 -39
  95. package/node_modules/@noodle-borg/service/dist/routes/access.js +4 -2
  96. package/node_modules/@noodle-borg/service/dist/routes/auth-doctor-live.js +3 -2
  97. package/node_modules/@noodle-borg/service/dist/routes/control-plane.js +1 -0
  98. package/node_modules/@noodle-borg/service/dist/routes/deploy-preflight.js +2 -2
  99. package/node_modules/@noodle-borg/service/dist/serve-resource-auth.js +1 -1
  100. package/node_modules/@noodle-borg/service/dist/store/in-memory.d.ts +3 -8
  101. package/node_modules/@noodle-borg/service/dist/store/in-memory.js +6 -4
  102. package/node_modules/@noodle-borg/service/dist/store/json-file.d.ts +3 -8
  103. package/node_modules/@noodle-borg/service/dist/store/json-file.js +8 -6
  104. package/node_modules/@noodle-borg/service/dist/store/records.js +13 -2
  105. package/node_modules/@noodle-borg/service/dist/store.d.ts +19 -8
  106. package/node_modules/@noodle-borg/service/package.json +1 -1
  107. package/node_modules/@noodle-borg/transport-http/dist/handler.d.ts +2 -3
  108. package/node_modules/@noodle-borg/transport-http/dist/handler.js +3 -4
  109. package/node_modules/@noodle-borg/transport-http/dist/identity-authorization.d.ts +3 -2
  110. package/node_modules/@noodle-borg/transport-http/dist/identity-authorization.js +23 -12
  111. package/node_modules/@noodle-borg/transport-http/dist/index.d.ts +1 -0
  112. package/node_modules/@noodle-borg/transport-http/dist/serve-request.js +3 -0
  113. package/node_modules/@noodle-borg/transport-http/dist/target-authentication.d.ts +15 -0
  114. package/node_modules/@noodle-borg/transport-http/dist/target-authentication.js +9 -0
  115. package/node_modules/@noodle-borg/wire-contracts/dist/index.d.ts +6 -0
  116. package/node_modules/@noodle-borg/wire-contracts/dist/index.js +4 -1
  117. package/node_modules/@noodle-borg/wire-contracts/dist/service-info.d.ts +41 -0
  118. package/node_modules/@noodle-borg/wire-contracts/dist/service-info.js +26 -0
  119. package/package.json +2 -2
  120. package/node_modules/@noodle-borg/service/dist/routes/access-mode.js +0 -7
@@ -31,6 +31,13 @@ export const PRODUCT_FEATURES = [
31
31
  description: 'Direct or federated OIDC resource-server verification assigns customer identity at the trusted verifier boundary; Firebase and Microsoft remain built-in adapters.',
32
32
  hosts: { claude: 'supported', chatgpt: 'supported', embedded: 'supported' },
33
33
  },
34
+ {
35
+ id: 'mixed-customer-auth',
36
+ title: 'Mixed anonymous and customer access',
37
+ since: 'next',
38
+ description: 'Anonymous Help and protected customer tools share one MCP endpoint with direct or federated OIDC sign-in. Local Devtools supports optional sign-in; live host linking remains unverified.',
39
+ hosts: { claude: 'preview', chatgpt: 'preview', embedded: 'preview' },
40
+ },
34
41
  {
35
42
  id: 'explicit-context-provider',
36
43
  title: 'Explicit context-provider tool',
@@ -104,6 +104,14 @@ export const CATALOG_LOCAL_AUTHORING = [
104
104
  value: '<slug>',
105
105
  summary: 'Environment slug for local target context.',
106
106
  },
107
+ {
108
+ ...OPTIONAL_FLAG,
109
+ name: 'access',
110
+ type: 'string',
111
+ value: '<mode>',
112
+ constraints: { choices: ['mixed', 'customers'] },
113
+ summary: 'Local access: customers requires sign-in; mixed allows anonymous tools and optional customer sign-in. Defaults to customers when auth is declared.',
114
+ },
107
115
  CONNECTORS_FLAG,
108
116
  {
109
117
  ...OPTIONAL_FLAG,
@@ -182,7 +182,15 @@ function compileAppPackageInternal(input, preflightComplete) {
182
182
  skill,
183
183
  surface,
184
184
  provenance: {
185
- sourceManifestSha256: sha256Canonical(input.sourceManifest),
185
+ sourceManifestSha256: sha256Canonical({
186
+ ...input.sourceManifest,
187
+ tools: input.sourceManifest.tools.map((tool) => {
188
+ if (tool.authorization?.discovery !== 'authorized')
189
+ return tool;
190
+ const { discovery: _discovery, ...authorization } = tool.authorization;
191
+ return { ...tool, authorization };
192
+ }),
193
+ }),
186
194
  mcpSurfaceSha256: sha256Canonical(surface),
187
195
  compilerVersion: '1',
188
196
  },
@@ -35,6 +35,9 @@ export function emitCatalogArtifactSurfaces({ manifest, catalog, structuralAmbie
35
35
  ...(s.tool.authorization
36
36
  ? {
37
37
  authorization: {
38
+ ...(s.tool.authorization.discovery === 'public'
39
+ ? { discovery: 'public' }
40
+ : {}),
38
41
  ...(s.tool.authorization.requiredScopes
39
42
  ? {
40
43
  requiredScopes: canonicalizeAuthorizationValues(s.tool.authorization.requiredScopes),
@@ -92,6 +92,8 @@ export interface ArtifactMeta {
92
92
  readonly [key: string]: unknown;
93
93
  }
94
94
  export interface ArtifactToolAuthorization {
95
+ /** Public descriptor discovery does not grant execution permission. */
96
+ readonly discovery?: 'public';
95
97
  /** Every scope must be present on the verified caller. */
96
98
  readonly requiredScopes?: readonly string[];
97
99
  /** At least one role must be present on the verified caller. */
@@ -62,7 +62,8 @@
62
62
  * `0.20.0` (Nested operation authority): resolved operation references carry exact declared calls,
63
63
  * including independently validated credential bindings for sandboxed compute dependencies.
64
64
  */
65
- export declare const ARTIFACT_SCHEMA_VERSION = "0.20.0";
65
+ /** `0.21.0`: protected tools may opt into public descriptor discovery without execution permission. */
66
+ export declare const ARTIFACT_SCHEMA_VERSION = "0.21.0";
66
67
  /** mimeType for an MCP Apps UI resource (SEP-1865). Widgets are served under this profile. */
67
68
  export declare const MCP_APP_MIME_TYPE = "text/html;profile=mcp-app";
68
69
  //# sourceMappingURL=version.d.ts.map
@@ -62,7 +62,8 @@
62
62
  * `0.20.0` (Nested operation authority): resolved operation references carry exact declared calls,
63
63
  * including independently validated credential bindings for sandboxed compute dependencies.
64
64
  */
65
- export const ARTIFACT_SCHEMA_VERSION = '0.20.0';
65
+ /** `0.21.0`: protected tools may opt into public descriptor discovery without execution permission. */
66
+ export const ARTIFACT_SCHEMA_VERSION = '0.21.0';
66
67
  /** mimeType for an MCP Apps UI resource (SEP-1865). Widgets are served under this profile. */
67
68
  export const MCP_APP_MIME_TYPE = 'text/html;profile=mcp-app';
68
69
  //# sourceMappingURL=version.js.map
@@ -1,5 +1,6 @@
1
1
  import { z } from 'zod';
2
2
  type ToolAuthorizationManifest = {
3
+ readonly discovery?: 'authorized' | 'public' | undefined;
3
4
  readonly requiredScopes?: readonly string[] | undefined;
4
5
  readonly allowedRoles?: readonly string[] | undefined;
5
6
  };
@@ -504,6 +505,7 @@ export declare const manifestV1Schema: z.ZodObject<{
504
505
  title: z.ZodOptional<z.ZodString>;
505
506
  description: z.ZodString;
506
507
  authorization: z.ZodOptional<z.ZodType<ToolAuthorizationManifest, unknown, z.core.$ZodTypeInternals<ToolAuthorizationManifest, unknown>>>;
508
+ securitySchemes: z.ZodOptional<z.ZodNever>;
507
509
  inputSchema: z.ZodRecord<z.ZodString, z.ZodUnknown>;
508
510
  outputSchema: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
509
511
  annotations: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
@@ -1164,6 +1166,7 @@ export declare const manifestV2Schema: z.ZodObject<{
1164
1166
  title: z.ZodOptional<z.ZodString>;
1165
1167
  description: z.ZodString;
1166
1168
  authorization: z.ZodOptional<z.ZodType<ToolAuthorizationManifest, unknown, z.core.$ZodTypeInternals<ToolAuthorizationManifest, unknown>>>;
1169
+ securitySchemes: z.ZodOptional<z.ZodNever>;
1167
1170
  inputSchema: z.ZodRecord<z.ZodString, z.ZodUnknown>;
1168
1171
  outputSchema: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
1169
1172
  annotations: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
@@ -1729,6 +1732,7 @@ export declare const manifestSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
1729
1732
  title: z.ZodOptional<z.ZodString>;
1730
1733
  description: z.ZodString;
1731
1734
  authorization: z.ZodOptional<z.ZodType<ToolAuthorizationManifest, unknown, z.core.$ZodTypeInternals<ToolAuthorizationManifest, unknown>>>;
1735
+ securitySchemes: z.ZodOptional<z.ZodNever>;
1732
1736
  inputSchema: z.ZodRecord<z.ZodString, z.ZodUnknown>;
1733
1737
  outputSchema: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
1734
1738
  annotations: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
@@ -2388,6 +2392,7 @@ export declare const manifestSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
2388
2392
  title: z.ZodOptional<z.ZodString>;
2389
2393
  description: z.ZodString;
2390
2394
  authorization: z.ZodOptional<z.ZodType<ToolAuthorizationManifest, unknown, z.core.$ZodTypeInternals<ToolAuthorizationManifest, unknown>>>;
2395
+ securitySchemes: z.ZodOptional<z.ZodNever>;
2391
2396
  inputSchema: z.ZodRecord<z.ZodString, z.ZodUnknown>;
2392
2397
  outputSchema: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
2393
2398
  annotations: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
@@ -70,25 +70,35 @@ const roleSchema = z
70
70
  const authorizationListSchema = (item) => z.array(item).min(1).max(MAX_TOOL_AUTHORIZATION_VALUES);
71
71
  const requiredScopesSchema = authorizationListSchema(scopeTokenSchema);
72
72
  const allowedRolesSchema = authorizationListSchema(roleSchema);
73
- const toolAuthorizationSchema = z.union([
73
+ const toolAuthorizationSchema = z
74
+ .union([
74
75
  z
75
76
  .object({
77
+ discovery: z.enum(['authorized', 'public']).optional(),
76
78
  requiredScopes: requiredScopesSchema,
77
79
  allowedRoles: allowedRolesSchema.optional(),
78
80
  })
79
81
  .strict(),
80
82
  z
81
83
  .object({
84
+ discovery: z.enum(['authorized', 'public']).optional(),
82
85
  requiredScopes: requiredScopesSchema.optional(),
83
86
  allowedRoles: allowedRolesSchema,
84
87
  })
85
88
  .strict(),
86
- ]);
89
+ ])
90
+ .overwrite((authorization) => {
91
+ if (authorization.discovery !== 'authorized')
92
+ return authorization;
93
+ const { discovery: _discovery, ...normalized } = authorization;
94
+ return normalized;
95
+ });
87
96
  const toolSchema = z.object({
88
97
  name: nameSchema,
89
98
  title: z.string().trim().min(1).optional(),
90
99
  description: z.string().min(1),
91
100
  authorization: toolAuthorizationSchema.optional(),
101
+ securitySchemes: z.never().optional(),
92
102
  inputSchema: jsonSchemaSchema,
93
103
  outputSchema: jsonSchemaSchema.optional(),
94
104
  annotations: z.record(z.string(), z.unknown()).optional(),
@@ -15,6 +15,9 @@ export function registerTools(server, artifact, deps, context) {
15
15
  server.setRequestHandler(ListToolsRequestSchema, async () => mapToolsList(artifact, context.caller, {
16
16
  knowledgeTools: await knowledgeToolsEnabled(artifact, deps),
17
17
  intentCapture: context.intentCapture?.enabled === true,
18
+ ...(context.toolAuthentication === undefined
19
+ ? {}
20
+ : { authentication: context.toolAuthentication }),
18
21
  }));
19
22
  server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
20
23
  const toolName = request.params.name;
@@ -8,6 +8,7 @@ export { buildMcpServer, type ProtocolRequestContext, type ProtocolToolDispatchC
8
8
  export { handleStatelessHttp } from './stateless.js';
9
9
  export { evaluateToolAuthorization, filterAuthorizedTools, TOOL_AUTHORIZATION_DENIED, type ToolAuthorizationCaller, type ToolAuthorizationDecision, type ToolAuthorizationRuleClass, toolAuthorizationRuleClass, toolAuthorizationRuleFingerprint, } from './tool-authorization.js';
10
10
  export { toolRequiresConfirmation } from './tool-confirmation.js';
11
+ export { filterDiscoverableTools, type ToolAuthenticationPolicy } from './tool-discovery.js';
11
12
  export { createDualEraMcpHandler, createPlatformDualEraMcpHandler, } from './v2/handler.js';
12
13
  export { LEGACY_MCP_PROTOCOL_VERSION, type McpProtocolEra, type McpProtocolMode, MODERN_MCP_PROTOCOL_VERSION, SERVED_MCP_PROTOCOL_VERSIONS, type ServedMcpProtocolVersion, } from './v2/versions.js';
13
14
  export { injectWidgetBridge } from './widget/inject.js';
@@ -7,6 +7,7 @@ export { buildMcpServer, } from './sdk-server.js';
7
7
  export { handleStatelessHttp } from './stateless.js';
8
8
  export { evaluateToolAuthorization, filterAuthorizedTools, TOOL_AUTHORIZATION_DENIED, toolAuthorizationRuleClass, toolAuthorizationRuleFingerprint, } from './tool-authorization.js';
9
9
  export { toolRequiresConfirmation } from './tool-confirmation.js';
10
+ export { filterDiscoverableTools } from './tool-discovery.js';
10
11
  export { createDualEraMcpHandler, createPlatformDualEraMcpHandler, } from './v2/handler.js';
11
12
  export { LEGACY_MCP_PROTOCOL_VERSION, MODERN_MCP_PROTOCOL_VERSION, SERVED_MCP_PROTOCOL_VERSIONS, } from './v2/versions.js';
12
13
  export { injectWidgetBridge } from './widget/inject.js';
@@ -1,6 +1,7 @@
1
1
  import type { ArtifactTool, RuntimeArtifact } from '@noodle-borg/compiler';
2
2
  import type { JsonRpcErrorObject } from './jsonrpc.js';
3
- import { type ToolAuthorizationCaller } from './tool-authorization.js';
3
+ import type { ToolAuthorizationCaller } from './tool-authorization.js';
4
+ import { type ToolAuthenticationPolicy, type ToolSecurityScheme } from './tool-discovery.js';
4
5
  import { type WidgetDomainProjection } from './widget/domain-projection.js';
5
6
  /**
6
7
  * Artifact → MCP wire mapping. The official `@modelcontextprotocol/sdk` owns JSON-RPC framing and
@@ -16,6 +17,7 @@ export interface ToolDescriptor {
16
17
  readonly inputSchema: Record<string, unknown>;
17
18
  readonly outputSchema?: Record<string, unknown>;
18
19
  readonly annotations?: Record<string, unknown>;
20
+ readonly securitySchemes?: readonly ToolSecurityScheme[];
19
21
  /** MCP `_meta` extension bag (e.g. MCP Apps `{ ui: { resourceUri } }` linking the tool to its widget). */
20
22
  readonly _meta?: Record<string, unknown>;
21
23
  }
@@ -44,6 +46,7 @@ export { mapExecutionError } from './execution-error-mapping.js';
44
46
  /** Map a resolved artifact tool to an MCP tool descriptor (carrying its `_meta` extension bag, if any). */
45
47
  export declare function mapTool(tool: ArtifactTool, options?: {
46
48
  readonly intentCapture?: boolean;
49
+ readonly authentication?: ToolAuthenticationPolicy;
47
50
  }): ToolDescriptor;
48
51
  /**
49
52
  * Map the artifact's tools to a `tools/list` result. App-only widget helpers remain discoverable here with
@@ -53,6 +56,7 @@ export declare function mapTool(tool: ArtifactTool, options?: {
53
56
  export declare function mapToolsList(artifact: RuntimeArtifact, caller?: ToolAuthorizationCaller, options?: {
54
57
  readonly knowledgeTools?: boolean;
55
58
  readonly intentCapture?: boolean;
59
+ readonly authentication?: ToolAuthenticationPolicy;
56
60
  }): ToolsListResult;
57
61
  /**
58
62
  * Map a successful tool output to a `tools/call` result. Structured (object) output is returned as
@@ -1,21 +1,27 @@
1
1
  import { splitResultMeta } from '@noodle-borg/runtime';
2
2
  import { artifactDeclaresContext, assertNoContextToolCollision, CONTEXT_TOOL_DESCRIPTOR, } from './context-tool.js';
3
3
  import { projectIntentCaptureInput } from './intent-capture.js';
4
- import { filterAuthorizedTools } from './tool-authorization.js';
4
+ import { filterDiscoverableTools, toolSecuritySchemes, } from './tool-discovery.js';
5
5
  import { projectWidgetResourceMeta, } from './widget/domain-projection.js';
6
6
  import { injectWidgetBridge } from './widget/inject.js';
7
7
  export { mapExecutionError } from './execution-error-mapping.js';
8
8
  /** Map a resolved artifact tool to an MCP tool descriptor (carrying its `_meta` extension bag, if any). */
9
9
  export function mapTool(tool, options) {
10
- return {
10
+ return withToolAuthentication({
11
11
  name: tool.name,
12
12
  ...(tool.title !== undefined ? { title: tool.title } : {}),
13
13
  description: tool.description,
14
14
  inputSchema: projectIntentCaptureInput(withPortableInteractionInput(tool), options?.intentCapture === true),
15
15
  ...(tool.outputSchema !== undefined ? { outputSchema: tool.outputSchema } : {}),
16
16
  ...(tool.annotations !== undefined ? { annotations: tool.annotations } : {}),
17
- ...(tool._meta !== undefined ? { _meta: tool._meta } : {}),
18
- };
17
+ ...(tool._meta === undefined ? {} : { _meta: tool._meta }),
18
+ }, tool, options?.authentication);
19
+ }
20
+ function withToolAuthentication(descriptor, tool, policy) {
21
+ if (policy !== 'mixed-customer')
22
+ return descriptor;
23
+ const securitySchemes = toolSecuritySchemes(tool);
24
+ return { ...descriptor, securitySchemes, _meta: { ...descriptor._meta, securitySchemes } };
19
25
  }
20
26
  const PORTABLE_INTERACTION_INPUT_SCHEMA = {
21
27
  type: 'object',
@@ -65,10 +71,12 @@ export function mapToolsList(artifact, caller, options) {
65
71
  assertNoContextToolCollision(artifact);
66
72
  return {
67
73
  tools: [
68
- ...filterAuthorizedTools(artifact.tools, caller).map((tool) => mapTool(tool, options)),
69
- ...(artifactDeclaresContext(artifact) ? [CONTEXT_TOOL_DESCRIPTOR] : []),
74
+ ...filterDiscoverableTools(artifact.tools, caller).map((tool) => mapTool(tool, options)),
75
+ ...(artifactDeclaresContext(artifact)
76
+ ? [withToolAuthentication(CONTEXT_TOOL_DESCRIPTOR, {}, options?.authentication)]
77
+ : []),
70
78
  ...(options?.knowledgeTools === true
71
- ? knowledgeToolDescriptors(artifact, options?.intentCapture === true)
79
+ ? knowledgeToolDescriptors(artifact, options?.intentCapture === true).map((descriptor) => withToolAuthentication(descriptor, {}, options?.authentication))
72
80
  : []),
73
81
  ],
74
82
  };
@@ -18,6 +18,8 @@ export interface ServedArtifact {
18
18
  readonly appPackageSnapshot?: AppPackageSnapshotV1;
19
19
  }
20
20
  export interface ProtocolRequestContext {
21
+ /** Resolved endpoint policy used only to derive MCP discovery metadata. */
22
+ readonly toolAuthentication?: import('./tool-discovery.js').ToolAuthenticationPolicy;
21
23
  readonly caller?: CallerIdentity;
22
24
  /** Internal hosting attribution, never MCP payload metadata or caller-visible context. */
23
25
  readonly publicAdmission?: NonNullable<ExecuteDeps['publicAdmission']>;
@@ -0,0 +1,14 @@
1
+ import type { ArtifactTool } from '@noodle-borg/compiler';
2
+ import { type ToolAuthorizationCaller } from './tool-authorization.js';
3
+ /** The hosting plane opts into schemes only for the resolved mixed customer policy. */
4
+ export type ToolAuthenticationPolicy = 'mixed-customer';
5
+ export type ToolSecurityScheme = {
6
+ readonly type: 'noauth';
7
+ } | {
8
+ readonly type: 'oauth2';
9
+ readonly scopes: readonly string[];
10
+ };
11
+ /** Descriptor publication only; execution and non-MCP capability filters remain authorization-only. */
12
+ export declare function filterDiscoverableTools(tools: readonly ArtifactTool[], caller: ToolAuthorizationCaller | undefined): readonly ArtifactTool[];
13
+ export declare function toolSecuritySchemes(tool: Pick<ArtifactTool, 'authorization'>): readonly ToolSecurityScheme[];
14
+ //# sourceMappingURL=tool-discovery.d.ts.map
@@ -0,0 +1,12 @@
1
+ import { evaluateToolAuthorization } from './tool-authorization.js';
2
+ /** Descriptor publication only; execution and non-MCP capability filters remain authorization-only. */
3
+ export function filterDiscoverableTools(tools, caller) {
4
+ return tools.filter((tool) => tool.authorization?.discovery === 'public' ||
5
+ evaluateToolAuthorization(tool.authorization, caller).allow);
6
+ }
7
+ export function toolSecuritySchemes(tool) {
8
+ return tool.authorization === undefined
9
+ ? [{ type: 'noauth' }]
10
+ : [{ type: 'oauth2', scopes: tool.authorization.requiredScopes ?? [] }];
11
+ }
12
+ //# sourceMappingURL=tool-discovery.js.map
@@ -93,6 +93,9 @@ function registerV2Tools(server, artifact, deps, context, era) {
93
93
  const mapped = mapToolsList(artifact, context.caller, {
94
94
  knowledgeTools: await knowledgeToolsEnabled(artifact, deps),
95
95
  intentCapture: context.intentCapture?.enabled === true,
96
+ ...(context.toolAuthentication === undefined
97
+ ? {}
98
+ : { authentication: context.toolAuthentication }),
96
99
  });
97
100
  return (era === 'modern' ? shapeModernToolsList(mapped) : mapped);
98
101
  });
@@ -1,12 +1,14 @@
1
+ import { MIXED_CUSTOMER_AUTH_FEATURE_VERSION, serviceInfoResponseSchema, } from '@noodle-borg/wire-contracts';
1
2
  export function serviceInfoPayload(buildInfo, developerMcp) {
2
- return {
3
+ return serviceInfoResponseSchema.parse({
3
4
  ok: true,
4
5
  status: 'ok',
5
6
  ...buildInfo,
7
+ features: { mixedCustomerAuth: MIXED_CUSTOMER_AUTH_FEATURE_VERSION },
6
8
  ...(developerMcp
7
9
  ? { developerPlugin: { mcpCapabilityVersion: DEVELOPER_MCP_CAPABILITY_VERSION } }
8
10
  : {}),
9
- };
11
+ });
10
12
  }
11
13
  export function cliCompatibilityError(buildInfo, cliVersion) {
12
14
  const supported = buildInfo.compatiblePackageVersions?.['@noodleseed/one'];
@@ -0,0 +1,24 @@
1
+ import { parse as parseYaml } from 'yaml';
2
+ export function isIdentityAccessMode(accessMode, manifest) {
3
+ if (accessMode === 'mixed' && manifest !== undefined) {
4
+ try {
5
+ const parsed = parseYaml(manifest);
6
+ if (typeof parsed === 'object' && parsed !== null && 'server' in parsed) {
7
+ const server = parsed.server;
8
+ if (typeof server === 'object' &&
9
+ server !== null &&
10
+ 'auth' in server &&
11
+ server.auth != null)
12
+ return true;
13
+ }
14
+ }
15
+ catch {
16
+ /* Compilation owns malformed manifest diagnostics. */
17
+ }
18
+ }
19
+ return (accessMode === 'owner-only' ||
20
+ accessMode === 'org-members' ||
21
+ accessMode === 'customers' ||
22
+ accessMode === 'authenticated');
23
+ }
24
+ //# sourceMappingURL=deployment-access-mode.js.map
@@ -0,0 +1,27 @@
1
+ import { isDeepStrictEqual } from 'node:util';
2
+ /** Compare inside the lifecycle lock/transaction, before any projection or active-pointer writes. */
3
+ export function matchesDeploymentActivation(target, precondition) {
4
+ if (target.schemaVersion !== (precondition?.expectedSchemaVersion ?? 1))
5
+ return false;
6
+ if (precondition === undefined)
7
+ return true;
8
+ if (target.accessMode !== precondition.expectedAccessMode)
9
+ return false;
10
+ const expected = precondition.expectedRevision;
11
+ if (expected === undefined)
12
+ return true;
13
+ // These are all persisted compile inputs plus the authority and lifecycle state used by the target.
14
+ // Active status may legitimately change on repeated activation; it is not an executable input.
15
+ return [
16
+ 'manifest',
17
+ 'connectors',
18
+ 'hostedAssets',
19
+ 'serverAuth',
20
+ 'serverVersion',
21
+ 'ownerSubject',
22
+ 'createdBySubject',
23
+ 'orgMembershipSources',
24
+ 'archivedAt',
25
+ ].every((key) => isDeepStrictEqual(target[key], expected[key]));
26
+ }
27
+ //# sourceMappingURL=deployment-activation-precondition.js.map
@@ -1,7 +1,7 @@
1
1
  import { usesCustomerAuthentication } from '@noodle-borg/module';
2
2
  import { deploymentRecordVersionError } from './deployment-record-version.js';
3
3
  /** Read the effective authentication authority only when this runtime understands the record. */
4
- export function deploymentAuthenticationFor(record) {
4
+ export function deploymentAuthenticationFor(record, compiledAuth) {
5
5
  if (deploymentRecordVersionError(record) !== undefined || record.accessMode === undefined) {
6
6
  return undefined;
7
7
  }
@@ -10,7 +10,7 @@ export function deploymentAuthenticationFor(record) {
10
10
  if (usesCustomerAuthentication({
11
11
  schemaVersion: record.schemaVersion,
12
12
  accessMode: record.accessMode,
13
- hasServerAuth: record.serverAuth !== undefined,
13
+ hasServerAuth: record.serverAuth !== undefined || compiledAuth !== undefined,
14
14
  })) {
15
15
  return 'customer';
16
16
  }
@@ -1,8 +1,8 @@
1
1
  import { cspFaultsInManifest } from '@noodle-borg/compiler';
2
2
  import { noopLogger } from '@noodle-borg/transport-http';
3
3
  import { BUSINESS_SETUP_MESSAGE, BusinessOnboarding } from './business-onboarding.js';
4
+ import { isIdentityAccessMode } from './deployment-access-mode.js';
4
5
  import { manifestUsesUserRoot } from './registry-access.js';
5
- import { isIdentityAccessMode } from './routes/access-mode.js';
6
6
  import { provisionPublicEmbed } from './routes/deploy-public-embed.js';
7
7
  import { deploymentActivationRejection } from './routes/deployment-activation-response.js';
8
8
  /** Shared post-authorization deploy flow. Both API deploys and installations retain registry activation hooks. */
@@ -42,7 +42,7 @@ export async function executeAuthorizedDeployment(input, dependencies) {
42
42
  }
43
43
  // Identity access modes bind the data plane to verified identity, so the deployer must be authenticated.
44
44
  // (Localhost dev runs the gate open; such deploys are rejected for lack of an owner/audit actor.)
45
- if (isIdentityAccessMode(accessMode) && !identity) {
45
+ if (isIdentityAccessMode(accessMode, manifest) && !identity) {
46
46
  return reject(401, {
47
47
  error: `${accessMode} deployments require an authenticated deployer`,
48
48
  });
@@ -11,9 +11,9 @@ export class UnsupportedDeploymentRecordVersionError extends Error {
11
11
  this.name = 'UnsupportedDeploymentRecordVersionError';
12
12
  }
13
13
  }
14
- /** R0 understands newer ownership records but must never serve their authentication semantics. */
14
+ /** R1 serves the two understood policy versions; future policy remains unavailable. */
15
15
  export function deploymentRecordVersionError(record) {
16
- return record.schemaVersion === 1 ? undefined : VERSION_ERROR;
16
+ return record.schemaVersion === 1 || record.schemaVersion === 2 ? undefined : VERSION_ERROR;
17
17
  }
18
18
  /** A stale writer cannot replace or deactivate a newer policy, even after registry preflight. */
19
19
  export function assertDeploymentAppendVersion(records, candidate) {
@@ -28,4 +28,28 @@ export function assertDeploymentAppendVersion(records, candidate) {
28
28
  export function unsupportedDeploymentRecordFailure() {
29
29
  return { ok: false, errors: [VERSION_ERROR] };
30
30
  }
31
+ /** Reject a deployment compiled against an operator policy that has since changed. */
32
+ export class DeploymentPolicyChangedError extends Error {
33
+ code = 'deployment_policy_changed';
34
+ constructor() {
35
+ super('The active deployment policy changed. Retry the deployment.');
36
+ }
37
+ }
38
+ export function assertDeploymentAppendPolicy(records, candidate, precondition) {
39
+ if (precondition === undefined)
40
+ return;
41
+ const current = records.find((record) => record.active && record.archivedAt === undefined && sameDeploymentScope(record, candidate));
42
+ const expected = precondition.active;
43
+ if (expected === null
44
+ ? current !== undefined
45
+ : current === undefined ||
46
+ current.deploymentId !== expected.deploymentId ||
47
+ current.schemaVersion !== expected.schemaVersion ||
48
+ current.accessMode !== expected.accessMode ||
49
+ current.ownerSubject !== expected.ownerSubject ||
50
+ current.manifest !== expected.manifest ||
51
+ JSON.stringify(current.serverAuth) !== JSON.stringify(expected.serverAuth)) {
52
+ throw new DeploymentPolicyChangedError();
53
+ }
54
+ }
31
55
  //# sourceMappingURL=deployment-record-version.js.map
@@ -1,11 +1,12 @@
1
- import { assertUniqueActiveCustomerAuthAudienceBindings, CustomerAuthAudienceConflictError, requiresCustomerAuthProjection, } from './customer-auth-audience-binding.js';
1
+ import { assertUniqueActiveCustomerAuthAudienceBindings, CustomerAuthAudienceConflictError, hasExactCustomerAuthProjection, } from './customer-auth-audience-binding.js';
2
2
  import { deploymentRecordVersionError } from './deployment-record-version.js';
3
3
  import { deploymentOwnerSubject } from './registry-helpers.js';
4
4
  import { activeRecord, activeRecordVersion } from './registry-state.js';
5
5
  import { tenantDeploymentKey, tryServedTargetFor } from './registry-targets.js';
6
+ import { resolveActiveAccessUpdate } from './store/records.js';
6
7
  /** Registry-facing access transition with customer audience ownership preflight and atomic translation. */
7
8
  export async function updateRegistryAccess(state, tenant, options, loadServed, customerVerifierFactory, hasCustomerAuthConflict) {
8
- if (options.accessMode === 'customers') {
9
+ if (options.accessMode === 'customers' || options.accessMode === 'mixed') {
9
10
  const record = options.serverVersion === undefined
10
11
  ? await activeRecord(state, tenant)
11
12
  : await activeRecordVersion(state, tenant, options.serverVersion);
@@ -13,7 +14,11 @@ export async function updateRegistryAccess(state, tenant, options, loadServed, c
13
14
  const auth = served?.served.artifact.server.auth;
14
15
  if (record !== undefined &&
15
16
  auth !== undefined &&
16
- (await hasCustomerAuthConflict({ ...record, accessMode: 'customers' }, auth))) {
17
+ (await hasCustomerAuthConflict({
18
+ ...record,
19
+ accessMode: options.accessMode,
20
+ ...(options.accessMode === 'mixed' ? { schemaVersion: 2 } : {}),
21
+ }, auth))) {
17
22
  return customerAuthAudienceConflict();
18
23
  }
19
24
  }
@@ -57,11 +62,19 @@ export async function updateActiveAccess(state, tenant, options, loadServed, cus
57
62
  const nextOwnerSubject = options.ownerSubject ?? previousOwnerSubject;
58
63
  const accessChanged = previousAccessMode !== nextAccessMode;
59
64
  const ownerChanged = previousOwnerSubject !== nextOwnerSubject;
60
- const served = accessChanged || ownerChanged || nextAccessMode === 'customers'
65
+ const served = accessChanged || ownerChanged || nextAccessMode === 'customers' || nextAccessMode === 'mixed'
61
66
  ? await loadServed(record.deploymentId)
62
67
  : undefined;
63
68
  const serverAuth = served?.served.artifact.server.auth;
64
- if (nextAccessMode === 'customers' && serverAuth === undefined) {
69
+ const schemaVersion = record.schemaVersion === 2 || (nextAccessMode === 'mixed' && serverAuth !== undefined) ? 2 : 1;
70
+ const policyChanged = schemaVersion !== record.schemaVersion;
71
+ if ((nextAccessMode === 'customers' && serverAuth === undefined) ||
72
+ !hasExactCustomerAuthProjection({
73
+ ...record,
74
+ schemaVersion,
75
+ accessMode: nextAccessMode,
76
+ ...(record.serverAuth === undefined && serverAuth !== undefined ? { serverAuth } : {}),
77
+ }, serverAuth)) {
65
78
  return {
66
79
  ok: false,
67
80
  status: 409,
@@ -69,19 +82,23 @@ export async function updateActiveAccess(state, tenant, options, loadServed, cus
69
82
  message: 'Customer access requires server authentication.',
70
83
  };
71
84
  }
72
- if (!accessChanged && !ownerChanged) {
85
+ if (!accessChanged && !ownerChanged && !policyChanged) {
73
86
  const committed = state.store === undefined
74
87
  ? commitInMemoryAccessUpdate(state, tenant, record, {
75
88
  accessMode: nextAccessMode,
76
89
  expectedAccessMode: record.accessMode,
90
+ ...(schemaVersion === 2 ? { schemaVersion: 2 } : {}),
77
91
  expectedSchemaVersion: record.schemaVersion,
92
+ expectedManifest: record.manifest,
78
93
  expectedOwnerSubject: previousOwnerSubject,
79
94
  ...(serverAuth !== undefined ? { serverAuth } : {}),
80
95
  })
81
96
  : await state.store.updateActiveAccess(tenant, record.deploymentId, {
82
97
  accessMode: nextAccessMode,
83
98
  expectedAccessMode: record.accessMode,
99
+ ...(schemaVersion === 2 ? { schemaVersion: 2 } : {}),
84
100
  expectedSchemaVersion: record.schemaVersion,
101
+ expectedManifest: record.manifest,
85
102
  expectedOwnerSubject: previousOwnerSubject,
86
103
  ...(serverAuth !== undefined ? { serverAuth } : {}),
87
104
  });
@@ -92,6 +109,7 @@ export async function updateActiveAccess(state, tenant, options, loadServed, cus
92
109
  ok: true,
93
110
  changed: false,
94
111
  accessChanged: false,
112
+ policyChanged: false,
95
113
  ownerChanged: false,
96
114
  previousAccessMode,
97
115
  ...(previousOwnerSubject !== undefined ? { previousOwnerSubject } : {}),
@@ -119,8 +137,9 @@ export async function updateActiveAccess(state, tenant, options, loadServed, cus
119
137
  const updated = {
120
138
  ...record,
121
139
  accessMode: nextAccessMode,
140
+ schemaVersion,
122
141
  ...(ownerChanged && nextOwnerSubject !== undefined ? { ownerSubject: nextOwnerSubject } : {}),
123
- ...(nextAccessMode === 'customers' && serverAuth !== undefined ? { serverAuth } : {}),
142
+ ...(serverAuth !== undefined ? { serverAuth } : {}),
124
143
  };
125
144
  const target = tryServedTargetFor(updated, served.served, customerVerifierFactory);
126
145
  if (target === undefined)
@@ -132,7 +151,9 @@ export async function updateActiveAccess(state, tenant, options, loadServed, cus
132
151
  ? { ownerSubject: nextOwnerSubject }
133
152
  : {}),
134
153
  expectedAccessMode: record.accessMode,
154
+ ...(schemaVersion === 2 ? { schemaVersion: 2 } : {}),
135
155
  expectedSchemaVersion: record.schemaVersion,
156
+ expectedManifest: record.manifest,
136
157
  expectedOwnerSubject: previousOwnerSubject,
137
158
  ...(serverAuth !== undefined ? { serverAuth } : {}),
138
159
  })
@@ -142,7 +163,9 @@ export async function updateActiveAccess(state, tenant, options, loadServed, cus
142
163
  ? { ownerSubject: nextOwnerSubject }
143
164
  : {}),
144
165
  expectedAccessMode: record.accessMode,
166
+ ...(schemaVersion === 2 ? { schemaVersion: 2 } : {}),
145
167
  expectedSchemaVersion: record.schemaVersion,
168
+ expectedManifest: record.manifest,
146
169
  expectedOwnerSubject: previousOwnerSubject,
147
170
  ...(serverAuth !== undefined ? { serverAuth } : {}),
148
171
  });
@@ -154,6 +177,7 @@ export async function updateActiveAccess(state, tenant, options, loadServed, cus
154
177
  ok: true,
155
178
  changed: true,
156
179
  accessChanged,
180
+ policyChanged,
157
181
  ownerChanged,
158
182
  previousAccessMode,
159
183
  ...(previousOwnerSubject !== undefined ? { previousOwnerSubject } : {}),
@@ -161,31 +185,9 @@ export async function updateActiveAccess(state, tenant, options, loadServed, cus
161
185
  };
162
186
  }
163
187
  function commitInMemoryAccessUpdate(state, tenant, observed, input) {
164
- const current = state.records.get(observed.deploymentId);
165
- if (current === undefined ||
166
- !current.active ||
167
- current.archivedAt !== undefined ||
168
- current.orgSlug !== tenant.org ||
169
- current.appSlug !== tenant.app ||
170
- current.environment !== tenant.env ||
171
- current.accessMode !== input.expectedAccessMode ||
172
- current.schemaVersion !== (input.expectedSchemaVersion ?? 1) ||
173
- deploymentOwnerSubject(current) !== input.expectedOwnerSubject) {
188
+ const committed = resolveActiveAccessUpdate(state.records.get(observed.deploymentId), tenant, input);
189
+ if (committed === undefined)
174
190
  return undefined;
175
- }
176
- if ((current.accessMode ?? 'owner-only') === input.accessMode &&
177
- (input.ownerSubject === undefined || deploymentOwnerSubject(current) === input.ownerSubject) &&
178
- !requiresCustomerAuthProjection({ ...current, accessMode: input.accessMode }, input.serverAuth)) {
179
- return current;
180
- }
181
- const committed = {
182
- ...current,
183
- accessMode: input.accessMode,
184
- ...(input.ownerSubject !== undefined ? { ownerSubject: input.ownerSubject } : {}),
185
- ...(requiresCustomerAuthProjection({ ...current, accessMode: input.accessMode }, input.serverAuth) && input.serverAuth !== undefined
186
- ? { serverAuth: input.serverAuth }
187
- : {}),
188
- };
189
191
  const proposed = new Map(state.records).set(committed.deploymentId, committed);
190
192
  assertUniqueActiveCustomerAuthAudienceBindings([...proposed.values()]);
191
193
  state.records.set(committed.deploymentId, committed);