@agiflowai/one-mcp 0.2.4 → 0.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,15 +1,16 @@
1
1
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
2
- import { CallToolRequestSchema, ListToolsRequestSchema, isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
3
- import { mkdir, readFile, readdir, unlink, writeFile } from "node:fs/promises";
2
+ import { CallToolRequestSchema, GetPromptRequestSchema, ListPromptsRequestSchema, ListToolsRequestSchema, isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
3
+ import { access, mkdir, readFile, readdir, stat, unlink, writeFile } from "node:fs/promises";
4
4
  import { existsSync } from "node:fs";
5
5
  import yaml from "js-yaml";
6
6
  import { z } from "zod";
7
7
  import { createHash, randomUUID } from "node:crypto";
8
- import { join, resolve } from "node:path";
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";
11
11
  import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
12
12
  import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
13
+ import { Liquid } from "liquidjs";
13
14
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
14
15
  import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
15
16
  import express from "express";
@@ -174,10 +175,25 @@ function validateRemoteConfigSource(source) {
174
175
  * Claude Code / Claude Desktop standard MCP config format
175
176
  * This is the format users write in their config files
176
177
  */
178
+ /**
179
+ * Prompt skill configuration schema
180
+ * Converts a prompt to an executable skill
181
+ */
182
+ const PromptSkillConfigSchema = z.object({
183
+ name: z.string(),
184
+ description: z.string(),
185
+ folder: z.string().optional()
186
+ });
187
+ /**
188
+ * Prompt configuration schema
189
+ * Supports converting prompts to skills
190
+ */
191
+ const PromptConfigSchema = z.object({ skill: PromptSkillConfigSchema.optional() });
177
192
  const AdditionalConfigSchema = z.object({
178
193
  instruction: z.string().optional(),
179
194
  toolBlacklist: z.array(z.string()).optional(),
180
- omitToolDescription: z.boolean().optional()
195
+ omitToolDescription: z.boolean().optional(),
196
+ prompts: z.record(z.string(), PromptConfigSchema).optional()
181
197
  }).optional();
182
198
  const ClaudeCodeStdioServerSchema = z.object({
183
199
  command: z.string(),
@@ -216,11 +232,16 @@ const RemoteConfigSourceSchema = z.object({
216
232
  ]).optional()
217
233
  });
218
234
  /**
235
+ * Skills configuration schema
236
+ */
237
+ const SkillsConfigSchema = z.object({ paths: z.array(z.string()) });
238
+ /**
219
239
  * Full Claude Code MCP configuration schema
220
240
  */
221
241
  const ClaudeCodeMcpConfigSchema = z.object({
222
242
  mcpServers: z.record(z.string(), ClaudeCodeServerConfigSchema),
223
- remoteConfigs: z.array(RemoteConfigSourceSchema).optional()
243
+ remoteConfigs: z.array(RemoteConfigSourceSchema).optional(),
244
+ skills: SkillsConfigSchema.optional()
224
245
  });
225
246
  /**
226
247
  * Internal MCP config format
@@ -239,12 +260,25 @@ const McpSseConfigSchema = z.object({
239
260
  url: z.string().url(),
240
261
  headers: z.record(z.string(), z.string()).optional()
241
262
  });
263
+ /**
264
+ * Internal prompt skill configuration schema
265
+ */
266
+ const InternalPromptSkillConfigSchema = z.object({
267
+ name: z.string(),
268
+ description: z.string(),
269
+ folder: z.string().optional()
270
+ });
271
+ /**
272
+ * Internal prompt configuration schema
273
+ */
274
+ const InternalPromptConfigSchema = z.object({ skill: InternalPromptSkillConfigSchema.optional() });
242
275
  const McpServerConfigSchema = z.discriminatedUnion("transport", [
243
276
  z.object({
244
277
  name: z.string(),
245
278
  instruction: z.string().optional(),
246
279
  toolBlacklist: z.array(z.string()).optional(),
247
280
  omitToolDescription: z.boolean().optional(),
281
+ prompts: z.record(z.string(), InternalPromptConfigSchema).optional(),
248
282
  transport: z.literal("stdio"),
249
283
  config: McpStdioConfigSchema
250
284
  }),
@@ -253,6 +287,7 @@ const McpServerConfigSchema = z.discriminatedUnion("transport", [
253
287
  instruction: z.string().optional(),
254
288
  toolBlacklist: z.array(z.string()).optional(),
255
289
  omitToolDescription: z.boolean().optional(),
290
+ prompts: z.record(z.string(), InternalPromptConfigSchema).optional(),
256
291
  transport: z.literal("http"),
257
292
  config: McpHttpConfigSchema
258
293
  }),
@@ -261,6 +296,7 @@ const McpServerConfigSchema = z.discriminatedUnion("transport", [
261
296
  instruction: z.string().optional(),
262
297
  toolBlacklist: z.array(z.string()).optional(),
263
298
  omitToolDescription: z.boolean().optional(),
299
+ prompts: z.record(z.string(), InternalPromptConfigSchema).optional(),
264
300
  transport: z.literal("sse"),
265
301
  config: McpSseConfigSchema
266
302
  })
@@ -268,7 +304,10 @@ const McpServerConfigSchema = z.discriminatedUnion("transport", [
268
304
  /**
269
305
  * Full internal MCP configuration schema
270
306
  */
271
- const InternalMcpConfigSchema = z.object({ mcpServers: z.record(z.string(), McpServerConfigSchema) });
307
+ const InternalMcpConfigSchema = z.object({
308
+ mcpServers: z.record(z.string(), McpServerConfigSchema),
309
+ skills: SkillsConfigSchema.optional()
310
+ });
272
311
  /**
273
312
  * Transform Claude Code config to internal format
274
313
  * Converts standard Claude Code MCP configuration to normalized internal format
@@ -290,6 +329,7 @@ function transformClaudeCodeConfig(claudeConfig) {
290
329
  instruction: stdioConfig.instruction || stdioConfig.config?.instruction,
291
330
  toolBlacklist: stdioConfig.config?.toolBlacklist,
292
331
  omitToolDescription: stdioConfig.config?.omitToolDescription,
332
+ prompts: stdioConfig.config?.prompts,
293
333
  transport: "stdio",
294
334
  config: {
295
335
  command: interpolatedCommand,
@@ -307,6 +347,7 @@ function transformClaudeCodeConfig(claudeConfig) {
307
347
  instruction: httpConfig.instruction || httpConfig.config?.instruction,
308
348
  toolBlacklist: httpConfig.config?.toolBlacklist,
309
349
  omitToolDescription: httpConfig.config?.omitToolDescription,
350
+ prompts: httpConfig.config?.prompts,
310
351
  transport,
311
352
  config: {
312
353
  url: interpolatedUrl,
@@ -315,7 +356,10 @@ function transformClaudeCodeConfig(claudeConfig) {
315
356
  };
316
357
  }
317
358
  }
318
- return { mcpServers: transformedServers };
359
+ return {
360
+ mcpServers: transformedServers,
361
+ skills: claudeConfig.skills
362
+ };
319
363
  }
320
364
  /**
321
365
  * Parse and validate MCP config from raw JSON
@@ -764,12 +808,14 @@ var ConfigFetcherService = class {
764
808
  //#region src/services/McpClientManagerService.ts
765
809
  /**
766
810
  * MCP Client wrapper for managing individual server connections
811
+ * This is an internal class used by McpClientManagerService
767
812
  */
768
813
  var McpClient = class {
769
814
  serverName;
770
815
  serverInstruction;
771
816
  toolBlacklist;
772
817
  omitToolDescription;
818
+ prompts;
773
819
  transport;
774
820
  client;
775
821
  childProcess;
@@ -779,6 +825,7 @@ var McpClient = class {
779
825
  this.serverInstruction = config.instruction;
780
826
  this.toolBlacklist = config.toolBlacklist;
781
827
  this.omitToolDescription = config.omitToolDescription;
828
+ this.prompts = config.prompts;
782
829
  this.transport = transport;
783
830
  this.client = client;
784
831
  }
@@ -874,7 +921,8 @@ var McpClientManagerService = class {
874
921
  const mcpClient = new McpClient(serverName, config.transport, client, {
875
922
  instruction: config.instruction,
876
923
  toolBlacklist: config.toolBlacklist,
877
- omitToolDescription: config.omitToolDescription
924
+ omitToolDescription: config.omitToolDescription,
925
+ prompts: config.prompts
878
926
  });
879
927
  try {
880
928
  await Promise.race([this.performConnection(mcpClient, config), new Promise((_, reject) => setTimeout(() => reject(/* @__PURE__ */ new Error(`Connection timeout after ${timeoutMs}ms`)), timeoutMs))]);
@@ -951,6 +999,261 @@ var McpClientManagerService = class {
951
999
  }
952
1000
  };
953
1001
 
1002
+ //#endregion
1003
+ //#region src/services/SkillService.ts
1004
+ /**
1005
+ * SkillService
1006
+ *
1007
+ * DESIGN PATTERNS:
1008
+ * - Service pattern for business logic encapsulation
1009
+ * - Single responsibility principle
1010
+ * - Lazy loading pattern for skill discovery
1011
+ *
1012
+ * CODING STANDARDS:
1013
+ * - Use async/await for asynchronous operations
1014
+ * - Throw descriptive errors for error cases
1015
+ * - Keep methods focused and well-named
1016
+ * - Document complex logic with comments
1017
+ *
1018
+ * AVOID:
1019
+ * - Mixing concerns (keep focused on single domain)
1020
+ * - Direct tool implementation (services should be tool-agnostic)
1021
+ */
1022
+ /**
1023
+ * Error thrown when skill loading fails
1024
+ */
1025
+ var SkillLoadError = class extends Error {
1026
+ constructor(message, filePath, cause) {
1027
+ super(message);
1028
+ this.filePath = filePath;
1029
+ this.cause = cause;
1030
+ this.name = "SkillLoadError";
1031
+ }
1032
+ };
1033
+ /**
1034
+ * Check if a path exists asynchronously
1035
+ * @param path - Path to check
1036
+ * @returns true if path exists, false otherwise
1037
+ * @throws Error for unexpected filesystem errors (permission denied, etc.)
1038
+ */
1039
+ async function pathExists(path) {
1040
+ try {
1041
+ await access(path);
1042
+ return true;
1043
+ } catch (error) {
1044
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") return false;
1045
+ throw new Error(`Failed to check path existence for "${path}": ${error instanceof Error ? error.message : "Unknown error"}`);
1046
+ }
1047
+ }
1048
+ /**
1049
+ * Service for loading and managing skills from configured skill directories.
1050
+ *
1051
+ * Skills are markdown files with YAML frontmatter that can be invoked via
1052
+ * the skill__ prefix in describe_tools and use_tool.
1053
+ *
1054
+ * Skills are only enabled when explicitly configured via the `skills.paths` array
1055
+ * in the MCP config.
1056
+ *
1057
+ * @example
1058
+ * // Config with skills enabled:
1059
+ * // skills:
1060
+ * // paths:
1061
+ * // - ".claude/skills"
1062
+ * // - "/absolute/path/to/skills"
1063
+ *
1064
+ * const skillService = new SkillService('/project/root', ['.claude/skills']);
1065
+ * const skills = await skillService.getSkills();
1066
+ */
1067
+ var SkillService = class {
1068
+ cwd;
1069
+ skillPaths;
1070
+ cachedSkills = null;
1071
+ skillsByName = null;
1072
+ /**
1073
+ * Creates a new SkillService instance
1074
+ * @param cwd - Current working directory for resolving relative paths
1075
+ * @param skillPaths - Array of paths to skills directories
1076
+ */
1077
+ constructor(cwd, skillPaths) {
1078
+ this.cwd = cwd;
1079
+ this.skillPaths = skillPaths;
1080
+ }
1081
+ /**
1082
+ * Get all available skills from configured directories.
1083
+ * Results are cached after first load.
1084
+ *
1085
+ * Skills from earlier entries in the config take precedence over
1086
+ * skills with the same name from later entries.
1087
+ *
1088
+ * @returns Array of loaded skills
1089
+ * @throws SkillLoadError if a critical error occurs during loading
1090
+ */
1091
+ async getSkills() {
1092
+ if (this.cachedSkills !== null) return this.cachedSkills;
1093
+ const skills = [];
1094
+ const loadedSkillNames = /* @__PURE__ */ new Set();
1095
+ for (const skillPath of this.skillPaths) {
1096
+ const skillsDir = isAbsolute(skillPath) ? skillPath : join(this.cwd, skillPath);
1097
+ const dirSkills = await this.loadSkillsFromDirectory(skillsDir, "project");
1098
+ for (const skill of dirSkills) if (!loadedSkillNames.has(skill.name)) {
1099
+ skills.push(skill);
1100
+ loadedSkillNames.add(skill.name);
1101
+ }
1102
+ }
1103
+ this.cachedSkills = skills;
1104
+ this.skillsByName = new Map(skills.map((skill) => [skill.name, skill]));
1105
+ return skills;
1106
+ }
1107
+ /**
1108
+ * Get a specific skill by name with O(1) lookup from cache.
1109
+ * @param name - The skill name (without skill__ prefix)
1110
+ * @returns The skill if found, undefined otherwise
1111
+ */
1112
+ async getSkill(name) {
1113
+ if (this.skillsByName === null) await this.getSkills();
1114
+ return this.skillsByName?.get(name);
1115
+ }
1116
+ /**
1117
+ * Clears the cached skills to force a fresh reload on the next getSkills() or getSkill() call.
1118
+ * Use this when skill files have been modified on disk.
1119
+ */
1120
+ clearCache() {
1121
+ this.cachedSkills = null;
1122
+ this.skillsByName = null;
1123
+ }
1124
+ /**
1125
+ * Load skills from a directory.
1126
+ * Supports both flat structure (SKILL.md) and nested structure (name/SKILL.md).
1127
+ *
1128
+ * @param dirPath - Path to the skills directory
1129
+ * @param location - Whether this is a 'project' or 'user' skill directory
1130
+ * @returns Array of successfully loaded skills (skips invalid skills)
1131
+ * @throws SkillLoadError if there's a critical I/O error
1132
+ *
1133
+ * @example
1134
+ * // Load skills from project directory
1135
+ * const skills = await this.loadSkillsFromDirectory('/path/to/.claude/skills', 'project');
1136
+ * // Returns: [{ name: 'pdf', description: '...', location: 'project', ... }]
1137
+ */
1138
+ async loadSkillsFromDirectory(dirPath, location) {
1139
+ const skills = [];
1140
+ try {
1141
+ if (!await pathExists(dirPath)) return skills;
1142
+ } catch (error) {
1143
+ throw new SkillLoadError(`Cannot access skills directory: ${error instanceof Error ? error.message : "Unknown error"}`, dirPath, error instanceof Error ? error : void 0);
1144
+ }
1145
+ let entries;
1146
+ try {
1147
+ entries = await readdir(dirPath);
1148
+ } catch (error) {
1149
+ throw new SkillLoadError(`Failed to read skills directory: ${error instanceof Error ? error.message : "Unknown error"}`, dirPath, error instanceof Error ? error : void 0);
1150
+ }
1151
+ for (const entry of entries) {
1152
+ const entryPath = join(dirPath, entry);
1153
+ let entryStat;
1154
+ try {
1155
+ entryStat = await stat(entryPath);
1156
+ } catch (error) {
1157
+ console.warn(`Skipping entry ${entryPath}: ${error instanceof Error ? error.message : "Unknown error"}`);
1158
+ continue;
1159
+ }
1160
+ if (entryStat.isDirectory()) {
1161
+ const skillFilePath = join(entryPath, "SKILL.md");
1162
+ try {
1163
+ if (await pathExists(skillFilePath)) {
1164
+ const skill = await this.loadSkillFile(skillFilePath, location);
1165
+ if (skill) skills.push(skill);
1166
+ }
1167
+ } catch (error) {
1168
+ console.warn(`Skipping skill at ${skillFilePath}: ${error instanceof Error ? error.message : "Unknown error"}`);
1169
+ continue;
1170
+ }
1171
+ } else if (entry === "SKILL.md") try {
1172
+ const skill = await this.loadSkillFile(entryPath, location);
1173
+ if (skill) skills.push(skill);
1174
+ } catch (error) {
1175
+ console.warn(`Skipping skill at ${entryPath}: ${error instanceof Error ? error.message : "Unknown error"}`);
1176
+ continue;
1177
+ }
1178
+ }
1179
+ return skills;
1180
+ }
1181
+ /**
1182
+ * Load a single skill file and parse its frontmatter.
1183
+ *
1184
+ * @param filePath - Path to the SKILL.md file
1185
+ * @param location - Whether this is a 'project' or 'user' skill
1186
+ * @returns The loaded skill, or null if the file is invalid (missing required frontmatter)
1187
+ * @throws SkillLoadError if there's an I/O error reading the file
1188
+ *
1189
+ * @example
1190
+ * // Load a skill from a file
1191
+ * const skill = await this.loadSkillFile('/path/to/pdf/SKILL.md', 'project');
1192
+ * // Returns: { name: 'pdf', description: 'PDF skill', location: 'project', content: '...', basePath: '/path/to/pdf' }
1193
+ * // Returns null if frontmatter is missing name or description
1194
+ */
1195
+ async loadSkillFile(filePath, location) {
1196
+ let content;
1197
+ try {
1198
+ content = await readFile(filePath, "utf-8");
1199
+ } catch (error) {
1200
+ throw new SkillLoadError(`Failed to read skill file: ${error instanceof Error ? error.message : "Unknown error"}`, filePath, error instanceof Error ? error : void 0);
1201
+ }
1202
+ const { metadata, body } = this.parseFrontmatter(content);
1203
+ if (!metadata.name || !metadata.description) return null;
1204
+ return {
1205
+ name: metadata.name,
1206
+ description: metadata.description,
1207
+ location,
1208
+ content: body,
1209
+ basePath: dirname(filePath)
1210
+ };
1211
+ }
1212
+ /**
1213
+ * Parse YAML frontmatter from markdown content.
1214
+ * Frontmatter is delimited by --- at start and end.
1215
+ *
1216
+ * @param content - Full markdown content with frontmatter
1217
+ * @returns Parsed metadata and body content
1218
+ *
1219
+ * @example
1220
+ * // Input content:
1221
+ * // ---
1222
+ * // name: my-skill
1223
+ * // description: A sample skill
1224
+ * // ---
1225
+ * // # Skill Content
1226
+ * // This is the skill body.
1227
+ *
1228
+ * const result = parseFrontmatter(content);
1229
+ * // result.metadata = { name: 'my-skill', description: 'A sample skill' }
1230
+ * // result.body = '# Skill Content\nThis is the skill body.'
1231
+ */
1232
+ parseFrontmatter(content) {
1233
+ const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/);
1234
+ if (!match) return {
1235
+ metadata: {},
1236
+ body: content
1237
+ };
1238
+ const [, frontmatter, body] = match;
1239
+ const metadata = {};
1240
+ const lines = frontmatter.split("\n");
1241
+ for (const line of lines) {
1242
+ const colonIndex = line.indexOf(":");
1243
+ if (colonIndex > 0) {
1244
+ const key = line.slice(0, colonIndex).trim();
1245
+ let value = line.slice(colonIndex + 1).trim();
1246
+ if (value.startsWith("\"") && value.endsWith("\"") || value.startsWith("'") && value.endsWith("'")) value = value.slice(1, -1);
1247
+ if (key === "name" || key === "description" || key === "license") metadata[key] = value;
1248
+ }
1249
+ }
1250
+ return {
1251
+ metadata,
1252
+ body: body.trim()
1253
+ };
1254
+ }
1255
+ };
1256
+
954
1257
  //#endregion
955
1258
  //#region src/utils/findConfigFile.ts
956
1259
  /**
@@ -1020,15 +1323,165 @@ function parseToolName(toolName) {
1020
1323
  return { actualToolName: toolName };
1021
1324
  }
1022
1325
 
1326
+ //#endregion
1327
+ //#region src/templates/skills-description.liquid?raw
1328
+ 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<item><name>{{ skill.displayName }}</name><description>{{ skill.description }}</description></item>\n{% endfor -%}\n</available_skills>\n</skills>\n{% endif %}\n";
1329
+
1330
+ //#endregion
1331
+ //#region src/templates/mcp-servers-description.liquid?raw
1332
+ var mcp_servers_description_default = "<mcp_servers>\n<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</instructions>\n\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<item><name>{{ tool.displayName }}</name><description>{{ tool.description | default: \"No description\" }}</description></item>\n{% endfor -%}\n{% endif -%}\n</tools>\n</server>\n{% endfor -%}\n</mcp_servers>\n";
1333
+
1023
1334
  //#endregion
1024
1335
  //#region src/tools/DescribeToolsTool.ts
1336
+ /**
1337
+ * Prefix used to identify skill invocations
1338
+ */
1339
+ const SKILL_PREFIX$1 = "skill__";
1340
+ /**
1341
+ * DescribeToolsTool provides progressive disclosure of MCP tools and skills.
1342
+ *
1343
+ * This tool lists available tools from all connected MCP servers and skills
1344
+ * from the configured skills directories. Users can query for specific tools
1345
+ * or skills to get detailed input schemas and descriptions.
1346
+ *
1347
+ * Tool naming conventions:
1348
+ * - Unique tools: use plain name (e.g., "browser_click")
1349
+ * - Clashing tools: use serverName__toolName format (e.g., "playwright__click")
1350
+ * - Skills: use skill__skillName format (e.g., "skill__pdf")
1351
+ *
1352
+ * @example
1353
+ * const tool = new DescribeToolsTool(clientManager, skillService);
1354
+ * const definition = await tool.getDefinition();
1355
+ * const result = await tool.execute({ toolNames: ['browser_click', 'skill__pdf'] });
1356
+ */
1025
1357
  var DescribeToolsTool = class DescribeToolsTool {
1026
1358
  static TOOL_NAME = "describe_tools";
1027
1359
  clientManager;
1028
- constructor(clientManager) {
1360
+ skillService;
1361
+ liquid = new Liquid();
1362
+ /**
1363
+ * Creates a new DescribeToolsTool instance
1364
+ * @param clientManager - The MCP client manager for accessing remote servers
1365
+ * @param skillService - Optional skill service for loading skills
1366
+ */
1367
+ constructor(clientManager, skillService) {
1029
1368
  this.clientManager = clientManager;
1369
+ this.skillService = skillService;
1030
1370
  }
1031
- async getDefinition() {
1371
+ /**
1372
+ * Collects skills derived from prompt configurations across all connected MCP servers.
1373
+ * Prompts with a skill configuration are converted to skill format for display.
1374
+ *
1375
+ * @returns Array of skill template data derived from prompts
1376
+ */
1377
+ collectPromptSkills() {
1378
+ const clients = this.clientManager.getAllClients();
1379
+ const promptSkills = [];
1380
+ for (const client of clients) {
1381
+ if (!client.prompts) continue;
1382
+ for (const promptConfig of Object.values(client.prompts)) if (promptConfig.skill) promptSkills.push({
1383
+ name: promptConfig.skill.name,
1384
+ displayName: promptConfig.skill.name,
1385
+ description: promptConfig.skill.description
1386
+ });
1387
+ }
1388
+ return promptSkills;
1389
+ }
1390
+ /**
1391
+ * Finds a prompt-based skill by name from all connected MCP servers.
1392
+ * Returns the prompt name and skill config for fetching the prompt content.
1393
+ *
1394
+ * @param skillName - The skill name to search for
1395
+ * @returns Object with serverName, promptName, and skill config, or undefined if not found
1396
+ */
1397
+ findPromptSkill(skillName) {
1398
+ if (!skillName) return void 0;
1399
+ const clients = this.clientManager.getAllClients();
1400
+ for (const client of clients) {
1401
+ if (!client.prompts) continue;
1402
+ for (const [promptName, promptConfig] of Object.entries(client.prompts)) if (promptConfig.skill && promptConfig.skill.name === skillName) return {
1403
+ serverName: client.serverName,
1404
+ promptName,
1405
+ skill: promptConfig.skill
1406
+ };
1407
+ }
1408
+ }
1409
+ /**
1410
+ * Retrieves skill content from a prompt-based skill configuration.
1411
+ * Fetches the prompt from the MCP server and extracts text content.
1412
+ *
1413
+ * @param skillName - The skill name being requested
1414
+ * @returns SkillDescription if found and successfully fetched, undefined otherwise
1415
+ */
1416
+ async getPromptSkillContent(skillName) {
1417
+ const promptSkill = this.findPromptSkill(skillName);
1418
+ if (!promptSkill) return void 0;
1419
+ const client = this.clientManager.getClient(promptSkill.serverName);
1420
+ if (!client) {
1421
+ console.error(`Client not found for server '${promptSkill.serverName}' when fetching prompt skill '${skillName}'`);
1422
+ return;
1423
+ }
1424
+ try {
1425
+ const instructions = (await client.getPrompt(promptSkill.promptName)).messages?.map((m) => {
1426
+ const content = m.content;
1427
+ if (typeof content === "string") return content;
1428
+ if (content && typeof content === "object" && "text" in content) return String(content.text);
1429
+ return "";
1430
+ }).join("\n") || "";
1431
+ return {
1432
+ name: promptSkill.skill.name,
1433
+ location: promptSkill.skill.folder || `prompt:${promptSkill.serverName}/${promptSkill.promptName}`,
1434
+ instructions
1435
+ };
1436
+ } catch (error) {
1437
+ console.error(`Failed to get prompt-based skill '${skillName}': ${error instanceof Error ? error.message : "Unknown error"}`);
1438
+ return;
1439
+ }
1440
+ }
1441
+ /**
1442
+ * Builds the skills section of the tool description using a Liquid template.
1443
+ *
1444
+ * Retrieves all available skills from the SkillService and prompt-based skills,
1445
+ * then renders them using the skills-description.liquid template. Skills are only
1446
+ * prefixed with skill__ when their name clashes with an MCP tool or another skill.
1447
+ *
1448
+ * @param mcpToolNames - Set of MCP tool names to check for clashes
1449
+ * @returns Rendered skills section string with available skills list
1450
+ */
1451
+ async buildSkillsSection(mcpToolNames) {
1452
+ const rawSkills = this.skillService ? await this.skillService.getSkills() : [];
1453
+ const promptSkills = this.collectPromptSkills();
1454
+ const allSkillsData = [...rawSkills.map((skill) => ({
1455
+ name: skill.name,
1456
+ displayName: skill.name,
1457
+ description: skill.description
1458
+ })), ...promptSkills];
1459
+ const skillNameCounts = /* @__PURE__ */ new Map();
1460
+ for (const skill of allSkillsData) skillNameCounts.set(skill.name, (skillNameCounts.get(skill.name) || 0) + 1);
1461
+ const skills = allSkillsData.map((skill) => {
1462
+ const clashesWithMcpTool = mcpToolNames.has(skill.name);
1463
+ const clashesWithOtherSkill = (skillNameCounts.get(skill.name) || 0) > 1;
1464
+ const needsPrefix = clashesWithMcpTool || clashesWithOtherSkill;
1465
+ return {
1466
+ name: skill.name,
1467
+ displayName: needsPrefix ? `${SKILL_PREFIX$1}${skill.name}` : skill.name,
1468
+ description: skill.description
1469
+ };
1470
+ });
1471
+ return this.liquid.parseAndRender(skills_description_default, { skills });
1472
+ }
1473
+ /**
1474
+ * Builds the MCP servers section of the tool description using a Liquid template.
1475
+ *
1476
+ * Collects all tools from connected MCP servers, detects name clashes,
1477
+ * and renders them using the mcp-servers-description.liquid template.
1478
+ *
1479
+ * Tool names are prefixed with serverName__ when the same tool exists
1480
+ * on multiple servers to avoid ambiguity.
1481
+ *
1482
+ * @returns Object with rendered servers section and set of all tool names for skill clash detection
1483
+ */
1484
+ async buildServersSection() {
1032
1485
  const clients = this.clientManager.getAllClients();
1033
1486
  const toolToServers = /* @__PURE__ */ new Map();
1034
1487
  const serverToolsMap = /* @__PURE__ */ new Map();
@@ -1047,30 +1500,63 @@ var DescribeToolsTool = class DescribeToolsTool {
1047
1500
  serverToolsMap.set(client.serverName, []);
1048
1501
  }
1049
1502
  }));
1050
- const serverDescriptions = clients.map((client) => {
1051
- const tools = serverToolsMap.get(client.serverName) || [];
1052
- const formatToolName = (toolName) => {
1053
- return (toolToServers.get(toolName) || []).length > 1 ? `${client.serverName}__${toolName}` : toolName;
1503
+ /**
1504
+ * Formats tool name with server prefix if the tool exists on multiple servers
1505
+ * @param toolName - The original tool name
1506
+ * @param serverName - The server providing this tool
1507
+ * @returns Tool name prefixed with serverName__ if clashing, otherwise plain name
1508
+ */
1509
+ const formatToolName = (toolName, serverName) => {
1510
+ return (toolToServers.get(toolName) || []).length > 1 ? `${serverName}__${toolName}` : toolName;
1511
+ };
1512
+ const allToolNames = /* @__PURE__ */ new Set();
1513
+ const servers = clients.map((client) => {
1514
+ const formattedTools = (serverToolsMap.get(client.serverName) || []).map((t) => ({
1515
+ displayName: formatToolName(t.name, client.serverName),
1516
+ description: t.description
1517
+ }));
1518
+ for (const tool of formattedTools) allToolNames.add(tool.displayName);
1519
+ return {
1520
+ name: client.serverName,
1521
+ instruction: client.serverInstruction,
1522
+ omitToolDescription: client.omitToolDescription || false,
1523
+ tools: formattedTools,
1524
+ toolNames: formattedTools.map((t) => t.displayName)
1054
1525
  };
1055
- const toolList = client.omitToolDescription ? tools.map((t) => formatToolName(t.name)).join(", ") : tools.map((t) => `${formatToolName(t.name)}: """${t.description || "No description"}"""`).join("\n");
1056
- const instructionLine = client.serverInstruction ? `\n"""${client.serverInstruction}"""` : "";
1057
- return `\n\n### Server: ${client.serverName}${instructionLine}\n\n- Available tools:\n${toolList || "No tools available"}`;
1058
1526
  });
1527
+ return {
1528
+ content: await this.liquid.parseAndRender(mcp_servers_description_default, { servers }),
1529
+ toolNames: allToolNames
1530
+ };
1531
+ }
1532
+ /**
1533
+ * Gets the tool definition including available servers, tools, and skills.
1534
+ *
1535
+ * The definition includes:
1536
+ * - List of all connected MCP servers with their available tools
1537
+ * - List of available skills with skill__ prefix
1538
+ * - Usage instructions for querying tool/skill details
1539
+ *
1540
+ * Tool names are prefixed with serverName__ when the same tool name
1541
+ * exists on multiple servers to avoid ambiguity.
1542
+ *
1543
+ * @returns Tool definition with description and input schema
1544
+ */
1545
+ async getDefinition() {
1546
+ const serversResult = await this.buildServersSection();
1547
+ const skillsSection = await this.buildSkillsSection(serversResult.toolNames);
1059
1548
  return {
1060
1549
  name: DescribeToolsTool.TOOL_NAME,
1061
- description: `## Available MCP Servers:${serverDescriptions.join("")}
1062
-
1063
- ## Usage:
1064
- 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:
1065
- - Arguments schema needed to pass to the tool use
1066
- - Description about each tool
1067
-
1068
- This tool is optimized for batch queries - you can request multiple tools at once for better performance.`,
1550
+ description: `${serversResult.content}
1551
+ ${skillsSection}`,
1069
1552
  inputSchema: {
1070
1553
  type: "object",
1071
1554
  properties: { toolNames: {
1072
1555
  type: "array",
1073
- items: { type: "string" },
1556
+ items: {
1557
+ type: "string",
1558
+ minLength: 1
1559
+ },
1074
1560
  description: "List of tool names to get detailed information about",
1075
1561
  minItems: 1
1076
1562
  } },
@@ -1079,6 +1565,17 @@ This tool is optimized for batch queries - you can request multiple tools at onc
1079
1565
  }
1080
1566
  };
1081
1567
  }
1568
+ /**
1569
+ * Executes tool description lookup for the requested tool and skill names.
1570
+ *
1571
+ * Handles three types of lookups:
1572
+ * 1. skill__name - Returns skill information from SkillService
1573
+ * 2. serverName__toolName - Returns tool from specific server
1574
+ * 3. plainToolName - Returns tool(s) from all servers (multiple if clashing)
1575
+ *
1576
+ * @param input - Object containing toolNames array
1577
+ * @returns CallToolResult with tool/skill descriptions or error
1578
+ */
1082
1579
  async execute(input) {
1083
1580
  try {
1084
1581
  const { toolNames } = input;
@@ -1096,9 +1593,13 @@ This tool is optimized for batch queries - you can request multiple tools at onc
1096
1593
  try {
1097
1594
  const tools = await client.listTools();
1098
1595
  const blacklist = new Set(client.toolBlacklist || []);
1099
- const filteredTools = tools.filter((t) => !blacklist.has(t.name));
1100
- serverToolsMap.set(client.serverName, filteredTools);
1101
- for (const tool of filteredTools) {
1596
+ const typedTools = tools.filter((t) => !blacklist.has(t.name)).map((t) => ({
1597
+ name: t.name,
1598
+ description: t.description,
1599
+ inputSchema: t.inputSchema
1600
+ }));
1601
+ serverToolsMap.set(client.serverName, typedTools);
1602
+ for (const tool of typedTools) {
1102
1603
  if (!toolToServers.has(tool.name)) toolToServers.set(tool.name, []);
1103
1604
  toolToServers.get(tool.name).push(client.serverName);
1104
1605
  }
@@ -1108,13 +1609,35 @@ This tool is optimized for batch queries - you can request multiple tools at onc
1108
1609
  }
1109
1610
  }));
1110
1611
  const foundTools = [];
1111
- const notFoundTools = [];
1112
- for (const requestedToolName of toolNames) {
1113
- const { serverName, actualToolName } = parseToolName(requestedToolName);
1612
+ const foundSkills = [];
1613
+ const notFoundItems = [];
1614
+ for (const requestedName of toolNames) {
1615
+ if (requestedName.startsWith(SKILL_PREFIX$1)) {
1616
+ const skillName = requestedName.slice(7);
1617
+ if (this.skillService) {
1618
+ const skill = await this.skillService.getSkill(skillName);
1619
+ if (skill) {
1620
+ foundSkills.push({
1621
+ name: skill.name,
1622
+ location: skill.basePath,
1623
+ instructions: skill.content
1624
+ });
1625
+ continue;
1626
+ }
1627
+ }
1628
+ const promptSkillContent = await this.getPromptSkillContent(skillName);
1629
+ if (promptSkillContent) {
1630
+ foundSkills.push(promptSkillContent);
1631
+ continue;
1632
+ }
1633
+ notFoundItems.push(requestedName);
1634
+ continue;
1635
+ }
1636
+ const { serverName, actualToolName } = parseToolName(requestedName);
1114
1637
  if (serverName) {
1115
1638
  const serverTools = serverToolsMap.get(serverName);
1116
1639
  if (!serverTools) {
1117
- notFoundTools.push(requestedToolName);
1640
+ notFoundItems.push(requestedName);
1118
1641
  continue;
1119
1642
  }
1120
1643
  const tool = serverTools.find((t) => t.name === actualToolName);
@@ -1126,11 +1649,27 @@ This tool is optimized for batch queries - you can request multiple tools at onc
1126
1649
  inputSchema: tool.inputSchema
1127
1650
  }
1128
1651
  });
1129
- else notFoundTools.push(requestedToolName);
1652
+ else notFoundItems.push(requestedName);
1130
1653
  } else {
1131
1654
  const servers = toolToServers.get(actualToolName);
1132
1655
  if (!servers || servers.length === 0) {
1133
- notFoundTools.push(requestedToolName);
1656
+ if (this.skillService) {
1657
+ const skill = await this.skillService.getSkill(actualToolName);
1658
+ if (skill) {
1659
+ foundSkills.push({
1660
+ name: skill.name,
1661
+ location: skill.basePath,
1662
+ instructions: skill.content
1663
+ });
1664
+ continue;
1665
+ }
1666
+ }
1667
+ const promptSkillContent = await this.getPromptSkillContent(actualToolName);
1668
+ if (promptSkillContent) {
1669
+ foundSkills.push(promptSkillContent);
1670
+ continue;
1671
+ }
1672
+ notFoundItems.push(requestedName);
1134
1673
  continue;
1135
1674
  }
1136
1675
  if (servers.length === 1) {
@@ -1157,17 +1696,27 @@ This tool is optimized for batch queries - you can request multiple tools at onc
1157
1696
  }
1158
1697
  }
1159
1698
  }
1160
- if (foundTools.length === 0) return {
1699
+ if (foundTools.length === 0 && foundSkills.length === 0) return {
1161
1700
  content: [{
1162
1701
  type: "text",
1163
- text: `None of the requested tools found on any connected server.\nRequested: ${toolNames.join(", ")}\nUse describe_tools to see available tools.`
1702
+ text: `None of the requested tools/skills found.\nRequested: ${toolNames.join(", ")}\nUse describe_tools to see available tools and skills.`
1164
1703
  }],
1165
1704
  isError: true
1166
1705
  };
1167
- const result = { tools: foundTools };
1168
- if (notFoundTools.length > 0) {
1169
- result.notFound = notFoundTools;
1170
- result.warnings = [`Tools not found: ${notFoundTools.join(", ")}`];
1706
+ const result = {};
1707
+ const nextSteps = [];
1708
+ if (foundTools.length > 0) {
1709
+ result.tools = foundTools;
1710
+ nextSteps.push("For MCP tools: Use the use_tool function with toolName and toolArgs based on the inputSchema above.");
1711
+ }
1712
+ if (foundSkills.length > 0) {
1713
+ result.skills = foundSkills;
1714
+ nextSteps.push(`For skill, just follow skill's description to continue.`);
1715
+ }
1716
+ if (nextSteps.length > 0) result.nextSteps = nextSteps;
1717
+ if (notFoundItems.length > 0) {
1718
+ result.notFound = notFoundItems;
1719
+ result.warnings = [`Items not found: ${notFoundItems.join(", ")}`];
1171
1720
  }
1172
1721
  return { content: [{
1173
1722
  type: "text",
@@ -1187,16 +1736,48 @@ This tool is optimized for batch queries - you can request multiple tools at onc
1187
1736
 
1188
1737
  //#endregion
1189
1738
  //#region src/tools/UseToolTool.ts
1739
+ /**
1740
+ * Prefix used to identify skill invocations (e.g., skill__pdf)
1741
+ */
1742
+ const SKILL_PREFIX = "skill__";
1743
+ /**
1744
+ * UseToolTool executes MCP tools and skills with proper error handling.
1745
+ *
1746
+ * This tool supports three invocation patterns:
1747
+ * 1. skill__skillName - Invokes a skill from the configured skills directory
1748
+ * 2. serverName__toolName - Invokes a tool on a specific MCP server
1749
+ * 3. plainToolName - Searches all servers for a unique tool match
1750
+ *
1751
+ * @example
1752
+ * const tool = new UseToolTool(clientManager, skillService);
1753
+ * await tool.execute({ toolName: 'skill__pdf' }); // Invoke a skill
1754
+ * await tool.execute({ toolName: 'playwright__browser_click', toolArgs: { ref: 'btn' } });
1755
+ */
1190
1756
  var UseToolTool = class UseToolTool {
1191
1757
  static TOOL_NAME = "use_tool";
1192
1758
  clientManager;
1193
- constructor(clientManager) {
1759
+ skillService;
1760
+ /**
1761
+ * Creates a new UseToolTool instance
1762
+ * @param clientManager - The MCP client manager for accessing remote servers
1763
+ * @param skillService - Optional skill service for loading and executing skills
1764
+ */
1765
+ constructor(clientManager, skillService) {
1194
1766
  this.clientManager = clientManager;
1767
+ this.skillService = skillService;
1195
1768
  }
1769
+ /**
1770
+ * Returns the MCP tool definition with name, description, and input schema.
1771
+ *
1772
+ * The definition describes how to use this tool to execute MCP tools or skills,
1773
+ * including the skill__ prefix format for skill invocations.
1774
+ *
1775
+ * @returns The tool definition conforming to MCP spec
1776
+ */
1196
1777
  getDefinition() {
1197
1778
  return {
1198
1779
  name: UseToolTool.TOOL_NAME,
1199
- description: `Execute an MCP tool with provided arguments. You MUST call describe_tools first to discover the tool's correct arguments. Then to use tool:
1780
+ 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:
1200
1781
  - Provide toolName and toolArgs based on the schema
1201
1782
  - If multiple servers provide the same tool, specify serverName
1202
1783
  `,
@@ -1205,7 +1786,8 @@ var UseToolTool = class UseToolTool {
1205
1786
  properties: {
1206
1787
  toolName: {
1207
1788
  type: "string",
1208
- description: "Name of the tool to execute"
1789
+ description: "Name of the tool to execute",
1790
+ minLength: 1
1209
1791
  },
1210
1792
  toolArgs: {
1211
1793
  type: "object",
@@ -1217,9 +1799,88 @@ var UseToolTool = class UseToolTool {
1217
1799
  }
1218
1800
  };
1219
1801
  }
1802
+ /**
1803
+ * Returns guidance message for skill invocation.
1804
+ *
1805
+ * Skills are not executed via use_tool - they provide instructions that should
1806
+ * be followed directly. This method returns a message directing users to use
1807
+ * describe_tools to get the skill details and follow its instructions.
1808
+ *
1809
+ * @param skill - The skill that was requested
1810
+ * @returns CallToolResult with guidance message
1811
+ */
1812
+ executeSkill(skill) {
1813
+ return { content: [{
1814
+ type: "text",
1815
+ 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.`
1816
+ }] };
1817
+ }
1818
+ /**
1819
+ * Finds a prompt-based skill by name from all connected MCP servers.
1820
+ *
1821
+ * @param skillName - The skill name to search for
1822
+ * @returns PromptSkillMatch if found, undefined otherwise
1823
+ */
1824
+ findPromptSkill(skillName) {
1825
+ if (!skillName) return void 0;
1826
+ const clients = this.clientManager.getAllClients();
1827
+ for (const client of clients) {
1828
+ if (!client.prompts) continue;
1829
+ for (const [promptName, promptConfig] of Object.entries(client.prompts)) if (promptConfig.skill && promptConfig.skill.name === skillName) return {
1830
+ serverName: client.serverName,
1831
+ promptName,
1832
+ skill: promptConfig.skill
1833
+ };
1834
+ }
1835
+ }
1836
+ /**
1837
+ * Returns guidance message for prompt-based skill invocation.
1838
+ *
1839
+ * @param promptSkill - The prompt skill match that was found
1840
+ * @returns CallToolResult with guidance message
1841
+ */
1842
+ executePromptSkill(promptSkill) {
1843
+ const location = promptSkill.skill.folder || `prompt:${promptSkill.serverName}/${promptSkill.promptName}`;
1844
+ return { content: [{
1845
+ type: "text",
1846
+ text: `Skill "${promptSkill.skill.name}" found. Skills provide instructions and should not be executed via use_tool.\n\nUse describe_tools to view the skill details at: ${location}\n\nThen follow the skill's instructions directly.`
1847
+ }] };
1848
+ }
1849
+ /**
1850
+ * Executes a tool or skill based on the provided input.
1851
+ *
1852
+ * Handles three invocation patterns:
1853
+ * 1. skill__skillName - Routes to skill execution via SkillService
1854
+ * 2. serverName__toolName - Routes to specific MCP server
1855
+ * 3. plainToolName - Searches all servers for unique match
1856
+ *
1857
+ * Edge cases:
1858
+ * - Returns error if skill not found when using skill__ prefix
1859
+ * - Returns error if tool is blacklisted on target server
1860
+ * - Returns disambiguation message if tool exists on multiple servers
1861
+ *
1862
+ * @param input - The tool/skill name and optional arguments
1863
+ * @returns CallToolResult with execution output or error
1864
+ */
1220
1865
  async execute(input) {
1221
1866
  try {
1222
1867
  const { toolName: inputToolName, toolArgs = {} } = input;
1868
+ if (inputToolName.startsWith(SKILL_PREFIX)) {
1869
+ const skillName = inputToolName.slice(7);
1870
+ if (this.skillService) {
1871
+ const skill = await this.skillService.getSkill(skillName);
1872
+ if (skill) return this.executeSkill(skill);
1873
+ }
1874
+ const promptSkill = this.findPromptSkill(skillName);
1875
+ if (promptSkill) return this.executePromptSkill(promptSkill);
1876
+ return {
1877
+ content: [{
1878
+ type: "text",
1879
+ text: `Skill "${skillName}" not found. Use describe_tools to see available skills.`
1880
+ }],
1881
+ isError: true
1882
+ };
1883
+ }
1223
1884
  const clients = this.clientManager.getAllClients();
1224
1885
  const { serverName, actualToolName } = parseToolName(inputToolName);
1225
1886
  if (serverName) {
@@ -1261,13 +1922,21 @@ var UseToolTool = class UseToolTool {
1261
1922
  return null;
1262
1923
  }));
1263
1924
  matchingServers.push(...results.filter((r) => r !== null));
1264
- if (matchingServers.length === 0) return {
1265
- content: [{
1266
- type: "text",
1267
- text: `Tool "${actualToolName}" not found on any connected server. Use describe_tools to see available tools.`
1268
- }],
1269
- isError: true
1270
- };
1925
+ if (matchingServers.length === 0) {
1926
+ if (this.skillService) {
1927
+ const skill = await this.skillService.getSkill(actualToolName);
1928
+ if (skill) return this.executeSkill(skill);
1929
+ }
1930
+ const promptSkill = this.findPromptSkill(actualToolName);
1931
+ if (promptSkill) return this.executePromptSkill(promptSkill);
1932
+ return {
1933
+ content: [{
1934
+ type: "text",
1935
+ text: `Tool or skill "${actualToolName}" not found. Use describe_tools to see available tools and skills.`
1936
+ }],
1937
+ isError: true
1938
+ };
1939
+ }
1271
1940
  if (matchingServers.length > 1) return {
1272
1941
  content: [{
1273
1942
  type: "text",
@@ -1326,34 +1995,114 @@ async function createServer(options) {
1326
1995
  const server = new Server({
1327
1996
  name: "@agiflowai/one-mcp",
1328
1997
  version: "0.1.0"
1329
- }, { capabilities: { tools: {} } });
1998
+ }, { capabilities: {
1999
+ tools: {},
2000
+ prompts: {}
2001
+ } });
1330
2002
  const clientManager = new McpClientManagerService();
1331
- if (options?.configFilePath) try {
1332
- const config = await new ConfigFetcherService({
1333
- configFilePath: options.configFilePath,
1334
- useCache: !options.noCache
1335
- }).fetchConfiguration(options.noCache || false);
2003
+ let configSkills;
2004
+ if (options?.configFilePath) {
2005
+ let config;
2006
+ try {
2007
+ config = await new ConfigFetcherService({
2008
+ configFilePath: options.configFilePath,
2009
+ useCache: !options.noCache
2010
+ }).fetchConfiguration(options.noCache || false);
2011
+ } catch (error) {
2012
+ throw new Error(`Failed to load MCP configuration from '${options.configFilePath}': ${error instanceof Error ? error.message : String(error)}`);
2013
+ }
2014
+ configSkills = config.skills;
2015
+ const failedConnections = [];
1336
2016
  const connectionPromises = Object.entries(config.mcpServers).map(async ([serverName, serverConfig]) => {
1337
2017
  try {
1338
2018
  await clientManager.connectToServer(serverName, serverConfig);
1339
2019
  console.error(`Connected to MCP server: ${serverName}`);
1340
2020
  } catch (error) {
2021
+ const err = error instanceof Error ? error : new Error(String(error));
2022
+ failedConnections.push({
2023
+ serverName,
2024
+ error: err
2025
+ });
1341
2026
  console.error(`Failed to connect to ${serverName}:`, error);
1342
2027
  }
1343
2028
  });
1344
2029
  await Promise.all(connectionPromises);
1345
- } catch (error) {
1346
- console.error("Failed to load MCP configuration:", error);
2030
+ 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(", ")}`);
2031
+ 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(", ")}`);
1347
2032
  }
1348
- const describeTools = new DescribeToolsTool(clientManager);
1349
- const useTool = new UseToolTool(clientManager);
2033
+ const skillsConfig = options?.skills || configSkills;
2034
+ const skillService = skillsConfig && skillsConfig.paths.length > 0 ? new SkillService(process.cwd(), skillsConfig.paths) : void 0;
2035
+ const describeTools = new DescribeToolsTool(clientManager, skillService);
2036
+ const useTool = new UseToolTool(clientManager, skillService);
1350
2037
  server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: [await describeTools.getDefinition(), useTool.getDefinition()] }));
1351
2038
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
1352
2039
  const { name, arguments: args } = request.params;
1353
- if (name === DescribeToolsTool.TOOL_NAME) return await describeTools.execute(args);
1354
- if (name === UseToolTool.TOOL_NAME) return await useTool.execute(args);
2040
+ if (name === DescribeToolsTool.TOOL_NAME) try {
2041
+ return await describeTools.execute(args);
2042
+ } catch (error) {
2043
+ throw new Error(`Failed to execute ${name}: ${error instanceof Error ? error.message : String(error)}`);
2044
+ }
2045
+ if (name === UseToolTool.TOOL_NAME) try {
2046
+ return await useTool.execute(args);
2047
+ } catch (error) {
2048
+ throw new Error(`Failed to execute ${name}: ${error instanceof Error ? error.message : String(error)}`);
2049
+ }
1355
2050
  throw new Error(`Unknown tool: ${name}`);
1356
2051
  });
2052
+ server.setRequestHandler(ListPromptsRequestSchema, async () => {
2053
+ const clients = clientManager.getAllClients();
2054
+ const promptToServers = /* @__PURE__ */ new Map();
2055
+ const serverPromptsMap = /* @__PURE__ */ new Map();
2056
+ await Promise.all(clients.map(async (client) => {
2057
+ try {
2058
+ const prompts = await client.listPrompts();
2059
+ serverPromptsMap.set(client.serverName, prompts);
2060
+ for (const prompt of prompts) {
2061
+ if (!promptToServers.has(prompt.name)) promptToServers.set(prompt.name, []);
2062
+ promptToServers.get(prompt.name).push(client.serverName);
2063
+ }
2064
+ } catch (error) {
2065
+ console.error(`Failed to list prompts from ${client.serverName}:`, error);
2066
+ serverPromptsMap.set(client.serverName, []);
2067
+ }
2068
+ }));
2069
+ const aggregatedPrompts = [];
2070
+ for (const client of clients) {
2071
+ const prompts = serverPromptsMap.get(client.serverName) || [];
2072
+ for (const prompt of prompts) {
2073
+ const hasClash = (promptToServers.get(prompt.name) || []).length > 1;
2074
+ aggregatedPrompts.push({
2075
+ name: hasClash ? `${client.serverName}__${prompt.name}` : prompt.name,
2076
+ description: prompt.description,
2077
+ arguments: prompt.arguments
2078
+ });
2079
+ }
2080
+ }
2081
+ return { prompts: aggregatedPrompts };
2082
+ });
2083
+ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
2084
+ const { name, arguments: args } = request.params;
2085
+ const clients = clientManager.getAllClients();
2086
+ const { serverName, actualToolName: actualPromptName } = parseToolName(name);
2087
+ if (serverName) {
2088
+ const client$1 = clientManager.getClient(serverName);
2089
+ if (!client$1) throw new Error(`Server not found: ${serverName}`);
2090
+ return await client$1.getPrompt(actualPromptName, args);
2091
+ }
2092
+ const serversWithPrompt = [];
2093
+ await Promise.all(clients.map(async (client$1) => {
2094
+ try {
2095
+ if ((await client$1.listPrompts()).some((p) => p.name === name)) serversWithPrompt.push(client$1.serverName);
2096
+ } catch (error) {
2097
+ console.error(`Failed to list prompts from ${client$1.serverName}:`, error);
2098
+ }
2099
+ }));
2100
+ if (serversWithPrompt.length === 0) throw new Error(`Prompt not found: ${name}`);
2101
+ if (serversWithPrompt.length > 1) throw new Error(`Prompt "${name}" exists on multiple servers: ${serversWithPrompt.join(", ")}. Use the prefixed format (e.g., "${serversWithPrompt[0]}__${name}") to specify which server to use.`);
2102
+ const client = clientManager.getClient(serversWithPrompt[0]);
2103
+ if (!client) throw new Error(`Server not found: ${serversWithPrompt[0]}`);
2104
+ return await client.getPrompt(name, args);
2105
+ });
1357
2106
  return server;
1358
2107
  }
1359
2108
 
@@ -1703,4 +2452,4 @@ var HttpTransportHandler = class {
1703
2452
  };
1704
2453
 
1705
2454
  //#endregion
1706
- export { findConfigFile as a, createServer as i, SseTransportHandler as n, McpClientManagerService as o, StdioTransportHandler as r, ConfigFetcherService as s, HttpTransportHandler as t };
2455
+ export { findConfigFile as a, ConfigFetcherService as c, createServer as i, SseTransportHandler as n, SkillService as o, StdioTransportHandler as r, McpClientManagerService as s, HttpTransportHandler as t };