@spunto/build 0.20.4 → 0.21.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spunto/build",
3
- "version": "0.20.4",
3
+ "version": "0.21.0",
4
4
  "description": "Spunto's shared Build engine — the devcontainer image protocol and VS Code extension registry clients, with no database, no HTTP framework and no UI.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -47,6 +47,15 @@ export type AgentCatalog = {
47
47
  /** The variable a task's chosen parameters reach the runner in, as one JSON object. */
48
48
  export const OPTIONS_ENV = "SPUNTO_AGENT_OPTIONS"
49
49
 
50
+ /**
51
+ * The variable the org's MCP servers reach the runner in (RFC 0049): a JSON array of
52
+ * `{ name, path }`, `path` being the platform's gateway for that server, resolved by the runner
53
+ * against `SPUNTO_API_URL` and called with `SPUNTO_WORKER_TOKEN`. No vendor token, no harness shape.
54
+ */
55
+ export const MCP_ENV = "SPUNTO_AGENT_MCP"
56
+
57
+ export type AgentMcpServer = { name: string; path: string }
58
+
50
59
  const CLAUDE_EFFORT: AgentParamValue[] = [
51
60
  { value: "low", label: "Low" },
52
61
  { value: "medium", label: "Medium" },
@@ -13,6 +13,9 @@
13
13
  // adapter it already had (`claude-runner`)
14
14
  // env SPUNTO_AGENT_OPTIONS — the task's parameters, as declared by the app's catalog
15
15
  // (`./catalogs.ts`): `{"effort":"high"}`
16
+ // SPUNTO_AGENT_MCP — the org's MCP servers (RFC 0049), in the platform's words:
17
+ // `[{"name":"linear","path":"/api/mcp-gateway/…"}]`, reached through the platform's
18
+ // gateway with the machine's own identity (`SPUNTO_WORKER_TOKEN`)
16
19
  // exit 0 once the input is closed and the last turn is over, 1 when the session could not run
17
20
  //
18
21
  // Credentials are not this file's business: whatever the member set as secrets (an
@@ -90,6 +93,30 @@ function readOptions() {
90
93
  }
91
94
  const params = readOptions()
92
95
 
96
+ /**
97
+ * The org's MCP servers (RFC 0049), as the SDK's `mcpServers`. Each is the platform's gateway, not
98
+ * the vendor's server: the machine shows its own identity and never holds the vendor's token. One
99
+ * that can't be reached (no API URL, no identity) is left out rather than failing the session.
100
+ */
101
+ function readMcpServers() {
102
+ let listed
103
+ try {
104
+ listed = JSON.parse(process.env.SPUNTO_AGENT_MCP || "[]")
105
+ } catch {
106
+ return {}
107
+ }
108
+ const api = (process.env.SPUNTO_API_URL || "").replace(/\/$/, "")
109
+ const token = process.env.SPUNTO_WORKER_TOKEN
110
+ if (!Array.isArray(listed) || !api || !token) return {}
111
+ const servers = {}
112
+ for (const s of listed) {
113
+ if (!s || typeof s.name !== "string" || typeof s.path !== "string") continue
114
+ servers[s.name] = { type: "http", url: `${api}${s.path}`, headers: { Authorization: `Bearer ${token}` } }
115
+ }
116
+ return servers
117
+ }
118
+ const mcpServers = readMcpServers()
119
+
93
120
  let sdk
94
121
  try {
95
122
  sdk = await import(SDK)
@@ -105,6 +132,8 @@ const session = sdk.query({
105
132
  ...(args.model ? { model: args.model } : {}),
106
133
  ...(args.resume ? { resume: args.resume } : {}),
107
134
  ...(typeof params.effort === "string" ? { effort: params.effort } : {}),
135
+ // Added to the servers Claude Code reads from its settings (`.mcp.json`, `~/.claude.json`).
136
+ ...(Object.keys(mcpServers).length ? { mcpServers } : {}),
108
137
  // A task runs unattended in a disposable machine — what `--dangerously-skip-permissions` did.
109
138
  permissionMode: "bypassPermissions",
110
139
  allowDangerouslySkipPermissions: true,
@@ -6,6 +6,7 @@
6
6
  // stdin {"type":"user","text"} · {"type":"interrupt","id"} · {"type":"title",…} (ignored: Codex
7
7
  // does not name its threads) ; closed = the session ends after its turn
8
8
  // stdout the SDK's `ThreadEvent`s, one per line (`codex-runner` in the platform's adapters)
9
+ // env SPUNTO_AGENT_MCP — the org's MCP servers, through the platform's gateway (RFC 0049)
9
10
  // env SPUNTO_AGENT_OPTIONS — the task's parameters (`./catalogs.ts`):
10
11
  // `{"reasoningEffort":"high","webSearch":"live"}`
11
12
  // exit 0 once the input is closed and the last turn is over, 1 when the session could not run
@@ -59,6 +60,29 @@ function readOptions() {
59
60
  }
60
61
  const params = readOptions()
61
62
 
63
+ /**
64
+ * The org's MCP servers (RFC 0049), as Codex's `mcp_servers` config. Each is the platform's
65
+ * gateway: Codex reads the machine's identity from `SPUNTO_WORKER_TOKEN` itself
66
+ * (`bearer_token_env_var`), and never holds the vendor's token.
67
+ */
68
+ function readMcpServers() {
69
+ let listed
70
+ try {
71
+ listed = JSON.parse(process.env.SPUNTO_AGENT_MCP || "[]")
72
+ } catch {
73
+ return {}
74
+ }
75
+ const api = (process.env.SPUNTO_API_URL || "").replace(/\/$/, "")
76
+ if (!Array.isArray(listed) || !api || !process.env.SPUNTO_WORKER_TOKEN) return {}
77
+ const servers = {}
78
+ for (const s of listed) {
79
+ if (!s || typeof s.name !== "string" || typeof s.path !== "string") continue
80
+ servers[s.name] = { url: `${api}${s.path}`, bearer_token_env_var: "SPUNTO_WORKER_TOKEN" }
81
+ }
82
+ return servers
83
+ }
84
+ const mcpServers = readMcpServers()
85
+
62
86
  let sdk
63
87
  try {
64
88
  sdk = await import(SDK)
@@ -67,7 +91,10 @@ try {
67
91
  }
68
92
 
69
93
  const apiKey = process.env.OPENAI_API_KEY || process.env.CODEX_API_KEY
70
- const codex = new sdk.Codex(apiKey ? { apiKey } : {})
94
+ const codex = new sdk.Codex({
95
+ ...(apiKey ? { apiKey } : {}),
96
+ ...(Object.keys(mcpServers).length ? { config: { mcp_servers: mcpServers } } : {}),
97
+ })
71
98
  const threadOptions = {
72
99
  workingDirectory: process.cwd(),
73
100
  ...(args.model ? { model: args.model } : {}),
@@ -91,5 +91,5 @@ export function harnessOfFeature(featureId: string): AgentHarness | null {
91
91
  }
92
92
 
93
93
  export { AGENT_FEATURE_SCRIPTS, AGENT_HOME, AGENT_NODE_VERSION } from "./features"
94
- export { AGENT_CATALOGS, OPTIONS_ENV, agentOptionsError } from "./catalogs"
95
- export type { AgentCatalog, AgentModel, AgentParam, AgentParamValue } from "./catalogs"
94
+ export { AGENT_CATALOGS, MCP_ENV, OPTIONS_ENV, agentOptionsError } from "./catalogs"
95
+ export type { AgentCatalog, AgentMcpServer, AgentModel, AgentParam, AgentParamValue } from "./catalogs"
@@ -23,9 +23,10 @@ echo "[feature] google-cloud-cli: installing..."
23
23
  apt-get update -y -qq
24
24
  apt-get install -y -qq apt-transport-https ca-certificates gnupg curl
25
25
 
26
- # Modern apt key setup (no deprecated apt-key)
26
+ # Modern apt key setup (no deprecated apt-key). --batch --yes: a recipe re-baked in place finds the
27
+ # keyring of its first bake, and gpg asks on /dev/tty before overwriting it — no tty, exit 2.
27
28
  curl -fsSL https://packages.cloud.google.com/apt/doc/apt-key.gpg \
28
- | gpg --dearmor -o /usr/share/keyrings/cloud.google.gpg
29
+ | gpg --batch --yes --dearmor -o /usr/share/keyrings/cloud.google.gpg
29
30
 
30
31
  echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] https://packages.cloud.google.com/apt cloud-sdk main" \
31
32
  | tee /etc/apt/sources.list.d/google-cloud-sdk.list