@noodleseed/agent-kit 0.54.0 → 0.56.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.
- package/manifest.json +251 -251
- package/package.json +1 -1
- package/skills/claude-code/SKILL.md +1 -1
- package/skills/claude-code/authoring-mcp-servers/SKILL.md +1 -1
- package/skills/claude-code/building-mcp-apps/SKILL.md +1 -1
- package/skills/claude-code/connecting-apis-to-mcp/SKILL.md +1 -1
- package/skills/claude-code/debugging-mcp-delivery/SKILL.md +1 -1
- package/skills/claude-code/deploying-mcp-services/SKILL.md +1 -1
- package/skills/claude-code/designing-mcp-products/SKILL.md +1 -1
- package/skills/claude-code/embedding-mcp-assistants/SKILL.md +1 -1
- package/skills/claude-code/examples/customer-auth/README.md +184 -155
- package/skills/claude-code/examples/customer-auth/src/server.ts +66 -20
- package/skills/claude-code/examples/customer-auth/test/server.test.ts +51 -9
- package/skills/claude-code/executing-noodle-plans/SKILL.md +1 -1
- package/skills/claude-code/publishing-mcp-integrations/SKILL.md +1 -1
- package/skills/claude-code/references/authoring-workflow.md +92 -3
- package/skills/claude-code/references/compile-errors.md +9 -0
- package/skills/claude-code/references/examples.md +1 -1
- package/skills/claude-code/references/sdk-surface.md +1 -0
- package/skills/claude-code/reporting-noodle-feedback/SKILL.md +1 -1
- package/skills/claude-code/verifying-mcp-delivery/SKILL.md +1 -1
- package/skills/codex/SKILL.md +1 -1
- package/skills/codex/authoring-mcp-servers/SKILL.md +1 -1
- package/skills/codex/building-mcp-apps/SKILL.md +1 -1
- package/skills/codex/connecting-apis-to-mcp/SKILL.md +1 -1
- package/skills/codex/debugging-mcp-delivery/SKILL.md +1 -1
- package/skills/codex/deploying-mcp-services/SKILL.md +1 -1
- package/skills/codex/designing-mcp-products/SKILL.md +1 -1
- package/skills/codex/embedding-mcp-assistants/SKILL.md +1 -1
- package/skills/codex/examples/customer-auth/README.md +184 -155
- package/skills/codex/examples/customer-auth/src/server.ts +66 -20
- package/skills/codex/examples/customer-auth/test/server.test.ts +51 -9
- package/skills/codex/executing-noodle-plans/SKILL.md +1 -1
- package/skills/codex/publishing-mcp-integrations/SKILL.md +1 -1
- package/skills/codex/references/authoring-workflow.md +92 -3
- package/skills/codex/references/compile-errors.md +9 -0
- package/skills/codex/references/examples.md +1 -1
- package/skills/codex/references/sdk-surface.md +1 -0
- package/skills/codex/reporting-noodle-feedback/SKILL.md +1 -1
- package/skills/codex/verifying-mcp-delivery/SKILL.md +1 -1
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
annotations,
|
|
3
3
|
connector,
|
|
4
4
|
customerAuth,
|
|
5
|
+
customerEndpoint,
|
|
5
6
|
embeddedAssistant,
|
|
6
7
|
openAICompatible,
|
|
7
8
|
secret,
|
|
@@ -11,18 +12,21 @@ import {
|
|
|
11
12
|
z,
|
|
12
13
|
} from '@noodleseed/one';
|
|
13
14
|
|
|
14
|
-
const
|
|
15
|
+
const customerApi = customerEndpoint('customer_api', {
|
|
16
|
+
allowedHttpsHostSuffixes: ['api.noodleseed.dev'],
|
|
17
|
+
});
|
|
15
18
|
|
|
16
19
|
const noodleseedApi = connector('noodleseed_app_api')
|
|
17
20
|
.version('1.0.0')
|
|
18
21
|
.http({
|
|
19
|
-
baseUrl:
|
|
20
|
-
allowedOrigins: [noodleseedApiOrigin],
|
|
22
|
+
baseUrl: customerApi,
|
|
21
23
|
auth: {
|
|
22
|
-
kind: '
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
kind: 'delegatedTokenExchange',
|
|
25
|
+
tokenUrl: 'https://id.noodleseed.dev/oauth/token',
|
|
26
|
+
clientId: variable('CUSTOMER_API_CLIENT_ID'),
|
|
27
|
+
clientSecret: secret('CUSTOMER_API_CLIENT_SECRET'),
|
|
28
|
+
scopes: ['organizations:read', 'org_apps:read', 'org_apps:write'],
|
|
29
|
+
audience: 'noodleseed-customer-api',
|
|
26
30
|
},
|
|
27
31
|
operations: {
|
|
28
32
|
list_org_apps: {
|
|
@@ -49,6 +53,19 @@ const noodleseedApi = connector('noodleseed_app_api')
|
|
|
49
53
|
organizations: '${response.organizations}',
|
|
50
54
|
},
|
|
51
55
|
},
|
|
56
|
+
archive_org_app: {
|
|
57
|
+
type: 'action',
|
|
58
|
+
method: 'POST',
|
|
59
|
+
path: '/api/organizations/${args.org_id}/apps/${args.app_id}/archive',
|
|
60
|
+
input: z.object({
|
|
61
|
+
org_id: z.string(),
|
|
62
|
+
app_id: z.string(),
|
|
63
|
+
}),
|
|
64
|
+
output: z.object({ archived: z.boolean() }),
|
|
65
|
+
response: {
|
|
66
|
+
archived: '${response.archived}',
|
|
67
|
+
},
|
|
68
|
+
},
|
|
52
69
|
},
|
|
53
70
|
});
|
|
54
71
|
|
|
@@ -69,22 +86,26 @@ export default server(
|
|
|
69
86
|
},
|
|
70
87
|
},
|
|
71
88
|
use: { app_api: noodleseedApi },
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
89
|
+
interactions: { confirmationFallback: 'host' },
|
|
90
|
+
auth: customerAuth.oidc({
|
|
91
|
+
issuer: 'https://id.noodleseed.dev',
|
|
92
|
+
audience: 'https://org.cloud.noodleseed.dev/app/mcp',
|
|
93
|
+
claims: {
|
|
77
94
|
id: 'sub',
|
|
78
95
|
email: 'email',
|
|
79
96
|
name: 'name',
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
97
|
+
orgs: 'permissions.orgs',
|
|
98
|
+
roles: 'permissions.roles',
|
|
99
|
+
scopes: 'permissions.scopes',
|
|
100
|
+
},
|
|
101
|
+
routing: {
|
|
102
|
+
endpoints: {
|
|
103
|
+
customer_api: { claim: 'tenant.api_base_url' },
|
|
104
|
+
},
|
|
84
105
|
},
|
|
85
106
|
}),
|
|
86
107
|
instructions:
|
|
87
|
-
'
|
|
108
|
+
'Direct/federated MCP OIDC demo. The customer IdP proves identity and privately selects the tenant API base URL, while the broker supplies delegated credentials and confirmed actions stay bound to the reviewed route.',
|
|
88
109
|
assistant: embeddedAssistant({
|
|
89
110
|
model: openAICompatible({
|
|
90
111
|
baseUrl: variable('ASSISTANT_MODEL_BASE_URL'),
|
|
@@ -111,13 +132,13 @@ export default server(
|
|
|
111
132
|
},
|
|
112
133
|
composer: { leadingIcon: 'brand-mark', shape: 'pill' },
|
|
113
134
|
},
|
|
114
|
-
suggestedPrompts: ['
|
|
135
|
+
suggestedPrompts: ['Explain how to connect this customer-authenticated MCP server'],
|
|
115
136
|
}),
|
|
116
137
|
},
|
|
117
138
|
[
|
|
118
139
|
tool('list_org_apps', {
|
|
119
140
|
title: 'List organization apps',
|
|
120
|
-
description: 'List NoodleSeed.com apps for an organization from
|
|
141
|
+
description: 'List NoodleSeed.com apps for an organization from its customer API.',
|
|
121
142
|
authorization: {
|
|
122
143
|
requiredScopes: ['org_apps:read'],
|
|
123
144
|
allowedRoles: ['org_admin', 'org_member'],
|
|
@@ -147,7 +168,7 @@ export default server(
|
|
|
147
168
|
description: 'List the NoodleSeed.com organizations the signed-in customer belongs to.',
|
|
148
169
|
contextProvider: true,
|
|
149
170
|
input: z.object({}),
|
|
150
|
-
// The
|
|
171
|
+
// The customer API returns every organization for the signed-in customer in one response, with no
|
|
151
172
|
// page parameter to pass through, so the bound is declared on the shape. A customer belongs to a
|
|
152
173
|
// handful of organizations; `noodle check` reports an unbounded list as
|
|
153
174
|
// `tool_design_output_bounds`.
|
|
@@ -162,5 +183,30 @@ export default server(
|
|
|
162
183
|
};
|
|
163
184
|
},
|
|
164
185
|
}),
|
|
186
|
+
tool('archive_org_app', {
|
|
187
|
+
title: 'Archive organization app',
|
|
188
|
+
description: 'Archive one NoodleSeed.com app through its customer API after confirmation.',
|
|
189
|
+
authorization: {
|
|
190
|
+
requiredScopes: ['org_apps:write'],
|
|
191
|
+
allowedRoles: ['org_admin'],
|
|
192
|
+
},
|
|
193
|
+
input: z.object({
|
|
194
|
+
org_id: z.string().meta({ title: 'Organization' }),
|
|
195
|
+
app_id: z.string().meta({ title: 'App' }),
|
|
196
|
+
}),
|
|
197
|
+
output: z.object({
|
|
198
|
+
archived: z.boolean(),
|
|
199
|
+
}),
|
|
200
|
+
annotations: annotations.openAction({ destructive: false, confirm: true }),
|
|
201
|
+
fulfil({ input, connectors }) {
|
|
202
|
+
const result = connectors.app_api.archiveOrgApp({
|
|
203
|
+
org_id: input.org_id,
|
|
204
|
+
app_id: input.app_id,
|
|
205
|
+
});
|
|
206
|
+
return {
|
|
207
|
+
archived: result.archived,
|
|
208
|
+
};
|
|
209
|
+
},
|
|
210
|
+
}),
|
|
165
211
|
],
|
|
166
212
|
);
|
|
@@ -23,17 +23,47 @@ describe('customer-auth example', () => {
|
|
|
23
23
|
name: 'Noodle Seed Assistant',
|
|
24
24
|
colorScheme: 'auto',
|
|
25
25
|
});
|
|
26
|
-
expect(manifest.server.auth).
|
|
27
|
-
kind: '
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
26
|
+
expect(manifest.server.auth).toEqual({
|
|
27
|
+
kind: 'oidc',
|
|
28
|
+
issuer: 'https://id.noodleseed.dev',
|
|
29
|
+
audience: 'https://org.cloud.noodleseed.dev/app/mcp',
|
|
30
|
+
claims: {
|
|
31
|
+
id: 'sub',
|
|
32
|
+
email: 'email',
|
|
33
|
+
name: 'name',
|
|
34
|
+
orgs: 'permissions.orgs',
|
|
35
|
+
roles: 'permissions.roles',
|
|
36
|
+
scopes: 'permissions.scopes',
|
|
37
|
+
},
|
|
38
|
+
routing: {
|
|
39
|
+
endpoints: {
|
|
40
|
+
customer_api: { claim: 'tenant.api_base_url' },
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
expect(manifest.server.interactions).toEqual({ confirmationFallback: 'host' });
|
|
45
|
+
const catalog = app.toConnectorCatalog();
|
|
46
|
+
expect(catalog?.connectors).toHaveLength(1);
|
|
47
|
+
expect(catalog?.connectors[0]?.http).toMatchObject({
|
|
48
|
+
baseUrl: {
|
|
49
|
+
kind: 'customerEndpoint',
|
|
50
|
+
name: 'customer_api',
|
|
51
|
+
policy: { allowedHttpsHostSuffixes: ['api.noodleseed.dev'] },
|
|
52
|
+
},
|
|
53
|
+
auth: {
|
|
54
|
+
kind: 'delegatedTokenExchange',
|
|
55
|
+
tokenUrl: 'https://id.noodleseed.dev/oauth/token',
|
|
56
|
+
clientId: '${env.CUSTOMER_API_CLIENT_ID}',
|
|
57
|
+
clientSecret: 'CUSTOMER_API_CLIENT_SECRET',
|
|
35
58
|
},
|
|
36
59
|
});
|
|
60
|
+
expect(catalog?.connectors[0]?.operations).toMatchObject({
|
|
61
|
+
list_org_apps: { type: 'read' },
|
|
62
|
+
list_organizations: { type: 'read' },
|
|
63
|
+
archive_org_app: { type: 'action', method: 'POST' },
|
|
64
|
+
});
|
|
65
|
+
expect(catalog?.connectors[0]?.http).not.toHaveProperty('allowedOrigins');
|
|
66
|
+
expect(JSON.stringify({ manifest, catalog })).not.toContain('tenant-a.api.noodleseed.dev');
|
|
37
67
|
expect(manifest.tools.find((tool) => tool.name === 'list_org_apps')?.authorization).toEqual({
|
|
38
68
|
requiredScopes: ['org_apps:read'],
|
|
39
69
|
allowedRoles: ['org_admin', 'org_member'],
|
|
@@ -41,5 +71,17 @@ describe('customer-auth example', () => {
|
|
|
41
71
|
expect(
|
|
42
72
|
manifest.tools.find((tool) => tool.name === 'list_my_organizations')?.authorization,
|
|
43
73
|
).toBeUndefined();
|
|
74
|
+
expect(manifest.tools.find((tool) => tool.name === 'archive_org_app')).toMatchObject({
|
|
75
|
+
authorization: {
|
|
76
|
+
requiredScopes: ['org_apps:write'],
|
|
77
|
+
allowedRoles: ['org_admin'],
|
|
78
|
+
},
|
|
79
|
+
annotations: {
|
|
80
|
+
readOnlyHint: false,
|
|
81
|
+
destructiveHint: false,
|
|
82
|
+
openWorldHint: true,
|
|
83
|
+
confirm: true,
|
|
84
|
+
},
|
|
85
|
+
});
|
|
44
86
|
});
|
|
45
87
|
});
|
|
@@ -3,7 +3,7 @@ name: executing-noodle-plans
|
|
|
3
3
|
description: "Use when the user asks to execute an approved, decision-complete implementation plan for a Noodle Seed project task by task with test-first changes, review, recovery, and final verification."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
<!-- noodle-skill version:0.
|
|
6
|
+
<!-- noodle-skill version:0.56.0 hash:6a9f132ddb79352e -->
|
|
7
7
|
|
|
8
8
|
# Execute a Noodle Seed implementation plan
|
|
9
9
|
|
|
@@ -3,7 +3,7 @@ name: publishing-mcp-integrations
|
|
|
3
3
|
description: "Use when preparing, reviewing, or submitting a Noodle Seed MCP integration to a host or app directory."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
<!-- noodle-skill version:0.
|
|
6
|
+
<!-- noodle-skill version:0.56.0 hash:efffbf82007f935d -->
|
|
7
7
|
|
|
8
8
|
# publishing-mcp-integrations
|
|
9
9
|
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
- Connectors
|
|
9
9
|
- HTTP connector example (full server)
|
|
10
10
|
- Customer OAuth for remote MCP clients
|
|
11
|
+
- Auth-derived customer API endpoints
|
|
11
12
|
- Per-tool authorization
|
|
12
13
|
- Delegated downstream auth (call your API as the signed-in user)
|
|
13
14
|
- Invocation context
|
|
@@ -109,6 +110,94 @@ For `customerAuth.oidc(...)` and `.federatedOidc(...)`, the application develope
|
|
|
109
110
|
|
|
110
111
|
That metadata must expose HTTPS `authorization_endpoint`, `token_endpoint`, `jwks_uri`, and RFC 7591 `registration_endpoint`; advertise authorization-code and refresh-token grants, Dynamic Client Registration, PKCE with `code_challenge_methods_supported: ["S256"]`, and public clients with `token_endpoint_auth_methods_supported: ["none"]`. Implement RFC 8707 `resource`, mint access-token `aud` for the exact MCP resource URL, and publish only public signing keys in JWKS. Run `noodle auth doctor src/server.ts`; its issuer-readiness probes perform bounded read-only GET checks and never register a client. A successful `noodle deploy --access customers` reports the same readiness without turning a diagnostic failure into a failed deployment.
|
|
111
112
|
|
|
113
|
+
## Auth-derived customer API endpoints
|
|
114
|
+
|
|
115
|
+
Use a customer endpoint when the verified IdP selects a different API base URL for each SaaS customer. The claim contains the complete base URL; tool input and `${user}` do not select it. Declare one named policy, use that reference as the connector `baseUrl`, and map its claim path in direct OIDC or on every federated issuer:
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
import { annotations, connector, customerAuth, customerEndpoint, secret, server, tool, variable, z } from '@noodleseed/one';
|
|
119
|
+
|
|
120
|
+
const customerApi = customerEndpoint('customer_api', {
|
|
121
|
+
allowedHttpsHostSuffixes: ['api.noodleseed.dev'],
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
const api = connector('customer_api_connector').version('1.0.0').http({
|
|
125
|
+
baseUrl: customerApi,
|
|
126
|
+
auth: {
|
|
127
|
+
kind: 'delegatedTokenExchange',
|
|
128
|
+
tokenUrl: 'https://id.noodleseed.dev/oauth/token',
|
|
129
|
+
clientId: variable('CUSTOMER_API_CLIENT_ID'),
|
|
130
|
+
clientSecret: secret('CUSTOMER_API_CLIENT_SECRET'),
|
|
131
|
+
},
|
|
132
|
+
operations: {
|
|
133
|
+
list_records: {
|
|
134
|
+
type: 'read',
|
|
135
|
+
method: 'GET',
|
|
136
|
+
path: '/records',
|
|
137
|
+
input: z.object({}),
|
|
138
|
+
output: z.object({ records: z.array(z.unknown()).max(100) }),
|
|
139
|
+
},
|
|
140
|
+
archive_record: {
|
|
141
|
+
type: 'action',
|
|
142
|
+
method: 'POST',
|
|
143
|
+
path: '/records/${args.record_id}/archive',
|
|
144
|
+
input: z.object({ record_id: z.string() }),
|
|
145
|
+
output: z.object({ archived: z.boolean() }),
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
export default server(
|
|
151
|
+
'customer_records',
|
|
152
|
+
{
|
|
153
|
+
title: 'Customer records',
|
|
154
|
+
version: '1.0.0',
|
|
155
|
+
use: { api },
|
|
156
|
+
interactions: { confirmationFallback: 'host' },
|
|
157
|
+
auth: customerAuth.oidc({
|
|
158
|
+
issuer: 'https://id.noodleseed.dev',
|
|
159
|
+
audience: 'https://org.cloud.noodleseed.dev/app/mcp',
|
|
160
|
+
routing: {
|
|
161
|
+
endpoints: {
|
|
162
|
+
customer_api: { claim: 'tenant.api_base_url' },
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
}),
|
|
166
|
+
},
|
|
167
|
+
[
|
|
168
|
+
tool('list_records', {
|
|
169
|
+
description: 'List records for the signed-in customer.',
|
|
170
|
+
input: z.object({}),
|
|
171
|
+
output: z.object({ records: z.array(z.unknown()).max(100) }),
|
|
172
|
+
fulfil: ({ connectors }) => {
|
|
173
|
+
const result = connectors.api.listRecords();
|
|
174
|
+
return { records: result.records };
|
|
175
|
+
},
|
|
176
|
+
}),
|
|
177
|
+
tool('archive_record', {
|
|
178
|
+
description: 'Archive one record for the signed-in customer.',
|
|
179
|
+
input: z.object({ record_id: z.string() }),
|
|
180
|
+
output: z.object({ archived: z.boolean() }),
|
|
181
|
+
annotations: annotations.openAction({ destructive: false, confirm: true }),
|
|
182
|
+
fulfil({ input, connectors }) {
|
|
183
|
+
const result = connectors.api.archiveRecord({ record_id: input.record_id });
|
|
184
|
+
return { archived: result.archived };
|
|
185
|
+
},
|
|
186
|
+
}),
|
|
187
|
+
],
|
|
188
|
+
);
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
`customerEndpoint` accepts exactly one non-empty policy arm: exact HTTPS origins, or HTTPS hostname suffixes. Exact policies may authorize an explicit non-default port; suffix policies allow port 443 and match only the exact host or dot-boundary subdomains. Do not add connector `allowedOrigins` to a customer-routed connector. Its fixed credential/token endpoints are validated independently and cannot come from caller claims.
|
|
192
|
+
|
|
193
|
+
On a bidirectional MCP transport whose client negotiated form elicitation, Noodle sends the standard confirmation form. The current stateless hosted MCP transport cannot initiate that exchange, so the example explicitly declares `server.interactions.confirmationFallback: 'host'`. That fallback trusts the MCP host to have collected native write approval before the tool call reaches Noodle; it is never inferred from client identity and does not replace authentication, authorization, policy, or accurate action/destructive annotations. Omit it when connected hosts are not trusted to provide that approval; confirmation then fails closed with `interaction_unavailable` when the standard exchange is unavailable.
|
|
194
|
+
|
|
195
|
+
Every federated issuer must repeat every endpoint key used by the app, although each issuer may choose a different claim path. Endpoint names use lowercase letters, numbers, and underscores. Resolved claims must be exact absolute HTTPS URLs of at most 2,048 UTF-8 bytes with no userinfo, query, fragment, IP literal, special-use host, or unsafe whitespace/control characters. The runtime preserves a canonical optional base path.
|
|
196
|
+
|
|
197
|
+
Resolved customer URLs are private routing authority: they never enter the manifest/artifact, `${user}`, logs, model output, widgets, public confirmation review, cache keys, or delegated-token-exchange assertions. Routed reads may be used by tools, including declared nested calls. A routed action—including one reached through a connector wrapper—requires exact `annotations.confirm: true`; omitted or `false` fails with `customer_endpoint_action_unsupported`. Routed resources, prompts, and ambient context fail with `customer_endpoint_surface_unsupported`.
|
|
198
|
+
|
|
199
|
+
At runtime, an initially missing, malformed, or disallowed claim returns the same safe `connector_route_unavailable` tool error before credential lookup or connector egress. Preparation stores only sorted route `{ key, fingerprint }` bindings in the private server-held continuation. Acceptance re-resolves the current request routes; a missing or changed binding returns `invalid_continuation` before policy, credentials, or egress, then the matching frozen snapshot is reused for the action and nested/later calls. `tools/list` remains based only on roles and scopes, so route availability neither reveals tenant topology nor changes the existing authorization filter.
|
|
200
|
+
|
|
112
201
|
## Per-tool authorization
|
|
113
202
|
|
|
114
203
|
Keep endpoint authentication in `customerAuth.*(...)`, then narrow individual tools with the optional typed `authorization` rule. Every `requiredScopes` value is required; any one `allowedRoles` value is sufficient; when both lists are present, both conditions apply. Omit `authorization` for an unrestricted tool. Do not invent a policy expression language or infer authorization from tool arguments, page context, connector output, email domains, or other unverified data.
|
|
@@ -135,7 +224,7 @@ Role values are trusted only from the explicitly configured claim path (or the p
|
|
|
135
224
|
|
|
136
225
|
Use delegated connector auth when the downstream API must enforce its own per-user authorization — a shared service credential plus a forwarded user id would bypass it. Three shapes exist; pick by who owns the downstream:
|
|
137
226
|
|
|
138
|
-
- **`delegatedTokenExchange`** — your own API. The platform signs a short-lived, verifiable assertion of the signed-in user and exchanges it at a token endpoint you implement (RFC 8693).
|
|
227
|
+
- **`delegatedTokenExchange`** — your own API. The platform signs a short-lived, verifiable assertion of the signed-in user and exchanges it at a token endpoint you implement (RFC 8693). It works with verified customer OIDC identities and the built-in Firebase/Microsoft adapters; no per-user OAuth enrollment. Embedded-assistant sessions support this exchange for static connectors. Customer-routed connectors require a direct or federated MCP OIDC request and fail closed in embedded-assistant sessions because those sessions carry no IdP endpoint claim.
|
|
139
228
|
- **`delegatedOAuth` with `provider: "firebase" | "microsoft"`** — Noodle-managed bridge providers using stored per-user refresh tokens. Requires the matching `customerAuth` bridge; any other provider string is the compile error `unsupported_delegated_provider`.
|
|
140
229
|
- **`delegatedSessionCookie`** — Firebase-managed session-cookie apps only; not a generic mechanism.
|
|
141
230
|
|
|
@@ -144,7 +233,7 @@ Use delegated connector auth when the downstream API must enforce its own per-us
|
|
|
144
233
|
```ts
|
|
145
234
|
auth: {
|
|
146
235
|
kind: 'delegatedTokenExchange',
|
|
147
|
-
tokenUrl: 'https://app.example.com/api/assistant/oauth/token', //
|
|
236
|
+
tokenUrl: 'https://app.example.com/api/assistant/oauth/token', // fixed HTTPS; static connectors also allowlist its origin
|
|
148
237
|
clientId: variable('EXAMPLE_DELEG_CLIENT_ID'),
|
|
149
238
|
clientSecret: secret('EXAMPLE_DELEG_CLIENT_SECRET'),
|
|
150
239
|
scopes: ['time_off'], // optional
|
|
@@ -167,7 +256,7 @@ scope=time_off (space-joined, when configured)
|
|
|
167
256
|
audience=example-api (when configured)
|
|
168
257
|
```
|
|
169
258
|
|
|
170
|
-
The `subject_token` claims: `iss` (platform issuer; JWKS at `{iss}/.well-known/jwks.json`), `sub` (verified user id), `aud` (your configured audience or the tokenUrl), `email`, `name`, `claims` (declared session claims), `tenant` (`org/app/env`), `deployment`, `iat`, `exp` (about 120 s), `jti`. Respond with `{ "access_token": "...", "token_type": "Bearer", "expires_in": 900 }`; the broker caches per user + connector + scopes until `expires_in` minus 300 s and presents the token downstream as `Authorization: Bearer`.
|
|
259
|
+
The `subject_token` claims: `iss` (platform issuer; JWKS at `{iss}/.well-known/jwks.json`), `sub` (verified user id), `aud` (your configured audience or the tokenUrl), `email`, `name`, `claims` (declared session claims), `tenant` (`org/app/env`), `deployment`, `iat`, `exp` (about 120 s), `jti`. A routed exchange adds `route: { key, fingerprint }`, never the customer URL; credential cache and single-flight keys include that route binding. Respond with `{ "access_token": "...", "token_type": "Bearer", "expires_in": 900 }`; the broker caches per user + connector + scopes + route until `expires_in` minus 300 s and presents the token downstream as `Authorization: Bearer`.
|
|
171
260
|
|
|
172
261
|
### The downstream token endpoint (your backend)
|
|
173
262
|
|
|
@@ -63,5 +63,14 @@ Run `noodle validate` (add `--json` for the machine-readable envelope, `--fix-pr
|
|
|
63
63
|
| `unsupported_credential_profile` | Select a credential profile declared by the connector and accepted by the operation; use the reported suggestions instead of inventing a profile name. |
|
|
64
64
|
| `credential_scope_mismatch` | Declare a connection source whose scopes include every operation-required scope, or select an external exchange provider that can mint them. |
|
|
65
65
|
| `credential_audience_mismatch` | Set the connection source audience to the operation-required audience exactly, or use an external exchange provider that can mint it. |
|
|
66
|
+
| `customer_endpoint_auth_required` | Protect the app with direct or federated `customerAuth` OIDC and map every reachable `customerEndpoint` key under auth routing. |
|
|
67
|
+
| `customer_endpoint_mapping_required` | Add the endpoint key at the cited auth routing path; every direct/federated issuer must map every customer endpoint used by the app. |
|
|
68
|
+
| `customer_endpoint_unknown_mapping` | Remove the unknown or unused auth routing key, or use that exact declared `customerEndpoint` key from a reachable connector operation. |
|
|
69
|
+
| `customer_endpoint_bridge_unsupported` | Replace the Firebase/Microsoft bridge with direct or federated OIDC before using auth-derived customer connector endpoints. |
|
|
70
|
+
| `customer_endpoint_action_unsupported` | Set exact `annotations.confirm: true` on the enclosing tool, or keep the customer-routed operation read-only; action hints alone do not enable confirmation. |
|
|
71
|
+
| `customer_endpoint_surface_unsupported` | Move the customer-routed call into a tool fulfilment; routed resources, prompts, and ambient providers are unsupported. |
|
|
72
|
+
| `customer_endpoint_credential_source_unsupported` | Remove the manifest connection binding; a customer-routed connector uses its declared delegated token exchange auth or no auth. |
|
|
73
|
+
| `customer_endpoint_policy_conflict` | Give every reachable declaration of this endpoint key one identical policy, or rename keys whose allowed origins differ. |
|
|
74
|
+
| `customer_endpoint_routing_inconsistent` | Regenerate the connector catalog so every action route includes all of its ordinary customer endpoint dependencies. |
|
|
66
75
|
| `unused_connector_alias` | A declared connector alias is never called; remove the unused `use` entry or wire it into a tool. |
|
|
67
76
|
| `arg_mismatch` | A connector call is missing or adds arguments; match the operation signature under `expected`/`got`. |
|
|
@@ -14,7 +14,7 @@ Paths are relative to this skill directory. Assets (images/fonts) are omitted fr
|
|
|
14
14
|
| `acme-discovery` | Top-of-funnel discovery→handoff: a discovery carousel, a `create_handoff` deep link, and a design-first UX spec + wireframe. | `examples/acme-discovery/src/server.ts` + `design/` |
|
|
15
15
|
| `acme-tasks` | A two-way productivity app designed around its top-3 prioritized flows (capture/prioritize/complete), with a design-first flow spec + wireframe. | `examples/acme-tasks/src/server.ts` + `design/` |
|
|
16
16
|
| `acme-bistro` | End-to-end ordering with a payment-only handoff; ships a gold-standard `design/` set (UX doc, wireframe with compliance audit, API contract). | `examples/acme-bistro/src/server.ts` + `design/` |
|
|
17
|
-
| `customer-auth` | End-user
|
|
17
|
+
| `customer-auth` | End-user OIDC, private customer API routing, route-bound confirmed actions, roles/scopes, and delegated credentials. | `examples/customer-auth/src/server.ts` |
|
|
18
18
|
| `gmail-multi-account` | One curated Gmail connector reused by two account bindings, canonical account arrays, exact mutation confirmation, and an accompanying personal-automation skill. | `examples/gmail-multi-account/src/server.ts` |
|
|
19
19
|
| `google-bigquery` | Keyless Google Workload Identity Federation with optional service-account impersonation, a BigQuery REST connector, and complete developer/operator setup. | `examples/google-bigquery/src/server.ts` |
|
|
20
20
|
|
|
@@ -3,7 +3,7 @@ name: reporting-noodle-feedback
|
|
|
3
3
|
description: "Use when a Noodle Seed bug, misleading instruction, missing capability, or concrete product improvement should be proposed to the user."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
<!-- noodle-skill version:0.
|
|
6
|
+
<!-- noodle-skill version:0.56.0 hash:0f404109f4845683 -->
|
|
7
7
|
|
|
8
8
|
# reporting-noodle-feedback
|
|
9
9
|
|
|
@@ -3,7 +3,7 @@ name: verifying-mcp-delivery
|
|
|
3
3
|
description: "Use when proving a Noodle Seed MCP project works at a named compile, local, connector, App, host, deployment, or production evidence level."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
<!-- noodle-skill version:0.
|
|
6
|
+
<!-- noodle-skill version:0.56.0 hash:6ef6ef551e26b78e -->
|
|
7
7
|
|
|
8
8
|
# verifying-mcp-delivery
|
|
9
9
|
|
package/skills/codex/SKILL.md
CHANGED
|
@@ -3,7 +3,7 @@ name: noodle-seed
|
|
|
3
3
|
description: "Use when building, validating, testing, deploying, or operating a local or hosted Noodle Seed MCP server or app authored in TypeScript with the noodle CLI."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
<!-- noodle-skill version:0.
|
|
6
|
+
<!-- noodle-skill version:0.56.0 hash:ec5bfcd0d8165205 -->
|
|
7
7
|
|
|
8
8
|
# Noodle Seed
|
|
9
9
|
|
|
@@ -3,7 +3,7 @@ name: authoring-mcp-servers
|
|
|
3
3
|
description: "Use when creating or extending a headless Noodle Seed MCP server, tool, resource, prompt, or typed model-facing capability."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
<!-- noodle-skill version:0.
|
|
6
|
+
<!-- noodle-skill version:0.56.0 hash:0b2fd8c7e43fc69f -->
|
|
7
7
|
|
|
8
8
|
# authoring-mcp-servers
|
|
9
9
|
|
|
@@ -3,7 +3,7 @@ name: building-mcp-apps
|
|
|
3
3
|
description: "Use when a Noodle Seed MCP App, widget, interactive card, visual interaction, or host-visible UI is the primary requested outcome."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
<!-- noodle-skill version:0.
|
|
6
|
+
<!-- noodle-skill version:0.56.0 hash:f7fa54992c8d7692 -->
|
|
7
7
|
|
|
8
8
|
# building-mcp-apps
|
|
9
9
|
|
|
@@ -3,7 +3,7 @@ name: connecting-apis-to-mcp
|
|
|
3
3
|
description: "Use when credentials, an API URL, an OpenAPI document, or an observed response must become real Noodle Seed MCP behavior."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
<!-- noodle-skill version:0.
|
|
6
|
+
<!-- noodle-skill version:0.56.0 hash:1e86b8704f407bd3 -->
|
|
7
7
|
|
|
8
8
|
# connecting-apis-to-mcp
|
|
9
9
|
|
|
@@ -3,7 +3,7 @@ name: debugging-mcp-delivery
|
|
|
3
3
|
description: "Use when an existing Noodle Seed MCP project has a concrete validation, runtime, connector, App, host, deployment, or production failure."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
<!-- noodle-skill version:0.
|
|
6
|
+
<!-- noodle-skill version:0.56.0 hash:aa715bae12041d7c -->
|
|
7
7
|
|
|
8
8
|
# debugging-mcp-delivery
|
|
9
9
|
|
|
@@ -3,7 +3,7 @@ name: deploying-mcp-services
|
|
|
3
3
|
description: "Use when the user explicitly requests a Noodle Seed hosted link, configuration write, deployment, access change, rollback, or connection write."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
<!-- noodle-skill version:0.
|
|
6
|
+
<!-- noodle-skill version:0.56.0 hash:93e735b7ffb45df1 -->
|
|
7
7
|
|
|
8
8
|
# deploying-mcp-services
|
|
9
9
|
|
|
@@ -3,7 +3,7 @@ name: designing-mcp-products
|
|
|
3
3
|
description: "Use when a Noodle Seed MCP product idea needs conversational fit, user benefit, scope, interaction, or evidence design before implementation."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
<!-- noodle-skill version:0.
|
|
6
|
+
<!-- noodle-skill version:0.56.0 hash:76cce86729cffbee -->
|
|
7
7
|
|
|
8
8
|
# designing-mcp-products
|
|
9
9
|
|
|
@@ -3,7 +3,7 @@ name: embedding-mcp-assistants
|
|
|
3
3
|
description: "Use when embedding a Noodle assistant into an existing SaaS or web application with browser, identity, session, and credential boundaries."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
<!-- noodle-skill version:0.
|
|
6
|
+
<!-- noodle-skill version:0.56.0 hash:cc54a67f21c0ecdb -->
|
|
7
7
|
|
|
8
8
|
# embedding-mcp-assistants
|
|
9
9
|
|