@agiflowai/one-mcp 0.2.3 → 0.2.5

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.
@@ -6,12 +6,16 @@ var __getOwnPropNames = Object.getOwnPropertyNames;
6
6
  var __getProtoOf = Object.getPrototypeOf;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
8
  var __copyProps = (to, from, except, desc) => {
9
- if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
10
- key = keys[i];
11
- if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
12
- get: ((k) => from[k]).bind(null, key),
13
- enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
14
- });
9
+ if (from && typeof from === "object" || typeof from === "function") {
10
+ for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
11
+ key = keys[i];
12
+ if (!__hasOwnProp.call(to, key) && key !== except) {
13
+ __defProp(to, key, {
14
+ get: ((k) => from[k]).bind(null, key),
15
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
16
+ });
17
+ }
18
+ }
15
19
  }
16
20
  return to;
17
21
  };
@@ -34,6 +38,7 @@ let node_os = require("node:os");
34
38
  let __modelcontextprotocol_sdk_client_index_js = require("@modelcontextprotocol/sdk/client/index.js");
35
39
  let __modelcontextprotocol_sdk_client_stdio_js = require("@modelcontextprotocol/sdk/client/stdio.js");
36
40
  let __modelcontextprotocol_sdk_client_sse_js = require("@modelcontextprotocol/sdk/client/sse.js");
41
+ let liquidjs = require("liquidjs");
37
42
  let __modelcontextprotocol_sdk_server_stdio_js = require("@modelcontextprotocol/sdk/server/stdio.js");
38
43
  let __modelcontextprotocol_sdk_server_sse_js = require("@modelcontextprotocol/sdk/server/sse.js");
39
44
  let express = require("express");
@@ -241,11 +246,16 @@ const RemoteConfigSourceSchema = zod.z.object({
241
246
  ]).optional()
242
247
  });
243
248
  /**
249
+ * Skills configuration schema
250
+ */
251
+ const SkillsConfigSchema = zod.z.object({ paths: zod.z.array(zod.z.string()) });
252
+ /**
244
253
  * Full Claude Code MCP configuration schema
245
254
  */
246
255
  const ClaudeCodeMcpConfigSchema = zod.z.object({
247
256
  mcpServers: zod.z.record(zod.z.string(), ClaudeCodeServerConfigSchema),
248
- remoteConfigs: zod.z.array(RemoteConfigSourceSchema).optional()
257
+ remoteConfigs: zod.z.array(RemoteConfigSourceSchema).optional(),
258
+ skills: SkillsConfigSchema.optional()
249
259
  });
250
260
  /**
251
261
  * Internal MCP config format
@@ -293,7 +303,10 @@ const McpServerConfigSchema = zod.z.discriminatedUnion("transport", [
293
303
  /**
294
304
  * Full internal MCP configuration schema
295
305
  */
