@stigmer/runner 3.12.4 → 3.12.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.
Files changed (146) hide show
  1. package/dist/.build-fingerprint +1 -1
  2. package/dist/activities/execute-cursor/cursor-mcp-config.d.ts +45 -0
  3. package/dist/activities/execute-cursor/cursor-mcp-config.js +76 -0
  4. package/dist/activities/execute-cursor/cursor-mcp-config.js.map +1 -0
  5. package/dist/activities/execute-cursor/index.d.ts +10 -1
  6. package/dist/activities/execute-cursor/index.js +47 -29
  7. package/dist/activities/execute-cursor/index.js.map +1 -1
  8. package/dist/activities/execute-cursor/prompt-builder.d.ts +13 -2
  9. package/dist/activities/execute-cursor/prompt-builder.js +11 -0
  10. package/dist/activities/execute-cursor/prompt-builder.js.map +1 -1
  11. package/dist/activities/execute-cursor/session-lifecycle.d.ts +1 -1
  12. package/dist/activities/execute-cursor/skill-resolver.d.ts +2 -21
  13. package/dist/activities/execute-cursor/skill-resolver.js +5 -108
  14. package/dist/activities/execute-cursor/skill-resolver.js.map +1 -1
  15. package/dist/activities/execute-deep-agent/approval-file-change.d.ts +9 -0
  16. package/dist/activities/execute-deep-agent/approval-file-change.js +2 -1
  17. package/dist/activities/execute-deep-agent/approval-file-change.js.map +1 -1
  18. package/dist/activities/execute-deep-agent/cas-capture-backend.d.ts +18 -1
  19. package/dist/activities/execute-deep-agent/cas-capture-backend.js +20 -3
  20. package/dist/activities/execute-deep-agent/cas-capture-backend.js.map +1 -1
  21. package/dist/activities/execute-deep-agent/index.js +57 -5
  22. package/dist/activities/execute-deep-agent/index.js.map +1 -1
  23. package/dist/activities/execute-deep-agent/prompt-builder.d.ts +9 -0
  24. package/dist/activities/execute-deep-agent/prompt-builder.js +10 -0
  25. package/dist/activities/execute-deep-agent/prompt-builder.js.map +1 -1
  26. package/dist/activities/execute-deep-agent/setup.js +17 -16
  27. package/dist/activities/execute-deep-agent/setup.js.map +1 -1
  28. package/dist/activities/execute-deep-agent/subagent-transformer.js +17 -11
  29. package/dist/activities/execute-deep-agent/subagent-transformer.js.map +1 -1
  30. package/dist/activities/execute-deep-agent/subagent-wiring.d.ts +3 -3
  31. package/dist/index.d.ts +1 -1
  32. package/dist/index.js +1 -1
  33. package/dist/middleware/path-normalization.d.ts +49 -49
  34. package/dist/middleware/path-normalization.js +73 -84
  35. package/dist/middleware/path-normalization.js.map +1 -1
  36. package/dist/middleware/types.d.ts +3 -3
  37. package/dist/runner-manager.d.ts +1 -1
  38. package/dist/runner-manager.js +22 -15
  39. package/dist/runner-manager.js.map +1 -1
  40. package/dist/runner.js +11 -0
  41. package/dist/runner.js.map +1 -1
  42. package/dist/shared/args-preview.d.ts +8 -0
  43. package/dist/shared/args-preview.js +14 -3
  44. package/dist/shared/args-preview.js.map +1 -1
  45. package/dist/shared/artifact-storage.d.ts +11 -1
  46. package/dist/shared/artifact-storage.js +10 -1
  47. package/dist/shared/artifact-storage.js.map +1 -1
  48. package/dist/shared/declared-preferences.d.ts +47 -0
  49. package/dist/shared/declared-preferences.js +64 -0
  50. package/dist/shared/declared-preferences.js.map +1 -0
  51. package/dist/shared/mcp-enabled-tools.d.ts +2 -3
  52. package/dist/shared/mcp-enabled-tools.js +2 -3
  53. package/dist/shared/mcp-enabled-tools.js.map +1 -1
  54. package/dist/shared/mcp-resolver.d.ts +20 -15
  55. package/dist/shared/mcp-resolver.js +11 -12
  56. package/dist/shared/mcp-resolver.js.map +1 -1
  57. package/dist/shared/placeholder-resolver.d.ts +9 -2
  58. package/dist/shared/placeholder-resolver.js +9 -2
  59. package/dist/shared/placeholder-resolver.js.map +1 -1
  60. package/dist/shared/plan-mode-permissions.d.ts +26 -46
  61. package/dist/shared/plan-mode-permissions.js +27 -55
  62. package/dist/shared/plan-mode-permissions.js.map +1 -1
  63. package/dist/shared/skill-mount.d.ts +89 -0
  64. package/dist/shared/skill-mount.js +142 -0
  65. package/dist/shared/skill-mount.js.map +1 -0
  66. package/dist/shared/skill-writer.d.ts +28 -26
  67. package/dist/shared/skill-writer.js +79 -102
  68. package/dist/shared/skill-writer.js.map +1 -1
  69. package/dist/shared/worker-shutdown.d.ts +67 -0
  70. package/dist/shared/worker-shutdown.js +79 -0
  71. package/dist/shared/worker-shutdown.js.map +1 -0
  72. package/dist/shared/workspace/types.d.ts +3 -2
  73. package/dist/workflow-engine/loader.js +38 -1
  74. package/dist/workflow-engine/loader.js.map +1 -1
  75. package/dist/workflow-engine/tasks/human-input.js +23 -2
  76. package/dist/workflow-engine/tasks/human-input.js.map +1 -1
  77. package/package.json +15 -11
  78. package/src/activities/execute-cursor/__tests__/build-prompt.test.ts +50 -0
  79. package/src/activities/execute-cursor/__tests__/cursor-mcp-config.test.ts +111 -0
  80. package/src/activities/execute-cursor/__tests__/skill-resolver.test.ts +3 -94
  81. package/src/activities/execute-cursor/cursor-mcp-config.ts +107 -0
  82. package/src/activities/execute-cursor/index.ts +63 -35
  83. package/src/activities/execute-cursor/prompt-builder.ts +29 -2
  84. package/src/activities/execute-cursor/session-lifecycle.ts +1 -1
  85. package/src/activities/execute-cursor/skill-resolver.ts +10 -137
  86. package/src/activities/execute-deep-agent/__tests__/approval-file-change.test.ts +15 -0
  87. package/src/activities/execute-deep-agent/__tests__/cas-capture-backend.test.ts +75 -0
  88. package/src/activities/execute-deep-agent/__tests__/hitl-reject.test.ts +1 -0
  89. package/src/activities/execute-deep-agent/__tests__/hitl-resume-approve-all.test.ts +1 -0
  90. package/src/activities/execute-deep-agent/__tests__/hitl-resume-history.test.ts +1 -0
  91. package/src/activities/execute-deep-agent/__tests__/index.test.ts +1 -0
  92. package/src/activities/execute-deep-agent/__tests__/plan-mode-path-normalization.test.ts +42 -29
  93. package/src/activities/execute-deep-agent/__tests__/prompt-builder.test.ts +52 -0
  94. package/src/activities/execute-deep-agent/__tests__/sequential-gate-resume.test.ts +1 -0
  95. package/src/activities/execute-deep-agent/__tests__/subagent-plan-mode-permissions.test.ts +11 -8
  96. package/src/activities/execute-deep-agent/approval-file-change.ts +11 -1
  97. package/src/activities/execute-deep-agent/cas-capture-backend.ts +20 -3
  98. package/src/activities/execute-deep-agent/index.ts +57 -5
  99. package/src/activities/execute-deep-agent/prompt-builder.ts +22 -0
  100. package/src/activities/execute-deep-agent/setup.ts +21 -19
  101. package/src/activities/execute-deep-agent/subagent-transformer.ts +19 -12
  102. package/src/activities/execute-deep-agent/subagent-wiring.ts +3 -3
  103. package/src/index.ts +1 -1
  104. package/src/middleware/__tests__/path-normalization.test.ts +32 -32
  105. package/src/middleware/path-normalization.ts +78 -90
  106. package/src/middleware/types.ts +3 -3
  107. package/src/runner-manager.ts +26 -16
  108. package/src/runner.ts +14 -0
  109. package/src/shared/__tests__/artifact-storage.test.ts +28 -0
  110. package/src/shared/__tests__/bedrock-seam.test.ts +5 -5
  111. package/src/shared/__tests__/declared-preferences.test.ts +109 -0
  112. package/src/shared/__tests__/foundry-seam.test.ts +5 -5
  113. package/src/shared/__tests__/plan-mode-permissions.test.ts +15 -44
  114. package/src/shared/__tests__/skill-mount.test.ts +238 -0
  115. package/src/shared/__tests__/skill-writer.test.ts +151 -128
  116. package/src/shared/__tests__/vertex-adapter.test.ts +30 -3
  117. package/src/shared/__tests__/vertex-seam.test.ts +5 -5
  118. package/src/shared/__tests__/worker-shutdown.test.ts +101 -0
  119. package/src/shared/args-preview.ts +17 -3
  120. package/src/shared/artifact-storage.ts +20 -1
  121. package/src/shared/declared-preferences.ts +84 -0
  122. package/src/shared/mcp-enabled-tools.ts +2 -3
  123. package/src/shared/mcp-resolver.ts +20 -20
  124. package/src/shared/placeholder-resolver.ts +9 -2
  125. package/src/shared/plan-mode-permissions.ts +27 -58
  126. package/src/shared/skill-mount.ts +179 -0
  127. package/src/shared/skill-writer.ts +96 -130
  128. package/src/shared/worker-shutdown.ts +99 -0
  129. package/src/shared/workspace/types.ts +3 -2
  130. package/src/workflow-engine/__tests__/loader.test.ts +51 -0
  131. package/src/workflow-engine/__tests__/tasks/human-input.test.ts +117 -0
  132. package/src/workflow-engine/loader.ts +46 -1
  133. package/src/workflow-engine/tasks/human-input.ts +33 -5
  134. package/dist/activities/execute-cursor/connect-backfill.d.ts +0 -19
  135. package/dist/activities/execute-cursor/connect-backfill.js +0 -27
  136. package/dist/activities/execute-cursor/connect-backfill.js.map +0 -1
  137. package/dist/activities/execute-cursor/mcp-resolver.d.ts +0 -120
  138. package/dist/activities/execute-cursor/mcp-resolver.js +0 -194
  139. package/dist/activities/execute-cursor/mcp-resolver.js.map +0 -1
  140. package/dist/activities/execute-cursor/placeholder-resolver.d.ts +0 -34
  141. package/dist/activities/execute-cursor/placeholder-resolver.js +0 -82
  142. package/dist/activities/execute-cursor/placeholder-resolver.js.map +0 -1
  143. package/src/activities/execute-cursor/__tests__/mcp-resolver.test.ts +0 -125
  144. package/src/activities/execute-cursor/connect-backfill.ts +0 -52
  145. package/src/activities/execute-cursor/mcp-resolver.ts +0 -324
  146. package/src/activities/execute-cursor/placeholder-resolver.ts +0 -109
