@di-code/coding-agent 0.1.3 → 0.1.6
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/README.md +91 -30
- package/dist/cli.d.ts +15 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +96 -17
- package/dist/cli.js.map +1 -1
- package/dist/core/clipboard-image.d.ts +3 -3
- package/dist/core/clipboard-image.d.ts.map +1 -1
- package/dist/core/clipboard-image.js +26 -13
- package/dist/core/clipboard-image.js.map +1 -1
- package/dist/core/context-builder.d.ts.map +1 -1
- package/dist/core/context-builder.js +6 -2
- package/dist/core/context-builder.js.map +1 -1
- package/dist/core/image-input.d.ts +1 -1
- package/dist/core/image-input.d.ts.map +1 -1
- package/dist/core/image-input.js +55 -10
- package/dist/core/image-input.js.map +1 -1
- package/dist/core/resources/loader.d.ts.map +1 -1
- package/dist/core/resources/loader.js +15 -42
- package/dist/core/resources/loader.js.map +1 -1
- package/dist/core/resources/types.d.ts +6 -9
- package/dist/core/resources/types.d.ts.map +1 -1
- package/dist/core/resources/types.js.map +1 -1
- package/dist/core/session/session-manager.d.ts +12 -1
- package/dist/core/session/session-manager.d.ts.map +1 -1
- package/dist/core/session/session-manager.js +66 -3
- package/dist/core/session/session-manager.js.map +1 -1
- package/dist/core/session/session-storage.d.ts.map +1 -1
- package/dist/core/session/session-storage.js +30 -15
- package/dist/core/session/session-storage.js.map +1 -1
- package/dist/core/session/types.d.ts +6 -1
- package/dist/core/session/types.d.ts.map +1 -1
- package/dist/core/session/types.js +1 -1
- package/dist/core/session/types.js.map +1 -1
- package/dist/core/session.d.ts +38 -3
- package/dist/core/session.d.ts.map +1 -1
- package/dist/core/session.js +167 -9
- package/dist/core/session.js.map +1 -1
- package/dist/core/system-prompt.js +2 -2
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/tools/bash.d.ts +2 -2
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/edit-diff.d.ts +25 -0
- package/dist/core/tools/edit-diff.d.ts.map +1 -0
- package/dist/core/tools/edit-diff.js +136 -0
- package/dist/core/tools/edit-diff.js.map +1 -0
- package/dist/core/tools/edit.d.ts +13 -4
- package/dist/core/tools/edit.d.ts.map +1 -1
- package/dist/core/tools/edit.js +64 -30
- package/dist/core/tools/edit.js.map +1 -1
- package/dist/core/tools/file-search.d.ts +14 -0
- package/dist/core/tools/file-search.d.ts.map +1 -0
- package/dist/core/tools/file-search.js +99 -0
- package/dist/core/tools/file-search.js.map +1 -0
- package/dist/core/tools/glob.d.ts +11 -0
- package/dist/core/tools/glob.d.ts.map +1 -0
- package/dist/core/tools/glob.js +44 -0
- package/dist/core/tools/glob.js.map +1 -0
- package/dist/core/tools/grep.d.ts +13 -0
- package/dist/core/tools/grep.d.ts.map +1 -0
- package/dist/core/tools/grep.js +67 -0
- package/dist/core/tools/grep.js.map +1 -0
- package/dist/core/tools/load-skill.d.ts +10 -0
- package/dist/core/tools/load-skill.d.ts.map +1 -0
- package/dist/core/tools/load-skill.js +22 -0
- package/dist/core/tools/load-skill.js.map +1 -0
- package/dist/core/tools/path-boundary.d.ts +2 -0
- package/dist/core/tools/path-boundary.d.ts.map +1 -1
- package/dist/core/tools/path-boundary.js +4 -0
- package/dist/core/tools/path-boundary.js.map +1 -1
- package/dist/core/tools/read.d.ts +2 -2
- package/dist/core/tools/read.d.ts.map +1 -1
- package/dist/core/tools/read.js.map +1 -1
- package/dist/core/tools/write.d.ts +2 -2
- package/dist/core/tools/write.d.ts.map +1 -1
- package/dist/core/tools/write.js.map +1 -1
- package/dist/core/user-data.d.ts +3 -0
- package/dist/core/user-data.d.ts.map +1 -0
- package/dist/core/user-data.js +8 -0
- package/dist/core/user-data.js.map +1 -0
- package/dist/entry.js +22 -2
- package/dist/entry.js.map +1 -1
- package/dist/extensions/runtime.js +2 -2
- package/dist/extensions/runtime.js.map +1 -1
- package/dist/i18n.d.ts +5 -0
- package/dist/i18n.d.ts.map +1 -0
- package/dist/i18n.js +223 -0
- package/dist/i18n.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts +20 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +272 -17
- package/dist/main.js.map +1 -1
- package/dist/mcp/config.d.ts +33 -0
- package/dist/mcp/config.d.ts.map +1 -0
- package/dist/mcp/config.js +211 -0
- package/dist/mcp/config.js.map +1 -0
- package/dist/mcp/loader.d.ts +21 -0
- package/dist/mcp/loader.d.ts.map +1 -0
- package/dist/mcp/loader.js +60 -0
- package/dist/mcp/loader.js.map +1 -0
- package/dist/mcp/tool-adapter.d.ts +5 -0
- package/dist/mcp/tool-adapter.d.ts.map +1 -0
- package/dist/mcp/tool-adapter.js +149 -0
- package/dist/mcp/tool-adapter.js.map +1 -0
- package/dist/modes/interactive-components.d.ts +2 -0
- package/dist/modes/interactive-components.d.ts.map +1 -1
- package/dist/modes/interactive-components.js +55 -31
- package/dist/modes/interactive-components.js.map +1 -1
- package/dist/modes/interactive-diff.d.ts +8 -0
- package/dist/modes/interactive-diff.d.ts.map +1 -0
- package/dist/modes/interactive-diff.js +78 -0
- package/dist/modes/interactive-diff.js.map +1 -0
- package/dist/modes/interactive-layout.d.ts +4 -0
- package/dist/modes/interactive-layout.d.ts.map +1 -1
- package/dist/modes/interactive-layout.js +4 -0
- package/dist/modes/interactive-layout.js.map +1 -1
- package/dist/modes/interactive-state.d.ts +5 -0
- package/dist/modes/interactive-state.d.ts.map +1 -1
- package/dist/modes/interactive-state.js +65 -11
- package/dist/modes/interactive-state.js.map +1 -1
- package/dist/modes/interactive.d.ts +19 -0
- package/dist/modes/interactive.d.ts.map +1 -1
- package/dist/modes/interactive.js +351 -52
- package/dist/modes/interactive.js.map +1 -1
- package/dist/modes/tree-selector.d.ts +30 -0
- package/dist/modes/tree-selector.d.ts.map +1 -0
- package/dist/modes/tree-selector.js +205 -0
- package/dist/modes/tree-selector.js.map +1 -0
- package/dist/plugins/loader.d.ts +19 -1
- package/dist/plugins/loader.d.ts.map +1 -1
- package/dist/plugins/loader.js +21 -2
- package/dist/plugins/loader.js.map +1 -1
- package/dist/provider-onboarding.d.ts +8 -1
- package/dist/provider-onboarding.d.ts.map +1 -1
- package/dist/provider-onboarding.js +160 -40
- package/dist/provider-onboarding.js.map +1 -1
- package/dist/rpc-entry.js +1 -1
- package/dist/rpc-entry.js.map +1 -1
- package/dist/startup.d.ts +34 -3
- package/dist/startup.d.ts.map +1 -1
- package/dist/startup.js +407 -87
- package/dist/startup.js.map +1 -1
- package/package.json +7 -4
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { mkdir, readFile, rename, unlink, writeFile } from "node:fs/promises";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { dirname, join, resolve } from "node:path";
|
|
4
|
+
const PROJECT_CONFIG_FILE_NAME = ".mcp.json";
|
|
5
|
+
const LOCAL_CONFIG_FILE_NAME = join(".di-code", "mcp.local.json");
|
|
6
|
+
const USER_CONFIG_FILE_NAME = join(".di-code", "mcp.json");
|
|
7
|
+
const SERVER_ID_PATTERN = /^[a-z0-9][a-z0-9_-]{0,62}$/;
|
|
8
|
+
function requiredString(value, path) {
|
|
9
|
+
if (typeof value !== "string" || !value.trim())
|
|
10
|
+
throw new Error(`${PROJECT_CONFIG_FILE_NAME}: ${path} must be a non-empty string`);
|
|
11
|
+
return value.trim();
|
|
12
|
+
}
|
|
13
|
+
function resolveEnvironmentValue(value, path, environment) {
|
|
14
|
+
return value.replace(/\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g, (_whole, name) => {
|
|
15
|
+
const resolved = environment[name];
|
|
16
|
+
if (!resolved)
|
|
17
|
+
throw new Error(`${PROJECT_CONFIG_FILE_NAME}: ${path} environment variable "${name}" is not set`);
|
|
18
|
+
return resolved;
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
function parseEnvironment(value, path, environment) {
|
|
22
|
+
if (value === undefined)
|
|
23
|
+
return undefined;
|
|
24
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
25
|
+
throw new Error(`${PROJECT_CONFIG_FILE_NAME}: ${path} must be an object`);
|
|
26
|
+
const result = {};
|
|
27
|
+
for (const [name, raw] of Object.entries(value)) {
|
|
28
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name))
|
|
29
|
+
throw new Error(`${PROJECT_CONFIG_FILE_NAME}: ${path}.${name} is not a valid environment variable name`);
|
|
30
|
+
result[name] = resolveEnvironmentValue(requiredString(raw, `${path}.${name}`), `${path}.${name}`, environment);
|
|
31
|
+
}
|
|
32
|
+
return result;
|
|
33
|
+
}
|
|
34
|
+
function parseHeaders(value, path, environment) {
|
|
35
|
+
if (value === undefined)
|
|
36
|
+
return undefined;
|
|
37
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
38
|
+
throw new Error(`${PROJECT_CONFIG_FILE_NAME}: ${path} must be an object`);
|
|
39
|
+
const result = {};
|
|
40
|
+
for (const [name, raw] of Object.entries(value)) {
|
|
41
|
+
if (!name.trim())
|
|
42
|
+
throw new Error(`${PROJECT_CONFIG_FILE_NAME}: ${path} contains an empty header name`);
|
|
43
|
+
result[name] = resolveEnvironmentValue(requiredString(raw, `${path}.${name}`), `${path}.${name}`, environment);
|
|
44
|
+
}
|
|
45
|
+
return result;
|
|
46
|
+
}
|
|
47
|
+
function inheritedEnvironment() {
|
|
48
|
+
return Object.fromEntries(Object.entries(process.env).filter((entry) => entry[1] !== undefined));
|
|
49
|
+
}
|
|
50
|
+
function parseServer(id, value, cwd, environment) {
|
|
51
|
+
if (!SERVER_ID_PATTERN.test(id))
|
|
52
|
+
throw new Error(`${PROJECT_CONFIG_FILE_NAME}: mcpServers.${id} must use lowercase letters, numbers, hyphens, or underscores`);
|
|
53
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
54
|
+
throw new Error(`${PROJECT_CONFIG_FILE_NAME}: mcpServers.${id} must be an object`);
|
|
55
|
+
const entry = value;
|
|
56
|
+
if (entry.type === undefined || entry.type === "stdio") {
|
|
57
|
+
if (entry.args !== undefined &&
|
|
58
|
+
(!Array.isArray(entry.args) || entry.args.some((argument) => typeof argument !== "string")))
|
|
59
|
+
throw new Error(`${PROJECT_CONFIG_FILE_NAME}: mcpServers.${id}.args must be an array of strings`);
|
|
60
|
+
const env = parseEnvironment(entry.env, `mcpServers.${id}.env`, environment);
|
|
61
|
+
return {
|
|
62
|
+
id,
|
|
63
|
+
transport: {
|
|
64
|
+
type: "stdio",
|
|
65
|
+
command: requiredString(entry.command, `mcpServers.${id}.command`),
|
|
66
|
+
...(entry.args === undefined ? {} : { args: [...entry.args] }),
|
|
67
|
+
cwd,
|
|
68
|
+
env: { ...inheritedEnvironment(), ...env },
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
if (entry.type !== "http")
|
|
73
|
+
throw new Error(`${PROJECT_CONFIG_FILE_NAME}: mcpServers.${id}.type "${String(entry.type)}" is not supported; expected stdio or http`);
|
|
74
|
+
const url = requiredString(entry.url, `mcpServers.${id}.url`);
|
|
75
|
+
let parsedUrl;
|
|
76
|
+
try {
|
|
77
|
+
parsedUrl = new URL(url);
|
|
78
|
+
}
|
|
79
|
+
catch (cause) {
|
|
80
|
+
throw new Error(`${PROJECT_CONFIG_FILE_NAME}: mcpServers.${id}.url must be an absolute http or https URL`, {
|
|
81
|
+
cause,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
if (parsedUrl.protocol !== "http:" && parsedUrl.protocol !== "https:")
|
|
85
|
+
throw new Error(`${PROJECT_CONFIG_FILE_NAME}: mcpServers.${id}.url must use http or https`);
|
|
86
|
+
if (parsedUrl.username || parsedUrl.password)
|
|
87
|
+
throw new Error(`${PROJECT_CONFIG_FILE_NAME}: mcpServers.${id}.url must not contain credentials`);
|
|
88
|
+
const headers = parseHeaders(entry.headers, `mcpServers.${id}.headers`, environment);
|
|
89
|
+
return {
|
|
90
|
+
id,
|
|
91
|
+
transport: {
|
|
92
|
+
type: "streamable-http",
|
|
93
|
+
url: parsedUrl.toString(),
|
|
94
|
+
...(headers ? { headers } : {}),
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
export function mcpConfigPath(cwd, scope = "project", homeDirectory = homedir()) {
|
|
99
|
+
return scope === "project"
|
|
100
|
+
? join(resolve(cwd), PROJECT_CONFIG_FILE_NAME)
|
|
101
|
+
: scope === "local"
|
|
102
|
+
? join(resolve(cwd), LOCAL_CONFIG_FILE_NAME)
|
|
103
|
+
: join(resolve(homeDirectory), USER_CONFIG_FILE_NAME);
|
|
104
|
+
}
|
|
105
|
+
async function readDocument(path) {
|
|
106
|
+
let raw;
|
|
107
|
+
try {
|
|
108
|
+
raw = await readFile(path, "utf8");
|
|
109
|
+
}
|
|
110
|
+
catch (cause) {
|
|
111
|
+
if (cause instanceof Error && "code" in cause && cause.code === "ENOENT")
|
|
112
|
+
return { mcpServers: {} };
|
|
113
|
+
throw cause;
|
|
114
|
+
}
|
|
115
|
+
let parsed;
|
|
116
|
+
try {
|
|
117
|
+
parsed = JSON.parse(raw);
|
|
118
|
+
}
|
|
119
|
+
catch (cause) {
|
|
120
|
+
throw new Error(`${PROJECT_CONFIG_FILE_NAME}: invalid JSON`, { cause });
|
|
121
|
+
}
|
|
122
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
|
|
123
|
+
throw new Error(`${PROJECT_CONFIG_FILE_NAME}: root value must be an object`);
|
|
124
|
+
const servers = parsed.mcpServers;
|
|
125
|
+
if (typeof servers !== "object" || servers === null || Array.isArray(servers))
|
|
126
|
+
throw new Error(`${PROJECT_CONFIG_FILE_NAME}: mcpServers must be an object`);
|
|
127
|
+
return { mcpServers: { ...servers } };
|
|
128
|
+
}
|
|
129
|
+
export async function readMcpConfigScope(cwd, scope, homeDirectory = homedir()) {
|
|
130
|
+
return readDocument(mcpConfigPath(cwd, scope, homeDirectory));
|
|
131
|
+
}
|
|
132
|
+
export async function loadMcpConfig(cwd, environment = process.env) {
|
|
133
|
+
const document = await readMcpConfigScope(cwd, "project");
|
|
134
|
+
return Object.entries(document.mcpServers)
|
|
135
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
136
|
+
.map(([id, server]) => parseServer(id, server, cwd, environment));
|
|
137
|
+
}
|
|
138
|
+
export async function loadEffectiveMcpConfig(options) {
|
|
139
|
+
const environment = options.environment ?? process.env;
|
|
140
|
+
const scopes = options.projectTrusted ? ["user", "project", "local"] : ["user"];
|
|
141
|
+
const merged = new Map();
|
|
142
|
+
for (const scope of scopes) {
|
|
143
|
+
const document = await readMcpConfigScope(options.cwd, scope, options.homeDirectory);
|
|
144
|
+
for (const [id, server] of Object.entries(document.mcpServers))
|
|
145
|
+
merged.set(id, server);
|
|
146
|
+
}
|
|
147
|
+
return [...merged.entries()]
|
|
148
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
149
|
+
.map(([id, server]) => parseServer(id, server, options.cwd, environment));
|
|
150
|
+
}
|
|
151
|
+
function redactedEntry(value) {
|
|
152
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
153
|
+
return value;
|
|
154
|
+
const entry = { ...value };
|
|
155
|
+
for (const field of ["env", "headers"]) {
|
|
156
|
+
const values = entry[field];
|
|
157
|
+
if (typeof values !== "object" || values === null || Array.isArray(values))
|
|
158
|
+
continue;
|
|
159
|
+
entry[field] = Object.fromEntries(Object.entries(values).map(([name, raw]) => [
|
|
160
|
+
name,
|
|
161
|
+
typeof raw === "string" && /^\$\{[A-Za-z_][A-Za-z0-9_]*\}$/.test(raw) ? raw : "[redacted]",
|
|
162
|
+
]));
|
|
163
|
+
}
|
|
164
|
+
return entry;
|
|
165
|
+
}
|
|
166
|
+
export async function listMcpConfig(cwd, scope, homeDirectory = homedir()) {
|
|
167
|
+
const document = await readMcpConfigScope(cwd, scope, homeDirectory);
|
|
168
|
+
return Object.entries(document.mcpServers)
|
|
169
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
170
|
+
.map(([id, config]) => ({ id, scope, config: redactedEntry(config) }));
|
|
171
|
+
}
|
|
172
|
+
export async function getMcpConfig(cwd, id, scope, homeDirectory = homedir()) {
|
|
173
|
+
const scopes = scope ? [scope] : ["local", "project", "user"];
|
|
174
|
+
for (const current of scopes) {
|
|
175
|
+
const document = await readMcpConfigScope(cwd, current, homeDirectory);
|
|
176
|
+
if (id in document.mcpServers)
|
|
177
|
+
return { id, scope: current, config: redactedEntry(document.mcpServers[id]) };
|
|
178
|
+
}
|
|
179
|
+
return undefined;
|
|
180
|
+
}
|
|
181
|
+
async function writeDocument(cwd, scope, document, homeDirectory = homedir()) {
|
|
182
|
+
const path = mcpConfigPath(cwd, scope, homeDirectory);
|
|
183
|
+
await mkdir(dirname(path), { recursive: true });
|
|
184
|
+
const temporaryPath = `${path}.${process.pid}.${Date.now()}.tmp`;
|
|
185
|
+
await writeFile(temporaryPath, `${JSON.stringify({ mcpServers: document.mcpServers }, null, 2)}\n`, "utf8");
|
|
186
|
+
try {
|
|
187
|
+
await rename(temporaryPath, path);
|
|
188
|
+
}
|
|
189
|
+
catch (cause) {
|
|
190
|
+
await unlink(temporaryPath).catch(() => undefined);
|
|
191
|
+
throw cause;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
export async function addMcpConfig(cwd, scope, id, config, options = {}) {
|
|
195
|
+
if (!SERVER_ID_PATTERN.test(id))
|
|
196
|
+
throw new Error(`Invalid MCP server id "${id}".`);
|
|
197
|
+
const document = await readMcpConfigScope(cwd, scope, options.homeDirectory);
|
|
198
|
+
if (id in document.mcpServers)
|
|
199
|
+
throw new Error(`MCP server "${id}" already exists in ${scope} scope.`);
|
|
200
|
+
parseServer(id, config, cwd, options.environment ?? process.env);
|
|
201
|
+
await writeDocument(cwd, scope, { mcpServers: { ...document.mcpServers, [id]: config } }, options.homeDirectory);
|
|
202
|
+
}
|
|
203
|
+
export async function removeMcpConfig(cwd, scope, id, homeDirectory = homedir()) {
|
|
204
|
+
const document = await readMcpConfigScope(cwd, scope, homeDirectory);
|
|
205
|
+
if (!(id in document.mcpServers))
|
|
206
|
+
throw new Error(`MCP server "${id}" was not found in ${scope} scope.`);
|
|
207
|
+
const { [id]: _removed, ...remaining } = document.mcpServers;
|
|
208
|
+
await writeDocument(cwd, scope, { mcpServers: remaining }, homeDirectory);
|
|
209
|
+
}
|
|
210
|
+
export { SERVER_ID_PATTERN };
|
|
211
|
+
//# 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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","sourcesContent":["import { mkdir, readFile, rename, unlink, writeFile } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { dirname, join, resolve } from \"node:path\";\nimport type { McpServerConfig } from \"@di-code/mcp\";\n\nconst PROJECT_CONFIG_FILE_NAME = \".mcp.json\";\nconst LOCAL_CONFIG_FILE_NAME = join(\".di-code\", \"mcp.local.json\");\nconst USER_CONFIG_FILE_NAME = join(\".di-code\", \"mcp.json\");\nconst SERVER_ID_PATTERN = /^[a-z0-9][a-z0-9_-]{0,62}$/;\nexport type McpConfigScope = \"local\" | \"project\" | \"user\";\ntype Environment = Readonly<Record<string, string | undefined>>;\nexport interface McpConfigDocument {\n\treadonly mcpServers: Readonly<Record<string, unknown>>;\n}\n\nfunction requiredString(value: unknown, path: string): string {\n\tif (typeof value !== \"string\" || !value.trim())\n\t\tthrow new Error(`${PROJECT_CONFIG_FILE_NAME}: ${path} must be a non-empty string`);\n\treturn value.trim();\n}\n\nfunction resolveEnvironmentValue(value: string, path: string, environment: Environment): string {\n\treturn value.replace(/\\$\\{([A-Za-z_][A-Za-z0-9_]*)\\}/g, (_whole, name: string) => {\n\t\tconst resolved = environment[name];\n\t\tif (!resolved) throw new Error(`${PROJECT_CONFIG_FILE_NAME}: ${path} environment variable \"${name}\" is not set`);\n\t\treturn resolved;\n\t});\n}\n\nfunction parseEnvironment(value: unknown, path: string, environment: Environment): Record<string, string> | undefined {\n\tif (value === undefined) return undefined;\n\tif (typeof value !== \"object\" || value === null || Array.isArray(value))\n\t\tthrow new Error(`${PROJECT_CONFIG_FILE_NAME}: ${path} must be an object`);\n\tconst result: Record<string, string> = {};\n\tfor (const [name, raw] of Object.entries(value as Record<string, unknown>)) {\n\t\tif (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name))\n\t\t\tthrow new Error(`${PROJECT_CONFIG_FILE_NAME}: ${path}.${name} is not a valid environment variable name`);\n\t\tresult[name] = resolveEnvironmentValue(requiredString(raw, `${path}.${name}`), `${path}.${name}`, environment);\n\t}\n\treturn result;\n}\n\nfunction parseHeaders(value: unknown, path: string, environment: Environment): Record<string, string> | undefined {\n\tif (value === undefined) return undefined;\n\tif (typeof value !== \"object\" || value === null || Array.isArray(value))\n\t\tthrow new Error(`${PROJECT_CONFIG_FILE_NAME}: ${path} must be an object`);\n\tconst result: Record<string, string> = {};\n\tfor (const [name, raw] of Object.entries(value as Record<string, unknown>)) {\n\t\tif (!name.trim()) throw new Error(`${PROJECT_CONFIG_FILE_NAME}: ${path} contains an empty header name`);\n\t\tresult[name] = resolveEnvironmentValue(requiredString(raw, `${path}.${name}`), `${path}.${name}`, environment);\n\t}\n\treturn result;\n}\n\nfunction inheritedEnvironment(): Record<string, string> {\n\treturn Object.fromEntries(\n\t\tObject.entries(process.env).filter((entry): entry is [string, string] => entry[1] !== undefined),\n\t);\n}\n\nfunction parseServer(id: string, value: unknown, cwd: string, environment: Environment): McpServerConfig {\n\tif (!SERVER_ID_PATTERN.test(id))\n\t\tthrow new Error(\n\t\t\t`${PROJECT_CONFIG_FILE_NAME}: mcpServers.${id} must use lowercase letters, numbers, hyphens, or underscores`,\n\t\t);\n\tif (typeof value !== \"object\" || value === null || Array.isArray(value))\n\t\tthrow new Error(`${PROJECT_CONFIG_FILE_NAME}: mcpServers.${id} must be an object`);\n\tconst entry = value as Record<string, unknown>;\n\tif (entry.type === undefined || entry.type === \"stdio\") {\n\t\tif (\n\t\t\tentry.args !== undefined &&\n\t\t\t(!Array.isArray(entry.args) || entry.args.some((argument) => typeof argument !== \"string\"))\n\t\t)\n\t\t\tthrow new Error(`${PROJECT_CONFIG_FILE_NAME}: mcpServers.${id}.args must be an array of strings`);\n\t\tconst env = parseEnvironment(entry.env, `mcpServers.${id}.env`, environment);\n\t\treturn {\n\t\t\tid,\n\t\t\ttransport: {\n\t\t\t\ttype: \"stdio\",\n\t\t\t\tcommand: requiredString(entry.command, `mcpServers.${id}.command`),\n\t\t\t\t...(entry.args === undefined ? {} : { args: [...(entry.args as string[])] }),\n\t\t\t\tcwd,\n\t\t\t\tenv: { ...inheritedEnvironment(), ...env },\n\t\t\t},\n\t\t};\n\t}\n\tif (entry.type !== \"http\")\n\t\tthrow new Error(\n\t\t\t`${PROJECT_CONFIG_FILE_NAME}: mcpServers.${id}.type \"${String(entry.type)}\" is not supported; expected stdio or http`,\n\t\t);\n\tconst url = requiredString(entry.url, `mcpServers.${id}.url`);\n\tlet parsedUrl: URL;\n\ttry {\n\t\tparsedUrl = new URL(url);\n\t} catch (cause) {\n\t\tthrow new Error(`${PROJECT_CONFIG_FILE_NAME}: mcpServers.${id}.url must be an absolute http or https URL`, {\n\t\t\tcause,\n\t\t});\n\t}\n\tif (parsedUrl.protocol !== \"http:\" && parsedUrl.protocol !== \"https:\")\n\t\tthrow new Error(`${PROJECT_CONFIG_FILE_NAME}: mcpServers.${id}.url must use http or https`);\n\tif (parsedUrl.username || parsedUrl.password)\n\t\tthrow new Error(`${PROJECT_CONFIG_FILE_NAME}: mcpServers.${id}.url must not contain credentials`);\n\tconst headers = parseHeaders(entry.headers, `mcpServers.${id}.headers`, environment);\n\treturn {\n\t\tid,\n\t\ttransport: {\n\t\t\ttype: \"streamable-http\" as const,\n\t\t\turl: parsedUrl.toString(),\n\t\t\t...(headers ? { headers } : {}),\n\t\t},\n\t};\n}\n\nexport function mcpConfigPath(cwd: string, scope: McpConfigScope = \"project\", homeDirectory = homedir()): string {\n\treturn scope === \"project\"\n\t\t? join(resolve(cwd), PROJECT_CONFIG_FILE_NAME)\n\t\t: scope === \"local\"\n\t\t\t? join(resolve(cwd), LOCAL_CONFIG_FILE_NAME)\n\t\t\t: join(resolve(homeDirectory), USER_CONFIG_FILE_NAME);\n}\n\nasync function readDocument(path: string): Promise<McpConfigDocument> {\n\tlet raw: string;\n\ttry {\n\t\traw = await readFile(path, \"utf8\");\n\t} catch (cause) {\n\t\tif (cause instanceof Error && \"code\" in cause && cause.code === \"ENOENT\") return { mcpServers: {} };\n\t\tthrow cause;\n\t}\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(raw);\n\t} catch (cause) {\n\t\tthrow new Error(`${PROJECT_CONFIG_FILE_NAME}: invalid JSON`, { cause });\n\t}\n\tif (typeof parsed !== \"object\" || parsed === null || Array.isArray(parsed))\n\t\tthrow new Error(`${PROJECT_CONFIG_FILE_NAME}: root value must be an object`);\n\tconst servers = (parsed as Record<string, unknown>).mcpServers;\n\tif (typeof servers !== \"object\" || servers === null || Array.isArray(servers))\n\t\tthrow new Error(`${PROJECT_CONFIG_FILE_NAME}: mcpServers must be an object`);\n\treturn { mcpServers: { ...(servers as Record<string, unknown>) } };\n}\n\nexport async function readMcpConfigScope(\n\tcwd: string,\n\tscope: McpConfigScope,\n\thomeDirectory = homedir(),\n): Promise<McpConfigDocument> {\n\treturn readDocument(mcpConfigPath(cwd, scope, homeDirectory));\n}\n\nexport async function loadMcpConfig(\n\tcwd: string,\n\tenvironment: Environment = process.env,\n): Promise<readonly McpServerConfig[]> {\n\tconst document = await readMcpConfigScope(cwd, \"project\");\n\treturn Object.entries(document.mcpServers)\n\t\t.sort(([left], [right]) => left.localeCompare(right))\n\t\t.map(([id, server]) => parseServer(id, server, cwd, environment));\n}\n\nexport async function loadEffectiveMcpConfig(options: {\n\tcwd: string;\n\thomeDirectory?: string;\n\tenvironment?: Environment;\n\tprojectTrusted: boolean;\n}): Promise<readonly McpServerConfig[]> {\n\tconst environment = options.environment ?? process.env;\n\tconst scopes: readonly McpConfigScope[] = options.projectTrusted ? [\"user\", \"project\", \"local\"] : [\"user\"];\n\tconst merged = new Map<string, unknown>();\n\tfor (const scope of scopes) {\n\t\tconst document = await readMcpConfigScope(options.cwd, scope, options.homeDirectory);\n\t\tfor (const [id, server] of Object.entries(document.mcpServers)) merged.set(id, server);\n\t}\n\treturn [...merged.entries()]\n\t\t.sort(([left], [right]) => left.localeCompare(right))\n\t\t.map(([id, server]) => parseServer(id, server, options.cwd, environment));\n}\n\nfunction redactedEntry(value: unknown): unknown {\n\tif (typeof value !== \"object\" || value === null || Array.isArray(value)) return value;\n\tconst entry = { ...(value as Record<string, unknown>) };\n\tfor (const field of [\"env\", \"headers\"]) {\n\t\tconst values = entry[field];\n\t\tif (typeof values !== \"object\" || values === null || Array.isArray(values)) continue;\n\t\tentry[field] = Object.fromEntries(\n\t\t\tObject.entries(values).map(([name, raw]) => [\n\t\t\t\tname,\n\t\t\t\ttypeof raw === \"string\" && /^\\$\\{[A-Za-z_][A-Za-z0-9_]*\\}$/.test(raw) ? raw : \"[redacted]\",\n\t\t\t]),\n\t\t);\n\t}\n\treturn entry;\n}\n\nexport async function listMcpConfig(\n\tcwd: string,\n\tscope: McpConfigScope,\n\thomeDirectory = homedir(),\n): Promise<readonly { id: string; scope: McpConfigScope; config: unknown }[]> {\n\tconst document = await readMcpConfigScope(cwd, scope, homeDirectory);\n\treturn Object.entries(document.mcpServers)\n\t\t.sort(([left], [right]) => left.localeCompare(right))\n\t\t.map(([id, config]) => ({ id, scope, config: redactedEntry(config) }));\n}\n\nexport async function getMcpConfig(\n\tcwd: string,\n\tid: string,\n\tscope?: McpConfigScope,\n\thomeDirectory = homedir(),\n): Promise<{ id: string; scope: McpConfigScope; config: unknown } | undefined> {\n\tconst scopes = scope ? [scope] : ([\"local\", \"project\", \"user\"] as const);\n\tfor (const current of scopes) {\n\t\tconst document = await readMcpConfigScope(cwd, current, homeDirectory);\n\t\tif (id in document.mcpServers) return { id, scope: current, config: redactedEntry(document.mcpServers[id]) };\n\t}\n\treturn undefined;\n}\n\nasync function writeDocument(\n\tcwd: string,\n\tscope: McpConfigScope,\n\tdocument: McpConfigDocument,\n\thomeDirectory = homedir(),\n): Promise<void> {\n\tconst path = mcpConfigPath(cwd, scope, homeDirectory);\n\tawait mkdir(dirname(path), { recursive: true });\n\tconst temporaryPath = `${path}.${process.pid}.${Date.now()}.tmp`;\n\tawait writeFile(temporaryPath, `${JSON.stringify({ mcpServers: document.mcpServers }, null, 2)}\\n`, \"utf8\");\n\ttry {\n\t\tawait rename(temporaryPath, path);\n\t} catch (cause) {\n\t\tawait unlink(temporaryPath).catch(() => undefined);\n\t\tthrow cause;\n\t}\n}\n\nexport async function addMcpConfig(\n\tcwd: string,\n\tscope: McpConfigScope,\n\tid: string,\n\tconfig: unknown,\n\toptions: { homeDirectory?: string; environment?: Environment } = {},\n): Promise<void> {\n\tif (!SERVER_ID_PATTERN.test(id)) throw new Error(`Invalid MCP server id \"${id}\".`);\n\tconst document = await readMcpConfigScope(cwd, scope, options.homeDirectory);\n\tif (id in document.mcpServers) throw new Error(`MCP server \"${id}\" already exists in ${scope} scope.`);\n\tparseServer(id, config, cwd, options.environment ?? process.env);\n\tawait writeDocument(cwd, scope, { mcpServers: { ...document.mcpServers, [id]: config } }, options.homeDirectory);\n}\n\nexport async function removeMcpConfig(\n\tcwd: string,\n\tscope: McpConfigScope,\n\tid: string,\n\thomeDirectory = homedir(),\n): Promise<void> {\n\tconst document = await readMcpConfigScope(cwd, scope, homeDirectory);\n\tif (!(id in document.mcpServers)) throw new Error(`MCP server \"${id}\" was not found in ${scope} scope.`);\n\tconst { [id]: _removed, ...remaining } = document.mcpServers;\n\tawait writeDocument(cwd, scope, { mcpServers: remaining }, homeDirectory);\n}\n\nexport { SERVER_ID_PATTERN };\n"]}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type McpDiagnostic, McpManager, type McpServerConnectionStatus } from "@di-code/mcp";
|
|
2
|
+
import { createMcpAgentTools } from "./tool-adapter.ts";
|
|
3
|
+
export interface McpLoadDiagnostic {
|
|
4
|
+
readonly serverId?: string;
|
|
5
|
+
readonly stage: "config" | "trust" | McpDiagnostic["stage"];
|
|
6
|
+
readonly message: string;
|
|
7
|
+
}
|
|
8
|
+
export interface McpLoadResult {
|
|
9
|
+
readonly manager: McpManager;
|
|
10
|
+
readonly tools: ReturnType<typeof createMcpAgentTools>;
|
|
11
|
+
readonly diagnostics: readonly McpLoadDiagnostic[];
|
|
12
|
+
}
|
|
13
|
+
/** Loads trusted project MCP servers. A failed server is isolated from all other tools. */
|
|
14
|
+
export declare function loadProjectMcp(options: {
|
|
15
|
+
readonly cwd: string;
|
|
16
|
+
readonly projectTrusted: boolean;
|
|
17
|
+
readonly reservedToolNames: Iterable<string>;
|
|
18
|
+
readonly homeDirectory?: string;
|
|
19
|
+
readonly onServerConnectionStatus?: (status: McpServerConnectionStatus) => void;
|
|
20
|
+
}): Promise<McpLoadResult>;
|
|
21
|
+
//# sourceMappingURL=loader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../src/mcp/loader.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,KAAK,aAAa,EAClB,UAAU,EAEV,KAAK,yBAAyB,EAE9B,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAExD,MAAM,WAAW,iBAAiB;IACjC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC;IAC5D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,aAAa;IAC7B,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC;IAC7B,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC,OAAO,mBAAmB,CAAC,CAAC;IACvD,QAAQ,CAAC,WAAW,EAAE,SAAS,iBAAiB,EAAE,CAAC;CACnD;AAED,2FAA2F;AAC3F,wBAAsB,cAAc,CAAC,OAAO,EAAE;IAC7C,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC,QAAQ,CAAC,iBAAiB,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC7C,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,wBAAwB,CAAC,EAAE,CAAC,MAAM,EAAE,yBAAyB,KAAK,IAAI,CAAC;CAChF,GAAG,OAAO,CAAC,aAAa,CAAC,CAoDzB"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { McpManager, redactMcpDiagnostic, } from "@di-code/mcp";
|
|
2
|
+
import { loadEffectiveMcpConfig, mcpConfigPath, readMcpConfigScope } from "./config.js";
|
|
3
|
+
import { createMcpAgentTools } from "./tool-adapter.js";
|
|
4
|
+
/** Loads trusted project MCP servers. A failed server is isolated from all other tools. */
|
|
5
|
+
export async function loadProjectMcp(options) {
|
|
6
|
+
const manager = new McpManager({ onServerConnectionStatus: options.onServerConnectionStatus });
|
|
7
|
+
if (!options.projectTrusted) {
|
|
8
|
+
try {
|
|
9
|
+
const [local, project] = await Promise.all([
|
|
10
|
+
readMcpConfigScope(options.cwd, "local", options.homeDirectory),
|
|
11
|
+
readMcpConfigScope(options.cwd, "project", options.homeDirectory),
|
|
12
|
+
]);
|
|
13
|
+
const projectDiagnostic = Object.keys(local.mcpServers).length > 0 || Object.keys(project.mcpServers).length > 0
|
|
14
|
+
? {
|
|
15
|
+
stage: "trust",
|
|
16
|
+
message: `${mcpConfigPath(options.cwd, "project", options.homeDirectory)} was skipped because project trust is not granted`,
|
|
17
|
+
}
|
|
18
|
+
: undefined;
|
|
19
|
+
const userConfigurations = await loadEffectiveMcpConfig({
|
|
20
|
+
cwd: options.cwd,
|
|
21
|
+
projectTrusted: false,
|
|
22
|
+
homeDirectory: options.homeDirectory,
|
|
23
|
+
});
|
|
24
|
+
const connected = await manager.connect(userConfigurations);
|
|
25
|
+
return {
|
|
26
|
+
manager,
|
|
27
|
+
tools: createMcpAgentTools(connected.servers, options.reservedToolNames),
|
|
28
|
+
diagnostics: [...(projectDiagnostic ? [projectDiagnostic] : []), ...connected.diagnostics],
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
catch (cause) {
|
|
32
|
+
await manager.close();
|
|
33
|
+
return { manager, tools: [], diagnostics: [{ stage: "config", message: redactMcpDiagnostic(cause) }] };
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
let configurations;
|
|
37
|
+
try {
|
|
38
|
+
configurations = await loadEffectiveMcpConfig({
|
|
39
|
+
cwd: options.cwd,
|
|
40
|
+
projectTrusted: true,
|
|
41
|
+
homeDirectory: options.homeDirectory,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
catch (cause) {
|
|
45
|
+
return { manager, tools: [], diagnostics: [{ stage: "config", message: redactMcpDiagnostic(cause) }] };
|
|
46
|
+
}
|
|
47
|
+
const connected = await manager.connect(configurations);
|
|
48
|
+
try {
|
|
49
|
+
return {
|
|
50
|
+
manager,
|
|
51
|
+
tools: createMcpAgentTools(connected.servers, options.reservedToolNames),
|
|
52
|
+
diagnostics: connected.diagnostics,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
catch (cause) {
|
|
56
|
+
await manager.close();
|
|
57
|
+
return { manager, tools: [], diagnostics: [{ stage: "list_tools", message: redactMcpDiagnostic(cause) }] };
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=loader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loader.js","sourceRoot":"","sources":["../../src/mcp/loader.ts"],"names":[],"mappings":"AAAA,OAAO,EAEN,UAAU,EAGV,mBAAmB,GACnB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,sBAAsB,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACxF,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAcxD,2FAA2F;AAC3F,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,OAMpC;IACA,MAAM,OAAO,GAAG,IAAI,UAAU,CAAC,EAAE,wBAAwB,EAAE,OAAO,CAAC,wBAAwB,EAAE,CAAC,CAAC;IAC/F,IAAI,CAAC,OAAO,CAAC,cAAc,EAAE,CAAC;QAC7B,IAAI,CAAC;YACJ,MAAM,CAAC,KAAK,EAAE,OAAO,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;gBAC1C,kBAAkB,CAAC,OAAO,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,CAAC,aAAa,CAAC;gBAC/D,kBAAkB,CAAC,OAAO,CAAC,GAAG,EAAE,SAAS,EAAE,OAAO,CAAC,aAAa,CAAC;aACjE,CAAC,CAAC;YACH,MAAM,iBAAiB,GACtB,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,MAAM,GAAG,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,MAAM,GAAG,CAAC;gBACrF,CAAC,CAAC;oBACA,KAAK,EAAE,OAAgB;oBACvB,OAAO,EAAE,GAAG,aAAa,CAAC,OAAO,CAAC,GAAG,EAAE,SAAS,EAAE,OAAO,CAAC,aAAa,CAAC,mDAAmD;iBAC3H;gBACF,CAAC,CAAC,SAAS,CAAC;YACd,MAAM,kBAAkB,GAAG,MAAM,sBAAsB,CAAC;gBACvD,GAAG,EAAE,OAAO,CAAC,GAAG;gBAChB,cAAc,EAAE,KAAK;gBACrB,aAAa,EAAE,OAAO,CAAC,aAAa;aACpC,CAAC,CAAC;YACH,MAAM,SAAS,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,kBAAkB,CAAC,CAAC;YAC5D,OAAO;gBACN,OAAO;gBACP,KAAK,EAAE,mBAAmB,CAAC,SAAS,CAAC,OAAO,EAAE,OAAO,CAAC,iBAAiB,CAAC;gBACxE,WAAW,EAAE,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,SAAS,CAAC,WAAW,CAAC;aAC1F,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;YACtB,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,WAAW,EAAE,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,mBAAmB,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC;QACxG,CAAC;IACF,CAAC;IACD,IAAI,cAA0C,CAAC;IAC/C,IAAI,CAAC;QACJ,cAAc,GAAG,MAAM,sBAAsB,CAAC;YAC7C,GAAG,EAAE,OAAO,CAAC,GAAG;YAChB,cAAc,EAAE,IAAI;YACpB,aAAa,EAAE,OAAO,CAAC,aAAa;SACpC,CAAC,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,WAAW,EAAE,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,mBAAmB,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC;IACxG,CAAC;IACD,MAAM,SAAS,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC;IACxD,IAAI,CAAC;QACJ,OAAO;YACN,OAAO;YACP,KAAK,EAAE,mBAAmB,CAAC,SAAS,CAAC,OAAO,EAAE,OAAO,CAAC,iBAAiB,CAAC;YACxE,WAAW,EAAE,SAAS,CAAC,WAAW;SAClC,CAAC;IACH,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;QACtB,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,WAAW,EAAE,CAAC,EAAE,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,mBAAmB,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC;IAC5G,CAAC;AACF,CAAC","sourcesContent":["import {\n\ttype McpDiagnostic,\n\tMcpManager,\n\ttype McpServerConfig,\n\ttype McpServerConnectionStatus,\n\tredactMcpDiagnostic,\n} from \"@di-code/mcp\";\nimport { loadEffectiveMcpConfig, mcpConfigPath, readMcpConfigScope } from \"./config.ts\";\nimport { createMcpAgentTools } from \"./tool-adapter.ts\";\n\nexport interface McpLoadDiagnostic {\n\treadonly serverId?: string;\n\treadonly stage: \"config\" | \"trust\" | McpDiagnostic[\"stage\"];\n\treadonly message: string;\n}\n\nexport interface McpLoadResult {\n\treadonly manager: McpManager;\n\treadonly tools: ReturnType<typeof createMcpAgentTools>;\n\treadonly diagnostics: readonly McpLoadDiagnostic[];\n}\n\n/** Loads trusted project MCP servers. A failed server is isolated from all other tools. */\nexport async function loadProjectMcp(options: {\n\treadonly cwd: string;\n\treadonly projectTrusted: boolean;\n\treadonly reservedToolNames: Iterable<string>;\n\treadonly homeDirectory?: string;\n\treadonly onServerConnectionStatus?: (status: McpServerConnectionStatus) => void;\n}): Promise<McpLoadResult> {\n\tconst manager = new McpManager({ onServerConnectionStatus: options.onServerConnectionStatus });\n\tif (!options.projectTrusted) {\n\t\ttry {\n\t\t\tconst [local, project] = await Promise.all([\n\t\t\t\treadMcpConfigScope(options.cwd, \"local\", options.homeDirectory),\n\t\t\t\treadMcpConfigScope(options.cwd, \"project\", options.homeDirectory),\n\t\t\t]);\n\t\t\tconst projectDiagnostic =\n\t\t\t\tObject.keys(local.mcpServers).length > 0 || Object.keys(project.mcpServers).length > 0\n\t\t\t\t\t? {\n\t\t\t\t\t\t\tstage: \"trust\" as const,\n\t\t\t\t\t\t\tmessage: `${mcpConfigPath(options.cwd, \"project\", options.homeDirectory)} was skipped because project trust is not granted`,\n\t\t\t\t\t\t}\n\t\t\t\t\t: undefined;\n\t\t\tconst userConfigurations = await loadEffectiveMcpConfig({\n\t\t\t\tcwd: options.cwd,\n\t\t\t\tprojectTrusted: false,\n\t\t\t\thomeDirectory: options.homeDirectory,\n\t\t\t});\n\t\t\tconst connected = await manager.connect(userConfigurations);\n\t\t\treturn {\n\t\t\t\tmanager,\n\t\t\t\ttools: createMcpAgentTools(connected.servers, options.reservedToolNames),\n\t\t\t\tdiagnostics: [...(projectDiagnostic ? [projectDiagnostic] : []), ...connected.diagnostics],\n\t\t\t};\n\t\t} catch (cause) {\n\t\t\tawait manager.close();\n\t\t\treturn { manager, tools: [], diagnostics: [{ stage: \"config\", message: redactMcpDiagnostic(cause) }] };\n\t\t}\n\t}\n\tlet configurations: readonly McpServerConfig[];\n\ttry {\n\t\tconfigurations = await loadEffectiveMcpConfig({\n\t\t\tcwd: options.cwd,\n\t\t\tprojectTrusted: true,\n\t\t\thomeDirectory: options.homeDirectory,\n\t\t});\n\t} catch (cause) {\n\t\treturn { manager, tools: [], diagnostics: [{ stage: \"config\", message: redactMcpDiagnostic(cause) }] };\n\t}\n\tconst connected = await manager.connect(configurations);\n\ttry {\n\t\treturn {\n\t\t\tmanager,\n\t\t\ttools: createMcpAgentTools(connected.servers, options.reservedToolNames),\n\t\t\tdiagnostics: connected.diagnostics,\n\t\t};\n\t} catch (cause) {\n\t\tawait manager.close();\n\t\treturn { manager, tools: [], diagnostics: [{ stage: \"list_tools\", message: redactMcpDiagnostic(cause) }] };\n\t}\n}\n"]}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { AgentTool } from "@di-code/agent";
|
|
2
|
+
import { type McpConnectedServer } from "@di-code/mcp";
|
|
3
|
+
/** Converts connected MCP tools to AgentTool values and rejects every namespace collision before registration. */
|
|
4
|
+
export declare function createMcpAgentTools(servers: readonly McpConnectedServer[], reservedNames?: Iterable<string>): readonly AgentTool[];
|
|
5
|
+
//# sourceMappingURL=tool-adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-adapter.d.ts","sourceRoot":"","sources":["../../src/mcp/tool-adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEhD,OAAO,EAEN,KAAK,kBAAkB,EAKvB,MAAM,cAAc,CAAC;AAyJtB,kHAAkH;AAClH,wBAAgB,mBAAmB,CAClC,OAAO,EAAE,SAAS,kBAAkB,EAAE,EACtC,aAAa,GAAE,QAAQ,CAAC,MAAM,CAAM,GAClC,SAAS,SAAS,EAAE,CAuBtB"}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { Type } from "@di-code/ai";
|
|
2
|
+
import { compileMcpInputSchema, McpError, } from "@di-code/mcp";
|
|
3
|
+
const MAX_TEXT_BYTES = 50 * 1024;
|
|
4
|
+
const MAX_IMAGE_BYTES = 5 * 1024 * 1024;
|
|
5
|
+
const MAX_BLOB_BYTES = 5 * 1024 * 1024;
|
|
6
|
+
const TOOL_NAME_PATTERN = /^[A-Za-z0-9_-]{1,64}$/;
|
|
7
|
+
function isImageMimeType(value) {
|
|
8
|
+
return value?.startsWith("image/") === true;
|
|
9
|
+
}
|
|
10
|
+
function truncateText(value) {
|
|
11
|
+
if (Buffer.byteLength(value, "utf8") <= MAX_TEXT_BYTES)
|
|
12
|
+
return value;
|
|
13
|
+
return `${Buffer.from(value, "utf8").subarray(0, MAX_TEXT_BYTES).toString("utf8")}\n[Truncated at ${MAX_TEXT_BYTES} bytes]`;
|
|
14
|
+
}
|
|
15
|
+
function sameJson(left, right) {
|
|
16
|
+
try {
|
|
17
|
+
return JSON.stringify(JSON.parse(left)) === JSON.stringify(right);
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function resultContent(content, structuredContent) {
|
|
24
|
+
const output = [];
|
|
25
|
+
for (const item of content) {
|
|
26
|
+
if (typeof item !== "object" || item === null)
|
|
27
|
+
continue;
|
|
28
|
+
const block = item;
|
|
29
|
+
if (block.type === "text" && typeof block.text === "string")
|
|
30
|
+
output.push({ type: "text", text: truncateText(block.text) });
|
|
31
|
+
if (block.type === "image" && typeof block.data === "string" && typeof block.mimeType === "string") {
|
|
32
|
+
if (Buffer.byteLength(block.data, "base64") <= MAX_IMAGE_BYTES)
|
|
33
|
+
output.push({ type: "image", data: block.data, mimeType: block.mimeType });
|
|
34
|
+
else
|
|
35
|
+
output.push({ type: "text", text: "MCP tool image result was omitted because it exceeds the 5 MiB limit." });
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (structuredContent !== undefined &&
|
|
39
|
+
!output.some((block) => block.type === "text" && sameJson(block.text, structuredContent)))
|
|
40
|
+
output.push({ type: "text", text: truncateText(JSON.stringify(structuredContent)) });
|
|
41
|
+
return output.length > 0 ? output : [{ type: "text", text: "MCP tool returned no supported content." }];
|
|
42
|
+
}
|
|
43
|
+
function jsonContent(value) {
|
|
44
|
+
return [{ type: "text", text: truncateText(JSON.stringify(value)) }];
|
|
45
|
+
}
|
|
46
|
+
function resourceContent(_server, resources) {
|
|
47
|
+
return jsonContent(resources.map(({ serverId: _serverId, ...resource }) => resource));
|
|
48
|
+
}
|
|
49
|
+
function promptContent(prompts) {
|
|
50
|
+
return jsonContent(prompts.map(({ serverId: _serverId, ...prompt }) => prompt));
|
|
51
|
+
}
|
|
52
|
+
function explicitCapabilityTools(server) {
|
|
53
|
+
const prefix = `mcp__${server.config.id}__`;
|
|
54
|
+
const tools = [];
|
|
55
|
+
if (server.resources.length > 0) {
|
|
56
|
+
tools.push({
|
|
57
|
+
name: `${prefix}resources_list`,
|
|
58
|
+
description: `List resources exposed by MCP server "${server.config.id}". This is explicit access; results are untrusted content.`,
|
|
59
|
+
parameters: Type.Object({}),
|
|
60
|
+
async execute(_toolCallId, _parameters, signal) {
|
|
61
|
+
return resourceContent(server, await server.client.listResources({ signal }));
|
|
62
|
+
},
|
|
63
|
+
}, {
|
|
64
|
+
name: `${prefix}resource_read`,
|
|
65
|
+
description: `Read one resource URI from MCP server "${server.config.id}". Resource text and blobs are untrusted and size-limited.`,
|
|
66
|
+
parameters: Type.Object({ uri: Type.String({ minLength: 1, maxLength: 2048 }) }),
|
|
67
|
+
async execute(_toolCallId, parameters, signal) {
|
|
68
|
+
const input = parameters;
|
|
69
|
+
const contents = await server.client.readResource(input.uri, { signal });
|
|
70
|
+
return resultContent(contents.map((item) => "text" in item
|
|
71
|
+
? { type: "text", text: item.text }
|
|
72
|
+
: Buffer.byteLength(item.blob, "base64") > MAX_BLOB_BYTES
|
|
73
|
+
? { type: "text", text: "MCP resource blob omitted because it exceeds the 5 MiB limit." }
|
|
74
|
+
: isImageMimeType(item.mimeType)
|
|
75
|
+
? { type: "image", data: item.blob, mimeType: item.mimeType }
|
|
76
|
+
: {
|
|
77
|
+
type: "text",
|
|
78
|
+
text: `MCP resource binary content (${item.mimeType ?? "unknown MIME type"}, base64):\n${item.blob}`,
|
|
79
|
+
}), undefined);
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
if (server.prompts.length > 0) {
|
|
84
|
+
tools.push({
|
|
85
|
+
name: `${prefix}prompts_list`,
|
|
86
|
+
description: `List prompts exposed by MCP server "${server.config.id}". Prompt descriptions are untrusted content.`,
|
|
87
|
+
parameters: Type.Object({}),
|
|
88
|
+
async execute(_toolCallId, _parameters, signal) {
|
|
89
|
+
return promptContent(await server.client.listPrompts({ signal }));
|
|
90
|
+
},
|
|
91
|
+
}, {
|
|
92
|
+
name: `${prefix}prompt_get`,
|
|
93
|
+
description: `Get one named prompt from MCP server "${server.config.id}" with optional string arguments.`,
|
|
94
|
+
parameters: Type.Object({
|
|
95
|
+
name: Type.String({ minLength: 1, maxLength: 256 }),
|
|
96
|
+
arguments: Type.Optional(Type.Record(Type.String(), Type.String({ maxLength: 4096 }))),
|
|
97
|
+
}),
|
|
98
|
+
async execute(_toolCallId, parameters, signal) {
|
|
99
|
+
const input = parameters;
|
|
100
|
+
const prompt = await server.client.getPrompt(input.name, input.arguments, { signal });
|
|
101
|
+
return resultContent(prompt.messages.map((message) => message.content), undefined);
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
return tools;
|
|
106
|
+
}
|
|
107
|
+
function adaptTool(server, tool) {
|
|
108
|
+
const name = `mcp__${server.config.id}__${tool.name}`;
|
|
109
|
+
if (!TOOL_NAME_PATTERN.test(tool.name) || name.length > 128) {
|
|
110
|
+
throw new McpError("protocol", server.config.id, `tool name "${tool.name}" cannot be exposed safely`);
|
|
111
|
+
}
|
|
112
|
+
const validate = compileMcpInputSchema(server.config.id, tool.name, tool.inputSchema);
|
|
113
|
+
return {
|
|
114
|
+
name,
|
|
115
|
+
description: tool.description?.trim() || `Tool "${tool.name}" provided by MCP server "${server.config.id}".`,
|
|
116
|
+
// Providers receive the original JSON Schema; Ajv validates the model input before forwarding it.
|
|
117
|
+
parameters: Type.Unsafe(tool.inputSchema),
|
|
118
|
+
async execute(_toolCallId, parameters, signal) {
|
|
119
|
+
validate(parameters);
|
|
120
|
+
const result = await server.client.callTool(tool.name, parameters, signal);
|
|
121
|
+
const content = resultContent(result.content, result.structuredContent);
|
|
122
|
+
if (result.isError)
|
|
123
|
+
throw new McpError("tool", server.config.id, content.map((block) => (block.type === "text" ? block.text : "[image]")).join("\n"));
|
|
124
|
+
return content;
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
/** Converts connected MCP tools to AgentTool values and rejects every namespace collision before registration. */
|
|
129
|
+
export function createMcpAgentTools(servers, reservedNames = []) {
|
|
130
|
+
const names = new Set(reservedNames);
|
|
131
|
+
const result = [];
|
|
132
|
+
for (const server of servers) {
|
|
133
|
+
for (const capabilityTool of explicitCapabilityTools(server)) {
|
|
134
|
+
if (names.has(capabilityTool.name))
|
|
135
|
+
throw new McpError("protocol", server.config.id, `tool name "${capabilityTool.name}" conflicts with an existing tool`);
|
|
136
|
+
names.add(capabilityTool.name);
|
|
137
|
+
result.push(capabilityTool);
|
|
138
|
+
}
|
|
139
|
+
for (const tool of server.tools) {
|
|
140
|
+
const adapted = adaptTool(server, tool);
|
|
141
|
+
if (names.has(adapted.name))
|
|
142
|
+
throw new McpError("protocol", server.config.id, `tool name "${adapted.name}" conflicts with an existing tool`);
|
|
143
|
+
names.add(adapted.name);
|
|
144
|
+
result.push(adapted);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return result;
|
|
148
|
+
}
|
|
149
|
+
//# sourceMappingURL=tool-adapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-adapter.js","sourceRoot":"","sources":["../../src/mcp/tool-adapter.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AgentTool } from \"@di-code/agent\";\nimport { type ToolResultContent, Type } from \"@di-code/ai\";\nimport {\n\tcompileMcpInputSchema,\n\ttype McpConnectedServer,\n\tMcpError,\n\ttype McpPrompt,\n\ttype McpResource,\n\ttype McpTool,\n} from \"@di-code/mcp\";\n\nconst MAX_TEXT_BYTES = 50 * 1024;\nconst MAX_IMAGE_BYTES = 5 * 1024 * 1024;\nconst MAX_BLOB_BYTES = 5 * 1024 * 1024;\nconst TOOL_NAME_PATTERN = /^[A-Za-z0-9_-]{1,64}$/;\n\nfunction isImageMimeType(value: string | undefined): value is string {\n\treturn value?.startsWith(\"image/\") === true;\n}\n\nfunction truncateText(value: string): string {\n\tif (Buffer.byteLength(value, \"utf8\") <= MAX_TEXT_BYTES) return value;\n\treturn `${Buffer.from(value, \"utf8\").subarray(0, MAX_TEXT_BYTES).toString(\"utf8\")}\\n[Truncated at ${MAX_TEXT_BYTES} bytes]`;\n}\n\nfunction sameJson(left: string, right: unknown): boolean {\n\ttry {\n\t\treturn JSON.stringify(JSON.parse(left)) === JSON.stringify(right);\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nfunction resultContent(content: readonly unknown[], structuredContent: unknown): ToolResultContent[] {\n\tconst output: ToolResultContent[] = [];\n\tfor (const item of content) {\n\t\tif (typeof item !== \"object\" || item === null) continue;\n\t\tconst block = item as Record<string, unknown>;\n\t\tif (block.type === \"text\" && typeof block.text === \"string\")\n\t\t\toutput.push({ type: \"text\", text: truncateText(block.text) });\n\t\tif (block.type === \"image\" && typeof block.data === \"string\" && typeof block.mimeType === \"string\") {\n\t\t\tif (Buffer.byteLength(block.data, \"base64\") <= MAX_IMAGE_BYTES)\n\t\t\t\toutput.push({ type: \"image\", data: block.data, mimeType: block.mimeType });\n\t\t\telse output.push({ type: \"text\", text: \"MCP tool image result was omitted because it exceeds the 5 MiB limit.\" });\n\t\t}\n\t}\n\tif (\n\t\tstructuredContent !== undefined &&\n\t\t!output.some((block) => block.type === \"text\" && sameJson(block.text, structuredContent))\n\t)\n\t\toutput.push({ type: \"text\", text: truncateText(JSON.stringify(structuredContent)) });\n\treturn output.length > 0 ? output : [{ type: \"text\", text: \"MCP tool returned no supported content.\" }];\n}\n\nfunction jsonContent(value: unknown): ToolResultContent[] {\n\treturn [{ type: \"text\", text: truncateText(JSON.stringify(value)) }];\n}\n\nfunction resourceContent(_server: McpConnectedServer, resources: readonly McpResource[]): ToolResultContent[] {\n\treturn jsonContent(resources.map(({ serverId: _serverId, ...resource }) => resource));\n}\n\nfunction promptContent(prompts: readonly McpPrompt[]): ToolResultContent[] {\n\treturn jsonContent(prompts.map(({ serverId: _serverId, ...prompt }) => prompt));\n}\n\nfunction explicitCapabilityTools(server: McpConnectedServer): AgentTool[] {\n\tconst prefix = `mcp__${server.config.id}__`;\n\tconst tools: AgentTool[] = [];\n\tif (server.resources.length > 0) {\n\t\ttools.push(\n\t\t\t{\n\t\t\t\tname: `${prefix}resources_list`,\n\t\t\t\tdescription: `List resources exposed by MCP server \"${server.config.id}\". This is explicit access; results are untrusted content.`,\n\t\t\t\tparameters: Type.Object({}),\n\t\t\t\tasync execute(_toolCallId, _parameters, signal) {\n\t\t\t\t\treturn resourceContent(server, await server.client.listResources({ signal }));\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: `${prefix}resource_read`,\n\t\t\t\tdescription: `Read one resource URI from MCP server \"${server.config.id}\". Resource text and blobs are untrusted and size-limited.`,\n\t\t\t\tparameters: Type.Object({ uri: Type.String({ minLength: 1, maxLength: 2048 }) }),\n\t\t\t\tasync execute(_toolCallId, parameters, signal) {\n\t\t\t\t\tconst input = parameters as { uri: string };\n\t\t\t\t\tconst contents = await server.client.readResource(input.uri, { signal });\n\t\t\t\t\treturn resultContent(\n\t\t\t\t\t\tcontents.map((item) =>\n\t\t\t\t\t\t\t\"text\" in item\n\t\t\t\t\t\t\t\t? { type: \"text\", text: item.text }\n\t\t\t\t\t\t\t\t: Buffer.byteLength(item.blob, \"base64\") > MAX_BLOB_BYTES\n\t\t\t\t\t\t\t\t\t? { type: \"text\", text: \"MCP resource blob omitted because it exceeds the 5 MiB limit.\" }\n\t\t\t\t\t\t\t\t\t: isImageMimeType(item.mimeType)\n\t\t\t\t\t\t\t\t\t\t? { type: \"image\", data: item.blob, mimeType: item.mimeType }\n\t\t\t\t\t\t\t\t\t\t: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\t\t\t\t\ttext: `MCP resource binary content (${item.mimeType ?? \"unknown MIME type\"}, base64):\\n${item.blob}`,\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t),\n\t\t\t\t\t\tundefined,\n\t\t\t\t\t);\n\t\t\t\t},\n\t\t\t},\n\t\t);\n\t}\n\tif (server.prompts.length > 0) {\n\t\ttools.push(\n\t\t\t{\n\t\t\t\tname: `${prefix}prompts_list`,\n\t\t\t\tdescription: `List prompts exposed by MCP server \"${server.config.id}\". Prompt descriptions are untrusted content.`,\n\t\t\t\tparameters: Type.Object({}),\n\t\t\t\tasync execute(_toolCallId, _parameters, signal) {\n\t\t\t\t\treturn promptContent(await server.client.listPrompts({ signal }));\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: `${prefix}prompt_get`,\n\t\t\t\tdescription: `Get one named prompt from MCP server \"${server.config.id}\" with optional string arguments.`,\n\t\t\t\tparameters: Type.Object({\n\t\t\t\t\tname: Type.String({ minLength: 1, maxLength: 256 }),\n\t\t\t\t\targuments: Type.Optional(Type.Record(Type.String(), Type.String({ maxLength: 4096 }))),\n\t\t\t\t}),\n\t\t\t\tasync execute(_toolCallId, parameters, signal) {\n\t\t\t\t\tconst input = parameters as { name: string; arguments?: Record<string, string> };\n\t\t\t\t\tconst prompt = await server.client.getPrompt(input.name, input.arguments, { signal });\n\t\t\t\t\treturn resultContent(\n\t\t\t\t\t\tprompt.messages.map((message) => message.content),\n\t\t\t\t\t\tundefined,\n\t\t\t\t\t);\n\t\t\t\t},\n\t\t\t},\n\t\t);\n\t}\n\treturn tools;\n}\n\nfunction adaptTool(server: McpConnectedServer, tool: McpTool): AgentTool {\n\tconst name = `mcp__${server.config.id}__${tool.name}`;\n\tif (!TOOL_NAME_PATTERN.test(tool.name) || name.length > 128) {\n\t\tthrow new McpError(\"protocol\", server.config.id, `tool name \"${tool.name}\" cannot be exposed safely`);\n\t}\n\tconst validate = compileMcpInputSchema(server.config.id, tool.name, tool.inputSchema);\n\treturn {\n\t\tname,\n\t\tdescription: tool.description?.trim() || `Tool \"${tool.name}\" provided by MCP server \"${server.config.id}\".`,\n\t\t// Providers receive the original JSON Schema; Ajv validates the model input before forwarding it.\n\t\tparameters: Type.Unsafe<Record<string, unknown>>(tool.inputSchema),\n\t\tasync execute(_toolCallId, parameters, signal): Promise<ToolResultContent[]> {\n\t\t\tvalidate(parameters);\n\t\t\tconst result = await server.client.callTool(tool.name, parameters as Record<string, unknown>, signal);\n\t\t\tconst content = resultContent(result.content, result.structuredContent);\n\t\t\tif (result.isError)\n\t\t\t\tthrow new McpError(\n\t\t\t\t\t\"tool\",\n\t\t\t\t\tserver.config.id,\n\t\t\t\t\tcontent.map((block) => (block.type === \"text\" ? block.text : \"[image]\")).join(\"\\n\"),\n\t\t\t\t);\n\t\t\treturn content;\n\t\t},\n\t};\n}\n\n/** Converts connected MCP tools to AgentTool values and rejects every namespace collision before registration. */\nexport function createMcpAgentTools(\n\tservers: readonly McpConnectedServer[],\n\treservedNames: Iterable<string> = [],\n): readonly AgentTool[] {\n\tconst names = new Set(reservedNames);\n\tconst result: AgentTool[] = [];\n\tfor (const server of servers) {\n\t\tfor (const capabilityTool of explicitCapabilityTools(server)) {\n\t\t\tif (names.has(capabilityTool.name))\n\t\t\t\tthrow new McpError(\n\t\t\t\t\t\"protocol\",\n\t\t\t\t\tserver.config.id,\n\t\t\t\t\t`tool name \"${capabilityTool.name}\" conflicts with an existing tool`,\n\t\t\t\t);\n\t\t\tnames.add(capabilityTool.name);\n\t\t\tresult.push(capabilityTool);\n\t\t}\n\t\tfor (const tool of server.tools) {\n\t\t\tconst adapted = adaptTool(server, tool);\n\t\t\tif (names.has(adapted.name))\n\t\t\t\tthrow new McpError(\"protocol\", server.config.id, `tool name \"${adapted.name}\" conflicts with an existing tool`);\n\t\t\tnames.add(adapted.name);\n\t\t\tresult.push(adapted);\n\t\t}\n\t}\n\treturn result;\n}\n"]}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type { AutocompleteItem, Component } from "@di-code/tui";
|
|
2
|
+
import { type Locale } from "../i18n.ts";
|
|
2
3
|
import type { InteractiveState } from "./interactive-state.ts";
|
|
3
4
|
export interface InteractiveViewState extends InteractiveState {
|
|
4
5
|
readonly model: string;
|
|
5
6
|
readonly theme: "dark" | "light";
|
|
7
|
+
readonly locale?: Locale;
|
|
6
8
|
readonly pasteImageShortcut?: string;
|
|
7
9
|
}
|
|
8
10
|
export declare class InteractiveHeader implements Component {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"interactive-components.d.ts","sourceRoot":"","sources":["../../src/modes/interactive-components.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,SAAS,EAAiB,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"interactive-components.d.ts","sourceRoot":"","sources":["../../src/modes/interactive-components.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,SAAS,EAAiB,MAAM,cAAc,CAAC;AAE/E,OAAO,EAAkB,KAAK,MAAM,EAAa,MAAM,YAAY,CAAC;AAGpE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAE/D,MAAM,WAAW,oBAAqB,SAAQ,gBAAgB;IAC7D,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;IACjC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,kBAAkB,CAAC,EAAE,MAAM,CAAC;CACrC;AAwFD,qBAAa,iBAAkB,YAAW,SAAS;IAClD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA6B;gBAE3C,SAAS,EAAE,MAAM,oBAAoB;IAIjD,UAAU,IAAI,IAAI;IAElB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE;CAW/B;AAED,qBAAa,eAAgB,YAAW,SAAS;IAChD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA6B;gBAE3C,SAAS,EAAE,MAAM,oBAAoB;IAIjD,UAAU,IAAI,IAAI;IAElB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE;CAwG/B;AAED,qBAAa,mBAAoB,YAAW,SAAS;IACpD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA6B;gBAE3C,SAAS,EAAE,MAAM,oBAAoB;IAIjD,UAAU,IAAI,IAAI;IAElB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE;CAU/B;AAED,qBAAa,iBAAkB,YAAW,SAAS;IAClD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA6B;gBAE3C,SAAS,EAAE,MAAM,oBAAoB;IAIjD,UAAU,IAAI,IAAI;IAElB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE;CAkB/B;AAQD,MAAM,WAAW,qBAAqB;IACrC,QAAQ,CAAC,KAAK,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAC5C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACvB;AAED,qBAAa,gBAAiB,YAAW,SAAS;IACjD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA8B;gBAE5C,SAAS,EAAE,MAAM,qBAAqB;IAIlD,UAAU,IAAI,IAAI;IAElB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE;CAgB/B"}
|