@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.
- package/dist/.build-fingerprint +1 -1
- package/dist/activities/execute-cursor/cursor-mcp-config.d.ts +45 -0
- package/dist/activities/execute-cursor/cursor-mcp-config.js +76 -0
- package/dist/activities/execute-cursor/cursor-mcp-config.js.map +1 -0
- package/dist/activities/execute-cursor/index.d.ts +10 -1
- package/dist/activities/execute-cursor/index.js +47 -29
- package/dist/activities/execute-cursor/index.js.map +1 -1
- package/dist/activities/execute-cursor/prompt-builder.d.ts +13 -2
- package/dist/activities/execute-cursor/prompt-builder.js +11 -0
- package/dist/activities/execute-cursor/prompt-builder.js.map +1 -1
- package/dist/activities/execute-cursor/session-lifecycle.d.ts +1 -1
- package/dist/activities/execute-cursor/skill-resolver.d.ts +2 -21
- package/dist/activities/execute-cursor/skill-resolver.js +5 -108
- package/dist/activities/execute-cursor/skill-resolver.js.map +1 -1
- package/dist/activities/execute-deep-agent/approval-file-change.d.ts +9 -0
- package/dist/activities/execute-deep-agent/approval-file-change.js +2 -1
- package/dist/activities/execute-deep-agent/approval-file-change.js.map +1 -1
- package/dist/activities/execute-deep-agent/cas-capture-backend.d.ts +18 -1
- package/dist/activities/execute-deep-agent/cas-capture-backend.js +20 -3
- package/dist/activities/execute-deep-agent/cas-capture-backend.js.map +1 -1
- package/dist/activities/execute-deep-agent/index.js +57 -5
- package/dist/activities/execute-deep-agent/index.js.map +1 -1
- package/dist/activities/execute-deep-agent/prompt-builder.d.ts +9 -0
- package/dist/activities/execute-deep-agent/prompt-builder.js +10 -0
- package/dist/activities/execute-deep-agent/prompt-builder.js.map +1 -1
- package/dist/activities/execute-deep-agent/setup.js +17 -16
- package/dist/activities/execute-deep-agent/setup.js.map +1 -1
- package/dist/activities/execute-deep-agent/subagent-transformer.js +17 -11
- package/dist/activities/execute-deep-agent/subagent-transformer.js.map +1 -1
- package/dist/activities/execute-deep-agent/subagent-wiring.d.ts +3 -3
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/middleware/path-normalization.d.ts +49 -49
- package/dist/middleware/path-normalization.js +73 -84
- package/dist/middleware/path-normalization.js.map +1 -1
- package/dist/middleware/types.d.ts +3 -3
- package/dist/runner-manager.d.ts +1 -1
- package/dist/runner-manager.js +22 -15
- package/dist/runner-manager.js.map +1 -1
- package/dist/runner.js +11 -0
- package/dist/runner.js.map +1 -1
- package/dist/shared/args-preview.d.ts +8 -0
- package/dist/shared/args-preview.js +14 -3
- package/dist/shared/args-preview.js.map +1 -1
- package/dist/shared/artifact-storage.d.ts +11 -1
- package/dist/shared/artifact-storage.js +10 -1
- package/dist/shared/artifact-storage.js.map +1 -1
- package/dist/shared/declared-preferences.d.ts +47 -0
- package/dist/shared/declared-preferences.js +64 -0
- package/dist/shared/declared-preferences.js.map +1 -0
- package/dist/shared/mcp-enabled-tools.d.ts +2 -3
- package/dist/shared/mcp-enabled-tools.js +2 -3
- package/dist/shared/mcp-enabled-tools.js.map +1 -1
- package/dist/shared/mcp-resolver.d.ts +20 -15
- package/dist/shared/mcp-resolver.js +11 -12
- package/dist/shared/mcp-resolver.js.map +1 -1
- package/dist/shared/placeholder-resolver.d.ts +9 -2
- package/dist/shared/placeholder-resolver.js +9 -2
- package/dist/shared/placeholder-resolver.js.map +1 -1
- package/dist/shared/plan-mode-permissions.d.ts +26 -46
- package/dist/shared/plan-mode-permissions.js +27 -55
- package/dist/shared/plan-mode-permissions.js.map +1 -1
- package/dist/shared/skill-mount.d.ts +89 -0
- package/dist/shared/skill-mount.js +142 -0
- package/dist/shared/skill-mount.js.map +1 -0
- package/dist/shared/skill-writer.d.ts +28 -26
- package/dist/shared/skill-writer.js +79 -102
- package/dist/shared/skill-writer.js.map +1 -1
- package/dist/shared/worker-shutdown.d.ts +67 -0
- package/dist/shared/worker-shutdown.js +79 -0
- package/dist/shared/worker-shutdown.js.map +1 -0
- package/dist/shared/workspace/types.d.ts +3 -2
- package/dist/workflow-engine/loader.js +38 -1
- package/dist/workflow-engine/loader.js.map +1 -1
- package/dist/workflow-engine/tasks/human-input.js +23 -2
- package/dist/workflow-engine/tasks/human-input.js.map +1 -1
- package/package.json +15 -11
- package/src/activities/execute-cursor/__tests__/build-prompt.test.ts +50 -0
- package/src/activities/execute-cursor/__tests__/cursor-mcp-config.test.ts +111 -0
- package/src/activities/execute-cursor/__tests__/skill-resolver.test.ts +3 -94
- package/src/activities/execute-cursor/cursor-mcp-config.ts +107 -0
- package/src/activities/execute-cursor/index.ts +63 -35
- package/src/activities/execute-cursor/prompt-builder.ts +29 -2
- package/src/activities/execute-cursor/session-lifecycle.ts +1 -1
- package/src/activities/execute-cursor/skill-resolver.ts +10 -137
- package/src/activities/execute-deep-agent/__tests__/approval-file-change.test.ts +15 -0
- package/src/activities/execute-deep-agent/__tests__/cas-capture-backend.test.ts +75 -0
- package/src/activities/execute-deep-agent/__tests__/hitl-reject.test.ts +1 -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__/index.test.ts +1 -0
- package/src/activities/execute-deep-agent/__tests__/plan-mode-path-normalization.test.ts +42 -29
- package/src/activities/execute-deep-agent/__tests__/prompt-builder.test.ts +52 -0
- 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 +11 -8
- package/src/activities/execute-deep-agent/approval-file-change.ts +11 -1
- package/src/activities/execute-deep-agent/cas-capture-backend.ts +20 -3
- package/src/activities/execute-deep-agent/index.ts +57 -5
- package/src/activities/execute-deep-agent/prompt-builder.ts +22 -0
- package/src/activities/execute-deep-agent/setup.ts +21 -19
- package/src/activities/execute-deep-agent/subagent-transformer.ts +19 -12
- package/src/activities/execute-deep-agent/subagent-wiring.ts +3 -3
- package/src/index.ts +1 -1
- package/src/middleware/__tests__/path-normalization.test.ts +32 -32
- package/src/middleware/path-normalization.ts +78 -90
- package/src/middleware/types.ts +3 -3
- package/src/runner-manager.ts +26 -16
- package/src/runner.ts +14 -0
- package/src/shared/__tests__/artifact-storage.test.ts +28 -0
- package/src/shared/__tests__/bedrock-seam.test.ts +5 -5
- package/src/shared/__tests__/declared-preferences.test.ts +109 -0
- package/src/shared/__tests__/foundry-seam.test.ts +5 -5
- package/src/shared/__tests__/plan-mode-permissions.test.ts +15 -44
- package/src/shared/__tests__/skill-mount.test.ts +238 -0
- package/src/shared/__tests__/skill-writer.test.ts +151 -128
- package/src/shared/__tests__/vertex-adapter.test.ts +30 -3
- package/src/shared/__tests__/vertex-seam.test.ts +5 -5
- package/src/shared/__tests__/worker-shutdown.test.ts +101 -0
- package/src/shared/args-preview.ts +17 -3
- package/src/shared/artifact-storage.ts +20 -1
- package/src/shared/declared-preferences.ts +84 -0
- package/src/shared/mcp-enabled-tools.ts +2 -3
- package/src/shared/mcp-resolver.ts +20 -20
- package/src/shared/placeholder-resolver.ts +9 -2
- package/src/shared/plan-mode-permissions.ts +27 -58
- package/src/shared/skill-mount.ts +179 -0
- package/src/shared/skill-writer.ts +96 -130
- package/src/shared/worker-shutdown.ts +99 -0
- package/src/shared/workspace/types.ts +3 -2
- package/src/workflow-engine/__tests__/loader.test.ts +51 -0
- package/src/workflow-engine/__tests__/tasks/human-input.test.ts +117 -0
- package/src/workflow-engine/loader.ts +46 -1
- package/src/workflow-engine/tasks/human-input.ts +33 -5
- package/dist/activities/execute-cursor/connect-backfill.d.ts +0 -19
- package/dist/activities/execute-cursor/connect-backfill.js +0 -27
- package/dist/activities/execute-cursor/connect-backfill.js.map +0 -1
- package/dist/activities/execute-cursor/mcp-resolver.d.ts +0 -120
- package/dist/activities/execute-cursor/mcp-resolver.js +0 -194
- package/dist/activities/execute-cursor/mcp-resolver.js.map +0 -1
- package/dist/activities/execute-cursor/placeholder-resolver.d.ts +0 -34
- package/dist/activities/execute-cursor/placeholder-resolver.js +0 -82
- package/dist/activities/execute-cursor/placeholder-resolver.js.map +0 -1
- package/src/activities/execute-cursor/__tests__/mcp-resolver.test.ts +0 -125
- package/src/activities/execute-cursor/connect-backfill.ts +0 -52
- package/src/activities/execute-cursor/mcp-resolver.ts +0 -324
- 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
|
-
}
|