296
- const InternalMcpConfigSchema = zod.z.object({ mcpServers: zod.z.record(zod.z.string(), McpServerConfigSchema) });
306
+ const InternalMcpConfigSchema = zod.z.object({
307
+ mcpServers: zod.z.record(zod.z.string(), McpServerConfigSchema),
308
+ skills: SkillsConfigSchema.optional()
309
+ });
297
310
  /**
298
311
  * Transform Claude Code config to internal format
299
312
  * Converts standard Claude Code MCP configuration to normalized internal format
@@ -340,7 +353,10 @@ function transformClaudeCodeConfig(claudeConfig) {
340
353
  };
341
354
  }
342
355
  }
343
- return { mcpServers: transformedServers };
356
+ return {
357
+ mcpServers: transformedServers,
358
+ skills: claudeConfig.skills
359
+ };
344
360
  }
345
361
  /**
346
362
  * Parse and validate MCP config from raw JSON
@@ -976,6 +992,261 @@ var McpClientManagerService = class {
976
992
  }
977
993
  };
978
994
 
995
+ //#endregion
996
+ //#region src/services/SkillService.ts
997
+ /**
998
+ * SkillService
999
+ *
1000
+ * DESIGN PATTERNS:
1001
+ * - Service pattern for business logic encapsulation
1002
+ * - Single responsibility principle
1003
+ * - Lazy loading pattern for skill discovery
1004
+ *
1005
+ * CODING STANDARDS:
1006
+ * - Use async/await for asynchronous operations
1007
+ * - Throw descriptive errors for error cases
1008
+ * - Keep methods focused and well-named
1009
+ * - Document complex logic with comments
1010
+ *
1011
+ * AVOID:
1012
+ * - Mixing concerns (keep focused on single domain)
1013
+ * - Direct tool implementation (services should be tool-agnostic)
1014
+ */
1015
+ /**
1016
+ * Error thrown when skill loading fails
1017
+ */
1018
+ var SkillLoadError = class extends Error {
1019
+ constructor(message, filePath, cause) {
1020
+ super(message);
1021
+ this.filePath = filePath;
1022
+ this.cause = cause;
1023
+ this.name = "SkillLoadError";
1024
+ }
1025
+ };
1026
+ /**
1027
+ * Check if a path exists asynchronously
1028
+ * @param path - Path to check
1029
+ * @returns true if path exists, false otherwise
1030
+ * @throws Error for unexpected filesystem errors (permission denied, etc.)
1031
+ */
1032
+ async function pathExists(path) {
1033
+ try {
1034
+ await (0, node_fs_promises.access)(path);
1035
+ return true;
1036
+ } catch (error) {
1037
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") return false;
1038
+ throw new Error(`Failed to check path existence for "${path}": ${error instanceof Error ? error.message : "Unknown error"}`);
1039
+ }
1040
+ }
1041
+ /**
1042
+ * Service for loading and managing skills from configured skill directories.
1043
+ *
1044
+ * Skills are markdown files with YAML frontmatter that can be invoked via
1045
+ * the skill__ prefix in describe_tools and use_tool.
1046
+ *
1047
+ * Skills are only enabled when explicitly configured via the `skills.paths` array
1048
+ * in the MCP config.
1049
+ *
1050
+ * @example
1051
+ * // Config with skills enabled:
1052
+ * // skills:
1053
+ * // paths:
1054
+ * // - ".claude/skills"
1055
+ * // - "/absolute/path/to/skills"
1056
+ *
1057
+ * const skillService = new SkillService('/project/root', ['.claude/skills']);
1058
+ * const skills = await skillService.getSkills();
1059
+ */
1060
+ var SkillService = class {
1061
+ cwd;
1062
+ skillPaths;
1063
+ cachedSkills = null;
1064
+ skillsByName = null;
1065
+ /**
1066
+ * Creates a new SkillService instance
1067
+ * @param cwd - Current working directory for resolving relative paths
1068
+ * @param skillPaths - Array of paths to skills directories
1069
+ */
1070
+ constructor(cwd, skillPaths) {
1071
+ this.cwd = cwd;
1072
+ this.skillPaths = skillPaths;
1073
+ }
1074
+ /**
1075
+ * Get all available skills from configured directories.
1076
+ * Results are cached after first load.
1077
+ *
1078
+ * Skills from earlier entries in the config take precedence over
1079
+ * skills with the same name from later entries.
1080
+ *
1081
+ * @returns Array of loaded skills
1082
+ * @throws SkillLoadError if a critical error occurs during loading
1083
+ */
1084
+ async getSkills() {
1085
+ if (this.cachedSkills !== null) return this.cachedSkills;
1086
+ const skills = [];
1087
+ const loadedSkillNames = /* @__PURE__ */ new Set();
1088
+ for (const skillPath of this.skillPaths) {
1089
+ const skillsDir = (0, node_path.isAbsolute)(skillPath) ? skillPath : (0, node_path.join)(this.cwd, skillPath);
1090
+ const dirSkills = await this.loadSkillsFromDirectory(skillsDir, "project");
1091
+ for (const skill of dirSkills) if (!loadedSkillNames.has(skill.name)) {
1092
+ skills.push(skill);
1093
+ loadedSkillNames.add(skill.name);
1094
+ }
1095
+ }
1096
+ this.cachedSkills = skills;
1097
+ this.skillsByName = new Map(skills.map((skill) => [skill.name, skill]));
1098
+ return skills;
1099
+ }
1100
+ /**
1101
+ * Get a specific skill by name with O(1) lookup from cache.
1102
+ * @param name - The skill name (without skill__ prefix)
1103
+ * @returns The skill if found, undefined otherwise
1104
+ */
1105
+ async getSkill(name) {
1106
+ if (this.skillsByName === null) await this.getSkills();
1107
+ return this.skillsByName?.get(name);
1108
+ }
1109
+ /**
1110
+ * Clears the cached skills to force a fresh reload on the next getSkills() or getSkill() call.
1111
+ * Use this when skill files have been modified on disk.
1112
+ */
1113
+ clearCache() {
1114
+ this.cachedSkills = null;
1115
+ this.skillsByName = null;
1116
+ }
1117
+ /**
1118
+ * Load skills from a directory.
1119
+ * Supports both flat structure (SKILL.md) and nested structure (name/SKILL.md).
1120
+ *
1121
+ * @param dirPath - Path to the skills directory
1122
+ * @param location - Whether this is a 'project' or 'user' skill directory
1123
+ * @returns Array of successfully loaded skills (skips invalid skills)
1124
+ * @throws SkillLoadError if there's a critical I/O error
1125
+ *
1126
+ * @example
1127
+ * // Load skills from project directory
1128
+ * const skills = await this.loadSkillsFromDirectory('/path/to/.claude/skills', 'project');
1129
+ * // Returns: [{ name: 'pdf', description: '...', location: 'project', ... }]
1130
+ */
1131
+ async loadSkillsFromDirectory(dirPath, location) {
1132
+ const skills = [];
1133
+ try {
1134
+ if (!await pathExists(dirPath)) return skills;
1135
+ } catch (error) {
1136
+ throw new SkillLoadError(`Cannot access skills directory: ${error instanceof Error ? error.message : "Unknown error"}`, dirPath, error instanceof Error ? error : void 0);
1137
+ }
1138
+ let entries;
1139
+ try {
1140
+ entries = await (0, node_fs_promises.readdir)(dirPath);
1141
+ } catch (error) {
1142
+ throw new SkillLoadError(`Failed to read skills directory: ${error instanceof Error ? error.message : "Unknown error"}`, dirPath, error instanceof Error ? error : void 0);
1143
+ }
1144
+ for (const entry of entries) {
1145
+ const entryPath = (0, node_path.join)(dirPath, entry);
1146
+ let entryStat;
1147
+ try {
1148
+ entryStat = await (0, node_fs_promises.stat)(entryPath);
1149
+ } catch (error) {
1150
+ console.warn(`Skipping entry ${entryPath}: ${error instanceof Error ? error.message : "Unknown error"}`);
1151
+ continue;
1152
+ }
1153
+ if (entryStat.isDirectory()) {
1154
+ const skillFilePath = (0, node_path.join)(entryPath, "SKILL.md");
1155
+ try {
1156
+ if (await pathExists(skillFilePath)) {
1157
+ const skill = await this.loadSkillFile(skillFilePath, location);
1158
+ if (skill) skills.push(skill);
1159
+ }
1160
+ } catch (error) {
1161
+ console.warn(`Skipping skill at ${skillFilePath}: ${error instanceof Error ? error.message : "Unknown error"}`);
1162
+ continue;
1163
+ }
1164
+ } else if (entry === "SKILL.md") try {
1165
+ const skill = await this.loadSkillFile(entryPath, location);
1166
+ if (skill) skills.push(skill);
1167
+ } catch (error) {
1168
+ console.warn(`Skipping skill at ${entryPath}: ${error instanceof Error ? error.message : "Unknown error"}`);
1169
+ continue;
1170
+ }
1171
+ }
1172
+ return skills;
1173
+ }
1174
+ /**
1175
+ * Load a single skill file and parse its frontmatter.
1176
+ *
1177
+ * @param filePath - Path to the SKILL.md file
1178
+ * @param location - Whether this is a 'project' or 'user' skill
1179
+ * @returns The loaded skill, or null if the file is invalid (missing required frontmatter)
1180
+ * @throws SkillLoadError if there's an I/O error reading the file
1181
+ *
1182
+ * @example
1183
+ * // Load a skill from a file
1184
+ * const skill = await this.loadSkillFile('/path/to/pdf/SKILL.md', 'project');
1185
+ * // Returns: { name: 'pdf', description: 'PDF skill', location: 'project', content: '...', basePath: '/path/to/pdf' }
1186
+ * // Returns null if frontmatter is missing name or description
1187
+ */
1188
+ async loadSkillFile(filePath, location) {
1189
+ let content;
1190
+ try {
1191
+ content = await (0, node_fs_promises.readFile)(filePath, "utf-8");
1192
+ } catch (error) {
1193
+ throw new SkillLoadError(`Failed to read skill file: ${error instanceof Error ? error.message : "Unknown error"}`, filePath, error instanceof Error ? error : void 0);
1194
+ }
1195
+ const { metadata, body } = this.parseFrontmatter(content);
1196
+ if (!metadata.name || !metadata.description) return null;
1197
+ return {
1198
+ name: metadata.name,
1199
+ description: metadata.description,
1200
+ location,
1201
+ content: body,
1202
+ basePath: (0, node_path.dirname)(filePath)
1203
+ };
1204
+ }
1205
+ /**
1206
+ * Parse YAML frontmatter from markdown content.
1207
+ * Frontmatter is delimited by --- at start and end.
1208
+ *
1209
+ * @param content - Full markdown content with frontmatter
1210
+ * @returns Parsed metadata and body content
1211
+ *
1212
+ * @example
1213
+ * // Input content:
1214
+ * // ---
1215
+ * // name: my-skill
1216
+ * // description: A sample skill
1217
+ * // ---
1218
+ * // # Skill Content
1219
+ * // This is the skill body.
1220
+ *
1221
+ * const result = parseFrontmatter(content);
1222
+ * // result.metadata = { name: 'my-skill', description: 'A sample skill' }
1223
+ * // result.body = '# Skill Content\nThis is the skill body.'
1224
+ */
1225
+ parseFrontmatter(content) {
1226
+ const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/);
1227
+ if (!match) return {
1228
+ metadata: {},
1229
+ body: content
1230
+ };
1231
+ const [, frontmatter, body] = match;
1232
+ const metadata = {};
1233
+ const lines = frontmatter.split("\n");
1234
+ for (const line of lines) {
1235
+ const colonIndex = line.indexOf(":");
1236
+ if (colonIndex > 0) {
1237
+ const key = line.slice(0, colonIndex).trim();
1238
+ let value = line.slice(colonIndex + 1).trim();
1239
+ if (value.startsWith("\"") && value.endsWith("\"") || value.startsWith("'") && value.endsWith("'")) value = value.slice(1, -1);
1240
+ if (key === "name" || key === "description" || key === "license") metadata[key] = value;
1241
+ }
1242
+ }
1243
+ return {
1244
+ metadata,
1245
+ body: body.trim()
1246
+ };
1247
+ }
1248
+ };
1249
+
979
1250
  //#endregion
