@agiflowai/one-mcp 0.2.7 → 0.2.8
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 +34 -0
- package/dist/cli.cjs +564 -52
- package/dist/cli.mjs +565 -53
- package/dist/{http-B4NAfsQl.cjs → http-BzrxGEr-.cjs} +270 -145
- package/dist/{http-DSkkpGJU.mjs → http-DeUYygKb.mjs} +271 -146
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +8 -0
- package/dist/index.d.mts +8 -0
- package/dist/index.mjs +1 -1
- package/package.json +2 -2
|
@@ -4,7 +4,7 @@ import { access, mkdir, readFile, readdir, stat, unlink, watch, writeFile } from
|
|
|
4
4
|
import { existsSync } from "node:fs";
|
|
5
5
|
import yaml from "js-yaml";
|
|
6
6
|
import { z } from "zod";
|
|
7
|
-
import { createHash, randomUUID } from "node:crypto";
|
|
7
|
+
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
8
8
|
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
9
9
|
import { tmpdir } from "node:os";
|
|
10
10
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
@@ -202,6 +202,7 @@ const ClaudeCodeStdioServerSchema = z.object({
|
|
|
202
202
|
env: z.record(z.string(), z.string()).optional(),
|
|
203
203
|
disabled: z.boolean().optional(),
|
|
204
204
|
instruction: z.string().optional(),
|
|
205
|
+
timeout: z.number().positive().optional(),
|
|
205
206
|
config: AdditionalConfigSchema
|
|
206
207
|
});
|
|
207
208
|
const ClaudeCodeHttpServerSchema = z.object({
|
|
@@ -210,6 +211,7 @@ const ClaudeCodeHttpServerSchema = z.object({
|
|
|
210
211
|
type: z.enum(["http", "sse"]).optional(),
|
|
211
212
|
disabled: z.boolean().optional(),
|
|
212
213
|
instruction: z.string().optional(),
|
|
214
|
+
timeout: z.number().positive().optional(),
|
|
213
215
|
config: AdditionalConfigSchema
|
|
214
216
|
});
|
|
215
217
|
const ClaudeCodeServerConfigSchema = z.union([ClaudeCodeStdioServerSchema, ClaudeCodeHttpServerSchema]);
|
|
@@ -240,6 +242,7 @@ const SkillsConfigSchema = z.object({ paths: z.array(z.string()) });
|
|
|
240
242
|
* Full Claude Code MCP configuration schema
|
|
241
243
|
*/
|
|
242
244
|
const ClaudeCodeMcpConfigSchema = z.object({
|
|
245
|
+
id: z.string().optional(),
|
|
243
246
|
mcpServers: z.record(z.string(), ClaudeCodeServerConfigSchema),
|
|
244
247
|
remoteConfigs: z.array(RemoteConfigSourceSchema).optional(),
|
|
245
248
|
skills: SkillsConfigSchema.optional()
|
|
@@ -280,6 +283,7 @@ const McpServerConfigSchema = z.discriminatedUnion("transport", [
|
|
|
280
283
|
toolBlacklist: z.array(z.string()).optional(),
|
|
281
284
|
omitToolDescription: z.boolean().optional(),
|
|
282
285
|
prompts: z.record(z.string(), InternalPromptConfigSchema).optional(),
|
|
286
|
+
timeout: z.number().positive().optional(),
|
|
283
287
|
transport: z.literal("stdio"),
|
|
284
288
|
config: McpStdioConfigSchema
|
|
285
289
|
}),
|
|
@@ -289,6 +293,7 @@ const McpServerConfigSchema = z.discriminatedUnion("transport", [
|
|
|
289
293
|
toolBlacklist: z.array(z.string()).optional(),
|
|
290
294
|
omitToolDescription: z.boolean().optional(),
|
|
291
295
|
prompts: z.record(z.string(), InternalPromptConfigSchema).optional(),
|
|
296
|
+
timeout: z.number().positive().optional(),
|
|
292
297
|
transport: z.literal("http"),
|
|
293
298
|
config: McpHttpConfigSchema
|
|
294
299
|
}),
|
|
@@ -298,6 +303,7 @@ const McpServerConfigSchema = z.discriminatedUnion("transport", [
|
|
|
298
303
|
toolBlacklist: z.array(z.string()).optional(),
|
|
299
304
|
omitToolDescription: z.boolean().optional(),
|
|
300
305
|
prompts: z.record(z.string(), InternalPromptConfigSchema).optional(),
|
|
306
|
+
timeout: z.number().positive().optional(),
|
|
301
307
|
transport: z.literal("sse"),
|
|
302
308
|
config: McpSseConfigSchema
|
|
303
309
|
})
|
|
@@ -306,6 +312,7 @@ const McpServerConfigSchema = z.discriminatedUnion("transport", [
|
|
|
306
312
|
* Full internal MCP configuration schema
|
|
307
313
|
*/
|
|
308
314
|
const InternalMcpConfigSchema = z.object({
|
|
315
|
+
id: z.string().optional(),
|
|
309
316
|
mcpServers: z.record(z.string(), McpServerConfigSchema),
|
|
310
317
|
skills: SkillsConfigSchema.optional()
|
|
311
318
|
});
|
|
@@ -331,6 +338,7 @@ function transformClaudeCodeConfig(claudeConfig) {
|
|
|
331
338
|
toolBlacklist: stdioConfig.config?.toolBlacklist,
|
|
332
339
|
omitToolDescription: stdioConfig.config?.omitToolDescription,
|
|
333
340
|
prompts: stdioConfig.config?.prompts,
|
|
341
|
+
timeout: stdioConfig.timeout,
|
|
334
342
|
transport: "stdio",
|
|
335
343
|
config: {
|
|
336
344
|
command: interpolatedCommand,
|
|
@@ -349,6 +357,7 @@ function transformClaudeCodeConfig(claudeConfig) {
|
|
|
349
357
|
toolBlacklist: httpConfig.config?.toolBlacklist,
|
|
350
358
|
omitToolDescription: httpConfig.config?.omitToolDescription,
|
|
351
359
|
prompts: httpConfig.config?.prompts,
|
|
360
|
+
timeout: httpConfig.timeout,
|
|
352
361
|
transport,
|
|
353
362
|
config: {
|
|
354
363
|
url: interpolatedUrl,
|
|
@@ -358,6 +367,7 @@ function transformClaudeCodeConfig(claudeConfig) {
|
|
|
358
367
|
}
|
|
359
368
|
}
|
|
360
369
|
return {
|
|
370
|
+
id: claudeConfig.id,
|
|
361
371
|
mcpServers: transformedServers,
|
|
362
372
|
skills: claudeConfig.skills
|
|
363
373
|
};
|
|
@@ -518,22 +528,21 @@ var RemoteConfigCacheService = class {
|
|
|
518
528
|
try {
|
|
519
529
|
if (!existsSync(this.cacheDir)) return;
|
|
520
530
|
const now = Date.now();
|
|
521
|
-
const
|
|
522
|
-
|
|
523
|
-
for (const file of files) {
|
|
524
|
-
if (!file.endsWith(".json")) continue;
|
|
531
|
+
const jsonFiles = (await readdir(this.cacheDir)).filter((file) => file.endsWith(".json"));
|
|
532
|
+
const expiredCount = (await Promise.all(jsonFiles.map(async (file) => {
|
|
525
533
|
const filePath = join(this.cacheDir, file);
|
|
526
534
|
try {
|
|
527
535
|
const content = await readFile(filePath, "utf-8");
|
|
528
536
|
if (now > JSON.parse(content).expiresAt) {
|
|
529
537
|
await unlink(filePath);
|
|
530
|
-
|
|
538
|
+
return true;
|
|
531
539
|
}
|
|
540
|
+
return false;
|
|
532
541
|
} catch (error) {
|
|
533
542
|
await unlink(filePath).catch(() => {});
|
|
534
|
-
|
|
543
|
+
return true;
|
|
535
544
|
}
|
|
536
|
-
}
|
|
545
|
+
}))).filter(Boolean).length;
|
|
537
546
|
if (expiredCount > 0) console.error(`Cleaned up ${expiredCount} expired remote config cache entries`);
|
|
538
547
|
} catch (error) {
|
|
539
548
|
console.error("Failed to clean expired remote config cache:", error);
|
|
@@ -549,14 +558,15 @@ var RemoteConfigCacheService = class {
|
|
|
549
558
|
totalSize: 0
|
|
550
559
|
};
|
|
551
560
|
const jsonFiles = (await readdir(this.cacheDir)).filter((file) => file.endsWith(".json"));
|
|
552
|
-
|
|
553
|
-
for (const file of jsonFiles) {
|
|
561
|
+
const totalSize = (await Promise.all(jsonFiles.map(async (file) => {
|
|
554
562
|
const filePath = join(this.cacheDir, file);
|
|
555
563
|
try {
|
|
556
564
|
const content = await readFile(filePath, "utf-8");
|
|
557
|
-
|
|
558
|
-
} catch {
|
|
559
|
-
|
|
565
|
+
return Buffer.byteLength(content, "utf-8");
|
|
566
|
+
} catch {
|
|
567
|
+
return 0;
|
|
568
|
+
}
|
|
569
|
+
}))).reduce((sum, size) => sum + size, 0);
|
|
560
570
|
return {
|
|
561
571
|
totalEntries: jsonFiles.length,
|
|
562
572
|
totalSize
|
|
@@ -807,6 +817,8 @@ var ConfigFetcherService = class {
|
|
|
807
817
|
|
|
808
818
|
//#endregion
|
|
809
819
|
//#region src/services/McpClientManagerService.ts
|
|
820
|
+
/** Default connection timeout in milliseconds (30 seconds) */
|
|
821
|
+
const DEFAULT_CONNECTION_TIMEOUT_MS = 3e4;
|
|
810
822
|
/**
|
|
811
823
|
* MCP Client wrapper for managing individual server connections
|
|
812
824
|
* This is an internal class used by McpClientManagerService
|
|
@@ -912,8 +924,10 @@ var McpClientManagerService = class {
|
|
|
912
924
|
}
|
|
913
925
|
/**
|
|
914
926
|
* Connect to an MCP server based on its configuration with timeout
|
|
927
|
+
* Uses the timeout from server config, falling back to default (30s)
|
|
915
928
|
*/
|
|
916
|
-
async connectToServer(serverName, config
|
|
929
|
+
async connectToServer(serverName, config) {
|
|
930
|
+
const timeoutMs = config.timeout ?? DEFAULT_CONNECTION_TIMEOUT_MS;
|
|
917
931
|
if (this.clients.has(serverName)) throw new Error(`Client for ${serverName} is already connected`);
|
|
918
932
|
const client = new Client({
|
|
919
933
|
name: `@agiflowai/one-mcp-client`,
|
|
@@ -1208,6 +1222,71 @@ function extractSkillFrontMatter(content) {
|
|
|
1208
1222
|
return null;
|
|
1209
1223
|
}
|
|
1210
1224
|
|
|
1225
|
+
//#endregion
|
|
1226
|
+
//#region src/utils/generateServerId.ts
|
|
1227
|
+
/**
|
|
1228
|
+
* generateServerId Utilities
|
|
1229
|
+
*
|
|
1230
|
+
* DESIGN PATTERNS:
|
|
1231
|
+
* - Pure functions with no side effects
|
|
1232
|
+
* - Single responsibility per function
|
|
1233
|
+
* - Functional programming approach
|
|
1234
|
+
*
|
|
1235
|
+
* CODING STANDARDS:
|
|
1236
|
+
* - Export individual functions, not classes
|
|
1237
|
+
* - Use descriptive function names with verbs
|
|
1238
|
+
* - Add JSDoc comments for complex logic
|
|
1239
|
+
* - Keep functions small and focused
|
|
1240
|
+
*
|
|
1241
|
+
* AVOID:
|
|
1242
|
+
* - Side effects (mutating external state)
|
|
1243
|
+
* - Stateful logic (use services for state)
|
|
1244
|
+
* - Complex external dependencies
|
|
1245
|
+
*/
|
|
1246
|
+
/**
|
|
1247
|
+
* Character set for generating human-readable IDs.
|
|
1248
|
+
* Excludes confusing characters: 0, O, 1, l, I
|
|
1249
|
+
*/
|
|
1250
|
+
const CHARSET = "23456789abcdefghjkmnpqrstuvwxyz";
|
|
1251
|
+
/**
|
|
1252
|
+
* Default length for generated server IDs (6 characters)
|
|
1253
|
+
*/
|
|
1254
|
+
const DEFAULT_ID_LENGTH = 6;
|
|
1255
|
+
/**
|
|
1256
|
+
* Generate a short, human-readable server ID.
|
|
1257
|
+
*
|
|
1258
|
+
* Uses Node.js crypto.randomBytes for cryptographically secure randomness
|
|
1259
|
+
* with rejection sampling to avoid modulo bias.
|
|
1260
|
+
*
|
|
1261
|
+
* The generated ID:
|
|
1262
|
+
* - Is 6 characters long by default
|
|
1263
|
+
* - Uses only lowercase alphanumeric characters
|
|
1264
|
+
* - Excludes confusing characters (0, O, 1, l, I)
|
|
1265
|
+
*
|
|
1266
|
+
* @param length - Length of the ID to generate (default: 6)
|
|
1267
|
+
* @returns A random, human-readable ID
|
|
1268
|
+
*
|
|
1269
|
+
* @example
|
|
1270
|
+
* generateServerId() // "abc234"
|
|
1271
|
+
* generateServerId(4) // "x7mn"
|
|
1272
|
+
*/
|
|
1273
|
+
function generateServerId(length = DEFAULT_ID_LENGTH) {
|
|
1274
|
+
const charsetLength = 31;
|
|
1275
|
+
const maxUnbiased = Math.floor(256 / charsetLength) * charsetLength - 1;
|
|
1276
|
+
let result = "";
|
|
1277
|
+
let remaining = length;
|
|
1278
|
+
while (remaining > 0) {
|
|
1279
|
+
const bytes = randomBytes(remaining);
|
|
1280
|
+
for (let i = 0; i < bytes.length && remaining > 0; i++) {
|
|
1281
|
+
const byte = bytes[i];
|
|
1282
|
+
if (byte > maxUnbiased) continue;
|
|
1283
|
+
result += CHARSET[byte % charsetLength];
|
|
1284
|
+
remaining--;
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
return result;
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1211
1290
|
//#endregion
|
|
1212
1291
|
//#region src/services/SkillService.ts
|
|
1213
1292
|
/**
|
|
@@ -1308,13 +1387,13 @@ var SkillService = class {
|
|
|
1308
1387
|
if (this.cachedSkills !== null) return this.cachedSkills;
|
|
1309
1388
|
const skills = [];
|
|
1310
1389
|
const loadedSkillNames = /* @__PURE__ */ new Set();
|
|
1311
|
-
|
|
1390
|
+
const allDirSkills = await Promise.all(this.skillPaths.map(async (skillPath) => {
|
|
1312
1391
|
const skillsDir = isAbsolute(skillPath) ? skillPath : join(this.cwd, skillPath);
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1392
|
+
return this.loadSkillsFromDirectory(skillsDir, "project");
|
|
1393
|
+
}));
|
|
1394
|
+
for (const dirSkills of allDirSkills) for (const skill of dirSkills) if (!loadedSkillNames.has(skill.name)) {
|
|
1395
|
+
skills.push(skill);
|
|
1396
|
+
loadedSkillNames.add(skill.name);
|
|
1318
1397
|
}
|
|
1319
1398
|
this.cachedSkills = skills;
|
|
1320
1399
|
this.skillsByName = new Map(skills.map((skill) => [skill.name, skill]));
|
|
@@ -1352,9 +1431,15 @@ var SkillService = class {
|
|
|
1352
1431
|
*/
|
|
1353
1432
|
async startWatching() {
|
|
1354
1433
|
this.stopWatching();
|
|
1355
|
-
|
|
1434
|
+
const existenceChecks = await Promise.all(this.skillPaths.map(async (skillPath) => {
|
|
1356
1435
|
const skillsDir = isAbsolute(skillPath) ? skillPath : join(this.cwd, skillPath);
|
|
1357
|
-
|
|
1436
|
+
return {
|
|
1437
|
+
skillsDir,
|
|
1438
|
+
exists: await pathExists(skillsDir)
|
|
1439
|
+
};
|
|
1440
|
+
}));
|
|
1441
|
+
for (const { skillsDir, exists } of existenceChecks) {
|
|
1442
|
+
if (!exists) continue;
|
|
1358
1443
|
const abortController = new AbortController();
|
|
1359
1444
|
this.watchers.push(abortController);
|
|
1360
1445
|
this.watchDirectory(skillsDir, abortController.signal).catch((error) => {
|
|
@@ -1412,34 +1497,49 @@ var SkillService = class {
|
|
|
1412
1497
|
} catch (error) {
|
|
1413
1498
|
throw new SkillLoadError(`Failed to read skills directory: ${error instanceof Error ? error.message : "Unknown error"}`, dirPath, error instanceof Error ? error : void 0);
|
|
1414
1499
|
}
|
|
1415
|
-
|
|
1500
|
+
const entryStats = await Promise.all(entries.map(async (entry) => {
|
|
1416
1501
|
const entryPath = join(dirPath, entry);
|
|
1417
|
-
let entryStat;
|
|
1418
1502
|
try {
|
|
1419
|
-
|
|
1503
|
+
return {
|
|
1504
|
+
entry,
|
|
1505
|
+
entryPath,
|
|
1506
|
+
stat: await stat(entryPath),
|
|
1507
|
+
error: null
|
|
1508
|
+
};
|
|
1420
1509
|
} catch (error) {
|
|
1421
1510
|
console.warn(`Skipping entry ${entryPath}: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
1422
|
-
|
|
1511
|
+
return {
|
|
1512
|
+
entry,
|
|
1513
|
+
entryPath,
|
|
1514
|
+
stat: null,
|
|
1515
|
+
error
|
|
1516
|
+
};
|
|
1423
1517
|
}
|
|
1518
|
+
}));
|
|
1519
|
+
const skillFilesToLoad = [];
|
|
1520
|
+
for (const { entry, entryPath, stat: entryStat } of entryStats) {
|
|
1521
|
+
if (!entryStat) continue;
|
|
1424
1522
|
if (entryStat.isDirectory()) {
|
|
1425
1523
|
const skillFilePath = join(entryPath, "SKILL.md");
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
if (
|
|
1524
|
+
skillFilesToLoad.push({
|
|
1525
|
+
filePath: skillFilePath,
|
|
1526
|
+
isRootLevel: false
|
|
1527
|
+
});
|
|
1528
|
+
} else if (entry === "SKILL.md") skillFilesToLoad.push({
|
|
1529
|
+
filePath: entryPath,
|
|
1530
|
+
isRootLevel: true
|
|
1531
|
+
});
|
|
1532
|
+
}
|
|
1533
|
+
const loadResults = await Promise.all(skillFilesToLoad.map(async ({ filePath, isRootLevel }) => {
|
|
1534
|
+
try {
|
|
1535
|
+
if (!isRootLevel && !await pathExists(filePath)) return null;
|
|
1536
|
+
return await this.loadSkillFile(filePath, location);
|
|
1438
1537
|
} catch (error) {
|
|
1439
|
-
console.warn(`Skipping skill at ${
|
|
1440
|
-
|
|
1538
|
+
console.warn(`Skipping skill at ${filePath}: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
1539
|
+
return null;
|
|
1441
1540
|
}
|
|
1442
|
-
}
|
|
1541
|
+
}));
|
|
1542
|
+
for (const skill of loadResults) if (skill) skills.push(skill);
|
|
1443
1543
|
return skills;
|
|
1444
1544
|
}
|
|
1445
1545
|
/**
|
|
@@ -1485,7 +1585,7 @@ var SkillService = class {
|
|
|
1485
1585
|
* Prefix added to skill names when they clash with MCP tool names.
|
|
1486
1586
|
* This ensures skills can be uniquely identified even when a tool has the same name.
|
|
1487
1587
|
*/
|
|
1488
|
-
const SKILL_PREFIX
|
|
1588
|
+
const SKILL_PREFIX = "skill__";
|
|
1489
1589
|
/**
|
|
1490
1590
|
* Log prefix for skill detection messages.
|
|
1491
1591
|
* Used to easily filter skill detection logs in stderr output.
|
|
@@ -1496,10 +1596,15 @@ const LOG_PREFIX_SKILL_DETECTION = "[skill-detection]";
|
|
|
1496
1596
|
* Format: "prompt:{serverName}:{promptName}"
|
|
1497
1597
|
*/
|
|
1498
1598
|
const PROMPT_LOCATION_PREFIX = "prompt:";
|
|
1599
|
+
/**
|
|
1600
|
+
* Default server ID used when no ID is provided via CLI or config.
|
|
1601
|
+
* This fallback is used when auto-generation also fails.
|
|
1602
|
+
*/
|
|
1603
|
+
const DEFAULT_SERVER_ID = "unknown";
|
|
1499
1604
|
|
|
1500
1605
|
//#endregion
|
|
1501
1606
|
//#region src/templates/toolkit-description.liquid?raw
|
|
1502
|
-
var toolkit_description_default = "<toolkit>\n<instruction>\nBefore you use any capabilities below, you MUST call this tool with a list of names to learn how to use them properly; this includes:\n- For tools: Arguments schema needed to pass to use_tool\n- For skills: Detailed instructions that will expand when invoked (Prefer to be explored first when relevant)\n\nThis tool is optimized for batch queries - you can request multiple capabilities at once for better performance.\n\nHow to invoke:\n- For MCP tools: Use use_tool with toolName and toolArgs based on the schema\n- For skills: Use this tool with the skill name to get expanded instructions\n</instruction>\n\n<available_capabilities>\n{% for server in servers -%}\n<group name=\"{{ server.name }}\">\n{% if server.instruction -%}\n<group_instruction>{{ server.instruction }}</group_instruction>\n{% endif -%}\n{% if server.omitToolDescription -%}\n{% for toolName in server.toolNames -%}\n<item name=\"{{ toolName }}\"></item>\n{% endfor -%}\n{% else -%}\n{% for tool in server.tools -%}\n<item name=\"{{ tool.displayName }}\"><description>{{ tool.description | default: \"No description\" }}</description></item>\n{% endfor -%}\n{% endif -%}\n</group>\n{% endfor -%}\n{% if skills.size > 0 -%}\n<group name=\"skills\">\n{% for skill in skills -%}\n<item name=\"{{ skill.displayName }}\"><description>{{ skill.description }}</description></item>\n{% endfor -%}\n</group>\n{% endif -%}\n</available_capabilities>\n</toolkit>\n";
|
|
1607
|
+
var toolkit_description_default = "<toolkit id=\"{{ serverId }}\">\n<instruction>\nBefore you use any capabilities below, you MUST call this tool with a list of names to learn how to use them properly; this includes:\n- For tools: Arguments schema needed to pass to use_tool\n- For skills: Detailed instructions that will expand when invoked (Prefer to be explored first when relevant)\n\nThis tool is optimized for batch queries - you can request multiple capabilities at once for better performance.\n\nHow to invoke:\n- For MCP tools: Use use_tool with toolName and toolArgs based on the schema\n- For skills: Use this tool with the skill name to get expanded instructions\n</instruction>\n\n<available_capabilities>\n{% for server in servers -%}\n<group name=\"{{ server.name }}\">\n{% if server.instruction -%}\n<group_instruction>{{ server.instruction }}</group_instruction>\n{% endif -%}\n{% if server.omitToolDescription -%}\n{% for toolName in server.toolNames -%}\n<item name=\"{{ toolName }}\"></item>\n{% endfor -%}\n{% else -%}\n{% for tool in server.tools -%}\n<item name=\"{{ tool.displayName }}\"><description>{{ tool.description | default: \"No description\" }}</description></item>\n{% endfor -%}\n{% endif -%}\n</group>\n{% endfor -%}\n{% if skills.size > 0 -%}\n<group name=\"skills\">\n{% for skill in skills -%}\n<item name=\"{{ skill.displayName }}\"><description>{{ skill.description }}</description></item>\n{% endfor -%}\n</group>\n{% endif -%}\n</available_capabilities>\n</toolkit>\n";
|
|
1503
1608
|
|
|
1504
1609
|
//#endregion
|
|
1505
1610
|
//#region src/tools/DescribeToolsTool.ts
|
|
@@ -1538,14 +1643,18 @@ var DescribeToolsTool = class DescribeToolsTool {
|
|
|
1538
1643
|
liquid = new Liquid();
|
|
1539
1644
|
/** Cache for auto-detected skills from prompt front-matter */
|
|
1540
1645
|
autoDetectedSkillsCache = null;
|
|
1646
|
+
/** Unique server identifier for this one-mcp instance */
|
|
1647
|
+
serverId;
|
|
1541
1648
|
/**
|
|
1542
1649
|
* Creates a new DescribeToolsTool instance
|
|
1543
1650
|
* @param clientManager - The MCP client manager for accessing remote servers
|
|
1544
1651
|
* @param skillService - Optional skill service for loading skills
|
|
1652
|
+
* @param serverId - Unique server identifier for this one-mcp instance
|
|
1545
1653
|
*/
|
|
1546
|
-
constructor(clientManager, skillService) {
|
|
1654
|
+
constructor(clientManager, skillService, serverId) {
|
|
1547
1655
|
this.clientManager = clientManager;
|
|
1548
1656
|
this.skillService = skillService;
|
|
1657
|
+
this.serverId = serverId || DEFAULT_SERVER_ID;
|
|
1549
1658
|
}
|
|
1550
1659
|
/**
|
|
1551
1660
|
* Clears the cached auto-detected skills from prompt front-matter.
|
|
@@ -1572,44 +1681,42 @@ var DescribeToolsTool = class DescribeToolsTool {
|
|
|
1572
1681
|
async detectSkillsFromPromptFrontMatter() {
|
|
1573
1682
|
if (this.autoDetectedSkillsCache !== null) return this.autoDetectedSkillsCache;
|
|
1574
1683
|
const clients = this.clientManager.getAllClients();
|
|
1575
|
-
const autoDetectedSkills = [];
|
|
1576
1684
|
let listPromptsFailures = 0;
|
|
1577
1685
|
let fetchPromptFailures = 0;
|
|
1578
|
-
const
|
|
1579
|
-
|
|
1686
|
+
const autoDetectedSkills = (await Promise.all(clients.map(async (client) => {
|
|
1687
|
+
const detectedSkills = [];
|
|
1580
1688
|
const configuredPromptNames = new Set(client.prompts ? Object.keys(client.prompts) : []);
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
const
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
}
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
}
|
|
1609
|
-
}
|
|
1610
|
-
|
|
1611
|
-
}
|
|
1612
|
-
await Promise.all(fetchPromises);
|
|
1689
|
+
try {
|
|
1690
|
+
const prompts = await client.listPrompts();
|
|
1691
|
+
if (!prompts || prompts.length === 0) return detectedSkills;
|
|
1692
|
+
const promptResults = await Promise.all(prompts.map(async (promptInfo) => {
|
|
1693
|
+
if (configuredPromptNames.has(promptInfo.name)) return null;
|
|
1694
|
+
try {
|
|
1695
|
+
const skillExtraction = extractSkillFrontMatter(((await client.getPrompt(promptInfo.name)).messages || []).map((m) => {
|
|
1696
|
+
const content = m.content;
|
|
1697
|
+
if (typeof content === "string") return content;
|
|
1698
|
+
if (content && typeof content === "object" && "text" in content) return String(content.text);
|
|
1699
|
+
return "";
|
|
1700
|
+
}).join("\n"));
|
|
1701
|
+
if (skillExtraction) return {
|
|
1702
|
+
serverName: client.serverName,
|
|
1703
|
+
promptName: promptInfo.name,
|
|
1704
|
+
skill: skillExtraction.skill
|
|
1705
|
+
};
|
|
1706
|
+
return null;
|
|
1707
|
+
} catch (error) {
|
|
1708
|
+
fetchPromptFailures++;
|
|
1709
|
+
console.error(`${LOG_PREFIX_SKILL_DETECTION} Failed to fetch prompt '${promptInfo.name}' from ${client.serverName}: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
1710
|
+
return null;
|
|
1711
|
+
}
|
|
1712
|
+
}));
|
|
1713
|
+
for (const result of promptResults) if (result) detectedSkills.push(result);
|
|
1714
|
+
} catch (error) {
|
|
1715
|
+
listPromptsFailures++;
|
|
1716
|
+
console.error(`${LOG_PREFIX_SKILL_DETECTION} Failed to list prompts from ${client.serverName}: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
1717
|
+
}
|
|
1718
|
+
return detectedSkills;
|
|
1719
|
+
}))).flat();
|
|
1613
1720
|
if (listPromptsFailures > 0 || fetchPromptFailures > 0) console.error(`${LOG_PREFIX_SKILL_DETECTION} Completed with ${listPromptsFailures} server failure(s) and ${fetchPromptFailures} prompt failure(s). Detected ${autoDetectedSkills.length} skill(s).`);
|
|
1614
1721
|
this.autoDetectedSkillsCache = autoDetectedSkills;
|
|
1615
1722
|
return autoDetectedSkills;
|
|
@@ -1770,14 +1877,15 @@ var DescribeToolsTool = class DescribeToolsTool {
|
|
|
1770
1877
|
const clashesWithMcpTool = allToolNames.has(skill.name);
|
|
1771
1878
|
return {
|
|
1772
1879
|
name: skill.name,
|
|
1773
|
-
displayName: clashesWithMcpTool ? `${SKILL_PREFIX
|
|
1880
|
+
displayName: clashesWithMcpTool ? `${SKILL_PREFIX}${skill.name}` : skill.name,
|
|
1774
1881
|
description: skill.description
|
|
1775
1882
|
};
|
|
1776
1883
|
});
|
|
1777
1884
|
return {
|
|
1778
1885
|
content: await this.liquid.parseAndRender(toolkit_description_default, {
|
|
1779
1886
|
servers,
|
|
1780
|
-
skills
|
|
1887
|
+
skills,
|
|
1888
|
+
serverId: this.serverId
|
|
1781
1889
|
}),
|
|
1782
1890
|
toolNames: allToolNames
|
|
1783
1891
|
};
|
|
@@ -1859,40 +1967,42 @@ var DescribeToolsTool = class DescribeToolsTool {
|
|
|
1859
1967
|
serverToolsMap.set(client.serverName, []);
|
|
1860
1968
|
}
|
|
1861
1969
|
}));
|
|
1862
|
-
const
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1970
|
+
const lookupResults = await Promise.all(toolNames.map(async (requestedName) => {
|
|
1971
|
+
const result$1 = {
|
|
1972
|
+
tools: [],
|
|
1973
|
+
skills: [],
|
|
1974
|
+
notFound: null
|
|
1975
|
+
};
|
|
1976
|
+
if (requestedName.startsWith(SKILL_PREFIX)) {
|
|
1977
|
+
const skillName = requestedName.slice(SKILL_PREFIX.length);
|
|
1868
1978
|
if (this.skillService) {
|
|
1869
1979
|
const skill = await this.skillService.getSkill(skillName);
|
|
1870
1980
|
if (skill) {
|
|
1871
|
-
|
|
1981
|
+
result$1.skills.push({
|
|
1872
1982
|
name: skill.name,
|
|
1873
1983
|
location: skill.basePath,
|
|
1874
1984
|
instructions: formatSkillInstructions(skill.name, skill.content)
|
|
1875
1985
|
});
|
|
1876
|
-
|
|
1986
|
+
return result$1;
|
|
1877
1987
|
}
|
|
1878
1988
|
}
|
|
1879
1989
|
const promptSkillContent = await this.getPromptSkillContent(skillName);
|
|
1880
1990
|
if (promptSkillContent) {
|
|
1881
|
-
|
|
1882
|
-
|
|
1991
|
+
result$1.skills.push(promptSkillContent);
|
|
1992
|
+
return result$1;
|
|
1883
1993
|
}
|
|
1884
|
-
|
|
1885
|
-
|
|
1994
|
+
result$1.notFound = requestedName;
|
|
1995
|
+
return result$1;
|
|
1886
1996
|
}
|
|
1887
1997
|
const { serverName, actualToolName } = parseToolName(requestedName);
|
|
1888
1998
|
if (serverName) {
|
|
1889
1999
|
const serverTools = serverToolsMap.get(serverName);
|
|
1890
2000
|
if (!serverTools) {
|
|
1891
|
-
|
|
1892
|
-
|
|
2001
|
+
result$1.notFound = requestedName;
|
|
2002
|
+
return result$1;
|
|
1893
2003
|
}
|
|
1894
2004
|
const tool = serverTools.find((t) => t.name === actualToolName);
|
|
1895
|
-
if (tool)
|
|
2005
|
+
if (tool) result$1.tools.push({
|
|
1896
2006
|
server: serverName,
|
|
1897
2007
|
tool: {
|
|
1898
2008
|
name: tool.name,
|
|
@@ -1900,52 +2010,61 @@ var DescribeToolsTool = class DescribeToolsTool {
|
|
|
1900
2010
|
inputSchema: tool.inputSchema
|
|
1901
2011
|
}
|
|
1902
2012
|
});
|
|
1903
|
-
else
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
}
|
|
1918
|
-
const promptSkillContent = await this.getPromptSkillContent(actualToolName);
|
|
1919
|
-
if (promptSkillContent) {
|
|
1920
|
-
foundSkills.push(promptSkillContent);
|
|
1921
|
-
continue;
|
|
2013
|
+
else result$1.notFound = requestedName;
|
|
2014
|
+
return result$1;
|
|
2015
|
+
}
|
|
2016
|
+
const servers = toolToServers.get(actualToolName);
|
|
2017
|
+
if (!servers || servers.length === 0) {
|
|
2018
|
+
if (this.skillService) {
|
|
2019
|
+
const skill = await this.skillService.getSkill(actualToolName);
|
|
2020
|
+
if (skill) {
|
|
2021
|
+
result$1.skills.push({
|
|
2022
|
+
name: skill.name,
|
|
2023
|
+
location: skill.basePath,
|
|
2024
|
+
instructions: formatSkillInstructions(skill.name, skill.content)
|
|
2025
|
+
});
|
|
2026
|
+
return result$1;
|
|
1922
2027
|
}
|
|
1923
|
-
notFoundItems.push(requestedName);
|
|
1924
|
-
continue;
|
|
1925
2028
|
}
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
server,
|
|
1931
|
-
tool: {
|
|
1932
|
-
name: tool.name,
|
|
1933
|
-
description: tool.description,
|
|
1934
|
-
inputSchema: tool.inputSchema
|
|
1935
|
-
}
|
|
1936
|
-
});
|
|
1937
|
-
} else for (const server of servers) {
|
|
1938
|
-
const tool = serverToolsMap.get(server).find((t) => t.name === actualToolName);
|
|
1939
|
-
foundTools.push({
|
|
1940
|
-
server,
|
|
1941
|
-
tool: {
|
|
1942
|
-
name: tool.name,
|
|
1943
|
-
description: tool.description,
|
|
1944
|
-
inputSchema: tool.inputSchema
|
|
1945
|
-
}
|
|
1946
|
-
});
|
|
2029
|
+
const promptSkillContent = await this.getPromptSkillContent(actualToolName);
|
|
2030
|
+
if (promptSkillContent) {
|
|
2031
|
+
result$1.skills.push(promptSkillContent);
|
|
2032
|
+
return result$1;
|
|
1947
2033
|
}
|
|
2034
|
+
result$1.notFound = requestedName;
|
|
2035
|
+
return result$1;
|
|
2036
|
+
}
|
|
2037
|
+
if (servers.length === 1) {
|
|
2038
|
+
const server = servers[0];
|
|
2039
|
+
const tool = serverToolsMap.get(server).find((t) => t.name === actualToolName);
|
|
2040
|
+
result$1.tools.push({
|
|
2041
|
+
server,
|
|
2042
|
+
tool: {
|
|
2043
|
+
name: tool.name,
|
|
2044
|
+
description: tool.description,
|
|
2045
|
+
inputSchema: tool.inputSchema
|
|
2046
|
+
}
|
|
2047
|
+
});
|
|
2048
|
+
} else for (const server of servers) {
|
|
2049
|
+
const tool = serverToolsMap.get(server).find((t) => t.name === actualToolName);
|
|
2050
|
+
result$1.tools.push({
|
|
2051
|
+
server,
|
|
2052
|
+
tool: {
|
|
2053
|
+
name: tool.name,
|
|
2054
|
+
description: tool.description,
|
|
2055
|
+
inputSchema: tool.inputSchema
|
|
2056
|
+
}
|
|
2057
|
+
});
|
|
1948
2058
|
}
|
|
2059
|
+
return result$1;
|
|
2060
|
+
}));
|
|
2061
|
+
const foundTools = [];
|
|
2062
|
+
const foundSkills = [];
|
|
2063
|
+
const notFoundItems = [];
|
|
2064
|
+
for (const result$1 of lookupResults) {
|
|
2065
|
+
foundTools.push(...result$1.tools);
|
|
2066
|
+
foundSkills.push(...result$1.skills);
|
|
2067
|
+
if (result$1.notFound) notFoundItems.push(result$1.notFound);
|
|
1949
2068
|
}
|
|
1950
2069
|
if (foundTools.length === 0 && foundSkills.length === 0) return {
|
|
1951
2070
|
content: [{
|
|
@@ -1988,10 +2107,6 @@ var DescribeToolsTool = class DescribeToolsTool {
|
|
|
1988
2107
|
//#endregion
|
|
1989
2108
|
//#region src/tools/UseToolTool.ts
|
|
1990
2109
|
/**
|
|
1991
|
-
* Prefix used to identify skill invocations (e.g., skill__pdf)
|
|
1992
|
-
*/
|
|
1993
|
-
const SKILL_PREFIX = "skill__";
|
|
1994
|
-
/**
|
|
1995
2110
|
* UseToolTool executes MCP tools and skills with proper error handling.
|
|
1996
2111
|
*
|
|
1997
2112
|
* This tool supports three invocation patterns:
|
|
@@ -2008,14 +2123,18 @@ var UseToolTool = class UseToolTool {
|
|
|
2008
2123
|
static TOOL_NAME = "use_tool";
|
|
2009
2124
|
clientManager;
|
|
2010
2125
|
skillService;
|
|
2126
|
+
/** Unique server identifier for this one-mcp instance */
|
|
2127
|
+
serverId;
|
|
2011
2128
|
/**
|
|
2012
2129
|
* Creates a new UseToolTool instance
|
|
2013
2130
|
* @param clientManager - The MCP client manager for accessing remote servers
|
|
2014
2131
|
* @param skillService - Optional skill service for loading and executing skills
|
|
2132
|
+
* @param serverId - Unique server identifier for this one-mcp instance
|
|
2015
2133
|
*/
|
|
2016
|
-
constructor(clientManager, skillService) {
|
|
2134
|
+
constructor(clientManager, skillService, serverId) {
|
|
2017
2135
|
this.clientManager = clientManager;
|
|
2018
2136
|
this.skillService = skillService;
|
|
2137
|
+
this.serverId = serverId || DEFAULT_SERVER_ID;
|
|
2019
2138
|
}
|
|
2020
2139
|
/**
|
|
2021
2140
|
* Returns the MCP tool definition with name, description, and input schema.
|
|
@@ -2031,6 +2150,8 @@ var UseToolTool = class UseToolTool {
|
|
|
2031
2150
|
description: `Execute an MCP tool (NOT Skill) with provided arguments. You MUST call describe_tools first to discover the tool's correct arguments. Then to use tool:
|
|
2032
2151
|
- Provide toolName and toolArgs based on the schema
|
|
2033
2152
|
- If multiple servers provide the same tool, specify serverName
|
|
2153
|
+
|
|
2154
|
+
IMPORTANT: Only use tools discovered from describe_tools with id="${this.serverId}".
|
|
2034
2155
|
`,
|
|
2035
2156
|
inputSchema: {
|
|
2036
2157
|
type: "object",
|
|
@@ -2117,7 +2238,7 @@ var UseToolTool = class UseToolTool {
|
|
|
2117
2238
|
try {
|
|
2118
2239
|
const { toolName: inputToolName, toolArgs = {} } = input;
|
|
2119
2240
|
if (inputToolName.startsWith(SKILL_PREFIX)) {
|
|
2120
|
-
const skillName = inputToolName.slice(
|
|
2241
|
+
const skillName = inputToolName.slice(SKILL_PREFIX.length);
|
|
2121
2242
|
if (this.skillService) {
|
|
2122
2243
|
const skill = await this.skillService.getSkill(skillName);
|
|
2123
2244
|
if (skill) return this.executeSkill(skill);
|
|
@@ -2252,6 +2373,7 @@ async function createServer(options) {
|
|
|
2252
2373
|
} });
|
|
2253
2374
|
const clientManager = new McpClientManagerService();
|
|
2254
2375
|
let configSkills;
|
|
2376
|
+
let configId;
|
|
2255
2377
|
if (options?.configFilePath) {
|
|
2256
2378
|
let config;
|
|
2257
2379
|
try {
|
|
@@ -2263,6 +2385,7 @@ async function createServer(options) {
|
|
|
2263
2385
|
throw new Error(`Failed to load MCP configuration from '${options.configFilePath}': ${error instanceof Error ? error.message : String(error)}`);
|
|
2264
2386
|
}
|
|
2265
2387
|
configSkills = config.skills;
|
|
2388
|
+
configId = config.id;
|
|
2266
2389
|
const failedConnections = [];
|
|
2267
2390
|
const connectionPromises = Object.entries(config.mcpServers).map(async ([serverName, serverConfig]) => {
|
|
2268
2391
|
try {
|
|
@@ -2281,13 +2404,15 @@ async function createServer(options) {
|
|
|
2281
2404
|
if (failedConnections.length > 0 && failedConnections.length < Object.keys(config.mcpServers).length) console.error(`Warning: Some MCP server connections failed: ${failedConnections.map((f) => f.serverName).join(", ")}`);
|
|
2282
2405
|
if (failedConnections.length > 0 && failedConnections.length === Object.keys(config.mcpServers).length) throw new Error(`All MCP server connections failed: ${failedConnections.map((f) => `${f.serverName}: ${f.error.message}`).join(", ")}`);
|
|
2283
2406
|
}
|
|
2407
|
+
const serverId = options?.serverId || configId || generateServerId();
|
|
2408
|
+
console.error(`[one-mcp] Server ID: ${serverId}`);
|
|
2284
2409
|
const skillsConfig = options?.skills || configSkills;
|
|
2285
2410
|
const toolsRef = { describeTools: null };
|
|
2286
2411
|
const skillService = skillsConfig && skillsConfig.paths.length > 0 ? new SkillService(process.cwd(), skillsConfig.paths, { onCacheInvalidated: () => {
|
|
2287
2412
|
toolsRef.describeTools?.clearAutoDetectedSkillsCache();
|
|
2288
2413
|
} }) : void 0;
|
|
2289
|
-
const describeTools = new DescribeToolsTool(clientManager, skillService);
|
|
2290
|
-
const useTool = new UseToolTool(clientManager, skillService);
|
|
2414
|
+
const describeTools = new DescribeToolsTool(clientManager, skillService, serverId);
|
|
2415
|
+
const useTool = new UseToolTool(clientManager, skillService, serverId);
|
|
2291
2416
|
toolsRef.describeTools = describeTools;
|
|
2292
2417
|
if (skillService) skillService.startWatching().catch((error) => {
|
|
2293
2418
|
console.error(`[skill-watcher] File watcher failed (non-critical): ${error instanceof Error ? error.message : "Unknown error"}`);
|