@@ -1,125 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach } from "vitest";
2
- import { resolveMcpServers } from "../mcp-resolver.js";
3
-
4
- function makeUsage(
5
- slug: string,
6
- enabledTools: string[] = [],
7
- toolApprovalOverrides: Array<{ toolName: string; requiresApproval: boolean }> = [],
8
- ) {
9
- return {
10
- mcpServerRef: { slug, org: "test-org", kind: 0 },
11
- enabledTools,
12
- toolApprovalOverrides,
13
- } as any;
14
- }
15
-
16
- function httpMcpServer(slug: string, defaultEnabledTools: string[] = []) {
17
- return {
18
- metadata: { id: `id-${slug}`, slug },
19
- spec: {
20
- serverType: { case: "http", value: { url: "https://mcp.example.com/mcp", headers: {} } },
21
- env: {},
22
- defaultEnabledTools,
23
- },
24
- status: undefined,
25
- } as any;
26
- }
27
-
28
- function clientReturning(serversBySlug: Record<string, unknown>) {
29
- return {
30
- getMcpServerByReference: vi.fn(async (ref: { slug: string }) => {
31
- const server = serversBySlug[ref.slug];
32
- if (!server) throw new Error(`not found: ${ref.slug}`);
33
- return server;
34
- }),
35
- } as any;
36
- }
37
-
38
- // The effective-list semantics live in shared/mcp-enabled-tools.ts (tested
39
- // there); these tests pin the CURSOR resolver's threading — the near-duplicate
40
- // of shared/mcp-resolver.ts that must mirror it until oss#387 consolidates.
41
- describe("resolveMcpServers (cursor) — enabled_tools threading (issue #350)", () => {
42
- beforeEach(() => {
43
- vi.restoreAllMocks();
44
- vi.spyOn(console, "warn").mockImplementation(() => {});
45
- });
46
-
47
- it("carries the usage's enabled_tools as the effective allow-list", async () => {
48
- const client = clientReturning({ github: httpMcpServer("github") });
49
-
50
- const result = await resolveMcpServers(
51
- client, [makeUsage("github", ["create_pr"])], {}, "stdio-forbidden",
52
- );
53
-
54
- expect(result.resolvedServers[0].enabledTools).toEqual(["create_pr"]);
55
- });
56
-
57
- it("falls back to default_enabled_tools for an empty usage list", async () => {
58
- const client = clientReturning({
59
- github: httpMcpServer("github", ["search_code"]),
60
- });
61
-
62
- const result = await resolveMcpServers(
63
- client, [makeUsage("github")], {}, "stdio-forbidden",
64
- );
65
-
66
- expect(result.resolvedServers[0].enabledTools).toEqual(["search_code"]);
67
- });
68
-
69
- it("resolves unrestricted (absent field) when both lists are empty", async () => {
70
- const client = clientReturning({ github: httpMcpServer("github") });
71
-
72
- const result = await resolveMcpServers(
73
- client, [makeUsage("github")], {}, "stdio-forbidden",
74
- );
75
-
76
- expect(result.resolvedServers[0].enabledTools).toBeUndefined();
77
- });
78
-
79
- it("never narrows the Cursor SDK config — the SDK has no allow-list field; enforcement is the hook's disabled arm", async () => {
80
- const client = clientReturning({ github: httpMcpServer("github") });
81
-
82
- const result = await resolveMcpServers(
83
- client, [makeUsage("github", ["create_pr"])], {}, "stdio-forbidden",
84
- );
85
-
86
- expect(result.cursorConfig.github).toEqual({
87
- type: "http",
88
- url: "https://mcp.example.com/mcp",
89
- headers: undefined,
90
- });
91
- });
92
- });
93
-
94
- describe("resolveMcpServers (cursor) — tool_approval_overrides threading (issue #349)", () => {
95
- beforeEach(() => {
96
- vi.restoreAllMocks();
97
- vi.spyOn(console, "warn").mockImplementation(() => {});
98
- });
99
-
100
- it("carries the usage's overrides on its own resolved server only", async () => {
101
- // Riding the server is the scoping mechanism: an override can no longer
102
- // reach a same-named tool on another server, because it never exists
103
- // anywhere but its own server's object.
104
- const client = clientReturning({
105
- github: httpMcpServer("github"),
106
- slack: httpMcpServer("slack"),
107
- });
108
-
109
- const result = await resolveMcpServers(
110
- client,
111
- [
112
- makeUsage("github", [], [{ toolName: "delete_item", requiresApproval: false }]),
113
- makeUsage("slack"),
114
- ],
115
- {},
116
- "stdio-forbidden",
117
- );
118
-
119
- const bySlug = new Map(result.resolvedServers.map((s) => [s.slug, s]));
120
- expect(bySlug.get("github")!.toolApprovalOverrides).toEqual([
121
- { toolName: "delete_item", requiresApproval: false },
122
- ]);
123
- expect(bySlug.get("slack")!.toolApprovalOverrides).toEqual([]);
124
- });
125
- });
@@ -1,52 +0,0 @@
1
- /**
2
- * Connect backfill for MCP servers without discovered capabilities.
3
- *
4
- * Thin wrapper around the shared connect-backfill module that preserves
5
- * the cursor-specific McpResolutionResult interface (which includes
6
- * cursorConfig). The core backfill logic lives in shared/connect-backfill.ts.
7
- *
8
- * Non-fatal: if connect fails for any server, the original servers are
9
- * kept and execution continues with empty policies (fail-closed).
10
- */
11
-
12
- import type { McpResolutionResult } from "./mcp-resolver.js";
13
- import { toCursorMcpConfig } from "./mcp-resolver.js";
14
- import type { StigmerClient } from "../../client/stigmer-client.js";
15
- import type { McpTransportPosture } from "../../shared/mcp-transport-guard.js";
16
- import type { McpServerUsage } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/spec_pb";
17
- import { backfillMcpServersIfNeeded as sharedBackfill } from "../../shared/connect-backfill.js";
18
-
19
- /**
20
- * Run connect backfill and return an updated cursor-specific
21
- * McpResolutionResult with rebuilt cursorConfig.
22
- */
23
- export async function backfillMcpServersIfNeeded(
24
- client: StigmerClient,
25
- currentResult: McpResolutionResult,
26
- usages: McpServerUsage[],
27
- envVars: Record<string, string>,
28
- org: string,
29
- transportPosture: McpTransportPosture,
30
- onHeartbeat?: () => void,
31
- secretKeys?: ReadonlySet<string>,
32
- ): Promise<McpResolutionResult> {
33
- const updatedServers = await sharedBackfill(
34
- client,
35
- currentResult.resolvedServers,
36
- usages,
37
- envVars,
38
- org,
39
- transportPosture,
40
- onHeartbeat,
41
- secretKeys,
42
- );
43
-
44
- if (updatedServers === currentResult.resolvedServers) {
45
- return currentResult;
46
- }
47
-
48
- return {
49
- cursorConfig: toCursorMcpConfig(updatedServers),
50
- resolvedServers: updatedServers,
51
- };
52
- }
@@ -1,324 +0,0 @@
1
- /**
2
- * Resolves Stigmer McpServerUsage references into Cursor SDK McpServerConfig
3
- * and loads tool approval policies from each MCP server resource.
4
- *
5
- * Stigmer sessions reference MCP servers by slug via McpServerUsage. Each
6
- * usage points to an McpServer resource that has connection config (stdio
7
- * or HTTP) and tool approval policies (system-generated + manual overrides).
8
- *
9
- * This module fetches those resources and produces:
10
- * 1. Cursor SDK McpServerConfig for Agent.create()
11
- * 2. Tool approval policies for the HITL hook script
12
- *
13
- * Secrets (env vars, headers) come from the merged execution context,
14
- * same pipeline as the Python agent-runner's config_transformer.py.
15
- */
16
-
17
- import type { McpServerUsage, ToolApprovalOverride } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/spec_pb";
18
- import type { McpServer } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/api_pb";
19
- import type { ToolApprovalPolicy } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/spec_pb";
20
- import type { StigmerClient } from "../../client/stigmer-client.js";
21
- import {
22
- assertTransportAllowed,
23
- McpTransportError,
24
- type McpTransportPosture,
25
- } from "../../shared/mcp-transport-guard.js";
26
- import {
27
- resolveHeaders,
28
- resolvePlaceholders,
29
- filterEnvToDeclaredKeys,
30
- PlaceholderResolutionError,
31
- } from "./placeholder-resolver.js";
32
- import { effectiveEnabledTools } from "../../shared/mcp-enabled-tools.js";
33
-
34
- /**
35
- * Cursor SDK MCP server config shape (matches @cursor/sdk McpServerConfig).
36
- * Defined here to avoid tight coupling to the SDK's internal types.
37
- */
38
- export type CursorMcpServerConfig =
39
- | {
40
- type?: "stdio";
41
- command: string;
42
- args?: string[];
43
- env?: Record<string, string>;
44
- cwd?: string;
45
- }
46
- | {
47
- type?: "http" | "sse";
48
- url: string;
49
- headers?: Record<string, string>;
50
- };
51
-
52
- /**
53
- * Resolved MCP server with connection config and approval policies.
54
- *
55
- * Extends the basic connection info with the full policy data needed
56
- * for HITL enforcement. Loaded in a single fetch per server to avoid
57
- * extra round-trips.
58
- */
59
- export interface ResolvedMcpServer {
60
- slug: string;
61
- connectionType: "stdio" | "http" | "sse";
62
- command?: string;
63
- args?: string[];
64
- env?: Record<string, string>;
65
- cwd?: string;
66
- url?: string;
67
- headers?: Record<string, string>;
68
- /** System-generated approval policies from the connect flow's LLM classifier. */
69
- toolApprovals: ToolApprovalPolicy[];
70
- /** Manual overrides set by the MCP server owner. */
71
- pinnedToolApprovals: ToolApprovalPolicy[];
72
- /** True when the server has never been connected (no tool discovery yet). */
73
- discoveredCapabilitiesEmpty: boolean;
74
- /**
75
- * The EFFECTIVE tool allow-list for this server (issue #350): the usage's
76
- * enabled_tools, falling back to the server's default_enabled_tools when
77
- * the usage's list is empty (see shared/mcp-enabled-tools.ts). Absent when
78
- * unrestricted. The Cursor SDK config cannot hide tools, so this harness
79
- * enforces it in the HITL hook: the restricted map is written into the
80
- * approval state (mcpServerEnabledTools) and non-enabled calls are denied
81
- * with the non-pausing "disabled" kind (see hook-script.ts).
82
- */
83
- enabledTools?: string[];
84
- /**
85
- * Layer-3 per-agent approval overrides from the usage that resolved this
86
- * server (issue #349): riding the resolved server is what SCOPES an
87
- * override to its own server — a flat cross-server list is how an
88
- * override once leaked onto (or silently un-gated) a same-named tool on
89
- * another server. Deliberately REQUIRED, not optional: empty means "no
90
- * overrides", and every construction site — including the shared
91
- * resolver this one mirrors and the synthesized attachments, which have
92
- * no usage and therefore no layer 3 — must say so explicitly, so a
93
- * forgotten mirror cannot compile. Consumed by mergeApprovalPolicies
94
- * (shared/approval-policy.ts).
95
- */
96
- toolApprovalOverrides: ToolApprovalOverride[];
97
- }
98
-
99
- /**
100
- * Result of resolving MCP servers: Cursor SDK config for Agent.create()
101
- * and the full resolved server list for policy evaluation.
102
- */
103
- export interface McpResolutionResult {
104
- cursorConfig: Record<string, CursorMcpServerConfig>;
105
- resolvedServers: ResolvedMcpServer[];
106
- }
107
-
108
- /**
109
- * Fetch McpServer resources for each merged usage and resolve into
110
- * Cursor SDK config format plus approval policies.
111
- *
112
- * Replicates the Python agent-runner's pattern:
113
- * 1. For each McpServerUsage, use the ref to fetch the full McpServer resource
114
- * 2. Extract connection config (stdio or http) from McpServerSpec
115
- * 3. Extract approval policies from McpServerStatus + McpServerSpec
116
- * 4. Transform into Cursor SDK's mcpServers config
117
- *
118
- * @param transportPosture Whether stdio servers may run here (derive via
119
- * resolveMcpTransportPosture(config.mode)). A stdio server under a
120
- * forbidding posture throws {@link McpTransportError} and fails the
121
- * whole resolution — never degraded to a skipped server.
122
- */
123
- export async function resolveMcpServers(
124
- client: StigmerClient,
125
- usages: McpServerUsage[],
126
- envVars: Record<string, string>,
127
- transportPosture: McpTransportPosture,
128
- ): Promise<McpResolutionResult> {
129
- const resolved: ResolvedMcpServer[] = [];
130
-
131
- for (const usage of usages) {
132
- const ref = usage.mcpServerRef;
133
- if (!ref?.slug) continue;
134
-
135
- try {
136
- const mcpServer = await client.getMcpServerByReference(ref);
137
- const serverEnv = filterEnvToDeclaredKeys(
138
- mcpServer.spec?.env,
139
- envVars,
140
- ref.slug,
141
- );
142
- const server = mcpServerToResolved(
143
- mcpServer,
144
- ref.slug,
145
- serverEnv,
146
- usage.enabledTools,
147
- usage.toolApprovalOverrides ?? [],
148
- );
149
- if (server) {
150
- assertTransportAllowed(server.slug, server.connectionType, transportPosture);
151
- resolved.push(server);
152
- }
153
- } catch (err) {
154
- if (err instanceof McpTransportError) {
155
- // Policy rejection, not a resolution hiccup: swallowing it here
156
- // would mean the agent silently loses tools. Fail the execution.
157
- throw err;
158
- }
159
- if (err instanceof PlaceholderResolutionError) {
160
- console.error(
161
- `MCP server ${ref.org}/${ref.slug}: ${err.message}`,
162
- );
163
- } else {
164
- console.warn(
165
- `Failed to resolve MCP server ${ref.org}/${ref.slug}: ${err instanceof Error ? err.message : err}`,
166
- );
167
- }
168
- }
169
- }
170
-
171
- return {
172
- cursorConfig: toCursorMcpConfig(resolved),
173
- resolvedServers: resolved,
174
- };
175
- }
176
-
177
- /**
178
- * Convert a fetched McpServer resource into our intermediate format,
179
- * including approval policies and discovery state.
180
- */
181
- function mcpServerToResolved(
182
- server: McpServer,
183
- slug: string,
184
- envVars: Record<string, string>,
185
- usageEnabledTools?: readonly string[],
186
- usageToolApprovalOverrides: ToolApprovalOverride[] = [],
187
- ): ResolvedMcpServer | null {
188
- const spec = server.spec;
189
- if (!spec) return null;
190
-
191
- const status = server.status;
192
- const toolApprovals = status?.toolApprovals ?? [];
193
- const pinnedToolApprovals = spec.pinnedToolApprovals ?? [];
194
- const discoveredCapabilitiesEmpty = !status?.discoveredCapabilities
195
- || (status.discoveredCapabilities.tools.length === 0
196
- && status.discoveredCapabilities.resourceTemplates.length === 0);
197
-
198
- const base = {
199
- toolApprovals,
200
- pinnedToolApprovals,
201
- discoveredCapabilitiesEmpty,
202
- enabledTools: effectiveEnabledTools(usageEnabledTools, spec.defaultEnabledTools),
203
- toolApprovalOverrides: usageToolApprovalOverrides,
204
- };
205
-
206
- switch (spec.serverType.case) {
207
- case "stdio": {
208
- const stdio = spec.serverType.value;
209
- if (!stdio.command) return null;
210
- const resolvedArgs = stdio.args.length > 0
211
- ? stdio.args.map((arg, i) =>
212
- resolvePlaceholders(arg, envVars, `stdio arg[${i}]`),
213
- )
214
- : undefined;
215
- return {
216
- slug,
217
- connectionType: "stdio",
218
- command: stdio.command,
219
- args: resolvedArgs,
220
- env: Object.keys(envVars).length > 0 ? { ...envVars } : undefined,
221
- cwd: stdio.workingDir || undefined,
222
- ...base,
223
- };
224
- }
225
- case "http": {
226
- const http = spec.serverType.value;
227
- if (!http.url) return null;
228
- const rawHeaders = Object.keys(http.headers).length > 0 ? http.headers : undefined;
229
- const resolved = rawHeaders
230
- ? resolveHeaders(Object.fromEntries(Object.entries(rawHeaders)), envVars)
231
- : undefined;
232
- return {
233
- slug,
234
- connectionType: "http",
235
- url: http.url,
236
- headers: resolved,
237
- ...base,
238
- };
239
- }
240
- default:
241
- return null;
242
- }
243
- }
244
-
245
- /**
246
- * Transform resolved MCP servers into the Cursor SDK's mcpServers config
247
- * for Agent.create().
248
- */
249
- export function toCursorMcpConfig(
250
- servers: ResolvedMcpServer[],
251
- ): Record<string, CursorMcpServerConfig> {
252
- const result: Record<string, CursorMcpServerConfig> = {};
253
-
254
- for (const server of servers) {
255
- if (server.connectionType === "stdio") {
256
- if (!server.command) continue;
257
- result[server.slug] = {
258
- type: "stdio",
259
- command: server.command,
260
- args: server.args,
261
- env: server.env,
262
- cwd: server.cwd,
263
- };
264
- } else {
265
- if (!server.url) continue;
266
- result[server.slug] = {
267
- type: server.connectionType,
268
- url: server.url,
269
- headers: server.headers,
270
- };
271
- }
272
- }
273
-
274
- return result;
275
- }
276
-
277
- /**
278
- * Extract MCP server slugs from session-level McpServerUsage references.
279
- */
280
- export function extractMcpServerSlugs(usages: McpServerUsage[]): string[] {
281
- return usages
282
- .map((u) => u.mcpServerRef?.slug)
283
- .filter((s): s is string => !!s);
284
- }
285
-
286
- /**
287
- * Validate that resolved MCP servers have their required env vars populated.
288
- * Returns a list of warnings for servers with empty/missing env.
289
- * Used as a pre-flight check before agent.send() to surface config issues early.
290
- */
291
- export function validateMcpServerEnv(
292
- servers: ResolvedMcpServer[],
293
- usages: McpServerUsage[],
294
- envVars: Record<string, string>,
295
- ): string[] {
296
- const warnings: string[] = [];
297
-
298
- for (const usage of usages) {
299
- const slug = usage.mcpServerRef?.slug;
300
- if (!slug) continue;
301
-
302
- const resolved = servers.find((s) => s.slug === slug);
303
- if (!resolved) {
304
- warnings.push(`MCP server '${slug}': failed to resolve (server may not exist or is inaccessible)`);
305
- continue;
306
- }
307
-
308
- if (resolved.connectionType === "stdio" && resolved.env) {
309
- const emptyKeys = Object.entries(resolved.env)
310
- .filter(([, v]) => !v)
311
- .map(([k]) => k);
312
- if (emptyKeys.length > 0) {
313
- warnings.push(
314
- `MCP server '${slug}': env vars [${emptyKeys.join(", ")}] are empty — ` +
315
- `server subprocess will likely fail to connect`,
316
- );
317
- }
318
- }
319
- }
320
-
321
- return warnings;
322
- }
323
-
324
- export { PlaceholderResolutionError } from "./placeholder-resolver.js";
@@ -1,109 +0,0 @@
1
- /**
2
- * Strict placeholder resolver for ${VAR_NAME} syntax in MCP server configs.
3
- *
4
- * Port of the Python agent-runner's PlaceholderResolver (strict mode only).
5
- * Always raises on unresolved placeholders — sending literal ${VAR} as an
6
- * HTTP header value (e.g. Authorization: Bearer ${API_KEY}) produces
7
- * cryptic auth failures from the remote server.
8
- */
9
-
10
- const PLACEHOLDER_RE = /\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g;
11
-
12
- export class PlaceholderResolutionError extends Error {
13
- constructor(
14
- public readonly variableName: string,
15
- public readonly context?: string,
16
- ) {
17
- const where = context ? ` in ${context}` : "";
18
- super(
19
- `Unresolved placeholder \${${variableName}}${where}: ` +
20
- `variable is not present in the execution environment`,
21
- );
22
- this.name = "PlaceholderResolutionError";
23
- }
24
- }
25
-
26
- /**
27
- * Resolve all ${VAR_NAME} placeholders in a template string.
28
- *
29
- * @throws PlaceholderResolutionError if any placeholder cannot be resolved.
30
- */
31
- export function resolvePlaceholders(
32
- template: string,
33
- envVars: Record<string, string>,
34
- context?: string,
35
- ): string {
36
- return template.replace(PLACEHOLDER_RE, (match, varName: string) => {
37
- if (varName in envVars) {
38
- return envVars[varName];
39
- }
40
- throw new PlaceholderResolutionError(varName, context);
41
- });
42
- }
43
-
44
- /**
45
- * Resolve placeholders in all values of a headers map.
46
- *
47
- * @throws PlaceholderResolutionError if any header value contains an
48
- * unresolvable placeholder.
49
- */
50
- export function resolveHeaders(
51
- headers: Record<string, string>,
52
- envVars: Record<string, string>,
53
- ): Record<string, string> {
54
- const resolved: Record<string, string> = {};
55
- for (const [key, value] of Object.entries(headers)) {
56
- resolved[key] = resolvePlaceholders(value, envVars, `header "${key}"`);
57
- }
58
- return resolved;
59
- }
60
-
61
- /**
62
- * Filter env vars to only keys declared in the MCP server's spec.env.
63
- *
64
- * Mirrors the agent-runner's _filter_env_to_declared_keys: prevents
65
- * secret over-sharing by restricting the subprocess/HTTP environment to
66
- * explicitly declared variables.
67
- */
68
- export function filterEnvToDeclaredKeys(
69
- declaredEnv: Record<string, unknown> | undefined,
70
- envVars: Record<string, string>,
71
- serverSlug: string,
72
- ): Record<string, string> {
73
- if (!declaredEnv || Object.keys(declaredEnv).length === 0) {
74
- if (Object.keys(envVars).length > 0) {
75
- console.log(
76
- `MCP server '${serverSlug}' has no env declarations — ` +
77
- `dropping ${Object.keys(envVars).length} env var(s)`,
78
- );
79
- }
80
- return {};
81
- }
82
-
83
- const declaredKeys = new Set(Object.keys(declaredEnv));
84
- const filtered: Record<string, string> = {};
85
-
86
- for (const [key, value] of Object.entries(envVars)) {
87
- if (declaredKeys.has(key)) {
88
- filtered[key] = value;
89
- }
90
- }
91
-
92
- const dropped = Object.keys(envVars).length - Object.keys(filtered).length;
93
- if (dropped > 0) {
94
- console.log(
95
- `MCP server '${serverSlug}': passing ${Object.keys(filtered).length} ` +
96
- `declared env var(s), filtered out ${dropped} undeclared key(s)`,
97
- );
98
- }
99
-
100
- const missing = [...declaredKeys].filter((k) => !(k in filtered));
101
- if (missing.length > 0) {
102
- console.warn(
103
- `MCP server '${serverSlug}': env declares [${missing.sort().join(", ")}] ` +
104
- `but they are not present in the resolved environment`,
105
- );
106
- }
107
-
108
- return filtered;
109
- }