@vibesdotdev/mcp 1.0.0-rc.2 → 1.0.0-rc.5
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/README.md +11 -11
- package/dist/adapters/index.d.ts +0 -2
- package/dist/adapters/index.d.ts.map +1 -1
- package/dist/adapters/index.js +0 -2
- package/dist/adapters/index.js.map +1 -1
- package/dist/adapters/mcp/connection-identity.d.ts +18 -0
- package/dist/adapters/mcp/connection-identity.d.ts.map +1 -1
- package/dist/adapters/mcp/connection-identity.js +26 -0
- package/dist/adapters/mcp/connection-identity.js.map +1 -1
- package/dist/adapters/mcp/dual-era-handler.d.ts +22 -0
- package/dist/adapters/mcp/dual-era-handler.d.ts.map +1 -0
- package/dist/adapters/mcp/dual-era-handler.js +150 -0
- package/dist/adapters/mcp/dual-era-handler.js.map +1 -0
- package/dist/adapters/mcp/index.d.ts +0 -4
- package/dist/adapters/mcp/index.d.ts.map +1 -1
- package/dist/adapters/mcp/index.js +2 -10
- package/dist/adapters/mcp/index.js.map +1 -1
- package/dist/adapters/mcp/mcp-http-session.d.ts +1 -1
- package/dist/adapters/mcp/mcp-http-session.d.ts.map +1 -1
- package/dist/adapters/mcp/mcp-http-session.js +1 -1
- package/dist/adapters/mcp/mcp-http-session.js.map +1 -1
- package/dist/adapters/mcp/projection-doctor.d.ts.map +1 -1
- package/dist/adapters/mcp/projection-doctor.js +11 -4
- package/dist/adapters/mcp/projection-doctor.js.map +1 -1
- package/dist/adapters/mcp/projection.d.ts +1 -1
- package/dist/adapters/mcp/projection.d.ts.map +1 -1
- package/dist/adapters/mcp/projection.js.map +1 -1
- package/dist/adapters/mcp/sampling-context.d.ts.map +1 -1
- package/dist/adapters/mcp/sampling-context.js +1 -1
- package/dist/adapters/mcp/sampling-context.js.map +1 -1
- package/dist/adapters/mcp/sampling.d.ts +1 -1
- package/dist/adapters/mcp/sampling.d.ts.map +1 -1
- package/dist/adapters/mcp/server.consumer.d.ts +1 -1
- package/dist/adapters/mcp/server.consumer.d.ts.map +1 -1
- package/dist/adapters/mcp/server.consumer.js +59 -32
- package/dist/adapters/mcp/server.consumer.js.map +1 -1
- package/dist/adapters/mcp/server.d.ts +1 -22
- package/dist/adapters/mcp/server.d.ts.map +1 -1
- package/dist/adapters/mcp/server.js +232 -266
- package/dist/adapters/mcp/server.js.map +1 -1
- package/dist/adapters/mcp/startup-refusal.d.ts +2 -2
- package/dist/adapters/mcp/startup-refusal.d.ts.map +1 -1
- package/dist/adapters/mcp/startup-refusal.js +10 -10
- package/dist/adapters/mcp/startup-refusal.js.map +1 -1
- package/dist/adapters/mcp/stdio-relay.d.ts +1 -34
- package/dist/adapters/mcp/stdio-relay.d.ts.map +1 -1
- package/dist/adapters/mcp/stdio-relay.js +55 -71
- package/dist/adapters/mcp/stdio-relay.js.map +1 -1
- package/dist/adapters/mcp/tool-approval.d.ts +35 -0
- package/dist/adapters/mcp/tool-approval.d.ts.map +1 -0
- package/dist/adapters/mcp/tool-approval.js +156 -0
- package/dist/adapters/mcp/tool-approval.js.map +1 -0
- package/dist/adapters/mcp/tool-audit-live.d.ts +8 -12
- package/dist/adapters/mcp/tool-audit-live.d.ts.map +1 -1
- package/dist/adapters/mcp/tool-audit-live.js +9 -13
- package/dist/adapters/mcp/tool-audit-live.js.map +1 -1
- package/dist/adapters/mcp/tool-audit.d.ts +5 -0
- package/dist/adapters/mcp/tool-audit.d.ts.map +1 -1
- package/dist/adapters/mcp/tool-audit.js +73 -5
- package/dist/adapters/mcp/tool-audit.js.map +1 -1
- package/dist/adapters/mcp/trust-tag.d.ts.map +1 -1
- package/dist/adapters/mcp/trust-tag.js +5 -0
- package/dist/adapters/mcp/trust-tag.js.map +1 -1
- package/dist/adapters/mcp/web-handler.d.ts +6 -1
- package/dist/adapters/mcp/web-handler.d.ts.map +1 -1
- package/dist/adapters/mcp/web-handler.js +33 -13
- package/dist/adapters/mcp/web-handler.js.map +1 -1
- package/dist/adapters/transport-bun-http.d.ts +7 -3
- package/dist/adapters/transport-bun-http.d.ts.map +1 -1
- package/dist/adapters/transport-bun-http.js +5 -5
- package/dist/adapters/transport-bun-http.js.map +1 -1
- package/dist/adapters/types.d.ts +7 -9
- package/dist/adapters/types.d.ts.map +1 -1
- package/dist/adapters/zod-mcp.d.ts.map +1 -1
- package/dist/adapters/zod-mcp.js +4 -6
- package/dist/adapters/zod-mcp.js.map +1 -1
- package/dist/cli/mcp/audit/dev.audit.tools.impl.consumer.d.ts.map +1 -1
- package/dist/cli/mcp/audit/dev.audit.tools.impl.consumer.js +6 -1
- package/dist/cli/mcp/audit/dev.audit.tools.impl.consumer.js.map +1 -1
- package/dist/cli/surface-mcp/dev.mcp.serve.impl.consumer.d.ts.map +1 -1
- package/dist/cli/surface-mcp/dev.mcp.serve.impl.consumer.js +2 -0
- package/dist/cli/surface-mcp/dev.mcp.serve.impl.consumer.js.map +1 -1
- package/dist/config/mcp.config.descriptor.d.ts +28 -33
- package/dist/config/mcp.config.descriptor.d.ts.map +1 -1
- package/dist/config/mcp.config.descriptor.js +23 -19
- package/dist/config/mcp.config.descriptor.js.map +1 -1
- package/dist/docs/resources.docs.descriptor.d.ts.map +1 -1
- package/dist/docs/resources.docs.descriptor.js +43 -332
- package/dist/docs/resources.docs.descriptor.js.map +1 -1
- package/dist/docs/server.docs.descriptor.d.ts.map +1 -1
- package/dist/docs/server.docs.descriptor.js +71 -300
- package/dist/docs/server.docs.descriptor.js.map +1 -1
- package/dist/docs/tools.docs.descriptor.d.ts.map +1 -1
- package/dist/docs/tools.docs.descriptor.js +69 -244
- package/dist/docs/tools.docs.descriptor.js.map +1 -1
- package/dist/index.d.ts +7 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -6
- package/dist/index.js.map +1 -1
- package/dist/mcp.runtime.plugin.d.ts.map +1 -1
- package/dist/mcp.runtime.plugin.js +3 -1
- package/dist/mcp.runtime.plugin.js.map +1 -1
- package/dist/svelte/tool-widgets/summary.d.ts +2 -2
- package/dist/svelte/tool-widgets/summary.d.ts.map +1 -1
- package/dist/svelte/tool-widgets/summary.js +9 -4
- package/dist/tools/vibes-context/schemas.d.ts +2 -1
- package/dist/tools/vibes-context/schemas.d.ts.map +1 -1
- package/dist/tools/vibes-context/schemas.js +22 -4
- package/dist/tools/vibes-context/schemas.js.map +1 -1
- package/dist/tools/vibes-context/vibes-context.descriptor.d.ts.map +1 -1
- package/dist/tools/vibes-context/vibes-context.descriptor.js +11 -1
- package/dist/tools/vibes-context/vibes-context.descriptor.js.map +1 -1
- package/dist/tools/vibes-context/vibes-context.impl.d.ts.map +1 -1
- package/dist/tools/vibes-context/vibes-context.impl.js +22 -11
- package/dist/tools/vibes-context/vibes-context.impl.js.map +1 -1
- package/dist/types.d.ts +0 -32
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -0
- package/dist/types.js.map +1 -1
- package/dist/types.registry.d.ts +0 -59
- package/dist/types.registry.d.ts.map +1 -1
- package/dist/types.registry.js.map +1 -1
- package/package.json +61 -92
- package/dist/adapters/compatibility.d.ts +0 -20
- package/dist/adapters/compatibility.d.ts.map +0 -1
- package/dist/adapters/compatibility.js +0 -57
- package/dist/adapters/compatibility.js.map +0 -1
- package/dist/adapters/host-context.consumer.d.ts +0 -12
- package/dist/adapters/host-context.consumer.d.ts.map +0 -1
- package/dist/adapters/host-context.consumer.js +0 -10
- package/dist/adapters/host-context.consumer.js.map +0 -1
- package/dist/adapters/host-context.d.ts +0 -32
- package/dist/adapters/host-context.d.ts.map +0 -1
- package/dist/adapters/host-context.js +0 -17
- package/dist/adapters/host-context.js.map +0 -1
- package/dist/adapters/mcp/context.d.ts +0 -137
- package/dist/adapters/mcp/context.d.ts.map +0 -1
- package/dist/adapters/mcp/context.js +0 -230
- package/dist/adapters/mcp/context.js.map +0 -1
- package/dist/adapters/mcp/formatters.d.ts +0 -41
- package/dist/adapters/mcp/formatters.d.ts.map +0 -1
- package/dist/adapters/mcp/formatters.js +0 -102
- package/dist/adapters/mcp/formatters.js.map +0 -1
- package/dist/adapters/mcp/from-api-endpoint.d.ts +0 -59
- package/dist/adapters/mcp/from-api-endpoint.d.ts.map +0 -1
- package/dist/adapters/mcp/from-api-endpoint.js +0 -206
- package/dist/adapters/mcp/from-api-endpoint.js.map +0 -1
- package/dist/adapters/mcp/vibes-types.d.ts +0 -115
- package/dist/adapters/mcp/vibes-types.d.ts.map +0 -1
- package/dist/adapters/mcp/vibes-types.js +0 -7
- package/dist/adapters/mcp/vibes-types.js.map +0 -1
- package/dist/mcp/index.d.ts +0 -20
- package/dist/mcp/index.d.ts.map +0 -1
- package/dist/mcp/index.js +0 -19
- package/dist/mcp/index.js.map +0 -1
- package/dist/mcp.consumer.plugin.d.ts +0 -4
- package/dist/mcp.consumer.plugin.d.ts.map +0 -1
- package/dist/mcp.consumer.plugin.js +0 -19
- package/dist/mcp.consumer.plugin.js.map +0 -1
- package/dist/schemas/host-adapters.d.ts +0 -14
- package/dist/schemas/host-adapters.d.ts.map +0 -1
- package/dist/schemas/host-adapters.js +0 -7
- package/dist/schemas/host-adapters.js.map +0 -1
- package/dist/schemas/tool.d.ts +0 -8
- package/dist/schemas/tool.d.ts.map +0 -1
- package/dist/schemas/tool.js +0 -8
- package/dist/schemas/tool.js.map +0 -1
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
1
|
+
import mcpPackage from '../../../package.json' with { type: 'json' };
|
|
2
|
+
import { CallToolResultSchema, TextContentSchema, ResourceLinkSchema } from "@modelcontextprotocol/core";
|
|
3
|
+
import { McpServer, ResourceTemplate, ProtocolError, ProtocolErrorCode, acceptedContent, inputRequired, isInputRequiredResult } from "@modelcontextprotocol/server";
|
|
3
4
|
import z from 'zod/v4';
|
|
4
|
-
import {
|
|
5
|
+
import { approvalRequestStateCodec } from "./tool-approval.js";
|
|
5
6
|
const registeredTools = new WeakMap();
|
|
6
7
|
const explicitResumeInputSchema = z.object({
|
|
7
8
|
action: z.literal('resume'),
|
|
@@ -14,7 +15,7 @@ const receiptRestoreInputSchema = z.object({
|
|
|
14
15
|
participantId: z.string()
|
|
15
16
|
});
|
|
16
17
|
const explicitSessionTargetSchema = z.object({ sessionId: z.string().trim().min(1) });
|
|
17
|
-
import { admitConnectionCaller } from "./connection-identity.js";
|
|
18
|
+
import { admitConnectionCaller, RELAY_ATTESTATION_SOURCE, STDIO_LAUNCH_SOURCE, verifiedConnectionPrincipal } from "./connection-identity.js";
|
|
18
19
|
import { PROMPTS_MANIFEST_DESCRIPTOR_PREFIX, PROMPTS_MANIFEST_KIND_ID, promptsManifestDescriptorSchema } from '@vibesdotdev/ai-schemas';
|
|
19
20
|
import { resolvePromptText } from '@vibesdotdev/tools/prompts/manifests';
|
|
20
21
|
import { isDiscriminatedUnion, getDiscriminator, getDiscriminatorOptions, flattenDiscriminatedUnionToObject, isPlainUnion, resolveMcpDescription } from "../zod-mcp.js";
|
|
@@ -28,59 +29,16 @@ import { getLogger } from '@vibesdotdev/logging';
|
|
|
28
29
|
import { invokeTool } from '@vibesdotdev/tools/execution/invoke';
|
|
29
30
|
import { ToolInputValidationError } from '@vibesdotdev/tools/execution/input-validation';
|
|
30
31
|
import { buildDirectToolCallContext } from '@vibesdotdev/tools/execution/direct-context';
|
|
31
|
-
import { DEFAULT_AGENT_CAPABILITY_CEILING, TOOL_AGENT_CONTEXT_METADATA_KEY } from '@vibesdotdev/tools/schemas/runtime/capability-tier';
|
|
32
32
|
import { isLocalToolId, localToolsOf } from '@vibesdotdev/tools/local/node/ensure-local-tools';
|
|
33
|
-
import { detectMcpClientId } from '@vibesdotdev/tools/schemas/presets/registry';
|
|
34
|
-
import { loadMcpConfig } from "../../config/load.js";
|
|
35
33
|
import { formatLocalMcpToolDescription, vibesToolOrigin } from "./local-origin.js";
|
|
36
|
-
import { normalizeHarness } from "./serve-identity.js";
|
|
37
34
|
import { missingRequiredTools, selectMcpTools } from "./tool-selection.js";
|
|
38
|
-
import { deriveTrustTier, wrapToolOutputText } from "./trust-tag.js";
|
|
35
|
+
import { deriveTrustTier, shouldDropFromModelContext, wrapToolOutputText } from "./trust-tag.js";
|
|
39
36
|
import { createProjectionBirthResource, PROJECTION_BIRTH_RESOURCE_URI, projectionCatalogHash } from "./projection.js";
|
|
40
37
|
import { createMcpWireNameMap, mcpWireToolName } from "./mcp-wire-name.js";
|
|
41
38
|
import { ambientMeta, formatAmbientPrefix, liftAmbientEnvelope } from "./ambient-envelope.js";
|
|
42
39
|
import { computeMcpToolCatalog, createMcpToolCatalogCache, formatRegistrationFailureInstructions } from "./tool-catalog.js";
|
|
43
40
|
import { runWithMcpSamplingCallback } from "./sampling-context.js";
|
|
44
|
-
|
|
45
|
-
* Resolve the effective capability tier ceiling for an MCP connection from the
|
|
46
|
-
* runtime's `mcp` ConfigManifest (`clientCapabilityCeilings`).
|
|
47
|
-
*
|
|
48
|
-
* Verified transport harness identity (`admitConnectionCaller`) takes
|
|
49
|
-
* precedence over server-level `mcpClientId` and `preset`. Unconfigured
|
|
50
|
-
* profiles fall back to the tools default ceiling (`'1'`).
|
|
51
|
-
*/
|
|
52
|
-
export function resolveMcpClientCapabilityCeiling(config, input, source) {
|
|
53
|
-
const ceilings = config.clientCapabilityCeilings;
|
|
54
|
-
const candidates = [];
|
|
55
|
-
const pushCandidate = (raw) => {
|
|
56
|
-
const trimmed = raw?.trim().toLowerCase();
|
|
57
|
-
if (!trimmed)
|
|
58
|
-
return;
|
|
59
|
-
if (!candidates.includes(trimmed))
|
|
60
|
-
candidates.push(trimmed);
|
|
61
|
-
const normalized = (source ? normalizeHarness(source, trimmed) : undefined) ?? detectMcpClientId(trimmed) ?? undefined;
|
|
62
|
-
if (normalized && !candidates.includes(normalized))
|
|
63
|
-
candidates.push(normalized);
|
|
64
|
-
};
|
|
65
|
-
const verifiedHarness = input.admittedCaller?.harness;
|
|
66
|
-
if (verifiedHarness && verifiedHarness !== 'custom') {
|
|
67
|
-
pushCandidate(verifiedHarness);
|
|
68
|
-
}
|
|
69
|
-
pushCandidate(input.mcpClientId);
|
|
70
|
-
if (input.preset) {
|
|
71
|
-
pushCandidate(input.preset.replace(/^mcp:/i, ''));
|
|
72
|
-
}
|
|
73
|
-
if (verifiedHarness === 'custom') {
|
|
74
|
-
pushCandidate(verifiedHarness);
|
|
75
|
-
}
|
|
76
|
-
for (const candidate of candidates) {
|
|
77
|
-
const configured = ceilings[candidate];
|
|
78
|
-
if (configured !== undefined) {
|
|
79
|
-
return { profile: candidate, ceiling: configured };
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
return { profile: candidates[0], ceiling: DEFAULT_AGENT_CAPABILITY_CEILING };
|
|
83
|
-
}
|
|
41
|
+
import { requireMcpToolApproval } from "./tool-approval.js";
|
|
84
42
|
/**
|
|
85
43
|
* Project-local catalog ids (CLI `list --source=local` parity). Explicit
|
|
86
44
|
* options win (tests, serve bootstrap); otherwise the tools this runtime's
|
|
@@ -139,7 +97,6 @@ const ToolHookFailureSchema = z.object({
|
|
|
139
97
|
metadata: JsonObjectSchema.optional(),
|
|
140
98
|
alternatives: z.array(z.string()).optional()
|
|
141
99
|
});
|
|
142
|
-
const SafetyReadOnlySchema = z.object({ readOnly: z.boolean().optional() });
|
|
143
100
|
// SDK metadata and content contracts govern passthrough. Content stays required:
|
|
144
101
|
// the SDK's default empty array must not classify ordinary domain output as MCP.
|
|
145
102
|
const mcpToolResponseSchema = CallToolResultSchema.extend({
|
|
@@ -223,51 +180,50 @@ function getSuggestedToolCallFromErrorMetadata(metadata) {
|
|
|
223
180
|
function isZodLike(value) {
|
|
224
181
|
return value instanceof z.ZodType;
|
|
225
182
|
}
|
|
226
|
-
function
|
|
227
|
-
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
readOnlyHint: true,
|
|
238
|
-
destructiveHint: false,
|
|
239
|
-
idempotentHint: true,
|
|
240
|
-
openWorldHint: false
|
|
241
|
-
},
|
|
242
|
-
write: {
|
|
243
|
-
readOnlyHint: false,
|
|
244
|
-
destructiveHint: true,
|
|
245
|
-
idempotentHint: false,
|
|
246
|
-
openWorldHint: false
|
|
247
|
-
},
|
|
248
|
-
execute: {
|
|
249
|
-
readOnlyHint: false,
|
|
250
|
-
destructiveHint: false,
|
|
251
|
-
idempotentHint: false,
|
|
252
|
-
openWorldHint: true
|
|
253
|
-
}
|
|
254
|
-
}[category] ?? {
|
|
255
|
-
readOnlyHint: false,
|
|
256
|
-
destructiveHint: false,
|
|
257
|
-
idempotentHint: false,
|
|
258
|
-
openWorldHint: false
|
|
183
|
+
function safetyFacts(value) {
|
|
184
|
+
if (value === undefined)
|
|
185
|
+
return { category: undefined };
|
|
186
|
+
if (typeof value === 'string')
|
|
187
|
+
return { category: value };
|
|
188
|
+
return {
|
|
189
|
+
category: value.category,
|
|
190
|
+
readOnly: value.readOnly,
|
|
191
|
+
idempotent: value.idempotent,
|
|
192
|
+
destructive: value.destructive,
|
|
193
|
+
openWorld: value.openWorld
|
|
259
194
|
};
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* MCP annotations are tool-level hints. For a multi-action tool they are the
|
|
198
|
+
* conservative union of the top-level safety and every declared action or
|
|
199
|
+
* variant. A single write/execute action must clear `readOnlyHint`; a single
|
|
200
|
+
* write action must set `destructiveHint`. A descriptor that declares no
|
|
201
|
+
* safety at all is treated as the spec's conservative default (not read-only,
|
|
202
|
+
* destructive, open-world) rather than advertising no hint.
|
|
203
|
+
*/
|
|
204
|
+
function deriveMcpAnnotations(source) {
|
|
205
|
+
const top = safetyFacts(source.safety);
|
|
206
|
+
const actions = Object.values(source.actionSafety ?? {}).map((value) => safetyFacts(value));
|
|
207
|
+
const variants = (source.variantSafety ?? []).filter((value) => value !== undefined).map(safetyFacts);
|
|
208
|
+
const all = [top, ...actions, ...variants];
|
|
209
|
+
const categories = all.map((facts) => facts.category).filter((category) => category !== undefined);
|
|
210
|
+
const anyNonRead = categories.some((category) => category !== 'read');
|
|
211
|
+
const anyWrite = categories.includes('write');
|
|
212
|
+
const anyExecute = categories.includes('execute');
|
|
213
|
+
const explicitReadOnly = [top, ...variants].map((facts) => facts.readOnly).find((value) => value !== undefined);
|
|
214
|
+
const explicitDestructive = [top, ...variants].map((facts) => facts.destructive).find((value) => value !== undefined);
|
|
215
|
+
const explicitIdempotent = [top, ...variants].map((facts) => facts.idempotent).find((value) => value !== undefined);
|
|
216
|
+
const explicitOpenWorld = [top, ...variants].map((facts) => facts.openWorld).find((value) => value !== undefined);
|
|
217
|
+
const readOnly = explicitReadOnly ?? (top.category === 'read' && !anyNonRead);
|
|
218
|
+
const destructive = explicitDestructive ?? (anyWrite || variants.some((facts) => facts.destructive === true));
|
|
219
|
+
const idempotent = explicitIdempotent ?? readOnly;
|
|
220
|
+
const declaredNetwork = source.permissions?.some((permission) => permission === 'canAccessNetwork' || permission === 'network') ?? false;
|
|
221
|
+
const openWorld = explicitOpenWorld ?? (anyExecute || declaredNetwork || variants.some((facts) => facts.openWorld === true));
|
|
266
222
|
return {
|
|
267
|
-
readOnlyHint: readOnly
|
|
268
|
-
destructiveHint:
|
|
269
|
-
idempotentHint:
|
|
270
|
-
openWorldHint:
|
|
223
|
+
readOnlyHint: readOnly,
|
|
224
|
+
destructiveHint: destructive,
|
|
225
|
+
idempotentHint: idempotent,
|
|
226
|
+
openWorldHint: openWorld
|
|
271
227
|
};
|
|
272
228
|
}
|
|
273
229
|
function uniqueName(base, used) {
|
|
@@ -306,15 +262,13 @@ function buildResourceText(descriptor, resourceName, uri, variables) {
|
|
|
306
262
|
}
|
|
307
263
|
/** MCP prompt arguments arrive as strings; each manifest-declared argument becomes one. */
|
|
308
264
|
function buildPromptArgsSchema(argumentsSchema) {
|
|
309
|
-
if (!argumentsSchema || argumentsSchema.length === 0)
|
|
310
|
-
return undefined;
|
|
311
265
|
const argumentSchemas = {};
|
|
312
|
-
for (const arg of argumentsSchema) {
|
|
266
|
+
for (const arg of argumentsSchema ?? []) {
|
|
313
267
|
// Describe the outer schema: the SDK lists an optional argument's own description.
|
|
314
268
|
const description = arg.description ?? `${arg.name} argument`;
|
|
315
269
|
argumentSchemas[arg.name] = arg.required ? z.string().describe(description) : z.string().optional().describe(description);
|
|
316
270
|
}
|
|
317
|
-
return argumentSchemas;
|
|
271
|
+
return z.object(argumentSchemas);
|
|
318
272
|
}
|
|
319
273
|
function registerDescriptorMcpDefinitions(server, descriptors, runtime, logger, options = { announce: true }) {
|
|
320
274
|
const resourceUris = new Set();
|
|
@@ -394,10 +348,18 @@ function registerDescriptorMcpDefinitions(server, descriptors, runtime, logger,
|
|
|
394
348
|
.withId(`${PROMPTS_MANIFEST_DESCRIPTOR_PREFIX}${promptId}`)
|
|
395
349
|
.firstDescriptor();
|
|
396
350
|
const manifest = registered ? promptsManifestDescriptorSchema.parse(registered).manifest : undefined;
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
351
|
+
if (!manifest) {
|
|
352
|
+
// A tool names only real prompts/manifest descriptors. A missing
|
|
353
|
+
// descriptor is a registration defect; do not advertise a placeholder.
|
|
354
|
+
mcpLog.error(`Prompt '${promptId}' has no prompts/manifest descriptor; skipping`);
|
|
355
|
+
continue;
|
|
356
|
+
}
|
|
357
|
+
const promptDescription = resolveMcpDescription({
|
|
358
|
+
id: promptId,
|
|
359
|
+
name: manifest.name,
|
|
360
|
+
description: manifest.description || undefined
|
|
361
|
+
});
|
|
362
|
+
const argsSchema = buildPromptArgsSchema(manifest.arguments);
|
|
401
363
|
server.registerPrompt(name, {
|
|
402
364
|
description: promptDescription,
|
|
403
365
|
argsSchema
|
|
@@ -411,7 +373,7 @@ function registerDescriptorMcpDefinitions(server, descriptors, runtime, logger,
|
|
|
411
373
|
role: 'user',
|
|
412
374
|
content: {
|
|
413
375
|
type: 'text',
|
|
414
|
-
text: text || `Prompt '${promptId}'
|
|
376
|
+
text: text || `Prompt '${promptId}' rendered no text.`
|
|
415
377
|
}
|
|
416
378
|
}
|
|
417
379
|
]
|
|
@@ -439,25 +401,35 @@ function registerDescriptorMcpDefinitions(server, descriptors, runtime, logger,
|
|
|
439
401
|
*/
|
|
440
402
|
export function buildServer(runtime, options, logger, catalog = computeMcpToolCatalog(runtime, options, logger, resolveLocalToolIds(runtime, options))) {
|
|
441
403
|
const buildLog = logger.child('build');
|
|
442
|
-
const serverName = options.serverName ?? '
|
|
443
|
-
const serverVersion = options.serverVersion ??
|
|
404
|
+
const serverName = options.serverName ?? 'mcp';
|
|
405
|
+
const serverVersion = options.serverVersion ?? mcpPackage.version;
|
|
444
406
|
if (catalog.degradedKind) {
|
|
445
407
|
// Constitution rule 13: empty tools/list must not look healthy. The
|
|
446
408
|
// banner was composed (and error-logged) by the catalog fold.
|
|
447
409
|
return new McpServer({ name: serverName, version: serverVersion }, {
|
|
448
410
|
capabilities: catalog.capabilities,
|
|
449
|
-
instructions: catalog.instructions
|
|
411
|
+
instructions: catalog.instructions,
|
|
412
|
+
requestState: { verify: approvalRequestStateCodec.verify }
|
|
450
413
|
});
|
|
451
414
|
}
|
|
452
415
|
const selected = catalog.selected;
|
|
453
416
|
if (!selected) {
|
|
454
417
|
throw new Error('MCP tool catalog is non-degraded but carries no selection');
|
|
455
418
|
}
|
|
419
|
+
const requiredToolIds = new Set(selected.preset.requiredTools ?? []);
|
|
420
|
+
const missingRequiredLoaders = catalog.zombieCandidates.filter((id) => requiredToolIds.has(id));
|
|
421
|
+
if (missingRequiredLoaders.length > 0) {
|
|
422
|
+
throw new Error(`REQUIRED MCP TOOLS HAVE NO LOADER for preset ${selected.presetId}: ` +
|
|
423
|
+
`${missingRequiredLoaders.join(', ')}. A required continuity tool cannot be advertised ` +
|
|
424
|
+
'without an implementation; fix the composition before serving.');
|
|
425
|
+
}
|
|
456
426
|
const enabledDescriptors = catalog.enabledDescriptors;
|
|
457
427
|
const localToolIds = catalog.localToolIds;
|
|
458
428
|
const descriptors = catalog.descriptors;
|
|
459
429
|
createMcpWireNameMap(descriptors.map(descriptor => descriptor.id), options.mcpClientId, catalog.purposePresets);
|
|
460
|
-
const capabilities = catalog.capabilities
|
|
430
|
+
const capabilities = options.buildProvenance && !catalog.capabilities.resources
|
|
431
|
+
? { ...catalog.capabilities, resources: { listChanged: false } }
|
|
432
|
+
: catalog.capabilities;
|
|
461
433
|
const instructions = catalog.instructions;
|
|
462
434
|
const preScanFailedTools = [...catalog.preScanFailedTools];
|
|
463
435
|
const failedTools = [...preScanFailedTools];
|
|
@@ -465,7 +437,8 @@ export function buildServer(runtime, options, logger, catalog = computeMcpToolCa
|
|
|
465
437
|
const registeredToolIds = [];
|
|
466
438
|
const server = new McpServer({ name: serverName, version: serverVersion }, {
|
|
467
439
|
capabilities,
|
|
468
|
-
instructions: instructions || undefined
|
|
440
|
+
instructions: instructions || undefined,
|
|
441
|
+
requestState: { verify: approvalRequestStateCodec.verify }
|
|
469
442
|
});
|
|
470
443
|
for (const descriptor of descriptors) {
|
|
471
444
|
if (!descriptor.schemas?.input) {
|
|
@@ -545,100 +518,6 @@ export function buildServer(runtime, options, logger, catalog = computeMcpToolCa
|
|
|
545
518
|
appendMcpInstructions(server, birth.instructions);
|
|
546
519
|
}
|
|
547
520
|
registerDescriptorMcpDefinitions(server, descriptors, runtime, logger, { announce });
|
|
548
|
-
// Client auto-detection (2025-era scar tissue): when serve was spawned without
|
|
549
|
-
// --client, the base `mcp` preset applies and client-curated tools stay hidden.
|
|
550
|
-
// After initialize, we additive-register the detected preset and emit
|
|
551
|
-
// tools/listChanged.
|
|
552
|
-
//
|
|
553
|
-
// MCP 2026-07-28: tools/list MUST NOT vary per connection. This path mutates
|
|
554
|
-
// the catalog after initialize and is therefore gated behind an explicit
|
|
555
|
-
// opt-in (`allowClientAutoDetectUpgrade`, default OFF). Pass --client (or
|
|
556
|
-
// pack config) so the catalog is fixed at start.
|
|
557
|
-
// See docs/proposals/mcp-2026-07-28/FINDINGS.md.
|
|
558
|
-
if (!options.mcpClientId && options.allowClientAutoDetectUpgrade) {
|
|
559
|
-
const baselineIds = new Set(descriptors.map((descriptor) => descriptor.id));
|
|
560
|
-
server.server.oninitialized = () => {
|
|
561
|
-
const detected = settleNow(() => {
|
|
562
|
-
const clientName = server.server.getClientVersion()?.name;
|
|
563
|
-
const detectedClientId = detectMcpClientId(clientName, catalog.purposePresets);
|
|
564
|
-
if (!detectedClientId)
|
|
565
|
-
return;
|
|
566
|
-
buildLog.warn('MCP catalog mutated after initialize (client auto-detect). ' +
|
|
567
|
-
'Illegal under protocol 2026-07-28 — pass --client so the preset is fixed at start.', { clientName, detectedClientId });
|
|
568
|
-
// Same registration bound as the construct-time fold: a settled
|
|
569
|
-
// surface must not re-admit an unavailable descriptor through
|
|
570
|
-
// this opt-in mutation path. `zombieCandidates` is exactly the
|
|
571
|
-
// fold's unavailable set.
|
|
572
|
-
const settlementExcluded = catalog.loaderSettlement === 'settled' ? new Set(catalog.zombieCandidates) : null;
|
|
573
|
-
const upgraded = selectMcpTools(settlementExcluded
|
|
574
|
-
? enabledDescriptors.filter((descriptor) => !settlementExcluded.has(descriptor.id))
|
|
575
|
-
: [...enabledDescriptors], { mcpClientId: detectedClientId, customPresets: catalog.purposePresets });
|
|
576
|
-
const additions = upgraded.descriptors.filter((descriptor) => !baselineIds.has(descriptor.id));
|
|
577
|
-
if (additions.length === 0)
|
|
578
|
-
return;
|
|
579
|
-
let added = 0;
|
|
580
|
-
const upgradeFailures = [];
|
|
581
|
-
for (const descriptor of additions) {
|
|
582
|
-
const registered = settleNow(() => registerTool(server, descriptor, runtime, { ...options, customPresets: catalog.purposePresets }, logger, localToolIds));
|
|
583
|
-
if (!registered.ok) {
|
|
584
|
-
const facts = valueFreeFailure(registered.cause);
|
|
585
|
-
upgradeFailures.push(`${descriptor.id}: ${facts.name}`);
|
|
586
|
-
// Error (not warn): continuity tools often land only via
|
|
587
|
-
// this path when --client was omitted at serve start.
|
|
588
|
-
buildLog.error(`Client-detected tool failed to register: ${descriptor.id}`, { error: facts });
|
|
589
|
-
continue;
|
|
590
|
-
}
|
|
591
|
-
if (registered.value) {
|
|
592
|
-
baselineIds.add(descriptor.id);
|
|
593
|
-
added += 1;
|
|
594
|
-
}
|
|
595
|
-
else {
|
|
596
|
-
upgradeFailures.push(`${descriptor.id}: schema conversion failed`);
|
|
597
|
-
buildLog.error(`Client-detected tool failed to register: ${descriptor.id}`, { error: 'schema conversion failed' });
|
|
598
|
-
}
|
|
599
|
-
}
|
|
600
|
-
// Initialize already returned construct-time instructions; patch
|
|
601
|
-
// so agents that re-read server state / later sessions see the gap.
|
|
602
|
-
// Hosts that only saw initialize may still miss this — error log
|
|
603
|
-
// remains load-bearing until listChanged + instructions re-push.
|
|
604
|
-
if (upgradeFailures.length > 0) {
|
|
605
|
-
appendMcpInstructions(server, formatRegistrationFailureInstructions(upgradeFailures));
|
|
606
|
-
buildLog.error(`Client-detected preset upgrade incomplete: ${upgradeFailures.length} tool(s) not advertised`, { upgradeFailures, added, detectedClientId });
|
|
607
|
-
}
|
|
608
|
-
// Required continuity tools for the detected client must not
|
|
609
|
-
// vanish silently (brain/presence class outage via auto-detect).
|
|
610
|
-
const stillMissingRequired = missingRequiredTools([...baselineIds], upgraded.preset);
|
|
611
|
-
if (stillMissingRequired.length > 0) {
|
|
612
|
-
const section = `⚠ DEGRADED SURFACE: required tools missing after client auto-detect ` +
|
|
613
|
-
`(${detectedClientId}): ${stillMissingRequired.join(', ')}. ` +
|
|
614
|
-
`Report this to the user IMMEDIATELY; do NOT silently work around it.`;
|
|
615
|
-
appendMcpInstructions(server, section);
|
|
616
|
-
buildLog.error('REQUIRED MCP TOOLS MISSING after client auto-detect', {
|
|
617
|
-
detectedClientId,
|
|
618
|
-
stillMissingRequired
|
|
619
|
-
});
|
|
620
|
-
}
|
|
621
|
-
buildLog.info('Client-detected preset upgrade', {
|
|
622
|
-
clientName,
|
|
623
|
-
detectedClientId,
|
|
624
|
-
presetId: upgraded.presetId,
|
|
625
|
-
added,
|
|
626
|
-
addedTools: additions.map((descriptor) => descriptor.id),
|
|
627
|
-
upgradeFailures
|
|
628
|
-
});
|
|
629
|
-
});
|
|
630
|
-
if (!detected.ok) {
|
|
631
|
-
const facts = valueFreeFailure(detected.cause);
|
|
632
|
-
// Whole-path failure (not per-tool): still a rule-13 injury —
|
|
633
|
-
// thin base preset can look healthy without continuity tools.
|
|
634
|
-
appendMcpInstructions(server, '⚠ DEGRADED SURFACE: client preset auto-detection failed after initialize. ' +
|
|
635
|
-
'Continuity tools for the connected client may be missing. ' +
|
|
636
|
-
'Report this to the user IMMEDIATELY; do NOT silently work around it. ' +
|
|
637
|
-
'Diagnose: pass --client explicitly or check MCP initialize clientInfo.name.');
|
|
638
|
-
buildLog.error('Client preset auto-detection failed', { error: facts });
|
|
639
|
-
}
|
|
640
|
-
};
|
|
641
|
-
}
|
|
642
521
|
// Own admission before asynchronous SDK schema parsing. RegisteredTool is
|
|
643
522
|
// a public SDK handle; replacing it leaves admitted calls' schemas intact.
|
|
644
523
|
//
|
|
@@ -648,18 +527,26 @@ export function buildServer(runtime, options, logger, catalog = computeMcpToolCa
|
|
|
648
527
|
// failures stay tool-result-shaped but stamp `_meta.errorCode`
|
|
649
528
|
// (`INVALID_INPUT`), mirroring the execution path's capability-error
|
|
650
529
|
// projection below.
|
|
651
|
-
server.server.setRequestHandler(
|
|
530
|
+
server.server.setRequestHandler('tools/call', async (request, ctx) => {
|
|
652
531
|
const invocation = runtime.acquireInvocation();
|
|
653
532
|
try {
|
|
654
533
|
const attempted = await settle(() => invocation.runtime.runInvocation(async () => {
|
|
655
534
|
const tool = registeredTools.get(server)?.get(request.params.name);
|
|
656
535
|
if (!tool?.registration.enabled)
|
|
657
|
-
throw new
|
|
536
|
+
throw new ProtocolError(ProtocolErrorCode.InvalidParams, `Unknown tool: ${request.params.name}`);
|
|
658
537
|
if (request.params.task)
|
|
659
|
-
throw new
|
|
660
|
-
const
|
|
661
|
-
|
|
662
|
-
|
|
538
|
+
throw new ProtocolError(ProtocolErrorCode.InvalidParams, 'This tool does not support task execution');
|
|
539
|
+
const schema = tool.registration.inputSchema;
|
|
540
|
+
const validated = schema
|
|
541
|
+
? await schema['~standard'].validate(request.params.arguments)
|
|
542
|
+
: { value: request.params.arguments };
|
|
543
|
+
if ('issues' in validated && validated.issues) {
|
|
544
|
+
const issues = formatZodIssues({
|
|
545
|
+
issues: validated.issues.map((issue) => ({
|
|
546
|
+
path: issue.path?.map((part) => typeof part === 'object' && part !== null && 'key' in part ? part.key : part),
|
|
547
|
+
message: issue.message
|
|
548
|
+
}))
|
|
549
|
+
});
|
|
663
550
|
const text = `Invalid input for '${request.params.name}': ${issues}`;
|
|
664
551
|
// Omit structuredContent on isError: MCP clients that kept
|
|
665
552
|
// outputSchema validate it even on errors, and {ok:false,error}
|
|
@@ -670,13 +557,19 @@ export function buildServer(runtime, options, logger, catalog = computeMcpToolCa
|
|
|
670
557
|
_meta: { errorCode: 'INVALID_INPUT' }
|
|
671
558
|
};
|
|
672
559
|
}
|
|
673
|
-
|
|
560
|
+
const result = await tool.invoke(request.params.arguments, ctx, invocation);
|
|
561
|
+
// Low-level tools/call handlers own the era projection the SDK's
|
|
562
|
+
// built-in handler would have applied: the 2025 wire codec wraps a
|
|
563
|
+
// non-object output schema and its structuredContent as `{result}`.
|
|
564
|
+
return isInputRequiredResult(result)
|
|
565
|
+
? result
|
|
566
|
+
: server.server.projectCallToolResult(result, tool.registration.outputSchemaJson);
|
|
674
567
|
}));
|
|
675
568
|
if (attempted.ok)
|
|
676
569
|
return attempted.value;
|
|
677
570
|
// McpError must escape so the SDK answers with a JSON-RPC error
|
|
678
571
|
// response carrying its code and the request id.
|
|
679
|
-
if (attempted.cause instanceof
|
|
572
|
+
if (attempted.cause instanceof ProtocolError)
|
|
680
573
|
throw attempted.cause;
|
|
681
574
|
const failure = projectMcpToolFailure(attempted.cause);
|
|
682
575
|
return {
|
|
@@ -791,9 +684,30 @@ function normalizableOutputSchema(rawOutputSchema) {
|
|
|
791
684
|
const cached = normalizableOutputSchemas.get(rawOutputSchema);
|
|
792
685
|
if (cached)
|
|
793
686
|
return cached.schema;
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
687
|
+
let schema;
|
|
688
|
+
// MCP's 2025 wire requires an object-root output schema, and every
|
|
689
|
+
// structuredContent this server emits is an object. A union of object
|
|
690
|
+
// variants is object-shaped on every branch, so it is advertised: the
|
|
691
|
+
// 2025 codec wraps its non-object root and its structuredContent together.
|
|
692
|
+
// A non-object root (`z.unknown()`, arrays, primitives, mixed unions)
|
|
693
|
+
// would make clients demand structuredContent the text wire path never
|
|
694
|
+
// produces, so it is not advertised at all.
|
|
695
|
+
const isObjectOutput = (value) => {
|
|
696
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value))
|
|
697
|
+
return false;
|
|
698
|
+
const json = value;
|
|
699
|
+
if (json['type'] === 'object')
|
|
700
|
+
return true;
|
|
701
|
+
for (const key of ['anyOf', 'oneOf']) {
|
|
702
|
+
const branches = json[key];
|
|
703
|
+
if (Array.isArray(branches) && branches.length > 0)
|
|
704
|
+
return branches.every(isObjectOutput);
|
|
705
|
+
}
|
|
706
|
+
return false;
|
|
707
|
+
};
|
|
708
|
+
const json = settleNow(() => rawOutputSchema.toJSONSchema());
|
|
709
|
+
if (json.ok && isObjectOutput(json.value))
|
|
710
|
+
schema = rawOutputSchema;
|
|
797
711
|
normalizableOutputSchemas.set(rawOutputSchema, { schema });
|
|
798
712
|
return schema;
|
|
799
713
|
}
|
|
@@ -823,13 +737,16 @@ function registerTool(server, descriptor, runtime, options, logger, localToolIds
|
|
|
823
737
|
// safeParseAsync(undefined, ...) which throws "undefined is not an object (evaluating 's._zod')".
|
|
824
738
|
// Skip output schemas that are plain unions or discriminated unions to avoid this SDK bug.
|
|
825
739
|
const rawOutputSchema = descriptor.schemas.output === undefined ? undefined : requireToolZodSchema(descriptor.schemas.output, descriptor.id);
|
|
826
|
-
const outputSchema = rawOutputSchema ? normalizableOutputSchema(rawOutputSchema) : undefined;
|
|
827
|
-
// Derive MCP annotations from safety field
|
|
828
|
-
const annotations = deriveMcpAnnotations(descriptor.safety);
|
|
829
740
|
// Trust-tag tier is derived from `safety` until `descriptor.capability.tier`
|
|
830
741
|
// lands in the tier-spec PR (AI-SURFACE-2026-05.md §4.3 step 1). Captured
|
|
831
742
|
// once at registration so it composes with cache-stable system prompts.
|
|
832
743
|
const trustTier = deriveTrustTier(descriptor);
|
|
744
|
+
// A tier-3 result deliberately carries no structuredContent. Advertising
|
|
745
|
+
// an output schema would make a modern client demand content that this
|
|
746
|
+
// surface never emits, so the schema is not advertised at all.
|
|
747
|
+
const outputSchema = rawOutputSchema && !shouldDropFromModelContext(trustTier)
|
|
748
|
+
? normalizableOutputSchema(rawOutputSchema)
|
|
749
|
+
: undefined;
|
|
833
750
|
const toolAccessor = runtime.assets('tools/tool');
|
|
834
751
|
const isDiscriminated = toolAccessor.isDiscriminated(descriptor.id);
|
|
835
752
|
let variantSafety;
|
|
@@ -844,12 +761,25 @@ function registerTool(server, descriptor, runtime, options, logger, localToolIds
|
|
|
844
761
|
}
|
|
845
762
|
}
|
|
846
763
|
}
|
|
764
|
+
// MCP annotations are the conservative union of the top-level safety and
|
|
765
|
+
// every per-action/variant safety. A write action can never be advertised
|
|
766
|
+
// as read-only just because the tool's default action is a read.
|
|
767
|
+
const annotationSource = {
|
|
768
|
+
safety: descriptor.safety,
|
|
769
|
+
permissions: descriptor.permissions
|
|
770
|
+
};
|
|
771
|
+
if (descriptor.schemas.actionSafety)
|
|
772
|
+
annotationSource.actionSafety = descriptor.schemas.actionSafety;
|
|
773
|
+
if (variantSafety)
|
|
774
|
+
annotationSource.variantSafety = Object.values(variantSafety);
|
|
775
|
+
const annotations = deriveMcpAnnotations(annotationSource);
|
|
847
776
|
const isLocal = isLocalToolId(descriptor.id, localToolIds);
|
|
848
777
|
const origin = vibesToolOrigin(isLocal);
|
|
849
778
|
const description = formatLocalMcpToolDescription(getDescriptorMcpDescription(descriptor), isLocal);
|
|
850
779
|
const dispatchRuntime = runtime;
|
|
851
780
|
const vibesRegistrationMeta = {
|
|
852
781
|
origin,
|
|
782
|
+
toolId: descriptor.id,
|
|
853
783
|
category: descriptor.category,
|
|
854
784
|
subcategory: descriptor.subcategory,
|
|
855
785
|
tags: descriptor.tags,
|
|
@@ -857,6 +787,8 @@ function registerTool(server, descriptor, runtime, options, logger, localToolIds
|
|
|
857
787
|
safety: descriptor.safety,
|
|
858
788
|
mcpClientId: options.mcpClientId
|
|
859
789
|
};
|
|
790
|
+
if (descriptor.schemas.actionSafety)
|
|
791
|
+
vibesRegistrationMeta.actionSafety = descriptor.schemas.actionSafety;
|
|
860
792
|
if (variantSafety)
|
|
861
793
|
vibesRegistrationMeta.variants = variantSafety;
|
|
862
794
|
const registrationOptions = {
|
|
@@ -867,7 +799,7 @@ function registerTool(server, descriptor, runtime, options, logger, localToolIds
|
|
|
867
799
|
annotations,
|
|
868
800
|
_meta: { vibes: vibesRegistrationMeta }
|
|
869
801
|
};
|
|
870
|
-
const handler = async (args,
|
|
802
|
+
const handler = async (args, ctx, boundInvocation) => {
|
|
871
803
|
const callId = `mcp_${Date.now()}_${crypto.randomUUID()}`;
|
|
872
804
|
// Per-call nonce for trust-tag wrapper (F4). Shared across all text items
|
|
873
805
|
// and error paths in this tool call so the model can verify boundary integrity.
|
|
@@ -879,7 +811,7 @@ function registerTool(server, descriptor, runtime, options, logger, localToolIds
|
|
|
879
811
|
const stalePrefix = options.staleBodyBanner?.() ?? null;
|
|
880
812
|
let toolCallCtx;
|
|
881
813
|
const callStarted = Date.now();
|
|
882
|
-
const traceStage = (stage) => logger.
|
|
814
|
+
const traceStage = (stage) => logger.debug('MCP invocation stage', { callId, tool: descriptor.id, stage, elapsedMs: Date.now() - callStarted });
|
|
883
815
|
traceStage('start');
|
|
884
816
|
const invocation = boundInvocation?.retain() ?? dispatchRuntime.acquireInvocation();
|
|
885
817
|
const runtime = invocation.runtime;
|
|
@@ -910,15 +842,14 @@ function registerTool(server, descriptor, runtime, options, logger, localToolIds
|
|
|
910
842
|
resolvedArgs = { [discriminator]: defaultAction, ...resolvedArgs };
|
|
911
843
|
}
|
|
912
844
|
}
|
|
913
|
-
//
|
|
914
|
-
//
|
|
915
|
-
//
|
|
916
|
-
|
|
917
|
-
const transportSessionId = extra.sessionId ?? options.resolveTransportSessionId?.();
|
|
845
|
+
// A 2025-era transport session is call data, never mutable
|
|
846
|
+
// process-global runtime scope. A 2026-era request carries its
|
|
847
|
+
// explicit session handle in the tool input instead.
|
|
848
|
+
const transportSessionId = ctx.sessionId ?? options.resolveTransportSessionId?.();
|
|
918
849
|
// A session-family call may name the participant it continues as
|
|
919
850
|
// (the documented bind flow). That is a binding hint the session layer
|
|
920
851
|
// verifies, not identity: agent identity comes only from the connection.
|
|
921
|
-
const callMeta =
|
|
852
|
+
const callMeta = asJsonObject(ctx.mcpReq._meta);
|
|
922
853
|
const metaParticipantId = (isStringValue(callMeta?.participantId) && callMeta.participantId) ||
|
|
923
854
|
(isStringValue(callMeta?.['vibes/participantId']) &&
|
|
924
855
|
callMeta['vibes/participantId']) ||
|
|
@@ -929,15 +860,22 @@ function registerTool(server, descriptor, runtime, options, logger, localToolIds
|
|
|
929
860
|
callId,
|
|
930
861
|
logger: logger.child(callDescriptor.id)
|
|
931
862
|
};
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
if (
|
|
863
|
+
baseToolCallCtx.signal = ctx.mcpReq.signal;
|
|
864
|
+
const progressToken = ctx.mcpReq._meta?.progressToken;
|
|
865
|
+
if (progressToken !== undefined) {
|
|
935
866
|
baseToolCallCtx.sendProgress = (progress) => {
|
|
936
|
-
const update = {
|
|
867
|
+
const update = {
|
|
868
|
+
progressToken
|
|
869
|
+
};
|
|
870
|
+
if (progress.progress !== undefined)
|
|
871
|
+
update.progress = progress.progress;
|
|
872
|
+
if (progress.total !== undefined)
|
|
873
|
+
update.total = progress.total;
|
|
937
874
|
if (progress.message)
|
|
938
875
|
update.message = progress.message;
|
|
939
|
-
|
|
876
|
+
void ctx.mcpReq.notify({ method: 'notifications/progress', params: update });
|
|
940
877
|
};
|
|
878
|
+
}
|
|
941
879
|
// Stamp trusted identity from the runtime's `auth/identity` (the same
|
|
942
880
|
// builder the tools-cli surface uses). The MCP stdio surface runs as an
|
|
943
881
|
// authenticated local account; without this, owner-scoped tools
|
|
@@ -951,33 +889,42 @@ function registerTool(server, descriptor, runtime, options, logger, localToolIds
|
|
|
951
889
|
: { ...directContext.caller };
|
|
952
890
|
// Admission (RC08 U7): identity only from what this connection's
|
|
953
891
|
// transport verified, before the hook bus or any observer runs.
|
|
892
|
+
const verifiedConnection = options.resolveVerifiedConnectionIdentity?.() ?? options.verifiedConnectionIdentity;
|
|
893
|
+
if ((verifiedConnection?.source === STDIO_LAUNCH_SOURCE ||
|
|
894
|
+
verifiedConnection?.source === RELAY_ATTESTATION_SOURCE) &&
|
|
895
|
+
typeof directContext.metadata?.actorUserId === 'string' &&
|
|
896
|
+
!directContext.metadata.orgId &&
|
|
897
|
+
runtime.hasContext('orgId')) {
|
|
898
|
+
const actorUserId = directContext.metadata.actorUserId;
|
|
899
|
+
const resolvedOrg = await settle(() => runtime.runInvocation(() => runtime.context('orgId'), {
|
|
900
|
+
actor: { userId: actorUserId }
|
|
901
|
+
}));
|
|
902
|
+
if (resolvedOrg.ok && typeof resolvedOrg.value === 'string' && resolvedOrg.value.trim().length > 0) {
|
|
903
|
+
const orgId = resolvedOrg.value.trim();
|
|
904
|
+
directContext.metadata.orgId = orgId;
|
|
905
|
+
directContext.metadata.organizationId = orgId;
|
|
906
|
+
}
|
|
907
|
+
}
|
|
954
908
|
const admittedCaller = admitConnectionCaller({
|
|
955
|
-
verified:
|
|
909
|
+
verified: verifiedConnection,
|
|
956
910
|
declared: options.resolveConnectionIdentity?.(),
|
|
957
911
|
call: caller,
|
|
958
912
|
principal: directContext.metadata
|
|
959
913
|
});
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
mcpClientId: options.mcpClientId,
|
|
964
|
-
preset: options.preset
|
|
965
|
-
}, runtime);
|
|
966
|
-
const agentContext = {
|
|
967
|
-
agentId: admittedCaller?.agentId ?? resolvedCeiling.profile ?? 'mcp-client',
|
|
968
|
-
ceiling: resolvedCeiling.ceiling,
|
|
969
|
-
callChain: []
|
|
970
|
-
};
|
|
914
|
+
// A harness is a transport, not a role. The call carries the
|
|
915
|
+
// direct consumer surface identity; the tools owner's policy is the
|
|
916
|
+
// only place that decides whether a tool is in bounds.
|
|
971
917
|
const metadata = {
|
|
972
918
|
...directContext.metadata,
|
|
973
|
-
'mcp.transportMode': options.mode
|
|
974
|
-
[TOOL_AGENT_CONTEXT_METADATA_KEY]: agentContext,
|
|
975
|
-
agentCapabilityCeiling: resolvedCeiling.ceiling
|
|
919
|
+
'mcp.transportMode': options.mode
|
|
976
920
|
};
|
|
977
921
|
if (transportSessionId)
|
|
978
922
|
metadata['mcp.transportSessionId'] = transportSessionId;
|
|
979
923
|
if (connectionKey)
|
|
980
924
|
metadata['mcp.connectionKey'] = connectionKey;
|
|
925
|
+
// The 2026 HTTP request owns a fresh server instance; explicit
|
|
926
|
+
// session handles, not an ambient binding, are its continuity.
|
|
927
|
+
metadata['mcp.protocolEra'] = ctx.mcpReq.envelope !== undefined ? 'modern' : 'legacy';
|
|
981
928
|
const directToolCallCtx = {
|
|
982
929
|
...directContext,
|
|
983
930
|
caller: admittedCaller,
|
|
@@ -994,15 +941,14 @@ function registerTool(server, descriptor, runtime, options, logger, localToolIds
|
|
|
994
941
|
: directToolCallCtx;
|
|
995
942
|
traceStage('stamp-session');
|
|
996
943
|
const sessionStampedCtx = await stampMcpSessionCaller(runtime, server, preparedToolCallCtx, transportSessionId ?? connectionKey, options.mode, callDescriptor.id, resolvedArgs, logger);
|
|
997
|
-
const stampedToolCallCtx =
|
|
998
|
-
...sessionStampedCtx,
|
|
999
|
-
metadata: {
|
|
1000
|
-
...sessionStampedCtx.metadata,
|
|
1001
|
-
[TOOL_AGENT_CONTEXT_METADATA_KEY]: agentContext,
|
|
1002
|
-
agentCapabilityCeiling: resolvedCeiling.ceiling
|
|
1003
|
-
}
|
|
1004
|
-
};
|
|
944
|
+
const stampedToolCallCtx = sessionStampedCtx;
|
|
1005
945
|
toolCallCtx = stampedToolCallCtx;
|
|
946
|
+
// The tool owner declares the approval contract; this surface carries
|
|
947
|
+
// the question to the connected client and fails closed when it cannot.
|
|
948
|
+
// Written in the multi-round-trip form so 2026 and 2025 share it.
|
|
949
|
+
const approval = await requireMcpToolApproval(ctx, callDescriptor, resolvedArgs);
|
|
950
|
+
if (approval)
|
|
951
|
+
return approval;
|
|
1006
952
|
// Route through invokeTool so tools/tool hook bus events
|
|
1007
953
|
// (contextBuild / beforeExecute / afterExecute / error) fire
|
|
1008
954
|
// around execution. invokeTool handles discriminated dispatch
|
|
@@ -1014,21 +960,27 @@ function registerTool(server, descriptor, runtime, options, logger, localToolIds
|
|
|
1014
960
|
if (options.toolExecutionTimeoutMs !== undefined && options.toolExecutionTimeoutMs > 0) {
|
|
1015
961
|
execution.timeoutMs = options.toolExecutionTimeoutMs;
|
|
1016
962
|
}
|
|
1017
|
-
const
|
|
1018
|
-
|
|
1019
|
-
.
|
|
1020
|
-
|
|
1021
|
-
event.metadata.set(TOOL_AGENT_CONTEXT_METADATA_KEY, agentContext);
|
|
1022
|
-
}
|
|
1023
|
-
});
|
|
1024
|
-
const rawOutput = await (async () => {
|
|
1025
|
-
try {
|
|
1026
|
-
return await runWithMcpSamplingCallback(options.scopedValues, (request) => server.server.request({ method: 'sampling/createMessage', params: request }, CreateMessageResultSchema, { relatedRequestId: extra.requestId }), () => invokeTool(execution));
|
|
963
|
+
const principal = verifiedConnectionPrincipal({ verified: verifiedConnection, metadata: directContext.metadata });
|
|
964
|
+
const rawOutput = await runWithMcpSamplingCallback(options.scopedValues, (request) => {
|
|
965
|
+
if (options.sampling === 'off') {
|
|
966
|
+
throw new CapabilityError('MCP sampling is disabled by configuration.', 'UNAVAILABLE', { reason: 'sampling-disabled', retryable: false });
|
|
1027
967
|
}
|
|
1028
|
-
|
|
1029
|
-
|
|
968
|
+
// 2026-07-28 removed the server→client request channel.
|
|
969
|
+
// Sampling is deprecated in that revision; a tool that
|
|
970
|
+
// needs a model call must use its own provider rather
|
|
971
|
+
// than hang on a channel the revision does not carry.
|
|
972
|
+
if (ctx.mcpReq.envelope !== undefined) {
|
|
973
|
+
throw new CapabilityError('MCP sampling is unavailable on protocol revision 2026-07-28; the tool must call its own model provider.', 'UNAVAILABLE', { reason: 'sampling-unavailable-modern', retryable: false });
|
|
1030
974
|
}
|
|
1031
|
-
|
|
975
|
+
return ctx.mcpReq.requestSampling(request,
|
|
976
|
+
// Keep the server-to-client request on this call's
|
|
977
|
+
// response stream: a relayed stdio body reads that
|
|
978
|
+
// stream, not a standalone SSE channel.
|
|
979
|
+
{ relatedRequestId: ctx.mcpReq.id });
|
|
980
|
+
},
|
|
981
|
+
// A verified connection is this host's own account: the call runs as
|
|
982
|
+
// its request principal, so caller-bearer clients see the login.
|
|
983
|
+
() => principal ? runtime.runInvocation(() => invokeTool(execution), { principal }) : invokeTool(execution));
|
|
1032
984
|
traceStage('result');
|
|
1033
985
|
const outputSchema = callDescriptor.schemas.output;
|
|
1034
986
|
const lifted = liftAmbientEnvelope(rawOutput, isZodLike(outputSchema) ? outputSchema : undefined);
|
|
@@ -1071,6 +1023,8 @@ function registerTool(server, descriptor, runtime, options, logger, localToolIds
|
|
|
1071
1023
|
...mcpResponse,
|
|
1072
1024
|
content: wrappedContent
|
|
1073
1025
|
};
|
|
1026
|
+
if (shouldDropFromModelContext(trustTier))
|
|
1027
|
+
delete result.structuredContent;
|
|
1074
1028
|
if (envelopeMeta)
|
|
1075
1029
|
result._meta = { ...mcpResponse._meta, ...envelopeMeta };
|
|
1076
1030
|
return result;
|
|
@@ -1124,7 +1078,9 @@ function registerTool(server, descriptor, runtime, options, logger, localToolIds
|
|
|
1124
1078
|
}
|
|
1125
1079
|
const result = { content };
|
|
1126
1080
|
// Plain object outputs retain typed data without requiring clients to parse text.
|
|
1127
|
-
|
|
1081
|
+
// A tier-3 result is dropped from model context in BOTH text and
|
|
1082
|
+
// structured form; leaving structuredContent would leak the payload.
|
|
1083
|
+
if (wired.structured && !shouldDropFromModelContext(trustTier))
|
|
1128
1084
|
result.structuredContent = wired.structured;
|
|
1129
1085
|
if (envelopeMeta)
|
|
1130
1086
|
result._meta = envelopeMeta;
|
|
@@ -1316,6 +1272,16 @@ async function stampMcpSessionCaller(runtime, server, ctx, transportSessionId, m
|
|
|
1316
1272
|
});
|
|
1317
1273
|
}
|
|
1318
1274
|
const isSessionFamilyTool = toolId === 'session' || toolId.startsWith('session-');
|
|
1275
|
+
// A session-family call that names its durable target does not need an
|
|
1276
|
+
// ambient transport binding. This is the normal shape on a 2026 HTTP
|
|
1277
|
+
// request (one fresh server per request) and on a 2025 client that has
|
|
1278
|
+
// not run `session init` on this connection yet.
|
|
1279
|
+
const hasExplicitSessionTarget = mode === 'http' &&
|
|
1280
|
+
ctx.metadata?.['mcp.protocolEra'] === 'modern' &&
|
|
1281
|
+
isSessionFamilyTool &&
|
|
1282
|
+
((typeof input['sessionId'] === 'string' && input['sessionId'].trim().length > 0) ||
|
|
1283
|
+
['todoId', 'messageId'].some((key) => typeof input[key] === 'string' && input[key].trim().length > 0) ||
|
|
1284
|
+
['sessionIds', 'todoIds', 'messageIds'].some((key) => Array.isArray(input[key]) && input[key].length > 0));
|
|
1319
1285
|
const { participantId: _hintParticipant, sessionId: _hintSession, ...unboundCaller } = ctx.caller ?? {};
|
|
1320
1286
|
if (!runtime.hasContext('session/manager')) {
|
|
1321
1287
|
return { ...ctx, caller: unboundCaller };
|
|
@@ -1377,7 +1343,7 @@ async function stampMcpSessionCaller(runtime, server, ctx, transportSessionId, m
|
|
|
1377
1343
|
// participant identity, but without borrowing the closed session's custody.
|
|
1378
1344
|
const explicitResume = toolId === 'session' && explicitResumeInputSchema.safeParse(input).success;
|
|
1379
1345
|
if (!getNeedsId &&
|
|
1380
|
-
(explicitResume || sessionUtils?.isSessionDiscoveryCall(toolId, input.action) ||
|
|
1346
|
+
(hasExplicitSessionTarget || explicitResume || sessionUtils?.isSessionDiscoveryCall(toolId, input.action) ||
|
|
1381
1347
|
(!sessionUtils &&
|
|
1382
1348
|
toolId === 'session' &&
|
|
1383
1349
|
(input.action === 'init' || input.action === 'status')))) {
|