@docyrus/docyrus 0.0.29 → 0.0.30
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/main.js +1 -1
- package/main.js.map +1 -1
- package/package.json +1 -1
- package/server-loader.js +780 -56
- package/server-loader.js.map +4 -4
package/server-loader.js
CHANGED
|
@@ -544,7 +544,7 @@ var init_dist = __esm({
|
|
|
544
544
|
});
|
|
545
545
|
if (!chunk) {
|
|
546
546
|
if (i === 1) {
|
|
547
|
-
await new Promise((
|
|
547
|
+
await new Promise((resolve3) => setTimeout(resolve3));
|
|
548
548
|
maxReadCount = 3;
|
|
549
549
|
continue;
|
|
550
550
|
}
|
|
@@ -692,7 +692,7 @@ var init_dist = __esm({
|
|
|
692
692
|
// src/server/server-loader.ts
|
|
693
693
|
var import_node_fs = require("node:fs");
|
|
694
694
|
var import_node_url = require("node:url");
|
|
695
|
-
var
|
|
695
|
+
var import_node_path6 = require("node:path");
|
|
696
696
|
var import_picocolors2 = __toESM(require_picocolors());
|
|
697
697
|
|
|
698
698
|
// src/agent/envStore.ts
|
|
@@ -795,9 +795,9 @@ var AgentEnvStore = class {
|
|
|
795
795
|
|
|
796
796
|
// src/server/agentServer.ts
|
|
797
797
|
var import_node_child_process = require("node:child_process");
|
|
798
|
-
var
|
|
799
|
-
var
|
|
800
|
-
var
|
|
798
|
+
var import_node_crypto3 = require("node:crypto");
|
|
799
|
+
var import_promises5 = require("node:fs/promises");
|
|
800
|
+
var import_node_path5 = require("node:path");
|
|
801
801
|
|
|
802
802
|
// ../../node_modules/.pnpm/hono@4.12.8/node_modules/hono/dist/compose.js
|
|
803
803
|
var compose = (middleware, onError, onNotFound) => {
|
|
@@ -4050,8 +4050,8 @@ var OAuthFlowManager = class {
|
|
|
4050
4050
|
if (state.currentStep) {
|
|
4051
4051
|
return Promise.resolve(state.currentStep);
|
|
4052
4052
|
}
|
|
4053
|
-
return new Promise((
|
|
4054
|
-
state.waitingForStep = { resolve:
|
|
4053
|
+
return new Promise((resolve3) => {
|
|
4054
|
+
state.waitingForStep = { resolve: resolve3 };
|
|
4055
4055
|
});
|
|
4056
4056
|
}
|
|
4057
4057
|
async start(params) {
|
|
@@ -4072,8 +4072,8 @@ var OAuthFlowManager = class {
|
|
|
4072
4072
|
onPrompt: async (prompt) => {
|
|
4073
4073
|
const step = buildPromptStep({ state, type: "prompt", prompt });
|
|
4074
4074
|
this.emitStep(state, step);
|
|
4075
|
-
return await new Promise((
|
|
4076
|
-
state.pendingInput = { type: "prompt", resolve:
|
|
4075
|
+
return await new Promise((resolve3) => {
|
|
4076
|
+
state.pendingInput = { type: "prompt", resolve: resolve3 };
|
|
4077
4077
|
});
|
|
4078
4078
|
},
|
|
4079
4079
|
onManualCodeInput: async () => {
|
|
@@ -4085,8 +4085,8 @@ var OAuthFlowManager = class {
|
|
|
4085
4085
|
}
|
|
4086
4086
|
});
|
|
4087
4087
|
this.emitStep(state, step);
|
|
4088
|
-
return await new Promise((
|
|
4089
|
-
state.pendingInput = { type: "manual-code", resolve:
|
|
4088
|
+
return await new Promise((resolve3) => {
|
|
4089
|
+
state.pendingInput = { type: "manual-code", resolve: resolve3 };
|
|
4090
4090
|
});
|
|
4091
4091
|
},
|
|
4092
4092
|
onProgress: (_message) => {
|
|
@@ -4128,6 +4128,535 @@ var OAuthFlowManager = class {
|
|
|
4128
4128
|
}
|
|
4129
4129
|
};
|
|
4130
4130
|
|
|
4131
|
+
// src/server/mcpConfigService.ts
|
|
4132
|
+
var import_node_crypto2 = require("node:crypto");
|
|
4133
|
+
var import_promises3 = require("node:fs/promises");
|
|
4134
|
+
var import_node_os = require("node:os");
|
|
4135
|
+
var import_node_path3 = require("node:path");
|
|
4136
|
+
var PROJECT_CONFIG_NAME = ".pi/mcp.json";
|
|
4137
|
+
var IMPORT_PATHS = {
|
|
4138
|
+
"cursor": (0, import_node_path3.join)((0, import_node_os.homedir)(), ".cursor", "mcp.json"),
|
|
4139
|
+
"claude-code": (0, import_node_path3.join)((0, import_node_os.homedir)(), ".claude", "claude_desktop_config.json"),
|
|
4140
|
+
"claude-desktop": (0, import_node_path3.join)((0, import_node_os.homedir)(), "Library", "Application Support", "Claude", "claude_desktop_config.json"),
|
|
4141
|
+
"codex": (0, import_node_path3.join)((0, import_node_os.homedir)(), ".codex", "config.json"),
|
|
4142
|
+
"windsurf": (0, import_node_path3.join)((0, import_node_os.homedir)(), ".windsurf", "mcp.json"),
|
|
4143
|
+
"vscode": ".vscode/mcp.json"
|
|
4144
|
+
};
|
|
4145
|
+
function getMcpConfigPath(agentDir) {
|
|
4146
|
+
return (0, import_node_path3.join)(agentDir, "mcp.json");
|
|
4147
|
+
}
|
|
4148
|
+
function getMcpCachePath(agentDir) {
|
|
4149
|
+
return (0, import_node_path3.join)(agentDir, "mcp-cache.json");
|
|
4150
|
+
}
|
|
4151
|
+
var SERVER_NAME_REGEX = /^[a-zA-Z0-9][a-zA-Z0-9_-]*$/;
|
|
4152
|
+
function validateServerName(name) {
|
|
4153
|
+
if (!name || typeof name !== "string") {
|
|
4154
|
+
return { ok: false, error: "Server name is required" };
|
|
4155
|
+
}
|
|
4156
|
+
if (!SERVER_NAME_REGEX.test(name)) {
|
|
4157
|
+
return { ok: false, error: "Server name must start with alphanumeric and contain only alphanumeric, hyphens, and underscores" };
|
|
4158
|
+
}
|
|
4159
|
+
return { ok: true };
|
|
4160
|
+
}
|
|
4161
|
+
function validateServerEntry(entry) {
|
|
4162
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
|
|
4163
|
+
return { ok: false, error: "Server entry must be an object" };
|
|
4164
|
+
}
|
|
4165
|
+
const obj = entry;
|
|
4166
|
+
const hasCommand = typeof obj.command === "string" && obj.command.length > 0;
|
|
4167
|
+
const hasUrl = typeof obj.url === "string" && obj.url.length > 0;
|
|
4168
|
+
if (!hasCommand && !hasUrl) {
|
|
4169
|
+
return { ok: false, error: "Server entry must have either 'command' (stdio) or 'url' (http)" };
|
|
4170
|
+
}
|
|
4171
|
+
if (obj.lifecycle !== void 0) {
|
|
4172
|
+
if (!["keep-alive", "lazy", "eager"].includes(obj.lifecycle)) {
|
|
4173
|
+
return { ok: false, error: "lifecycle must be 'keep-alive', 'lazy', or 'eager'" };
|
|
4174
|
+
}
|
|
4175
|
+
}
|
|
4176
|
+
if (obj.auth !== void 0) {
|
|
4177
|
+
if (!["oauth", "bearer"].includes(obj.auth)) {
|
|
4178
|
+
return { ok: false, error: "auth must be 'oauth' or 'bearer'" };
|
|
4179
|
+
}
|
|
4180
|
+
}
|
|
4181
|
+
if (obj.args !== void 0) {
|
|
4182
|
+
if (!Array.isArray(obj.args) || !obj.args.every((a) => typeof a === "string")) {
|
|
4183
|
+
return { ok: false, error: "args must be an array of strings" };
|
|
4184
|
+
}
|
|
4185
|
+
}
|
|
4186
|
+
if (obj.env !== void 0) {
|
|
4187
|
+
if (!isStringRecord(obj.env)) {
|
|
4188
|
+
return { ok: false, error: "env must be a Record<string, string>" };
|
|
4189
|
+
}
|
|
4190
|
+
}
|
|
4191
|
+
if (obj.headers !== void 0) {
|
|
4192
|
+
if (!isStringRecord(obj.headers)) {
|
|
4193
|
+
return { ok: false, error: "headers must be a Record<string, string>" };
|
|
4194
|
+
}
|
|
4195
|
+
}
|
|
4196
|
+
return { ok: true, value: obj };
|
|
4197
|
+
}
|
|
4198
|
+
function isStringRecord(value) {
|
|
4199
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
4200
|
+
return false;
|
|
4201
|
+
}
|
|
4202
|
+
return Object.values(value).every((v) => typeof v === "string");
|
|
4203
|
+
}
|
|
4204
|
+
function detectTransport(entry) {
|
|
4205
|
+
return entry.url ? "http" : "stdio";
|
|
4206
|
+
}
|
|
4207
|
+
function redactSensitiveFields(entry) {
|
|
4208
|
+
const copy = { ...entry };
|
|
4209
|
+
if (copy.bearerToken) {
|
|
4210
|
+
copy.bearerToken = copy.bearerToken.slice(0, 4) + "****";
|
|
4211
|
+
}
|
|
4212
|
+
if (copy.env) {
|
|
4213
|
+
const redactedEnv = { ...copy.env };
|
|
4214
|
+
for (const key of Object.keys(redactedEnv)) {
|
|
4215
|
+
const lower = key.toLowerCase();
|
|
4216
|
+
if (lower.includes("token") || lower.includes("secret") || lower.includes("key") || lower.includes("password")) {
|
|
4217
|
+
const val = redactedEnv[key];
|
|
4218
|
+
redactedEnv[key] = val.length > 4 ? val.slice(0, 4) + "****" : "****";
|
|
4219
|
+
}
|
|
4220
|
+
}
|
|
4221
|
+
copy.env = redactedEnv;
|
|
4222
|
+
}
|
|
4223
|
+
return copy;
|
|
4224
|
+
}
|
|
4225
|
+
function parseConfigFile(raw2) {
|
|
4226
|
+
if (!raw2 || typeof raw2 !== "object") {
|
|
4227
|
+
return { mcpServers: {} };
|
|
4228
|
+
}
|
|
4229
|
+
const obj = raw2;
|
|
4230
|
+
const servers = obj.mcpServers ?? obj["mcp-servers"] ?? {};
|
|
4231
|
+
if (typeof servers !== "object" || servers === null || Array.isArray(servers)) {
|
|
4232
|
+
return { mcpServers: {} };
|
|
4233
|
+
}
|
|
4234
|
+
return {
|
|
4235
|
+
mcpServers: servers,
|
|
4236
|
+
imports: Array.isArray(obj.imports) ? obj.imports : void 0,
|
|
4237
|
+
settings: obj.settings
|
|
4238
|
+
};
|
|
4239
|
+
}
|
|
4240
|
+
function extractServers(config, kind) {
|
|
4241
|
+
if (!config || typeof config !== "object") {
|
|
4242
|
+
return {};
|
|
4243
|
+
}
|
|
4244
|
+
const obj = config;
|
|
4245
|
+
let servers;
|
|
4246
|
+
switch (kind) {
|
|
4247
|
+
case "claude-desktop":
|
|
4248
|
+
case "claude-code":
|
|
4249
|
+
case "codex":
|
|
4250
|
+
servers = obj.mcpServers;
|
|
4251
|
+
break;
|
|
4252
|
+
case "cursor":
|
|
4253
|
+
case "windsurf":
|
|
4254
|
+
case "vscode":
|
|
4255
|
+
servers = obj.mcpServers ?? obj["mcp-servers"];
|
|
4256
|
+
break;
|
|
4257
|
+
default:
|
|
4258
|
+
return {};
|
|
4259
|
+
}
|
|
4260
|
+
if (!servers || typeof servers !== "object" || Array.isArray(servers)) {
|
|
4261
|
+
return {};
|
|
4262
|
+
}
|
|
4263
|
+
return servers;
|
|
4264
|
+
}
|
|
4265
|
+
async function readJsonFile(filePath) {
|
|
4266
|
+
try {
|
|
4267
|
+
const content = await (0, import_promises3.readFile)(filePath, "utf-8");
|
|
4268
|
+
return JSON.parse(content);
|
|
4269
|
+
} catch {
|
|
4270
|
+
return null;
|
|
4271
|
+
}
|
|
4272
|
+
}
|
|
4273
|
+
async function loadMcpServerConfig(agentDir, cwd) {
|
|
4274
|
+
const configPath = getMcpConfigPath(agentDir);
|
|
4275
|
+
let config = { mcpServers: {} };
|
|
4276
|
+
const raw2 = await readJsonFile(configPath);
|
|
4277
|
+
if (raw2) {
|
|
4278
|
+
config = parseConfigFile(raw2);
|
|
4279
|
+
}
|
|
4280
|
+
if (config.imports?.length) {
|
|
4281
|
+
for (const importKind of config.imports) {
|
|
4282
|
+
const importPath = IMPORT_PATHS[importKind];
|
|
4283
|
+
if (!importPath) {
|
|
4284
|
+
continue;
|
|
4285
|
+
}
|
|
4286
|
+
const fullPath = importPath.startsWith(".") ? (0, import_node_path3.resolve)(cwd, importPath) : importPath;
|
|
4287
|
+
const imported = await readJsonFile(fullPath);
|
|
4288
|
+
if (!imported) {
|
|
4289
|
+
continue;
|
|
4290
|
+
}
|
|
4291
|
+
const servers = extractServers(imported, importKind);
|
|
4292
|
+
for (const [name, def] of Object.entries(servers)) {
|
|
4293
|
+
if (!config.mcpServers[name]) {
|
|
4294
|
+
config.mcpServers[name] = def;
|
|
4295
|
+
}
|
|
4296
|
+
}
|
|
4297
|
+
}
|
|
4298
|
+
}
|
|
4299
|
+
const projectPath = (0, import_node_path3.resolve)(cwd, PROJECT_CONFIG_NAME);
|
|
4300
|
+
if (projectPath !== configPath) {
|
|
4301
|
+
const projectRaw = await readJsonFile(projectPath);
|
|
4302
|
+
if (projectRaw) {
|
|
4303
|
+
const validated = parseConfigFile(projectRaw);
|
|
4304
|
+
config.mcpServers = { ...config.mcpServers, ...validated.mcpServers };
|
|
4305
|
+
if (validated.settings) {
|
|
4306
|
+
config.settings = { ...config.settings, ...validated.settings };
|
|
4307
|
+
}
|
|
4308
|
+
}
|
|
4309
|
+
}
|
|
4310
|
+
return config;
|
|
4311
|
+
}
|
|
4312
|
+
var CACHE_VERSION = 1;
|
|
4313
|
+
var CACHE_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
4314
|
+
async function loadMcpServerCache(agentDir) {
|
|
4315
|
+
const cachePath = getMcpCachePath(agentDir);
|
|
4316
|
+
const raw2 = await readJsonFile(cachePath);
|
|
4317
|
+
if (!raw2 || typeof raw2 !== "object") {
|
|
4318
|
+
return null;
|
|
4319
|
+
}
|
|
4320
|
+
const obj = raw2;
|
|
4321
|
+
if (obj.version !== CACHE_VERSION) {
|
|
4322
|
+
return null;
|
|
4323
|
+
}
|
|
4324
|
+
if (!obj.servers || typeof obj.servers !== "object") {
|
|
4325
|
+
return null;
|
|
4326
|
+
}
|
|
4327
|
+
return raw2;
|
|
4328
|
+
}
|
|
4329
|
+
function computeServerHash(definition) {
|
|
4330
|
+
const identity = {
|
|
4331
|
+
command: definition.command,
|
|
4332
|
+
args: definition.args,
|
|
4333
|
+
env: definition.env,
|
|
4334
|
+
cwd: definition.cwd,
|
|
4335
|
+
url: definition.url,
|
|
4336
|
+
headers: definition.headers,
|
|
4337
|
+
auth: definition.auth,
|
|
4338
|
+
bearerToken: definition.bearerToken,
|
|
4339
|
+
bearerTokenEnv: definition.bearerTokenEnv,
|
|
4340
|
+
exposeResources: definition.exposeResources
|
|
4341
|
+
};
|
|
4342
|
+
const normalized = stableStringify(identity);
|
|
4343
|
+
return (0, import_node_crypto2.createHash)("sha256").update(normalized).digest("hex");
|
|
4344
|
+
}
|
|
4345
|
+
function isServerCacheValid(entry, definition, maxAgeMs = CACHE_MAX_AGE_MS) {
|
|
4346
|
+
if (!entry || entry.configHash !== computeServerHash(definition)) {
|
|
4347
|
+
return false;
|
|
4348
|
+
}
|
|
4349
|
+
if (!entry.cachedAt || typeof entry.cachedAt !== "number") {
|
|
4350
|
+
return false;
|
|
4351
|
+
}
|
|
4352
|
+
if (maxAgeMs > 0 && Date.now() - entry.cachedAt > maxAgeMs) {
|
|
4353
|
+
return false;
|
|
4354
|
+
}
|
|
4355
|
+
return true;
|
|
4356
|
+
}
|
|
4357
|
+
function stableStringify(value) {
|
|
4358
|
+
if (value === null || value === void 0 || typeof value !== "object") {
|
|
4359
|
+
const serialized = JSON.stringify(value);
|
|
4360
|
+
return serialized === void 0 ? "undefined" : serialized;
|
|
4361
|
+
}
|
|
4362
|
+
if (Array.isArray(value)) {
|
|
4363
|
+
return `[${value.map((v) => stableStringify(v)).join(",")}]`;
|
|
4364
|
+
}
|
|
4365
|
+
const obj = value;
|
|
4366
|
+
const keys = Object.keys(obj).sort();
|
|
4367
|
+
return `{${keys.map((k) => `${JSON.stringify(k)}:${stableStringify(obj[k])}`).join(",")}}`;
|
|
4368
|
+
}
|
|
4369
|
+
async function getServerProvenance(agentDir, cwd) {
|
|
4370
|
+
const provenance = /* @__PURE__ */ new Map();
|
|
4371
|
+
const userPath = getMcpConfigPath(agentDir);
|
|
4372
|
+
const userRaw = await readJsonFile(userPath);
|
|
4373
|
+
const userConfig = userRaw ? parseConfigFile(userRaw) : { mcpServers: {} };
|
|
4374
|
+
for (const name of Object.keys(userConfig.mcpServers)) {
|
|
4375
|
+
provenance.set(name, { path: userPath, kind: "user" });
|
|
4376
|
+
}
|
|
4377
|
+
if (userConfig.imports?.length) {
|
|
4378
|
+
for (const importKind of userConfig.imports) {
|
|
4379
|
+
const importPath = IMPORT_PATHS[importKind];
|
|
4380
|
+
if (!importPath) {
|
|
4381
|
+
continue;
|
|
4382
|
+
}
|
|
4383
|
+
const fullPath = importPath.startsWith(".") ? (0, import_node_path3.resolve)(cwd, importPath) : importPath;
|
|
4384
|
+
const imported = await readJsonFile(fullPath);
|
|
4385
|
+
if (!imported) {
|
|
4386
|
+
continue;
|
|
4387
|
+
}
|
|
4388
|
+
const servers = extractServers(imported, importKind);
|
|
4389
|
+
for (const name of Object.keys(servers)) {
|
|
4390
|
+
if (!provenance.has(name)) {
|
|
4391
|
+
provenance.set(name, { path: userPath, kind: "import", importKind });
|
|
4392
|
+
}
|
|
4393
|
+
}
|
|
4394
|
+
}
|
|
4395
|
+
}
|
|
4396
|
+
const projectPath = (0, import_node_path3.resolve)(cwd, PROJECT_CONFIG_NAME);
|
|
4397
|
+
if (projectPath !== userPath) {
|
|
4398
|
+
const projectRaw = await readJsonFile(projectPath);
|
|
4399
|
+
if (projectRaw) {
|
|
4400
|
+
const projectConfig = parseConfigFile(projectRaw);
|
|
4401
|
+
for (const name of Object.keys(projectConfig.mcpServers)) {
|
|
4402
|
+
provenance.set(name, { path: projectPath, kind: "project" });
|
|
4403
|
+
}
|
|
4404
|
+
}
|
|
4405
|
+
}
|
|
4406
|
+
return provenance;
|
|
4407
|
+
}
|
|
4408
|
+
async function readUserConfig(agentDir) {
|
|
4409
|
+
const configPath = getMcpConfigPath(agentDir);
|
|
4410
|
+
let raw2 = {};
|
|
4411
|
+
const parsed = await readJsonFile(configPath);
|
|
4412
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
4413
|
+
raw2 = parsed;
|
|
4414
|
+
}
|
|
4415
|
+
const config = parseConfigFile(raw2);
|
|
4416
|
+
return { raw: raw2, config };
|
|
4417
|
+
}
|
|
4418
|
+
async function writeUserConfig(agentDir, raw2) {
|
|
4419
|
+
const configPath = getMcpConfigPath(agentDir);
|
|
4420
|
+
await (0, import_promises3.mkdir)((0, import_node_path3.dirname)(configPath), { recursive: true });
|
|
4421
|
+
const tmpPath = `${configPath}.${process.pid}.tmp`;
|
|
4422
|
+
await (0, import_promises3.writeFile)(tmpPath, JSON.stringify(raw2, null, 2) + "\n", "utf-8");
|
|
4423
|
+
await (0, import_promises3.rename)(tmpPath, configPath);
|
|
4424
|
+
}
|
|
4425
|
+
async function addMcpServer(agentDir, name, entry) {
|
|
4426
|
+
const { raw: raw2, config } = await readUserConfig(agentDir);
|
|
4427
|
+
if (config.mcpServers[name]) {
|
|
4428
|
+
throw new Error(`Server "${name}" already exists`);
|
|
4429
|
+
}
|
|
4430
|
+
const servers = raw2.mcpServers ?? raw2["mcp-servers"] ?? {};
|
|
4431
|
+
const key = raw2["mcp-servers"] && !raw2.mcpServers ? "mcp-servers" : "mcpServers";
|
|
4432
|
+
servers[name] = entry;
|
|
4433
|
+
raw2[key] = servers;
|
|
4434
|
+
await writeUserConfig(agentDir, raw2);
|
|
4435
|
+
}
|
|
4436
|
+
async function updateMcpServer(agentDir, name, entry) {
|
|
4437
|
+
const { raw: raw2 } = await readUserConfig(agentDir);
|
|
4438
|
+
const servers = raw2.mcpServers ?? raw2["mcp-servers"] ?? {};
|
|
4439
|
+
const key = raw2["mcp-servers"] && !raw2.mcpServers ? "mcp-servers" : "mcpServers";
|
|
4440
|
+
if (!servers[name]) {
|
|
4441
|
+
throw new Error(`Server "${name}" not found in user config`);
|
|
4442
|
+
}
|
|
4443
|
+
servers[name] = entry;
|
|
4444
|
+
raw2[key] = servers;
|
|
4445
|
+
await writeUserConfig(agentDir, raw2);
|
|
4446
|
+
}
|
|
4447
|
+
async function removeMcpServer(agentDir, name) {
|
|
4448
|
+
const { raw: raw2 } = await readUserConfig(agentDir);
|
|
4449
|
+
const servers = raw2.mcpServers ?? raw2["mcp-servers"] ?? {};
|
|
4450
|
+
const key = raw2["mcp-servers"] && !raw2.mcpServers ? "mcp-servers" : "mcpServers";
|
|
4451
|
+
if (!servers[name]) {
|
|
4452
|
+
throw new Error(`Server "${name}" not found in user config`);
|
|
4453
|
+
}
|
|
4454
|
+
delete servers[name];
|
|
4455
|
+
raw2[key] = servers;
|
|
4456
|
+
await writeUserConfig(agentDir, raw2);
|
|
4457
|
+
}
|
|
4458
|
+
function getServerPrefix(serverName, mode) {
|
|
4459
|
+
if (mode === "none") {
|
|
4460
|
+
return "";
|
|
4461
|
+
}
|
|
4462
|
+
if (mode === "short") {
|
|
4463
|
+
let short = serverName.replace(/-?mcp$/i, "").replace(/-/g, "_");
|
|
4464
|
+
if (!short) {
|
|
4465
|
+
short = "mcp";
|
|
4466
|
+
}
|
|
4467
|
+
return short;
|
|
4468
|
+
}
|
|
4469
|
+
return serverName.replace(/-/g, "_");
|
|
4470
|
+
}
|
|
4471
|
+
function formatToolName(toolName, serverName, prefix) {
|
|
4472
|
+
const p = getServerPrefix(serverName, prefix);
|
|
4473
|
+
return p ? `${p}_${toolName}` : toolName;
|
|
4474
|
+
}
|
|
4475
|
+
|
|
4476
|
+
// src/server/skillsService.ts
|
|
4477
|
+
var import_promises4 = require("node:fs/promises");
|
|
4478
|
+
var import_node_path4 = require("node:path");
|
|
4479
|
+
function parseFrontmatter(content) {
|
|
4480
|
+
const frontmatter = {};
|
|
4481
|
+
if (!content.startsWith("---")) {
|
|
4482
|
+
return { frontmatter, body: content };
|
|
4483
|
+
}
|
|
4484
|
+
const endIndex = content.indexOf("\n---", 3);
|
|
4485
|
+
if (endIndex === -1) {
|
|
4486
|
+
return { frontmatter, body: content };
|
|
4487
|
+
}
|
|
4488
|
+
const fmBlock = content.slice(4, endIndex);
|
|
4489
|
+
for (const line of fmBlock.split("\n")) {
|
|
4490
|
+
const colonIndex = line.indexOf(":");
|
|
4491
|
+
if (colonIndex <= 0) {
|
|
4492
|
+
continue;
|
|
4493
|
+
}
|
|
4494
|
+
const key = line.slice(0, colonIndex).trim();
|
|
4495
|
+
const value = line.slice(colonIndex + 1).trim();
|
|
4496
|
+
if (key) {
|
|
4497
|
+
frontmatter[key] = value;
|
|
4498
|
+
}
|
|
4499
|
+
}
|
|
4500
|
+
const body = content.slice(endIndex + 4).trimStart();
|
|
4501
|
+
return { frontmatter, body };
|
|
4502
|
+
}
|
|
4503
|
+
async function dirExists(path) {
|
|
4504
|
+
try {
|
|
4505
|
+
const s = await (0, import_promises4.stat)(path);
|
|
4506
|
+
return s.isDirectory();
|
|
4507
|
+
} catch {
|
|
4508
|
+
return false;
|
|
4509
|
+
}
|
|
4510
|
+
}
|
|
4511
|
+
async function listSkills(agentDir) {
|
|
4512
|
+
const skillsDir = (0, import_node_path4.join)(agentDir, "skills");
|
|
4513
|
+
let entries;
|
|
4514
|
+
try {
|
|
4515
|
+
entries = await (0, import_promises4.readdir)(skillsDir, { withFileTypes: true });
|
|
4516
|
+
} catch {
|
|
4517
|
+
return [];
|
|
4518
|
+
}
|
|
4519
|
+
const skills = [];
|
|
4520
|
+
for (const entry of entries) {
|
|
4521
|
+
if (!entry.isDirectory()) {
|
|
4522
|
+
continue;
|
|
4523
|
+
}
|
|
4524
|
+
const skillDir = (0, import_node_path4.join)(skillsDir, entry.name);
|
|
4525
|
+
const skillMdPath = (0, import_node_path4.join)(skillDir, "SKILL.md");
|
|
4526
|
+
let content;
|
|
4527
|
+
try {
|
|
4528
|
+
content = await (0, import_promises4.readFile)(skillMdPath, "utf-8");
|
|
4529
|
+
} catch {
|
|
4530
|
+
continue;
|
|
4531
|
+
}
|
|
4532
|
+
const parsed = parseFrontmatter(content);
|
|
4533
|
+
const hasReferences = await dirExists((0, import_node_path4.join)(skillDir, "references"));
|
|
4534
|
+
skills.push({
|
|
4535
|
+
name: parsed.frontmatter.name || entry.name,
|
|
4536
|
+
description: parsed.frontmatter.description || null,
|
|
4537
|
+
directory: entry.name,
|
|
4538
|
+
hasReferences,
|
|
4539
|
+
userInvocable: parsed.frontmatter["user-invocable"] === "true",
|
|
4540
|
+
frontmatter: parsed.frontmatter
|
|
4541
|
+
});
|
|
4542
|
+
}
|
|
4543
|
+
return skills.sort((a, b) => a.name.localeCompare(b.name));
|
|
4544
|
+
}
|
|
4545
|
+
async function getSkillDetail(agentDir, skillName) {
|
|
4546
|
+
const skillsDir = (0, import_node_path4.join)(agentDir, "skills");
|
|
4547
|
+
let entries;
|
|
4548
|
+
try {
|
|
4549
|
+
entries = await (0, import_promises4.readdir)(skillsDir, { withFileTypes: true });
|
|
4550
|
+
} catch {
|
|
4551
|
+
return null;
|
|
4552
|
+
}
|
|
4553
|
+
for (const entry of entries) {
|
|
4554
|
+
if (!entry.isDirectory()) {
|
|
4555
|
+
continue;
|
|
4556
|
+
}
|
|
4557
|
+
const skillDir = (0, import_node_path4.join)(skillsDir, entry.name);
|
|
4558
|
+
const skillMdPath = (0, import_node_path4.join)(skillDir, "SKILL.md");
|
|
4559
|
+
let content;
|
|
4560
|
+
try {
|
|
4561
|
+
content = await (0, import_promises4.readFile)(skillMdPath, "utf-8");
|
|
4562
|
+
} catch {
|
|
4563
|
+
continue;
|
|
4564
|
+
}
|
|
4565
|
+
const parsed = parseFrontmatter(content);
|
|
4566
|
+
const fmName = parsed.frontmatter.name || entry.name;
|
|
4567
|
+
if (entry.name === skillName || fmName === skillName) {
|
|
4568
|
+
const hasReferences = await dirExists((0, import_node_path4.join)(skillDir, "references"));
|
|
4569
|
+
return {
|
|
4570
|
+
skill: {
|
|
4571
|
+
name: fmName,
|
|
4572
|
+
description: parsed.frontmatter.description || null,
|
|
4573
|
+
directory: entry.name,
|
|
4574
|
+
hasReferences,
|
|
4575
|
+
userInvocable: parsed.frontmatter["user-invocable"] === "true",
|
|
4576
|
+
frontmatter: parsed.frontmatter
|
|
4577
|
+
},
|
|
4578
|
+
content
|
|
4579
|
+
};
|
|
4580
|
+
}
|
|
4581
|
+
}
|
|
4582
|
+
return null;
|
|
4583
|
+
}
|
|
4584
|
+
|
|
4585
|
+
// src/server/toolsService.ts
|
|
4586
|
+
var BUILT_IN_TOOLS = {
|
|
4587
|
+
agent: [
|
|
4588
|
+
{ name: "read", description: "Read file contents" },
|
|
4589
|
+
{ name: "bash", description: "Execute shell commands" },
|
|
4590
|
+
{ name: "grep", description: "Search file contents" },
|
|
4591
|
+
{ name: "find", description: "Find files by name pattern" },
|
|
4592
|
+
{ name: "ls", description: "List directory contents" }
|
|
4593
|
+
],
|
|
4594
|
+
coder: [
|
|
4595
|
+
{ name: "read", description: "Read file contents" },
|
|
4596
|
+
{ name: "bash", description: "Execute shell commands" },
|
|
4597
|
+
{ name: "edit", description: "Edit file contents" },
|
|
4598
|
+
{ name: "write", description: "Write file contents" },
|
|
4599
|
+
{ name: "grep", description: "Search file contents" },
|
|
4600
|
+
{ name: "find", description: "Find files by name pattern" },
|
|
4601
|
+
{ name: "ls", description: "List directory contents" }
|
|
4602
|
+
]
|
|
4603
|
+
};
|
|
4604
|
+
async function listAllTools(params) {
|
|
4605
|
+
const { profile, agentDir, cwd } = params;
|
|
4606
|
+
const tools = [];
|
|
4607
|
+
const builtIn = BUILT_IN_TOOLS[profile] ?? BUILT_IN_TOOLS.coder;
|
|
4608
|
+
for (const tool of builtIn) {
|
|
4609
|
+
tools.push({
|
|
4610
|
+
name: tool.name,
|
|
4611
|
+
description: tool.description,
|
|
4612
|
+
source: "built-in"
|
|
4613
|
+
});
|
|
4614
|
+
}
|
|
4615
|
+
let config;
|
|
4616
|
+
let cache;
|
|
4617
|
+
try {
|
|
4618
|
+
[config, cache] = await Promise.all([
|
|
4619
|
+
loadMcpServerConfig(agentDir, cwd),
|
|
4620
|
+
loadMcpServerCache(agentDir)
|
|
4621
|
+
]);
|
|
4622
|
+
} catch {
|
|
4623
|
+
return tools;
|
|
4624
|
+
}
|
|
4625
|
+
const serverNames = Object.keys(config.mcpServers);
|
|
4626
|
+
if (serverNames.length === 0) {
|
|
4627
|
+
return tools;
|
|
4628
|
+
}
|
|
4629
|
+
tools.push({
|
|
4630
|
+
name: "mcp",
|
|
4631
|
+
description: "Call an MCP server tool",
|
|
4632
|
+
source: "built-in"
|
|
4633
|
+
});
|
|
4634
|
+
if (!cache) {
|
|
4635
|
+
return tools;
|
|
4636
|
+
}
|
|
4637
|
+
const prefix = config.settings?.toolPrefix ?? "server";
|
|
4638
|
+
for (const serverName of serverNames) {
|
|
4639
|
+
const serverCache = cache.servers[serverName];
|
|
4640
|
+
if (!serverCache) {
|
|
4641
|
+
continue;
|
|
4642
|
+
}
|
|
4643
|
+
for (const tool of serverCache.tools ?? []) {
|
|
4644
|
+
if (!tool?.name) {
|
|
4645
|
+
continue;
|
|
4646
|
+
}
|
|
4647
|
+
tools.push({
|
|
4648
|
+
name: formatToolName(tool.name, serverName, prefix),
|
|
4649
|
+
description: tool.description ?? null,
|
|
4650
|
+
source: `mcp:${serverName}`,
|
|
4651
|
+
serverName,
|
|
4652
|
+
originalName: tool.name,
|
|
4653
|
+
inputSchema: tool.inputSchema
|
|
4654
|
+
});
|
|
4655
|
+
}
|
|
4656
|
+
}
|
|
4657
|
+
return tools;
|
|
4658
|
+
}
|
|
4659
|
+
|
|
4131
4660
|
// src/server/agentServer.ts
|
|
4132
4661
|
function generateMessageId() {
|
|
4133
4662
|
return `msg_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
|
|
@@ -4185,7 +4714,7 @@ async function waitForIdle(session, timeoutMs = 3e4) {
|
|
|
4185
4714
|
if (!session.isStreaming) {
|
|
4186
4715
|
return;
|
|
4187
4716
|
}
|
|
4188
|
-
return new Promise((
|
|
4717
|
+
return new Promise((resolve3, reject) => {
|
|
4189
4718
|
const timer = setTimeout(() => {
|
|
4190
4719
|
unsubscribe();
|
|
4191
4720
|
reject(new Error("Timed out waiting for session to become idle"));
|
|
@@ -4194,13 +4723,13 @@ async function waitForIdle(session, timeoutMs = 3e4) {
|
|
|
4194
4723
|
if (event.type === "agent_end") {
|
|
4195
4724
|
clearTimeout(timer);
|
|
4196
4725
|
unsubscribe();
|
|
4197
|
-
|
|
4726
|
+
resolve3();
|
|
4198
4727
|
}
|
|
4199
4728
|
});
|
|
4200
4729
|
if (!session.isStreaming) {
|
|
4201
4730
|
clearTimeout(timer);
|
|
4202
4731
|
unsubscribe();
|
|
4203
|
-
|
|
4732
|
+
resolve3();
|
|
4204
4733
|
}
|
|
4205
4734
|
});
|
|
4206
4735
|
}
|
|
@@ -4312,8 +4841,8 @@ var FS_IGNORE = /* @__PURE__ */ new Set([
|
|
|
4312
4841
|
".svelte-kit"
|
|
4313
4842
|
]);
|
|
4314
4843
|
function resolveSafePath(cwd, requestPath) {
|
|
4315
|
-
const resolved = (0,
|
|
4316
|
-
const normalizedCwd = cwd.endsWith(
|
|
4844
|
+
const resolved = (0, import_node_path5.resolve)(cwd, requestPath);
|
|
4845
|
+
const normalizedCwd = cwd.endsWith(import_node_path5.sep) ? cwd : cwd + import_node_path5.sep;
|
|
4317
4846
|
if (resolved !== cwd && !resolved.startsWith(normalizedCwd)) {
|
|
4318
4847
|
throw new Error("Path escapes working directory");
|
|
4319
4848
|
}
|
|
@@ -4330,7 +4859,7 @@ async function buildTree(params) {
|
|
|
4330
4859
|
}
|
|
4331
4860
|
let entries;
|
|
4332
4861
|
try {
|
|
4333
|
-
entries = await (0,
|
|
4862
|
+
entries = await (0, import_promises5.readdir)(dir, { withFileTypes: true });
|
|
4334
4863
|
} catch {
|
|
4335
4864
|
return [];
|
|
4336
4865
|
}
|
|
@@ -4350,8 +4879,8 @@ async function buildTree(params) {
|
|
|
4350
4879
|
if (ignore.has(entry.name)) {
|
|
4351
4880
|
continue;
|
|
4352
4881
|
}
|
|
4353
|
-
const fullPath = (0,
|
|
4354
|
-
const relativePath = (0,
|
|
4882
|
+
const fullPath = (0, import_node_path5.join)(dir, entry.name);
|
|
4883
|
+
const relativePath = (0, import_node_path5.relative)(cwd, fullPath);
|
|
4355
4884
|
if (entry.isDirectory()) {
|
|
4356
4885
|
const children = await buildTree({
|
|
4357
4886
|
dir: fullPath,
|
|
@@ -4377,7 +4906,7 @@ async function walkFiles(params) {
|
|
|
4377
4906
|
}
|
|
4378
4907
|
let entries;
|
|
4379
4908
|
try {
|
|
4380
|
-
entries = await (0,
|
|
4909
|
+
entries = await (0, import_promises5.readdir)(dir, { withFileTypes: true });
|
|
4381
4910
|
} catch {
|
|
4382
4911
|
return;
|
|
4383
4912
|
}
|
|
@@ -4388,8 +4917,8 @@ async function walkFiles(params) {
|
|
|
4388
4917
|
if (ignore.has(entry.name)) {
|
|
4389
4918
|
continue;
|
|
4390
4919
|
}
|
|
4391
|
-
const fullPath = (0,
|
|
4392
|
-
const relativePath = (0,
|
|
4920
|
+
const fullPath = (0, import_node_path5.join)(dir, entry.name);
|
|
4921
|
+
const relativePath = (0, import_node_path5.relative)(cwd, fullPath);
|
|
4393
4922
|
if (entry.isDirectory()) {
|
|
4394
4923
|
await walkFiles({ dir: fullPath, cwd, pattern, ignore, maxResults, results });
|
|
4395
4924
|
} else if (entry.isFile() && pattern.test(relativePath)) {
|
|
@@ -4830,11 +5359,11 @@ async function createAgentServer(params) {
|
|
|
4830
5359
|
}
|
|
4831
5360
|
try {
|
|
4832
5361
|
const resolved = resolveSafePath(context.cwd, filePath);
|
|
4833
|
-
const fileStat = await (0,
|
|
5362
|
+
const fileStat = await (0, import_promises5.stat)(resolved);
|
|
4834
5363
|
if (!fileStat.isFile()) {
|
|
4835
5364
|
return c.json({ error: "Path is not a file" }, 400);
|
|
4836
5365
|
}
|
|
4837
|
-
const content = await (0,
|
|
5366
|
+
const content = await (0, import_promises5.readFile)(resolved, "utf-8");
|
|
4838
5367
|
return c.json({
|
|
4839
5368
|
path: filePath,
|
|
4840
5369
|
content,
|
|
@@ -4854,9 +5383,9 @@ async function createAgentServer(params) {
|
|
|
4854
5383
|
}
|
|
4855
5384
|
try {
|
|
4856
5385
|
const resolved = resolveSafePath(context.cwd, body.path);
|
|
4857
|
-
await (0,
|
|
4858
|
-
await (0,
|
|
4859
|
-
const fileStat = await (0,
|
|
5386
|
+
await (0, import_promises5.mkdir)((0, import_node_path5.join)(resolved, ".."), { recursive: true });
|
|
5387
|
+
await (0, import_promises5.writeFile)(resolved, body.content, "utf-8");
|
|
5388
|
+
const fileStat = await (0, import_promises5.stat)(resolved);
|
|
4860
5389
|
return c.json({ ok: true, path: body.path, size: fileStat.size });
|
|
4861
5390
|
} catch (error) {
|
|
4862
5391
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -4870,7 +5399,7 @@ async function createAgentServer(params) {
|
|
|
4870
5399
|
}
|
|
4871
5400
|
try {
|
|
4872
5401
|
const resolved = resolveSafePath(context.cwd, body.path);
|
|
4873
|
-
await (0,
|
|
5402
|
+
await (0, import_promises5.mkdir)(resolved, { recursive: true });
|
|
4874
5403
|
return c.json({ ok: true, path: body.path });
|
|
4875
5404
|
} catch (error) {
|
|
4876
5405
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -4885,8 +5414,8 @@ async function createAgentServer(params) {
|
|
|
4885
5414
|
try {
|
|
4886
5415
|
const resolvedFrom = resolveSafePath(context.cwd, body.from);
|
|
4887
5416
|
const resolvedTo = resolveSafePath(context.cwd, body.to);
|
|
4888
|
-
await (0,
|
|
4889
|
-
await (0,
|
|
5417
|
+
await (0, import_promises5.mkdir)((0, import_node_path5.join)(resolvedTo, ".."), { recursive: true });
|
|
5418
|
+
await (0, import_promises5.rename)(resolvedFrom, resolvedTo);
|
|
4890
5419
|
return c.json({ ok: true, from: body.from, to: body.to });
|
|
4891
5420
|
} catch (error) {
|
|
4892
5421
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -4904,8 +5433,8 @@ async function createAgentServer(params) {
|
|
|
4904
5433
|
if (resolved === context.cwd) {
|
|
4905
5434
|
return c.json({ error: "Cannot delete working directory" }, 400);
|
|
4906
5435
|
}
|
|
4907
|
-
const fileStat = await (0,
|
|
4908
|
-
await (0,
|
|
5436
|
+
const fileStat = await (0, import_promises5.stat)(resolved);
|
|
5437
|
+
await (0, import_promises5.rm)(resolved, { recursive: fileStat.isDirectory() });
|
|
4909
5438
|
return c.json({ ok: true, path: filePath });
|
|
4910
5439
|
} catch (error) {
|
|
4911
5440
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -4914,7 +5443,7 @@ async function createAgentServer(params) {
|
|
|
4914
5443
|
}
|
|
4915
5444
|
});
|
|
4916
5445
|
const CLI_EXEC = process.env.DOCYRUS_CLI_EXECUTABLE || process.execPath;
|
|
4917
|
-
const CLI_ENTRY = process.env.DOCYRUS_CLI_ENTRY || (0,
|
|
5446
|
+
const CLI_ENTRY = process.env.DOCYRUS_CLI_ENTRY || (0, import_node_path5.resolve)(process.argv[1] ? (0, import_node_path5.join)(process.argv[1], "..") : __dirname, "main.js");
|
|
4918
5447
|
const CLI_SCOPE = process.env.DOCYRUS_CLI_SCOPE;
|
|
4919
5448
|
const CLI_TIMEOUT_MS = 3e4;
|
|
4920
5449
|
function runCliCommand(args) {
|
|
@@ -4964,7 +5493,7 @@ async function createAgentServer(params) {
|
|
|
4964
5493
|
}
|
|
4965
5494
|
async function detectDevPort(cwd) {
|
|
4966
5495
|
try {
|
|
4967
|
-
const pkg = JSON.parse(await (0,
|
|
5496
|
+
const pkg = JSON.parse(await (0, import_promises5.readFile)((0, import_node_path5.join)(cwd, "package.json"), "utf-8"));
|
|
4968
5497
|
const devScript = pkg.scripts?.dev;
|
|
4969
5498
|
if (devScript) {
|
|
4970
5499
|
const portFlag = devScript.match(/--port\s+(\d+)/);
|
|
@@ -4980,7 +5509,7 @@ async function createAgentServer(params) {
|
|
|
4980
5509
|
}
|
|
4981
5510
|
for (const name of ["vite.config.ts", "vite.config.mts", "vite.config.js"]) {
|
|
4982
5511
|
try {
|
|
4983
|
-
const content = await (0,
|
|
5512
|
+
const content = await (0, import_promises5.readFile)((0, import_node_path5.join)(cwd, name), "utf-8");
|
|
4984
5513
|
const portMatch = content.match(/port\s*:\s*(\d+)/);
|
|
4985
5514
|
if (portMatch) {
|
|
4986
5515
|
return Number(portMatch[1]);
|
|
@@ -4990,19 +5519,19 @@ async function createAgentServer(params) {
|
|
|
4990
5519
|
}
|
|
4991
5520
|
for (const name of ["next.config.ts", "next.config.mts", "next.config.js", "next.config.mjs"]) {
|
|
4992
5521
|
try {
|
|
4993
|
-
await (0,
|
|
5522
|
+
await (0, import_promises5.stat)((0, import_node_path5.join)(cwd, name));
|
|
4994
5523
|
return 3e3;
|
|
4995
5524
|
} catch {
|
|
4996
5525
|
}
|
|
4997
5526
|
}
|
|
4998
5527
|
try {
|
|
4999
|
-
await (0,
|
|
5528
|
+
await (0, import_promises5.stat)((0, import_node_path5.join)(cwd, "angular.json"));
|
|
5000
5529
|
return 4200;
|
|
5001
5530
|
} catch {
|
|
5002
5531
|
}
|
|
5003
5532
|
for (const name of ["nuxt.config.ts", "nuxt.config.js"]) {
|
|
5004
5533
|
try {
|
|
5005
|
-
await (0,
|
|
5534
|
+
await (0, import_promises5.stat)((0, import_node_path5.join)(cwd, name));
|
|
5006
5535
|
return 3e3;
|
|
5007
5536
|
} catch {
|
|
5008
5537
|
}
|
|
@@ -5032,14 +5561,14 @@ async function createAgentServer(params) {
|
|
|
5032
5561
|
} catch {
|
|
5033
5562
|
}
|
|
5034
5563
|
try {
|
|
5035
|
-
const pkg = JSON.parse(await (0,
|
|
5564
|
+
const pkg = JSON.parse(await (0, import_promises5.readFile)((0, import_node_path5.join)(cwd, "package.json"), "utf-8"));
|
|
5036
5565
|
packageName = pkg.name ?? null;
|
|
5037
5566
|
packageVersion = pkg.version ?? null;
|
|
5038
5567
|
} catch {
|
|
5039
5568
|
}
|
|
5040
5569
|
cachedProjectInfo = {
|
|
5041
5570
|
path: cwd,
|
|
5042
|
-
folder: (0,
|
|
5571
|
+
folder: (0, import_node_path5.basename)(cwd),
|
|
5043
5572
|
repo,
|
|
5044
5573
|
packageName,
|
|
5045
5574
|
packageVersion
|
|
@@ -5250,8 +5779,8 @@ async function createAgentServer(params) {
|
|
|
5250
5779
|
}
|
|
5251
5780
|
if (entry.status !== "deleted") {
|
|
5252
5781
|
try {
|
|
5253
|
-
const resolved = (0,
|
|
5254
|
-
const buf = await (0,
|
|
5782
|
+
const resolved = (0, import_node_path5.resolve)(cwd, entry.path);
|
|
5783
|
+
const buf = await (0, import_promises5.readFile)(resolved);
|
|
5255
5784
|
if (buf.subarray(0, 8192).includes(0)) {
|
|
5256
5785
|
return null;
|
|
5257
5786
|
}
|
|
@@ -5288,6 +5817,185 @@ async function createAgentServer(params) {
|
|
|
5288
5817
|
return c.json({ error: message }, 500);
|
|
5289
5818
|
}
|
|
5290
5819
|
});
|
|
5820
|
+
app.get("/api/mcp/servers", async (c) => {
|
|
5821
|
+
try {
|
|
5822
|
+
const [config, cache, provenance] = await Promise.all([
|
|
5823
|
+
loadMcpServerConfig(context.agentDir, context.cwd),
|
|
5824
|
+
loadMcpServerCache(context.agentDir),
|
|
5825
|
+
getServerProvenance(context.agentDir, context.cwd)
|
|
5826
|
+
]);
|
|
5827
|
+
const servers = Object.entries(config.mcpServers).map(([name, entry]) => {
|
|
5828
|
+
const prov = provenance.get(name);
|
|
5829
|
+
const serverCache = cache?.servers[name] ?? null;
|
|
5830
|
+
return {
|
|
5831
|
+
name,
|
|
5832
|
+
transport: detectTransport(entry),
|
|
5833
|
+
config: redactSensitiveFields(entry),
|
|
5834
|
+
provenance: prov?.kind ?? "user",
|
|
5835
|
+
importSource: prov?.importKind ?? null,
|
|
5836
|
+
cache: serverCache ? {
|
|
5837
|
+
toolCount: serverCache.tools?.length ?? 0,
|
|
5838
|
+
resourceCount: serverCache.resources?.length ?? 0,
|
|
5839
|
+
cachedAt: new Date(serverCache.cachedAt).toISOString(),
|
|
5840
|
+
isValid: isServerCacheValid(serverCache, entry)
|
|
5841
|
+
} : null
|
|
5842
|
+
};
|
|
5843
|
+
});
|
|
5844
|
+
return c.json({
|
|
5845
|
+
servers,
|
|
5846
|
+
settings: {
|
|
5847
|
+
toolPrefix: config.settings?.toolPrefix ?? "server",
|
|
5848
|
+
idleTimeout: config.settings?.idleTimeout ?? 10,
|
|
5849
|
+
directTools: config.settings?.directTools ?? false
|
|
5850
|
+
},
|
|
5851
|
+
imports: config.imports ?? null
|
|
5852
|
+
});
|
|
5853
|
+
} catch (error) {
|
|
5854
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
5855
|
+
return c.json({ error: message }, 500);
|
|
5856
|
+
}
|
|
5857
|
+
});
|
|
5858
|
+
app.get("/api/mcp/servers/:serverName", async (c) => {
|
|
5859
|
+
const serverName = c.req.param("serverName");
|
|
5860
|
+
try {
|
|
5861
|
+
const [config, cache, provenance] = await Promise.all([
|
|
5862
|
+
loadMcpServerConfig(context.agentDir, context.cwd),
|
|
5863
|
+
loadMcpServerCache(context.agentDir),
|
|
5864
|
+
getServerProvenance(context.agentDir, context.cwd)
|
|
5865
|
+
]);
|
|
5866
|
+
const entry = config.mcpServers[serverName];
|
|
5867
|
+
if (!entry) {
|
|
5868
|
+
return c.json({ error: `Server "${serverName}" not found` }, 404);
|
|
5869
|
+
}
|
|
5870
|
+
const prov = provenance.get(serverName);
|
|
5871
|
+
const serverCache = cache?.servers[serverName] ?? null;
|
|
5872
|
+
return c.json({
|
|
5873
|
+
name: serverName,
|
|
5874
|
+
transport: detectTransport(entry),
|
|
5875
|
+
config: redactSensitiveFields(entry),
|
|
5876
|
+
provenance: prov?.kind ?? "user",
|
|
5877
|
+
tools: serverCache?.tools?.map((t) => ({
|
|
5878
|
+
name: t.name,
|
|
5879
|
+
description: t.description ?? null,
|
|
5880
|
+
inputSchema: t.inputSchema
|
|
5881
|
+
})) ?? [],
|
|
5882
|
+
resources: serverCache?.resources?.map((r) => ({
|
|
5883
|
+
uri: r.uri,
|
|
5884
|
+
name: r.name,
|
|
5885
|
+
description: r.description ?? null
|
|
5886
|
+
})) ?? [],
|
|
5887
|
+
cache: serverCache ? {
|
|
5888
|
+
configHash: serverCache.configHash,
|
|
5889
|
+
toolCount: serverCache.tools?.length ?? 0,
|
|
5890
|
+
resourceCount: serverCache.resources?.length ?? 0,
|
|
5891
|
+
cachedAt: new Date(serverCache.cachedAt).toISOString(),
|
|
5892
|
+
isValid: isServerCacheValid(serverCache, entry)
|
|
5893
|
+
} : null
|
|
5894
|
+
});
|
|
5895
|
+
} catch (error) {
|
|
5896
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
5897
|
+
return c.json({ error: message }, 500);
|
|
5898
|
+
}
|
|
5899
|
+
});
|
|
5900
|
+
app.post("/api/mcp/servers", async (c) => {
|
|
5901
|
+
try {
|
|
5902
|
+
const body = await c.req.json();
|
|
5903
|
+
if (!body.name) {
|
|
5904
|
+
return c.json({ error: "Missing required field: name" }, 400);
|
|
5905
|
+
}
|
|
5906
|
+
const nameResult = validateServerName(body.name);
|
|
5907
|
+
if (!nameResult.ok) {
|
|
5908
|
+
return c.json({ error: nameResult.error }, 400);
|
|
5909
|
+
}
|
|
5910
|
+
const entryResult = validateServerEntry(body.config);
|
|
5911
|
+
if (!entryResult.ok) {
|
|
5912
|
+
return c.json({ error: entryResult.error }, 400);
|
|
5913
|
+
}
|
|
5914
|
+
await addMcpServer(context.agentDir, body.name, entryResult.value);
|
|
5915
|
+
return c.json({ ok: true, name: body.name, config: redactSensitiveFields(entryResult.value) });
|
|
5916
|
+
} catch (error) {
|
|
5917
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
5918
|
+
const status = message.includes("already exists") ? 409 : 500;
|
|
5919
|
+
return c.json({ error: message }, status);
|
|
5920
|
+
}
|
|
5921
|
+
});
|
|
5922
|
+
app.put("/api/mcp/servers/:serverName", async (c) => {
|
|
5923
|
+
const serverName = c.req.param("serverName");
|
|
5924
|
+
try {
|
|
5925
|
+
const provenance = await getServerProvenance(context.agentDir, context.cwd);
|
|
5926
|
+
const prov = provenance.get(serverName);
|
|
5927
|
+
if (prov && prov.kind !== "user") {
|
|
5928
|
+
return c.json({ error: `Cannot modify ${prov.kind} server "${serverName}"` }, 403);
|
|
5929
|
+
}
|
|
5930
|
+
const body = await c.req.json();
|
|
5931
|
+
const entryResult = validateServerEntry(body.config);
|
|
5932
|
+
if (!entryResult.ok) {
|
|
5933
|
+
return c.json({ error: entryResult.error }, 400);
|
|
5934
|
+
}
|
|
5935
|
+
await updateMcpServer(context.agentDir, serverName, entryResult.value);
|
|
5936
|
+
return c.json({ ok: true, name: serverName, config: redactSensitiveFields(entryResult.value) });
|
|
5937
|
+
} catch (error) {
|
|
5938
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
5939
|
+
const status = message.includes("not found") ? 404 : 500;
|
|
5940
|
+
return c.json({ error: message }, status);
|
|
5941
|
+
}
|
|
5942
|
+
});
|
|
5943
|
+
app.delete("/api/mcp/servers/:serverName", async (c) => {
|
|
5944
|
+
const serverName = c.req.param("serverName");
|
|
5945
|
+
try {
|
|
5946
|
+
const provenance = await getServerProvenance(context.agentDir, context.cwd);
|
|
5947
|
+
const prov = provenance.get(serverName);
|
|
5948
|
+
if (prov && prov.kind !== "user") {
|
|
5949
|
+
return c.json({ error: `Cannot remove ${prov.kind} server "${serverName}"` }, 403);
|
|
5950
|
+
}
|
|
5951
|
+
await removeMcpServer(context.agentDir, serverName);
|
|
5952
|
+
return c.json({ ok: true, name: serverName });
|
|
5953
|
+
} catch (error) {
|
|
5954
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
5955
|
+
const status = message.includes("not found") ? 404 : 500;
|
|
5956
|
+
return c.json({ error: message }, status);
|
|
5957
|
+
}
|
|
5958
|
+
});
|
|
5959
|
+
app.get("/api/skills", async (c) => {
|
|
5960
|
+
try {
|
|
5961
|
+
const skills = await listSkills(context.agentDir);
|
|
5962
|
+
return c.json({ skills });
|
|
5963
|
+
} catch (error) {
|
|
5964
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
5965
|
+
return c.json({ error: message }, 500);
|
|
5966
|
+
}
|
|
5967
|
+
});
|
|
5968
|
+
app.get("/api/skills/:skillName", async (c) => {
|
|
5969
|
+
const skillName = c.req.param("skillName");
|
|
5970
|
+
try {
|
|
5971
|
+
const result = await getSkillDetail(context.agentDir, skillName);
|
|
5972
|
+
if (!result) {
|
|
5973
|
+
return c.json({ error: `Skill "${skillName}" not found` }, 404);
|
|
5974
|
+
}
|
|
5975
|
+
return c.json({ ...result.skill, content: result.content });
|
|
5976
|
+
} catch (error) {
|
|
5977
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
5978
|
+
return c.json({ error: message }, 500);
|
|
5979
|
+
}
|
|
5980
|
+
});
|
|
5981
|
+
app.get("/api/tools", async (c) => {
|
|
5982
|
+
try {
|
|
5983
|
+
const tools = await listAllTools({
|
|
5984
|
+
profile: context.profile,
|
|
5985
|
+
agentDir: context.agentDir,
|
|
5986
|
+
cwd: context.cwd
|
|
5987
|
+
});
|
|
5988
|
+
const builtInCount = tools.filter((t) => t.source === "built-in").length;
|
|
5989
|
+
const mcpCount = tools.filter((t) => t.source !== "built-in").length;
|
|
5990
|
+
return c.json({
|
|
5991
|
+
tools,
|
|
5992
|
+
summary: { builtIn: builtInCount, mcp: mcpCount, total: tools.length }
|
|
5993
|
+
});
|
|
5994
|
+
} catch (error) {
|
|
5995
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
5996
|
+
return c.json({ error: message }, 500);
|
|
5997
|
+
}
|
|
5998
|
+
});
|
|
5291
5999
|
const BOOLEAN_CLI_FLAGS = /* @__PURE__ */ new Set(["json", "verbose", "global", "noAuth", "expand", "i"]);
|
|
5292
6000
|
function buildCliArgs(pathSegments, query, body) {
|
|
5293
6001
|
const args = [...pathSegments, "--json"];
|
|
@@ -5438,6 +6146,22 @@ async function createAgentServer(params) {
|
|
|
5438
6146
|
process.stderr.write(` POST /api/env/stop \u2014 stop dev server
|
|
5439
6147
|
`);
|
|
5440
6148
|
process.stderr.write(` GET /api/git/diff \u2014 uncommitted file diffs
|
|
6149
|
+
`);
|
|
6150
|
+
process.stderr.write(` GET /api/mcp/servers \u2014 list MCP servers
|
|
6151
|
+
`);
|
|
6152
|
+
process.stderr.write(` GET /api/mcp/servers/:name \u2014 MCP server details
|
|
6153
|
+
`);
|
|
6154
|
+
process.stderr.write(` POST /api/mcp/servers \u2014 add MCP server
|
|
6155
|
+
`);
|
|
6156
|
+
process.stderr.write(` PUT /api/mcp/servers/:name \u2014 update MCP server
|
|
6157
|
+
`);
|
|
6158
|
+
process.stderr.write(` DEL /api/mcp/servers/:name \u2014 remove MCP server
|
|
6159
|
+
`);
|
|
6160
|
+
process.stderr.write(` GET /api/skills \u2014 list skills
|
|
6161
|
+
`);
|
|
6162
|
+
process.stderr.write(` GET /api/skills/:name \u2014 skill details
|
|
6163
|
+
`);
|
|
6164
|
+
process.stderr.write(` GET /api/tools \u2014 list all tools
|
|
5441
6165
|
`);
|
|
5442
6166
|
process.stderr.write(` * /api/cli/** \u2014 proxy any docyrus CLI command
|
|
5443
6167
|
|
|
@@ -5487,7 +6211,7 @@ function extractBearerToken(authorizationHeader) {
|
|
|
5487
6211
|
return token;
|
|
5488
6212
|
}
|
|
5489
6213
|
function hashToken(token) {
|
|
5490
|
-
return (0,
|
|
6214
|
+
return (0, import_node_crypto3.createHash)("sha256").update(token).digest();
|
|
5491
6215
|
}
|
|
5492
6216
|
function isBearerTokenAuthorized(actualToken, expectedToken) {
|
|
5493
6217
|
if (!expectedToken) {
|
|
@@ -5498,7 +6222,7 @@ function isBearerTokenAuthorized(actualToken, expectedToken) {
|
|
|
5498
6222
|
}
|
|
5499
6223
|
const actualHash = hashToken(actualToken);
|
|
5500
6224
|
const expectedHash = hashToken(expectedToken);
|
|
5501
|
-
return (0,
|
|
6225
|
+
return (0, import_node_crypto3.timingSafeEqual)(actualHash, expectedHash);
|
|
5502
6226
|
}
|
|
5503
6227
|
function createUnauthorizedResponse() {
|
|
5504
6228
|
return new Response(JSON.stringify({ error: "Unauthorized" }), {
|
|
@@ -5564,15 +6288,15 @@ function readLoaderRequest() {
|
|
|
5564
6288
|
}
|
|
5565
6289
|
async function loadPiExports() {
|
|
5566
6290
|
const piPackageDir = readRequiredEnv("PI_PACKAGE_DIR");
|
|
5567
|
-
const moduleUrl = (0, import_node_url.pathToFileURL)((0,
|
|
6291
|
+
const moduleUrl = (0, import_node_url.pathToFileURL)((0, import_node_path6.join)(piPackageDir, "dist", "index.js")).href;
|
|
5568
6292
|
return await import(moduleUrl);
|
|
5569
6293
|
}
|
|
5570
6294
|
function resolvePackagedPiResourceRoot() {
|
|
5571
6295
|
const candidates = [
|
|
5572
|
-
(0,
|
|
5573
|
-
(0,
|
|
5574
|
-
(0,
|
|
5575
|
-
(0,
|
|
6296
|
+
(0, import_node_path6.resolve)(process.cwd(), "apps/api-cli/resources/pi-agent"),
|
|
6297
|
+
(0, import_node_path6.resolve)(__dirname, "../resources/pi-agent"),
|
|
6298
|
+
(0, import_node_path6.resolve)(__dirname, "resources/pi-agent"),
|
|
6299
|
+
(0, import_node_path6.resolve)(process.cwd(), "dist/apps/api-cli/resources/pi-agent")
|
|
5576
6300
|
];
|
|
5577
6301
|
const resolved = candidates.find((candidate) => (0, import_node_fs.existsSync)(candidate));
|
|
5578
6302
|
if (!resolved) {
|
|
@@ -5581,13 +6305,13 @@ function resolvePackagedPiResourceRoot() {
|
|
|
5581
6305
|
return resolved;
|
|
5582
6306
|
}
|
|
5583
6307
|
function resolvePackagedExtensionPaths(resourceRoot) {
|
|
5584
|
-
const extensionsRoot = (0,
|
|
6308
|
+
const extensionsRoot = (0, import_node_path6.join)(resourceRoot, "extensions");
|
|
5585
6309
|
if (!(0, import_node_fs.existsSync)(extensionsRoot)) {
|
|
5586
6310
|
return [];
|
|
5587
6311
|
}
|
|
5588
6312
|
return (0, import_node_fs.readdirSync)(extensionsRoot, {
|
|
5589
6313
|
withFileTypes: true
|
|
5590
|
-
}).filter((entry) => entry.isDirectory() || entry.isFile() && (entry.name.endsWith(".ts") || entry.name.endsWith(".js"))).map((entry) => (0,
|
|
6314
|
+
}).filter((entry) => entry.isDirectory() || entry.isFile() && (entry.name.endsWith(".ts") || entry.name.endsWith(".js"))).map((entry) => (0, import_node_path6.join)(extensionsRoot, entry.name)).sort((left, right) => left.localeCompare(right));
|
|
5591
6315
|
}
|
|
5592
6316
|
function setProcessArgValue(flag, value) {
|
|
5593
6317
|
const existingIndex = process.argv.indexOf(flag);
|
|
@@ -5701,16 +6425,16 @@ async function main() {
|
|
|
5701
6425
|
const version = process.env.DOCYRUS_PI_VERSION || "dev";
|
|
5702
6426
|
const resourceRoot = resolvePackagedPiResourceRoot();
|
|
5703
6427
|
const packagedExtensionPaths = resolvePackagedExtensionPaths(resourceRoot);
|
|
5704
|
-
const mcpConfigPath = (0,
|
|
6428
|
+
const mcpConfigPath = (0, import_node_path6.join)(agentDir, "mcp.json");
|
|
5705
6429
|
const hasPackagedMcpAdapter = packagedExtensionPaths.some((extensionPath) => extensionPath.includes("pi-mcp-adapter"));
|
|
5706
|
-
const envStore = new AgentEnvStore((0,
|
|
6430
|
+
const envStore = new AgentEnvStore((0, import_node_path6.join)(agentDir, "env.json"));
|
|
5707
6431
|
await envStore.hydrateProcessEnv(process.env);
|
|
5708
6432
|
if (hasPackagedMcpAdapter) {
|
|
5709
6433
|
setProcessArgValue("--mcp-config", mcpConfigPath);
|
|
5710
6434
|
}
|
|
5711
|
-
const authStorage = pi.AuthStorage.create((0,
|
|
6435
|
+
const authStorage = pi.AuthStorage.create((0, import_node_path6.join)(agentDir, "auth.json"));
|
|
5712
6436
|
const settingsManager = pi.SettingsManager.create(cwd, agentDir);
|
|
5713
|
-
const modelsJsonPath = (0,
|
|
6437
|
+
const modelsJsonPath = (0, import_node_path6.join)(agentDir, "models.json");
|
|
5714
6438
|
const modelRegistry = new pi.ModelRegistry(authStorage, modelsJsonPath);
|
|
5715
6439
|
const quietStartup = !request.verbose;
|
|
5716
6440
|
if (quietStartup) {
|
|
@@ -5739,7 +6463,7 @@ async function main() {
|
|
|
5739
6463
|
agentDir,
|
|
5740
6464
|
settingsManager,
|
|
5741
6465
|
additionalExtensionPaths: packagedExtensionPaths,
|
|
5742
|
-
systemPrompt: (0,
|
|
6466
|
+
systemPrompt: (0, import_node_path6.join)(
|
|
5743
6467
|
resourceRoot,
|
|
5744
6468
|
"prompts",
|
|
5745
6469
|
request.profile === "agent" ? "agent-system.md" : "coder-system.md"
|