@finalai/agent-provider 0.1.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.
Potentially problematic release.
This version of @finalai/agent-provider might be problematic. Click here for more details.
- package/README.md +12 -0
- package/dist/modules/channel-context.d.mts +102 -0
- package/dist/modules/channel-context.mjs +52 -0
- package/dist/modules/channel-context.mjs.map +7 -0
- package/dist/modules/channel-socket.d.mts +65 -0
- package/dist/modules/channel-socket.mjs +188 -0
- package/dist/modules/channel-socket.mjs.map +7 -0
- package/dist/modules/channel.d.mts +382 -0
- package/dist/modules/channel.mjs +146 -0
- package/dist/modules/channel.mjs.map +7 -0
- package/dist/modules/conformance/agent.d.mts +20 -0
- package/dist/modules/conformance/agent.mjs +623 -0
- package/dist/modules/conformance/agent.mjs.map +7 -0
- package/dist/modules/conformance/fixtures.d.mts +41 -0
- package/dist/modules/conformance/fixtures.mjs +190 -0
- package/dist/modules/conformance/fixtures.mjs.map +7 -0
- package/dist/modules/conformance/harness-kit.d.mts +82 -0
- package/dist/modules/conformance/harness-kit.mjs +1303 -0
- package/dist/modules/conformance/harness-kit.mjs.map +7 -0
- package/dist/modules/conformance/image.d.mts +2 -0
- package/dist/modules/conformance/image.mjs +79 -0
- package/dist/modules/conformance/image.mjs.map +7 -0
- package/dist/modules/conformance/index.d.mts +8 -0
- package/dist/modules/conformance/index.mjs +12 -0
- package/dist/modules/conformance/index.mjs.map +7 -0
- package/dist/modules/conformance/local-harness.d.mts +25 -0
- package/dist/modules/conformance/local-harness.mjs +108 -0
- package/dist/modules/conformance/local-harness.mjs.map +7 -0
- package/dist/modules/conformance/protocol.d.mts +30 -0
- package/dist/modules/conformance/protocol.mjs +56 -0
- package/dist/modules/conformance/protocol.mjs.map +7 -0
- package/dist/modules/conformance/sandbox-kit.d.mts +19 -0
- package/dist/modules/conformance/sandbox-kit.mjs +320 -0
- package/dist/modules/conformance/sandbox-kit.mjs.map +7 -0
- package/dist/modules/conformance/scripted-model/chat-completions.d.mts +30 -0
- package/dist/modules/conformance/scripted-model/chat-completions.mjs +125 -0
- package/dist/modules/conformance/scripted-model/chat-completions.mjs.map +7 -0
- package/dist/modules/conformance/scripted-model/directives.d.mts +84 -0
- package/dist/modules/conformance/scripted-model/directives.mjs +304 -0
- package/dist/modules/conformance/scripted-model/directives.mjs.map +7 -0
- package/dist/modules/conformance/scripted-model/handler.d.mts +14 -0
- package/dist/modules/conformance/scripted-model/handler.mjs +133 -0
- package/dist/modules/conformance/scripted-model/handler.mjs.map +7 -0
- package/dist/modules/conformance/scripted-model/index.d.mts +6 -0
- package/dist/modules/conformance/scripted-model/index.mjs +7 -0
- package/dist/modules/conformance/scripted-model/index.mjs.map +7 -0
- package/dist/modules/conformance/scripted-model/messages.d.mts +39 -0
- package/dist/modules/conformance/scripted-model/messages.mjs +139 -0
- package/dist/modules/conformance/scripted-model/messages.mjs.map +7 -0
- package/dist/modules/conformance/scripted-model/responses.d.mts +62 -0
- package/dist/modules/conformance/scripted-model/responses.mjs +273 -0
- package/dist/modules/conformance/scripted-model/responses.mjs.map +7 -0
- package/dist/modules/conformance/scripted-model/sse.d.mts +15 -0
- package/dist/modules/conformance/scripted-model/sse.mjs +75 -0
- package/dist/modules/conformance/scripted-model/sse.mjs.map +7 -0
- package/dist/modules/conformance/v1-agent.d.mts +24 -0
- package/dist/modules/conformance/v1-agent.mjs +40 -0
- package/dist/modules/conformance/v1-agent.mjs.map +7 -0
- package/dist/modules/credential-routes.d.mts +24 -0
- package/dist/modules/credential-routes.mjs +25 -0
- package/dist/modules/credential-routes.mjs.map +7 -0
- package/dist/modules/credentials.d.mts +206 -0
- package/dist/modules/credentials.mjs +207 -0
- package/dist/modules/credentials.mjs.map +7 -0
- package/dist/modules/dynamic.d.mts +32 -0
- package/dist/modules/dynamic.mjs +77 -0
- package/dist/modules/dynamic.mjs.map +7 -0
- package/dist/modules/harness.d.mts +397 -0
- package/dist/modules/harness.mjs +213 -0
- package/dist/modules/harness.mjs.map +7 -0
- package/dist/modules/index.d.mts +15 -0
- package/dist/modules/index.mjs +40 -0
- package/dist/modules/index.mjs.map +7 -0
- package/dist/modules/json.d.mts +12 -0
- package/dist/modules/json.mjs +19 -0
- package/dist/modules/json.mjs.map +7 -0
- package/dist/modules/local-channel-socket.d.mts +23 -0
- package/dist/modules/local-channel-socket.mjs +141 -0
- package/dist/modules/local-channel-socket.mjs.map +7 -0
- package/dist/modules/manifest.d.mts +558 -0
- package/dist/modules/manifest.mjs +266 -0
- package/dist/modules/manifest.mjs.map +7 -0
- package/dist/modules/mcp-content.d.mts +21 -0
- package/dist/modules/mcp-content.mjs +86 -0
- package/dist/modules/mcp-content.mjs.map +7 -0
- package/dist/modules/provider-ref.d.mts +29 -0
- package/dist/modules/provider-ref.mjs +55 -0
- package/dist/modules/provider-ref.mjs.map +7 -0
- package/dist/modules/sandbox/bytes.d.mts +9 -0
- package/dist/modules/sandbox/bytes.mjs +154 -0
- package/dist/modules/sandbox/bytes.mjs.map +7 -0
- package/dist/modules/sandbox/client.d.mts +51 -0
- package/dist/modules/sandbox/client.mjs +500 -0
- package/dist/modules/sandbox/client.mjs.map +7 -0
- package/dist/modules/sandbox/confined-files.d.mts +22 -0
- package/dist/modules/sandbox/confined-files.mjs +142 -0
- package/dist/modules/sandbox/confined-files.mjs.map +7 -0
- package/dist/modules/sandbox/convenience.d.mts +10 -0
- package/dist/modules/sandbox/convenience.mjs +116 -0
- package/dist/modules/sandbox/convenience.mjs.map +7 -0
- package/dist/modules/sandbox/editor.d.mts +49 -0
- package/dist/modules/sandbox/editor.mjs +43 -0
- package/dist/modules/sandbox/editor.mjs.map +7 -0
- package/dist/modules/sandbox/frame-channel.d.mts +47 -0
- package/dist/modules/sandbox/frame-channel.mjs +153 -0
- package/dist/modules/sandbox/frame-channel.mjs.map +7 -0
- package/dist/modules/sandbox/index.d.mts +18 -0
- package/dist/modules/sandbox/index.mjs +70 -0
- package/dist/modules/sandbox/index.mjs.map +7 -0
- package/dist/modules/sandbox/mcp-channel.d.mts +9 -0
- package/dist/modules/sandbox/mcp-channel.mjs +77 -0
- package/dist/modules/sandbox/mcp-channel.mjs.map +7 -0
- package/dist/modules/sandbox/mcp-hosted.d.mts +46 -0
- package/dist/modules/sandbox/mcp-hosted.mjs +221 -0
- package/dist/modules/sandbox/mcp-hosted.mjs.map +7 -0
- package/dist/modules/sandbox/mcp-search.d.mts +51 -0
- package/dist/modules/sandbox/mcp-search.mjs +129 -0
- package/dist/modules/sandbox/mcp-search.mjs.map +7 -0
- package/dist/modules/sandbox/mcp-shell.d.mts +21 -0
- package/dist/modules/sandbox/mcp-shell.mjs +205 -0
- package/dist/modules/sandbox/mcp-shell.mjs.map +7 -0
- package/dist/modules/sandbox/mcp-testing.d.mts +11 -0
- package/dist/modules/sandbox/mcp-testing.mjs +33 -0
- package/dist/modules/sandbox/mcp-testing.mjs.map +7 -0
- package/dist/modules/sandbox/mcp.d.mts +83 -0
- package/dist/modules/sandbox/mcp.mjs +971 -0
- package/dist/modules/sandbox/mcp.mjs.map +7 -0
- package/dist/modules/sandbox/memory-sandbox.d.mts +32 -0
- package/dist/modules/sandbox/memory-sandbox.mjs +416 -0
- package/dist/modules/sandbox/memory-sandbox.mjs.map +7 -0
- package/dist/modules/sandbox/memory-shell.d.mts +29 -0
- package/dist/modules/sandbox/memory-shell.mjs +278 -0
- package/dist/modules/sandbox/memory-shell.mjs.map +7 -0
- package/dist/modules/sandbox/open-scope-node.d.mts +4 -0
- package/dist/modules/sandbox/open-scope-node.mjs +19 -0
- package/dist/modules/sandbox/open-scope-node.mjs.map +7 -0
- package/dist/modules/sandbox/open-scope.d.mts +16 -0
- package/dist/modules/sandbox/open-scope.mjs +66 -0
- package/dist/modules/sandbox/open-scope.mjs.map +7 -0
- package/dist/modules/sandbox/paths.d.mts +7 -0
- package/dist/modules/sandbox/paths.mjs +50 -0
- package/dist/modules/sandbox/paths.mjs.map +7 -0
- package/dist/modules/sandbox/rpc-peer.d.mts +35 -0
- package/dist/modules/sandbox/rpc-peer.mjs +224 -0
- package/dist/modules/sandbox/rpc-peer.mjs.map +7 -0
- package/dist/modules/sandbox/seeds.d.mts +26 -0
- package/dist/modules/sandbox/seeds.mjs +228 -0
- package/dist/modules/sandbox/seeds.mjs.map +7 -0
- package/dist/modules/sandbox/server.d.mts +66 -0
- package/dist/modules/sandbox/server.mjs +567 -0
- package/dist/modules/sandbox/server.mjs.map +7 -0
- package/dist/modules/sandbox/snapshots.d.mts +49 -0
- package/dist/modules/sandbox/snapshots.mjs +55 -0
- package/dist/modules/sandbox/snapshots.mjs.map +7 -0
- package/dist/modules/sandbox/stream-mux.d.mts +18 -0
- package/dist/modules/sandbox/stream-mux.mjs +285 -0
- package/dist/modules/sandbox/stream-mux.mjs.map +7 -0
- package/dist/modules/sandbox/testing.d.mts +25 -0
- package/dist/modules/sandbox/testing.mjs +64 -0
- package/dist/modules/sandbox/testing.mjs.map +7 -0
- package/dist/modules/sandbox/types.d.mts +352 -0
- package/dist/modules/sandbox/types.mjs +86 -0
- package/dist/modules/sandbox/types.mjs.map +7 -0
- package/dist/modules/sandbox/wire.d.mts +671 -0
- package/dist/modules/sandbox/wire.mjs +537 -0
- package/dist/modules/sandbox/wire.mjs.map +7 -0
- package/dist/modules/sandbox/workspace-root.d.mts +8 -0
- package/dist/modules/sandbox/workspace-root.mjs +79 -0
- package/dist/modules/sandbox/workspace-root.mjs.map +7 -0
- package/dist/modules/schemas.d.mts +121 -0
- package/dist/modules/schemas.mjs +146 -0
- package/dist/modules/schemas.mjs.map +7 -0
- package/dist/modules/semantic-events.d.mts +31 -0
- package/dist/modules/semantic-events.mjs +23 -0
- package/dist/modules/semantic-events.mjs.map +7 -0
- package/package.json +199 -0
|
@@ -0,0 +1,971 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createMcpHandler,
|
|
3
|
+
fromJsonSchema,
|
|
4
|
+
McpServer,
|
|
5
|
+
ResourceTemplate
|
|
6
|
+
} from "@modelcontextprotocol/server";
|
|
7
|
+
import * as z from "zod";
|
|
8
|
+
import { SandboxAccess, ToolApproval } from "../manifest.mjs";
|
|
9
|
+
import { bytesToBase64, utf8Decode } from "./bytes.mjs";
|
|
10
|
+
import {
|
|
11
|
+
createHostedMcpConnections,
|
|
12
|
+
EMPTY_MCP_CATALOG,
|
|
13
|
+
hostedTools,
|
|
14
|
+
SandboxHostedServerKind
|
|
15
|
+
} from "./mcp-hosted.mjs";
|
|
16
|
+
import { globFiles, grepFiles } from "./mcp-search.mjs";
|
|
17
|
+
import { createSandboxShell, SandboxShellMode } from "./mcp-shell.mjs";
|
|
18
|
+
import { resolvePosixPath } from "./paths.mjs";
|
|
19
|
+
import {
|
|
20
|
+
PortProtocol,
|
|
21
|
+
SandboxCapability,
|
|
22
|
+
SandboxEntryType
|
|
23
|
+
} from "./types.mjs";
|
|
24
|
+
import {
|
|
25
|
+
COMPUTER_USE_SERVER_NAME,
|
|
26
|
+
SandboxHostedServerKind as SandboxHostedServerKind2,
|
|
27
|
+
withHostedMcpEndpoints
|
|
28
|
+
} from "./mcp-hosted.mjs";
|
|
29
|
+
import { SandboxShellMode as SandboxShellMode2 } from "./mcp-shell.mjs";
|
|
30
|
+
var SandboxMcpTool = /* @__PURE__ */ ((SandboxMcpTool2) => {
|
|
31
|
+
SandboxMcpTool2["Run"] = "run";
|
|
32
|
+
SandboxMcpTool2["Spawn"] = "spawn";
|
|
33
|
+
SandboxMcpTool2["ProcessOutput"] = "process_output";
|
|
34
|
+
SandboxMcpTool2["ProcessInput"] = "process_input";
|
|
35
|
+
SandboxMcpTool2["ProcessTerminate"] = "process_terminate";
|
|
36
|
+
SandboxMcpTool2["ReadFile"] = "read_file";
|
|
37
|
+
SandboxMcpTool2["WriteFile"] = "write_file";
|
|
38
|
+
SandboxMcpTool2["EditFile"] = "edit_file";
|
|
39
|
+
SandboxMcpTool2["ListDir"] = "list_dir";
|
|
40
|
+
SandboxMcpTool2["Stat"] = "stat";
|
|
41
|
+
SandboxMcpTool2["Grep"] = "grep";
|
|
42
|
+
SandboxMcpTool2["Glob"] = "glob";
|
|
43
|
+
SandboxMcpTool2["GetHost"] = "get_host";
|
|
44
|
+
return SandboxMcpTool2;
|
|
45
|
+
})(SandboxMcpTool || {});
|
|
46
|
+
var SandboxMcpScopeKind = /* @__PURE__ */ ((SandboxMcpScopeKind2) => {
|
|
47
|
+
SandboxMcpScopeKind2["All"] = "all";
|
|
48
|
+
SandboxMcpScopeKind2["Hosted"] = "hosted";
|
|
49
|
+
SandboxMcpScopeKind2["ComputerUse"] = "computerUse";
|
|
50
|
+
SandboxMcpScopeKind2["LocalServer"] = "localServer";
|
|
51
|
+
return SandboxMcpScopeKind2;
|
|
52
|
+
})(SandboxMcpScopeKind || {});
|
|
53
|
+
const SANDBOX_FILE_RESOURCE_SCHEME = "sandbox-file";
|
|
54
|
+
const objectSchema = (properties, required) => ({
|
|
55
|
+
type: "object",
|
|
56
|
+
properties,
|
|
57
|
+
required,
|
|
58
|
+
additionalProperties: false
|
|
59
|
+
});
|
|
60
|
+
const str = (description) => ({ type: "string", description });
|
|
61
|
+
const int = (description, minimum = 0) => ({
|
|
62
|
+
type: "integer",
|
|
63
|
+
minimum,
|
|
64
|
+
description
|
|
65
|
+
});
|
|
66
|
+
const envSchema = {
|
|
67
|
+
type: "object",
|
|
68
|
+
additionalProperties: { type: "string" },
|
|
69
|
+
description: "Extra environment variables for this command only."
|
|
70
|
+
};
|
|
71
|
+
const readOnly = {
|
|
72
|
+
readOnlyHint: true,
|
|
73
|
+
destructiveHint: false,
|
|
74
|
+
idempotentHint: true,
|
|
75
|
+
openWorldHint: false
|
|
76
|
+
};
|
|
77
|
+
const mutating = {
|
|
78
|
+
readOnlyHint: false,
|
|
79
|
+
destructiveHint: true,
|
|
80
|
+
idempotentHint: false,
|
|
81
|
+
openWorldHint: false
|
|
82
|
+
};
|
|
83
|
+
const tool = (args) => {
|
|
84
|
+
const annotations = {
|
|
85
|
+
title: args.title,
|
|
86
|
+
...args.write ? mutating : readOnly
|
|
87
|
+
};
|
|
88
|
+
if (args.destructive != null) annotations.destructiveHint = args.destructive;
|
|
89
|
+
if (args.idempotent != null) annotations.idempotentHint = args.idempotent;
|
|
90
|
+
return {
|
|
91
|
+
descriptor: {
|
|
92
|
+
name: args.name,
|
|
93
|
+
description: args.description,
|
|
94
|
+
inputSchema: args.inputSchema,
|
|
95
|
+
annotations
|
|
96
|
+
},
|
|
97
|
+
capability: args.capability,
|
|
98
|
+
write: args.write,
|
|
99
|
+
policyKeys: args.policyKeys
|
|
100
|
+
};
|
|
101
|
+
};
|
|
102
|
+
const BUILT_IN_TOOLS = [
|
|
103
|
+
tool({
|
|
104
|
+
name: "run" /* Run */,
|
|
105
|
+
title: "Run command",
|
|
106
|
+
description: "Run a shell command in the sandbox and wait for it. The shell persists between calls (cd and exported variables carry over). Returns exit_code, stdout and stderr.",
|
|
107
|
+
inputSchema: objectSchema(
|
|
108
|
+
{
|
|
109
|
+
command: str("The bash command to run."),
|
|
110
|
+
cwd: str("Directory to run in (absolute, or relative to the working directory)."),
|
|
111
|
+
timeout_ms: int("Kill the command after this many milliseconds.", 1),
|
|
112
|
+
env: envSchema
|
|
113
|
+
},
|
|
114
|
+
["command"]
|
|
115
|
+
),
|
|
116
|
+
capability: SandboxCapability.Process,
|
|
117
|
+
write: true,
|
|
118
|
+
policyKeys: ["shell", "process"]
|
|
119
|
+
}),
|
|
120
|
+
tool({
|
|
121
|
+
name: "spawn" /* Spawn */,
|
|
122
|
+
title: "Start background process",
|
|
123
|
+
description: "Start a long-running command (a server, a watcher) without waiting. Returns process_id; read its output with process_output.",
|
|
124
|
+
inputSchema: objectSchema(
|
|
125
|
+
{
|
|
126
|
+
command: str("The bash command to start."),
|
|
127
|
+
cwd: str("Directory to run in."),
|
|
128
|
+
env: envSchema
|
|
129
|
+
},
|
|
130
|
+
["command"]
|
|
131
|
+
),
|
|
132
|
+
capability: SandboxCapability.Process,
|
|
133
|
+
write: true,
|
|
134
|
+
policyKeys: ["shell", "process"]
|
|
135
|
+
}),
|
|
136
|
+
tool({
|
|
137
|
+
name: "process_output" /* ProcessOutput */,
|
|
138
|
+
title: "Read process output",
|
|
139
|
+
description: "Read output of a process started with spawn, from byte offsets (pass the next_offset values from the previous call). Optionally waits for new output.",
|
|
140
|
+
inputSchema: objectSchema(
|
|
141
|
+
{
|
|
142
|
+
process_id: str("The id returned by spawn."),
|
|
143
|
+
stdout_offset: int("Byte offset into stdout."),
|
|
144
|
+
stderr_offset: int("Byte offset into stderr."),
|
|
145
|
+
max_bytes: int("Maximum bytes per stream to return (default 65536).", 1),
|
|
146
|
+
wait_ms: int("Wait up to this long for new output or exit (max 30000).")
|
|
147
|
+
},
|
|
148
|
+
["process_id"]
|
|
149
|
+
),
|
|
150
|
+
capability: SandboxCapability.Process,
|
|
151
|
+
write: false,
|
|
152
|
+
policyKeys: ["shell", "process"],
|
|
153
|
+
idempotent: false
|
|
154
|
+
}),
|
|
155
|
+
tool({
|
|
156
|
+
name: "process_input" /* ProcessInput */,
|
|
157
|
+
title: "Write process input",
|
|
158
|
+
description: "Write text to the stdin of a process started with spawn; set end to close stdin.",
|
|
159
|
+
inputSchema: objectSchema(
|
|
160
|
+
{
|
|
161
|
+
process_id: str("The id returned by spawn."),
|
|
162
|
+
data: str("Text to write."),
|
|
163
|
+
end: { type: "boolean" }
|
|
164
|
+
},
|
|
165
|
+
["process_id"]
|
|
166
|
+
),
|
|
167
|
+
capability: SandboxCapability.Process,
|
|
168
|
+
write: true,
|
|
169
|
+
policyKeys: ["shell", "process"],
|
|
170
|
+
destructive: false
|
|
171
|
+
}),
|
|
172
|
+
tool({
|
|
173
|
+
name: "process_terminate" /* ProcessTerminate */,
|
|
174
|
+
title: "Terminate process",
|
|
175
|
+
description: "Stop a process started with spawn.",
|
|
176
|
+
inputSchema: objectSchema(
|
|
177
|
+
{
|
|
178
|
+
process_id: str("The id returned by spawn."),
|
|
179
|
+
signal: str("Signal name (default SIGTERM).")
|
|
180
|
+
},
|
|
181
|
+
["process_id"]
|
|
182
|
+
),
|
|
183
|
+
capability: SandboxCapability.Process,
|
|
184
|
+
write: true,
|
|
185
|
+
policyKeys: ["shell", "process"]
|
|
186
|
+
}),
|
|
187
|
+
tool({
|
|
188
|
+
name: "read_file" /* ReadFile */,
|
|
189
|
+
title: "Read file",
|
|
190
|
+
description: "Read a text file. Use offset (1-based line) and limit (line count) for large files; large or binary files also come back as a resource link.",
|
|
191
|
+
inputSchema: objectSchema(
|
|
192
|
+
{
|
|
193
|
+
path: str("File path (absolute, or relative to the working directory)."),
|
|
194
|
+
offset: int("First line to read (1-based).", 1),
|
|
195
|
+
limit: int("Number of lines to read.", 1)
|
|
196
|
+
},
|
|
197
|
+
["path"]
|
|
198
|
+
),
|
|
199
|
+
capability: SandboxCapability.Files,
|
|
200
|
+
write: false,
|
|
201
|
+
policyKeys: ["files.read", "files"]
|
|
202
|
+
}),
|
|
203
|
+
tool({
|
|
204
|
+
name: "write_file" /* WriteFile */,
|
|
205
|
+
title: "Write file",
|
|
206
|
+
description: "Create or overwrite a text file. Parent directories are created.",
|
|
207
|
+
inputSchema: objectSchema({ path: str("File path."), content: str("Full file content.") }, [
|
|
208
|
+
"path",
|
|
209
|
+
"content"
|
|
210
|
+
]),
|
|
211
|
+
capability: SandboxCapability.Files,
|
|
212
|
+
write: true,
|
|
213
|
+
policyKeys: ["files.write", "files"]
|
|
214
|
+
}),
|
|
215
|
+
tool({
|
|
216
|
+
name: "edit_file" /* EditFile */,
|
|
217
|
+
title: "Edit file",
|
|
218
|
+
description: "Replace an exact string in a file. old_string must occur exactly once unless replace_all is true; include surrounding lines to make it unique.",
|
|
219
|
+
inputSchema: objectSchema(
|
|
220
|
+
{
|
|
221
|
+
path: str("File path."),
|
|
222
|
+
old_string: str("Exact text to replace."),
|
|
223
|
+
new_string: str("Replacement text."),
|
|
224
|
+
replace_all: { type: "boolean", description: "Replace every occurrence." }
|
|
225
|
+
},
|
|
226
|
+
["path", "old_string", "new_string"]
|
|
227
|
+
),
|
|
228
|
+
capability: SandboxCapability.Files,
|
|
229
|
+
write: true,
|
|
230
|
+
policyKeys: ["files.write", "files"]
|
|
231
|
+
}),
|
|
232
|
+
tool({
|
|
233
|
+
name: "list_dir" /* ListDir */,
|
|
234
|
+
title: "List directory",
|
|
235
|
+
description: "List the entries of a directory (name, type, size).",
|
|
236
|
+
inputSchema: objectSchema(
|
|
237
|
+
{ path: str("Directory path (default: the working directory).") },
|
|
238
|
+
[]
|
|
239
|
+
),
|
|
240
|
+
capability: SandboxCapability.Files,
|
|
241
|
+
write: false,
|
|
242
|
+
policyKeys: ["files.read", "files"]
|
|
243
|
+
}),
|
|
244
|
+
tool({
|
|
245
|
+
name: "stat" /* Stat */,
|
|
246
|
+
title: "Stat path",
|
|
247
|
+
description: "Type, size, modification time and mode of a path, or exists: false.",
|
|
248
|
+
inputSchema: objectSchema({ path: str("Path to inspect.") }, ["path"]),
|
|
249
|
+
capability: SandboxCapability.Files,
|
|
250
|
+
write: false,
|
|
251
|
+
policyKeys: ["files.read", "files"]
|
|
252
|
+
}),
|
|
253
|
+
tool({
|
|
254
|
+
name: "grep" /* Grep */,
|
|
255
|
+
title: "Search file contents",
|
|
256
|
+
description: "Search files under a path for a regular expression. Returns path:line:text matches. Skips .git and node_modules.",
|
|
257
|
+
inputSchema: objectSchema(
|
|
258
|
+
{
|
|
259
|
+
pattern: str("JavaScript regular expression."),
|
|
260
|
+
path: str("File or directory to search (default: the working directory)."),
|
|
261
|
+
glob: str('Only search files matching this glob (e.g. "*.ts").'),
|
|
262
|
+
ignore_case: { type: "boolean" },
|
|
263
|
+
max_results: int("Maximum matches (default 200).", 1)
|
|
264
|
+
},
|
|
265
|
+
["pattern"]
|
|
266
|
+
),
|
|
267
|
+
capability: SandboxCapability.Files,
|
|
268
|
+
write: false,
|
|
269
|
+
policyKeys: ["files.read", "files"]
|
|
270
|
+
}),
|
|
271
|
+
tool({
|
|
272
|
+
name: "glob" /* Glob */,
|
|
273
|
+
title: "Find files",
|
|
274
|
+
description: 'Find files whose path (relative to path) matches a glob such as "**/*.ts".',
|
|
275
|
+
inputSchema: objectSchema(
|
|
276
|
+
{
|
|
277
|
+
pattern: str("Glob pattern."),
|
|
278
|
+
path: str("Directory to search (default: the working directory)."),
|
|
279
|
+
max_results: int("Maximum paths (default 500).", 1)
|
|
280
|
+
},
|
|
281
|
+
["pattern"]
|
|
282
|
+
),
|
|
283
|
+
capability: SandboxCapability.Files,
|
|
284
|
+
write: false,
|
|
285
|
+
policyKeys: ["files.read", "files"]
|
|
286
|
+
}),
|
|
287
|
+
tool({
|
|
288
|
+
name: "get_host" /* GetHost */,
|
|
289
|
+
title: "Get port URL",
|
|
290
|
+
description: "The URL (and any required headers) at which a port served inside the sandbox is reachable.",
|
|
291
|
+
inputSchema: objectSchema(
|
|
292
|
+
{
|
|
293
|
+
port: int("Port number.", 1),
|
|
294
|
+
protocol: {
|
|
295
|
+
type: "string",
|
|
296
|
+
enum: Object.values(PortProtocol),
|
|
297
|
+
description: "Default http."
|
|
298
|
+
}
|
|
299
|
+
},
|
|
300
|
+
["port"]
|
|
301
|
+
),
|
|
302
|
+
capability: SandboxCapability.Ports,
|
|
303
|
+
write: false,
|
|
304
|
+
policyKeys: ["ports"]
|
|
305
|
+
})
|
|
306
|
+
];
|
|
307
|
+
const scopeOf = (options) => options.scope ?? { kind: "all" /* All */ };
|
|
308
|
+
const hostedServersOf = (options) => {
|
|
309
|
+
if (options.access === SandboxAccess.Read) return { computerUse: false, localMcp: false };
|
|
310
|
+
const capabilities = new Set(options.capabilities);
|
|
311
|
+
const scope = scopeOf(options);
|
|
312
|
+
const computerUse = capabilities.has(SandboxCapability.ComputerUse) && scope.kind !== "localServer" /* LocalServer */;
|
|
313
|
+
const localMcp = capabilities.has(SandboxCapability.LocalMcp) && scope.kind !== "computerUse" /* ComputerUse */;
|
|
314
|
+
return scope.kind === "localServer" /* LocalServer */ ? { computerUse, localMcp, localServer: scope.server } : { computerUse, localMcp };
|
|
315
|
+
};
|
|
316
|
+
const hostedSpec = (hosted) => {
|
|
317
|
+
const annotations = { ...mutating, ...hosted.tool.annotations };
|
|
318
|
+
return {
|
|
319
|
+
descriptor: { ...hosted.tool, name: hosted.name, annotations },
|
|
320
|
+
capability: hosted.server.kind === SandboxHostedServerKind.ComputerUse ? SandboxCapability.ComputerUse : SandboxCapability.LocalMcp,
|
|
321
|
+
write: true,
|
|
322
|
+
policyKeys: hosted.policyKeys,
|
|
323
|
+
hosted
|
|
324
|
+
};
|
|
325
|
+
};
|
|
326
|
+
const toolSpecs = (options) => {
|
|
327
|
+
const capabilities = new Set(options.capabilities);
|
|
328
|
+
const builtIn = scopeOf(options).kind === "all" /* All */ ? BUILT_IN_TOOLS.filter((spec) => capabilities.has(spec.capability)) : [];
|
|
329
|
+
const visible = options.access === SandboxAccess.Read ? builtIn.filter((spec) => !spec.write) : options.access === SandboxAccess.Execute ? builtIn.filter((spec) => !spec.write || spec.capability !== SandboxCapability.Files) : builtIn;
|
|
330
|
+
const hosted = hostedTools({
|
|
331
|
+
catalog: options.catalog ?? EMPTY_MCP_CATALOG,
|
|
332
|
+
...hostedServersOf(options),
|
|
333
|
+
taken: new Set(builtIn.map((spec) => spec.descriptor.name))
|
|
334
|
+
});
|
|
335
|
+
return [...visible, ...hosted.map(hostedSpec)];
|
|
336
|
+
};
|
|
337
|
+
const sandboxMcpToolDescriptors = (options) => toolSpecs(options).map((spec) => spec.descriptor);
|
|
338
|
+
const sandboxMcpToolPolicyKeys = (options, name) => {
|
|
339
|
+
const spec = toolSpecs(options).find((candidate) => candidate.descriptor.name === name);
|
|
340
|
+
return spec == null ? null : [name, ...spec.policyKeys];
|
|
341
|
+
};
|
|
342
|
+
const DEFAULT_LIMITS = {
|
|
343
|
+
maxOutputChars: 64e3,
|
|
344
|
+
maxInlineBytes: 256 * 1024,
|
|
345
|
+
processBufferBytes: 4 * 1024 * 1024,
|
|
346
|
+
maxProcesses: 32,
|
|
347
|
+
exitReportGraceMs: 2e3,
|
|
348
|
+
exitOutputChars: 4e3
|
|
349
|
+
};
|
|
350
|
+
const textResult = (text) => ({ content: [{ type: "text", text }] });
|
|
351
|
+
const jsonResult = (value) => textResult(JSON.stringify(value, null, 2));
|
|
352
|
+
const errorResult = (message) => ({
|
|
353
|
+
content: [{ type: "text", text: message }],
|
|
354
|
+
isError: true
|
|
355
|
+
});
|
|
356
|
+
const truncateMiddle = (text, max) => {
|
|
357
|
+
if (text.length <= max) return text;
|
|
358
|
+
const half = Math.floor(max / 2);
|
|
359
|
+
return `${text.slice(0, half)}
|
|
360
|
+
[... ${text.length - max} characters omitted ...]
|
|
361
|
+
${text.slice(text.length - half)}`;
|
|
362
|
+
};
|
|
363
|
+
const listRequestSchema = z.object({ method: z.literal("tools/list") });
|
|
364
|
+
const runArgsSchema = z.object({
|
|
365
|
+
command: z.string().min(1),
|
|
366
|
+
cwd: z.string().optional(),
|
|
367
|
+
timeout_ms: z.number().int().positive().optional(),
|
|
368
|
+
env: z.record(z.string(), z.string()).optional()
|
|
369
|
+
});
|
|
370
|
+
const spawnArgsSchema = runArgsSchema.omit({ timeout_ms: true });
|
|
371
|
+
const processOutputArgsSchema = z.object({
|
|
372
|
+
process_id: z.string().min(1),
|
|
373
|
+
stdout_offset: z.number().int().nonnegative().optional(),
|
|
374
|
+
stderr_offset: z.number().int().nonnegative().optional(),
|
|
375
|
+
max_bytes: z.number().int().positive().optional(),
|
|
376
|
+
wait_ms: z.number().int().nonnegative().optional()
|
|
377
|
+
});
|
|
378
|
+
const processInputArgsSchema = z.object({
|
|
379
|
+
process_id: z.string().min(1),
|
|
380
|
+
data: z.string().optional(),
|
|
381
|
+
end: z.boolean().optional()
|
|
382
|
+
});
|
|
383
|
+
const processTerminateArgsSchema = z.object({
|
|
384
|
+
process_id: z.string().min(1),
|
|
385
|
+
signal: z.string().optional()
|
|
386
|
+
});
|
|
387
|
+
const readFileArgsSchema = z.object({
|
|
388
|
+
path: z.string().min(1),
|
|
389
|
+
offset: z.number().int().positive().optional(),
|
|
390
|
+
limit: z.number().int().positive().optional()
|
|
391
|
+
});
|
|
392
|
+
const writeFileArgsSchema = z.object({ path: z.string().min(1), content: z.string() });
|
|
393
|
+
const editFileArgsSchema = z.object({
|
|
394
|
+
path: z.string().min(1),
|
|
395
|
+
old_string: z.string().min(1),
|
|
396
|
+
new_string: z.string(),
|
|
397
|
+
replace_all: z.boolean().optional()
|
|
398
|
+
});
|
|
399
|
+
const pathArgsSchema = z.object({ path: z.string().min(1) });
|
|
400
|
+
const listDirArgsSchema = z.object({ path: z.string().optional() });
|
|
401
|
+
const grepArgsSchema = z.object({
|
|
402
|
+
pattern: z.string().min(1),
|
|
403
|
+
path: z.string().optional(),
|
|
404
|
+
glob: z.string().optional(),
|
|
405
|
+
ignore_case: z.boolean().optional(),
|
|
406
|
+
max_results: z.number().int().positive().optional()
|
|
407
|
+
});
|
|
408
|
+
const globArgsSchema = z.object({
|
|
409
|
+
pattern: z.string().min(1),
|
|
410
|
+
path: z.string().optional(),
|
|
411
|
+
max_results: z.number().int().positive().optional()
|
|
412
|
+
});
|
|
413
|
+
const getHostArgsSchema = z.object({
|
|
414
|
+
port: z.number().int().min(1).max(65535),
|
|
415
|
+
protocol: z.nativeEnum(PortProtocol).optional()
|
|
416
|
+
});
|
|
417
|
+
const appendOutput = (buffer, chunk, cap) => {
|
|
418
|
+
buffer.chunks.push(chunk);
|
|
419
|
+
buffer.size += chunk.byteLength;
|
|
420
|
+
while (buffer.size > cap && buffer.chunks.length > 1) {
|
|
421
|
+
const dropped = buffer.chunks.shift();
|
|
422
|
+
if (dropped == null) break;
|
|
423
|
+
buffer.size -= dropped.byteLength;
|
|
424
|
+
buffer.base += dropped.byteLength;
|
|
425
|
+
}
|
|
426
|
+
};
|
|
427
|
+
const readOutput = (buffer, offset, maxBytes) => {
|
|
428
|
+
const start = Math.max(offset, buffer.base);
|
|
429
|
+
const end = buffer.base + buffer.size;
|
|
430
|
+
const out = new Uint8Array(Math.max(0, Math.min(end - start, maxBytes)));
|
|
431
|
+
let position = buffer.base;
|
|
432
|
+
let written = 0;
|
|
433
|
+
for (const chunk of buffer.chunks) {
|
|
434
|
+
const chunkEnd = position + chunk.byteLength;
|
|
435
|
+
if (chunkEnd > start && written < out.byteLength) {
|
|
436
|
+
const from = Math.max(0, start - position);
|
|
437
|
+
const take = Math.min(chunk.byteLength - from, out.byteLength - written);
|
|
438
|
+
out.set(chunk.subarray(from, from + take), written);
|
|
439
|
+
written += take;
|
|
440
|
+
}
|
|
441
|
+
position = chunkEnd;
|
|
442
|
+
}
|
|
443
|
+
return {
|
|
444
|
+
data: utf8Decode(out),
|
|
445
|
+
offset: start,
|
|
446
|
+
next_offset: start + written,
|
|
447
|
+
skipped: start - offset,
|
|
448
|
+
available: end
|
|
449
|
+
};
|
|
450
|
+
};
|
|
451
|
+
const createSandboxMcpServer = (args) => {
|
|
452
|
+
const limits = { ...DEFAULT_LIMITS, ...args.limits };
|
|
453
|
+
let catalog = args.catalog ?? EMPTY_MCP_CATALOG;
|
|
454
|
+
let specs = toolSpecs({ ...args, catalog });
|
|
455
|
+
let byName = new Map(specs.map((spec) => [spec.descriptor.name, spec]));
|
|
456
|
+
const processes = /* @__PURE__ */ new Map();
|
|
457
|
+
const served = hostedServersOf(args);
|
|
458
|
+
let handler = null;
|
|
459
|
+
let learning = null;
|
|
460
|
+
let discovering = false;
|
|
461
|
+
const checked = /* @__PURE__ */ new Set();
|
|
462
|
+
const asError = (cause) => cause instanceof Error ? cause : new Error(String(cause));
|
|
463
|
+
const reportError = (error) => args.onError?.(error);
|
|
464
|
+
const mergeCatalog = (next) => {
|
|
465
|
+
const merged = {
|
|
466
|
+
computerUse: next.computerUse ?? catalog.computerUse,
|
|
467
|
+
localMcp: next.localMcp ?? catalog.localMcp
|
|
468
|
+
};
|
|
469
|
+
if (JSON.stringify(merged) === JSON.stringify(catalog)) return false;
|
|
470
|
+
catalog = merged;
|
|
471
|
+
specs = toolSpecs({ ...args, catalog });
|
|
472
|
+
byName = new Map(specs.map((spec) => [spec.descriptor.name, spec]));
|
|
473
|
+
handler?.notify.toolsChanged();
|
|
474
|
+
return true;
|
|
475
|
+
};
|
|
476
|
+
const unknownCatalogs = () => ({
|
|
477
|
+
computerUse: served.computerUse && catalog.computerUse == null,
|
|
478
|
+
localMcp: served.localMcp && catalog.localMcp == null
|
|
479
|
+
});
|
|
480
|
+
const connections = createHostedMcpConnections({
|
|
481
|
+
getSandbox: () => sandboxFor(),
|
|
482
|
+
fetch: args.fetch,
|
|
483
|
+
name: args.name,
|
|
484
|
+
version: args.version
|
|
485
|
+
});
|
|
486
|
+
const learnFrom = (sandbox) => {
|
|
487
|
+
const needed = unknownCatalogs();
|
|
488
|
+
if (!needed.computerUse && !needed.localMcp) return Promise.resolve();
|
|
489
|
+
learning ??= (async () => {
|
|
490
|
+
try {
|
|
491
|
+
if (mergeCatalog(await connections.learn(sandbox, needed))) args.onCatalog?.(catalog);
|
|
492
|
+
} finally {
|
|
493
|
+
learning = null;
|
|
494
|
+
}
|
|
495
|
+
})();
|
|
496
|
+
return learning;
|
|
497
|
+
};
|
|
498
|
+
const sandboxFor = async () => {
|
|
499
|
+
const sandbox = await args.getSandbox();
|
|
500
|
+
learnFrom(sandbox).catch((cause) => reportError(asError(cause)));
|
|
501
|
+
return sandbox;
|
|
502
|
+
};
|
|
503
|
+
const beforeList = async (waitForDiscovery = false) => {
|
|
504
|
+
const needed = unknownCatalogs();
|
|
505
|
+
if (!needed.computerUse && !needed.localMcp) return;
|
|
506
|
+
const live = args.peekSandbox?.() ?? null;
|
|
507
|
+
if (live != null) {
|
|
508
|
+
const discovered = learnFrom(live).catch((cause) => reportError(asError(cause)));
|
|
509
|
+
if (waitForDiscovery || scopeOf(args).kind !== "all" /* All */) await discovered;
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
if (discovering) return;
|
|
513
|
+
discovering = true;
|
|
514
|
+
sandboxFor().then((sandbox) => learnFrom(sandbox)).catch((cause) => {
|
|
515
|
+
discovering = false;
|
|
516
|
+
reportError(asError(cause));
|
|
517
|
+
});
|
|
518
|
+
};
|
|
519
|
+
const checkCatalog = (server) => {
|
|
520
|
+
const key = server.kind === SandboxHostedServerKind.Local ? `local:${server.name}` : "cu";
|
|
521
|
+
if (checked.has(key)) return;
|
|
522
|
+
checked.add(key);
|
|
523
|
+
void (async () => {
|
|
524
|
+
const tools = await connections.tools(server);
|
|
525
|
+
const next = server.kind === SandboxHostedServerKind.ComputerUse ? { computerUse: tools } : catalog.localMcp == null ? {} : { localMcp: { ...catalog.localMcp, [server.name]: tools } };
|
|
526
|
+
if (mergeCatalog(next)) args.onCatalog?.(catalog);
|
|
527
|
+
})().catch((cause) => {
|
|
528
|
+
checked.delete(key);
|
|
529
|
+
reportError(asError(cause));
|
|
530
|
+
});
|
|
531
|
+
};
|
|
532
|
+
const shell = createSandboxShell({ getSandbox: sandboxFor, mode: args.shell });
|
|
533
|
+
const isAllowed = async (spec, name, input) => {
|
|
534
|
+
for (const key of [name, ...spec.policyKeys]) {
|
|
535
|
+
const decision = args.toolPolicy?.[key];
|
|
536
|
+
if (decision === ToolApproval.Allow) return true;
|
|
537
|
+
if (decision === ToolApproval.Ask)
|
|
538
|
+
return args.approve == null ? false : args.approve(name, input);
|
|
539
|
+
}
|
|
540
|
+
return true;
|
|
541
|
+
};
|
|
542
|
+
const backgroundProcess = (id) => {
|
|
543
|
+
const entry = processes.get(id);
|
|
544
|
+
if (entry == null) throw new Error(`no process ${id}; start one with spawn`);
|
|
545
|
+
return entry;
|
|
546
|
+
};
|
|
547
|
+
const notifyProcess = (entry) => {
|
|
548
|
+
for (const listener of [...entry.changed]) listener();
|
|
549
|
+
};
|
|
550
|
+
const tailOf = (buffer) => {
|
|
551
|
+
const end = buffer.base + buffer.size;
|
|
552
|
+
const text = readOutput(
|
|
553
|
+
buffer,
|
|
554
|
+
end - limits.exitOutputChars * 4,
|
|
555
|
+
limits.exitOutputChars * 4
|
|
556
|
+
).data;
|
|
557
|
+
return text.length <= limits.exitOutputChars ? text : text.slice(text.length - limits.exitOutputChars);
|
|
558
|
+
};
|
|
559
|
+
let closed = false;
|
|
560
|
+
const reportExit = (id, entry) => {
|
|
561
|
+
const report = args.onProcessExit;
|
|
562
|
+
if (report == null) return;
|
|
563
|
+
setTimeout(() => {
|
|
564
|
+
if (closed || entry.exitSeen) return;
|
|
565
|
+
entry.exitSeen = true;
|
|
566
|
+
report({
|
|
567
|
+
processId: id,
|
|
568
|
+
command: entry.command,
|
|
569
|
+
exitCode: entry.exitCode,
|
|
570
|
+
stdout: tailOf(entry.stdout),
|
|
571
|
+
stderr: tailOf(entry.stderr)
|
|
572
|
+
});
|
|
573
|
+
}, limits.exitReportGraceMs);
|
|
574
|
+
};
|
|
575
|
+
const pumpOutput = async (entry, stream, buffer) => {
|
|
576
|
+
const reader = stream.getReader();
|
|
577
|
+
try {
|
|
578
|
+
for (; ; ) {
|
|
579
|
+
const { done, value } = await reader.read();
|
|
580
|
+
if (done) break;
|
|
581
|
+
appendOutput(buffer, value, limits.processBufferBytes);
|
|
582
|
+
notifyProcess(entry);
|
|
583
|
+
}
|
|
584
|
+
} catch {
|
|
585
|
+
}
|
|
586
|
+
};
|
|
587
|
+
const run = async (name, rawArgs, signal) => {
|
|
588
|
+
switch (name) {
|
|
589
|
+
case "run" /* Run */: {
|
|
590
|
+
const input = runArgsSchema.parse(rawArgs);
|
|
591
|
+
const result = await shell.run({
|
|
592
|
+
command: input.command,
|
|
593
|
+
cwd: input.cwd,
|
|
594
|
+
env: input.env,
|
|
595
|
+
timeoutMs: input.timeout_ms ?? 12e4,
|
|
596
|
+
signal
|
|
597
|
+
});
|
|
598
|
+
return jsonResult({
|
|
599
|
+
exit_code: result.exitCode,
|
|
600
|
+
stdout: truncateMiddle(result.stdout, limits.maxOutputChars),
|
|
601
|
+
stderr: truncateMiddle(result.stderr, limits.maxOutputChars)
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
case "spawn" /* Spawn */: {
|
|
605
|
+
const input = spawnArgsSchema.parse(rawArgs);
|
|
606
|
+
if (processes.size >= limits.maxProcesses) {
|
|
607
|
+
for (const [id2, entry2] of processes)
|
|
608
|
+
if (entry2.exited && entry2.exitSeen) processes.delete(id2);
|
|
609
|
+
}
|
|
610
|
+
if (processes.size >= limits.maxProcesses) {
|
|
611
|
+
return errorResult(
|
|
612
|
+
`too many background processes (${limits.maxProcesses}); terminate one first`
|
|
613
|
+
);
|
|
614
|
+
}
|
|
615
|
+
const sandbox = await sandboxFor();
|
|
616
|
+
const child = await sandbox.spawn({
|
|
617
|
+
command: input.command,
|
|
618
|
+
shell: true,
|
|
619
|
+
cwd: input.cwd,
|
|
620
|
+
env: input.env
|
|
621
|
+
});
|
|
622
|
+
const id = `p${crypto.randomUUID().slice(0, 8)}`;
|
|
623
|
+
const entry = {
|
|
624
|
+
process: child,
|
|
625
|
+
stdout: { chunks: [], base: 0, size: 0 },
|
|
626
|
+
stderr: { chunks: [], base: 0, size: 0 },
|
|
627
|
+
exitCode: null,
|
|
628
|
+
exited: false,
|
|
629
|
+
changed: /* @__PURE__ */ new Set(),
|
|
630
|
+
command: input.command,
|
|
631
|
+
exitSeen: false
|
|
632
|
+
};
|
|
633
|
+
processes.set(id, entry);
|
|
634
|
+
void pumpOutput(entry, child.stdout, entry.stdout);
|
|
635
|
+
void pumpOutput(entry, child.stderr, entry.stderr);
|
|
636
|
+
child.wait().then(
|
|
637
|
+
(result) => {
|
|
638
|
+
entry.exited = true;
|
|
639
|
+
entry.exitCode = result.exitCode;
|
|
640
|
+
notifyProcess(entry);
|
|
641
|
+
reportExit(id, entry);
|
|
642
|
+
},
|
|
643
|
+
() => {
|
|
644
|
+
entry.exited = true;
|
|
645
|
+
notifyProcess(entry);
|
|
646
|
+
reportExit(id, entry);
|
|
647
|
+
}
|
|
648
|
+
);
|
|
649
|
+
return jsonResult({ process_id: id, pid: child.pid });
|
|
650
|
+
}
|
|
651
|
+
case "process_output" /* ProcessOutput */: {
|
|
652
|
+
const input = processOutputArgsSchema.parse(rawArgs);
|
|
653
|
+
const entry = backgroundProcess(input.process_id);
|
|
654
|
+
const stdoutOffset = input.stdout_offset ?? 0;
|
|
655
|
+
const stderrOffset = input.stderr_offset ?? 0;
|
|
656
|
+
const hasNews = () => entry.exited || entry.stdout.base + entry.stdout.size > stdoutOffset || entry.stderr.base + entry.stderr.size > stderrOffset;
|
|
657
|
+
const waitMs = Math.min(input.wait_ms ?? 0, 3e4);
|
|
658
|
+
if (waitMs > 0 && !hasNews()) {
|
|
659
|
+
await new Promise((resolve) => {
|
|
660
|
+
const done = () => {
|
|
661
|
+
clearTimeout(timer);
|
|
662
|
+
entry.changed.delete(listener);
|
|
663
|
+
signal?.removeEventListener("abort", done);
|
|
664
|
+
resolve();
|
|
665
|
+
};
|
|
666
|
+
const listener = () => {
|
|
667
|
+
if (hasNews()) done();
|
|
668
|
+
};
|
|
669
|
+
const timer = setTimeout(done, waitMs);
|
|
670
|
+
entry.changed.add(listener);
|
|
671
|
+
signal?.addEventListener("abort", done, { once: true });
|
|
672
|
+
});
|
|
673
|
+
}
|
|
674
|
+
const maxBytes = input.max_bytes ?? 65536;
|
|
675
|
+
if (entry.exited) entry.exitSeen = true;
|
|
676
|
+
return jsonResult({
|
|
677
|
+
stdout: readOutput(entry.stdout, stdoutOffset, maxBytes),
|
|
678
|
+
stderr: readOutput(entry.stderr, stderrOffset, maxBytes),
|
|
679
|
+
running: !entry.exited,
|
|
680
|
+
exit_code: entry.exitCode
|
|
681
|
+
});
|
|
682
|
+
}
|
|
683
|
+
case "process_input" /* ProcessInput */: {
|
|
684
|
+
const input = processInputArgsSchema.parse(rawArgs);
|
|
685
|
+
const entry = backgroundProcess(input.process_id);
|
|
686
|
+
await entry.process.write(input.data ?? "", { end: input.end === true });
|
|
687
|
+
return jsonResult({ written: input.data?.length ?? 0, stdin_closed: input.end === true });
|
|
688
|
+
}
|
|
689
|
+
case "process_terminate" /* ProcessTerminate */: {
|
|
690
|
+
const input = processTerminateArgsSchema.parse(rawArgs);
|
|
691
|
+
const entry = backgroundProcess(input.process_id);
|
|
692
|
+
entry.exitSeen = true;
|
|
693
|
+
await entry.process.kill(input.signal);
|
|
694
|
+
const result = await entry.process.wait().catch(() => ({ exitCode: entry.exitCode ?? -1 }));
|
|
695
|
+
processes.delete(input.process_id);
|
|
696
|
+
return jsonResult({ process_id: input.process_id, exit_code: result.exitCode });
|
|
697
|
+
}
|
|
698
|
+
case "read_file" /* ReadFile */: {
|
|
699
|
+
const input = readFileArgsSchema.parse(rawArgs);
|
|
700
|
+
const sandbox = await sandboxFor();
|
|
701
|
+
const path = resolvePosixPath(sandbox.defaultWorkingDirectory, input.path);
|
|
702
|
+
const stat = await sandbox.stat({ path, abortSignal: signal });
|
|
703
|
+
if (stat == null) return errorResult(`${path}: no such file`);
|
|
704
|
+
if (stat.type === SandboxEntryType.Directory)
|
|
705
|
+
return errorResult(`${path} is a directory; use list_dir`);
|
|
706
|
+
const link = {
|
|
707
|
+
type: "resource_link",
|
|
708
|
+
uri: `${SANDBOX_FILE_RESOURCE_SCHEME}://${encodeURI(path)}`,
|
|
709
|
+
name: path.split("/").pop() ?? path,
|
|
710
|
+
size: stat.size
|
|
711
|
+
};
|
|
712
|
+
const ranged = input.offset != null || input.limit != null;
|
|
713
|
+
const startLine = input.offset ?? 1;
|
|
714
|
+
const endLine = input.limit == null ? void 0 : startLine + input.limit - 1;
|
|
715
|
+
if (!ranged && stat.size > limits.maxInlineBytes) {
|
|
716
|
+
const head = await sandbox.readTextFile({
|
|
717
|
+
path,
|
|
718
|
+
startLine: 1,
|
|
719
|
+
endLine: 200,
|
|
720
|
+
abortSignal: signal
|
|
721
|
+
}) ?? "";
|
|
722
|
+
return {
|
|
723
|
+
content: [
|
|
724
|
+
{
|
|
725
|
+
type: "text",
|
|
726
|
+
text: `${path} is ${stat.size} bytes, too large to return whole. First 200 lines below; read more with offset and limit, or fetch the resource.
|
|
727
|
+
|
|
728
|
+
${truncateMiddle(head, limits.maxOutputChars)}`
|
|
729
|
+
},
|
|
730
|
+
link
|
|
731
|
+
]
|
|
732
|
+
};
|
|
733
|
+
}
|
|
734
|
+
const text = await sandbox.readTextFile({
|
|
735
|
+
path,
|
|
736
|
+
startLine: ranged ? startLine : void 0,
|
|
737
|
+
endLine,
|
|
738
|
+
abortSignal: signal
|
|
739
|
+
});
|
|
740
|
+
if (text == null) return errorResult(`${path}: no such file`);
|
|
741
|
+
if (text.includes("\0")) {
|
|
742
|
+
return {
|
|
743
|
+
content: [
|
|
744
|
+
{ type: "text", text: `${path} is a binary file (${stat.size} bytes).` },
|
|
745
|
+
link
|
|
746
|
+
]
|
|
747
|
+
};
|
|
748
|
+
}
|
|
749
|
+
return textResult(
|
|
750
|
+
truncateMiddle(text, Math.max(limits.maxOutputChars, limits.maxInlineBytes))
|
|
751
|
+
);
|
|
752
|
+
}
|
|
753
|
+
case "write_file" /* WriteFile */: {
|
|
754
|
+
const input = writeFileArgsSchema.parse(rawArgs);
|
|
755
|
+
const sandbox = await sandboxFor();
|
|
756
|
+
const path = resolvePosixPath(sandbox.defaultWorkingDirectory, input.path);
|
|
757
|
+
await sandbox.writeTextFile({ path, content: input.content, abortSignal: signal });
|
|
758
|
+
return textResult(
|
|
759
|
+
`Wrote ${new TextEncoder().encode(input.content).byteLength} bytes to ${path}`
|
|
760
|
+
);
|
|
761
|
+
}
|
|
762
|
+
case "edit_file" /* EditFile */: {
|
|
763
|
+
const input = editFileArgsSchema.parse(rawArgs);
|
|
764
|
+
const sandbox = await sandboxFor();
|
|
765
|
+
const path = resolvePosixPath(sandbox.defaultWorkingDirectory, input.path);
|
|
766
|
+
const text = await sandbox.readTextFile({ path, abortSignal: signal });
|
|
767
|
+
if (text == null) return errorResult(`${path}: no such file`);
|
|
768
|
+
const count = text.split(input.old_string).length - 1;
|
|
769
|
+
if (count === 0) return errorResult(`old_string was not found in ${path}`);
|
|
770
|
+
if (count > 1 && input.replace_all !== true) {
|
|
771
|
+
return errorResult(
|
|
772
|
+
`old_string occurs ${count} times in ${path}; add surrounding context or set replace_all`
|
|
773
|
+
);
|
|
774
|
+
}
|
|
775
|
+
const next = input.replace_all === true ? text.split(input.old_string).join(input.new_string) : text.replace(input.old_string, () => input.new_string);
|
|
776
|
+
await sandbox.writeTextFile({ path, content: next, abortSignal: signal });
|
|
777
|
+
return textResult(
|
|
778
|
+
`Replaced ${input.replace_all === true ? count : 1} occurrence(s) in ${path}`
|
|
779
|
+
);
|
|
780
|
+
}
|
|
781
|
+
case "list_dir" /* ListDir */: {
|
|
782
|
+
const input = listDirArgsSchema.parse(rawArgs);
|
|
783
|
+
const sandbox = await sandboxFor();
|
|
784
|
+
const path = resolvePosixPath(sandbox.defaultWorkingDirectory, input.path ?? ".");
|
|
785
|
+
const entries = await sandbox.readDir({ path, abortSignal: signal });
|
|
786
|
+
return jsonResult({
|
|
787
|
+
path,
|
|
788
|
+
entries: entries.map((entry) => ({
|
|
789
|
+
name: entry.name,
|
|
790
|
+
type: entry.type,
|
|
791
|
+
size: entry.size
|
|
792
|
+
}))
|
|
793
|
+
});
|
|
794
|
+
}
|
|
795
|
+
case "stat" /* Stat */: {
|
|
796
|
+
const input = pathArgsSchema.parse(rawArgs);
|
|
797
|
+
const sandbox = await sandboxFor();
|
|
798
|
+
const path = resolvePosixPath(sandbox.defaultWorkingDirectory, input.path);
|
|
799
|
+
const stat = await sandbox.stat({ path, abortSignal: signal });
|
|
800
|
+
return jsonResult(stat == null ? { path, exists: false } : { exists: true, ...stat });
|
|
801
|
+
}
|
|
802
|
+
case "grep" /* Grep */: {
|
|
803
|
+
const input = grepArgsSchema.parse(rawArgs);
|
|
804
|
+
const sandbox = await sandboxFor();
|
|
805
|
+
const result = await grepFiles({
|
|
806
|
+
sandbox,
|
|
807
|
+
cwd: sandbox.defaultWorkingDirectory,
|
|
808
|
+
pattern: input.pattern,
|
|
809
|
+
path: input.path,
|
|
810
|
+
glob: input.glob,
|
|
811
|
+
ignoreCase: input.ignore_case,
|
|
812
|
+
maxResults: input.max_results ?? 200,
|
|
813
|
+
signal
|
|
814
|
+
});
|
|
815
|
+
const lines = result.matches.map((match) => `${match.path}:${match.line}:${match.text}`);
|
|
816
|
+
if (result.truncated) lines.push("[results truncated]");
|
|
817
|
+
return textResult(lines.length === 0 ? "No matches." : lines.join("\n"));
|
|
818
|
+
}
|
|
819
|
+
case "glob" /* Glob */: {
|
|
820
|
+
const input = globArgsSchema.parse(rawArgs);
|
|
821
|
+
const sandbox = await sandboxFor();
|
|
822
|
+
const result = await globFiles({
|
|
823
|
+
sandbox,
|
|
824
|
+
cwd: sandbox.defaultWorkingDirectory,
|
|
825
|
+
pattern: input.pattern,
|
|
826
|
+
path: input.path,
|
|
827
|
+
maxResults: input.max_results ?? 500,
|
|
828
|
+
signal
|
|
829
|
+
});
|
|
830
|
+
const lines = [...result.paths];
|
|
831
|
+
if (result.truncated) lines.push("[results truncated]");
|
|
832
|
+
return textResult(lines.length === 0 ? "No files matched." : lines.join("\n"));
|
|
833
|
+
}
|
|
834
|
+
case "get_host" /* GetHost */: {
|
|
835
|
+
const input = getHostArgsSchema.parse(rawArgs);
|
|
836
|
+
const sandbox = await sandboxFor();
|
|
837
|
+
const endpoint = await sandbox.getPortEndpoint({
|
|
838
|
+
port: input.port,
|
|
839
|
+
protocol: input.protocol
|
|
840
|
+
});
|
|
841
|
+
return jsonResult({ url: endpoint.url, headers: endpoint.headers ?? {} });
|
|
842
|
+
}
|
|
843
|
+
default:
|
|
844
|
+
return errorResult(`unknown tool ${name}`);
|
|
845
|
+
}
|
|
846
|
+
};
|
|
847
|
+
const callTool = async (name, rawArgs, signal) => {
|
|
848
|
+
const spec = byName.get(name);
|
|
849
|
+
if (spec == null) return errorResult(`unknown tool ${name}`);
|
|
850
|
+
if (!await isAllowed(spec, name, rawArgs)) return errorResult(`${name} was not approved`);
|
|
851
|
+
try {
|
|
852
|
+
if (spec.hosted != null) {
|
|
853
|
+
try {
|
|
854
|
+
return await connections.call(spec.hosted.server, spec.hosted.tool.name, rawArgs);
|
|
855
|
+
} finally {
|
|
856
|
+
checkCatalog(spec.hosted.server);
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
return await run(name, rawArgs, signal);
|
|
860
|
+
} catch (error) {
|
|
861
|
+
if (error instanceof z.ZodError)
|
|
862
|
+
return errorResult(`invalid arguments: ${z.prettifyError(error)}`);
|
|
863
|
+
return errorResult(error instanceof Error ? error.message : String(error));
|
|
864
|
+
}
|
|
865
|
+
};
|
|
866
|
+
const readResource = async (uri) => {
|
|
867
|
+
const path = decodeURI(uri.pathname);
|
|
868
|
+
const spec = byName.get("read_file" /* ReadFile */);
|
|
869
|
+
if (spec == null || !await isAllowed(spec, "read_file" /* ReadFile */, { path }))
|
|
870
|
+
throw new Error(`${path}: file read was not approved`);
|
|
871
|
+
const sandbox = await sandboxFor();
|
|
872
|
+
const bytes = await sandbox.readBinaryFile({ path });
|
|
873
|
+
if (bytes == null) throw new Error(`${path}: no such file`);
|
|
874
|
+
const text = new TextDecoder("utf-8", { fatal: false }).decode(bytes);
|
|
875
|
+
if (!text.includes("\0") && !text.includes("\uFFFD")) {
|
|
876
|
+
return { contents: [{ uri: uri.href, mimeType: "text/plain", text }] };
|
|
877
|
+
}
|
|
878
|
+
return {
|
|
879
|
+
contents: [
|
|
880
|
+
{ uri: uri.href, mimeType: "application/octet-stream", blob: bytesToBase64(bytes) }
|
|
881
|
+
]
|
|
882
|
+
};
|
|
883
|
+
};
|
|
884
|
+
const buildServer = () => {
|
|
885
|
+
const server = new McpServer(
|
|
886
|
+
{ name: args.name ?? "final-sandbox", version: args.version ?? "1" },
|
|
887
|
+
// The hosted servers' tools can follow the first list (see beforeList).
|
|
888
|
+
{
|
|
889
|
+
capabilities: {
|
|
890
|
+
tools: { listChanged: served.computerUse || served.localMcp },
|
|
891
|
+
resources: {}
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
);
|
|
895
|
+
for (const spec of specs) {
|
|
896
|
+
server.registerTool(
|
|
897
|
+
spec.descriptor.name,
|
|
898
|
+
{
|
|
899
|
+
title: spec.descriptor.annotations?.title,
|
|
900
|
+
description: spec.descriptor.description,
|
|
901
|
+
inputSchema: fromJsonSchema(spec.descriptor.inputSchema),
|
|
902
|
+
annotations: spec.descriptor.annotations
|
|
903
|
+
},
|
|
904
|
+
(input, ctx) => callTool(spec.descriptor.name, input, ctx.mcpReq.signal)
|
|
905
|
+
);
|
|
906
|
+
}
|
|
907
|
+
if (args.capabilities.includes(SandboxCapability.Files)) {
|
|
908
|
+
server.registerResource(
|
|
909
|
+
"sandbox-file",
|
|
910
|
+
new ResourceTemplate(`${SANDBOX_FILE_RESOURCE_SCHEME}://{+path}`, { list: void 0 }),
|
|
911
|
+
{ description: "A file in the sandbox, by absolute path." },
|
|
912
|
+
(uri) => readResource(uri)
|
|
913
|
+
);
|
|
914
|
+
}
|
|
915
|
+
return server;
|
|
916
|
+
};
|
|
917
|
+
const isListRequest = async (request) => {
|
|
918
|
+
if (request.method !== "POST") return false;
|
|
919
|
+
try {
|
|
920
|
+
const body = await request.clone().json();
|
|
921
|
+
return (Array.isArray(body) ? body : [body]).some(
|
|
922
|
+
(message) => listRequestSchema.safeParse(message).success
|
|
923
|
+
);
|
|
924
|
+
} catch {
|
|
925
|
+
return false;
|
|
926
|
+
}
|
|
927
|
+
};
|
|
928
|
+
const getHandler = () => {
|
|
929
|
+
handler ??= createMcpHandler(() => buildServer(), { onerror: args.onError });
|
|
930
|
+
return handler;
|
|
931
|
+
};
|
|
932
|
+
return {
|
|
933
|
+
get descriptors() {
|
|
934
|
+
return specs.map((spec) => spec.descriptor);
|
|
935
|
+
},
|
|
936
|
+
fetch: async (request) => {
|
|
937
|
+
if (await isListRequest(request)) await beforeList();
|
|
938
|
+
return getHandler().fetch(request);
|
|
939
|
+
},
|
|
940
|
+
listTools: () => specs.map((spec) => spec.descriptor),
|
|
941
|
+
refreshTools: async () => {
|
|
942
|
+
await beforeList(true);
|
|
943
|
+
return specs.map((spec) => spec.descriptor);
|
|
944
|
+
},
|
|
945
|
+
updateCatalog: (next) => {
|
|
946
|
+
mergeCatalog(next);
|
|
947
|
+
},
|
|
948
|
+
callTool,
|
|
949
|
+
close: async () => {
|
|
950
|
+
closed = true;
|
|
951
|
+
await shell.close();
|
|
952
|
+
for (const entry of processes.values()) await entry.process.kill().catch(() => void 0);
|
|
953
|
+
processes.clear();
|
|
954
|
+
await connections.close();
|
|
955
|
+
await handler?.close();
|
|
956
|
+
}
|
|
957
|
+
};
|
|
958
|
+
};
|
|
959
|
+
export {
|
|
960
|
+
COMPUTER_USE_SERVER_NAME,
|
|
961
|
+
SANDBOX_FILE_RESOURCE_SCHEME,
|
|
962
|
+
SandboxHostedServerKind2 as SandboxHostedServerKind,
|
|
963
|
+
SandboxMcpScopeKind,
|
|
964
|
+
SandboxMcpTool,
|
|
965
|
+
SandboxShellMode2 as SandboxShellMode,
|
|
966
|
+
createSandboxMcpServer,
|
|
967
|
+
sandboxMcpToolDescriptors,
|
|
968
|
+
sandboxMcpToolPolicyKeys,
|
|
969
|
+
withHostedMcpEndpoints
|
|
970
|
+
};
|
|
971
|
+
//# sourceMappingURL=mcp.mjs.map
|