@agiflowai/one-mcp 0.2.5 → 0.2.7

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.
@@ -38,6 +38,7 @@ let node_os = require("node:os");
38
38
  let __modelcontextprotocol_sdk_client_index_js = require("@modelcontextprotocol/sdk/client/index.js");
39
39
  let __modelcontextprotocol_sdk_client_stdio_js = require("@modelcontextprotocol/sdk/client/stdio.js");
40
40
  let __modelcontextprotocol_sdk_client_sse_js = require("@modelcontextprotocol/sdk/client/sse.js");
41
+ let __modelcontextprotocol_sdk_client_streamableHttp_js = require("@modelcontextprotocol/sdk/client/streamableHttp.js");
41
42
  let liquidjs = require("liquidjs");
42
43
  let __modelcontextprotocol_sdk_server_stdio_js = require("@modelcontextprotocol/sdk/server/stdio.js");
43
44
  let __modelcontextprotocol_sdk_server_sse_js = require("@modelcontextprotocol/sdk/server/sse.js");
@@ -204,10 +205,25 @@ function validateRemoteConfigSource(source) {
204
205
  * Claude Code / Claude Desktop standard MCP config format
205
206
  * This is the format users write in their config files
206
207
  */
208
+ /**
209
+ * Prompt skill configuration schema
210
+ * Converts a prompt to an executable skill
211
+ */
212
+ const PromptSkillConfigSchema = zod.z.object({
213
+ name: zod.z.string(),
214
+ description: zod.z.string(),
215
+ folder: zod.z.string().optional()
216
+ });
217
+ /**
218
+ * Prompt configuration schema
219
+ * Supports converting prompts to skills
220
+ */
221
+ const PromptConfigSchema = zod.z.object({ skill: PromptSkillConfigSchema.optional() });
207
222
  const AdditionalConfigSchema = zod.z.object({
208
223
  instruction: zod.z.string().optional(),
209
224
  toolBlacklist: zod.z.array(zod.z.string()).optional(),
210
- omitToolDescription: zod.z.boolean().optional()
225
+ omitToolDescription: zod.z.boolean().optional(),
226
+ prompts: zod.z.record(zod.z.string(), PromptConfigSchema).optional()
211
227
  }).optional();
212
228
  const ClaudeCodeStdioServerSchema = zod.z.object({
213
229
  command: zod.z.string(),
@@ -274,12 +290,25 @@ const McpSseConfigSchema = zod.z.object({
274
290
  url: zod.z.string().url(),
275
291
  headers: zod.z.record(zod.z.string(), zod.z.string()).optional()
276
292
  });
293
+ /**
294
+ * Internal prompt skill configuration schema
295
+ */
296
+ const InternalPromptSkillConfigSchema = zod.z.object({
297
+ name: zod.z.string(),
298
+ description: zod.z.string(),
299
+ folder: zod.z.string().optional()
300
+ });
301
+ /**
302
+ * Internal prompt configuration schema
303
+ */
304
+ const InternalPromptConfigSchema = zod.z.object({ skill: InternalPromptSkillConfigSchema.optional() });
277
305
  const McpServerConfigSchema = zod.z.discriminatedUnion("transport", [
278
306
  zod.z.object({
279
307
  name: zod.z.string(),
280
308
  instruction: zod.z.string().optional(),
281
309
  toolBlacklist: zod.z.array(zod.z.string()).optional(),
282
310
  omitToolDescription: zod.z.boolean().optional(),
311
+ prompts: zod.z.record(zod.z.string(), InternalPromptConfigSchema).optional(),
283
312
  transport: zod.z.literal("stdio"),
284
313
  config: McpStdioConfigSchema
285
314
  }),
@@ -288,6 +317,7 @@ const McpServerConfigSchema = zod.z.discriminatedUnion("transport", [
288
317
  instruction: zod.z.string().optional(),
289
318
  toolBlacklist: zod.z.array(zod.z.string()).optional(),
290
319
  omitToolDescription: zod.z.boolean().optional(),
320
+ prompts: zod.z.record(zod.z.string(), InternalPromptConfigSchema).optional(),
291
321
  transport: zod.z.literal("http"),
292
322
  config: McpHttpConfigSchema
293
323
  }),
@@ -296,6 +326,7 @@ const McpServerConfigSchema = zod.z.discriminatedUnion("transport", [
296
326
  instruction: zod.z.string().optional(),
297
327
  toolBlacklist: zod.z.array(zod.z.string()).optional(),
298
328
  omitToolDescription: zod.z.boolean().optional(),
329
+ prompts: zod.z.record(zod.z.string(), InternalPromptConfigSchema).optional(),
299
330
  transport: zod.z.literal("sse"),
300
331
  config: McpSseConfigSchema
301
332
  })
@@ -328,6 +359,7 @@ function transformClaudeCodeConfig(claudeConfig) {
328
359
  instruction: stdioConfig.instruction || stdioConfig.config?.instruction,
329
360
  toolBlacklist: stdioConfig.config?.toolBlacklist,
330
361
  omitToolDescription: stdioConfig.config?.omitToolDescription,
362
+ prompts: stdioConfig.config?.prompts,
331
363
  transport: "stdio",
332
364
  config: {
333
365
  command: interpolatedCommand,
@@ -345,6 +377,7 @@ function transformClaudeCodeConfig(claudeConfig) {
345
377
  instruction: httpConfig.instruction || httpConfig.config?.instruction,
346
378
  toolBlacklist: httpConfig.config?.toolBlacklist,
347
379
  omitToolDescription: httpConfig.config?.omitToolDescription,
380
+ prompts: httpConfig.config?.prompts,
348
381
  transport,
349
382
  config: {
350
383
  url: interpolatedUrl,
@@ -805,12 +838,14 @@ var ConfigFetcherService = class {
805
838
  //#region src/services/McpClientManagerService.ts
806
839
  /**
807
840
  * MCP Client wrapper for managing individual server connections
841
+ * This is an internal class used by McpClientManagerService
808
842
  */
809
843
  var McpClient = class {
810
844
  serverName;
811
845
  serverInstruction;
812
846
  toolBlacklist;
813
847
  omitToolDescription;
848
+ prompts;
814
849
  transport;
815
850
  client;
816
851
  childProcess;
@@ -820,6 +855,7 @@ var McpClient = class {
820
855
  this.serverInstruction = config.instruction;
821
856
  this.toolBlacklist = config.toolBlacklist;
822
857
  this.omitToolDescription = config.omitToolDescription;
858
+ this.prompts = config.prompts;
823
859
  this.transport = transport;
824
860
  this.client = client;
825
861
  }
@@ -915,7 +951,8 @@ var McpClientManagerService = class {
915
951
  const mcpClient = new McpClient(serverName, config.transport, client, {
916
952
  instruction: config.instruction,
917
953
  toolBlacklist: config.toolBlacklist,
918
- omitToolDescription: config.omitToolDescription
954
+ omitToolDescription: config.omitToolDescription,
955
+ prompts: config.prompts
919
956
  });
920
957
  try {
921
958
  await Promise.race([this.performConnection(mcpClient, config), new Promise((_, reject) => setTimeout(() => reject(/* @__PURE__ */ new Error(`Connection timeout after ${timeoutMs}ms`)), timeoutMs))]);
@@ -937,6 +974,7 @@ var McpClientManagerService = class {
937
974
  */
938
975
  async performConnection(mcpClient, config) {
939
976
  if (config.transport === "stdio") await this.connectStdioClient(mcpClient, config.config);
977
+ else if (config.transport === "http") await this.connectHttpClient(mcpClient, config.config);
940
978
  else if (config.transport === "sse") await this.connectSseClient(mcpClient, config.config);
941
979
  else throw new Error(`Unsupported transport type: ${config.transport}`);
942
980
  }
@@ -950,6 +988,10 @@ var McpClientManagerService = class {
950
988
  const childProcess = transport["_process"];
951
989
  if (childProcess) mcpClient.setChildProcess(childProcess);
952
990
  }
991
+ async connectHttpClient(mcpClient, config) {
992
+ const transport = new __modelcontextprotocol_sdk_client_streamableHttp_js.StreamableHTTPClientTransport(new URL(config.url), { requestInit: config.headers ? { headers: config.headers } : void 0 });
993
+ await mcpClient["client"].connect(transport);
994
+ }
953
995
  async connectSseClient(mcpClient, config) {
954
996
  const transport = new __modelcontextprotocol_sdk_client_sse_js.SSEClientTransport(new URL(config.url));
955
997
  await mcpClient["client"].connect(transport);
@@ -992,6 +1034,209 @@ var McpClientManagerService = class {
992
1034
  }
993
1035
  };
994
1036
 
1037
+ //#endregion
1038
+ //#region src/utils/findConfigFile.ts
1039
+ /**
1040
+ * Config File Finder Utility
1041
+ *
1042
+ * DESIGN PATTERNS:
1043
+ * - Utility function pattern for reusable logic
1044
+ * - Fail-fast pattern with early returns
1045
+ * - Environment variable configuration pattern
1046
+ *
1047
+ * CODING STANDARDS:
1048
+ * - Use sync filesystem operations for config discovery (performance)
1049
+ * - Check PROJECT_PATH environment variable first
1050
+ * - Fall back to current working directory
1051
+ * - Support both .yaml and .json extensions
1052
+ * - Return null if no config file is found
1053
+ *
1054
+ * AVOID:
1055
+ * - Throwing errors (return null instead for optional config)
1056
+ * - Hardcoded file names without extension variants
1057
+ * - Ignoring environment variables
1058
+ */
1059
+ /**
1060
+ * Find MCP configuration file by checking PROJECT_PATH first, then cwd
1061
+ * Looks for both mcp-config.yaml and mcp-config.json
1062
+ *
1063
+ * @returns Absolute path to config file, or null if not found
1064
+ */
1065
+ function findConfigFile() {
1066
+ const configFileNames = [
1067
+ "mcp-config.yaml",
1068
+ "mcp-config.yml",
1069
+ "mcp-config.json"
1070
+ ];
1071
+ const projectPath = process.env.PROJECT_PATH;
1072
+ if (projectPath) for (const fileName of configFileNames) {
1073
+ const configPath = (0, node_path.resolve)(projectPath, fileName);
1074
+ if ((0, node_fs.existsSync)(configPath)) return configPath;
1075
+ }
1076
+ const cwd = process.cwd();
1077
+ for (const fileName of configFileNames) {
1078
+ const configPath = (0, node_path.join)(cwd, fileName);
1079
+ if ((0, node_fs.existsSync)(configPath)) return configPath;
1080
+ }
1081
+ return null;
1082
+ }
1083
+
1084
+ //#endregion
1085
+ //#region src/utils/parseToolName.ts
1086
+ /**
1087
+ * Parse tool name to extract server and actual tool name
1088
+ * Supports both plain tool names and prefixed format: {serverName}__{toolName}
1089
+ *
1090
+ * @param toolName - The tool name to parse (e.g., "my_tool" or "server__my_tool")
1091
+ * @returns Parsed result with optional serverName and actualToolName
1092
+ *
1093
+ * @example
1094
+ * parseToolName("my_tool") // { actualToolName: "my_tool" }
1095
+ * parseToolName("server__my_tool") // { serverName: "server", actualToolName: "my_tool" }
1096
+ */
1097
+ function parseToolName(toolName) {
1098
+ const separatorIndex = toolName.indexOf("__");
1099
+ if (separatorIndex > 0) return {
1100
+ serverName: toolName.substring(0, separatorIndex),
1101
+ actualToolName: toolName.substring(separatorIndex + 2)
1102
+ };
1103
+ return { actualToolName: toolName };
1104
+ }
1105
+
1106
+ //#endregion
1107
+ //#region src/utils/parseFrontMatter.ts
1108
+ /**
1109
+ * Parses YAML front matter from a string content.
1110
+ * Front matter must be at the start of the content, delimited by `---`.
1111
+ *
1112
+ * Supports:
1113
+ * - Simple key: value pairs
1114
+ * - Literal block scalar (|) for multi-line preserving newlines
1115
+ * - Folded block scalar (>) for multi-line folding to single line
1116
+ *
1117
+ * @param content - The content string that may contain front matter
1118
+ * @returns Object with parsed front matter (or null) and remaining content
1119
+ *
1120
+ * @example
1121
+ * const result = parseFrontMatter(`---
1122
+ * name: my-skill
1123
+ * description: A skill description
1124
+ * ---
1125
+ * The actual content here`);
1126
+ * // result.frontMatter = { name: 'my-skill', description: 'A skill description' }
1127
+ * // result.content = 'The actual content here'
1128
+ *
1129
+ * @example
1130
+ * // Multi-line with literal block scalar
1131
+ * const result = parseFrontMatter(`---
1132
+ * name: my-skill
1133
+ * description: |
1134
+ * Line 1
1135
+ * Line 2
1136
+ * ---
1137
+ * Content`);
1138
+ * // result.frontMatter.description = 'Line 1\nLine 2'
1139
+ */
1140
+ function parseFrontMatter(content) {
1141
+ const trimmedContent = content.trimStart();
1142
+ if (!trimmedContent.startsWith("---")) return {
1143
+ frontMatter: null,
1144
+ content
1145
+ };
1146
+ const endDelimiterIndex = trimmedContent.indexOf("\n---", 3);
1147
+ if (endDelimiterIndex === -1) return {
1148
+ frontMatter: null,
1149
+ content
1150
+ };
1151
+ const yamlContent = trimmedContent.slice(4, endDelimiterIndex).trim();
1152
+ if (!yamlContent) return {
1153
+ frontMatter: null,
1154
+ content
1155
+ };
1156
+ const frontMatter = {};
1157
+ const lines = yamlContent.split("\n");
1158
+ let currentKey = null;
1159
+ let currentValue = [];
1160
+ let multiLineMode = null;
1161
+ let baseIndent = 0;
1162
+ const saveCurrentKey = () => {
1163
+ if (currentKey && currentValue.length > 0) if (multiLineMode === "literal") frontMatter[currentKey] = currentValue.join("\n").trimEnd();
1164
+ else if (multiLineMode === "folded") frontMatter[currentKey] = currentValue.join(" ").trim();
1165
+ else frontMatter[currentKey] = currentValue.join("").trim();
1166
+ currentKey = null;
1167
+ currentValue = [];
1168
+ multiLineMode = null;
1169
+ baseIndent = 0;
1170
+ };
1171
+ for (let i = 0; i < lines.length; i++) {
1172
+ const line = lines[i];
1173
+ const trimmedLine = line.trim();
1174
+ const colonIndex = line.indexOf(":");
1175
+ if (colonIndex !== -1 && !line.startsWith(" ") && !line.startsWith(" ")) {
1176
+ saveCurrentKey();
1177
+ const key = line.slice(0, colonIndex).trim();
1178
+ let value = line.slice(colonIndex + 1).trim();
1179
+ if (value === "|" || value === "|-") {
1180
+ currentKey = key;
1181
+ multiLineMode = "literal";
1182
+ if (i + 1 < lines.length) {
1183
+ const match = lines[i + 1].match(/^(\s+)/);
1184
+ baseIndent = match ? match[1].length : 2;
1185
+ }
1186
+ } else if (value === ">" || value === ">-") {
1187
+ currentKey = key;
1188
+ multiLineMode = "folded";
1189
+ if (i + 1 < lines.length) {
1190
+ const match = lines[i + 1].match(/^(\s+)/);
1191
+ baseIndent = match ? match[1].length : 2;
1192
+ }
1193
+ } else {
1194
+ if (value.startsWith("\"") && value.endsWith("\"") || value.startsWith("'") && value.endsWith("'")) value = value.slice(1, -1);
1195
+ if (key && value) frontMatter[key] = value;
1196
+ }
1197
+ } else if (multiLineMode && currentKey) {
1198
+ const lineIndent = line.match(/^(\s*)/)?.[1].length || 0;
1199
+ if (trimmedLine === "") currentValue.push("");
1200
+ else if (lineIndent >= baseIndent) {
1201
+ const unindentedLine = line.slice(baseIndent);
1202
+ currentValue.push(unindentedLine);
1203
+ } else saveCurrentKey();
1204
+ }
1205
+ }
1206
+ saveCurrentKey();
1207
+ return {
1208
+ frontMatter,
1209
+ content: trimmedContent.slice(endDelimiterIndex + 4).trimStart()
1210
+ };
1211
+ }
1212
+ /**
1213
+ * Checks if parsed front matter contains valid skill metadata.
1214
+ * A valid skill front matter must have both `name` and `description` fields.
1215
+ *
1216
+ * @param frontMatter - The parsed front matter object
1217
+ * @returns True if front matter contains valid skill metadata
1218
+ */
1219
+ function isValidSkillFrontMatter(frontMatter) {
1220
+ return frontMatter !== null && typeof frontMatter.name === "string" && frontMatter.name.length > 0 && typeof frontMatter.description === "string" && frontMatter.description.length > 0;
1221
+ }
1222
+ /**
1223
+ * Extracts skill front matter from content if present and valid.
1224
+ *
1225
+ * @param content - The content string that may contain skill front matter
1226
+ * @returns Object with skill metadata and content, or null if no valid skill front matter
1227
+ */
1228
+ function extractSkillFrontMatter(content) {
1229
+ const { frontMatter, content: remainingContent } = parseFrontMatter(content);
1230
+ if (frontMatter && isValidSkillFrontMatter(frontMatter)) return {
1231
+ skill: {
1232
+ name: frontMatter.name,
1233
+ description: frontMatter.description
1234
+ },
1235
+ content: remainingContent
1236
+ };
1237
+ return null;
1238
+ }
1239
+
995
1240
  //#endregion
996
1241
  //#region src/services/SkillService.ts
997
1242
  /**
@@ -1062,14 +1307,21 @@ var SkillService = class {
1062
1307
  skillPaths;
1063
1308
  cachedSkills = null;
1064
1309
  skillsByName = null;
1310
+ /** Active file watchers for skill directories */
1311
+ watchers = [];
1312
+ /** Callback invoked when cache is invalidated due to file changes */
1313
+ onCacheInvalidated;
1065
1314
  /**
1066
1315
  * Creates a new SkillService instance
1067
1316
  * @param cwd - Current working directory for resolving relative paths
1068
1317
  * @param skillPaths - Array of paths to skills directories
1318
+ * @param options - Optional configuration
1319
+ * @param options.onCacheInvalidated - Callback invoked when cache is invalidated due to file changes
1069
1320
  */
1070
- constructor(cwd, skillPaths) {
1321
+ constructor(cwd, skillPaths, options) {
1071
1322
  this.cwd = cwd;
1072
1323
  this.skillPaths = skillPaths;
1324
+ this.onCacheInvalidated = options?.onCacheInvalidated;
1073
1325
  }
1074
1326
  /**
1075
1327
  * Get all available skills from configured directories.
@@ -1115,6 +1367,54 @@ var SkillService = class {
1115
1367
  this.skillsByName = null;
1116
1368
  }
1117
1369
  /**
1370
+ * Starts watching skill directories for changes to SKILL.md files.
1371
+ * When changes are detected, the cache is automatically invalidated.
1372
+ *
1373
+ * Uses Node.js fs.watch with recursive option for efficient directory monitoring.
1374
+ * Only invalidates cache when SKILL.md files are modified.
1375
+ *
1376
+ * @example
1377
+ * const skillService = new SkillService(cwd, skillPaths, {
1378
+ * onCacheInvalidated: () => console.log('Skills cache invalidated')
1379
+ * });
1380
+ * await skillService.startWatching();
1381
+ */
1382
+ async startWatching() {
1383
+ this.stopWatching();
1384
+ for (const skillPath of this.skillPaths) {
1385
+ const skillsDir = (0, node_path.isAbsolute)(skillPath) ? skillPath : (0, node_path.join)(this.cwd, skillPath);
1386
+ if (!await pathExists(skillsDir)) continue;
1387
+ const abortController = new AbortController();
1388
+ this.watchers.push(abortController);
1389
+ this.watchDirectory(skillsDir, abortController.signal).catch((error) => {
1390
+ if (error?.name !== "AbortError") console.error(`[skill-watcher] Error watching ${skillsDir}: ${error instanceof Error ? error.message : "Unknown error"}`);
1391
+ });
1392
+ }
1393
+ }
1394
+ /**
1395
+ * Stops all active file watchers.
1396
+ * Should be called when the service is being disposed.
1397
+ */
1398
+ stopWatching() {
1399
+ for (const controller of this.watchers) controller.abort();
1400
+ this.watchers = [];
1401
+ }
1402
+ /**
1403
+ * Watches a directory for changes to SKILL.md files.
1404
+ * @param dirPath - Directory path to watch
1405
+ * @param signal - AbortSignal to stop watching
1406
+ */
1407
+ async watchDirectory(dirPath, signal) {
1408
+ const watcher = (0, node_fs_promises.watch)(dirPath, {
1409
+ recursive: true,
1410
+ signal
1411
+ });
1412
+ for await (const event of watcher) if (event.filename && event.filename.endsWith("SKILL.md")) {
1413
+ this.clearCache();
1414
+ this.onCacheInvalidated?.();
1415
+ }
1416
+ }
1417
+ /**
1118
1418
  * Load skills from a directory.
1119
1419
  * Supports both flat structure (SKILL.md) and nested structure (name/SKILL.md).
1120
1420
  *
@@ -1173,6 +1473,7 @@ var SkillService = class {
1173
1473
  }
1174
1474
  /**
1175
1475
  * Load a single skill file and parse its frontmatter.
1476
+ * Supports multi-line YAML values using literal (|) and folded (>) block scalars.
1176
1477
  *
1177
1478
  * @param filePath - Path to the SKILL.md file
1178
1479
  * @param location - Whether this is a 'project' or 'user' skill
@@ -1186,150 +1487,62 @@ var SkillService = class {
1186
1487
  * // Returns null if frontmatter is missing name or description
1187
1488
  */
1188
1489
  async loadSkillFile(filePath, location) {
1189
- let content;
1490
+ let fileContent;
1190
1491
  try {
1191
- content = await (0, node_fs_promises.readFile)(filePath, "utf-8");
1492
+ fileContent = await (0, node_fs_promises.readFile)(filePath, "utf-8");
1192
1493
  } catch (error) {
1193
1494
  throw new SkillLoadError(`Failed to read skill file: ${error instanceof Error ? error.message : "Unknown error"}`, filePath, error instanceof Error ? error : void 0);
1194
1495
  }
1195
- const { metadata, body } = this.parseFrontmatter(content);
1196
- if (!metadata.name || !metadata.description) return null;
1496
+ const { frontMatter, content } = parseFrontMatter(fileContent);
1497
+ if (!frontMatter || !frontMatter.name || !frontMatter.description) return null;
1197
1498
  return {
1198
- name: metadata.name,
1199
- description: metadata.description,
1499
+ name: frontMatter.name,
1500
+ description: frontMatter.description,
1200
1501
  location,
1201
- content: body,
1502
+ content,
1202
1503
  basePath: (0, node_path.dirname)(filePath)
1203
1504
  };
1204
1505
  }
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
1506
  };
1249
1507
 
1250
1508
  //#endregion
1251
- //#region src/utils/findConfigFile.ts
1509
+ //#region src/constants/index.ts
1252
1510
  /**
1253
- * Config File Finder Utility
1254
- *
1255
- * DESIGN PATTERNS:
1256
- * - Utility function pattern for reusable logic
1257
- * - Fail-fast pattern with early returns
1258
- * - Environment variable configuration pattern
1259
- *
1260
- * CODING STANDARDS:
1261
- * - Use sync filesystem operations for config discovery (performance)
1262
- * - Check PROJECT_PATH environment variable first
1263
- * - Fall back to current working directory
1264
- * - Support both .yaml and .json extensions
1265
- * - Return null if no config file is found
1266
- *
1267
- * AVOID:
1268
- * - Throwing errors (return null instead for optional config)
1269
- * - Hardcoded file names without extension variants
1270
- * - Ignoring environment variables
1511
+ * Shared constants for one-mcp package
1271
1512
  */
1272
1513
  /**
1273
- * Find MCP configuration file by checking PROJECT_PATH first, then cwd
1274
- * Looks for both mcp-config.yaml and mcp-config.json
1275
- *
1276
- * @returns Absolute path to config file, or null if not found
1514
+ * Prefix added to skill names when they clash with MCP tool names.
1515
+ * This ensures skills can be uniquely identified even when a tool has the same name.
1277
1516
  */
1278
- function findConfigFile() {
1279
- const configFileNames = [
1280
- "mcp-config.yaml",
1281
- "mcp-config.yml",
1282
- "mcp-config.json"
1283
- ];
1284
- const projectPath = process.env.PROJECT_PATH;
1285
- if (projectPath) for (const fileName of configFileNames) {
1286
- const configPath = (0, node_path.resolve)(projectPath, fileName);
1287
- if ((0, node_fs.existsSync)(configPath)) return configPath;
1288
- }
1289
- const cwd = process.cwd();
1290
- for (const fileName of configFileNames) {
1291
- const configPath = (0, node_path.join)(cwd, fileName);
1292
- if ((0, node_fs.existsSync)(configPath)) return configPath;
1293
- }
1294
- return null;
1295
- }
1296
-
1297
- //#endregion
1298
- //#region src/utils/parseToolName.ts
1517
+ const SKILL_PREFIX$1 = "skill__";
1299
1518
  /**
1300
- * Parse tool name to extract server and actual tool name
1301
- * Supports both plain tool names and prefixed format: {serverName}__{toolName}
1302
- *
1303
- * @param toolName - The tool name to parse (e.g., "my_tool" or "server__my_tool")
1304
- * @returns Parsed result with optional serverName and actualToolName
1305
- *
1306
- * @example
1307
- * parseToolName("my_tool") // { actualToolName: "my_tool" }
1308
- * parseToolName("server__my_tool") // { serverName: "server", actualToolName: "my_tool" }
1519
+ * Log prefix for skill detection messages.
1520
+ * Used to easily filter skill detection logs in stderr output.
1309
1521
  */
1310
- function parseToolName(toolName) {
1311
- const separatorIndex = toolName.indexOf("__");
1312
- if (separatorIndex > 0) return {
1313
- serverName: toolName.substring(0, separatorIndex),
1314
- actualToolName: toolName.substring(separatorIndex + 2)
1315
- };
1316
- return { actualToolName: toolName };
1317
- }
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";
1522
+ const LOG_PREFIX_SKILL_DETECTION = "[skill-detection]";
1523
+ /**
1524
+ * Prefix for prompt-based skill locations.
1525
+ * Format: "prompt:{serverName}:{promptName}"
1526
+ */
1527
+ const PROMPT_LOCATION_PREFIX = "prompt:";
1322
1528
 
1323
1529
  //#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";
1530
+ //#region src/templates/toolkit-description.liquid?raw
1531
+ var toolkit_description_default = "<toolkit>\n<instruction>\nBefore you use any capabilities below, you MUST call this tool with a list of names to learn how to use them properly; this includes:\n- For tools: Arguments schema needed to pass to use_tool\n- For skills: Detailed instructions that will expand when invoked (Prefer to be explored first when relevant)\n\nThis tool is optimized for batch queries - you can request multiple capabilities at once for better performance.\n\nHow to invoke:\n- For MCP tools: Use use_tool with toolName and toolArgs based on the schema\n- For skills: Use this tool with the skill name to get expanded instructions\n</instruction>\n\n<available_capabilities>\n{% for server in servers -%}\n<group name=\"{{ server.name }}\">\n{% if server.instruction -%}\n<group_instruction>{{ server.instruction }}</group_instruction>\n{% endif -%}\n{% if server.omitToolDescription -%}\n{% for toolName in server.toolNames -%}\n<item name=\"{{ toolName }}\"></item>\n{% endfor -%}\n{% else -%}\n{% for tool in server.tools -%}\n<item name=\"{{ tool.displayName }}\"><description>{{ tool.description | default: \"No description\" }}</description></item>\n{% endfor -%}\n{% endif -%}\n</group>\n{% endfor -%}\n{% if skills.size > 0 -%}\n<group name=\"skills\">\n{% for skill in skills -%}\n<item name=\"{{ skill.displayName }}\"><description>{{ skill.description }}</description></item>\n{% endfor -%}\n</group>\n{% endif -%}\n</available_capabilities>\n</toolkit>\n";
1326
1532
 
1327
1533
  //#endregion
1328
1534
  //#region src/tools/DescribeToolsTool.ts
1329
1535
  /**
1330
- * Prefix used to identify skill invocations
1536
+ * Formats skill instructions with the loading command message prefix.
1537
+ * This message is used by Claude Code to indicate that a skill is being loaded.
1538
+ *
1539
+ * @param name - The skill name
1540
+ * @param instructions - The raw skill instructions/content
1541
+ * @returns Formatted instructions with command message prefix
1331
1542
  */
1332
- const SKILL_PREFIX$1 = "skill__";
1543
+ function formatSkillInstructions(name, instructions) {
1544
+ return `<command-message>The "${name}" skill is loading</command-message>\n${instructions}`;
1545
+ }
1333
1546
  /**
1334
1547
  * DescribeToolsTool provides progressive disclosure of MCP tools and skills.
1335
1548
  *
@@ -1352,6 +1565,8 @@ var DescribeToolsTool = class DescribeToolsTool {
1352
1565
  clientManager;
1353
1566
  skillService;
1354
1567
  liquid = new liquidjs.Liquid();
1568
+ /** Cache for auto-detected skills from prompt front-matter */
1569
+ autoDetectedSkillsCache = null;
1355
1570
  /**
1356
1571
  * Creates a new DescribeToolsTool instance
1357
1572
  * @param clientManager - The MCP client manager for accessing remote servers
@@ -1362,43 +1577,169 @@ var DescribeToolsTool = class DescribeToolsTool {
1362
1577
  this.skillService = skillService;
1363
1578
  }
1364
1579
  /**
1365
- * Builds the skills section of the tool description using a Liquid template.
1580
+ * Clears the cached auto-detected skills from prompt front-matter.
1581
+ * Use this when prompt configurations may have changed or when
1582
+ * the skill service cache is invalidated.
1583
+ */
1584
+ clearAutoDetectedSkillsCache() {
1585
+ this.autoDetectedSkillsCache = null;
1586
+ }
1587
+ /**
1588
+ * Detects and caches skills from prompt front-matter across all connected MCP servers.
1589
+ * Fetches all prompts and checks their content for YAML front-matter with name/description.
1590
+ * Results are cached to avoid repeated fetches.
1366
1591
  *
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.
1592
+ * Error Handling Strategy:
1593
+ * - Errors are logged to stderr but do not fail the overall detection process
1594
+ * - This ensures partial results are returned even if some servers/prompts fail
1595
+ * - Common failure reasons: server temporarily unavailable, prompt requires arguments,
1596
+ * network timeout, or server doesn't support listPrompts
1597
+ * - Errors are prefixed with [skill-detection] for easy filtering in logs
1370
1598
  *
1371
- * @param mcpToolNames - Set of MCP tool names to check for clashes
1372
- * @returns Rendered skills section string with available skills list
1599
+ * @returns Array of auto-detected skills from prompt front-matter
1373
1600
  */
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;
1601
+ async detectSkillsFromPromptFrontMatter() {
1602
+ if (this.autoDetectedSkillsCache !== null) return this.autoDetectedSkillsCache;
1603
+ const clients = this.clientManager.getAllClients();
1604
+ const autoDetectedSkills = [];
1605
+ let listPromptsFailures = 0;
1606
+ let fetchPromptFailures = 0;
1607
+ const fetchPromises = [];
1608
+ for (const client of clients) {
1609
+ const configuredPromptNames = new Set(client.prompts ? Object.keys(client.prompts) : []);
1610
+ const listPromptsPromise = (async () => {
1611
+ try {
1612
+ const prompts = await client.listPrompts();
1613
+ if (!prompts || prompts.length === 0) return;
1614
+ const promptFetchPromises = prompts.map(async (promptInfo) => {
1615
+ if (configuredPromptNames.has(promptInfo.name)) return;
1616
+ try {
1617
+ const skillExtraction = extractSkillFrontMatter(((await client.getPrompt(promptInfo.name)).messages || []).map((m) => {
1618
+ const content = m.content;
1619
+ if (typeof content === "string") return content;
1620
+ if (content && typeof content === "object" && "text" in content) return String(content.text);
1621
+ return "";
1622
+ }).join("\n"));
1623
+ if (skillExtraction) autoDetectedSkills.push({
1624
+ serverName: client.serverName,
1625
+ promptName: promptInfo.name,
1626
+ skill: skillExtraction.skill
1627
+ });
1628
+ } catch (error) {
1629
+ fetchPromptFailures++;
1630
+ console.error(`${LOG_PREFIX_SKILL_DETECTION} Failed to fetch prompt '${promptInfo.name}' from ${client.serverName}: ${error instanceof Error ? error.message : "Unknown error"}`);
1631
+ }
1632
+ });
1633
+ await Promise.all(promptFetchPromises);
1634
+ } catch (error) {
1635
+ listPromptsFailures++;
1636
+ console.error(`${LOG_PREFIX_SKILL_DETECTION} Failed to list prompts from ${client.serverName}: ${error instanceof Error ? error.message : "Unknown error"}`);
1637
+ }
1638
+ })();
1639
+ fetchPromises.push(listPromptsPromise);
1640
+ }
1641
+ await Promise.all(fetchPromises);
1642
+ if (listPromptsFailures > 0 || fetchPromptFailures > 0) console.error(`${LOG_PREFIX_SKILL_DETECTION} Completed with ${listPromptsFailures} server failure(s) and ${fetchPromptFailures} prompt failure(s). Detected ${autoDetectedSkills.length} skill(s).`);
1643
+ this.autoDetectedSkillsCache = autoDetectedSkills;
1644
+ return autoDetectedSkills;
1645
+ }
1646
+ /**
1647
+ * Collects skills derived from prompt configurations across all connected MCP servers.
1648
+ * Includes both explicitly configured prompts and auto-detected skills from front-matter.
1649
+ *
1650
+ * @returns Array of skill template data derived from prompts
1651
+ */
1652
+ async collectPromptSkills() {
1653
+ const clients = this.clientManager.getAllClients();
1654
+ const promptSkills = [];
1655
+ for (const client of clients) {
1656
+ if (!client.prompts) continue;
1657
+ for (const promptConfig of Object.values(client.prompts)) if (promptConfig.skill) promptSkills.push({
1658
+ name: promptConfig.skill.name,
1659
+ displayName: promptConfig.skill.name,
1660
+ description: promptConfig.skill.description
1661
+ });
1662
+ }
1663
+ const autoDetectedSkills = await this.detectSkillsFromPromptFrontMatter();
1664
+ for (const autoSkill of autoDetectedSkills) promptSkills.push({
1665
+ name: autoSkill.skill.name,
1666
+ displayName: autoSkill.skill.name,
1667
+ description: autoSkill.skill.description
1668
+ });
1669
+ return promptSkills;
1670
+ }
1671
+ /**
1672
+ * Finds a prompt-based skill by name from all connected MCP servers.
1673
+ * Searches both explicitly configured prompts and auto-detected skills from front-matter.
1674
+ *
1675
+ * @param skillName - The skill name to search for
1676
+ * @returns Object with serverName, promptName, and skill config, or undefined if not found
1677
+ */
1678
+ async findPromptSkill(skillName) {
1679
+ if (!skillName) return void 0;
1680
+ const clients = this.clientManager.getAllClients();
1681
+ for (const client of clients) {
1682
+ if (!client.prompts) continue;
1683
+ for (const [promptName, promptConfig] of Object.entries(client.prompts)) if (promptConfig.skill && promptConfig.skill.name === skillName) return {
1684
+ serverName: client.serverName,
1685
+ promptName,
1686
+ skill: promptConfig.skill
1687
+ };
1688
+ }
1689
+ const autoDetectedSkills = await this.detectSkillsFromPromptFrontMatter();
1690
+ for (const autoSkill of autoDetectedSkills) if (autoSkill.skill.name === skillName) return {
1691
+ serverName: autoSkill.serverName,
1692
+ promptName: autoSkill.promptName,
1693
+ skill: autoSkill.skill,
1694
+ autoDetected: true
1695
+ };
1696
+ }
1697
+ /**
1698
+ * Retrieves skill content from a prompt-based skill configuration.
1699
+ * Fetches the prompt from the MCP server and extracts text content.
1700
+ * Handles both explicitly configured prompts and auto-detected skills from front-matter.
1701
+ *
1702
+ * @param skillName - The skill name being requested
1703
+ * @returns SkillDescription if found and successfully fetched, undefined otherwise
1704
+ */
1705
+ async getPromptSkillContent(skillName) {
1706
+ const promptSkill = await this.findPromptSkill(skillName);
1707
+ if (!promptSkill) return void 0;
1708
+ const client = this.clientManager.getClient(promptSkill.serverName);
1709
+ if (!client) {
1710
+ console.error(`Client not found for server '${promptSkill.serverName}' when fetching prompt skill '${skillName}'`);
1711
+ return;
1712
+ }
1713
+ try {
1714
+ const rawInstructions = (await client.getPrompt(promptSkill.promptName)).messages?.map((m) => {
1715
+ const content = m.content;
1716
+ if (typeof content === "string") return content;
1717
+ if (content && typeof content === "object" && "text" in content) return String(content.text);
1718
+ return "";
1719
+ }).join("\n") || "";
1382
1720
  return {
1383
- name: skill.name,
1384
- displayName: needsPrefix ? `${SKILL_PREFIX$1}${skill.name}` : skill.name,
1385
- description: skill.description
1721
+ name: promptSkill.skill.name,
1722
+ location: promptSkill.skill.folder || `${PROMPT_LOCATION_PREFIX}${promptSkill.serverName}/${promptSkill.promptName}`,
1723
+ instructions: formatSkillInstructions(promptSkill.skill.name, rawInstructions)
1386
1724
  };
1387
- });
1388
- return this.liquid.parseAndRender(skills_description_default, { skills });
1725
+ } catch (error) {
1726
+ console.error(`Failed to get prompt-based skill '${skillName}': ${error instanceof Error ? error.message : "Unknown error"}`);
1727
+ return;
1728
+ }
1389
1729
  }
1390
1730
  /**
1391
- * Builds the MCP servers section of the tool description using a Liquid template.
1731
+ * Builds the combined toolkit description using a single Liquid template.
1392
1732
  *
1393
- * Collects all tools from connected MCP servers, detects name clashes,
1394
- * and renders them using the mcp-servers-description.liquid template.
1733
+ * Collects all tools from connected MCP servers and all skills, then renders
1734
+ * them together using the toolkit-description.liquid template.
1395
1735
  *
1396
1736
  * Tool names are prefixed with serverName__ when the same tool exists
1397
- * on multiple servers to avoid ambiguity.
1737
+ * on multiple servers. Skill names are prefixed with skill__ when they
1738
+ * clash with MCP tools or other skills.
1398
1739
  *
1399
- * @returns Object with rendered servers section and set of all tool names for skill clash detection
1740
+ * @returns Object with rendered description and set of all tool names
1400
1741
  */
1401
- async buildServersSection() {
1742
+ async buildToolkitDescription() {
1402
1743
  const clients = this.clientManager.getAllClients();
1403
1744
  const toolToServers = /* @__PURE__ */ new Map();
1404
1745
  const serverToolsMap = /* @__PURE__ */ new Map();
@@ -1419,9 +1760,6 @@ var DescribeToolsTool = class DescribeToolsTool {
1419
1760
  }));
1420
1761
  /**
1421
1762
  * 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
1763
  */
1426
1764
  const formatToolName = (toolName, serverName) => {
1427
1765
  return (toolToServers.get(toolName) || []).length > 1 ? `${serverName}__${toolName}` : toolName;
@@ -1441,31 +1779,56 @@ var DescribeToolsTool = class DescribeToolsTool {
1441
1779
  toolNames: formattedTools.map((t) => t.displayName)
1442
1780
  };
1443
1781
  });
1782
+ const rawSkills = this.skillService ? await this.skillService.getSkills() : [];
1783
+ const promptSkills = await this.collectPromptSkills();
1784
+ const seenSkillNames = /* @__PURE__ */ new Set();
1785
+ const allSkillsData = [];
1786
+ for (const skill of rawSkills) if (!seenSkillNames.has(skill.name)) {
1787
+ seenSkillNames.add(skill.name);
1788
+ allSkillsData.push({
1789
+ name: skill.name,
1790
+ displayName: skill.name,
1791
+ description: skill.description
1792
+ });
1793
+ }
1794
+ for (const skill of promptSkills) if (!seenSkillNames.has(skill.name)) {
1795
+ seenSkillNames.add(skill.name);
1796
+ allSkillsData.push(skill);
1797
+ }
1798
+ const skills = allSkillsData.map((skill) => {
1799
+ const clashesWithMcpTool = allToolNames.has(skill.name);
1800
+ return {
1801
+ name: skill.name,
1802
+ displayName: clashesWithMcpTool ? `${SKILL_PREFIX$1}${skill.name}` : skill.name,
1803
+ description: skill.description
1804
+ };
1805
+ });
1444
1806
  return {
1445
- content: await this.liquid.parseAndRender(mcp_servers_description_default, { servers }),
1807
+ content: await this.liquid.parseAndRender(toolkit_description_default, {
1808
+ servers,
1809
+ skills
1810
+ }),
1446
1811
  toolNames: allToolNames
1447
1812
  };
1448
1813
  }
1449
1814
  /**
1450
- * Gets the tool definition including available servers, tools, and skills.
1815
+ * Gets the tool definition including available tools and skills in a unified format.
1451
1816
  *
1452
1817
  * 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
1818
+ * - All MCP tools from connected servers
1819
+ * - All available skills (file-based and prompt-based)
1820
+ * - Unified instructions for querying capability details
1456
1821
  *
1457
- * Tool names are prefixed with serverName__ when the same tool name
1458
- * exists on multiple servers to avoid ambiguity.
1822
+ * Tool names are prefixed with serverName__ when clashing.
1823
+ * Skill names are prefixed with skill__ when clashing.
1459
1824
  *
1460
1825
  * @returns Tool definition with description and input schema
1461
1826
  */
1462
1827
  async getDefinition() {
1463
- const serversResult = await this.buildServersSection();
1464
- const skillsSection = await this.buildSkillsSection(serversResult.toolNames);
1828
+ const { content } = await this.buildToolkitDescription();
1465
1829
  return {
1466
1830
  name: DescribeToolsTool.TOOL_NAME,
1467
- description: `${serversResult.content}
1468
- ${skillsSection}`,
1831
+ description: content,
1469
1832
  inputSchema: {
1470
1833
  type: "object",
1471
1834
  properties: { toolNames: {
@@ -1530,16 +1893,24 @@ ${skillsSection}`,
1530
1893
  const notFoundItems = [];
1531
1894
  for (const requestedName of toolNames) {
1532
1895
  if (requestedName.startsWith(SKILL_PREFIX$1)) {
1533
- const skillName = requestedName.slice(7);
1896
+ const skillName = requestedName.slice(SKILL_PREFIX$1.length);
1534
1897
  if (this.skillService) {
1535
1898
  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);
1899
+ if (skill) {
1900
+ foundSkills.push({
1901
+ name: skill.name,
1902
+ location: skill.basePath,
1903
+ instructions: formatSkillInstructions(skill.name, skill.content)
1904
+ });
1905
+ continue;
1906
+ }
1907
+ }
1908
+ const promptSkillContent = await this.getPromptSkillContent(skillName);
1909
+ if (promptSkillContent) {
1910
+ foundSkills.push(promptSkillContent);
1911
+ continue;
1912
+ }
1913
+ notFoundItems.push(requestedName);
1543
1914
  continue;
1544
1915
  }
1545
1916
  const { serverName, actualToolName } = parseToolName(requestedName);
@@ -1568,11 +1939,16 @@ ${skillsSection}`,
1568
1939
  foundSkills.push({
1569
1940
  name: skill.name,
1570
1941
  location: skill.basePath,
1571
- instructions: skill.content
1942
+ instructions: formatSkillInstructions(skill.name, skill.content)
1572
1943
  });
1573
1944
  continue;
1574
1945
  }
1575
1946
  }
1947
+ const promptSkillContent = await this.getPromptSkillContent(actualToolName);
1948
+ if (promptSkillContent) {
1949
+ foundSkills.push(promptSkillContent);
1950
+ continue;
1951
+ }
1576
1952
  notFoundItems.push(requestedName);
1577
1953
  continue;
1578
1954
  }
@@ -1681,7 +2057,7 @@ var UseToolTool = class UseToolTool {
1681
2057
  getDefinition() {
1682
2058
  return {
1683
2059
  name: UseToolTool.TOOL_NAME,
1684
- 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:
2060
+ 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:
1685
2061
  - Provide toolName and toolArgs based on the schema
1686
2062
  - If multiple servers provide the same tool, specify serverName
1687
2063
  `,
@@ -1720,6 +2096,37 @@ var UseToolTool = class UseToolTool {
1720
2096
  }] };
1721
2097
  }
1722
2098
  /**
2099
+ * Finds a prompt-based skill by name from all connected MCP servers.
2100
+ *
2101
+ * @param skillName - The skill name to search for
2102
+ * @returns PromptSkillMatch if found, undefined otherwise
2103
+ */
2104
+ findPromptSkill(skillName) {
2105
+ if (!skillName) return void 0;
2106
+ const clients = this.clientManager.getAllClients();
2107
+ for (const client of clients) {
2108
+ if (!client.prompts) continue;
2109
+ for (const [promptName, promptConfig] of Object.entries(client.prompts)) if (promptConfig.skill && promptConfig.skill.name === skillName) return {
2110
+ serverName: client.serverName,
2111
+ promptName,
2112
+ skill: promptConfig.skill
2113
+ };
2114
+ }
2115
+ }
2116
+ /**
2117
+ * Returns guidance message for prompt-based skill invocation.
2118
+ *
2119
+ * @param promptSkill - The prompt skill match that was found
2120
+ * @returns CallToolResult with guidance message
2121
+ */
2122
+ executePromptSkill(promptSkill) {
2123
+ const location = promptSkill.skill.folder || `prompt:${promptSkill.serverName}/${promptSkill.promptName}`;
2124
+ return { content: [{
2125
+ type: "text",
2126
+ 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.`
2127
+ }] };
2128
+ }
2129
+ /**
1723
2130
  * Executes a tool or skill based on the provided input.
1724
2131
  *
1725
2132
  * Handles three invocation patterns:
@@ -1740,22 +2147,19 @@ var UseToolTool = class UseToolTool {
1740
2147
  const { toolName: inputToolName, toolArgs = {} } = input;
1741
2148
  if (inputToolName.startsWith(SKILL_PREFIX)) {
1742
2149
  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 {
2150
+ if (this.skillService) {
2151
+ const skill = await this.skillService.getSkill(skillName);
2152
+ if (skill) return this.executeSkill(skill);
2153
+ }
2154
+ const promptSkill = this.findPromptSkill(skillName);
2155
+ if (promptSkill) return this.executePromptSkill(promptSkill);
2156
+ return {
1752
2157
  content: [{
1753
2158
  type: "text",
1754
2159
  text: `Skill "${skillName}" not found. Use describe_tools to see available skills.`
1755
2160
  }],
1756
2161
  isError: true
1757
2162
  };
1758
- return this.executeSkill(skill);
1759
2163
  }
1760
2164
  const clients = this.clientManager.getAllClients();
1761
2165
  const { serverName, actualToolName } = parseToolName(inputToolName);
@@ -1803,6 +2207,8 @@ var UseToolTool = class UseToolTool {
1803
2207
  const skill = await this.skillService.getSkill(actualToolName);
1804
2208
  if (skill) return this.executeSkill(skill);
1805
2209
  }
2210
+ const promptSkill = this.findPromptSkill(actualToolName);
2211
+ if (promptSkill) return this.executePromptSkill(promptSkill);
1806
2212
  return {
1807
2213
  content: [{
1808
2214
  type: "text",
@@ -1869,7 +2275,10 @@ async function createServer(options) {
1869
2275
  const server = new __modelcontextprotocol_sdk_server_index_js.Server({
1870
2276
  name: "@agiflowai/one-mcp",
1871
2277
  version: "0.1.0"
1872
- }, { capabilities: { tools: {} } });
2278
+ }, { capabilities: {
2279
+ tools: {},
2280
+ prompts: {}
2281
+ } });
1873
2282
  const clientManager = new McpClientManagerService();
1874
2283
  let configSkills;
1875
2284
  if (options?.configFilePath) {
@@ -1902,9 +2311,16 @@ async function createServer(options) {
1902
2311
  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(", ")}`);
1903
2312
  }
1904
2313
  const skillsConfig = options?.skills || configSkills;
1905
- const skillService = skillsConfig && skillsConfig.paths.length > 0 ? new SkillService(process.cwd(), skillsConfig.paths) : void 0;
2314
+ const toolsRef = { describeTools: null };
2315
+ const skillService = skillsConfig && skillsConfig.paths.length > 0 ? new SkillService(process.cwd(), skillsConfig.paths, { onCacheInvalidated: () => {
2316
+ toolsRef.describeTools?.clearAutoDetectedSkillsCache();
2317
+ } }) : void 0;
1906
2318
  const describeTools = new DescribeToolsTool(clientManager, skillService);
1907
2319
  const useTool = new UseToolTool(clientManager, skillService);
2320
+ toolsRef.describeTools = describeTools;
2321
+ if (skillService) skillService.startWatching().catch((error) => {
2322
+ console.error(`[skill-watcher] File watcher failed (non-critical): ${error instanceof Error ? error.message : "Unknown error"}`);
2323
+ });
1908
2324
  server.setRequestHandler(__modelcontextprotocol_sdk_types_js.ListToolsRequestSchema, async () => ({ tools: [await describeTools.getDefinition(), useTool.getDefinition()] }));
1909
2325
  server.setRequestHandler(__modelcontextprotocol_sdk_types_js.CallToolRequestSchema, async (request) => {
1910
2326
  const { name, arguments: args } = request.params;
@@ -1920,6 +2336,60 @@ async function createServer(options) {
1920
2336
  }
1921
2337
  throw new Error(`Unknown tool: ${name}`);
1922
2338
  });
2339
+ server.setRequestHandler(__modelcontextprotocol_sdk_types_js.ListPromptsRequestSchema, async () => {
2340
+ const clients = clientManager.getAllClients();
2341
+ const promptToServers = /* @__PURE__ */ new Map();
2342
+ const serverPromptsMap = /* @__PURE__ */ new Map();
2343
+ await Promise.all(clients.map(async (client) => {
2344
+ try {
2345
+ const prompts = await client.listPrompts();
2346
+ serverPromptsMap.set(client.serverName, prompts);
2347
+ for (const prompt of prompts) {
2348
+ if (!promptToServers.has(prompt.name)) promptToServers.set(prompt.name, []);
2349
+ promptToServers.get(prompt.name).push(client.serverName);
2350
+ }
2351
+ } catch (error) {
2352
+ console.error(`Failed to list prompts from ${client.serverName}:`, error);
2353
+ serverPromptsMap.set(client.serverName, []);
2354
+ }
2355
+ }));
2356
+ const aggregatedPrompts = [];
2357
+ for (const client of clients) {
2358
+ const prompts = serverPromptsMap.get(client.serverName) || [];
2359
+ for (const prompt of prompts) {
2360
+ const hasClash = (promptToServers.get(prompt.name) || []).length > 1;
2361
+ aggregatedPrompts.push({
2362
+ name: hasClash ? `${client.serverName}__${prompt.name}` : prompt.name,
2363
+ description: prompt.description,
2364
+ arguments: prompt.arguments
2365
+ });
2366
+ }
2367
+ }
2368
+ return { prompts: aggregatedPrompts };
2369
+ });
2370
+ server.setRequestHandler(__modelcontextprotocol_sdk_types_js.GetPromptRequestSchema, async (request) => {
2371
+ const { name, arguments: args } = request.params;
2372
+ const clients = clientManager.getAllClients();
2373
+ const { serverName, actualToolName: actualPromptName } = parseToolName(name);
2374
+ if (serverName) {
2375
+ const client$1 = clientManager.getClient(serverName);
2376
+ if (!client$1) throw new Error(`Server not found: ${serverName}`);
2377
+ return await client$1.getPrompt(actualPromptName, args);
2378
+ }
2379
+ const serversWithPrompt = [];
2380
+ await Promise.all(clients.map(async (client$1) => {
2381
+ try {
2382
+ if ((await client$1.listPrompts()).some((p) => p.name === name)) serversWithPrompt.push(client$1.serverName);
2383
+ } catch (error) {
2384
+ console.error(`Failed to list prompts from ${client$1.serverName}:`, error);
2385
+ }
2386
+ }));
2387
+ if (serversWithPrompt.length === 0) throw new Error(`Prompt not found: ${name}`);
2388
+ 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.`);
2389
+ const client = clientManager.getClient(serversWithPrompt[0]);
2390
+ if (!client) throw new Error(`Server not found: ${serversWithPrompt[0]}`);
2391
+ return await client.getPrompt(name, args);
2392
+ });
1923
2393
  return server;
1924
2394
  }
1925
2395