980
1251
  //#region src/utils/findConfigFile.ts
981
1252
  /**
@@ -1045,15 +1316,89 @@ function parseToolName(toolName) {
1045
1316
  return { actualToolName: toolName };
1046
1317
  }
1047
1318
 
1319
+ //#endregion
1320
+ //#region src/templates/skills-description.liquid?raw
1321
+ var skills_description_default = "{% if skills.size > 0 %}\n<skills>\n<instructions>\nWhen users ask you to perform tasks, check if any of the available skills below can help complete the task more effectively. Skills provide specialized capabilities and domain knowledge.\n\nHow to use skills:\n- Invoke skills using this tool with the skill name only (no arguments)\n- When you invoke a skill, you will see <command-message>The \"{name}\" skill is loading</command-message>\n- The skill's prompt will expand and provide detailed instructions on how to complete the task\n- Examples:\n - `skill: \"pdf\"` - invoke the pdf skill\n - `skill: \"xlsx\"` - invoke the xlsx skill\n - `skill: \"ms-office-suite:pdf\"` - invoke using fully qualified name\n\nImportant:\n- Only use skills listed in <available_skills> below\n- Do not invoke a skill that is already running\n- Do not use this tool for built-in CLI commands (like /help, /clear, etc.)\n</instructions>\n\n<available_skills>\n{% for skill in skills -%}\n<skill-item><name>{{ skill.displayName }}</name><description>{{ skill.description }}</description></skill-item>\n{% endfor -%}\n</available_skills>\n</skills>\n{% endif %}\n\n<usage_instructions>\nBefore you use any tools above, you MUST call this tool with a list of tool names to learn how to use them properly before use_tool; this includes:\n- Arguments schema needed to pass to the tool use\n- Description about each tool\n\nThis tool is optimized for batch queries - you can request multiple tools at once for better performance.\n</usage_instructions>\n";
1322
+
1323
+ //#endregion
1324
+ //#region src/templates/mcp-servers-description.liquid?raw
1325
+ var mcp_servers_description_default = "<mcp_servers>\n{% for server in servers -%}\n<server name=\"{{ server.name }}\">\n{% if server.instruction -%}\n<instruction>{{ server.instruction }}</instruction>\n{% endif -%}\n<tools>\n{% if server.omitToolDescription -%}\n{{ server.toolNames | join: \", \" }}\n{% else -%}\n{% for tool in server.tools -%}\n<tool-item><name>{{ tool.displayName }}</name><description>{{ tool.description | default: \"No description\" }}</description></tool-item>\n{% endfor -%}\n{% endif -%}\n</tools>\n</server>\n{% endfor -%}\n</mcp_servers>\n";
1326
+
1048
1327
  //#endregion
1049
1328
  //#region src/tools/DescribeToolsTool.ts
1329
+ /**
1330
+ * Prefix used to identify skill invocations
1331
+ */
1332
+ const SKILL_PREFIX$1 = "skill__";
1333
+ /**
1334
+ * DescribeToolsTool provides progressive disclosure of MCP tools and skills.
1335
+ *
1336
+ * This tool lists available tools from all connected MCP servers and skills
1337
+ * from the configured skills directories. Users can query for specific tools
1338
+ * or skills to get detailed input schemas and descriptions.
1339
+ *
1340
+ * Tool naming conventions:
1341
+ * - Unique tools: use plain name (e.g., "browser_click")
1342
+ * - Clashing tools: use serverName__toolName format (e.g., "playwright__click")
1343
+ * - Skills: use skill__skillName format (e.g., "skill__pdf")
1344
+ *
1345
+ * @example
1346
+ * const tool = new DescribeToolsTool(clientManager, skillService);
1347
+ * const definition = await tool.getDefinition();
1348
+ * const result = await tool.execute({ toolNames: ['browser_click', 'skill__pdf'] });
1349
+ */
1050
1350
  var DescribeToolsTool = class DescribeToolsTool {
1051
1351
  static TOOL_NAME = "describe_tools";
1052
1352
  clientManager;
1053
- constructor(clientManager) {
1353
+ skillService;
1354
+ liquid = new liquidjs.Liquid();
1355
+ /**
1356
+ * Creates a new DescribeToolsTool instance
1357
+ * @param clientManager - The MCP client manager for accessing remote servers
1358
+ * @param skillService - Optional skill service for loading skills
1359
+ */
1360
+ constructor(clientManager, skillService) {
1054
1361
  this.clientManager = clientManager;
1362
+ this.skillService = skillService;
1055
1363
  }
1056
- async getDefinition() {
1364
+ /**
1365
+ * Builds the skills section of the tool description using a Liquid template.
1366
+ *
1367
+ * Retrieves all available skills from the SkillService and renders them
1368
+ * using the skills-description.liquid template. Skills are only prefixed
1369
+ * with skill__ when their name clashes with an MCP tool or another skill.
1370
+ *
1371
+ * @param mcpToolNames - Set of MCP tool names to check for clashes
1372
+ * @returns Rendered skills section string with available skills list
1373
+ */
1374
+ async buildSkillsSection(mcpToolNames) {
1375
+ const rawSkills = this.skillService ? await this.skillService.getSkills() : [];
1376
+ const skillNameCounts = /* @__PURE__ */ new Map();
1377
+ for (const skill of rawSkills) skillNameCounts.set(skill.name, (skillNameCounts.get(skill.name) || 0) + 1);
1378
+ const skills = rawSkills.map((skill) => {
1379
+ const clashesWithMcpTool = mcpToolNames.has(skill.name);
1380
+ const clashesWithOtherSkill = (skillNameCounts.get(skill.name) || 0) > 1;
1381
+ const needsPrefix = clashesWithMcpTool || clashesWithOtherSkill;
1382
+ return {
1383
+ name: skill.name,
1384
+ displayName: needsPrefix ? `${SKILL_PREFIX$1}${skill.name}` : skill.name,
1385
+ description: skill.description
1386
+ };
1387
+ });
1388
+ return this.liquid.parseAndRender(skills_description_default, { skills });
1389
+ }
1390
+ /**
1391
+ * Builds the MCP servers section of the tool description using a Liquid template.
1392
+ *
1393
+ * Collects all tools from connected MCP servers, detects name clashes,
1394
+ * and renders them using the mcp-servers-description.liquid template.
1395
+ *
1396
+ * Tool names are prefixed with serverName__ when the same tool exists
1397
+ * on multiple servers to avoid ambiguity.
1398
+ *
1399
+ * @returns Object with rendered servers section and set of all tool names for skill clash detection
1400
+ */
1401
+ async buildServersSection() {
1057
1402
  const clients = this.clientManager.getAllClients();
1058
1403
  const toolToServers = /* @__PURE__ */ new Map();
1059
1404
  const serverToolsMap = /* @__PURE__ */ new Map();
@@ -1072,30 +1417,63 @@ var DescribeToolsTool = class DescribeToolsTool {
1072
1417
  serverToolsMap.set(client.serverName, []);
1073
1418
  }
1074
1419
  }));
1075
- const serverDescriptions = clients.map((client) => {
1076
- const tools = serverToolsMap.get(client.serverName) || [];
1077
- const formatToolName = (toolName) => {
1078
- return (toolToServers.get(toolName) || []).length > 1 ? `${client.serverName}__${toolName}` : toolName;
1420
+ /**
1421
+ * Formats tool name with server prefix if the tool exists on multiple servers
1422
+ * @param toolName - The original tool name
1423
+ * @param serverName - The server providing this tool
1424
+ * @returns Tool name prefixed with serverName__ if clashing, otherwise plain name
1425
+ */
1426
+ const formatToolName = (toolName, serverName) => {
1427
+ return (toolToServers.get(toolName) || []).length > 1 ? `${serverName}__${toolName}` : toolName;
1428
+ };
1429
+ const allToolNames = /* @__PURE__ */ new Set();
1430
+ const servers = clients.map((client) => {
1431
+ const formattedTools = (serverToolsMap.get(client.serverName) || []).map((t) => ({
1432
+ displayName: formatToolName(t.name, client.serverName),
1433
+ description: t.description
1434
+ }));
1435
+ for (const tool of formattedTools) allToolNames.add(tool.displayName);
1436
+ return {
1437
+ name: client.serverName,
1438
+ instruction: client.serverInstruction,
1439
+ omitToolDescription: client.omitToolDescription || false,
1440
+ tools: formattedTools,
1441
+ toolNames: formattedTools.map((t) => t.displayName)
1079
1442
  };
1080
- const toolList = client.omitToolDescription ? tools.map((t) => formatToolName(t.name)).join(", ") : tools.map((t) => `${formatToolName(t.name)}: """${t.description || "No description"}"""`).join("\n");
1081
- const instructionLine = client.serverInstruction ? `\n"""${client.serverInstruction}"""` : "";
1082
- return `\n\n### Server: ${client.serverName}${instructionLine}\n\n- Available tools:\n${toolList || "No tools available"}`;
1083
1443
  });
1444
+ return {
1445
+ content: await this.liquid.parseAndRender(mcp_servers_description_default, { servers }),
1446
+ toolNames: allToolNames
1447
+ };
1448
+ }
1449
+ /**
1450
+ * Gets the tool definition including available servers, tools, and skills.
1451
+ *
1452
+ * The definition includes:
1453
+ * - List of all connected MCP servers with their available tools
1454
+ * - List of available skills with skill__ prefix
1455
+ * - Usage instructions for querying tool/skill details
1456
+ *
1457
+ * Tool names are prefixed with serverName__ when the same tool name
1458
+ * exists on multiple servers to avoid ambiguity.
1459
+ *
1460
+ * @returns Tool definition with description and input schema
1461
+ */
1462
+ async getDefinition() {
1463
+ const serversResult = await this.buildServersSection();
1464
+ const skillsSection = await this.buildSkillsSection(serversResult.toolNames);
1084
1465
  return {
1085
1466
  name: DescribeToolsTool.TOOL_NAME,
1086
- description: `## Available MCP Servers:${serverDescriptions.join("")}
1087
-
1088
- ## Usage:
1089
- Before you use any tools above, you MUST call this tool with a list of tool names to learn how to use them properly before use_tool; this includes:
1090
- - Arguments schema needed to pass to the tool use
1091
- - Description about each tool
1092
-
1093
- This tool is optimized for batch queries - you can request multiple tools at once for better performance.`,
1467
+ description: `${serversResult.content}
1468
+ ${skillsSection}`,
1094
1469
  inputSchema: {
1095
1470
  type: "object",
1096
1471
  properties: { toolNames: {
1097
1472
  type: "array",
1098
- items: { type: "string" },
1473
+ items: {
1474
+ type: "string",
1475
+ minLength: 1
1476
+ },
1099
1477
  description: "List of tool names to get detailed information about",
1100
1478
  minItems: 1
1101
1479
  } },
@@ -1104,6 +1482,17 @@ This tool is optimized for batch queries - you can request multiple tools at onc
1104
1482
  }
1105
1483
  };
1106
1484
  }
1485
+ /**
1486
+ * Executes tool description lookup for the requested tool and skill names.
1487
+ *
1488
+ * Handles three types of lookups:
1489
+ * 1. skill__name - Returns skill information from SkillService
1490
+ * 2. serverName__toolName - Returns tool from specific server
1491
+ * 3. plainToolName - Returns tool(s) from all servers (multiple if clashing)
1492
+ *
1493
+ * @param input - Object containing toolNames array
1494
+ * @returns CallToolResult with tool/skill descriptions or error
1495
+ */
1107
1496
  async execute(input) {
1108
1497
  try {
1109
1498
  const { toolNames } = input;
@@ -1121,9 +1510,13 @@ This tool is optimized for batch queries - you can request multiple tools at onc
1121
1510
  try {
1122
1511
  const tools = await client.listTools();
1123
1512
  const blacklist = new Set(client.toolBlacklist || []);
1124
- const filteredTools = tools.filter((t) => !blacklist.has(t.name));
1125
- serverToolsMap.set(client.serverName, filteredTools);
1126
- for (const tool of filteredTools) {
1513
+ const typedTools = tools.filter((t) => !blacklist.has(t.name)).map((t) => ({
1514
+ name: t.name,
1515
+ description: t.description,
1516
+ inputSchema: t.inputSchema
1517
+ }));
1518
+ serverToolsMap.set(client.serverName, typedTools);
1519
+ for (const tool of typedTools) {
1127
1520
  if (!toolToServers.has(tool.name)) toolToServers.set(tool.name, []);
1128
1521
  toolToServers.get(tool.name).push(client.serverName);
1129
1522
  }
@@ -1133,13 +1526,27 @@ This tool is optimized for batch queries - you can request multiple tools at onc
1133
1526
  }
1134
1527
  }));
1135
1528
  const foundTools = [];
1136
- const notFoundTools = [];
1137
- for (const requestedToolName of toolNames) {
1138
- const { serverName, actualToolName } = parseToolName(requestedToolName);
1529
+ const foundSkills = [];
1530
+ const notFoundItems = [];
1531
+ for (const requestedName of toolNames) {
1532
+ if (requestedName.startsWith(SKILL_PREFIX$1)) {
1533
+ const skillName = requestedName.slice(7);
1534
+ if (this.skillService) {
1535
+ const skill = await this.skillService.getSkill(skillName);
1536
+ if (skill) foundSkills.push({
1537
+ name: skill.name,
1538
+ location: skill.basePath,
1539
+ instructions: skill.content
1540
+ });
1541
+ else notFoundItems.push(requestedName);
1542
+ } else notFoundItems.push(requestedName);
1543
+ continue;
1544
+ }
1545
+ const { serverName, actualToolName } = parseToolName(requestedName);
1139
1546
  if (serverName) {
1140
1547
  const serverTools = serverToolsMap.get(serverName);
1141
1548
  if (!serverTools) {
1142
- notFoundTools.push(requestedToolName);
1549
+ notFoundItems.push(requestedName);
1143
1550
  continue;
1144
1551
  }
1145
1552
  const tool = serverTools.find((t) => t.name === actualToolName);
@@ -1151,11 +1558,22 @@ This tool is optimized for batch queries - you can request multiple tools at onc
1151
1558
  inputSchema: tool.inputSchema
1152
1559
  }
1153
1560
  });
1154
- else notFoundTools.push(requestedToolName);
1561
+ else notFoundItems.push(requestedName);
1155
1562
  } else {
1156
1563
  const servers = toolToServers.get(actualToolName);
1157
1564
  if (!servers || servers.length === 0) {
1158
- notFoundTools.push(requestedToolName);
1565
+ if (this.skillService) {
1566
+ const skill = await this.skillService.getSkill(actualToolName);
1567
+ if (skill) {
1568
+ foundSkills.push({
1569
+ name: skill.name,
1570
+ location: skill.basePath,
1571
+ instructions: skill.content
1572
+ });
1573
+ continue;
1574
+ }
1575
+ }
1576
+ notFoundItems.push(requestedName);
1159
1577
  continue;
1160
1578
  }
1161
1579
  if (servers.length === 1) {
@@ -1182,17 +1600,27 @@ This tool is optimized for batch queries - you can request multiple tools at onc
1182
1600
  }
1183
1601
  }
1184
1602
  }
1185
- if (foundTools.length === 0) return {
1603
+ if (foundTools.length === 0 && foundSkills.length === 0) return {
1186
1604
  content: [{
1187
1605
  type: "text",
1188
- text: `None of the requested tools found on any connected server.\nRequested: ${toolNames.join(", ")}\nUse describe_tools to see available tools.`
1606
+ text: `None of the requested tools/skills found.\nRequested: ${toolNames.join(", ")}\nUse describe_tools to see available tools and skills.`
1189
1607
  }],
1190
1608
  isError: true
1191
1609
  };
1192
- const result = { tools: foundTools };
1193
- if (notFoundTools.length > 0) {
1194
- result.notFound = notFoundTools;
1195
- result.warnings = [`Tools not found: ${notFoundTools.join(", ")}`];
1610
+ const result = {};
1611
+ const nextSteps = [];
1612
+ if (foundTools.length > 0) {
1613
+ result.tools = foundTools;
1614
+ nextSteps.push("For MCP tools: Use the use_tool function with toolName and toolArgs based on the inputSchema above.");
1615
+ }
1616
+ if (foundSkills.length > 0) {
1617
+ result.skills = foundSkills;
1618
+ nextSteps.push(`For skill, just follow skill's description to continue.`);
1619
+ }
1620
+ if (nextSteps.length > 0) result.nextSteps = nextSteps;
1621
+ if (notFoundItems.length > 0) {
1622
+ result.notFound = notFoundItems;
1623
+ result.warnings = [`Items not found: ${notFoundItems.join(", ")}`];
1196
1624
  }
1197
1625
  return { content: [{
1198
1626
  type: "text",
@@ -1212,12 +1640,44 @@ This tool is optimized for batch queries - you can request multiple tools at onc
1212
1640
 
1213
1641
  //#endregion
1214
1642
  //#region src/tools/UseToolTool.ts
1643
+ /**
1644
+ * Prefix used to identify skill invocations (e.g., skill__pdf)
1645
+ */
1646
+ const SKILL_PREFIX = "skill__";
1647
+ /**
1648
+ * UseToolTool executes MCP tools and skills with proper error handling.
1649
+ *
1650
+ * This tool supports three invocation patterns:
1651
+ * 1. skill__skillName - Invokes a skill from the configured skills directory
1652
+ * 2. serverName__toolName - Invokes a tool on a specific MCP server
1653
+ * 3. plainToolName - Searches all servers for a unique tool match
1654
+ *
1655
+ * @example
1656
+ * const tool = new UseToolTool(clientManager, skillService);
1657
+ * await tool.execute({ toolName: 'skill__pdf' }); // Invoke a skill
1658
+ * await tool.execute({ toolName: 'playwright__browser_click', toolArgs: { ref: 'btn' } });
1659
+ */
1215
1660
  var UseToolTool = class UseToolTool {
1216
1661
  static TOOL_NAME = "use_tool";
1217
1662
  clientManager;
1218
- constructor(clientManager) {
1663
+ skillService;
1664
+ /**
1665
+ * Creates a new UseToolTool instance
1666
+ * @param clientManager - The MCP client manager for accessing remote servers
1667
+ * @param skillService - Optional skill service for loading and executing skills
1668
+ */
1669
+ constructor(clientManager, skillService) {
1219
1670
  this.clientManager = clientManager;
1671
+ this.skillService = skillService;
1220
1672
  }
1673
+ /**
1674
+ * Returns the MCP tool definition with name, description, and input schema.
1675
+ *
1676
+ * The definition describes how to use this tool to execute MCP tools or skills,
1677
+ * including the skill__ prefix format for skill invocations.
1678
+ *
1679
+ * @returns The tool definition conforming to MCP spec
1680
+ */
1221
1681
  getDefinition() {
1222
1682
  return {
1223
1683
  name: UseToolTool.TOOL_NAME,
@@ -1230,7 +1690,8 @@ var UseToolTool = class UseToolTool {
1230
1690
  properties: {
1231
1691
  toolName: {
1232
1692
  type: "string",
1233
- description: "Name of the tool to execute"
1693
+ description: "Name of the tool to execute",
1694
+ minLength: 1
1234
1695
  },
1235
1696
  toolArgs: {
1236
1697
  type: "object",
@@ -1242,9 +1703,60 @@ var UseToolTool = class UseToolTool {
1242
1703
  }
1243
1704
  };
1244
1705
  }
1706
+ /**
1707
+ * Returns guidance message for skill invocation.
1708
+ *
1709
+ * Skills are not executed via use_tool - they provide instructions that should
1710
+ * be followed directly. This method returns a message directing users to use
1711
+ * describe_tools to get the skill details and follow its instructions.
1712
+ *
1713
+ * @param skill - The skill that was requested
1714
+ * @returns CallToolResult with guidance message
1715
+ */
1716
+ executeSkill(skill) {
1717
+ return { content: [{
1718
+ type: "text",
1719
+ text: `Skill "${skill.name}" found. Skills provide instructions and should not be executed via use_tool.\n\nUse describe_tools to view the skill details at: ${skill.basePath}\n\nThen follow the skill's instructions directly.`
1720
+ }] };
1721
+ }
1722
+ /**
1723
+ * Executes a tool or skill based on the provided input.
1724
+ *
1725
+ * Handles three invocation patterns:
1726
+ * 1. skill__skillName - Routes to skill execution via SkillService
1727
+ * 2. serverName__toolName - Routes to specific MCP server
1728
+ * 3. plainToolName - Searches all servers for unique match
1729
+ *
1730
+ * Edge cases:
1731
+ * - Returns error if skill not found when using skill__ prefix
1732
+ * - Returns error if tool is blacklisted on target server
1733
+ * - Returns disambiguation message if tool exists on multiple servers
1734
+ *
1735
+ * @param input - The tool/skill name and optional arguments
1736
+ * @returns CallToolResult with execution output or error
1737
+ */
1245
1738
  async execute(input) {
1246
1739
  try {
1247
1740
  const { toolName: inputToolName, toolArgs = {} } = input;
1741
+ if (inputToolName.startsWith(SKILL_PREFIX)) {
1742
+ const skillName = inputToolName.slice(7);
1743
+ if (!this.skillService) return {
1744
+ content: [{
1745
+ type: "text",
1746
+ text: `Skills are not configured. Cannot execute skill "${skillName}".`
1747
+ }],
1748
+ isError: true
1749
+ };
1750
+ const skill = await this.skillService.getSkill(skillName);
1751
+ if (!skill) return {
1752
+ content: [{
1753
+ type: "text",
1754
+ text: `Skill "${skillName}" not found. Use describe_tools to see available skills.`
1755
+ }],
1756
+ isError: true
1757
+ };
1758
+ return this.executeSkill(skill);
1759
+ }
1248
1760
  const clients = this.clientManager.getAllClients();
1249
1761
  const { serverName, actualToolName } = parseToolName(inputToolName);
1250
1762
  if (serverName) {
@@ -1286,13 +1798,19 @@ var UseToolTool = class UseToolTool {
1286
1798
  return null;
1287
1799
  }));
1288
1800
  matchingServers.push(...results.filter((r) => r !== null));
1289
- if (matchingServers.length === 0) return {
1290
- content: [{
1291
- type: "text",
1292
- text: `Tool "${actualToolName}" not found on any connected server. Use describe_tools to see available tools.`
1293
- }],
1294
- isError: true
1295
- };
1801
+ if (matchingServers.length === 0) {
1802
+ if (this.skillService) {
1803
+ const skill = await this.skillService.getSkill(actualToolName);
1804
+ if (skill) return this.executeSkill(skill);
1805
+ }
1806
+ return {
1807
+ content: [{
1808
+ type: "text",
1809
+ text: `Tool or skill "${actualToolName}" not found. Use describe_tools to see available tools and skills.`
1810
+ }],
1811
+ isError: true
1812
+ };
1813
+ }
1296
1814
  if (matchingServers.length > 1) return {
1297
1815
  content: [{
1298
1816
  type: "text",
@@ -1353,30 +1871,53 @@ async function createServer(options) {
1353
1871
  version: "0.1.0"
1354
1872
  }, { capabilities: { tools: {} } });
1355
1873
  const clientManager = new McpClientManagerService();
1356
- if (options?.configFilePath) try {
1357
- const config = await new ConfigFetcherService({
1358
- configFilePath: options.configFilePath,
1359
- useCache: !options.noCache
1360
- }).fetchConfiguration(options.noCache || false);
1874
+ let configSkills;
1875
+ if (options?.configFilePath) {
1876
+ let config;
1877
+ try {
1878
+ config = await new ConfigFetcherService({
1879
+ configFilePath: options.configFilePath,
1880
+ useCache: !options.noCache
1881
+ }).fetchConfiguration(options.noCache || false);
1882
+ } catch (error) {
1883
+ throw new Error(`Failed to load MCP configuration from '${options.configFilePath}': ${error instanceof Error ? error.message : String(error)}`);
1884
+ }
1885
+ configSkills = config.skills;
1886
+ const failedConnections = [];
1361
1887
  const connectionPromises = Object.entries(config.mcpServers).map(async ([serverName, serverConfig]) => {
1362
1888
  try {
1363
1889
  await clientManager.connectToServer(serverName, serverConfig);
1364
1890
  console.error(`Connected to MCP server: ${serverName}`);
1365
1891
  } catch (error) {
1892
+ const err = error instanceof Error ? error : new Error(String(error));
1893
+ failedConnections.push({
1894
+ serverName,
1895
+ error: err
1896
+ });
1366
1897
  console.error(`Failed to connect to ${serverName}:`, error);
1367
1898
  }
1368
1899
  });
1369
1900
  await Promise.all(connectionPromises);
1370
- } catch (error) {
1371
- console.error("Failed to load MCP configuration:", error);
1901
+ 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(", ")}`);
1902
+ 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(", ")}`);
1372
1903
  }
1373
- const describeTools = new DescribeToolsTool(clientManager);
1374
- const useTool = new UseToolTool(clientManager);
1904
+ const skillsConfig = options?.skills || configSkills;
1905
+ const skillService = skillsConfig && skillsConfig.paths.length > 0 ? new SkillService(process.cwd(), skillsConfig.paths) : void 0;
1906
+ const describeTools = new DescribeToolsTool(clientManager, skillService);
1907
+ const useTool = new UseToolTool(clientManager, skillService);
1375
1908
  server.setRequestHandler(__modelcontextprotocol_sdk_types_js.ListToolsRequestSchema, async () => ({ tools: [await describeTools.getDefinition(), useTool.getDefinition()] }));
1376
1909
  server.setRequestHandler(__modelcontextprotocol_sdk_types_js.CallToolRequestSchema, async (request) => {
1377
1910
  const { name, arguments: args } = request.params;
1378
- if (name === DescribeToolsTool.TOOL_NAME) return await describeTools.execute(args);
1379
- if (name === UseToolTool.TOOL_NAME) return await useTool.execute(args);
1911
+ if (name === DescribeToolsTool.TOOL_NAME) try {
1912
+ return await describeTools.execute(args);
1913
+ } catch (error) {
1914
+ throw new Error(`Failed to execute ${name}: ${error instanceof Error ? error.message : String(error)}`);
1915
+ }
1916
+ if (name === UseToolTool.TOOL_NAME) try {
1917
+ return await useTool.execute(args);
1918
+ } catch (error) {
1919
+ throw new Error(`Failed to execute ${name}: ${error instanceof Error ? error.message : String(error)}`);
1920
+ }
1380
1921
  throw new Error(`Unknown tool: ${name}`);
1381
1922
  });
1382
1923
  return server;
@@ -1746,6 +2287,12 @@ Object.defineProperty(exports, 'McpClientManagerService', {
1746
2287
  return McpClientManagerService;
1747
2288
  }
1748
2289
  });
2290
+ Object.defineProperty(exports, 'SkillService', {
2291
+ enumerable: true,
2292
+ get: function () {
2293
+ return SkillService;
2294
+ }
2295
+ });
1749
2296
  Object.defineProperty(exports, 'SseTransportHandler', {
1750
2297
  enumerable: true,
1751
2298
  get: function () {