@stigmer/runner 3.9.0 → 3.11.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/README.md +12 -1
- package/dist/.build-fingerprint +1 -1
- package/dist/activities/call-llm.js +9 -10
- package/dist/activities/call-llm.js.map +1 -1
- package/dist/activities/classify-tool-approvals.d.ts +2 -1
- package/dist/activities/classify-tool-approvals.js +28 -2
- package/dist/activities/classify-tool-approvals.js.map +1 -1
- package/dist/activities/discover-mcp-server.d.ts +32 -0
- package/dist/activities/discover-mcp-server.js +162 -27
- package/dist/activities/discover-mcp-server.js.map +1 -1
- package/dist/activities/execute-cursor/__test-utils__/cursor-hook-harness.d.ts +8 -0
- package/dist/activities/execute-cursor/__test-utils__/cursor-hook-harness.js +1 -1
- package/dist/activities/execute-cursor/__test-utils__/cursor-hook-harness.js.map +1 -1
- package/dist/activities/execute-cursor/approval-state.d.ts +28 -2
- package/dist/activities/execute-cursor/approval-state.js +7 -1
- package/dist/activities/execute-cursor/approval-state.js.map +1 -1
- package/dist/activities/execute-cursor/attachment-resolver.d.ts +14 -0
- package/dist/activities/execute-cursor/attachment-resolver.js +25 -4
- package/dist/activities/execute-cursor/attachment-resolver.js.map +1 -1
- package/dist/activities/execute-cursor/blueprint-resolver.d.ts +1 -9
- package/dist/activities/execute-cursor/blueprint-resolver.js +6 -22
- package/dist/activities/execute-cursor/blueprint-resolver.js.map +1 -1
- package/dist/activities/execute-cursor/env-resolver.js +3 -1
- package/dist/activities/execute-cursor/env-resolver.js.map +1 -1
- package/dist/activities/execute-cursor/error-classifier.d.ts +40 -3
- package/dist/activities/execute-cursor/error-classifier.js +81 -3
- package/dist/activities/execute-cursor/error-classifier.js.map +1 -1
- package/dist/activities/execute-cursor/extract-structured-output.d.ts +29 -0
- package/dist/activities/execute-cursor/extract-structured-output.js +58 -0
- package/dist/activities/execute-cursor/extract-structured-output.js.map +1 -0
- package/dist/activities/execute-cursor/hook-script.d.ts +14 -3
- package/dist/activities/execute-cursor/hook-script.js +72 -10
- package/dist/activities/execute-cursor/hook-script.js.map +1 -1
- package/dist/activities/execute-cursor/index.d.ts +5 -1
- package/dist/activities/execute-cursor/index.js +59 -58
- package/dist/activities/execute-cursor/index.js.map +1 -1
- package/dist/activities/execute-cursor/mcp-resolver.d.ts +24 -1
- package/dist/activities/execute-cursor/mcp-resolver.js +5 -2
- package/dist/activities/execute-cursor/mcp-resolver.js.map +1 -1
- package/dist/activities/execute-cursor/prompt-builder.d.ts +18 -4
- package/dist/activities/execute-cursor/prompt-builder.js +12 -7
- package/dist/activities/execute-cursor/prompt-builder.js.map +1 -1
- package/dist/activities/execute-cursor/turn-stream.js +4 -1
- package/dist/activities/execute-cursor/turn-stream.js.map +1 -1
- package/dist/activities/execute-deep-agent/attachment-injector.d.ts +18 -1
- package/dist/activities/execute-deep-agent/attachment-injector.js +68 -23
- package/dist/activities/execute-deep-agent/attachment-injector.js.map +1 -1
- package/dist/activities/execute-deep-agent/environment.js +3 -1
- package/dist/activities/execute-deep-agent/environment.js.map +1 -1
- package/dist/activities/execute-deep-agent/index.js +15 -0
- package/dist/activities/execute-deep-agent/index.js.map +1 -1
- package/dist/activities/execute-deep-agent/prompt-builder.d.ts +7 -7
- package/dist/activities/execute-deep-agent/prompt-builder.js +8 -2
- package/dist/activities/execute-deep-agent/prompt-builder.js.map +1 -1
- package/dist/activities/execute-deep-agent/setup.d.ts +10 -0
- package/dist/activities/execute-deep-agent/setup.js +65 -26
- package/dist/activities/execute-deep-agent/setup.js.map +1 -1
- package/dist/activities/execute-deep-agent/subagent-transformer.d.ts +18 -1
- package/dist/activities/execute-deep-agent/subagent-transformer.js +8 -1
- package/dist/activities/execute-deep-agent/subagent-transformer.js.map +1 -1
- package/dist/activities/execute-deep-agent/subagent-wiring.d.ts +11 -4
- package/dist/activities/execute-deep-agent/subagent-wiring.js +13 -4
- package/dist/activities/execute-deep-agent/subagent-wiring.js.map +1 -1
- package/dist/activities/hydrate-workflow-execution.js +3 -1
- package/dist/activities/hydrate-workflow-execution.js.map +1 -1
- package/dist/activities/workflow-event-activities.d.ts +28 -10
- package/dist/activities/workflow-event-activities.js +87 -58
- package/dist/activities/workflow-event-activities.js.map +1 -1
- package/dist/claimcheck/payload-codec.js +21 -1
- package/dist/claimcheck/payload-codec.js.map +1 -1
- package/dist/client/stigmer-client.d.ts +9 -4
- package/dist/client/stigmer-client.js +28 -15
- package/dist/client/stigmer-client.js.map +1 -1
- package/dist/encryption/config.d.ts +32 -0
- package/dist/encryption/config.js +68 -0
- package/dist/encryption/config.js.map +1 -0
- package/dist/encryption/index.d.ts +3 -0
- package/dist/encryption/index.js +3 -0
- package/dist/encryption/index.js.map +1 -0
- package/dist/encryption/payload-codec.d.ts +41 -0
- package/dist/encryption/payload-codec.js +130 -0
- package/dist/encryption/payload-codec.js.map +1 -0
- package/dist/payload-codecs.d.ts +16 -0
- package/dist/payload-codecs.js +38 -0
- package/dist/payload-codecs.js.map +1 -0
- package/dist/preflight.d.ts +31 -0
- package/dist/preflight.js +43 -0
- package/dist/preflight.js.map +1 -1
- package/dist/runner-manager.js +5 -15
- package/dist/runner-manager.js.map +1 -1
- package/dist/runner.js +5 -16
- package/dist/runner.js.map +1 -1
- package/dist/shared/approval-policy.d.ts +9 -3
- package/dist/shared/approval-policy.js +15 -6
- package/dist/shared/approval-policy.js.map +1 -1
- package/dist/shared/attachment-naming.d.ts +53 -0
- package/dist/shared/attachment-naming.js +59 -0
- package/dist/shared/attachment-naming.js.map +1 -0
- package/dist/shared/attachment-vision.d.ts +43 -2
- package/dist/shared/attachment-vision.js +72 -6
- package/dist/shared/attachment-vision.js.map +1 -1
- package/dist/shared/caller-identity.d.ts +23 -2
- package/dist/shared/caller-identity.js +36 -5
- package/dist/shared/caller-identity.js.map +1 -1
- package/dist/shared/channel-attachment.js +1 -0
- package/dist/shared/channel-attachment.js.map +1 -1
- package/dist/shared/checkpointer/http-saver.d.ts +42 -1
- package/dist/shared/checkpointer/http-saver.js +96 -8
- package/dist/shared/checkpointer/http-saver.js.map +1 -1
- package/dist/shared/conversation-attachment.js +1 -0
- package/dist/shared/conversation-attachment.js.map +1 -1
- package/dist/shared/datastore-attachment.d.ts +50 -7
- package/dist/shared/datastore-attachment.js +93 -11
- package/dist/shared/datastore-attachment.js.map +1 -1
- package/dist/shared/http-retry.d.ts +43 -0
- package/dist/shared/http-retry.js +50 -0
- package/dist/shared/http-retry.js.map +1 -0
- package/dist/shared/llm-backend.d.ts +275 -0
- package/dist/shared/llm-backend.js +425 -0
- package/dist/shared/llm-backend.js.map +1 -0
- package/dist/shared/llm-proxy.d.ts +8 -0
- package/dist/shared/llm-proxy.js +15 -0
- package/dist/shared/llm-proxy.js.map +1 -1
- package/dist/shared/mcp-enabled-tools.d.ts +57 -0
- package/dist/shared/mcp-enabled-tools.js +86 -0
- package/dist/shared/mcp-enabled-tools.js.map +1 -0
- package/dist/shared/mcp-manager.d.ts +17 -2
- package/dist/shared/mcp-manager.js +37 -25
- package/dist/shared/mcp-manager.js.map +1 -1
- package/dist/shared/mcp-resolver.d.ts +39 -2
- package/dist/shared/mcp-resolver.js +38 -2
- package/dist/shared/mcp-resolver.js.map +1 -1
- package/dist/shared/model-client.d.ts +12 -5
- package/dist/shared/model-client.js +138 -18
- package/dist/shared/model-client.js.map +1 -1
- package/dist/shared/model-error.js +198 -5
- package/dist/shared/model-error.js.map +1 -1
- package/dist/shared/model-registry.d.ts +20 -2
- package/dist/shared/model-registry.js +37 -2
- package/dist/shared/model-registry.js.map +1 -1
- package/dist/shared/plan-mode-permissions.d.ts +26 -0
- package/dist/shared/plan-mode-permissions.js +28 -0
- package/dist/shared/plan-mode-permissions.js.map +1 -0
- package/dist/worker.d.ts +2 -1
- package/dist/worker.js +2 -4
- package/dist/worker.js.map +1 -1
- package/dist/workflow-engine/types.d.ts +18 -0
- package/dist/workflow-engine/types.js.map +1 -1
- package/dist/workflows/call-agent-orchestrator.d.ts +9 -0
- package/dist/workflows/call-agent-orchestrator.js +1 -0
- package/dist/workflows/call-agent-orchestrator.js.map +1 -1
- package/dist/workflows/connect-mcp-server.js +7 -0
- package/dist/workflows/connect-mcp-server.js.map +1 -1
- package/dist/workflows/engine-core.js +23 -2
- package/dist/workflows/engine-core.js.map +1 -1
- package/dist/workflows/execute-from-execution.d.ts +1 -1
- package/dist/workflows/execute-from-execution.js +11 -1
- package/dist/workflows/execute-from-execution.js.map +1 -1
- package/package.json +8 -2
- package/src/__tests__/claimcheck-codec.test.ts +36 -0
- package/src/__tests__/encryption-codec.test.ts +234 -0
- package/src/__tests__/fixtures/encrypted-payload-fixture.json +15 -0
- package/src/__tests__/history-encryption-e2e.test.ts +243 -0
- package/src/__tests__/preflight.test.ts +50 -2
- package/src/activities/__tests__/call-llm.test.ts +75 -0
- package/src/activities/__tests__/classify-tool-approvals.test.ts +117 -1
- package/src/activities/__tests__/discover-mcp-server.hang.test.ts +103 -0
- package/src/activities/__tests__/discover-mcp-server.test.ts +203 -0
- package/src/activities/__tests__/workflow-event-activities.test.ts +107 -8
- package/src/activities/call-llm.ts +9 -16
- package/src/activities/classify-tool-approvals.ts +34 -4
- package/src/activities/discover-mcp-server.ts +190 -32
- package/src/activities/execute-cursor/__test-utils__/cursor-hook-harness.ts +9 -0
- package/src/activities/execute-cursor/__tests__/approval-gate.test.ts +14 -0
- package/src/activities/execute-cursor/__tests__/attachment-resolver.test.ts +92 -0
- package/src/activities/execute-cursor/__tests__/build-prompt.test.ts +40 -14
- package/src/activities/execute-cursor/__tests__/error-classifier-extraction.test.ts +208 -0
- package/src/activities/execute-cursor/__tests__/extract-structured-output.test.ts +120 -0
- package/src/activities/execute-cursor/__tests__/hook-script.test.ts +93 -0
- package/src/activities/execute-cursor/__tests__/mcp-resolver.test.ts +125 -0
- package/src/activities/execute-cursor/__tests__/prompt-builder-delegation.test.ts +1 -1
- package/src/activities/execute-cursor/__tests__/turn-stream.test.ts +13 -0
- package/src/activities/execute-cursor/approval-state.ts +30 -1
- package/src/activities/execute-cursor/attachment-resolver.ts +38 -3
- package/src/activities/execute-cursor/blueprint-resolver.ts +7 -27
- package/src/activities/execute-cursor/env-resolver.ts +3 -1
- package/src/activities/execute-cursor/error-classifier.ts +91 -4
- package/src/activities/execute-cursor/extract-structured-output.ts +72 -0
- package/src/activities/execute-cursor/hook-script.ts +74 -10
- package/src/activities/execute-cursor/index.ts +63 -72
- package/src/activities/execute-cursor/mcp-resolver.ts +36 -2
- package/src/activities/execute-cursor/prompt-builder.ts +34 -9
- package/src/activities/execute-cursor/turn-stream.ts +5 -2
- package/src/activities/execute-deep-agent/__tests__/attachment-injector.test.ts +132 -8
- package/src/activities/execute-deep-agent/__tests__/datastore-degradation.test.ts +104 -0
- package/src/activities/execute-deep-agent/__tests__/hitl-reject.test.ts +2 -0
- package/src/activities/execute-deep-agent/__tests__/hitl-resume-approve-all.test.ts +1 -0
- package/src/activities/execute-deep-agent/__tests__/hitl-resume-history.test.ts +1 -0
- package/src/activities/execute-deep-agent/__tests__/prompt-builder.test.ts +34 -5
- package/src/activities/execute-deep-agent/__tests__/sequential-gate-resume.test.ts +1 -0
- package/src/activities/execute-deep-agent/__tests__/subagent-plan-mode-permissions.test.ts +173 -0
- package/src/activities/execute-deep-agent/__tests__/subagent-wiring.test.ts +12 -7
- package/src/activities/execute-deep-agent/attachment-injector.ts +94 -30
- package/src/activities/execute-deep-agent/environment.ts +3 -1
- package/src/activities/execute-deep-agent/index.ts +20 -0
- package/src/activities/execute-deep-agent/prompt-builder.ts +20 -10
- package/src/activities/execute-deep-agent/setup.ts +86 -31
- package/src/activities/execute-deep-agent/subagent-transformer.ts +23 -1
- package/src/activities/execute-deep-agent/subagent-wiring.ts +14 -4
- package/src/activities/hydrate-workflow-execution.ts +3 -1
- package/src/activities/workflow-event-activities.ts +96 -69
- package/src/claimcheck/payload-codec.ts +33 -1
- package/src/client/__tests__/stigmer-client.test.ts +8 -8
- package/src/client/stigmer-client.ts +32 -18
- package/src/encryption/config.ts +91 -0
- package/src/encryption/index.ts +3 -0
- package/src/encryption/payload-codec.ts +152 -0
- package/src/payload-codecs.ts +56 -0
- package/src/preflight.ts +45 -0
- package/src/runner-manager.ts +6 -24
- package/src/runner.ts +6 -25
- package/src/shared/__tests__/approval-policy.test.ts +82 -39
- package/src/shared/__tests__/attachment-naming.test.ts +159 -0
- package/src/shared/__tests__/attachment-vision.test.ts +97 -0
- package/src/shared/__tests__/bedrock-adapter.test.ts +213 -0
- package/src/shared/__tests__/bedrock-seam.test.ts +390 -0
- package/src/shared/__tests__/caller-identity.test.ts +25 -0
- package/src/shared/__tests__/channel-attachment.test.ts +1 -1
- package/src/shared/__tests__/connect-backfill.test.ts +1 -0
- package/src/shared/__tests__/conversation-attachment.test.ts +1 -1
- package/src/shared/__tests__/datastore-attachment.test.ts +129 -1
- package/src/shared/__tests__/foundry-adapter.test.ts +276 -0
- package/src/shared/__tests__/foundry-seam.test.ts +482 -0
- package/src/shared/__tests__/http-retry.test.ts +67 -0
- package/src/shared/__tests__/llm-backend.test.ts +616 -0
- package/src/shared/__tests__/mcp-enabled-tools.test.ts +86 -0
- package/src/shared/__tests__/mcp-manager.test.ts +170 -2
- package/src/shared/__tests__/mcp-resolver.test.ts +146 -3
- package/src/shared/__tests__/model-client.test.ts +154 -0
- package/src/shared/__tests__/model-error.test.ts +289 -1
- package/src/shared/__tests__/model-registry.test.ts +71 -0
- package/src/shared/__tests__/synthesized-attachment.test.ts +1 -0
- package/src/shared/__tests__/vertex-adapter.test.ts +169 -0
- package/src/shared/__tests__/vertex-seam.test.ts +295 -0
- package/src/shared/approval-policy.ts +14 -7
- package/src/shared/attachment-naming.ts +78 -0
- package/src/shared/attachment-vision.ts +92 -9
- package/src/shared/caller-identity.ts +40 -5
- package/src/shared/channel-attachment.ts +1 -0
- package/src/shared/checkpointer/__tests__/http-saver.test.ts +196 -1
- package/src/shared/checkpointer/http-saver.ts +117 -9
- package/src/shared/conversation-attachment.ts +1 -0
- package/src/shared/datastore-attachment.ts +106 -11
- package/src/shared/http-retry.ts +50 -0
- package/src/shared/llm-backend.ts +544 -0
- package/src/shared/llm-proxy.ts +15 -0
- package/src/shared/mcp-enabled-tools.ts +105 -0
- package/src/shared/mcp-manager.ts +43 -26
- package/src/shared/mcp-resolver.ts +73 -2
- package/src/shared/model-client.ts +161 -19
- package/src/shared/model-error.ts +222 -4
- package/src/shared/model-registry.ts +50 -2
- package/src/shared/plan-mode-permissions.ts +30 -0
- package/src/worker.ts +4 -5
- package/src/workflow-engine/types.ts +18 -0
- package/src/workflows/__tests__/execute-serverless-workflow.test.ts +68 -2
- package/src/workflows/call-agent-orchestrator.ts +10 -0
- package/src/workflows/connect-mcp-server.ts +7 -0
- package/src/workflows/engine-core.ts +23 -2
- package/src/workflows/execute-from-execution.ts +12 -2
|
@@ -4,21 +4,37 @@
|
|
|
4
4
|
* Wraps @langchain/mcp-adapters MultiServerMCPClient with:
|
|
5
5
|
* - Conversion from ResolvedMcpServer[] to the SDK's Connection config
|
|
6
6
|
* - Proper async cleanup via close()
|
|
7
|
-
* - Tool filtering
|
|
7
|
+
* - Tool filtering by each server's effective enabled_tools allow-list
|
|
8
|
+
* (issue #350; semantics in shared/mcp-enabled-tools.ts) — the model only
|
|
9
|
+
* ever sees the tools the agent's manifest enables
|
|
8
10
|
*
|
|
9
11
|
* Transport policy (stdio is local-runner-only) is enforced upstream at
|
|
10
12
|
* resolution time by shared/mcp-transport-guard.ts — servers reaching
|
|
11
13
|
* this manager have already passed it.
|
|
12
14
|
*
|
|
15
|
+
* Stdio env contract (oss#256): a subprocess receives exactly the
|
|
16
|
+
* variables declared in the server's spec.env (resolved upstream by
|
|
17
|
+
* filterEnvToDeclaredKeys) plus the MCP SDK's minimal base environment
|
|
18
|
+
* (HOME, LOGNAME, PATH, SHELL, TERM, USER — getDefaultEnvironment in
|
|
19
|
+
* @modelcontextprotocol/sdk, merged under whatever we pass). The
|
|
20
|
+
* runner's own process env is never passed: it carries runner-internal
|
|
21
|
+
* credentials (STIGMER_TOKEN, STIGMER_RUNNER_HITL_SECRET,
|
|
22
|
+
* CURSOR_API_KEY, LLM provider keys) that no third-party MCP subprocess
|
|
23
|
+
* may see. A declared-empty env and an undeclared env are deliberately
|
|
24
|
+
* equivalent — both yield the SDK base environment.
|
|
25
|
+
*
|
|
13
26
|
* The Cursor execution path does NOT use this manager — it passes MCP
|
|
14
27
|
* configs directly to the Cursor SDK via toCursorMcpConfig(). This
|
|
15
|
-
* manager
|
|
28
|
+
* manager serves LangGraph-based deep agent executions, and discovery
|
|
29
|
+
* (activities/discover-mcp-server.ts) builds its spawn config through
|
|
30
|
+
* toMcpClientConfig too.
|
|
16
31
|
*/
|
|
17
32
|
|
|
18
33
|
import { MultiServerMCPClient } from "@langchain/mcp-adapters";
|
|
19
34
|
import type { Connection } from "@langchain/mcp-adapters";
|
|
20
35
|
import type { DynamicStructuredTool } from "@langchain/core/tools";
|
|
21
36
|
import type { ResolvedMcpServer } from "./mcp-resolver.js";
|
|
37
|
+
import { filterToolsByEnabledTools } from "./mcp-enabled-tools.js";
|
|
22
38
|
|
|
23
39
|
/**
|
|
24
40
|
* Convert harness-agnostic ResolvedMcpServer[] into the
|
|
@@ -32,11 +48,18 @@ export function toMcpClientConfig(
|
|
|
32
48
|
for (const server of servers) {
|
|
33
49
|
if (server.connectionType === "stdio") {
|
|
34
50
|
if (!server.command) continue;
|
|
51
|
+
if (!server.env || Object.keys(server.env).length === 0) {
|
|
52
|
+
console.log(
|
|
53
|
+
`[MCP] Server '${server.slug}' declares no env — its subprocess ` +
|
|
54
|
+
`starts with the minimal base environment only. Declare variables ` +
|
|
55
|
+
`in the McpServer's spec.env to pass them.`,
|
|
56
|
+
);
|
|
57
|
+
}
|
|
35
58
|
config[server.slug] = {
|
|
36
59
|
transport: "stdio",
|
|
37
60
|
command: server.command,
|
|
38
61
|
args: server.args ?? [],
|
|
39
|
-
env: server.env
|
|
62
|
+
env: server.env,
|
|
40
63
|
cwd: server.cwd,
|
|
41
64
|
};
|
|
42
65
|
} else if (server.connectionType === "http" || server.connectionType === "sse") {
|
|
@@ -75,11 +98,25 @@ export async function connectMcpServers(
|
|
|
75
98
|
}
|
|
76
99
|
|
|
77
100
|
const client = new MultiServerMCPClient(connectionConfig);
|
|
78
|
-
const
|
|
101
|
+
const discoveredToolMap = await client.initializeConnections();
|
|
79
102
|
|
|
103
|
+
// Enforce each server's effective enabled_tools allow-list (issue #350)
|
|
104
|
+
// HERE, before anything downstream sees the tools: the parent tool list,
|
|
105
|
+
// the approval gate's toolServerMap, and the sub-agent McpAccess filter
|
|
106
|
+
// (whose subset-of-parent check is only real against a narrowed map) all
|
|
107
|
+
// derive from this result. Servers without a restriction (enabledTools
|
|
108
|
+
// absent — including synthesized attachments) pass through untouched.
|
|
109
|
+
const enabledBySlug = new Map(
|
|
110
|
+
servers.map((s) => [s.slug, s.enabledTools]),
|
|
111
|
+
);
|
|
112
|
+
const serverToolMap: Record<string, DynamicStructuredTool[]> = {};
|
|
80
113
|
const tools: DynamicStructuredTool[] = [];
|
|
81
|
-
for (const
|
|
82
|
-
|
|
114
|
+
for (const [slug, discovered] of Object.entries(discoveredToolMap)) {
|
|
115
|
+
const filtered = filterToolsByEnabledTools(
|
|
116
|
+
slug, discovered, enabledBySlug.get(slug),
|
|
117
|
+
);
|
|
118
|
+
serverToolMap[slug] = filtered;
|
|
119
|
+
tools.push(...filtered);
|
|
83
120
|
}
|
|
84
121
|
|
|
85
122
|
console.log(
|
|
@@ -91,23 +128,3 @@ export async function connectMcpServers(
|
|
|
91
128
|
|
|
92
129
|
return { client, tools, serverToolMap };
|
|
93
130
|
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Snapshot of process.env as a string-only record (no undefined values).
|
|
97
|
-
* Used as a fallback when an MCP server has no declared env — the
|
|
98
|
-
* subprocess inherits the runner's full environment, matching standard
|
|
99
|
-
* Unix child-process behavior.
|
|
100
|
-
*
|
|
101
|
-
* Without this, @modelcontextprotocol/sdk only passes a restricted
|
|
102
|
-
* whitelist (HOME, PATH, USER, etc.) which drops platform variables
|
|
103
|
-
* like STIGMER_SERVER_ADDRESS.
|
|
104
|
-
*/
|
|
105
|
-
function processEnvAsStrings(): Record<string, string> {
|
|
106
|
-
const env: Record<string, string> = {};
|
|
107
|
-
for (const [key, value] of Object.entries(process.env)) {
|
|
108
|
-
if (value !== undefined) {
|
|
109
|
-
env[key] = value;
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
return env;
|
|
113
|
-
}
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* there until the two are consolidated.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
import type { McpServerUsage } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/spec_pb";
|
|
14
|
+
import type { McpServerUsage, ToolApprovalOverride } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/spec_pb";
|
|
15
15
|
import type { McpServer } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/api_pb";
|
|
16
16
|
import type { ToolApprovalPolicy } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/spec_pb";
|
|
17
17
|
import type { StigmerClient } from "../client/stigmer-client.js";
|
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
filterEnvToDeclaredKeys,
|
|
27
27
|
PlaceholderResolutionError,
|
|
28
28
|
} from "./placeholder-resolver.js";
|
|
29
|
+
import { effectiveEnabledTools } from "./mcp-enabled-tools.js";
|
|
29
30
|
|
|
30
31
|
/**
|
|
31
32
|
* Harness-agnostic intermediate representation of a resolved MCP server.
|
|
@@ -45,6 +46,28 @@ export interface ResolvedMcpServer {
|
|
|
45
46
|
toolApprovals: ToolApprovalPolicy[];
|
|
46
47
|
pinnedToolApprovals: ToolApprovalPolicy[];
|
|
47
48
|
discoveredCapabilitiesEmpty: boolean;
|
|
49
|
+
/**
|
|
50
|
+
* The EFFECTIVE tool allow-list for this server (issue #350): the usage's
|
|
51
|
+
* enabled_tools, falling back to the server's default_enabled_tools when
|
|
52
|
+
* the usage's list is empty (see shared/mcp-enabled-tools.ts). Absent when
|
|
53
|
+
* unrestricted — which keeps synthesized attachment servers (built without
|
|
54
|
+
* a usage) and the discovery path unfiltered by construction. Enforced at
|
|
55
|
+
* connect time by connectMcpServers (shared/mcp-manager.ts).
|
|
56
|
+
*/
|
|
57
|
+
enabledTools?: string[];
|
|
58
|
+
/**
|
|
59
|
+
* Layer-3 per-agent approval overrides from the usage that resolved this
|
|
60
|
+
* server (issue #349): riding the resolved server is what SCOPES an
|
|
61
|
+
* override to its own server — a flat cross-server list is how an
|
|
62
|
+
* override once leaked onto (or silently un-gated) a same-named tool on
|
|
63
|
+
* another server. Deliberately REQUIRED, not optional: empty means "no
|
|
64
|
+
* overrides", and every construction site — including the near-duplicate
|
|
65
|
+
* Cursor resolver and the synthesized attachments, which have no usage
|
|
66
|
+
* and therefore no layer 3 — must say so explicitly, so a forgotten
|
|
67
|
+
* mirror cannot compile. Consumed by mergeApprovalPolicies
|
|
68
|
+
* (shared/approval-policy.ts).
|
|
69
|
+
*/
|
|
70
|
+
toolApprovalOverrides: ToolApprovalOverride[];
|
|
48
71
|
}
|
|
49
72
|
|
|
50
73
|
export interface McpResolutionResult {
|
|
@@ -78,7 +101,13 @@ export async function resolveMcpServers(
|
|
|
78
101
|
envVars,
|
|
79
102
|
ref.slug,
|
|
80
103
|
);
|
|
81
|
-
const server = mcpServerToResolved(
|
|
104
|
+
const server = mcpServerToResolved(
|
|
105
|
+
mcpServer,
|
|
106
|
+
ref.slug,
|
|
107
|
+
serverEnv,
|
|
108
|
+
usage.enabledTools,
|
|
109
|
+
usage.toolApprovalOverrides ?? [],
|
|
110
|
+
);
|
|
82
111
|
if (server) {
|
|
83
112
|
assertTransportAllowed(server.slug, server.connectionType, transportPosture);
|
|
84
113
|
resolved.push(server);
|
|
@@ -108,6 +137,8 @@ export function mcpServerToResolved(
|
|
|
108
137
|
server: McpServer,
|
|
109
138
|
slug: string,
|
|
110
139
|
envVars: Record<string, string>,
|
|
140
|
+
usageEnabledTools?: readonly string[],
|
|
141
|
+
usageToolApprovalOverrides: ToolApprovalOverride[] = [],
|
|
111
142
|
): ResolvedMcpServer | null {
|
|
112
143
|
const spec = server.spec;
|
|
113
144
|
if (!spec) return null;
|
|
@@ -123,6 +154,13 @@ export function mcpServerToResolved(
|
|
|
123
154
|
toolApprovals,
|
|
124
155
|
pinnedToolApprovals,
|
|
125
156
|
discoveredCapabilitiesEmpty,
|
|
157
|
+
// Callers without a usage (the discovery activity) pass nothing, so the
|
|
158
|
+
// default_enabled_tools fallback still applies but a per-agent
|
|
159
|
+
// restriction cannot: discovery must see the server's full toolset.
|
|
160
|
+
enabledTools: effectiveEnabledTools(usageEnabledTools, spec.defaultEnabledTools),
|
|
161
|
+
// Same no-usage rule for layer 3: discovery has no agent context, so no
|
|
162
|
+
// per-agent overrides exist — defaulting to [] keeps that structural.
|
|
163
|
+
toolApprovalOverrides: usageToolApprovalOverrides,
|
|
126
164
|
};
|
|
127
165
|
|
|
128
166
|
switch (spec.serverType.case) {
|
|
@@ -170,4 +208,37 @@ export function extractMcpServerSlugs(usages: McpServerUsage[]): string[] {
|
|
|
170
208
|
.filter((s): s is string => !!s);
|
|
171
209
|
}
|
|
172
210
|
|
|
211
|
+
/**
|
|
212
|
+
* Merge MCP server usages from agent (base) and session (overlay).
|
|
213
|
+
*
|
|
214
|
+
* Replicates session_context_merge.py::merge_mcp_server_usages():
|
|
215
|
+
* - Agent-level usages are the base set
|
|
216
|
+
* - Session-level usages extend or override by mcp_server_ref.slug
|
|
217
|
+
* - If both reference the same slug, session-level takes precedence —
|
|
218
|
+
* the WHOLE usage, including enabled_tools and approval overrides
|
|
219
|
+
*
|
|
220
|
+
* Shared by both harnesses (blueprint-resolver.ts for Cursor,
|
|
221
|
+
* execute-deep-agent/setup.ts for the native harness) so a duplicate slug
|
|
222
|
+
* resolves identically everywhere: exactly one usage per server, whose
|
|
223
|
+
* enabled_tools is the one the enforcement filter honors.
|
|
224
|
+
*/
|
|
225
|
+
export function mergeMcpServerUsages(
|
|
226
|
+
agentUsages: McpServerUsage[],
|
|
227
|
+
sessionUsages: McpServerUsage[],
|
|
228
|
+
): McpServerUsage[] {
|
|
229
|
+
const bySlug = new Map<string, McpServerUsage>();
|
|
230
|
+
|
|
231
|
+
for (const usage of agentUsages) {
|
|
232
|
+
const slug = usage.mcpServerRef?.slug;
|
|
233
|
+
if (slug) bySlug.set(slug, usage);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
for (const usage of sessionUsages) {
|
|
237
|
+
const slug = usage.mcpServerRef?.slug;
|
|
238
|
+
if (slug) bySlug.set(slug, usage);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return [...bySlug.values()];
|
|
242
|
+
}
|
|
243
|
+
|
|
173
244
|
export { PlaceholderResolutionError } from "./placeholder-resolver.js";
|
|
@@ -6,10 +6,11 @@
|
|
|
6
6
|
* (e.g. "claude-haiku-4-5-20251001") before a client is built — so no
|
|
7
7
|
* activity can bypass the translation and 404 the provider.
|
|
8
8
|
*
|
|
9
|
-
* Layering: `llm-proxy.ts`
|
|
10
|
-
* dependency); this module is the LangChain-aware layer above
|
|
11
|
-
* the resolve -> infer-provider -> strip-prefix ->
|
|
12
|
-
* sequence that used to be copy-pasted
|
|
9
|
+
* Layering: `llm-proxy.ts` and `llm-backend.ts` stay pure routing utilities
|
|
10
|
+
* (no LangChain dependency); this module is the LangChain-aware layer above
|
|
11
|
+
* them that owns the resolve -> infer-provider -> strip-prefix ->
|
|
12
|
+
* proxy-or-backend-wire -> construct sequence that used to be copy-pasted
|
|
13
|
+
* across every LLM activity.
|
|
13
14
|
*/
|
|
14
15
|
|
|
15
16
|
import { ChatAnthropic } from "@langchain/anthropic";
|
|
@@ -23,6 +24,15 @@ import {
|
|
|
23
24
|
buildProxyHeaders,
|
|
24
25
|
type LlmProvider,
|
|
25
26
|
} from "./llm-proxy.js";
|
|
27
|
+
import {
|
|
28
|
+
resolveAnthropicBackend,
|
|
29
|
+
checkVertexPrerequisites,
|
|
30
|
+
checkBedrockPrerequisites,
|
|
31
|
+
checkFoundryPrerequisites,
|
|
32
|
+
toVertexModelId,
|
|
33
|
+
toBedrockModelId,
|
|
34
|
+
toFoundryDeploymentName,
|
|
35
|
+
} from "./llm-backend.js";
|
|
26
36
|
import { resolveToApiModelId } from "./model-registry.js";
|
|
27
37
|
|
|
28
38
|
export interface BuildChatModelOptions {
|
|
@@ -52,7 +62,13 @@ export interface BuildChatModelOptions {
|
|
|
52
62
|
export interface BuiltChatModel {
|
|
53
63
|
readonly model: BaseChatModel;
|
|
54
64
|
readonly provider: LlmProvider;
|
|
55
|
-
/**
|
|
65
|
+
/**
|
|
66
|
+
* The canonical provider API id the registry resolved to — what pricing
|
|
67
|
+
* and usage metrics key on. A backend adapter may translate it for the
|
|
68
|
+
* wire (Vertex separates the snapshot date with `@`), but the translated
|
|
69
|
+
* form never leaves the adapter: this field stays canonical (the
|
|
70
|
+
* canonical-id invariant in design decision 001-provider-backends).
|
|
71
|
+
*/
|
|
56
72
|
readonly apiModelId: string;
|
|
57
73
|
}
|
|
58
74
|
|
|
@@ -67,6 +83,105 @@ export async function buildChatModel(opts: BuildChatModelOptions): Promise<Built
|
|
|
67
83
|
const provider = inferProvider(resolved);
|
|
68
84
|
const apiModelId = stripProviderPrefix(resolved);
|
|
69
85
|
|
|
86
|
+
// Backend precedence by construction: a proxied call never consults the
|
|
87
|
+
// backend var — the proxy owns provider routing (the factories' preflight
|
|
88
|
+
// warns when both are configured). Selection is deployment-static and read
|
|
89
|
+
// here the same way provider API keys are, not plumbed through Config.
|
|
90
|
+
const anthropicBackend =
|
|
91
|
+
provider === "anthropic" && !opts.proxyEndpoint
|
|
92
|
+
? resolveAnthropicBackend()
|
|
93
|
+
: "public";
|
|
94
|
+
|
|
95
|
+
// Backend SDKs (and their auth subtrees: google-auth-library, AWS smithy)
|
|
96
|
+
// load lazily so deployments that never configure a backend never
|
|
97
|
+
// evaluate them — cheap cold starts stay cheap, and bundle-slim's CJS
|
|
98
|
+
// output preserves exactly this deferred evaluation (see
|
|
99
|
+
// scripts/bundle-slim.mjs). The factory is invoked lazily by
|
|
100
|
+
// ChatAnthropic once per cached client (batch + streaming), so the class
|
|
101
|
+
// is captured here, in async context.
|
|
102
|
+
//
|
|
103
|
+
// Each factory MUST honor options.maxRetries and options.timeout:
|
|
104
|
+
// LangChain owns retrying and hands the factory maxRetries: 0 — a factory
|
|
105
|
+
// that drops it nests the SDK's default 2 retries inside LangChain's
|
|
106
|
+
// retry loop, multiplying every transient failure — and `timeout` arrives
|
|
107
|
+
// through the same options object (ChatAnthropic spreads clientOptions
|
|
108
|
+
// into it), so a factory that drops it silently unbounds
|
|
109
|
+
// STIGMER_LLM_REQUEST_TIMEOUT_MS on that backend. Pinned by the seam
|
|
110
|
+
// tests. Prerequisites are re-checked here (not only in
|
|
111
|
+
// the factories' preflight) so paths that construct models without a
|
|
112
|
+
// runner factory still fail at dispatch with the catalog message instead
|
|
113
|
+
// of mid-request. Credentials are read natively by each SDK from its
|
|
114
|
+
// standard conventions (GCP: CLOUD_ML_REGION + ADC; AWS: AWS_REGION +
|
|
115
|
+
// the credential chain / AWS_BEARER_TOKEN_BEDROCK; Foundry:
|
|
116
|
+
// ANTHROPIC_FOUNDRY_API_KEY, or the Azure credential chain when no key
|
|
117
|
+
// is set).
|
|
118
|
+
let backendCreateClient:
|
|
119
|
+
| ((options: { maxRetries?: number; timeout?: number }) => unknown)
|
|
120
|
+
| undefined;
|
|
121
|
+
let wireModelId = apiModelId;
|
|
122
|
+
let maxTokens = opts.maxTokens;
|
|
123
|
+
if (anthropicBackend === "vertex") {
|
|
124
|
+
const prereq = checkVertexPrerequisites();
|
|
125
|
+
if (prereq !== null) throw new Error(prereq);
|
|
126
|
+
const { AnthropicVertex } = await import("@anthropic-ai/vertex-sdk");
|
|
127
|
+
backendCreateClient = (options) =>
|
|
128
|
+
new AnthropicVertex({ maxRetries: options.maxRetries, timeout: options.timeout });
|
|
129
|
+
wireModelId = toVertexModelId(apiModelId);
|
|
130
|
+
} else if (anthropicBackend === "bedrock") {
|
|
131
|
+
const prereq = checkBedrockPrerequisites();
|
|
132
|
+
if (prereq !== null) throw new Error(prereq);
|
|
133
|
+
const { AnthropicBedrock } = await import("@anthropic-ai/bedrock-sdk");
|
|
134
|
+
backendCreateClient = (options) =>
|
|
135
|
+
new AnthropicBedrock({ maxRetries: options.maxRetries, timeout: options.timeout });
|
|
136
|
+
wireModelId = toBedrockModelId(apiModelId);
|
|
137
|
+
if (maxTokens === undefined) {
|
|
138
|
+
// LangChain's per-model maxTokens table prefix-matches the model
|
|
139
|
+
// name. Vertex's translated ids still match their canonical prefix;
|
|
140
|
+
// Bedrock's `anthropic.…` ids match nothing and silently fall back
|
|
141
|
+
// to 4096 — a silent output cap. Probe the CANONICAL id with a
|
|
142
|
+
// throwaway construction (pure field assignment, no I/O; the key is
|
|
143
|
+
// never used) so bedrock inherits exactly the default the public API
|
|
144
|
+
// and vertex get for the same model — including models the table
|
|
145
|
+
// doesn't know yet, where all backends agree on the fallback.
|
|
146
|
+
// Pinned by the canonical-parity test in bedrock-adapter.test.ts.
|
|
147
|
+
maxTokens = new ChatAnthropic({ model: apiModelId, apiKey: "max-tokens-probe" }).maxTokens;
|
|
148
|
+
}
|
|
149
|
+
} else if (anthropicBackend === "foundry") {
|
|
150
|
+
const prereq = checkFoundryPrerequisites();
|
|
151
|
+
if (prereq !== null) throw new Error(prereq);
|
|
152
|
+
const { AnthropicFoundry } = await import("@anthropic-ai/foundry-sdk");
|
|
153
|
+
// Auth is an explicit either/or (the Foundry constructor throws when
|
|
154
|
+
// given both a key and a token provider): ANTHROPIC_FOUNDRY_API_KEY if
|
|
155
|
+
// present, otherwise keyless Microsoft Entra ID through the Azure
|
|
156
|
+
// credential chain (env service principal -> workload identity ->
|
|
157
|
+
// managed identity -> az CLI) — the Azure analogue of Vertex ADC and
|
|
158
|
+
// the AWS chain, resolved at request time. The provider is built once
|
|
159
|
+
// here (getBearerTokenProvider caches tokens until near-expiry) and
|
|
160
|
+
// shared by both cached clients; the SDK still consults it per request,
|
|
161
|
+
// so refreshed tokens flow without reconstruction (pinned by
|
|
162
|
+
// foundry-seam.test.ts). Endpoint (resource or base URL) and the key
|
|
163
|
+
// are read natively by the SDK from its own env vars.
|
|
164
|
+
let azureADTokenProvider: (() => Promise<string>) | undefined;
|
|
165
|
+
if (!process.env.ANTHROPIC_FOUNDRY_API_KEY?.trim()) {
|
|
166
|
+
const { DefaultAzureCredential, getBearerTokenProvider } = await import("@azure/identity");
|
|
167
|
+
azureADTokenProvider = getBearerTokenProvider(
|
|
168
|
+
new DefaultAzureCredential(),
|
|
169
|
+
"https://ai.azure.com/.default",
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
backendCreateClient = (options) =>
|
|
173
|
+
new AnthropicFoundry({
|
|
174
|
+
maxRetries: options.maxRetries,
|
|
175
|
+
timeout: options.timeout,
|
|
176
|
+
...(azureADTokenProvider ? { azureADTokenProvider } : {}),
|
|
177
|
+
});
|
|
178
|
+
// Unlike the vertex/bedrock ids, the deployment name needs no maxTokens
|
|
179
|
+
// handling: stripping the snapshot date preserves LangChain's per-model
|
|
180
|
+
// default (probed 2026-08-11 across the full catalog; pinned by the
|
|
181
|
+
// parity test in foundry-adapter.test.ts).
|
|
182
|
+
wireModelId = toFoundryDeploymentName(apiModelId);
|
|
183
|
+
}
|
|
184
|
+
|
|
70
185
|
const baseUrl = opts.proxyEndpoint
|
|
71
186
|
? resolveProxyBaseUrl(opts.proxyEndpoint, provider)
|
|
72
187
|
: undefined;
|
|
@@ -85,9 +200,29 @@ export async function buildChatModel(opts: BuildChatModelOptions): Promise<Built
|
|
|
85
200
|
const common = {
|
|
86
201
|
temperature: opts.temperature ?? 0,
|
|
87
202
|
apiKey,
|
|
88
|
-
...(
|
|
89
|
-
...(opts.timeoutMs ? { maxRetries: opts.maxRetries ?? 0
|
|
203
|
+
...(maxTokens ? { maxTokens } : {}),
|
|
204
|
+
...(opts.timeoutMs ? { maxRetries: opts.maxRetries ?? 0 } : {}),
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
// The request timeout lives in a different slot per wrapper: ChatOpenAI
|
|
208
|
+
// reads `timeout` as a constructor field, but ChatAnthropic ignores it
|
|
209
|
+
// there — its slot is `clientOptions.timeout`, which the wrapper spreads
|
|
210
|
+
// into the createClient factory options and (on the default factory) the
|
|
211
|
+
// SDK constructor. This split is what makes STIGMER_LLM_REQUEST_TIMEOUT_MS
|
|
212
|
+
// bound every path; putting `timeout` in the shared constructor spread is
|
|
213
|
+
// the exact regression that made it inert for Anthropic (T02 finding 2).
|
|
214
|
+
// The `maxRetries` half above stays constructor-level for both wrappers:
|
|
215
|
+
// that is LangChain's own retry knob, distinct from the SDK-level
|
|
216
|
+
// maxRetries the factories receive.
|
|
217
|
+
const anthropicClientOptions = {
|
|
218
|
+
...(opts.timeoutMs ? { timeout: opts.timeoutMs } : {}),
|
|
219
|
+
...(baseUrl ? { baseURL: baseUrl } : {}),
|
|
220
|
+
...(headers ? { defaultHeaders: headers } : {}),
|
|
90
221
|
};
|
|
222
|
+
const anthropicClientOptionsField =
|
|
223
|
+
Object.keys(anthropicClientOptions).length > 0
|
|
224
|
+
? { clientOptions: anthropicClientOptions }
|
|
225
|
+
: {};
|
|
91
226
|
|
|
92
227
|
// The two SDKs name the transport-override block differently (OpenAI:
|
|
93
228
|
// `configuration`, Anthropic: `clientOptions`) — encapsulating that here is
|
|
@@ -96,6 +231,7 @@ export async function buildChatModel(opts: BuildChatModelOptions): Promise<Built
|
|
|
96
231
|
? new ChatOpenAI({
|
|
97
232
|
model: apiModelId,
|
|
98
233
|
...common,
|
|
234
|
+
...(opts.timeoutMs ? { timeout: opts.timeoutMs } : {}),
|
|
99
235
|
...(baseUrl || headers
|
|
100
236
|
? {
|
|
101
237
|
configuration: {
|
|
@@ -105,18 +241,24 @@ export async function buildChatModel(opts: BuildChatModelOptions): Promise<Built
|
|
|
105
241
|
}
|
|
106
242
|
: {}),
|
|
107
243
|
})
|
|
108
|
-
:
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
244
|
+
: backendCreateClient
|
|
245
|
+
? // Backend adapter: the translated id is wire detail only — each
|
|
246
|
+
// client moves it from the JSON body into its URL path. The waiver
|
|
247
|
+
// in ChatAnthropic (an API key is not required when createClient is
|
|
248
|
+
// provided) is what lets this construct with no ANTHROPIC_API_KEY.
|
|
249
|
+
// (Backend mode never has a proxy — see the precedence rule above —
|
|
250
|
+
// so the clientOptions here carry at most the timeout.)
|
|
251
|
+
new ChatAnthropic({
|
|
252
|
+
model: wireModelId,
|
|
253
|
+
...common,
|
|
254
|
+
...anthropicClientOptionsField,
|
|
255
|
+
createClient: backendCreateClient,
|
|
256
|
+
})
|
|
257
|
+
: new ChatAnthropic({
|
|
258
|
+
model: apiModelId,
|
|
259
|
+
...common,
|
|
260
|
+
...anthropicClientOptionsField,
|
|
261
|
+
});
|
|
120
262
|
|
|
121
263
|
return { model, provider, apiModelId };
|
|
122
264
|
}
|