@edukors/ai-cli 0.0.0-stage → 0.1.1
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/LICENSE +9 -0
- package/README.md +162 -2
- package/dist/cli/main.d.ts +2 -0
- package/dist/cli/main.js +256 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/constants.d.ts +2 -0
- package/dist/constants.js +3 -0
- package/dist/constants.js.map +1 -0
- package/dist/diagnostics.d.ts +28 -0
- package/dist/diagnostics.js +35 -0
- package/dist/diagnostics.js.map +1 -0
- package/dist/engine.d.ts +157 -0
- package/dist/engine.js +395 -0
- package/dist/engine.js.map +1 -0
- package/dist/errors.d.ts +27 -0
- package/dist/errors.js +59 -0
- package/dist/errors.js.map +1 -0
- package/dist/events.d.ts +95 -0
- package/dist/events.js +8 -0
- package/dist/events.js.map +1 -0
- package/dist/host/main.d.ts +2 -0
- package/dist/host/main.js +30 -0
- package/dist/host/main.js.map +1 -0
- package/dist/host/stdio.d.ts +34 -0
- package/dist/host/stdio.js +254 -0
- package/dist/host/stdio.js.map +1 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/config.d.ts +19 -0
- package/dist/mcp/config.js +63 -0
- package/dist/mcp/config.js.map +1 -0
- package/dist/mcp/external.d.ts +55 -0
- package/dist/mcp/external.js +129 -0
- package/dist/mcp/external.js.map +1 -0
- package/dist/mcp/toolServer.d.ts +30 -0
- package/dist/mcp/toolServer.js +102 -0
- package/dist/mcp/toolServer.js.map +1 -0
- package/dist/platform/discovery.d.ts +23 -0
- package/dist/platform/discovery.js +64 -0
- package/dist/platform/discovery.js.map +1 -0
- package/dist/platform/env.d.ts +12 -0
- package/dist/platform/env.js +104 -0
- package/dist/platform/env.js.map +1 -0
- package/dist/platform/login.d.ts +16 -0
- package/dist/platform/login.js +42 -0
- package/dist/platform/login.js.map +1 -0
- package/dist/platform/paths.d.ts +19 -0
- package/dist/platform/paths.js +46 -0
- package/dist/platform/paths.js.map +1 -0
- package/dist/platform/process.d.ts +55 -0
- package/dist/platform/process.js +186 -0
- package/dist/platform/process.js.map +1 -0
- package/dist/provider.d.ts +156 -0
- package/dist/provider.js +2 -0
- package/dist/provider.js.map +1 -0
- package/dist/providers/claude/adapter.d.ts +43 -0
- package/dist/providers/claude/adapter.js +398 -0
- package/dist/providers/claude/adapter.js.map +1 -0
- package/dist/providers/claude/events.d.ts +36 -0
- package/dist/providers/claude/events.js +90 -0
- package/dist/providers/claude/events.js.map +1 -0
- package/dist/providers/codex/adapter.d.ts +28 -0
- package/dist/providers/codex/adapter.js +497 -0
- package/dist/providers/codex/adapter.js.map +1 -0
- package/dist/providers/codex/rpc.d.ts +17 -0
- package/dist/providers/codex/rpc.js +75 -0
- package/dist/providers/codex/rpc.js.map +1 -0
- package/dist/providers.d.ts +9 -0
- package/dist/providers.js +15 -0
- package/dist/providers.js.map +1 -0
- package/dist/redact.d.ts +7 -0
- package/dist/redact.js +48 -0
- package/dist/redact.js.map +1 -0
- package/dist/registry/lock.d.ts +18 -0
- package/dist/registry/lock.js +83 -0
- package/dist/registry/lock.js.map +1 -0
- package/dist/registry/registry.d.ts +38 -0
- package/dist/registry/registry.js +119 -0
- package/dist/registry/registry.js.map +1 -0
- package/dist/session.d.ts +126 -0
- package/dist/session.js +333 -0
- package/dist/session.js.map +1 -0
- package/dist/settings.d.ts +8 -0
- package/dist/settings.js +7 -0
- package/dist/settings.js.map +1 -0
- package/dist/skills.d.ts +24 -0
- package/dist/skills.js +91 -0
- package/dist/skills.js.map +1 -0
- package/dist/testing/fakeProvider.d.ts +22 -0
- package/dist/testing/fakeProvider.js +266 -0
- package/dist/testing/fakeProvider.js.map +1 -0
- package/dist/testing/index.d.ts +2 -0
- package/dist/testing/index.js +2 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/tools.d.ts +45 -0
- package/dist/tools.js +32 -0
- package/dist/tools.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.js +3 -0
- package/dist/version.js.map +1 -0
- package/fixtures/fake-ai.mjs +230 -0
- package/fixtures/fake-claude.mjs +62 -0
- package/fixtures/fake-codex.mjs +281 -0
- package/fixtures/fake-mcp-server.mjs +14 -0
- package/package.json +74 -4
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { TOOL_SERVER_NAME } from "../constants.js";
|
|
2
|
+
const NAME = /^[A-Za-z0-9_-]{1,64}$/;
|
|
3
|
+
const isObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
4
|
+
const isStrings = (v) => Array.isArray(v) && v.every((x) => typeof x === "string");
|
|
5
|
+
const isStringMap = (v) => isObject(v) && Object.values(v).every((x) => typeof x === "string");
|
|
6
|
+
function server(name, raw) {
|
|
7
|
+
if (name === TOOL_SERVER_NAME)
|
|
8
|
+
return { code: "reserved", server: name };
|
|
9
|
+
// Letters, digits, `_` and `-`: the name becomes part of tool names and of Codex's config keys.
|
|
10
|
+
if (!NAME.test(name))
|
|
11
|
+
return { code: "badName", server: name };
|
|
12
|
+
if (!isObject(raw))
|
|
13
|
+
return { code: "notObject", server: name };
|
|
14
|
+
if (raw.timeout !== undefined && !(typeof raw.timeout === "number" && raw.timeout > 0))
|
|
15
|
+
return { code: "badField", server: name, field: "timeout" };
|
|
16
|
+
const timeout = typeof raw.timeout === "number" ? { timeout: raw.timeout } : {};
|
|
17
|
+
const type = raw.type ?? "stdio";
|
|
18
|
+
if (type === "stdio") {
|
|
19
|
+
if (typeof raw.command !== "string" || !raw.command.trim())
|
|
20
|
+
return { code: "noCommand", server: name };
|
|
21
|
+
if (raw.args !== undefined && !isStrings(raw.args))
|
|
22
|
+
return { code: "badField", server: name, field: "args" };
|
|
23
|
+
if (raw.env !== undefined && !isStringMap(raw.env))
|
|
24
|
+
return { code: "badField", server: name, field: "env" };
|
|
25
|
+
return { type: "stdio", command: raw.command, ...(raw.args ? { args: raw.args } : {}), ...(raw.env ? { env: raw.env } : {}), ...timeout };
|
|
26
|
+
}
|
|
27
|
+
if (type === "http" || type === "sse") {
|
|
28
|
+
if (typeof raw.url !== "string" || !/^https?:\/\/\S+$/.test(raw.url))
|
|
29
|
+
return { code: "noUrl", server: name };
|
|
30
|
+
if (raw.headers !== undefined && !isStringMap(raw.headers))
|
|
31
|
+
return { code: "badField", server: name, field: "headers" };
|
|
32
|
+
return { type, url: raw.url, ...(raw.headers ? { headers: raw.headers } : {}), ...timeout };
|
|
33
|
+
}
|
|
34
|
+
return { code: "badType", server: name, detail: String(type) };
|
|
35
|
+
}
|
|
36
|
+
/** The valid servers, and every problem found. Empty text is no servers. */
|
|
37
|
+
export function parseMcpConfig(input) {
|
|
38
|
+
let raw = input;
|
|
39
|
+
if (typeof input === "string") {
|
|
40
|
+
if (!input.trim())
|
|
41
|
+
return { servers: {}, problems: [] };
|
|
42
|
+
try {
|
|
43
|
+
raw = JSON.parse(input);
|
|
44
|
+
}
|
|
45
|
+
catch (e) {
|
|
46
|
+
return { servers: {}, problems: [{ code: "notJson", detail: e instanceof Error ? e.message : String(e) }] };
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
const list = isObject(raw) && "mcpServers" in raw ? raw.mcpServers : raw;
|
|
50
|
+
if (!isObject(list))
|
|
51
|
+
return { servers: {}, problems: [{ code: "notServers" }] };
|
|
52
|
+
const servers = {};
|
|
53
|
+
const problems = [];
|
|
54
|
+
for (const [name, entry] of Object.entries(list)) {
|
|
55
|
+
const parsed = server(name, entry);
|
|
56
|
+
if ("code" in parsed)
|
|
57
|
+
problems.push(parsed);
|
|
58
|
+
else
|
|
59
|
+
servers[name] = parsed;
|
|
60
|
+
}
|
|
61
|
+
return { servers, problems };
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/mcp/config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { ScopedLog } from "../diagnostics.js";
|
|
3
|
+
import type { ExternalServer } from "../provider.js";
|
|
4
|
+
import type { ToolResult } from "../tools.js";
|
|
5
|
+
/**
|
|
6
|
+
* The user's own MCP servers. The library connects to each one as a client
|
|
7
|
+
* and offers its tools again, through the same channel as the application's
|
|
8
|
+
* tools, so every call is authorized by the application and reported as
|
|
9
|
+
* events, whichever provider runs the session. Providers are never handed the
|
|
10
|
+
* server itself: a CLI in headless mode would call it unchecked.
|
|
11
|
+
*/
|
|
12
|
+
/** `timeout` is in milliseconds, as in Claude Code's `.mcp.json`. */
|
|
13
|
+
export declare const McpServerSpecSchema: z.ZodUnion<readonly [z.ZodObject<{
|
|
14
|
+
type: z.ZodOptional<z.ZodLiteral<"stdio">>;
|
|
15
|
+
command: z.ZodString;
|
|
16
|
+
args: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
17
|
+
env: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
18
|
+
timeout: z.ZodOptional<z.ZodNumber>;
|
|
19
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
20
|
+
type: z.ZodEnum<{
|
|
21
|
+
http: "http";
|
|
22
|
+
sse: "sse";
|
|
23
|
+
}>;
|
|
24
|
+
url: z.ZodURL;
|
|
25
|
+
headers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
26
|
+
timeout: z.ZodOptional<z.ZodNumber>;
|
|
27
|
+
}, z.core.$strip>]>;
|
|
28
|
+
export type McpServerSpec = z.infer<typeof McpServerSpecSchema>;
|
|
29
|
+
export interface ExternalStatus {
|
|
30
|
+
server: string;
|
|
31
|
+
status: "connected" | "failed";
|
|
32
|
+
tools?: number;
|
|
33
|
+
error?: string;
|
|
34
|
+
}
|
|
35
|
+
/** Checks a set of server specs; a bad one is reported, never thrown. */
|
|
36
|
+
export declare function checkServers(raw: Record<string, unknown>): {
|
|
37
|
+
servers: Record<string, McpServerSpec>;
|
|
38
|
+
rejected: ExternalStatus[];
|
|
39
|
+
};
|
|
40
|
+
export declare class ExternalServers {
|
|
41
|
+
private readonly log;
|
|
42
|
+
private readonly connections;
|
|
43
|
+
private constructor();
|
|
44
|
+
/** Connects to every server at once; each gets `CONNECT_TIMEOUT_MS`. */
|
|
45
|
+
static connect(raw: Record<string, unknown>, log: ScopedLog): Promise<{
|
|
46
|
+
servers: ExternalServers;
|
|
47
|
+
statuses: ExternalStatus[];
|
|
48
|
+
}>;
|
|
49
|
+
/** What providers are given: names and tools, no way to reach the servers. */
|
|
50
|
+
list(): ExternalServer[];
|
|
51
|
+
has(server: string, tool: string): boolean;
|
|
52
|
+
call(server: string, tool: string, args: Record<string, unknown>): Promise<ToolResult>;
|
|
53
|
+
close(): Promise<void>;
|
|
54
|
+
private open;
|
|
55
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
2
|
+
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
|
|
3
|
+
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
4
|
+
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { AiCliError } from "../errors.js";
|
|
7
|
+
import { buildEnv } from "../platform/env.js";
|
|
8
|
+
import { TOOL_SERVER_NAME } from "./toolServer.js";
|
|
9
|
+
import { VERSION } from "../version.js";
|
|
10
|
+
/**
|
|
11
|
+
* The user's own MCP servers. The library connects to each one as a client
|
|
12
|
+
* and offers its tools again, through the same channel as the application's
|
|
13
|
+
* tools, so every call is authorized by the application and reported as
|
|
14
|
+
* events, whichever provider runs the session. Providers are never handed the
|
|
15
|
+
* server itself: a CLI in headless mode would call it unchecked.
|
|
16
|
+
*/
|
|
17
|
+
/** `timeout` is in milliseconds, as in Claude Code's `.mcp.json`. */
|
|
18
|
+
export const McpServerSpecSchema = z.union([
|
|
19
|
+
z.object({
|
|
20
|
+
type: z.literal("stdio").optional(),
|
|
21
|
+
command: z.string().min(1),
|
|
22
|
+
args: z.array(z.string()).optional(),
|
|
23
|
+
env: z.record(z.string(), z.string()).optional(),
|
|
24
|
+
timeout: z.number().positive().optional(),
|
|
25
|
+
}),
|
|
26
|
+
z.object({
|
|
27
|
+
type: z.enum(["http", "sse"]),
|
|
28
|
+
url: z.url({ protocol: /^https?$/ }),
|
|
29
|
+
headers: z.record(z.string(), z.string()).optional(),
|
|
30
|
+
timeout: z.number().positive().optional(),
|
|
31
|
+
}),
|
|
32
|
+
]);
|
|
33
|
+
const SERVER_NAME = /^[A-Za-z0-9_-]{1,64}$/;
|
|
34
|
+
const CONNECT_TIMEOUT_MS = 15_000;
|
|
35
|
+
const CALL_TIMEOUT_MS = 120_000;
|
|
36
|
+
/** Checks a set of server specs; a bad one is reported, never thrown. */
|
|
37
|
+
export function checkServers(raw) {
|
|
38
|
+
const servers = {};
|
|
39
|
+
const rejected = [];
|
|
40
|
+
for (const [name, spec] of Object.entries(raw)) {
|
|
41
|
+
if (!SERVER_NAME.test(name) || name === TOOL_SERVER_NAME) {
|
|
42
|
+
rejected.push({ server: name, status: "failed", error: "invalid or reserved server name" });
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
const parsed = McpServerSpecSchema.safeParse(spec);
|
|
46
|
+
if (parsed.success)
|
|
47
|
+
servers[name] = parsed.data;
|
|
48
|
+
else
|
|
49
|
+
rejected.push({ server: name, status: "failed", error: z.prettifyError(parsed.error).slice(0, 300) });
|
|
50
|
+
}
|
|
51
|
+
return { servers, rejected };
|
|
52
|
+
}
|
|
53
|
+
export class ExternalServers {
|
|
54
|
+
log;
|
|
55
|
+
connections = new Map();
|
|
56
|
+
constructor(log) {
|
|
57
|
+
this.log = log;
|
|
58
|
+
}
|
|
59
|
+
/** Connects to every server at once; each gets `CONNECT_TIMEOUT_MS`. */
|
|
60
|
+
static async connect(raw, log) {
|
|
61
|
+
const external = new ExternalServers(log);
|
|
62
|
+
const { servers, rejected } = checkServers(raw);
|
|
63
|
+
const statuses = await Promise.all(Object.entries(servers).map(([name, spec]) => external.open(name, spec)));
|
|
64
|
+
return { servers: external, statuses: [...rejected, ...statuses] };
|
|
65
|
+
}
|
|
66
|
+
/** What providers are given: names and tools, no way to reach the servers. */
|
|
67
|
+
list() {
|
|
68
|
+
return [...this.connections.values()].map(({ name, tools }) => ({ name, tools }));
|
|
69
|
+
}
|
|
70
|
+
has(server, tool) {
|
|
71
|
+
return Boolean(this.connections.get(server)?.tools.some((t) => t.name === tool));
|
|
72
|
+
}
|
|
73
|
+
async call(server, tool, args) {
|
|
74
|
+
const connection = this.connections.get(server);
|
|
75
|
+
if (!connection)
|
|
76
|
+
throw new AiCliError("TOOL_NOT_FOUND", `the MCP server "${server}" is not connected`);
|
|
77
|
+
const result = await connection.client.callTool({ name: tool, arguments: args }, undefined, { timeout: connection.timeoutMs });
|
|
78
|
+
const parts = Array.isArray(result.content) ? result.content : [];
|
|
79
|
+
const text = parts.map((c) => (c.type === "text" ? (c.text ?? "") : `[${c.type ?? "content"}]`)).join("\n");
|
|
80
|
+
return { content: text || (result.structuredContent ? JSON.stringify(result.structuredContent) : ""), ...(result.isError ? { isError: true } : {}) };
|
|
81
|
+
}
|
|
82
|
+
async close() {
|
|
83
|
+
const all = [...this.connections.values()];
|
|
84
|
+
this.connections.clear();
|
|
85
|
+
await Promise.all(all.map((c) => c.client.close().catch(() => undefined)));
|
|
86
|
+
}
|
|
87
|
+
async open(name, spec) {
|
|
88
|
+
const client = new Client({ name: "edukors-ai-cli", version: VERSION });
|
|
89
|
+
try {
|
|
90
|
+
let transport;
|
|
91
|
+
if (!("command" in spec)) {
|
|
92
|
+
const requestInit = spec.headers ? { headers: spec.headers } : undefined;
|
|
93
|
+
transport = spec.type === "sse" ? new SSEClientTransport(new URL(spec.url), { requestInit }) : new StreamableHTTPClientTransport(new URL(spec.url), { requestInit });
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
// The same restricted environment as provider processes, plus what the user configured.
|
|
97
|
+
const stdio = new StdioClientTransport({ command: spec.command, args: spec.args ?? [], env: buildEnv({ set: spec.env ?? {} }), stderr: "pipe" });
|
|
98
|
+
stdio.stderr?.on("data", (chunk) => this.log.debug(`mcp ${name}: ${String(chunk).trimEnd().slice(0, 300)}`));
|
|
99
|
+
transport = stdio;
|
|
100
|
+
}
|
|
101
|
+
const tools = await withTimeout((async () => {
|
|
102
|
+
await client.connect(transport);
|
|
103
|
+
const found = [];
|
|
104
|
+
let cursor;
|
|
105
|
+
do {
|
|
106
|
+
const page = await client.listTools(cursor ? { cursor } : {});
|
|
107
|
+
for (const t of page.tools)
|
|
108
|
+
found.push({ name: t.name, description: t.description ?? "", inputSchema: t.inputSchema });
|
|
109
|
+
cursor = page.nextCursor;
|
|
110
|
+
} while (cursor);
|
|
111
|
+
return found;
|
|
112
|
+
})(), CONNECT_TIMEOUT_MS);
|
|
113
|
+
this.connections.set(name, { name, client, tools, timeoutMs: spec.timeout ?? CALL_TIMEOUT_MS });
|
|
114
|
+
this.log.info(`mcp ${name} connected`, { tools: tools.length });
|
|
115
|
+
return { server: name, status: "connected", tools: tools.length };
|
|
116
|
+
}
|
|
117
|
+
catch (e) {
|
|
118
|
+
await client.close().catch(() => undefined);
|
|
119
|
+
const error = e instanceof Error ? e.message : String(e);
|
|
120
|
+
this.log.warn(`mcp ${name} failed`, { error });
|
|
121
|
+
return { server: name, status: "failed", error: error.slice(0, 300) };
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
function withTimeout(work, ms) {
|
|
126
|
+
let timer;
|
|
127
|
+
return Promise.race([work, new Promise((_, reject) => (timer = setTimeout(() => reject(new Error("did not connect in time")), ms)))]).finally(() => clearTimeout(timer));
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=external.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"external.js","sourceRoot":"","sources":["../../src/mcp/external.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import type { ScopedLog } from "../diagnostics.js";
|
|
3
|
+
import type { ToolResult, ToolSpec } from "../tools.js";
|
|
4
|
+
/**
|
|
5
|
+
* The application's tools as an MCP server. Providers that host MCP servers
|
|
6
|
+
* in-process (Claude through the Agent SDK) get the instance; providers that
|
|
7
|
+
* only take a URL (Codex) get it over HTTP on 127.0.0.1, behind a token made
|
|
8
|
+
* for one session. Either way every call goes through `call`, which is the
|
|
9
|
+
* session's turn sink: validation, events and cancellation stay in the core.
|
|
10
|
+
*/
|
|
11
|
+
import { TOOL_SERVER_NAME } from "../constants.js";
|
|
12
|
+
export { TOOL_SERVER_NAME };
|
|
13
|
+
export type ToolCaller = (name: string, args: unknown) => Promise<ToolResult>;
|
|
14
|
+
export declare function createToolMcpServer(tools: readonly ToolSpec[], call: ToolCaller, name?: string): McpServer;
|
|
15
|
+
/** One MCP server the bridge serves: the application's (`edukors`) or one of the user's, under its own name. */
|
|
16
|
+
export interface BridgedServer {
|
|
17
|
+
name: string;
|
|
18
|
+
tools: readonly ToolSpec[];
|
|
19
|
+
call: ToolCaller;
|
|
20
|
+
}
|
|
21
|
+
export interface ToolBridge {
|
|
22
|
+
/** The address of the application's tools (`/mcp`). */
|
|
23
|
+
url: string;
|
|
24
|
+
/** The address of a server by name: `/mcp` for the application's, `/mcp/<name>` for the others. */
|
|
25
|
+
urlFor(name: string): string;
|
|
26
|
+
/** Sent by the provider as `Authorization: Bearer <token>`. Never written to disk or argv. */
|
|
27
|
+
token: string;
|
|
28
|
+
close(): Promise<void>;
|
|
29
|
+
}
|
|
30
|
+
export declare function startToolBridge(servers: readonly BridgedServer[], log: ScopedLog): Promise<ToolBridge>;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
import { createServer } from "node:http";
|
|
3
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
5
|
+
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
6
|
+
import { VERSION } from "../version.js";
|
|
7
|
+
/**
|
|
8
|
+
* The application's tools as an MCP server. Providers that host MCP servers
|
|
9
|
+
* in-process (Claude through the Agent SDK) get the instance; providers that
|
|
10
|
+
* only take a URL (Codex) get it over HTTP on 127.0.0.1, behind a token made
|
|
11
|
+
* for one session. Either way every call goes through `call`, which is the
|
|
12
|
+
* session's turn sink: validation, events and cancellation stay in the core.
|
|
13
|
+
*/
|
|
14
|
+
import { TOOL_SERVER_NAME } from "../constants.js";
|
|
15
|
+
export { TOOL_SERVER_NAME };
|
|
16
|
+
/** The largest request body the bridge reads; tool arguments are small. */
|
|
17
|
+
const MAX_BODY = 4 << 20;
|
|
18
|
+
export function createToolMcpServer(tools, call, name = TOOL_SERVER_NAME) {
|
|
19
|
+
const mcp = new McpServer({ name, version: VERSION }, { capabilities: { tools: {} } });
|
|
20
|
+
// The low-level handlers take the JSON Schema as the application wrote it.
|
|
21
|
+
mcp.server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
22
|
+
tools: tools.map((t) => ({ name: t.name, description: t.description, inputSchema: { type: "object", ...t.inputSchema } })),
|
|
23
|
+
}));
|
|
24
|
+
mcp.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
25
|
+
const result = await call(request.params.name, request.params.arguments ?? {});
|
|
26
|
+
return { content: [{ type: "text", text: result.content }], isError: Boolean(result.isError) };
|
|
27
|
+
});
|
|
28
|
+
return mcp;
|
|
29
|
+
}
|
|
30
|
+
export async function startToolBridge(servers, log) {
|
|
31
|
+
const token = randomBytes(24).toString("base64url");
|
|
32
|
+
const expected = `Bearer ${token}`;
|
|
33
|
+
const byPath = new Map(servers.map((s) => [s.name === TOOL_SERVER_NAME ? "/mcp" : `/mcp/${s.name}`, s]));
|
|
34
|
+
// Stateless: one server and transport per request, which is all a CLI needs.
|
|
35
|
+
async function serve(target, req, res, body) {
|
|
36
|
+
const server = createToolMcpServer(target.tools, target.call, target.name);
|
|
37
|
+
const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, enableJsonResponse: true });
|
|
38
|
+
res.on("close", () => {
|
|
39
|
+
void transport.close().catch(() => undefined);
|
|
40
|
+
void server.close().catch(() => undefined);
|
|
41
|
+
});
|
|
42
|
+
await server.connect(transport);
|
|
43
|
+
await transport.handleRequest(req, res, body);
|
|
44
|
+
}
|
|
45
|
+
const http = createServer((req, res) => {
|
|
46
|
+
const started = Date.now();
|
|
47
|
+
res.on("finish", () => log.debug(`tool bridge ${req.method} ${(req.url ?? "").split("?")[0]} -> ${res.statusCode} (${Date.now() - started} ms)`));
|
|
48
|
+
if (req.headers.authorization !== expected) {
|
|
49
|
+
res.writeHead(401, { "content-type": "application/json" }).end('{"error":"unauthorized"}');
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
const target = byPath.get((req.url ?? "").split("?")[0] ?? "");
|
|
53
|
+
if (!target) {
|
|
54
|
+
res.writeHead(404).end();
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const chunks = [];
|
|
58
|
+
let size = 0;
|
|
59
|
+
req.on("data", (chunk) => {
|
|
60
|
+
size += chunk.length;
|
|
61
|
+
if (size > MAX_BODY) {
|
|
62
|
+
res.writeHead(413).end();
|
|
63
|
+
req.destroy();
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
chunks.push(chunk);
|
|
67
|
+
});
|
|
68
|
+
req.on("end", () => {
|
|
69
|
+
if (res.headersSent)
|
|
70
|
+
return;
|
|
71
|
+
let body;
|
|
72
|
+
try {
|
|
73
|
+
body = chunks.length ? JSON.parse(Buffer.concat(chunks).toString("utf8")) : undefined;
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
res.writeHead(400, { "content-type": "application/json" }).end('{"error":"not JSON"}');
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
serve(target, req, res, body).catch((e) => {
|
|
80
|
+
log.warn("tool bridge request failed", e);
|
|
81
|
+
if (!res.headersSent)
|
|
82
|
+
res.writeHead(500).end();
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
await new Promise((resolve, reject) => {
|
|
87
|
+
http.once("error", reject);
|
|
88
|
+
http.listen(0, "127.0.0.1", () => resolve());
|
|
89
|
+
});
|
|
90
|
+
const { port } = http.address();
|
|
91
|
+
const urlFor = (name) => `http://127.0.0.1:${port}/mcp${name === TOOL_SERVER_NAME ? "" : `/${encodeURIComponent(name)}`}`;
|
|
92
|
+
return {
|
|
93
|
+
url: urlFor(TOOL_SERVER_NAME),
|
|
94
|
+
urlFor,
|
|
95
|
+
token,
|
|
96
|
+
close: () => new Promise((resolve) => {
|
|
97
|
+
http.closeAllConnections();
|
|
98
|
+
http.close(() => resolve());
|
|
99
|
+
}),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=toolServer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"toolServer.js","sourceRoot":"","sources":["../../src/mcp/toolServer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Finding a provider's executable the way a terminal would. Applications
|
|
3
|
+
* opened from a desktop shell often have a short PATH, so common install
|
|
4
|
+
* directories and, on POSIX, the user's login shell are consulted too.
|
|
5
|
+
*/
|
|
6
|
+
export interface FindOptions {
|
|
7
|
+
/** Executable names, without extension (e.g. `["claude"]`). */
|
|
8
|
+
names: string[];
|
|
9
|
+
/** Variable holding an explicit path, from advanced settings or tests. */
|
|
10
|
+
envVar?: string;
|
|
11
|
+
/** Explicit path from advanced settings; takes precedence over everything. */
|
|
12
|
+
override?: string;
|
|
13
|
+
extraDirs?: string[];
|
|
14
|
+
env?: NodeJS.ProcessEnv;
|
|
15
|
+
platform?: NodeJS.Platform;
|
|
16
|
+
/** Ask the login shell as a last resort (POSIX only). Default true. */
|
|
17
|
+
loginShell?: boolean;
|
|
18
|
+
}
|
|
19
|
+
export declare function findExecutable(options: FindOptions): Promise<string | null>;
|
|
20
|
+
/** The first `x.y.z` in the output of `<executable> --version`. */
|
|
21
|
+
export declare function readVersion(file: string, args?: string[], env?: Record<string, string>): Promise<string | undefined>;
|
|
22
|
+
/** Compares dotted numeric versions; pre-release suffixes are ignored. */
|
|
23
|
+
export declare function compareVersions(a: string, b: string): number;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { accessSync, constants, statSync } from "node:fs";
|
|
2
|
+
import { isAbsolute, join } from "node:path";
|
|
3
|
+
import { commonBinDirs } from "./env.js";
|
|
4
|
+
import { runCommand } from "./process.js";
|
|
5
|
+
function isExecutable(path, platform) {
|
|
6
|
+
try {
|
|
7
|
+
if (!statSync(path).isFile())
|
|
8
|
+
return false;
|
|
9
|
+
if (platform !== "win32")
|
|
10
|
+
accessSync(path, constants.X_OK);
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export async function findExecutable(options) {
|
|
18
|
+
const env = options.env ?? process.env;
|
|
19
|
+
const platform = options.platform ?? process.platform;
|
|
20
|
+
const explicit = options.override ?? (options.envVar ? env[options.envVar] : undefined);
|
|
21
|
+
if (explicit)
|
|
22
|
+
return isAbsolute(explicit) && isExecutable(explicit, platform) ? explicit : null;
|
|
23
|
+
const exts = platform === "win32" ? (env.PATHEXT ?? ".EXE;.CMD;.BAT;.COM").split(";").filter(Boolean).map((e) => e.toLowerCase()) : [""];
|
|
24
|
+
const pathVar = Object.entries(env).find(([k]) => k.toUpperCase() === "PATH")?.[1] ?? "";
|
|
25
|
+
const dirs = [...pathVar.split(platform === "win32" ? ";" : ":"), ...(options.extraDirs ?? []), ...commonBinDirs(platform, env)].filter(Boolean);
|
|
26
|
+
for (const dir of dirs)
|
|
27
|
+
for (const name of options.names)
|
|
28
|
+
for (const ext of exts) {
|
|
29
|
+
const candidate = join(dir, name + ext);
|
|
30
|
+
if (isExecutable(candidate, platform))
|
|
31
|
+
return candidate;
|
|
32
|
+
}
|
|
33
|
+
if (platform !== "win32" && options.loginShell !== false) {
|
|
34
|
+
const shell = env.SHELL || (platform === "darwin" ? "/bin/zsh" : "/bin/sh");
|
|
35
|
+
for (const name of options.names) {
|
|
36
|
+
if (!/^[\w.-]+$/.test(name))
|
|
37
|
+
continue;
|
|
38
|
+
const result = await runCommand({ file: shell, args: ["-lc", `command -v ${name}`], env: { ...env }, timeoutMs: 8_000 }).catch(() => null);
|
|
39
|
+
const found = result?.stdout.trim().split("\n").find(Boolean);
|
|
40
|
+
if (found && isAbsolute(found) && isExecutable(found, platform))
|
|
41
|
+
return found;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
/** The first `x.y.z` in the output of `<executable> --version`. */
|
|
47
|
+
export async function readVersion(file, args = ["--version"], env) {
|
|
48
|
+
const result = await runCommand({ file, args, env, timeoutMs: 10_000 }).catch(() => null);
|
|
49
|
+
if (!result)
|
|
50
|
+
return undefined;
|
|
51
|
+
return `${result.stdout}\n${result.stderr}`.match(/\d+\.\d+\.\d+/)?.[0];
|
|
52
|
+
}
|
|
53
|
+
/** Compares dotted numeric versions; pre-release suffixes are ignored. */
|
|
54
|
+
export function compareVersions(a, b) {
|
|
55
|
+
const pa = a.split(/[.+-]/).slice(0, 3).map(Number);
|
|
56
|
+
const pb = b.split(/[.+-]/).slice(0, 3).map(Number);
|
|
57
|
+
for (let i = 0; i < 3; i++) {
|
|
58
|
+
const d = (pa[i] || 0) - (pb[i] || 0);
|
|
59
|
+
if (d !== 0)
|
|
60
|
+
return Math.sign(d);
|
|
61
|
+
}
|
|
62
|
+
return 0;
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=discovery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discovery.js","sourceRoot":"","sources":["../../src/platform/discovery.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** Where user-level CLIs are commonly installed but GUI-launched apps often lack on PATH. */
|
|
2
|
+
export declare function commonBinDirs(platform?: NodeJS.Platform, env?: NodeJS.ProcessEnv): string[];
|
|
3
|
+
export interface BuildEnvOptions {
|
|
4
|
+
base?: NodeJS.ProcessEnv;
|
|
5
|
+
/** Provider-specific variables, applied last. `undefined` removes a variable. */
|
|
6
|
+
set?: Record<string, string | undefined>;
|
|
7
|
+
/** Directories appended to PATH when absent (e.g. the executable's own directory). */
|
|
8
|
+
extraPath?: string[];
|
|
9
|
+
platform?: NodeJS.Platform;
|
|
10
|
+
}
|
|
11
|
+
/** An explicit environment for a provider process. */
|
|
12
|
+
export declare function buildEnv(options?: BuildEnvOptions): Record<string, string>;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { homedir } from "node:os";
|
|
2
|
+
import { delimiter, join } from "node:path";
|
|
3
|
+
/**
|
|
4
|
+
* Variables a provider process may inherit. Everything else, credentials
|
|
5
|
+
* included (ANTHROPIC_API_KEY, OPENAI_API_KEY, CLAUDE_CONFIG_DIR, CODEX_HOME...),
|
|
6
|
+
* is left out, so the selected account cannot be overridden by the
|
|
7
|
+
* environment the application happened to start in.
|
|
8
|
+
*/
|
|
9
|
+
const INHERITED = new Set([
|
|
10
|
+
"PATH",
|
|
11
|
+
"HOME",
|
|
12
|
+
"USER",
|
|
13
|
+
"LOGNAME",
|
|
14
|
+
"SHELL",
|
|
15
|
+
"LANG",
|
|
16
|
+
"LANGUAGE",
|
|
17
|
+
"TZ",
|
|
18
|
+
"TMPDIR",
|
|
19
|
+
"TMP",
|
|
20
|
+
"TEMP",
|
|
21
|
+
// Linux keyrings and desktop sessions.
|
|
22
|
+
"XDG_RUNTIME_DIR",
|
|
23
|
+
"DBUS_SESSION_BUS_ADDRESS",
|
|
24
|
+
"DISPLAY",
|
|
25
|
+
"WAYLAND_DISPLAY",
|
|
26
|
+
// Corporate networks.
|
|
27
|
+
"HTTP_PROXY",
|
|
28
|
+
"HTTPS_PROXY",
|
|
29
|
+
"NO_PROXY",
|
|
30
|
+
"ALL_PROXY",
|
|
31
|
+
"NODE_EXTRA_CA_CERTS",
|
|
32
|
+
"SSL_CERT_FILE",
|
|
33
|
+
"SSL_CERT_DIR",
|
|
34
|
+
// Windows essentials.
|
|
35
|
+
"SYSTEMROOT",
|
|
36
|
+
"SYSTEMDRIVE",
|
|
37
|
+
"WINDIR",
|
|
38
|
+
"COMSPEC",
|
|
39
|
+
"PATHEXT",
|
|
40
|
+
"USERPROFILE",
|
|
41
|
+
"USERNAME",
|
|
42
|
+
"APPDATA",
|
|
43
|
+
"LOCALAPPDATA",
|
|
44
|
+
"PROGRAMDATA",
|
|
45
|
+
"PROGRAMFILES",
|
|
46
|
+
"PROGRAMFILES(X86)",
|
|
47
|
+
"HOMEDRIVE",
|
|
48
|
+
"HOMEPATH",
|
|
49
|
+
"NUMBER_OF_PROCESSORS",
|
|
50
|
+
"PROCESSOR_ARCHITECTURE",
|
|
51
|
+
].map((name) => name.toUpperCase()));
|
|
52
|
+
const inherited = (name) => INHERITED.has(name.toUpperCase()) || /^LC_[A-Z]+$/.test(name);
|
|
53
|
+
/** Where user-level CLIs are commonly installed but GUI-launched apps often lack on PATH. */
|
|
54
|
+
export function commonBinDirs(platform = process.platform, env = process.env) {
|
|
55
|
+
const home = env.HOME || env.USERPROFILE || homedir();
|
|
56
|
+
if (platform === "win32") {
|
|
57
|
+
return [
|
|
58
|
+
env.APPDATA ? join(env.APPDATA, "npm") : "",
|
|
59
|
+
env.LOCALAPPDATA ? join(env.LOCALAPPDATA, "Programs") : "",
|
|
60
|
+
join(home, ".bun", "bin"),
|
|
61
|
+
join(home, ".local", "bin"),
|
|
62
|
+
].filter(Boolean);
|
|
63
|
+
}
|
|
64
|
+
return [
|
|
65
|
+
join(home, ".local", "bin"),
|
|
66
|
+
"/opt/homebrew/bin",
|
|
67
|
+
"/usr/local/bin",
|
|
68
|
+
join(home, ".npm-global", "bin"),
|
|
69
|
+
join(home, ".bun", "bin"),
|
|
70
|
+
join(home, ".volta", "bin"),
|
|
71
|
+
join(home, ".cargo", "bin"),
|
|
72
|
+
"/usr/bin",
|
|
73
|
+
"/bin",
|
|
74
|
+
];
|
|
75
|
+
}
|
|
76
|
+
/** An explicit environment for a provider process. */
|
|
77
|
+
export function buildEnv(options = {}) {
|
|
78
|
+
const base = options.base ?? process.env;
|
|
79
|
+
const platform = options.platform ?? process.platform;
|
|
80
|
+
const env = {};
|
|
81
|
+
for (const [name, value] of Object.entries(base))
|
|
82
|
+
if (value !== undefined && inherited(name))
|
|
83
|
+
env[name] = value;
|
|
84
|
+
const pathKey = Object.keys(env).find((k) => k.toUpperCase() === "PATH") ?? "PATH";
|
|
85
|
+
const sep = platform === "win32" ? ";" : delimiter;
|
|
86
|
+
const entries = (env[pathKey] ?? "").split(sep).filter(Boolean);
|
|
87
|
+
for (const dir of [...(options.extraPath ?? []), ...commonBinDirs(platform, base)])
|
|
88
|
+
if (!entries.includes(dir))
|
|
89
|
+
entries.push(dir);
|
|
90
|
+
env[pathKey] = entries.join(sep);
|
|
91
|
+
for (const [name, value] of Object.entries(options.set ?? {})) {
|
|
92
|
+
// Windows names are case-insensitive: replace any existing spelling.
|
|
93
|
+
if (platform === "win32")
|
|
94
|
+
for (const k of Object.keys(env))
|
|
95
|
+
if (k.toUpperCase() === name.toUpperCase())
|
|
96
|
+
delete env[k];
|
|
97
|
+
if (value === undefined)
|
|
98
|
+
delete env[name];
|
|
99
|
+
else
|
|
100
|
+
env[name] = value;
|
|
101
|
+
}
|
|
102
|
+
return env;
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=env.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env.js","sourceRoot":"","sources":["../../src/platform/env.ts"],"names":[],"mappings":"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"}
|