@moikapy/lich 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -1
- package/README.md +26 -3
- package/dist/{chunk-JC2G3XH2.js → chunk-JYURFAGB.js} +2 -2
- package/dist/{chunk-7HLVKVIG.js → chunk-QVJCIZIF.js} +732 -10
- package/dist/chunk-QVJCIZIF.js.map +1 -0
- package/dist/chunk-SAEB3QL3.js +58 -0
- package/dist/chunk-SAEB3QL3.js.map +1 -0
- package/dist/cli.js +243 -17
- package/dist/cli.js.map +1 -1
- package/dist/{gateway-RJFZJEUZ.js → gateway-5BG3YCZF.js} +2 -2
- package/dist/index.d.ts +260 -154
- package/dist/index.js +5 -3
- package/dist/{tui-LOUJVZ6A.js → tui-L6RABP2J.js} +4 -4
- package/docs/.vitepress/config.mts +4 -0
- package/docs/architecture/overview.md +31 -18
- package/docs/architecture/plugins.md +1 -1
- package/docs/architecture/tools.md +7 -2
- package/docs/getting-started.md +7 -6
- package/docs/index.md +5 -4
- package/docs/user-guide/cli.md +20 -8
- package/docs/user-guide/games.md +1 -1
- package/docs/user-guide/godot.md +3 -1
- package/docs/user-guide/library.md +4 -2
- package/docs/user-guide/plugins.md +2 -2
- package/docs/user-guide/redot.md +93 -0
- package/docs/user-guide/tui.md +2 -2
- package/examples/game_bridge/README.md +2 -0
- package/optional-mcps/godot/manifest.json +6 -0
- package/optional-mcps/redot/manifest.json +18 -0
- package/package.json +2 -1
- package/dist/chunk-6M6OAQGN.js +0 -17
- package/dist/chunk-6M6OAQGN.js.map +0 -1
- package/dist/chunk-7HLVKVIG.js.map +0 -1
- /package/dist/{chunk-JC2G3XH2.js.map → chunk-JYURFAGB.js.map} +0 -0
- /package/dist/{gateway-RJFZJEUZ.js.map → gateway-5BG3YCZF.js.map} +0 -0
- /package/dist/{tui-LOUJVZ6A.js.map → tui-L6RABP2J.js.map} +0 -0
|
@@ -1912,12 +1912,178 @@ function read_platform_token(config, platform) {
|
|
|
1912
1912
|
return value;
|
|
1913
1913
|
}
|
|
1914
1914
|
|
|
1915
|
+
// src/mcp/mcp_pin.ts
|
|
1916
|
+
import path11 from "path";
|
|
1917
|
+
|
|
1918
|
+
// src/mcp/mcp_catalog.ts
|
|
1919
|
+
import { existsSync as existsSync2, readFileSync as readFileSync4, readdirSync as readdirSync3 } from "fs";
|
|
1920
|
+
import path9 from "path";
|
|
1921
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1922
|
+
var cached;
|
|
1923
|
+
function catalog_dir() {
|
|
1924
|
+
let dir = path9.dirname(fileURLToPath2(import.meta.url));
|
|
1925
|
+
for (let hop = 0; hop < 6; hop += 1) {
|
|
1926
|
+
const candidate = path9.join(dir, "optional-mcps");
|
|
1927
|
+
if (existsSync2(path9.join(candidate, "redot", "manifest.json")) === true) {
|
|
1928
|
+
return candidate;
|
|
1929
|
+
}
|
|
1930
|
+
dir = path9.dirname(dir);
|
|
1931
|
+
}
|
|
1932
|
+
throw new Error("mcp catalog not found");
|
|
1933
|
+
}
|
|
1934
|
+
function read_manifest(file) {
|
|
1935
|
+
const parsed = safe_json_parse(readFileSync4(file, "utf8"));
|
|
1936
|
+
if (parsed === void 0 || typeof parsed.name !== "string") {
|
|
1937
|
+
throw new Error("mcp catalog manifest rejected");
|
|
1938
|
+
}
|
|
1939
|
+
return parsed;
|
|
1940
|
+
}
|
|
1941
|
+
function load_catalog() {
|
|
1942
|
+
if (cached !== void 0) {
|
|
1943
|
+
return cached;
|
|
1944
|
+
}
|
|
1945
|
+
const manifests = [];
|
|
1946
|
+
for (const name of readdirSync3(catalog_dir())) {
|
|
1947
|
+
const file = path9.join(catalog_dir(), name, "manifest.json");
|
|
1948
|
+
if (existsSync2(file) === true) {
|
|
1949
|
+
manifests.push(read_manifest(file));
|
|
1950
|
+
}
|
|
1951
|
+
}
|
|
1952
|
+
cached = manifests;
|
|
1953
|
+
return cached;
|
|
1954
|
+
}
|
|
1955
|
+
function catalog_by_name(name) {
|
|
1956
|
+
return load_catalog().find((entry) => entry.name === name);
|
|
1957
|
+
}
|
|
1958
|
+
|
|
1959
|
+
// src/mcp/mcp_refuse.ts
|
|
1960
|
+
import path10 from "path";
|
|
1961
|
+
var SHELL = /[;&|`$<>]/;
|
|
1962
|
+
var DOWNLOADERS = /* @__PURE__ */ new Set(["npx", "npm", "bunx", "uvx", "curl", "wget"]);
|
|
1963
|
+
function refuse_stdio_command(command) {
|
|
1964
|
+
if (command.includes("://") === true) {
|
|
1965
|
+
return "refused url; only a local binary is allowed";
|
|
1966
|
+
}
|
|
1967
|
+
if (SHELL.test(command) === true) {
|
|
1968
|
+
return "refused shell metacharacters in mcp command";
|
|
1969
|
+
}
|
|
1970
|
+
if (command.length === 0 || command.trim() !== command || /\s/.test(command) === true) {
|
|
1971
|
+
return "refused mcp command";
|
|
1972
|
+
}
|
|
1973
|
+
const base = path10.basename(command);
|
|
1974
|
+
if (DOWNLOADERS.has(base) === true) {
|
|
1975
|
+
return `refused download command '${base}'`;
|
|
1976
|
+
}
|
|
1977
|
+
return void 0;
|
|
1978
|
+
}
|
|
1979
|
+
function refuse_stdio_arg(arg) {
|
|
1980
|
+
if (arg.includes("://") === true) {
|
|
1981
|
+
return "refused url in mcp args";
|
|
1982
|
+
}
|
|
1983
|
+
if (SHELL.test(arg) === true) {
|
|
1984
|
+
return "refused shell metacharacters in mcp args";
|
|
1985
|
+
}
|
|
1986
|
+
return void 0;
|
|
1987
|
+
}
|
|
1988
|
+
|
|
1989
|
+
// src/mcp/mcp_url.ts
|
|
1990
|
+
function refuse_http_url(url) {
|
|
1991
|
+
let parsed;
|
|
1992
|
+
try {
|
|
1993
|
+
parsed = new URL(url);
|
|
1994
|
+
} catch {
|
|
1995
|
+
return "refused mcp url";
|
|
1996
|
+
}
|
|
1997
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
1998
|
+
return "refused mcp url";
|
|
1999
|
+
}
|
|
2000
|
+
if (parsed.username.length > 0 || parsed.password.length > 0) {
|
|
2001
|
+
return "refused mcp url credentials";
|
|
2002
|
+
}
|
|
2003
|
+
const host = parsed.hostname.toLowerCase();
|
|
2004
|
+
if (host !== "127.0.0.1" && host !== "localhost") {
|
|
2005
|
+
return "refused mcp url; loopback only";
|
|
2006
|
+
}
|
|
2007
|
+
return void 0;
|
|
2008
|
+
}
|
|
2009
|
+
|
|
2010
|
+
// src/mcp/mcp_pin.ts
|
|
2011
|
+
function refuse_catalog_stdio(name, command, args) {
|
|
2012
|
+
const pin = catalog_by_name(name);
|
|
2013
|
+
if (pin?.command_basename === void 0) {
|
|
2014
|
+
return void 0;
|
|
2015
|
+
}
|
|
2016
|
+
if (path11.basename(command) !== pin.command_basename) {
|
|
2017
|
+
return `refused command basename '${path11.basename(command)}'; only '${pin.command_basename}' is allowed`;
|
|
2018
|
+
}
|
|
2019
|
+
const prefix = pin.args_prefix ?? [];
|
|
2020
|
+
if (args.length !== prefix.length + 1) {
|
|
2021
|
+
return `refused ${name} args; expected ${prefix.join(" ")} <project>`;
|
|
2022
|
+
}
|
|
2023
|
+
for (let index = 0; index < prefix.length; index += 1) {
|
|
2024
|
+
if (args[index] !== prefix[index]) {
|
|
2025
|
+
return `refused ${name} args; expected ${prefix.join(" ")} <project>`;
|
|
2026
|
+
}
|
|
2027
|
+
}
|
|
2028
|
+
const project = args[prefix.length];
|
|
2029
|
+
if (project === void 0 || project.length === 0 || project.includes("://") === true) {
|
|
2030
|
+
return "refused project path; pass a local project directory";
|
|
2031
|
+
}
|
|
2032
|
+
return void 0;
|
|
2033
|
+
}
|
|
2034
|
+
function refuse_arg_list(args) {
|
|
2035
|
+
for (const arg of args) {
|
|
2036
|
+
const refused = refuse_stdio_arg(arg);
|
|
2037
|
+
if (refused !== void 0) {
|
|
2038
|
+
return refused;
|
|
2039
|
+
}
|
|
2040
|
+
}
|
|
2041
|
+
return void 0;
|
|
2042
|
+
}
|
|
2043
|
+
function refuse_mcp_entry(name, entry) {
|
|
2044
|
+
if (typeof entry.url === "string") {
|
|
2045
|
+
return refuse_http_url(entry.url);
|
|
2046
|
+
}
|
|
2047
|
+
if (typeof entry.command !== "string") {
|
|
2048
|
+
return "refused mcp entry";
|
|
2049
|
+
}
|
|
2050
|
+
const args = entry.args ?? [];
|
|
2051
|
+
return refuse_stdio_command(entry.command) ?? refuse_catalog_stdio(name, entry.command, args) ?? refuse_arg_list(args);
|
|
2052
|
+
}
|
|
2053
|
+
|
|
1915
2054
|
// src/agent/config.ts
|
|
1916
2055
|
var gateway_schema = z.object({
|
|
1917
2056
|
platforms: z.array(z.enum(["webhook", "telegram", "discord", "twitch"])).default([]),
|
|
1918
2057
|
/** Env-var names that hold tokens. Never store the secrets themselves. */
|
|
1919
2058
|
token_envs: z.record(z.string(), z.string().regex(ENV_VAR_NAME, "invalid env var name")).default({})
|
|
1920
2059
|
}).optional();
|
|
2060
|
+
var stdio_mcp_schema = z.object({
|
|
2061
|
+
enabled: z.boolean().default(false),
|
|
2062
|
+
command: z.string().min(1),
|
|
2063
|
+
args: z.array(z.string()).default([]),
|
|
2064
|
+
env: z.record(z.string().regex(ENV_VAR_NAME, "invalid env var name"), z.string()).optional()
|
|
2065
|
+
}).strict();
|
|
2066
|
+
var http_mcp_schema = z.object({
|
|
2067
|
+
enabled: z.boolean().default(false),
|
|
2068
|
+
url: z.string().min(1)
|
|
2069
|
+
}).strict();
|
|
2070
|
+
var mcp_server_schema = z.union([stdio_mcp_schema, http_mcp_schema]);
|
|
2071
|
+
var SERVER_NAME = /^[a-z][a-z0-9_]*$/;
|
|
2072
|
+
var mcp_servers_schema = z.record(z.string(), mcp_server_schema).superRefine((servers, ctx) => {
|
|
2073
|
+
for (const [name, entry] of Object.entries(servers)) {
|
|
2074
|
+
if (SERVER_NAME.test(name) === false) {
|
|
2075
|
+
ctx.addIssue({
|
|
2076
|
+
code: z.ZodIssueCode.custom,
|
|
2077
|
+
path: [name],
|
|
2078
|
+
message: "mcp server name must be snake_case"
|
|
2079
|
+
});
|
|
2080
|
+
}
|
|
2081
|
+
const refused = refuse_mcp_entry(name, entry);
|
|
2082
|
+
if (refused !== void 0) {
|
|
2083
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, path: [name], message: refused });
|
|
2084
|
+
}
|
|
2085
|
+
}
|
|
2086
|
+
}).optional();
|
|
1921
2087
|
var provider_schema = z.object({
|
|
1922
2088
|
kind: z.enum(["openai_compat", "anthropic", "ollama"]),
|
|
1923
2089
|
name: z.string().min(1),
|
|
@@ -1947,7 +2113,9 @@ var agent_config_schema = z.object({
|
|
|
1947
2113
|
plugins: z.array(z.string()).default([]),
|
|
1948
2114
|
gateway: gateway_schema,
|
|
1949
2115
|
log_level: z.enum(["debug", "info", "warn", "error"]).default("info"),
|
|
1950
|
-
theme: z.string().min(1).default("lich")
|
|
2116
|
+
theme: z.string().min(1).default("lich"),
|
|
2117
|
+
/** Named MCP servers. Each entry is stdio or loopback http. Default off. */
|
|
2118
|
+
mcp_servers: mcp_servers_schema
|
|
1951
2119
|
}).transform((config) => {
|
|
1952
2120
|
const work_dir = config.work_dir ?? process.cwd();
|
|
1953
2121
|
return {
|
|
@@ -1968,6 +2136,18 @@ function freeze_config(config) {
|
|
|
1968
2136
|
Object.freeze(config.gateway.token_envs);
|
|
1969
2137
|
Object.freeze(config.gateway);
|
|
1970
2138
|
}
|
|
2139
|
+
if (config.mcp_servers !== void 0) {
|
|
2140
|
+
for (const entry of Object.values(config.mcp_servers)) {
|
|
2141
|
+
if ("args" in entry) {
|
|
2142
|
+
Object.freeze(entry.args);
|
|
2143
|
+
}
|
|
2144
|
+
if ("env" in entry && entry.env !== void 0) {
|
|
2145
|
+
Object.freeze(entry.env);
|
|
2146
|
+
}
|
|
2147
|
+
Object.freeze(entry);
|
|
2148
|
+
}
|
|
2149
|
+
Object.freeze(config.mcp_servers);
|
|
2150
|
+
}
|
|
1971
2151
|
return config;
|
|
1972
2152
|
}
|
|
1973
2153
|
function parse_agent_config(raw) {
|
|
@@ -2000,15 +2180,543 @@ var AgentEmitter = class {
|
|
|
2000
2180
|
}
|
|
2001
2181
|
};
|
|
2002
2182
|
|
|
2003
|
-
// src/
|
|
2183
|
+
// src/mcp/mcp_http.ts
|
|
2184
|
+
async function post_rpc(url, fetch_fn, body) {
|
|
2185
|
+
const response = await fetch_fn(url, {
|
|
2186
|
+
method: "POST",
|
|
2187
|
+
redirect: "error",
|
|
2188
|
+
headers: { "content-type": "application/json", accept: "application/json" },
|
|
2189
|
+
body: JSON.stringify(body)
|
|
2190
|
+
});
|
|
2191
|
+
const parsed = await response.json();
|
|
2192
|
+
if (parsed.error !== void 0) {
|
|
2193
|
+
const detail = parsed.error.message;
|
|
2194
|
+
throw new Error(typeof detail === "string" && detail.length > 0 ? detail : "mcp error");
|
|
2195
|
+
}
|
|
2196
|
+
return parsed.result;
|
|
2197
|
+
}
|
|
2198
|
+
function http_pipe(url, fetch_fn) {
|
|
2199
|
+
let next_id = 1;
|
|
2200
|
+
return {
|
|
2201
|
+
request(method, params) {
|
|
2202
|
+
const id = next_id;
|
|
2203
|
+
next_id += 1;
|
|
2204
|
+
return post_rpc(url, fetch_fn, { jsonrpc: "2.0", id, method, params });
|
|
2205
|
+
},
|
|
2206
|
+
notify(method) {
|
|
2207
|
+
void fetch_fn(url, {
|
|
2208
|
+
method: "POST",
|
|
2209
|
+
redirect: "error",
|
|
2210
|
+
headers: { "content-type": "application/json" },
|
|
2211
|
+
body: JSON.stringify({ jsonrpc: "2.0", method })
|
|
2212
|
+
}).catch(() => void 0);
|
|
2213
|
+
},
|
|
2214
|
+
close() {
|
|
2215
|
+
return void 0;
|
|
2216
|
+
}
|
|
2217
|
+
};
|
|
2218
|
+
}
|
|
2219
|
+
|
|
2220
|
+
// src/mcp/mcp_plan.ts
|
|
2221
|
+
import { existsSync as existsSync3 } from "fs";
|
|
2222
|
+
import path12 from "path";
|
|
2223
|
+
function find_on_path(command, env_path) {
|
|
2224
|
+
if (env_path === void 0 || env_path.length === 0) {
|
|
2225
|
+
return void 0;
|
|
2226
|
+
}
|
|
2227
|
+
for (const dir of env_path.split(path12.delimiter)) {
|
|
2228
|
+
if (dir.length === 0) {
|
|
2229
|
+
continue;
|
|
2230
|
+
}
|
|
2231
|
+
const candidate = path12.join(dir, command);
|
|
2232
|
+
if (existsSync3(candidate) === true) {
|
|
2233
|
+
return candidate;
|
|
2234
|
+
}
|
|
2235
|
+
}
|
|
2236
|
+
return void 0;
|
|
2237
|
+
}
|
|
2238
|
+
function locate(command, env_path) {
|
|
2239
|
+
if (command.includes("/") === true || command.includes("\\") === true) {
|
|
2240
|
+
return existsSync3(command) === true ? command : void 0;
|
|
2241
|
+
}
|
|
2242
|
+
return find_on_path(command, env_path);
|
|
2243
|
+
}
|
|
2244
|
+
function plan_stdio(name, command, args, env_path) {
|
|
2245
|
+
const refused = refuse_mcp_entry(name, { command, args });
|
|
2246
|
+
if (refused !== void 0) {
|
|
2247
|
+
return refused;
|
|
2248
|
+
}
|
|
2249
|
+
const binary = locate(command, env_path);
|
|
2250
|
+
if (binary === void 0) {
|
|
2251
|
+
return catalog_by_name(name)?.missing_hint ?? "mcp command not found";
|
|
2252
|
+
}
|
|
2253
|
+
return { command: binary, args };
|
|
2254
|
+
}
|
|
2255
|
+
|
|
2256
|
+
// src/mcp/mcp_pipe.ts
|
|
2257
|
+
var SKIP_LIMIT = 32;
|
|
2258
|
+
function parse_rpc_line(line) {
|
|
2259
|
+
const parsed = safe_json_parse(line);
|
|
2260
|
+
if (typeof parsed !== "object" || parsed === null) {
|
|
2261
|
+
return void 0;
|
|
2262
|
+
}
|
|
2263
|
+
return parsed;
|
|
2264
|
+
}
|
|
2265
|
+
async function read_id(child, id) {
|
|
2266
|
+
for (let skipped = 0; skipped < SKIP_LIMIT; skipped += 1) {
|
|
2267
|
+
const line = await child.read_line();
|
|
2268
|
+
const failure = child.failed();
|
|
2269
|
+
if (failure !== void 0) {
|
|
2270
|
+
throw new Error(failure);
|
|
2271
|
+
}
|
|
2272
|
+
if (line === void 0) {
|
|
2273
|
+
throw new Error("mcp closed the pipe");
|
|
2274
|
+
}
|
|
2275
|
+
const parsed = parse_rpc_line(line);
|
|
2276
|
+
if (parsed === void 0 || parsed.id !== id) {
|
|
2277
|
+
continue;
|
|
2278
|
+
}
|
|
2279
|
+
if (parsed.error !== void 0) {
|
|
2280
|
+
const detail = parsed.error.message;
|
|
2281
|
+
throw new Error(typeof detail === "string" && detail.length > 0 ? detail : "mcp error");
|
|
2282
|
+
}
|
|
2283
|
+
return parsed.result;
|
|
2284
|
+
}
|
|
2285
|
+
throw new Error("mcp sent no matching response");
|
|
2286
|
+
}
|
|
2287
|
+
function stdio_pipe(child) {
|
|
2288
|
+
let next_id = 1;
|
|
2289
|
+
return {
|
|
2290
|
+
request(method, params) {
|
|
2291
|
+
const id = next_id;
|
|
2292
|
+
next_id += 1;
|
|
2293
|
+
child.write_line(JSON.stringify({ jsonrpc: "2.0", id, method, params }));
|
|
2294
|
+
return read_id(child, id);
|
|
2295
|
+
},
|
|
2296
|
+
notify(method) {
|
|
2297
|
+
child.write_line(JSON.stringify({ jsonrpc: "2.0", method }));
|
|
2298
|
+
},
|
|
2299
|
+
close() {
|
|
2300
|
+
child.stop();
|
|
2301
|
+
}
|
|
2302
|
+
};
|
|
2303
|
+
}
|
|
2304
|
+
|
|
2305
|
+
// src/mcp/mcp_names.ts
|
|
2306
|
+
function mcp_tool_name(server, tool) {
|
|
2307
|
+
return `mcp_${sanitize(server)}_${sanitize(tool)}`;
|
|
2308
|
+
}
|
|
2309
|
+
function sanitize(value) {
|
|
2310
|
+
return value.replace(/[^a-zA-Z0-9_]/g, "_");
|
|
2311
|
+
}
|
|
2312
|
+
|
|
2313
|
+
// src/mcp/mcp_register.ts
|
|
2314
|
+
function name_allowed(enabled, name) {
|
|
2315
|
+
if (enabled === "all") {
|
|
2316
|
+
return true;
|
|
2317
|
+
}
|
|
2318
|
+
return enabled.includes(name);
|
|
2319
|
+
}
|
|
2320
|
+
function run_call(session, wire_name, args, context) {
|
|
2321
|
+
return capture_errors(async () => {
|
|
2322
|
+
if (context.signal?.aborted === true) {
|
|
2323
|
+
throw new Error("cancelled");
|
|
2324
|
+
}
|
|
2325
|
+
return { ok: true, output: await session.call_tool(wire_name, args) };
|
|
2326
|
+
});
|
|
2327
|
+
}
|
|
2328
|
+
function tool_for(registered, wire_name, spec, session) {
|
|
2329
|
+
return {
|
|
2330
|
+
name: registered,
|
|
2331
|
+
description: spec.description,
|
|
2332
|
+
parameters: spec.parameters,
|
|
2333
|
+
timeout_ms: 12e4,
|
|
2334
|
+
execute: (args, context) => run_call(session, wire_name, args, context)
|
|
2335
|
+
};
|
|
2336
|
+
}
|
|
2337
|
+
function register_listed(registry, server, listed, enabled, session) {
|
|
2338
|
+
const excluded = new Set(catalog_by_name(server)?.exclude_tools ?? []);
|
|
2339
|
+
for (const spec of listed) {
|
|
2340
|
+
if (excluded.has(spec.name) === true) {
|
|
2341
|
+
continue;
|
|
2342
|
+
}
|
|
2343
|
+
const registered = mcp_tool_name(server, spec.name);
|
|
2344
|
+
if (name_allowed(enabled, registered) === false || registry.has(registered) === true) {
|
|
2345
|
+
continue;
|
|
2346
|
+
}
|
|
2347
|
+
registry.register(tool_for(registered, spec.name, spec, session));
|
|
2348
|
+
}
|
|
2349
|
+
}
|
|
2350
|
+
|
|
2351
|
+
// src/mcp/mcp_handshake.ts
|
|
2352
|
+
var PROTOCOL_VERSION = "2024-11-05";
|
|
2353
|
+
function init_params() {
|
|
2354
|
+
return {
|
|
2355
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
2356
|
+
capabilities: {},
|
|
2357
|
+
clientInfo: { name: "lich", version: "1" }
|
|
2358
|
+
};
|
|
2359
|
+
}
|
|
2360
|
+
function assert_handshake(result) {
|
|
2361
|
+
if (typeof result !== "object" || result === null) {
|
|
2362
|
+
throw new Error("mcp handshake rejected");
|
|
2363
|
+
}
|
|
2364
|
+
const body = result;
|
|
2365
|
+
if (typeof body.protocolVersion !== "string" || body.protocolVersion.length === 0) {
|
|
2366
|
+
throw new Error("mcp handshake rejected");
|
|
2367
|
+
}
|
|
2368
|
+
if (typeof body.serverInfo?.name !== "string" || body.serverInfo.name.length === 0) {
|
|
2369
|
+
throw new Error("mcp handshake rejected");
|
|
2370
|
+
}
|
|
2371
|
+
}
|
|
2372
|
+
|
|
2373
|
+
// src/mcp/mcp_content.ts
|
|
2374
|
+
function content_text(result) {
|
|
2375
|
+
if (typeof result !== "object" || result === null) {
|
|
2376
|
+
return "";
|
|
2377
|
+
}
|
|
2378
|
+
const body = result;
|
|
2379
|
+
const parts = [];
|
|
2380
|
+
if (Array.isArray(body.content) === true) {
|
|
2381
|
+
for (const item of body.content) {
|
|
2382
|
+
if (typeof item !== "object" || item === null) {
|
|
2383
|
+
continue;
|
|
2384
|
+
}
|
|
2385
|
+
const chunk = item;
|
|
2386
|
+
if (chunk.type === "text" && typeof chunk.text === "string") {
|
|
2387
|
+
parts.push(chunk.text);
|
|
2388
|
+
}
|
|
2389
|
+
if (chunk.type === "image") {
|
|
2390
|
+
parts.push("[image omitted]");
|
|
2391
|
+
}
|
|
2392
|
+
}
|
|
2393
|
+
}
|
|
2394
|
+
const text = parts.join("\n");
|
|
2395
|
+
if (body.isError === true) {
|
|
2396
|
+
throw new Error(text.length > 0 ? text : "mcp tool failed");
|
|
2397
|
+
}
|
|
2398
|
+
return text;
|
|
2399
|
+
}
|
|
2400
|
+
|
|
2401
|
+
// src/mcp/mcp_result.ts
|
|
2402
|
+
function tool_schema(raw) {
|
|
2403
|
+
if (typeof raw !== "object" || raw === null) {
|
|
2404
|
+
return { type: "object" };
|
|
2405
|
+
}
|
|
2406
|
+
const body = raw;
|
|
2407
|
+
const schema = { type: "object" };
|
|
2408
|
+
if (typeof body.properties === "object" && body.properties !== null) {
|
|
2409
|
+
schema.properties = body.properties;
|
|
2410
|
+
}
|
|
2411
|
+
if (Array.isArray(body.required) === true) {
|
|
2412
|
+
schema.required = body.required.filter((item) => typeof item === "string");
|
|
2413
|
+
}
|
|
2414
|
+
if (typeof body.additionalProperties === "boolean") {
|
|
2415
|
+
schema.additionalProperties = body.additionalProperties;
|
|
2416
|
+
}
|
|
2417
|
+
return schema;
|
|
2418
|
+
}
|
|
2419
|
+
function parse_tools(result) {
|
|
2420
|
+
if (typeof result !== "object" || result === null || Array.isArray(result.tools) === false) {
|
|
2421
|
+
throw new Error("mcp tools/list rejected");
|
|
2422
|
+
}
|
|
2423
|
+
const tools = [];
|
|
2424
|
+
for (const item of result.tools) {
|
|
2425
|
+
if (typeof item !== "object" || item === null) {
|
|
2426
|
+
continue;
|
|
2427
|
+
}
|
|
2428
|
+
const tool = item;
|
|
2429
|
+
if (typeof tool.name !== "string" || tool.name.length === 0) {
|
|
2430
|
+
continue;
|
|
2431
|
+
}
|
|
2432
|
+
tools.push({
|
|
2433
|
+
name: tool.name,
|
|
2434
|
+
description: typeof tool.description === "string" ? tool.description : tool.name,
|
|
2435
|
+
parameters: tool_schema(tool.inputSchema)
|
|
2436
|
+
});
|
|
2437
|
+
}
|
|
2438
|
+
return tools;
|
|
2439
|
+
}
|
|
2440
|
+
|
|
2441
|
+
// src/mcp/mcp_session.ts
|
|
2442
|
+
var McpSession = class {
|
|
2443
|
+
constructor(pipe) {
|
|
2444
|
+
this.pipe = pipe;
|
|
2445
|
+
}
|
|
2446
|
+
pipe;
|
|
2447
|
+
ready_done = false;
|
|
2448
|
+
closed = false;
|
|
2449
|
+
close() {
|
|
2450
|
+
if (this.closed === true) {
|
|
2451
|
+
return;
|
|
2452
|
+
}
|
|
2453
|
+
this.closed = true;
|
|
2454
|
+
this.pipe.close();
|
|
2455
|
+
}
|
|
2456
|
+
async list_tools() {
|
|
2457
|
+
await this.ensure_ready();
|
|
2458
|
+
return parse_tools(await this.pipe.request("tools/list", {}));
|
|
2459
|
+
}
|
|
2460
|
+
async call_tool(name, args) {
|
|
2461
|
+
await this.ensure_ready();
|
|
2462
|
+
return content_text(await this.pipe.request("tools/call", { name, arguments: args }));
|
|
2463
|
+
}
|
|
2464
|
+
async ensure_ready() {
|
|
2465
|
+
if (this.ready_done === true) {
|
|
2466
|
+
return;
|
|
2467
|
+
}
|
|
2468
|
+
try {
|
|
2469
|
+
assert_handshake(await this.pipe.request("initialize", init_params()));
|
|
2470
|
+
this.pipe.notify("notifications/initialized");
|
|
2471
|
+
this.ready_done = true;
|
|
2472
|
+
} catch (error) {
|
|
2473
|
+
this.close();
|
|
2474
|
+
throw error;
|
|
2475
|
+
}
|
|
2476
|
+
}
|
|
2477
|
+
};
|
|
2478
|
+
|
|
2479
|
+
// src/mcp/mcp_lines.ts
|
|
2480
|
+
function create_line_queue() {
|
|
2481
|
+
const pending = [];
|
|
2482
|
+
const waiters = [];
|
|
2483
|
+
let closed = false;
|
|
2484
|
+
return {
|
|
2485
|
+
push(line) {
|
|
2486
|
+
const waiter = waiters.shift();
|
|
2487
|
+
if (waiter !== void 0) {
|
|
2488
|
+
waiter(line);
|
|
2489
|
+
return;
|
|
2490
|
+
}
|
|
2491
|
+
pending.push(line);
|
|
2492
|
+
},
|
|
2493
|
+
close() {
|
|
2494
|
+
closed = true;
|
|
2495
|
+
for (const waiter of waiters.splice(0)) {
|
|
2496
|
+
waiter(void 0);
|
|
2497
|
+
}
|
|
2498
|
+
},
|
|
2499
|
+
read() {
|
|
2500
|
+
const next = pending.shift();
|
|
2501
|
+
if (next !== void 0) {
|
|
2502
|
+
return Promise.resolve(next);
|
|
2503
|
+
}
|
|
2504
|
+
if (closed === true) {
|
|
2505
|
+
return Promise.resolve(void 0);
|
|
2506
|
+
}
|
|
2507
|
+
return new Promise((resolve) => {
|
|
2508
|
+
waiters.push(resolve);
|
|
2509
|
+
});
|
|
2510
|
+
}
|
|
2511
|
+
};
|
|
2512
|
+
}
|
|
2513
|
+
|
|
2514
|
+
// src/mcp/mcp_child.ts
|
|
2515
|
+
function spawn_failure(code) {
|
|
2516
|
+
if (code === "ENOENT") {
|
|
2517
|
+
return "mcp command not found";
|
|
2518
|
+
}
|
|
2519
|
+
return "mcp spawn failed";
|
|
2520
|
+
}
|
|
2521
|
+
function failed_child(message) {
|
|
2522
|
+
return {
|
|
2523
|
+
write_line() {
|
|
2524
|
+
return void 0;
|
|
2525
|
+
},
|
|
2526
|
+
read_line() {
|
|
2527
|
+
return Promise.resolve(void 0);
|
|
2528
|
+
},
|
|
2529
|
+
stop() {
|
|
2530
|
+
return void 0;
|
|
2531
|
+
},
|
|
2532
|
+
failed() {
|
|
2533
|
+
return message;
|
|
2534
|
+
}
|
|
2535
|
+
};
|
|
2536
|
+
}
|
|
2537
|
+
|
|
2538
|
+
// src/mcp/mcp_stdio_bun.ts
|
|
2539
|
+
async function pump_stdout(stream, queue) {
|
|
2540
|
+
const reader = stream.getReader();
|
|
2541
|
+
const decoder = new TextDecoder();
|
|
2542
|
+
let buffer = "";
|
|
2543
|
+
for (; ; ) {
|
|
2544
|
+
const next = await reader.read();
|
|
2545
|
+
if (next.done === true) {
|
|
2546
|
+
if (buffer.length > 0) {
|
|
2547
|
+
queue.push(buffer);
|
|
2548
|
+
}
|
|
2549
|
+
queue.close();
|
|
2550
|
+
return;
|
|
2551
|
+
}
|
|
2552
|
+
buffer += decoder.decode(next.value, { stream: true });
|
|
2553
|
+
const parts = buffer.split("\n");
|
|
2554
|
+
buffer = parts.pop() ?? "";
|
|
2555
|
+
for (const part of parts) {
|
|
2556
|
+
queue.push(part);
|
|
2557
|
+
}
|
|
2558
|
+
}
|
|
2559
|
+
}
|
|
2560
|
+
async function drain_stderr(stream) {
|
|
2561
|
+
const reader = stream.getReader();
|
|
2562
|
+
while ((await reader.read()).done === false) {
|
|
2563
|
+
continue;
|
|
2564
|
+
}
|
|
2565
|
+
}
|
|
2566
|
+
function bun_line_child(command, args, env) {
|
|
2567
|
+
const queue = create_line_queue();
|
|
2568
|
+
try {
|
|
2569
|
+
const options = { stdin: "pipe", stdout: "pipe", stderr: "pipe" };
|
|
2570
|
+
const child = env === void 0 ? Bun.spawn([command, ...args], options) : Bun.spawn([command, ...args], { ...options, env });
|
|
2571
|
+
void pump_stdout(child.stdout, queue);
|
|
2572
|
+
void drain_stderr(child.stderr);
|
|
2573
|
+
return {
|
|
2574
|
+
write_line(line) {
|
|
2575
|
+
child.stdin.write(`${line}
|
|
2576
|
+
`);
|
|
2577
|
+
void child.stdin.flush();
|
|
2578
|
+
},
|
|
2579
|
+
read_line() {
|
|
2580
|
+
return queue.read();
|
|
2581
|
+
},
|
|
2582
|
+
stop() {
|
|
2583
|
+
child.kill();
|
|
2584
|
+
},
|
|
2585
|
+
failed() {
|
|
2586
|
+
return void 0;
|
|
2587
|
+
}
|
|
2588
|
+
};
|
|
2589
|
+
} catch (error) {
|
|
2590
|
+
const coded = error;
|
|
2591
|
+
return failed_child(spawn_failure(coded.code));
|
|
2592
|
+
}
|
|
2593
|
+
}
|
|
2594
|
+
|
|
2595
|
+
// src/mcp/mcp_stdio_node.ts
|
|
2004
2596
|
import { spawn as spawn3 } from "child_process";
|
|
2597
|
+
import { createInterface } from "readline";
|
|
2598
|
+
function child_env(extra) {
|
|
2599
|
+
if (extra === void 0) {
|
|
2600
|
+
return void 0;
|
|
2601
|
+
}
|
|
2602
|
+
const merged = {};
|
|
2603
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
2604
|
+
if (typeof value === "string") {
|
|
2605
|
+
merged[key] = value;
|
|
2606
|
+
}
|
|
2607
|
+
}
|
|
2608
|
+
for (const [key, value] of Object.entries(extra)) {
|
|
2609
|
+
merged[key] = value;
|
|
2610
|
+
}
|
|
2611
|
+
return merged;
|
|
2612
|
+
}
|
|
2613
|
+
function node_line_child(command, args, env) {
|
|
2614
|
+
const queue = create_line_queue();
|
|
2615
|
+
let failure;
|
|
2616
|
+
const child = spawn3(command, [...args], { stdio: ["pipe", "pipe", "pipe"], env: child_env(env) });
|
|
2617
|
+
const reader = createInterface({ input: child.stdout });
|
|
2618
|
+
reader.on("line", (line) => {
|
|
2619
|
+
queue.push(line);
|
|
2620
|
+
});
|
|
2621
|
+
reader.on("close", () => {
|
|
2622
|
+
queue.close();
|
|
2623
|
+
});
|
|
2624
|
+
child.stderr?.resume();
|
|
2625
|
+
child.on("error", (error) => {
|
|
2626
|
+
failure = spawn_failure(error.code);
|
|
2627
|
+
queue.close();
|
|
2628
|
+
});
|
|
2629
|
+
return {
|
|
2630
|
+
write_line(line) {
|
|
2631
|
+
child.stdin?.write(`${line}
|
|
2632
|
+
`);
|
|
2633
|
+
},
|
|
2634
|
+
read_line() {
|
|
2635
|
+
return queue.read();
|
|
2636
|
+
},
|
|
2637
|
+
stop() {
|
|
2638
|
+
child.kill();
|
|
2639
|
+
},
|
|
2640
|
+
failed() {
|
|
2641
|
+
return failure;
|
|
2642
|
+
}
|
|
2643
|
+
};
|
|
2644
|
+
}
|
|
2645
|
+
|
|
2646
|
+
// src/mcp/mcp_stdio.ts
|
|
2647
|
+
function running_under_bun() {
|
|
2648
|
+
return process.versions.bun !== void 0;
|
|
2649
|
+
}
|
|
2650
|
+
function default_line_spawner(command, args, env) {
|
|
2651
|
+
if (running_under_bun() === true) {
|
|
2652
|
+
return bun_line_child(command, args, env);
|
|
2653
|
+
}
|
|
2654
|
+
return node_line_child(command, args, env);
|
|
2655
|
+
}
|
|
2656
|
+
|
|
2657
|
+
// src/mcp/mcp_attach.ts
|
|
2658
|
+
async function open_and_register(registry, name, enabled, session) {
|
|
2659
|
+
try {
|
|
2660
|
+
register_listed(registry, name, await session.list_tools(), enabled, session);
|
|
2661
|
+
} catch (error) {
|
|
2662
|
+
session.close();
|
|
2663
|
+
const message = error instanceof Error ? error.message : "mcp skipped";
|
|
2664
|
+
logger.warn(`mcp ${name} skipped: ${message}`);
|
|
2665
|
+
}
|
|
2666
|
+
}
|
|
2667
|
+
async function attach_stdio(registry, name, entry, config, runtime) {
|
|
2668
|
+
const planned = plan_stdio(name, entry.command, entry.args, runtime?.env_path ?? process.env.PATH);
|
|
2669
|
+
if (typeof planned === "string") {
|
|
2670
|
+
logger.warn(`mcp ${name} skipped: ${planned}`);
|
|
2671
|
+
return;
|
|
2672
|
+
}
|
|
2673
|
+
const spawn5 = runtime?.spawn ?? default_line_spawner;
|
|
2674
|
+
const session = new McpSession(stdio_pipe(spawn5(planned.command, planned.args, entry.env)));
|
|
2675
|
+
await open_and_register(registry, name, config.tools_enabled, session);
|
|
2676
|
+
}
|
|
2677
|
+
async function attach_http(registry, name, url, config, runtime) {
|
|
2678
|
+
const session = new McpSession(http_pipe(url, runtime?.fetch_fn ?? fetch));
|
|
2679
|
+
await open_and_register(registry, name, config.tools_enabled, session);
|
|
2680
|
+
}
|
|
2681
|
+
|
|
2682
|
+
// src/mcp/mcp_tools.ts
|
|
2683
|
+
function allowlist_wants_mcp(enabled) {
|
|
2684
|
+
if (enabled === "all") {
|
|
2685
|
+
return true;
|
|
2686
|
+
}
|
|
2687
|
+
return enabled.some((name) => name.startsWith("mcp_") === true);
|
|
2688
|
+
}
|
|
2689
|
+
async function attach_enabled_mcp_tools(registry, config, runtime) {
|
|
2690
|
+
const servers = config.mcp_servers;
|
|
2691
|
+
if (servers === void 0 || allowlist_wants_mcp(config.tools_enabled) === false) {
|
|
2692
|
+
return;
|
|
2693
|
+
}
|
|
2694
|
+
for (const [name, entry] of Object.entries(servers)) {
|
|
2695
|
+
if (entry.enabled !== true) {
|
|
2696
|
+
continue;
|
|
2697
|
+
}
|
|
2698
|
+
try {
|
|
2699
|
+
if ("url" in entry) {
|
|
2700
|
+
await attach_http(registry, name, entry.url, config, runtime);
|
|
2701
|
+
continue;
|
|
2702
|
+
}
|
|
2703
|
+
await attach_stdio(registry, name, entry, config, runtime);
|
|
2704
|
+
} catch (error) {
|
|
2705
|
+
const message = error instanceof Error ? error.message : "mcp skipped";
|
|
2706
|
+
logger.warn(`mcp ${name} skipped: ${message}`);
|
|
2707
|
+
}
|
|
2708
|
+
}
|
|
2709
|
+
}
|
|
2710
|
+
|
|
2711
|
+
// src/plugins/builtin/gatekeeper.plugin.ts
|
|
2712
|
+
import { spawn as spawn4 } from "child_process";
|
|
2005
2713
|
import { statSync as statSync2 } from "fs";
|
|
2006
|
-
import
|
|
2714
|
+
import path13 from "path";
|
|
2007
2715
|
var HOOKS_OFF = ["-c", "core.hooksPath=/dev/null"];
|
|
2008
2716
|
var PATHSPEC_MAGIC = /[:*?[]/;
|
|
2009
2717
|
var SECRET_BASENAMES = [".env", ".env.local", "id_rsa"];
|
|
2010
2718
|
function is_secret_path(file_path) {
|
|
2011
|
-
const base =
|
|
2719
|
+
const base = path13.basename(file_path);
|
|
2012
2720
|
if (SECRET_BASENAMES.includes(base) === true) {
|
|
2013
2721
|
return true;
|
|
2014
2722
|
}
|
|
@@ -2035,7 +2743,7 @@ function matches_git_denylist(command) {
|
|
|
2035
2743
|
}
|
|
2036
2744
|
function run_git(args, work_dir, signal) {
|
|
2037
2745
|
return new Promise((resolve) => {
|
|
2038
|
-
const child =
|
|
2746
|
+
const child = spawn4("git", args, { cwd: work_dir, env: process.env });
|
|
2039
2747
|
let settled = false;
|
|
2040
2748
|
let out = "";
|
|
2041
2749
|
const on_abort = () => {
|
|
@@ -2086,7 +2794,7 @@ function invalid_commit_path(raw, work_dir) {
|
|
|
2086
2794
|
return `invalid_path: ${raw}`;
|
|
2087
2795
|
}
|
|
2088
2796
|
const resolved = resolve_safe_path(work_dir, raw);
|
|
2089
|
-
if (resolved ===
|
|
2797
|
+
if (resolved === path13.resolve(work_dir)) {
|
|
2090
2798
|
return `invalid_path: ${raw} resolves to work_dir`;
|
|
2091
2799
|
}
|
|
2092
2800
|
if (is_secret_path(raw) === true) {
|
|
@@ -3388,7 +4096,7 @@ function to_provider_error(error, fallback_name) {
|
|
|
3388
4096
|
|
|
3389
4097
|
// src/session/store.ts
|
|
3390
4098
|
import { appendFile, mkdir as mkdir2, readFile as readFile4 } from "fs/promises";
|
|
3391
|
-
import
|
|
4099
|
+
import path14 from "path";
|
|
3392
4100
|
var counter_state = { value: 0 };
|
|
3393
4101
|
function slugify_label(label) {
|
|
3394
4102
|
const slug = label.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40);
|
|
@@ -3399,7 +4107,7 @@ async function open_session(dir, label) {
|
|
|
3399
4107
|
counter_state.value += 1;
|
|
3400
4108
|
const label_part = label === void 0 ? "" : slugify_label(label);
|
|
3401
4109
|
const id = `${Date.now().toString(36)}-${counter_state.value}${label_part}`;
|
|
3402
|
-
const file_path =
|
|
4110
|
+
const file_path = path14.join(dir, `${id}.jsonl`);
|
|
3403
4111
|
return {
|
|
3404
4112
|
id,
|
|
3405
4113
|
path: file_path,
|
|
@@ -3680,8 +4388,11 @@ var Agent = class {
|
|
|
3680
4388
|
registry;
|
|
3681
4389
|
executor;
|
|
3682
4390
|
hook_runner;
|
|
3683
|
-
|
|
4391
|
+
mcp_runtime;
|
|
4392
|
+
mcp_attached = false;
|
|
4393
|
+
constructor(config, plugins = [], runtime) {
|
|
3684
4394
|
this.config = config;
|
|
4395
|
+
this.mcp_runtime = runtime?.mcp;
|
|
3685
4396
|
this.events = new AgentEmitter();
|
|
3686
4397
|
this.router = new ProviderRouter(config.providers);
|
|
3687
4398
|
const base_registry = new ToolRegistry();
|
|
@@ -3705,6 +4416,7 @@ var Agent = class {
|
|
|
3705
4416
|
}
|
|
3706
4417
|
}
|
|
3707
4418
|
async run(options) {
|
|
4419
|
+
await this.attach_mcp_once();
|
|
3708
4420
|
const usage_total = { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 };
|
|
3709
4421
|
const stop_collecting = this.events.on(collect_usage(usage_total));
|
|
3710
4422
|
await this.call_plugin_run_start(options.input);
|
|
@@ -3732,6 +4444,14 @@ var Agent = class {
|
|
|
3732
4444
|
const full_messages = [...options.history ?? [], ...outcome.messages];
|
|
3733
4445
|
return { outcome, messages: full_messages, usage_total, session_path };
|
|
3734
4446
|
}
|
|
4447
|
+
/** tools/list once, before the model sees definitions. Empty allowlists never connect. */
|
|
4448
|
+
async attach_mcp_once() {
|
|
4449
|
+
if (this.mcp_attached === true) {
|
|
4450
|
+
return;
|
|
4451
|
+
}
|
|
4452
|
+
this.mcp_attached = true;
|
|
4453
|
+
await attach_enabled_mcp_tools(this.registry, this.config, this.mcp_runtime);
|
|
4454
|
+
}
|
|
3735
4455
|
/** Per-run deps: the built-once ToolContext threads through every tool execution. */
|
|
3736
4456
|
loop_deps(tool_context) {
|
|
3737
4457
|
return {
|
|
@@ -3801,6 +4521,8 @@ export {
|
|
|
3801
4521
|
truncate_text,
|
|
3802
4522
|
logger,
|
|
3803
4523
|
register_builtin_tools,
|
|
4524
|
+
catalog_by_name,
|
|
4525
|
+
refuse_mcp_entry,
|
|
3804
4526
|
ToolRegistry,
|
|
3805
4527
|
ToolExecutor,
|
|
3806
4528
|
HookedToolRunner,
|
|
@@ -3819,4 +4541,4 @@ export {
|
|
|
3819
4541
|
create_agent_with_plugins,
|
|
3820
4542
|
run_agent
|
|
3821
4543
|
};
|
|
3822
|
-
//# sourceMappingURL=chunk-
|
|
4544
|
+
//# sourceMappingURL=chunk-QVJCIZIF.js.map
|