-
Notifications
You must be signed in to change notification settings - Fork 234
Expand file tree
/
Copy pathmcp-script-worker.mjs
More file actions
128 lines (117 loc) · 4.6 KB
/
Copy pathmcp-script-worker.mjs
File metadata and controls
128 lines (117 loc) · 4.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
import { parentPort, workerData } from "node:worker_threads";
import { formatWithOptions } from "node:util";
import vm from "node:vm";
const TOOLS_ENUMERATION_ERROR = "tools is not enumerable — use tools.search({ query })";
const RESERVED_TOOL_PROPS = new Set(["then", "catch", "finally", "toJSON", "toString", "valueOf"]);
// Keep this formatting logic in sync with mcp-code.ts; the standalone worker cannot import the TypeScript host module.
function needsInspectableFormatting(value, stack = new WeakSet()) {
if (value === undefined || typeof value === "bigint" || typeof value === "function" || typeof value === "symbol") return true;
if (typeof value !== "object" || value === null) return false;
if (stack.has(value)) return true;
if (value instanceof Map || value instanceof Set || value instanceof WeakMap || value instanceof WeakSet) return true;
stack.add(value);
try {
return Object.values(value).some((entry) => needsInspectableFormatting(entry, stack));
} finally {
stack.delete(value);
}
}
function formatValue(value) {
if (typeof value === "string") return value;
try {
if (!needsInspectableFormatting(value)) {
const json = JSON.stringify(value, null, 2);
if (json !== undefined) return json;
}
return formatWithOptions({ colors: false, depth: 6 }, value);
} catch {
return "[unserializable value]";
}
}
function toContentBlock(value) {
if (typeof value === "object" && value !== null) {
if (value.type === "text" && typeof value.text === "string") {
return { type: "text", text: value.text };
}
if (value.type === "image" && typeof value.data === "string" && typeof value.mimeType === "string") {
return { type: "image", data: value.data, mimeType: value.mimeType };
}
}
return { type: "text", text: formatValue(value) };
}
let nextRequestId = 0;
const pending = new Map();
parentPort.on("message", (message) => {
if (message?.type !== "result" || typeof message.id !== "number") return;
const resolve = pending.get(message.id);
if (!resolve) return;
pending.delete(message.id);
resolve(message.envelope);
});
function request(type, payload) {
return new Promise((resolve) => {
const id = ++nextRequestId;
pending.set(id, resolve);
parentPort.postMessage({ type, id, ...payload });
});
}
const tools = new Proxy(Object.create(null), {
get(_target, property) {
if (property === "search") {
return async (input) => request("search", { input });
}
if (property === "call") {
return async (path, args) => {
// Invalid paths never reach dispatch and therefore never appear in the call trace.
if (typeof path !== "string" || path.trim() === "") {
return {
ok: false,
error: {
code: "invalid_tool_path",
message: "tools.call(path, args) requires a non-empty tool path.",
},
};
}
return request("call", { path, args });
};
}
if (property === "describe") {
return async (input) => request("describe", { input });
}
if (typeof property !== "string" || RESERVED_TOOL_PROPS.has(property)) return undefined;
return (args) => request("call", { path: property, args });
},
ownKeys() {
throw new Error(TOOLS_ENUMERATION_ERROR);
},
});
const emit = (value) => {
parentPort.postMessage({ type: "emit", block: toContentBlock(value) });
};
const capturedConsole = Object.freeze({
log: (...args) => emit(`[console.log] ${formatWithOptions({ colors: false, depth: 4 }, ...args)}`),
info: (...args) => emit(`[console.info] ${formatWithOptions({ colors: false, depth: 4 }, ...args)}`),
warn: (...args) => emit(`[console.warn] ${formatWithOptions({ colors: false, depth: 4 }, ...args)}`),
error: (...args) => emit(`[console.error] ${formatWithOptions({ colors: false, depth: 4 }, ...args)}`),
debug: (...args) => emit(`[console.debug] ${formatWithOptions({ colors: false, depth: 4 }, ...args)}`),
});
try {
const context = vm.createContext(Object.assign(Object.create(null), {
tools,
emit,
console: capturedConsole,
}), {
codeGeneration: { strings: false, wasm: false },
name: "mcp_script",
});
const script = new vm.Script(`(async () => {\n${workerData.code}\n})()`, { filename: "mcp_script.js" });
const returnValue = await Promise.resolve(script.runInContext(context));
parentPort.postMessage(returnValue === undefined
? { type: "done" }
: { type: "done", returnBlock: toContentBlock(returnValue) });
} catch (error) {
parentPort.postMessage({
type: "error",
message: error instanceof Error ? error.message : String(error),
});
}