@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,129 @@
|
|
|
1
|
+
import { RE2JS } from "re2js";
|
|
2
|
+
import { relativeTo, resolvePosixPath } from "./paths.mjs";
|
|
3
|
+
import { SandboxEntryType } from "./types.mjs";
|
|
4
|
+
const SKIPPED_DIRECTORIES = /* @__PURE__ */ new Set([".git", "node_modules", ".hg", ".svn"]);
|
|
5
|
+
const DEFAULT_WALK_LIMITS = {
|
|
6
|
+
maxEntries: 2e4,
|
|
7
|
+
maxFileBytes: 2 * 1024 * 1024
|
|
8
|
+
};
|
|
9
|
+
const globToRegExp = (glob) => {
|
|
10
|
+
let out = "";
|
|
11
|
+
let braceDepth = 0;
|
|
12
|
+
for (let i = 0; i < glob.length; i += 1) {
|
|
13
|
+
const char = glob[i] ?? "";
|
|
14
|
+
if (char === "*") {
|
|
15
|
+
if (glob[i + 1] === "*") {
|
|
16
|
+
const slash = glob[i + 2] === "/";
|
|
17
|
+
out += slash ? "(?:.*/)?" : ".*";
|
|
18
|
+
i += slash ? 2 : 1;
|
|
19
|
+
} else {
|
|
20
|
+
out += "[^/]*";
|
|
21
|
+
}
|
|
22
|
+
} else if (char === "?") {
|
|
23
|
+
out += "[^/]";
|
|
24
|
+
} else if (char === "[") {
|
|
25
|
+
const end = glob.indexOf("]", i + 1);
|
|
26
|
+
if (end < 0) out += "\\[";
|
|
27
|
+
else {
|
|
28
|
+
const body = glob.slice(i + 1, end).replace(/^!/, "^").replace(/\\/g, "\\\\");
|
|
29
|
+
out += `[${body}]`;
|
|
30
|
+
i = end;
|
|
31
|
+
}
|
|
32
|
+
} else if (char === "{") {
|
|
33
|
+
braceDepth += 1;
|
|
34
|
+
out += "(?:";
|
|
35
|
+
} else if (char === "}" && braceDepth > 0) {
|
|
36
|
+
braceDepth -= 1;
|
|
37
|
+
out += ")";
|
|
38
|
+
} else if (char === "," && braceDepth > 0) {
|
|
39
|
+
out += "|";
|
|
40
|
+
} else {
|
|
41
|
+
out += char.replace(/[.+^$()|\\]/g, "\\$&");
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return RE2JS.compile(`^${out}$`);
|
|
45
|
+
};
|
|
46
|
+
const walkTree = async (sandbox, root, options) => {
|
|
47
|
+
const limits = options.limits ?? DEFAULT_WALK_LIMITS;
|
|
48
|
+
const queue = [{ path: root, relative: "" }];
|
|
49
|
+
let seen = 0;
|
|
50
|
+
while (queue.length > 0) {
|
|
51
|
+
options.signal?.throwIfAborted();
|
|
52
|
+
const directory = queue.shift();
|
|
53
|
+
if (directory == null) break;
|
|
54
|
+
const entries = await sandbox.readDir({ path: directory.path }).catch(() => []);
|
|
55
|
+
for (const entry of entries) {
|
|
56
|
+
seen += 1;
|
|
57
|
+
if (seen > limits.maxEntries) return { truncated: true };
|
|
58
|
+
const relative = directory.relative === "" ? entry.name : `${directory.relative}/${entry.name}`;
|
|
59
|
+
const keepGoing = await options.onEntry({
|
|
60
|
+
path: entry.path,
|
|
61
|
+
relative,
|
|
62
|
+
type: entry.type,
|
|
63
|
+
size: entry.size
|
|
64
|
+
});
|
|
65
|
+
if (!keepGoing) return { truncated: true };
|
|
66
|
+
if (entry.type === SandboxEntryType.Directory && !SKIPPED_DIRECTORIES.has(entry.name))
|
|
67
|
+
queue.push({ path: entry.path, relative });
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return { truncated: false };
|
|
71
|
+
};
|
|
72
|
+
const globFiles = async (args) => {
|
|
73
|
+
const root = resolvePosixPath(args.cwd, args.path ?? ".");
|
|
74
|
+
const matcher = globToRegExp(
|
|
75
|
+
args.pattern.startsWith("/") ? relativeTo(root, args.pattern) ?? args.pattern : args.pattern
|
|
76
|
+
);
|
|
77
|
+
const paths = [];
|
|
78
|
+
const { truncated } = await walkTree(args.sandbox, root, {
|
|
79
|
+
signal: args.signal,
|
|
80
|
+
onEntry: (entry) => {
|
|
81
|
+
if (entry.type !== SandboxEntryType.Directory && matcher.test(entry.relative))
|
|
82
|
+
paths.push(entry.path);
|
|
83
|
+
return paths.length < args.maxResults;
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
return { paths: paths.sort(), truncated };
|
|
87
|
+
};
|
|
88
|
+
const grepFiles = async (args) => {
|
|
89
|
+
const regex = RE2JS.compile(args.pattern, args.ignoreCase === true ? RE2JS.CASE_INSENSITIVE : 0);
|
|
90
|
+
const fileFilter = args.glob == null ? null : globToRegExp(args.glob);
|
|
91
|
+
const root = resolvePosixPath(args.cwd, args.path ?? ".");
|
|
92
|
+
const matches = [];
|
|
93
|
+
const searchFile = async (path, relative) => {
|
|
94
|
+
if (fileFilter != null && !fileFilter.test(relative) && !fileFilter.test(relative.split("/").pop() ?? ""))
|
|
95
|
+
return;
|
|
96
|
+
const text = await args.sandbox.readTextFile({ path, abortSignal: args.signal }).catch(() => null);
|
|
97
|
+
if (text == null || text.includes("\0")) return;
|
|
98
|
+
const lines = text.split("\n");
|
|
99
|
+
for (let index = 0; index < lines.length && matches.length < args.maxResults; index += 1) {
|
|
100
|
+
const lineText = lines[index] ?? "";
|
|
101
|
+
if (regex.test(lineText))
|
|
102
|
+
matches.push({ path, line: index + 1, text: lineText.slice(0, 500) });
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
const rootStat = await args.sandbox.stat({ path: root });
|
|
106
|
+
if (rootStat == null) return { matches, truncated: false };
|
|
107
|
+
if (rootStat.type === SandboxEntryType.File) {
|
|
108
|
+
await searchFile(root, rootStat.path.split("/").pop() ?? root);
|
|
109
|
+
return { matches, truncated: matches.length >= args.maxResults };
|
|
110
|
+
}
|
|
111
|
+
const { truncated } = await walkTree(args.sandbox, root, {
|
|
112
|
+
signal: args.signal,
|
|
113
|
+
onEntry: async (entry) => {
|
|
114
|
+
if (entry.type === SandboxEntryType.File && (entry.size == null || entry.size <= DEFAULT_WALK_LIMITS.maxFileBytes)) {
|
|
115
|
+
await searchFile(entry.path, entry.relative);
|
|
116
|
+
}
|
|
117
|
+
return matches.length < args.maxResults;
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
return { matches, truncated };
|
|
121
|
+
};
|
|
122
|
+
export {
|
|
123
|
+
DEFAULT_WALK_LIMITS,
|
|
124
|
+
globFiles,
|
|
125
|
+
globToRegExp,
|
|
126
|
+
grepFiles,
|
|
127
|
+
walkTree
|
|
128
|
+
};
|
|
129
|
+
//# sourceMappingURL=mcp-search.mjs.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/sandbox/mcp-search.ts"],
|
|
4
|
+
"sourcesContent": ["import { RE2JS } from 're2js';\nimport { relativeTo, resolvePosixPath } from \"./paths.mjs\";\nimport { SandboxEntryType, type SandboxSessionIface } from \"./types.mjs\";\n\n// grep and glob over the handle's readDir/readTextFile, so they work on every\n// provider and on read-only bindings (no process execution).\n\nconst SKIPPED_DIRECTORIES = new Set(['.git', 'node_modules', '.hg', '.svn']);\n\nexport interface WalkLimitsIface {\n maxEntries: number;\n maxFileBytes: number;\n}\n\nexport const DEFAULT_WALK_LIMITS: WalkLimitsIface = {\n maxEntries: 20_000,\n maxFileBytes: 2 * 1024 * 1024,\n};\n\n// Converts a glob (`**`, `*`, `?`, `[...]`, `{a,b}`) to an anchored RegExp.\nexport const globToRegExp = (glob: string): { test(input: string): boolean } => {\n let out = '';\n let braceDepth = 0;\n for (let i = 0; i < glob.length; i += 1) {\n const char = glob[i] ?? '';\n if (char === '*') {\n if (glob[i + 1] === '*') {\n const slash = glob[i + 2] === '/';\n out += slash ? '(?:.*/)?' : '.*';\n i += slash ? 2 : 1;\n } else {\n out += '[^/]*';\n }\n } else if (char === '?') {\n out += '[^/]';\n } else if (char === '[') {\n const end = glob.indexOf(']', i + 1);\n if (end < 0) out += '\\\\[';\n else {\n const body = glob\n .slice(i + 1, end)\n .replace(/^!/, '^')\n .replace(/\\\\/g, '\\\\\\\\');\n out += `[${body}]`;\n i = end;\n }\n } else if (char === '{') {\n braceDepth += 1;\n out += '(?:';\n } else if (char === '}' && braceDepth > 0) {\n braceDepth -= 1;\n out += ')';\n } else if (char === ',' && braceDepth > 0) {\n out += '|';\n } else {\n out += char.replace(/[.+^$()|\\\\]/g, '\\\\$&');\n }\n }\n return RE2JS.compile(`^${out}$`);\n};\n\nexport interface WalkEntryIface {\n path: string;\n relative: string;\n type: SandboxEntryType;\n size: number | null;\n}\n\nexport const walkTree = async (\n sandbox: Pick<SandboxSessionIface, 'readDir'>,\n root: string,\n options: {\n limits?: WalkLimitsIface;\n signal?: AbortSignal;\n onEntry: (entry: WalkEntryIface) => boolean | Promise<boolean>;\n },\n): Promise<{ truncated: boolean }> => {\n const limits = options.limits ?? DEFAULT_WALK_LIMITS;\n // Relative paths come from entry names: the sandbox reports its own paths,\n // which need not sit under `root` as written (a mapped session root, or a\n // Windows Device).\n const queue = [{ path: root, relative: '' }];\n let seen = 0;\n while (queue.length > 0) {\n options.signal?.throwIfAborted();\n const directory = queue.shift();\n if (directory == null) break;\n const entries = await sandbox.readDir({ path: directory.path }).catch(() => []);\n for (const entry of entries) {\n seen += 1;\n if (seen > limits.maxEntries) return { truncated: true };\n const relative =\n directory.relative === '' ? entry.name : `${directory.relative}/${entry.name}`;\n const keepGoing = await options.onEntry({\n path: entry.path,\n relative,\n type: entry.type,\n size: entry.size,\n });\n if (!keepGoing) return { truncated: true };\n if (entry.type === SandboxEntryType.Directory && !SKIPPED_DIRECTORIES.has(entry.name))\n queue.push({ path: entry.path, relative });\n }\n }\n return { truncated: false };\n};\n\nexport const globFiles = async (args: {\n sandbox: Pick<SandboxSessionIface, 'readDir'>;\n cwd: string;\n pattern: string;\n path?: string;\n maxResults: number;\n signal?: AbortSignal;\n}): Promise<{ paths: string[]; truncated: boolean }> => {\n const root = resolvePosixPath(args.cwd, args.path ?? '.');\n const matcher = globToRegExp(\n args.pattern.startsWith('/') ? (relativeTo(root, args.pattern) ?? args.pattern) : args.pattern,\n );\n const paths: string[] = [];\n const { truncated } = await walkTree(args.sandbox, root, {\n signal: args.signal,\n onEntry: (entry) => {\n if (entry.type !== SandboxEntryType.Directory && matcher.test(entry.relative))\n paths.push(entry.path);\n return paths.length < args.maxResults;\n },\n });\n return { paths: paths.sort(), truncated };\n};\n\nexport interface GrepMatchIface {\n path: string;\n line: number;\n text: string;\n}\n\nexport const grepFiles = async (args: {\n sandbox: Pick<SandboxSessionIface, 'readDir' | 'readTextFile' | 'stat'>;\n cwd: string;\n pattern: string;\n path?: string;\n glob?: string;\n ignoreCase?: boolean;\n maxResults: number;\n signal?: AbortSignal;\n}): Promise<{ matches: GrepMatchIface[]; truncated: boolean }> => {\n const regex = RE2JS.compile(args.pattern, args.ignoreCase === true ? RE2JS.CASE_INSENSITIVE : 0);\n const fileFilter = args.glob == null ? null : globToRegExp(args.glob);\n const root = resolvePosixPath(args.cwd, args.path ?? '.');\n const matches: GrepMatchIface[] = [];\n const searchFile = async (path: string, relative: string) => {\n if (\n fileFilter != null &&\n !fileFilter.test(relative) &&\n !fileFilter.test(relative.split('/').pop() ?? '')\n )\n return;\n const text = await args.sandbox\n .readTextFile({ path, abortSignal: args.signal })\n .catch(() => null);\n if (text == null || text.includes('\\u0000')) return;\n const lines = text.split('\\n');\n for (let index = 0; index < lines.length && matches.length < args.maxResults; index += 1) {\n const lineText = lines[index] ?? '';\n if (regex.test(lineText))\n matches.push({ path, line: index + 1, text: lineText.slice(0, 500) });\n }\n };\n const rootStat = await args.sandbox.stat({ path: root });\n if (rootStat == null) return { matches, truncated: false };\n if (rootStat.type === SandboxEntryType.File) {\n await searchFile(root, rootStat.path.split('/').pop() ?? root);\n return { matches, truncated: matches.length >= args.maxResults };\n }\n const { truncated } = await walkTree(args.sandbox, root, {\n signal: args.signal,\n onEntry: async (entry) => {\n if (\n entry.type === SandboxEntryType.File &&\n (entry.size == null || entry.size <= DEFAULT_WALK_LIMITS.maxFileBytes)\n ) {\n await searchFile(entry.path, entry.relative);\n }\n return matches.length < args.maxResults;\n },\n });\n return { matches, truncated };\n};\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { NetworkSandboxSessionIface, SandboxRunResultIface } from "./types.mjs";
|
|
2
|
+
export declare enum SandboxShellMode {
|
|
3
|
+
Persistent = "persistent",
|
|
4
|
+
PerCommand = "per-command"
|
|
5
|
+
}
|
|
6
|
+
export interface ShellRunArgsIface {
|
|
7
|
+
command: string;
|
|
8
|
+
cwd?: string;
|
|
9
|
+
env?: Record<string, string>;
|
|
10
|
+
timeoutMs?: number;
|
|
11
|
+
signal?: AbortSignal;
|
|
12
|
+
}
|
|
13
|
+
export interface SandboxShellIface {
|
|
14
|
+
run(args: ShellRunArgsIface): Promise<SandboxRunResultIface>;
|
|
15
|
+
close(): Promise<void>;
|
|
16
|
+
persistent(): boolean;
|
|
17
|
+
}
|
|
18
|
+
export declare const createSandboxShell: (args: {
|
|
19
|
+
getSandbox: () => Promise<NetworkSandboxSessionIface>;
|
|
20
|
+
mode?: SandboxShellMode;
|
|
21
|
+
}) => SandboxShellIface;
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { bytesToBase64, utf8Encode } from "./bytes.mjs";
|
|
2
|
+
import { shellQuote } from "./paths.mjs";
|
|
3
|
+
var SandboxShellMode = /* @__PURE__ */ ((SandboxShellMode2) => {
|
|
4
|
+
SandboxShellMode2["Persistent"] = "persistent";
|
|
5
|
+
SandboxShellMode2["PerCommand"] = "per-command";
|
|
6
|
+
return SandboxShellMode2;
|
|
7
|
+
})(SandboxShellMode || {});
|
|
8
|
+
const STARTUP_TIMEOUT_MS = 5e3;
|
|
9
|
+
const TIMEOUT_EXIT_CODE = 124;
|
|
10
|
+
const ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
11
|
+
const randomSentinel = () => `__FINAL_SHELL_${crypto.randomUUID().replace(/-/g, "")}__`;
|
|
12
|
+
const pumpText = async (stream, append) => {
|
|
13
|
+
const decoder = new TextDecoder();
|
|
14
|
+
const reader = stream.getReader();
|
|
15
|
+
try {
|
|
16
|
+
for (; ; ) {
|
|
17
|
+
const { done, value } = await reader.read();
|
|
18
|
+
if (done) break;
|
|
19
|
+
append(decoder.decode(value, { stream: true }));
|
|
20
|
+
}
|
|
21
|
+
} catch {
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
const createSandboxShell = (args) => {
|
|
25
|
+
let mode = args.mode ?? "persistent" /* Persistent */;
|
|
26
|
+
let live = null;
|
|
27
|
+
let queue = Promise.resolve();
|
|
28
|
+
const notify = (shell) => {
|
|
29
|
+
const changed = shell.changed;
|
|
30
|
+
shell.changed = null;
|
|
31
|
+
changed?.();
|
|
32
|
+
};
|
|
33
|
+
const start = async (sandbox) => {
|
|
34
|
+
const process = await sandbox.spawn({
|
|
35
|
+
command: "bash",
|
|
36
|
+
args: ["--noprofile", "--norc"],
|
|
37
|
+
cwd: sandbox.defaultWorkingDirectory
|
|
38
|
+
});
|
|
39
|
+
const shell = {
|
|
40
|
+
process,
|
|
41
|
+
stdout: "",
|
|
42
|
+
stderr: "",
|
|
43
|
+
exited: false,
|
|
44
|
+
exitCode: null,
|
|
45
|
+
changed: null
|
|
46
|
+
};
|
|
47
|
+
void pumpText(process.stdout, (text) => {
|
|
48
|
+
shell.stdout += text;
|
|
49
|
+
notify(shell);
|
|
50
|
+
});
|
|
51
|
+
void pumpText(process.stderr, (text) => {
|
|
52
|
+
shell.stderr += text;
|
|
53
|
+
notify(shell);
|
|
54
|
+
});
|
|
55
|
+
process.wait().then(
|
|
56
|
+
(result) => {
|
|
57
|
+
shell.exited = true;
|
|
58
|
+
shell.exitCode = result.exitCode;
|
|
59
|
+
notify(shell);
|
|
60
|
+
},
|
|
61
|
+
() => {
|
|
62
|
+
shell.exited = true;
|
|
63
|
+
notify(shell);
|
|
64
|
+
}
|
|
65
|
+
);
|
|
66
|
+
return shell;
|
|
67
|
+
};
|
|
68
|
+
const waitFor = (shell, done, deadline) => new Promise((resolve) => {
|
|
69
|
+
const timer = deadline == null ? null : setTimeout(() => resolve(done()), Math.max(0, deadline - Date.now()));
|
|
70
|
+
const check = () => {
|
|
71
|
+
if (done() || shell.exited) {
|
|
72
|
+
if (timer != null) clearTimeout(timer);
|
|
73
|
+
resolve(done());
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
shell.changed = check;
|
|
77
|
+
};
|
|
78
|
+
check();
|
|
79
|
+
});
|
|
80
|
+
const exchange = async (shell, run) => {
|
|
81
|
+
const sentinel = randomSentinel();
|
|
82
|
+
const exports = Object.entries(run.env ?? {}).filter(([key]) => ENV_NAME.test(key)).map(([key, value]) => `export ${key}=${shellQuote(value)}
|
|
83
|
+
`).join("");
|
|
84
|
+
const cd = run.cwd == null ? "" : `cd -- ${shellQuote(run.cwd)} && `;
|
|
85
|
+
const inner = `${cd}{
|
|
86
|
+
${run.command}
|
|
87
|
+
}`;
|
|
88
|
+
const body = exports === "" ? inner : `(
|
|
89
|
+
${exports}${inner}
|
|
90
|
+
)`;
|
|
91
|
+
const script = `eval "$(printf '%s' '${bytesToBase64(utf8Encode(body))}' | base64 -d)" < /dev/null
|
|
92
|
+
__final_status=$?
|
|
93
|
+
printf '\\n%s %d\\n' '${sentinel}' "$__final_status"
|
|
94
|
+
printf '\\n%s\\n' '${sentinel}' >&2
|
|
95
|
+
`;
|
|
96
|
+
shell.stdout = "";
|
|
97
|
+
shell.stderr = "";
|
|
98
|
+
try {
|
|
99
|
+
await shell.process.write(script);
|
|
100
|
+
} catch {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
const stdoutMarker = new RegExp(`\\n${sentinel} (\\d+)\\n`);
|
|
104
|
+
const stderrMarker = `
|
|
105
|
+
${sentinel}
|
|
106
|
+
`;
|
|
107
|
+
const finished = () => stdoutMarker.test(shell.stdout) && shell.stderr.includes(stderrMarker);
|
|
108
|
+
const deadline = run.timeoutMs == null ? null : Date.now() + run.timeoutMs;
|
|
109
|
+
const ok = await waitFor(shell, finished, deadline);
|
|
110
|
+
if (!ok) {
|
|
111
|
+
if (!shell.exited) return { timedOut: true };
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
const match = stdoutMarker.exec(shell.stdout);
|
|
115
|
+
const stdoutEnd = match?.index ?? shell.stdout.length;
|
|
116
|
+
const stderrEnd = shell.stderr.indexOf(stderrMarker);
|
|
117
|
+
return {
|
|
118
|
+
exitCode: Number(match?.[1] ?? "1"),
|
|
119
|
+
stdout: shell.stdout.slice(0, stdoutEnd),
|
|
120
|
+
stderr: shell.stderr.slice(0, stderrEnd < 0 ? shell.stderr.length : stderrEnd)
|
|
121
|
+
};
|
|
122
|
+
};
|
|
123
|
+
const runOnce = async (sandbox, run) => sandbox.run({
|
|
124
|
+
command: run.command,
|
|
125
|
+
shell: true,
|
|
126
|
+
cwd: run.cwd,
|
|
127
|
+
env: run.env,
|
|
128
|
+
timeoutMs: run.timeoutMs,
|
|
129
|
+
abortSignal: run.signal
|
|
130
|
+
});
|
|
131
|
+
const runPersistent = async (run) => {
|
|
132
|
+
run.signal?.throwIfAborted();
|
|
133
|
+
const sandbox = await args.getSandbox();
|
|
134
|
+
if (mode === "per-command" /* PerCommand */) return runOnce(sandbox, run);
|
|
135
|
+
const fresh = live == null || live.exited;
|
|
136
|
+
if (fresh) {
|
|
137
|
+
try {
|
|
138
|
+
live = await start(sandbox);
|
|
139
|
+
} catch {
|
|
140
|
+
mode = "per-command" /* PerCommand */;
|
|
141
|
+
return runOnce(sandbox, run);
|
|
142
|
+
}
|
|
143
|
+
const probe = await exchange(live, { command: "true", timeoutMs: STARTUP_TIMEOUT_MS });
|
|
144
|
+
if (probe == null || "timedOut" in probe) {
|
|
145
|
+
await live.process.kill().catch(() => void 0);
|
|
146
|
+
live = null;
|
|
147
|
+
mode = "per-command" /* PerCommand */;
|
|
148
|
+
return runOnce(sandbox, run);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
const shell = live;
|
|
152
|
+
if (shell == null) return runOnce(sandbox, run);
|
|
153
|
+
const abort = () => {
|
|
154
|
+
void shell.process.kill().catch(() => void 0);
|
|
155
|
+
};
|
|
156
|
+
run.signal?.addEventListener("abort", abort, { once: true });
|
|
157
|
+
let result;
|
|
158
|
+
try {
|
|
159
|
+
result = await exchange(shell, run);
|
|
160
|
+
} finally {
|
|
161
|
+
run.signal?.removeEventListener("abort", abort);
|
|
162
|
+
}
|
|
163
|
+
if (run.signal?.aborted === true) {
|
|
164
|
+
live = null;
|
|
165
|
+
run.signal.throwIfAborted();
|
|
166
|
+
}
|
|
167
|
+
if (result == null) {
|
|
168
|
+
live = null;
|
|
169
|
+
return { exitCode: shell.exitCode ?? 1, stdout: shell.stdout, stderr: shell.stderr };
|
|
170
|
+
}
|
|
171
|
+
if ("timedOut" in result) {
|
|
172
|
+
const partial = { stdout: shell.stdout, stderr: shell.stderr };
|
|
173
|
+
await shell.process.kill().catch(() => void 0);
|
|
174
|
+
live = null;
|
|
175
|
+
return {
|
|
176
|
+
exitCode: TIMEOUT_EXIT_CODE,
|
|
177
|
+
stdout: partial.stdout,
|
|
178
|
+
stderr: `${partial.stderr}
|
|
179
|
+
[timed out after ${run.timeoutMs ?? 0} ms; the shell was restarted]`
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
return result;
|
|
183
|
+
};
|
|
184
|
+
return {
|
|
185
|
+
run: (run) => {
|
|
186
|
+
const next = queue.then(
|
|
187
|
+
() => runPersistent(run),
|
|
188
|
+
() => runPersistent(run)
|
|
189
|
+
);
|
|
190
|
+
queue = next.catch(() => void 0);
|
|
191
|
+
return next;
|
|
192
|
+
},
|
|
193
|
+
close: async () => {
|
|
194
|
+
const shell = live;
|
|
195
|
+
live = null;
|
|
196
|
+
await shell?.process.kill().catch(() => void 0);
|
|
197
|
+
},
|
|
198
|
+
persistent: () => mode === "persistent" /* Persistent */ && live != null
|
|
199
|
+
};
|
|
200
|
+
};
|
|
201
|
+
export {
|
|
202
|
+
SandboxShellMode,
|
|
203
|
+
createSandboxShell
|
|
204
|
+
};
|
|
205
|
+
//# sourceMappingURL=mcp-shell.mjs.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/sandbox/mcp-shell.ts"],
|
|
4
|
+
"sourcesContent": ["import { bytesToBase64, utf8Encode } from \"./bytes.mjs\";\nimport { shellQuote } from \"./paths.mjs\";\nimport type {\n NetworkSandboxSessionIface,\n SandboxProcessIface,\n SandboxRunResultIface,\n} from \"./types.mjs\";\n\n// A persistent bash per MCP projection, so `cd`, exported variables and shell\n// state survive between `run` calls and no command pays a fresh `bash -lc`. Commands run one at a time; each is sent base64\n// encoded and `eval`ed with stdin from /dev/null, then a random sentinel line\n// carrying the exit status marks the end of its stdout and stderr. A command\n// that exits the shell ends it; the next command starts a new one. Sandboxes\n// without bash fall back to one `run` per command.\n\nexport enum SandboxShellMode {\n Persistent = 'persistent',\n PerCommand = 'per-command',\n}\n\nexport interface ShellRunArgsIface {\n command: string;\n cwd?: string;\n env?: Record<string, string>;\n timeoutMs?: number;\n signal?: AbortSignal;\n}\n\nexport interface SandboxShellIface {\n run(args: ShellRunArgsIface): Promise<SandboxRunResultIface>;\n close(): Promise<void>;\n // Whether the persistent shell is in use (false after a fallback).\n persistent(): boolean;\n}\n\nconst STARTUP_TIMEOUT_MS = 5_000;\nconst TIMEOUT_EXIT_CODE = 124;\nconst ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;\n\ninterface LiveShellIface {\n process: SandboxProcessIface;\n stdout: string;\n stderr: string;\n exited: boolean;\n exitCode: number | null;\n changed: (() => void) | null;\n}\n\nconst randomSentinel = () => `__FINAL_SHELL_${crypto.randomUUID().replace(/-/g, '')}__`;\n\nconst pumpText = async (stream: ReadableStream<Uint8Array>, append: (text: string) => void) => {\n const decoder = new TextDecoder();\n const reader = stream.getReader();\n try {\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n append(decoder.decode(value, { stream: true }));\n }\n } catch {\n // The process or connection went away; `exited` reports it.\n }\n};\n\nexport const createSandboxShell = (args: {\n getSandbox: () => Promise<NetworkSandboxSessionIface>;\n mode?: SandboxShellMode;\n}): SandboxShellIface => {\n let mode = args.mode ?? SandboxShellMode.Persistent;\n let live: LiveShellIface | null = null;\n let queue: Promise<unknown> = Promise.resolve();\n\n const notify = (shell: LiveShellIface) => {\n const changed = shell.changed;\n shell.changed = null;\n changed?.();\n };\n\n const start = async (sandbox: NetworkSandboxSessionIface): Promise<LiveShellIface> => {\n const process = await sandbox.spawn({\n command: 'bash',\n args: ['--noprofile', '--norc'],\n cwd: sandbox.defaultWorkingDirectory,\n });\n const shell: LiveShellIface = {\n process,\n stdout: '',\n stderr: '',\n exited: false,\n exitCode: null,\n changed: null,\n };\n void pumpText(process.stdout, (text) => {\n shell.stdout += text;\n notify(shell);\n });\n void pumpText(process.stderr, (text) => {\n shell.stderr += text;\n notify(shell);\n });\n process.wait().then(\n (result) => {\n shell.exited = true;\n shell.exitCode = result.exitCode;\n notify(shell);\n },\n () => {\n shell.exited = true;\n notify(shell);\n },\n );\n return shell;\n };\n\n const waitFor = (\n shell: LiveShellIface,\n done: () => boolean,\n deadline: number | null,\n ): Promise<boolean> =>\n new Promise<boolean>((resolve) => {\n const timer =\n deadline == null\n ? null\n : setTimeout(() => resolve(done()), Math.max(0, deadline - Date.now()));\n const check = () => {\n if (done() || shell.exited) {\n if (timer != null) clearTimeout(timer);\n resolve(done());\n return;\n }\n shell.changed = check;\n };\n check();\n });\n\n // Sends one framed command and collects its output. Returns null when the\n // shell died before reporting (so the caller can fall back or restart).\n const exchange = async (\n shell: LiveShellIface,\n run: ShellRunArgsIface,\n ): Promise<SandboxRunResultIface | { timedOut: true } | null> => {\n const sentinel = randomSentinel();\n const exports = Object.entries(run.env ?? {})\n .filter(([key]) => ENV_NAME.test(key))\n .map(([key, value]) => `export ${key}=${shellQuote(value)}\\n`)\n .join('');\n const cd = run.cwd == null ? '' : `cd -- ${shellQuote(run.cwd)} && `;\n // Per-command variables must not leak into later commands: use a subshell.\n const inner = `${cd}{\\n${run.command}\\n}`;\n const body = exports === '' ? inner : `(\\n${exports}${inner}\\n)`;\n const script =\n `eval \"$(printf '%s' '${bytesToBase64(utf8Encode(body))}' | base64 -d)\" < /dev/null\\n` +\n `__final_status=$?\\n` +\n `printf '\\\\n%s %d\\\\n' '${sentinel}' \"$__final_status\"\\n` +\n `printf '\\\\n%s\\\\n' '${sentinel}' >&2\\n`;\n shell.stdout = '';\n shell.stderr = '';\n try {\n await shell.process.write(script);\n } catch {\n // stdin is gone: the shell exited (or never ran).\n return null;\n }\n const stdoutMarker = new RegExp(`\\\\n${sentinel} (\\\\d+)\\\\n`);\n const stderrMarker = `\\n${sentinel}\\n`;\n const finished = () => stdoutMarker.test(shell.stdout) && shell.stderr.includes(stderrMarker);\n const deadline = run.timeoutMs == null ? null : Date.now() + run.timeoutMs;\n const ok = await waitFor(shell, finished, deadline);\n if (!ok) {\n if (!shell.exited) return { timedOut: true };\n return null;\n }\n const match = stdoutMarker.exec(shell.stdout);\n const stdoutEnd = match?.index ?? shell.stdout.length;\n const stderrEnd = shell.stderr.indexOf(stderrMarker);\n return {\n exitCode: Number(match?.[1] ?? '1'),\n stdout: shell.stdout.slice(0, stdoutEnd),\n stderr: shell.stderr.slice(0, stderrEnd < 0 ? shell.stderr.length : stderrEnd),\n };\n };\n\n const runOnce = async (sandbox: NetworkSandboxSessionIface, run: ShellRunArgsIface) =>\n sandbox.run({\n command: run.command,\n shell: true,\n cwd: run.cwd,\n env: run.env,\n timeoutMs: run.timeoutMs,\n abortSignal: run.signal,\n });\n\n const runPersistent = async (run: ShellRunArgsIface): Promise<SandboxRunResultIface> => {\n run.signal?.throwIfAborted();\n const sandbox = await args.getSandbox();\n if (mode === SandboxShellMode.PerCommand) return runOnce(sandbox, run);\n const fresh = live == null || live.exited;\n if (fresh) {\n try {\n live = await start(sandbox);\n } catch {\n mode = SandboxShellMode.PerCommand;\n return runOnce(sandbox, run);\n }\n // Probe once so a sandbox without bash falls back instead of hanging.\n const probe = await exchange(live, { command: 'true', timeoutMs: STARTUP_TIMEOUT_MS });\n if (probe == null || 'timedOut' in probe) {\n await live.process.kill().catch(() => undefined);\n live = null;\n mode = SandboxShellMode.PerCommand;\n return runOnce(sandbox, run);\n }\n }\n const shell = live;\n if (shell == null) return runOnce(sandbox, run);\n const abort = (): void => {\n void shell.process.kill().catch(() => undefined);\n };\n run.signal?.addEventListener('abort', abort, { once: true });\n let result: Awaited<ReturnType<typeof exchange>>;\n try {\n result = await exchange(shell, run);\n } finally {\n run.signal?.removeEventListener('abort', abort);\n }\n if (run.signal?.aborted === true) {\n live = null;\n run.signal.throwIfAborted();\n }\n if (result == null) {\n // The command ended the shell (`exit`); report what it printed.\n live = null;\n return { exitCode: shell.exitCode ?? 1, stdout: shell.stdout, stderr: shell.stderr };\n }\n if ('timedOut' in result) {\n const partial = { stdout: shell.stdout, stderr: shell.stderr };\n await shell.process.kill().catch(() => undefined);\n live = null;\n return {\n exitCode: TIMEOUT_EXIT_CODE,\n stdout: partial.stdout,\n stderr: `${partial.stderr}\\n[timed out after ${run.timeoutMs ?? 0} ms; the shell was restarted]`,\n };\n }\n return result;\n };\n\n return {\n run: (run) => {\n const next = queue.then(\n () => runPersistent(run),\n () => runPersistent(run),\n );\n queue = next.catch(() => undefined);\n return next;\n },\n close: async () => {\n const shell = live;\n live = null;\n await shell?.process.kill().catch(() => undefined);\n },\n persistent: () => mode === SandboxShellMode.Persistent && live != null,\n };\n};\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["SandboxShellMode"]
|
|
7
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/server';
|
|
2
|
+
import type { McpChannelEndpointIface } from "./types.mjs";
|
|
3
|
+
export interface InProcessMcpChannelHooksIface {
|
|
4
|
+
onConnect?: () => void;
|
|
5
|
+
failReceive?: () => boolean;
|
|
6
|
+
}
|
|
7
|
+
export declare const createInProcessMcpChannelEndpoint: (args: {
|
|
8
|
+
name: string;
|
|
9
|
+
register: (server: McpServer) => void;
|
|
10
|
+
hooks?: InProcessMcpChannelHooksIface;
|
|
11
|
+
}) => McpChannelEndpointIface;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { InMemoryTransport, parseJSONRPCMessage } from "@modelcontextprotocol/client";
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/server";
|
|
3
|
+
import { jsonValueSchema } from "../json.mjs";
|
|
4
|
+
const createInProcessMcpChannelEndpoint = (args) => ({
|
|
5
|
+
name: args.name,
|
|
6
|
+
connect: async () => {
|
|
7
|
+
args.hooks?.onConnect?.();
|
|
8
|
+
const server = new McpServer({ name: args.name, version: "1" });
|
|
9
|
+
args.register(server);
|
|
10
|
+
const [ours, theirs] = InMemoryTransport.createLinkedPair();
|
|
11
|
+
await server.connect(theirs);
|
|
12
|
+
const inbox = [];
|
|
13
|
+
ours.onmessage = (message) => {
|
|
14
|
+
inbox.push(jsonValueSchema.parse(JSON.parse(JSON.stringify(message))));
|
|
15
|
+
};
|
|
16
|
+
await ours.start();
|
|
17
|
+
return {
|
|
18
|
+
send: async (message) => ours.send(parseJSONRPCMessage(message)),
|
|
19
|
+
receive: async () => {
|
|
20
|
+
if (args.hooks?.failReceive?.() === true) throw new Error("the MCP pipe broke");
|
|
21
|
+
return inbox.splice(0);
|
|
22
|
+
},
|
|
23
|
+
close: async () => {
|
|
24
|
+
await ours.close();
|
|
25
|
+
await server.close();
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
export {
|
|
31
|
+
createInProcessMcpChannelEndpoint
|
|
32
|
+
};
|
|
33
|
+
//# sourceMappingURL=mcp-testing.mjs.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/sandbox/mcp-testing.ts"],
|
|
4
|
+
"sourcesContent": ["import { InMemoryTransport, parseJSONRPCMessage } from '@modelcontextprotocol/client';\nimport { McpServer } from '@modelcontextprotocol/server';\nimport { jsonValueSchema, type JsonValue } from \"../json.mjs\";\nimport type { McpChannelEndpointIface, McpChannelIface } from \"./types.mjs\";\n\n// A sandbox-hosted MCP server as a channel endpoint backed by an in-process MCP\n// server, as a provider whose backend relays a pipe to the sandbox's server\n// would hand it over. For tests of hosts and providers.\n\nexport interface InProcessMcpChannelHooksIface {\n onConnect?: () => void;\n // Fails `receive` while it returns true (a broken pipe).\n failReceive?: () => boolean;\n}\n\nexport const createInProcessMcpChannelEndpoint = (args: {\n name: string;\n register: (server: McpServer) => void;\n hooks?: InProcessMcpChannelHooksIface;\n}): McpChannelEndpointIface => ({\n name: args.name,\n connect: async (): Promise<McpChannelIface> => {\n args.hooks?.onConnect?.();\n const server = new McpServer({ name: args.name, version: '1' });\n args.register(server);\n const [ours, theirs] = InMemoryTransport.createLinkedPair();\n await server.connect(theirs);\n const inbox: JsonValue[] = [];\n ours.onmessage = (message) => {\n inbox.push(jsonValueSchema.parse(JSON.parse(JSON.stringify(message))));\n };\n await ours.start();\n return {\n send: async (message) => ours.send(parseJSONRPCMessage(message)),\n receive: async () => {\n if (args.hooks?.failReceive?.() === true) throw new Error('the MCP pipe broke');\n return inbox.splice(0);\n },\n close: async () => {\n await ours.close();\n await server.close();\n },\n };\n },\n});\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,mBAAmB,2BAA2B;AACvD,SAAS,iBAAiB;AAC1B,SAAS,uBAAuC;AAazC,MAAM,oCAAoC,CAAC,UAIlB;AAAA,EAC9B,MAAM,KAAK;AAAA,EACX,SAAS,YAAsC;AAC7C,SAAK,OAAO,YAAY;AACxB,UAAM,SAAS,IAAI,UAAU,EAAE,MAAM,KAAK,MAAM,SAAS,IAAI,CAAC;AAC9D,SAAK,SAAS,MAAM;AACpB,UAAM,CAAC,MAAM,MAAM,IAAI,kBAAkB,iBAAiB;AAC1D,UAAM,OAAO,QAAQ,MAAM;AAC3B,UAAM,QAAqB,CAAC;AAC5B,SAAK,YAAY,CAAC,YAAY;AAC5B,YAAM,KAAK,gBAAgB,MAAM,KAAK,MAAM,KAAK,UAAU,OAAO,CAAC,CAAC,CAAC;AAAA,IACvE;AACA,UAAM,KAAK,MAAM;AACjB,WAAO;AAAA,MACL,MAAM,OAAO,YAAY,KAAK,KAAK,oBAAoB,OAAO,CAAC;AAAA,MAC/D,SAAS,YAAY;AACnB,YAAI,KAAK,OAAO,cAAc,MAAM,KAAM,OAAM,IAAI,MAAM,oBAAoB;AAC9E,eAAO,MAAM,OAAO,CAAC;AAAA,MACvB;AAAA,MACA,OAAO,YAAY;AACjB,cAAM,KAAK,MAAM;AACjB,cAAM,OAAO,MAAM;AAAA,MACrB;AAAA,IACF;AAAA,EACF;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { FetchLike } from '@modelcontextprotocol/client';
|
|
2
|
+
import { type CallToolResult } from '@modelcontextprotocol/server';
|
|
3
|
+
import type { JsonObject } from "../json.mjs";
|
|
4
|
+
import { SandboxAccess, ToolApproval, type ToolDescriptorIface } from "../manifest.mjs";
|
|
5
|
+
import { SandboxShellMode } from "./mcp-shell.mjs";
|
|
6
|
+
import { SandboxCapability, type NetworkSandboxSessionIface, type SandboxMcpCatalogIface } from "./types.mjs";
|
|
7
|
+
export { COMPUTER_USE_SERVER_NAME, SandboxHostedServerKind, withHostedMcpEndpoints, type SandboxHostedServer, } from "./mcp-hosted.mjs";
|
|
8
|
+
export { SandboxShellMode } from "./mcp-shell.mjs";
|
|
9
|
+
export declare enum SandboxMcpTool {
|
|
10
|
+
Run = "run",
|
|
11
|
+
Spawn = "spawn",
|
|
12
|
+
ProcessOutput = "process_output",
|
|
13
|
+
ProcessInput = "process_input",
|
|
14
|
+
ProcessTerminate = "process_terminate",
|
|
15
|
+
ReadFile = "read_file",
|
|
16
|
+
WriteFile = "write_file",
|
|
17
|
+
EditFile = "edit_file",
|
|
18
|
+
ListDir = "list_dir",
|
|
19
|
+
Stat = "stat",
|
|
20
|
+
Grep = "grep",
|
|
21
|
+
Glob = "glob",
|
|
22
|
+
GetHost = "get_host"
|
|
23
|
+
}
|
|
24
|
+
export declare enum SandboxMcpScopeKind {
|
|
25
|
+
All = "all",
|
|
26
|
+
Hosted = "hosted",
|
|
27
|
+
ComputerUse = "computerUse",
|
|
28
|
+
LocalServer = "localServer"
|
|
29
|
+
}
|
|
30
|
+
export type SandboxMcpScope = {
|
|
31
|
+
kind: SandboxMcpScopeKind.All | SandboxMcpScopeKind.Hosted | SandboxMcpScopeKind.ComputerUse;
|
|
32
|
+
} | {
|
|
33
|
+
kind: SandboxMcpScopeKind.LocalServer;
|
|
34
|
+
server: string;
|
|
35
|
+
};
|
|
36
|
+
export declare const SANDBOX_FILE_RESOURCE_SCHEME = "sandbox-file";
|
|
37
|
+
export interface SandboxMcpDescriptorOptionsIface {
|
|
38
|
+
capabilities: readonly SandboxCapability[];
|
|
39
|
+
access?: SandboxAccess;
|
|
40
|
+
catalog?: SandboxMcpCatalogIface;
|
|
41
|
+
scope?: SandboxMcpScope;
|
|
42
|
+
}
|
|
43
|
+
export declare const sandboxMcpToolDescriptors: (options: SandboxMcpDescriptorOptionsIface) => ToolDescriptorIface[];
|
|
44
|
+
export declare const sandboxMcpToolPolicyKeys: (options: SandboxMcpDescriptorOptionsIface, name: string) => string[] | null;
|
|
45
|
+
export interface CreateSandboxMcpServerArgsIface extends SandboxMcpDescriptorOptionsIface {
|
|
46
|
+
getSandbox: () => Promise<NetworkSandboxSessionIface>;
|
|
47
|
+
toolPolicy?: Record<string, ToolApproval>;
|
|
48
|
+
approve?: (toolName: string, args: JsonObject) => Promise<boolean>;
|
|
49
|
+
shell?: SandboxShellMode;
|
|
50
|
+
name?: string;
|
|
51
|
+
version?: string;
|
|
52
|
+
fetch?: FetchLike;
|
|
53
|
+
limits?: Partial<SandboxMcpLimitsIface>;
|
|
54
|
+
onError?: (error: Error) => void;
|
|
55
|
+
peekSandbox?: () => NetworkSandboxSessionIface | null;
|
|
56
|
+
onCatalog?: (catalog: SandboxMcpCatalogIface) => void;
|
|
57
|
+
onProcessExit?: (exit: SandboxProcessExitIface) => void;
|
|
58
|
+
}
|
|
59
|
+
export interface SandboxProcessExitIface {
|
|
60
|
+
processId: string;
|
|
61
|
+
command: string;
|
|
62
|
+
exitCode: number | null;
|
|
63
|
+
stdout: string;
|
|
64
|
+
stderr: string;
|
|
65
|
+
}
|
|
66
|
+
export interface SandboxMcpLimitsIface {
|
|
67
|
+
maxOutputChars: number;
|
|
68
|
+
maxInlineBytes: number;
|
|
69
|
+
processBufferBytes: number;
|
|
70
|
+
maxProcesses: number;
|
|
71
|
+
exitReportGraceMs: number;
|
|
72
|
+
exitOutputChars: number;
|
|
73
|
+
}
|
|
74
|
+
export interface SandboxMcpServerIface {
|
|
75
|
+
readonly descriptors: readonly ToolDescriptorIface[];
|
|
76
|
+
fetch(request: Request): Promise<Response>;
|
|
77
|
+
listTools(): ToolDescriptorIface[];
|
|
78
|
+
refreshTools(): Promise<ToolDescriptorIface[]>;
|
|
79
|
+
updateCatalog(catalog: SandboxMcpCatalogIface): void;
|
|
80
|
+
callTool(name: string, args: JsonObject, signal?: AbortSignal): Promise<CallToolResult>;
|
|
81
|
+
close(): Promise<void>;
|
|
82
|
+
}
|
|
83
|
+
export declare const createSandboxMcpServer: (args: CreateSandboxMcpServerArgsIface) => SandboxMcpServerIface;
|