@noodleseed/one 0.139.3 → 0.140.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/dist/metrics-render.d.ts +8 -0
- package/dist/metrics-render.d.ts.map +1 -1
- package/dist/metrics-render.js +7 -1
- package/dist/metrics-render.js.map +1 -1
- package/node_modules/@noodle-borg/agent-kit/package.json +1 -1
- package/node_modules/@noodle-borg/control-plane/dist/personal-workspace.d.ts +7 -0
- package/node_modules/@noodle-borg/{service → control-plane}/dist/personal-workspace.js +1 -2
- package/node_modules/@noodle-borg/control-plane/dist/portable.d.ts +1 -0
- package/node_modules/@noodle-borg/control-plane/dist/portable.js +1 -0
- package/node_modules/@noodle-borg/module/dist/contract.d.ts +0 -64
- package/node_modules/@noodle-borg/module/dist/contract.js +3 -2
- package/node_modules/@noodle-borg/module/dist/index.d.ts +1 -0
- package/node_modules/@noodle-borg/module/dist/index.js +1 -0
- package/node_modules/@noodle-borg/module/dist/intent.d.ts +1 -1
- package/node_modules/@noodle-borg/module/dist/request-analytics.d.ts +80 -0
- package/node_modules/@noodle-borg/module/dist/request-analytics.js +10 -0
- package/node_modules/@noodle-borg/observability/dist/intent-capture.js +4 -0
- package/node_modules/@noodle-borg/observability/dist/request-event-buffer.js +4 -0
- package/node_modules/@noodle-borg/observability/dist/request-event-metrics.js +15 -0
- package/node_modules/@noodle-borg/observability/dist/request-events-postgres.js +19 -3
- package/node_modules/@noodle-borg/observability/dist/request-events.js +3 -0
- package/node_modules/@noodle-borg/observability/dist/runtime.js +61 -1
- package/node_modules/@noodle-borg/protocol/dist/handlers/prompts.js +2 -2
- package/node_modules/@noodle-borg/protocol/dist/handlers/resources.js +2 -2
- package/node_modules/@noodle-borg/protocol/dist/handlers/tools.js +3 -3
- package/node_modules/@noodle-borg/protocol/dist/observation.d.ts +17 -0
- package/node_modules/@noodle-borg/protocol/dist/observation.js +14 -0
- package/node_modules/@noodle-borg/protocol/dist/v2/server.js +5 -5
- package/node_modules/@noodle-borg/runtime/dist/index.d.ts +1 -1
- package/node_modules/@noodle-borg/runtime/dist/operation-execution.d.ts +1 -1
- package/node_modules/@noodle-borg/runtime/dist/operation-execution.js +34 -6
- package/node_modules/@noodle-borg/runtime/dist/result.d.ts +18 -0
- package/node_modules/@noodle-borg/service/dist/routes/control-plane.js +1 -1
- package/node_modules/@noodle-borg/service/dist/service-resource-cleanup.js +1 -6
- package/node_modules/@noodle-borg/service/package.json +1 -1
- package/node_modules/@noodle-borg/transport-http/dist/client-identity.js +124 -0
- package/node_modules/@noodle-borg/transport-http/dist/identity-authorization.js +8 -8
- package/node_modules/@noodle-borg/transport-http/dist/request-capture.js +97 -10
- package/node_modules/@noodle-borg/transport-http/dist/serve-request.js +40 -10
- package/package.json +2 -2
|
@@ -3,7 +3,7 @@ import { createDualEraMcpHandler, handleStatelessHttp, JSON_RPC, } from '@noodle
|
|
|
3
3
|
import { admissionContexts, requestsInvocationContext } from './admission-context.js';
|
|
4
4
|
import { authorizeIdentityMode, authorizeMixedMode, authorizePublicServiceMode, canonicalResourceUrl, isIdentityMode, } from './identity-authorization.js';
|
|
5
5
|
import { invocationClientHint } from './invocation-client-hints.js';
|
|
6
|
-
import { emitIntentEvents, emitRequestEvent, header } from './request-capture.js';
|
|
6
|
+
import { emitDeniedRequestEvent, emitIntentEvents, emitRequestEvent, header, } from './request-capture.js';
|
|
7
7
|
import { admissionRpcError, rpcError, sendJson } from './responses.js';
|
|
8
8
|
import { preflightServicePrincipalToolCall, sendServicePrincipalToolDenial, } from './service-principal-authorization.js';
|
|
9
9
|
import { preflightToolAuthorization, sendToolAuthorizationDenial } from './tool-authorization.js';
|
|
@@ -18,6 +18,22 @@ import { widgetDomainProjectionForRequest } from './widget-host.js';
|
|
|
18
18
|
export async function serveRequest(req, res, target, auth, routeId, maxBody, allowed, tenant) {
|
|
19
19
|
const startedAt = Date.now();
|
|
20
20
|
const requestId = randomUUID();
|
|
21
|
+
// Every structured log line for this request carries the analytics correlation id (#1309).
|
|
22
|
+
// Field merge, not `child()`: injected loggers are only guaranteed the four level methods.
|
|
23
|
+
const logger = {
|
|
24
|
+
info: (event, fields) => auth.logger.info(event, { requestId, ...fields }),
|
|
25
|
+
warn: (event, fields) => auth.logger.warn(event, { requestId, ...fields }),
|
|
26
|
+
};
|
|
27
|
+
const denied = (errorKind, parsedBody) => emitDeniedRequestEvent({
|
|
28
|
+
req,
|
|
29
|
+
auth,
|
|
30
|
+
tenant,
|
|
31
|
+
target,
|
|
32
|
+
...(parsedBody === undefined ? {} : { parsed: parsedBody }),
|
|
33
|
+
startedAt,
|
|
34
|
+
requestId,
|
|
35
|
+
errorKind,
|
|
36
|
+
});
|
|
21
37
|
const origin = header(req, 'origin');
|
|
22
38
|
if (origin !== undefined && !originAllowed(origin, allowed)) {
|
|
23
39
|
return sendJson(res, 403, rpcError(JSON_RPC.INVALID_REQUEST, 'origin not allowed'));
|
|
@@ -37,7 +53,7 @@ export async function serveRequest(req, res, target, auth, routeId, maxBody, all
|
|
|
37
53
|
if (isIdentityMode(auth.accessMode)) {
|
|
38
54
|
const authResult = await authorizeIdentityMode(req, res, auth);
|
|
39
55
|
if (!authResult.allow)
|
|
40
|
-
return;
|
|
56
|
+
return denied(`auth_denied.${authResult.reason}`);
|
|
41
57
|
if (authResult.caller !== undefined) {
|
|
42
58
|
protocolContext = {
|
|
43
59
|
...protocolContext,
|
|
@@ -59,7 +75,7 @@ export async function serveRequest(req, res, target, auth, routeId, maxBody, all
|
|
|
59
75
|
else if (auth.accessMode === 'mixed') {
|
|
60
76
|
const authResult = await authorizeMixedMode(req, res, auth);
|
|
61
77
|
if (!authResult.allow)
|
|
62
|
-
return;
|
|
78
|
+
return denied(`auth_denied.${authResult.reason}`);
|
|
63
79
|
if (authResult.caller !== undefined) {
|
|
64
80
|
protocolContext = {
|
|
65
81
|
...protocolContext,
|
|
@@ -117,7 +133,7 @@ export async function serveRequest(req, res, target, auth, routeId, maxBody, all
|
|
|
117
133
|
const decision = auth.admissionGate
|
|
118
134
|
? await auth.admissionGate(context)
|
|
119
135
|
: { allow: true };
|
|
120
|
-
|
|
136
|
+
logger.info('mcp.admission', {
|
|
121
137
|
routeId: context.routeId,
|
|
122
138
|
method: context.method,
|
|
123
139
|
category: context.category,
|
|
@@ -134,12 +150,13 @@ export async function serveRequest(req, res, target, auth, routeId, maxBody, all
|
|
|
134
150
|
if (decision.retryAfterSeconds !== undefined) {
|
|
135
151
|
res.setHeader('Retry-After', String(decision.retryAfterSeconds));
|
|
136
152
|
}
|
|
137
|
-
|
|
153
|
+
sendJson(res, decision.status ?? 403, admissionRpcError(context, decision));
|
|
154
|
+
return denied(`admission_denied.${boundedReason(decision.reason)}`, parsed);
|
|
138
155
|
}
|
|
139
156
|
}
|
|
140
157
|
const authorization = preflightToolAuthorization(parsed, target, protocolContext.caller);
|
|
141
158
|
for (const observation of authorization.observations) {
|
|
142
|
-
|
|
159
|
+
logger.info('mcp.tool_authorization', {
|
|
143
160
|
toolName: observation.toolName,
|
|
144
161
|
decision: observation.decision,
|
|
145
162
|
reason: observation.reason,
|
|
@@ -149,12 +166,12 @@ export async function serveRequest(req, res, target, auth, routeId, maxBody, all
|
|
|
149
166
|
}
|
|
150
167
|
if (authorization.denial !== undefined) {
|
|
151
168
|
sendToolAuthorizationDenial(req, res, auth, authorization.denial);
|
|
152
|
-
return;
|
|
169
|
+
return denied(`auth_denied.${authorization.denial.decision.reason}`, parsed);
|
|
153
170
|
}
|
|
154
171
|
const serviceAuthorization = preflightServicePrincipalToolCall(parsed, target, protocolContext.caller);
|
|
155
172
|
const serviceSubject = protocolContext.caller?.identityKind === 'service' ? protocolContext.caller.subject : undefined;
|
|
156
173
|
for (const observation of serviceAuthorization.observations) {
|
|
157
|
-
|
|
174
|
+
logger.info('mcp.service_principal_tool_authorization', {
|
|
158
175
|
subject: serviceSubject,
|
|
159
176
|
toolName: observation.toolName,
|
|
160
177
|
decision: observation.decision,
|
|
@@ -176,7 +193,7 @@ export async function serveRequest(req, res, target, auth, routeId, maxBody, all
|
|
|
176
193
|
}
|
|
177
194
|
catch {
|
|
178
195
|
try {
|
|
179
|
-
|
|
196
|
+
logger.warn('mcp.tool_authorization.observer_failed', {
|
|
180
197
|
toolName: observation.toolName,
|
|
181
198
|
decision: observation.decision,
|
|
182
199
|
});
|
|
@@ -188,7 +205,7 @@ export async function serveRequest(req, res, target, auth, routeId, maxBody, all
|
|
|
188
205
|
}
|
|
189
206
|
if (serviceAuthorization.denial !== undefined) {
|
|
190
207
|
sendServicePrincipalToolDenial(res, serviceAuthorization.denial);
|
|
191
|
-
return;
|
|
208
|
+
return denied(`auth_denied.${serviceAuthorization.denial.reason}`, parsed);
|
|
192
209
|
}
|
|
193
210
|
if (auth.resolveInvocationContext !== undefined && requestsInvocationContext(parsed)) {
|
|
194
211
|
const clientHint = invocationClientHint(parsed);
|
|
@@ -220,6 +237,7 @@ export async function serveRequest(req, res, target, auth, routeId, maxBody, all
|
|
|
220
237
|
},
|
|
221
238
|
};
|
|
222
239
|
}
|
|
240
|
+
const dispatchedAt = Date.now();
|
|
223
241
|
if (auth.protocolMode === 'legacy-only') {
|
|
224
242
|
await handleStatelessHttp(target, req, res, parsed, protocolContext);
|
|
225
243
|
}
|
|
@@ -251,9 +269,21 @@ export async function serveRequest(req, res, target, auth, routeId, maxBody, all
|
|
|
251
269
|
observed,
|
|
252
270
|
startedAt,
|
|
253
271
|
requestId,
|
|
272
|
+
timing: { queueMs: dispatchedAt - startedAt, execMs: Date.now() - dispatchedAt },
|
|
254
273
|
});
|
|
255
274
|
emitIntentEvents({ req, target, auth, tenant, parsed, observations, requestId });
|
|
256
275
|
}
|
|
276
|
+
/** Bound a gate-supplied denial reason into a low-cardinality token for the analytics errorKind. */
|
|
277
|
+
function boundedReason(reason) {
|
|
278
|
+
if (reason === undefined)
|
|
279
|
+
return 'denied';
|
|
280
|
+
const token = reason
|
|
281
|
+
.toLowerCase()
|
|
282
|
+
.replace(/[^a-z0-9_-]+/g, '_')
|
|
283
|
+
.replace(/^_+|_+$/g, '')
|
|
284
|
+
.slice(0, 32);
|
|
285
|
+
return token.length === 0 ? 'denied' : token;
|
|
286
|
+
}
|
|
257
287
|
export function originAllowed(origin, allowed) {
|
|
258
288
|
if (allowed === undefined)
|
|
259
289
|
return true;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@noodleseed/one",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.140.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Noodle CLI by Noodle Seed — author, run, and deploy declarative MCP servers. Embedding the assistant in your own web app is @noodleseed/assistant.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -236,7 +236,7 @@
|
|
|
236
236
|
"@modelcontextprotocol/client": "2.0.0",
|
|
237
237
|
"@modelcontextprotocol/server": "2.0.0",
|
|
238
238
|
"@noodle-borg/admission-limits": "0.0.0",
|
|
239
|
-
"@noodle-borg/agent-kit": "0.84.
|
|
239
|
+
"@noodle-borg/agent-kit": "0.84.1",
|
|
240
240
|
"@noodle-borg/app-audit": "0.0.0",
|
|
241
241
|
"@noodle-borg/app-package": "0.0.0",
|
|
242
242
|
"@noodle-borg/assistant-gateway": "0.0.0",
|