@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.
@@ -1,6 +1,6 @@
1
1
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
2
- import { CallToolRequestSchema, ListToolsRequestSchema, isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
3
- import { access, mkdir, readFile, readdir, stat, 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, watch, 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";
@@ -10,6 +10,7 @@ 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 { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
13
14
  import { Liquid } from "liquidjs";
14
15
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
15
16
  import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
@@ -175,10 +176,25 @@ function validateRemoteConfigSource(source) {
175
176
  * Claude Code / Claude Desktop standard MCP config format
176
177
  * This is the format users write in their config files
177
178
  */
179
+ /**
180
+ * Prompt skill configuration schema
181
+ * Converts a prompt to an executable skill
182
+ */
183
+ const PromptSkillConfigSchema = z.object({
184
+ name: z.string(),
185
+ description: z.string(),
186
+ folder: z.string().optional()
187
+ });
188
+ /**
189
+ * Prompt configuration schema
190
+ * Supports converting prompts to skills
191
+ */
192
+ const PromptConfigSchema = z.object({ skill: PromptSkillConfigSchema.optional() });
178
193
  const AdditionalConfigSchema = z.object({
179
194
  instruction: z.string().optional(),
180
195
  toolBlacklist: z.array(z.string()).optional(),
181
- omitToolDescription: z.boolean().optional()
196
+ omitToolDescription: z.boolean().optional(),
197
+ prompts: z.record(z.string(), PromptConfigSchema).optional()
182
198
  }).optional();
183
199
  const ClaudeCodeStdioServerSchema = z.object({
184
200
  command: z.string(),
@@ -245,12 +261,25 @@ const McpSseConfigSchema = z.object({
245
261
  url: z.string().url(),
246
262
  headers: z.record(z.string(), z.string()).optional()
247
263
  });
264
+ /**
265
+ * Internal prompt skill configuration schema
266
+ */
267
+ const InternalPromptSkillConfigSchema = z.object({
268
+ name: z.string(),
269
+ description: z.string(),
270
+ folder: z.string().optional()
271
+ });
272
+ /**
273
+ * Internal prompt configuration schema
274
+ */
275
+ const InternalPromptConfigSchema = z.object({ skill: InternalPromptSkillConfigSchema.optional() });
248
276
  const McpServerConfigSchema = z.discriminatedUnion("transport", [
249
277
  z.object({
250
278
  name: z.string(),
251
279
  instruction: z.string().optional(),
252
280
  toolBlacklist: z.array(z.string()).optional(),
253
281
  omitToolDescription: z.boolean().optional(),
282
+ prompts: z.record(z.string(), InternalPromptConfigSchema).optional(),
254
283
  transport: z.literal("stdio"),
255
284
  config: McpStdioConfigSchema
256
285
  }),
@@ -259,6 +288,7 @@ const McpServerConfigSchema = z.discriminatedUnion("transport", [
259
288
  instruction: z.string().optional(),
260
289
  toolBlacklist: z.array(z.string()).optional(),
261
290
  omitToolDescription: z.boolean().optional(),
291
+ prompts: z.record(z.string(), InternalPromptConfigSchema).optional(),
262
292
  transport: z.literal("http"),
263
293
  config: McpHttpConfigSchema
264
294
  }),
@@ -267,6 +297,7 @@ const McpServerConfigSchema = z.discriminatedUnion("transport", [
267
297
  instruction: z.string().optional(),
268
298
  toolBlacklist: z.array(z.string()).optional(),
269
299
  omitToolDescription: z.boolean().optional(),
300
+ prompts: z.record(z.string(), InternalPromptConfigSchema).optional(),
270
301
  transport: z.literal("sse"),
271
302
  config: McpSseConfigSchema
272
303
  })
@@ -299,6 +330,7 @@ function transformClaudeCodeConfig(claudeConfig) {
299
330
  instruction: stdioConfig.instruction || stdioConfig.config?.instruction,
300
331
  toolBlacklist: stdioConfig.config?.toolBlacklist,
301
332
  omitToolDescription: stdioConfig.config?.omitToolDescription,
333
+ prompts: stdioConfig.config?.prompts,
302
334
  transport: "stdio",
303
335
  config: {
304
336
  command: interpolatedCommand,
@@ -316,6 +348,7 @@ function transformClaudeCodeConfig(claudeConfig) {
316
348
  instruction: httpConfig.instruction || httpConfig.config?.instruction,
317
349
  toolBlacklist: httpConfig.config?.toolBlacklist,
318
350
  omitToolDescription: httpConfig.config?.omitToolDescription,
351
+ prompts: httpConfig.config?.prompts,
319
352
  transport,
320
353
  config: {
321
354
  url: interpolatedUrl,
@@ -776,12 +809,14 @@ var ConfigFetcherService = class {
776
809
  //#region src/services/McpClientManagerService.ts
777
810
  /**
778
811
  * MCP Client wrapper for managing individual server connections
812
+ * This is an internal class used by McpClientManagerService
779
813
  */
780
814
  var McpClient = class {
781
815
  serverName;
782
816
  serverInstruction;
783
817
  toolBlacklist;
784
818
  omitToolDescription;
819
+ prompts;
785
820
  transport;
786
821
  client;
787
822
  childProcess;
@@ -791,6 +826,7 @@ var McpClient = class {
791
826
  this.serverInstruction = config.instruction;
792
827
  this.toolBlacklist = config.toolBlacklist;
793
828
  this.omitToolDescription = config.omitToolDescription;
829
+ this.prompts = config.prompts;
794
830
  this.transport = transport;
795
831
  this.client = client;
796
832
  }
@@ -886,7 +922,8 @@ var McpClientManagerService = class {
886
922
  const mcpClient = new McpClient(serverName, config.transport, client, {
887
923
  instruction: config.instruction,
888
924
  toolBlacklist: config.toolBlacklist,
889
- omitToolDescription: config.omitToolDescription
925
+ omitToolDescription: config.omitToolDescription,
926
+ prompts: config.prompts
890
927
  });
891
928
  try {
892
929
  await Promise.race([this.performConnection(mcpClient, config), new Promise((_, reject) => setTimeout(() => reject(/* @__PURE__ */ new Error(`Connection timeout after ${timeoutMs}ms`)), timeoutMs))]);
@@ -908,6 +945,7 @@ var McpClientManagerService = class {
908
945
  */
909
946
  async performConnection(mcpClient, config) {
910
947
  if (config.transport === "stdio") await this.connectStdioClient(mcpClient, config.config);
948
+ else if (config.transport === "http") await this.connectHttpClient(mcpClient, config.config);
911
949
  else if (config.transport === "sse") await this.connectSseClient(mcpClient, config.config);
912
950
  else throw new Error(`Unsupported transport type: ${config.transport}`);
913
951
  }
@@ -921,6 +959,10 @@ var McpClientManagerService = class {
921
959
  const childProcess = transport["_process"];
922
960
  if (childProcess) mcpClient.setChildProcess(childProcess);
923
961
  }
962
+ async connectHttpClient(mcpClient, config) {
963
+ const transport = new StreamableHTTPClientTransport(new URL(config.url), { requestInit: config.headers ? { headers: config.headers } : void 0 });
964
+ await mcpClient["client"].connect(transport);
965
+ }
924
966
  async connectSseClient(mcpClient, config) {
925
967
  const transport = new SSEClientTransport(new URL(config.url));
926
968
  await mcpClient["client"].connect(transport);
@@ -963,6 +1005,209 @@ var McpClientManagerService = class {
963
1005
  }
964
1006
  };
965
1007
 
1008
+ //#endregion
1009
+ //#region src/utils/findConfigFile.ts
1010
+ /**
1011
+ * Config File Finder Utility
1012
+ *
1013
+ * DESIGN PATTERNS:
1014
+ * - Utility function pattern for reusable logic
1015
+ * - Fail-fast pattern with early returns
1016
+ * - Environment variable configuration pattern
1017
+ *
1018
+ * CODING STANDARDS:
1019
+ * - Use sync filesystem operations for config discovery (performance)
1020
+ * - Check PROJECT_PATH environment variable first
1021
+ * - Fall back to current working directory
1022
+ * - Support both .yaml and .json extensions
1023
+ * - Return null if no config file is found
1024
+ *
1025
+ * AVOID:
1026
+ * - Throwing errors (return null instead for optional config)
1027
+ * - Hardcoded file names without extension variants
1028
+ * - Ignoring environment variables
1029
+ */
1030
+ /**
1031
+ * Find MCP configuration file by checking PROJECT_PATH first, then cwd
1032
+ * Looks for both mcp-config.yaml and mcp-config.json
1033
+ *
1034
+ * @returns Absolute path to config file, or null if not found
1035
+ */
1036
+ function findConfigFile() {
1037
+ const configFileNames = [
1038
+ "mcp-config.yaml",
1039
+ "mcp-config.yml",
1040
+ "mcp-config.json"
1041
+ ];
1042
+ const projectPath = process.env.PROJECT_PATH;
1043
+ if (projectPath) for (const fileName of configFileNames) {
1044
+ const configPath = resolve(projectPath, fileName);
1045
+ if (existsSync(configPath)) return configPath;
1046
+ }
1047
+ const cwd = process.cwd();
1048
+ for (const fileName of configFileNames) {
1049
+ const configPath = join(cwd, fileName);
1050
+ if (existsSync(configPath)) return configPath;
1051
+ }
1052
+ return null;
1053
+ }
1054
+
1055
+ //#endregion
1056
+ //#region src/utils/parseToolName.ts
1057
+ /**
1058
+ * Parse tool name to extract server and actual tool name
1059
+ * Supports both plain tool names and prefixed format: {serverName}__{toolName}
1060
+ *
1061
+ * @param toolName - The tool name to parse (e.g., "my_tool" or "server__my_tool")
1062
+ * @returns Parsed result with optional serverName and actualToolName
1063
+ *
1064
+ * @example
1065
+ * parseToolName("my_tool") // { actualToolName: "my_tool" }
1066
+ * parseToolName("server__my_tool") // { serverName: "server", actualToolName: "my_tool" }
1067
+ */
1068
+ function parseToolName(toolName) {
1069
+ const separatorIndex = toolName.indexOf("__");
1070
+ if (separatorIndex > 0) return {
1071
+ serverName: toolName.substring(0, separatorIndex),
1072
+ actualToolName: toolName.substring(separatorIndex + 2)
1073
+ };
1074
+ return { actualToolName: toolName };
1075
+ }
1076
+
1077
+ //#endregion
1078
+ //#region src/utils/parseFrontMatter.ts
1079
+ /**
1080
+ * Parses YAML front matter from a string content.
1081
+ * Front matter must be at the start of the content, delimited by `---`.
1082
+ *
1083
+ * Supports:
1084
+ * - Simple key: value pairs
1085
+ * - Literal block scalar (|) for multi-line preserving newlines
1086
+ * - Folded block scalar (>) for multi-line folding to single line
1087
+ *
1088
+ * @param content - The content string that may contain front matter
1089
+ * @returns Object with parsed front matter (or null) and remaining content
1090
+ *
1091
+ * @example
1092
+ * const result = parseFrontMatter(`---
1093
+ * name: my-skill
1094
+ * description: A skill description
1095
+ * ---
1096
+ * The actual content here`);
1097
+ * // result.frontMatter = { name: 'my-skill', description: 'A skill description' }
1098
+ * // result.content = 'The actual content here'
1099
+ *
1100
+ * @example
1101
+ * // Multi-line with literal block scalar
1102
+ * const result = parseFrontMatter(`---
1103
+ * name: my-skill
1104
+ * description: |
1105
+ * Line 1
1106
+ * Line 2
1107
+ * ---
1108
+ * Content`);
1109
+ * // result.frontMatter.description = 'Line 1\nLine 2'
1110
+ */
1111
+ function parseFrontMatter(content) {
1112
+ const trimmedContent = content.trimStart();
1113
+ if (!trimmedContent.startsWith("---")) return {
1114
+ frontMatter: null,
1115
+ content
1116
+ };
1117
+ const endDelimiterIndex = trimmedContent.indexOf("\n---", 3);
1118
+ if (endDelimiterIndex === -1) return {
1119
+ frontMatter: null,
1120
+ content
1121
+ };
1122
+ const yamlContent = trimmedContent.slice(4, endDelimiterIndex).trim();
1123
+ if (!yamlContent) return {
1124
+ frontMatter: null,
1125
+ content
1126
+ };
1127
+ const frontMatter = {};
1128
+ const lines = yamlContent.split("\n");
1129
+ let currentKey = null;
1130
+ let currentValue = [];
1131
+ let multiLineMode = null;
1132
+ let baseIndent = 0;
1133
+ const saveCurrentKey = () => {
1134
+ if (currentKey && currentValue.length > 0) if (multiLineMode === "literal") frontMatter[currentKey] = currentValue.join("\n").trimEnd();
1135
+ else if (multiLineMode === "folded") frontMatter[currentKey] = currentValue.join(" ").trim();
1136
+ else frontMatter[currentKey] = currentValue.join("").trim();
1137
+ currentKey = null;
1138
+ currentValue = [];
1139
+ multiLineMode = null;
1140
+ baseIndent = 0;
1141
+ };
1142
+ for (let i = 0; i < lines.length; i++) {
1143
+ const line = lines[i];
1144
+ const trimmedLine = line.trim();
1145
+ const colonIndex = line.indexOf(":");
1146
+ if (colonIndex !== -1 && !line.startsWith(" ") && !line.startsWith(" ")) {
1147
+ saveCurrentKey();
1148
+ const key = line.slice(0, colonIndex).trim();
1149
+ let value = line.slice(colonIndex + 1).trim();
1150
+ if (value === "|" || value === "|-") {
1151
+ currentKey = key;
1152
+ multiLineMode = "literal";
1153
+ if (i + 1 < lines.length) {
1154
+ const match = lines[i + 1].match(/^(\s+)/);
1155
+ baseIndent = match ? match[1].length : 2;
1156
+ }
1157
+ } else if (value === ">" || value === ">-") {
1158
+ currentKey = key;
1159
+ multiLineMode = "folded";
1160
+ if (i + 1 < lines.length) {
1161
+ const match = lines[i + 1].match(/^(\s+)/);
1162
+ baseIndent = match ? match[1].length : 2;
1163
+ }
1164
+ } else {
1165
+ if (value.startsWith("\"") && value.endsWith("\"") || value.startsWith("'") && value.endsWith("'")) value = value.slice(1, -1);
1166
+ if (key && value) frontMatter[key] = value;
1167
+ }
1168
+ } else if (multiLineMode && currentKey) {
1169
+ const lineIndent = line.match(/^(\s*)/)?.[1].length || 0;
1170
+ if (trimmedLine === "") currentValue.push("");
1171
+ else if (lineIndent >= baseIndent) {
1172
+ const unindentedLine = line.slice(baseIndent);
1173
+ currentValue.push(unindentedLine);
1174
+ } else saveCurrentKey();
1175
+ }
1176
+ }
1177
+ saveCurrentKey();
1178
+ return {
1179
+ frontMatter,
1180
+ content: trimmedContent.slice(endDelimiterIndex + 4).trimStart()
1181
+ };
1182
+ }
1183
+ /**
1184
+ * Checks if parsed front matter contains valid skill metadata.
1185
+ * A valid skill front matter must have both `name` and `description` fields.
1186
+ *
1187
+ * @param frontMatter - The parsed front matter object
1188
+ * @returns True if front matter contains valid skill metadata
1189
+ */
1190
+ function isValidSkillFrontMatter(frontMatter) {
1191
+ return frontMatter !== null && typeof frontMatter.name === "string" && frontMatter.name.length > 0 && typeof frontMatter.description === "string" && frontMatter.description.length > 0;
1192
+ }
1193
+ /**
1194
+ * Extracts skill front matter from content if present and valid.
1195
+ *
1196
+ * @param content - The content string that may contain skill front matter
1197
+ * @returns Object with skill metadata and content, or null if no valid skill front matter
1198
+ */
1199
+ function extractSkillFrontMatter(content) {
1200
+ const { frontMatter, content: remainingContent } = parseFrontMatter(content);
1201
+ if (frontMatter && isValidSkillFrontMatter(frontMatter)) return {
1202
+ skill: {
1203
+ name: frontMatter.name,
1204
+ description: frontMatter.description
1205
+ },
1206
+ content: remainingContent
1207
+ };
1208
+ return null;
1209
+ }
1210
+
966
1211
  //#endregion
967
1212
  //#region src/services/SkillService.ts
968
1213
  /**
@@ -1033,14 +1278,21 @@ var SkillService = class {
1033
1278
  skillPaths;
1034
1279
  cachedSkills = null;
1035
1280
  skillsByName = null;
1281
+ /** Active file watchers for skill directories */
1282
+ watchers = [];
1283
+ /** Callback invoked when cache is invalidated due to file changes */
1284
+ onCacheInvalidated;
1036
1285
  /**
1037
1286
  * Creates a new SkillService instance
1038
1287
  * @param cwd - Current working directory for resolving relative paths
1039
1288
  * @param skillPaths - Array of paths to skills directories
1289
+ * @param options - Optional configuration
1290
+ * @param options.onCacheInvalidated - Callback invoked when cache is invalidated due to file changes
1040
1291
  */
1041
- constructor(cwd, skillPaths) {
1292
+ constructor(cwd, skillPaths, options) {
1042
1293
  this.cwd = cwd;
1043
1294
  this.skillPaths = skillPaths;
1295
+ this.onCacheInvalidated = options?.onCacheInvalidated;
1044
1296
  }
1045
1297
  /**
1046
1298
  * Get all available skills from configured directories.
@@ -1086,6 +1338,54 @@ var SkillService = class {
1086
1338
  this.skillsByName = null;
1087
1339
  }
1088
1340
  /**
1341
+ * Starts watching skill directories for changes to SKILL.md files.
1342
+ * When changes are detected, the cache is automatically invalidated.
1343
+ *
1344
+ * Uses Node.js fs.watch with recursive option for efficient directory monitoring.
1345
+ * Only invalidates cache when SKILL.md files are modified.
1346
+ *
1347
+ * @example
1348
+ * const skillService = new SkillService(cwd, skillPaths, {
1349
+ * onCacheInvalidated: () => console.log('Skills cache invalidated')
1350
+ * });
1351
+ * await skillService.startWatching();
1352
+ */
1353
+ async startWatching() {
1354
+ this.stopWatching();
1355
+ for (const skillPath of this.skillPaths) {
1356
+ const skillsDir = isAbsolute(skillPath) ? skillPath : join(this.cwd, skillPath);
1357
+ if (!await pathExists(skillsDir)) continue;
1358
+ const abortController = new AbortController();
1359
+ this.watchers.push(abortController);
1360
+ this.watchDirectory(skillsDir, abortController.signal).catch((error) => {
1361
+ if (error?.name !== "AbortError") console.error(`[skill-watcher] Error watching ${skillsDir}: ${error instanceof Error ? error.message : "Unknown error"}`);
1362
+ });
1363
+ }
1364
+ }
1365
+ /**
1366
+ * Stops all active file watchers.
1367
+ * Should be called when the service is being disposed.
1368
+ */
1369
+ stopWatching() {
1370
+ for (const controller of this.watchers) controller.abort();
1371
+ this.watchers = [];
1372
+ }
1373
+ /**
1374
+ * Watches a directory for changes to SKILL.md files.
1375
+ * @param dirPath - Directory path to watch
1376
+ * @param signal - AbortSignal to stop watching
1377
+ */
1378
+ async watchDirectory(dirPath, signal) {
1379
+ const watcher = watch(dirPath, {
1380
+ recursive: true,
1381
+ signal
1382
+ });
1383
+ for await (const event of watcher) if (event.filename && event.filename.endsWith("SKILL.md")) {
1384
+ this.clearCache();
1385
+ this.onCacheInvalidated?.();
1386
+ }
1387
+ }
1388
+ /**
1089
1389
  * Load skills from a directory.
1090
1390
  * Supports both flat structure (SKILL.md) and nested structure (name/SKILL.md).
1091
1391
  *
@@ -1144,6 +1444,7 @@ var SkillService = class {
1144
1444
  }
1145
1445
  /**
1146
1446
  * Load a single skill file and parse its frontmatter.
1447
+ * Supports multi-line YAML values using literal (|) and folded (>) block scalars.
1147
1448
  *
1148
1449
  * @param filePath - Path to the SKILL.md file
1149
1450
  * @param location - Whether this is a 'project' or 'user' skill
@@ -1157,150 +1458,62 @@ var SkillService = class {
1157
1458
  * // Returns null if frontmatter is missing name or description
1158
1459
  */
1159
1460
  async loadSkillFile(filePath, location) {
1160
- let content;
1461
+ let fileContent;
1161
1462
  try {
1162
- content = await readFile(filePath, "utf-8");
1463
+ fileContent = await readFile(filePath, "utf-8");
1163
1464
  } catch (error) {
1164
1465
  throw new SkillLoadError(`Failed to read skill file: ${error instanceof Error ? error.message : "Unknown error"}`, filePath, error instanceof Error ? error : void 0);
1165
1466
  }
1166
- const { metadata, body } = this.parseFrontmatter(content);
1167
- if (!metadata.name || !metadata.description) return null;
1467
+ const { frontMatter, content } = parseFrontMatter(fileContent);
1468
+ if (!frontMatter || !frontMatter.name || !frontMatter.description) return null;
1168
1469
  return {
1169
- name: metadata.name,
1170
- description: metadata.description,
1470
+ name: frontMatter.name,
1471
+ description: frontMatter.description,
1171
1472
  location,
1172
- content: body,
1473
+ content,
1173
1474
  basePath: dirname(filePath)
1174
1475
  };
1175
1476
  }
1176
- /**
1177
- * Parse YAML frontmatter from markdown content.
1178
- * Frontmatter is delimited by --- at start and end.
1179
- *
1180
- * @param content - Full markdown content with frontmatter
1181
- * @returns Parsed metadata and body content
1182
- *
1183
- * @example
1184
- * // Input content:
1185
- * // ---
1186
- * // name: my-skill
1187
- * // description: A sample skill
1188
- * // ---
1189
- * // # Skill Content
1190
- * // This is the skill body.
1191
- *
1192
- * const result = parseFrontmatter(content);
1193
- * // result.metadata = { name: 'my-skill', description: 'A sample skill' }
1194
- * // result.body = '# Skill Content\nThis is the skill body.'
1195
- */
1196
- parseFrontmatter(content) {
1197
- const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/);
1198
- if (!match) return {
1199
- metadata: {},
1200
- body: content
1201
- };
1202
- const [, frontmatter, body] = match;
1203
- const metadata = {};
1204
- const lines = frontmatter.split("\n");
1205
- for (const line of lines) {
1206
- const colonIndex = line.indexOf(":");
1207
- if (colonIndex > 0) {
1208
- const key = line.slice(0, colonIndex).trim();
1209
- let value = line.slice(colonIndex + 1).trim();
1210
- if (value.startsWith("\"") && value.endsWith("\"") || value.startsWith("'") && value.endsWith("'")) value = value.slice(1, -1);
1211
- if (key === "name" || key === "description" || key === "license") metadata[key] = value;
1212
- }
1213
- }
1214
- return {
1215
- metadata,
1216
- body: body.trim()
1217
- };
1218
- }
1219
1477
  };
1220
1478
 
1221
1479
  //#endregion
1222
- //#region src/utils/findConfigFile.ts
1480
+ //#region src/constants/index.ts
1223
1481
  /**
1224
- * Config File Finder Utility
1225
- *
1226
- * DESIGN PATTERNS:
1227
- * - Utility function pattern for reusable logic
1228
- * - Fail-fast pattern with early returns
1229
- * - Environment variable configuration pattern
1230
- *
1231
- * CODING STANDARDS:
1232
- * - Use sync filesystem operations for config discovery (performance)
1233
- * - Check PROJECT_PATH environment variable first
1234
- * - Fall back to current working directory
1235
- * - Support both .yaml and .json extensions
1236
- * - Return null if no config file is found
1237
- *
1238
- * AVOID:
1239
- * - Throwing errors (return null instead for optional config)
1240
- * - Hardcoded file names without extension variants
1241
- * - Ignoring environment variables
1482
+ * Shared constants for one-mcp package
1242
1483
  */
1243
1484
  /**
1244
- * Find MCP configuration file by checking PROJECT_PATH first, then cwd
1245
- * Looks for both mcp-config.yaml and mcp-config.json
1246
- *
1247
- * @returns Absolute path to config file, or null if not found
1485
+ * Prefix added to skill names when they clash with MCP tool names.
1486
+ * This ensures skills can be uniquely identified even when a tool has the same name.
1248
1487
  */
1249
- function findConfigFile() {
1250
- const configFileNames = [
1251
- "mcp-config.yaml",
1252
- "mcp-config.yml",
1253
- "mcp-config.json"
1254
- ];
1255
- const projectPath = process.env.PROJECT_PATH;
1256
- if (projectPath) for (const fileName of configFileNames) {
1257
- const configPath = resolve(projectPath, fileName);
1258
- if (existsSync(configPath)) return configPath;
1259
- }
1260
- const cwd = process.cwd();
1261
- for (const fileName of configFileNames) {
1262
- const configPath = join(cwd, fileName);
1263
- if (existsSync(configPath)) return configPath;
1264
- }
1265
- return null;
1266
- }
1267
-
1268
- //#endregion
1269
- //#region src/utils/parseToolName.ts
1488
+ const SKILL_PREFIX$1 = "skill__";
1270
1489
  /**
1271
- * Parse tool name to extract server and actual tool name
1272
- * Supports both plain tool names and prefixed format: {serverName}__{toolName}
1273
- *
1274
- * @param toolName - The tool name to parse (e.g., "my_tool" or "server__my_tool")
1275
- * @returns Parsed result with optional serverName and actualToolName
1276
- *
1277
- * @example
1278
- * parseToolName("my_tool") // { actualToolName: "my_tool" }
1279
- * parseToolName("server__my_tool") // { serverName: "server", actualToolName: "my_tool" }
1490
+ * Log prefix for skill detection messages.
1491
+ * Used to easily filter skill detection logs in stderr output.
1280
1492
  */
1281
- function parseToolName(toolName) {
1282
- const separatorIndex = toolName.indexOf("__");
1283
- if (separatorIndex > 0) return {
1284
- serverName: toolName.substring(0, separatorIndex),
1285
- actualToolName: toolName.substring(separatorIndex + 2)
1286
- };
1287
- return { actualToolName: toolName };
1288
- }
1289
-
1290
- //#endregion
1291
- //#region src/templates/skills-description.liquid?raw
1292
- 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";
1493
+ const LOG_PREFIX_SKILL_DETECTION = "[skill-detection]";
1494
+ /**
1495
+ * Prefix for prompt-based skill locations.
1496
+ * Format: "prompt:{serverName}:{promptName}"
1497
+ */
1498
+ const PROMPT_LOCATION_PREFIX = "prompt:";
1293
1499
 
1294
1500
  //#endregion
1295
- //#region src/templates/mcp-servers-description.liquid?raw
1296
- 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";
1501
+ //#region src/templates/toolkit-description.liquid?raw
1502
+ var toolkit_description_default = "<toolkit>\n<instruction>\nBefore you use any capabilities below, you MUST call this tool with a list of names to learn how to use them properly; this includes:\n- For tools: Arguments schema needed to pass to use_tool\n- For skills: Detailed instructions that will expand when invoked (Prefer to be explored first when relevant)\n\nThis tool is optimized for batch queries - you can request multiple capabilities at once for better performance.\n\nHow to invoke:\n- For MCP tools: Use use_tool with toolName and toolArgs based on the schema\n- For skills: Use this tool with the skill name to get expanded instructions\n</instruction>\n\n<available_capabilities>\n{% for server in servers -%}\n<group name=\"{{ server.name }}\">\n{% if server.instruction -%}\n<group_instruction>{{ server.instruction }}</group_instruction>\n{% endif -%}\n{% if server.omitToolDescription -%}\n{% for toolName in server.toolNames -%}\n<item name=\"{{ toolName }}\"></item>\n{% endfor -%}\n{% else -%}\n{% for tool in server.tools -%}\n<item name=\"{{ tool.displayName }}\"><description>{{ tool.description | default: \"No description\" }}</description></item>\n{% endfor -%}\n{% endif -%}\n</group>\n{% endfor -%}\n{% if skills.size > 0 -%}\n<group name=\"skills\">\n{% for skill in skills -%}\n<item name=\"{{ skill.displayName }}\"><description>{{ skill.description }}</description></item>\n{% endfor -%}\n</group>\n{% endif -%}\n</available_capabilities>\n</toolkit>\n";
1297
1503
 
1298
1504
  //#endregion
1299
1505
  //#region src/tools/DescribeToolsTool.ts
1300
1506
  /**
1301
- * Prefix used to identify skill invocations
1507
+ * Formats skill instructions with the loading command message prefix.
1508
+ * This message is used by Claude Code to indicate that a skill is being loaded.
1509
+ *
1510
+ * @param name - The skill name
1511
+ * @param instructions - The raw skill instructions/content
1512
+ * @returns Formatted instructions with command message prefix
1302
1513
  */
1303
- const SKILL_PREFIX$1 = "skill__";
1514
+ function formatSkillInstructions(name, instructions) {
1515
+ return `<command-message>The "${name}" skill is loading</command-message>\n${instructions}`;
1516
+ }
1304
1517
  /**
1305
1518
  * DescribeToolsTool provides progressive disclosure of MCP tools and skills.
1306
1519
  *
@@ -1323,6 +1536,8 @@ var DescribeToolsTool = class DescribeToolsTool {
1323
1536
  clientManager;
1324
1537
  skillService;
1325
1538
  liquid = new Liquid();
1539
+ /** Cache for auto-detected skills from prompt front-matter */
1540
+ autoDetectedSkillsCache = null;
1326
1541
  /**
1327
1542
  * Creates a new DescribeToolsTool instance
1328
1543
  * @param clientManager - The MCP client manager for accessing remote servers
@@ -1333,43 +1548,169 @@ var DescribeToolsTool = class DescribeToolsTool {
1333
1548
  this.skillService = skillService;
1334
1549
  }
1335
1550
  /**
1336
- * Builds the skills section of the tool description using a Liquid template.
1551
+ * Clears the cached auto-detected skills from prompt front-matter.
1552
+ * Use this when prompt configurations may have changed or when
1553
+ * the skill service cache is invalidated.
1554
+ */
1555
+ clearAutoDetectedSkillsCache() {
1556
+ this.autoDetectedSkillsCache = null;
1557
+ }
1558
+ /**
1559
+ * Detects and caches skills from prompt front-matter across all connected MCP servers.
1560
+ * Fetches all prompts and checks their content for YAML front-matter with name/description.
1561
+ * Results are cached to avoid repeated fetches.
1337
1562
  *
1338
- * Retrieves all available skills from the SkillService and renders them
1339
- * using the skills-description.liquid template. Skills are only prefixed
1340
- * with skill__ when their name clashes with an MCP tool or another skill.
1563
+ * Error Handling Strategy:
1564
+ * - Errors are logged to stderr but do not fail the overall detection process
1565
+ * - This ensures partial results are returned even if some servers/prompts fail
1566
+ * - Common failure reasons: server temporarily unavailable, prompt requires arguments,
1567
+ * network timeout, or server doesn't support listPrompts
1568
+ * - Errors are prefixed with [skill-detection] for easy filtering in logs
1341
1569
  *
1342
- * @param mcpToolNames - Set of MCP tool names to check for clashes
1343
- * @returns Rendered skills section string with available skills list
1570
+ * @returns Array of auto-detected skills from prompt front-matter
1344
1571
  */
1345
- async buildSkillsSection(mcpToolNames) {
1346
- const rawSkills = this.skillService ? await this.skillService.getSkills() : [];
1347
- const skillNameCounts = /* @__PURE__ */ new Map();
1348
- for (const skill of rawSkills) skillNameCounts.set(skill.name, (skillNameCounts.get(skill.name) || 0) + 1);
1349
- const skills = rawSkills.map((skill) => {
1350
- const clashesWithMcpTool = mcpToolNames.has(skill.name);
1351
- const clashesWithOtherSkill = (skillNameCounts.get(skill.name) || 0) > 1;
1352
- const needsPrefix = clashesWithMcpTool || clashesWithOtherSkill;
1572
+ async detectSkillsFromPromptFrontMatter() {
1573
+ if (this.autoDetectedSkillsCache !== null) return this.autoDetectedSkillsCache;
1574
+ const clients = this.clientManager.getAllClients();
1575
+ const autoDetectedSkills = [];
1576
+ let listPromptsFailures = 0;
1577
+ let fetchPromptFailures = 0;
1578
+ const fetchPromises = [];
1579
+ for (const client of clients) {
1580
+ const configuredPromptNames = new Set(client.prompts ? Object.keys(client.prompts) : []);
1581
+ const listPromptsPromise = (async () => {
1582
+ try {
1583
+ const prompts = await client.listPrompts();
1584
+ if (!prompts || prompts.length === 0) return;
1585
+ const promptFetchPromises = prompts.map(async (promptInfo) => {
1586
+ if (configuredPromptNames.has(promptInfo.name)) return;
1587
+ try {
1588
+ const skillExtraction = extractSkillFrontMatter(((await client.getPrompt(promptInfo.name)).messages || []).map((m) => {
1589
+ const content = m.content;
1590
+ if (typeof content === "string") return content;
1591
+ if (content && typeof content === "object" && "text" in content) return String(content.text);
1592
+ return "";
1593
+ }).join("\n"));
1594
+ if (skillExtraction) autoDetectedSkills.push({
1595
+ serverName: client.serverName,
1596
+ promptName: promptInfo.name,
1597
+ skill: skillExtraction.skill
1598
+ });
1599
+ } catch (error) {
1600
+ fetchPromptFailures++;
1601
+ console.error(`${LOG_PREFIX_SKILL_DETECTION} Failed to fetch prompt '${promptInfo.name}' from ${client.serverName}: ${error instanceof Error ? error.message : "Unknown error"}`);
1602
+ }
1603
+ });
1604
+ await Promise.all(promptFetchPromises);
1605
+ } catch (error) {
1606
+ listPromptsFailures++;
1607
+ console.error(`${LOG_PREFIX_SKILL_DETECTION} Failed to list prompts from ${client.serverName}: ${error instanceof Error ? error.message : "Unknown error"}`);
1608
+ }
1609
+ })();
1610
+ fetchPromises.push(listPromptsPromise);
1611
+ }
1612
+ await Promise.all(fetchPromises);
1613
+ if (listPromptsFailures > 0 || fetchPromptFailures > 0) console.error(`${LOG_PREFIX_SKILL_DETECTION} Completed with ${listPromptsFailures} server failure(s) and ${fetchPromptFailures} prompt failure(s). Detected ${autoDetectedSkills.length} skill(s).`);
1614
+ this.autoDetectedSkillsCache = autoDetectedSkills;
1615
+ return autoDetectedSkills;
1616
+ }
1617
+ /**
1618
+ * Collects skills derived from prompt configurations across all connected MCP servers.
1619
+ * Includes both explicitly configured prompts and auto-detected skills from front-matter.
1620
+ *
1621
+ * @returns Array of skill template data derived from prompts
1622
+ */
1623
+ async collectPromptSkills() {
1624
+ const clients = this.clientManager.getAllClients();
1625
+ const promptSkills = [];
1626
+ for (const client of clients) {
1627
+ if (!client.prompts) continue;
1628
+ for (const promptConfig of Object.values(client.prompts)) if (promptConfig.skill) promptSkills.push({
1629
+ name: promptConfig.skill.name,
1630
+ displayName: promptConfig.skill.name,
1631
+ description: promptConfig.skill.description
1632
+ });
1633
+ }
1634
+ const autoDetectedSkills = await this.detectSkillsFromPromptFrontMatter();
1635
+ for (const autoSkill of autoDetectedSkills) promptSkills.push({
1636
+ name: autoSkill.skill.name,
1637
+ displayName: autoSkill.skill.name,
1638
+ description: autoSkill.skill.description
1639
+ });
1640
+ return promptSkills;
1641
+ }
1642
+ /**
1643
+ * Finds a prompt-based skill by name from all connected MCP servers.
1644
+ * Searches both explicitly configured prompts and auto-detected skills from front-matter.
1645
+ *
1646
+ * @param skillName - The skill name to search for
1647
+ * @returns Object with serverName, promptName, and skill config, or undefined if not found
1648
+ */
1649
+ async findPromptSkill(skillName) {
1650
+ if (!skillName) return void 0;
1651
+ const clients = this.clientManager.getAllClients();
1652
+ for (const client of clients) {
1653
+ if (!client.prompts) continue;
1654
+ for (const [promptName, promptConfig] of Object.entries(client.prompts)) if (promptConfig.skill && promptConfig.skill.name === skillName) return {
1655
+ serverName: client.serverName,
1656
+ promptName,
1657
+ skill: promptConfig.skill
1658
+ };
1659
+ }
1660
+ const autoDetectedSkills = await this.detectSkillsFromPromptFrontMatter();
1661
+ for (const autoSkill of autoDetectedSkills) if (autoSkill.skill.name === skillName) return {
1662
+ serverName: autoSkill.serverName,
1663
+ promptName: autoSkill.promptName,
1664
+ skill: autoSkill.skill,
1665
+ autoDetected: true
1666
+ };
1667
+ }
1668
+ /**
1669
+ * Retrieves skill content from a prompt-based skill configuration.
1670
+ * Fetches the prompt from the MCP server and extracts text content.
1671
+ * Handles both explicitly configured prompts and auto-detected skills from front-matter.
1672
+ *
1673
+ * @param skillName - The skill name being requested
1674
+ * @returns SkillDescription if found and successfully fetched, undefined otherwise
1675
+ */
1676
+ async getPromptSkillContent(skillName) {
1677
+ const promptSkill = await this.findPromptSkill(skillName);
1678
+ if (!promptSkill) return void 0;
1679
+ const client = this.clientManager.getClient(promptSkill.serverName);
1680
+ if (!client) {
1681
+ console.error(`Client not found for server '${promptSkill.serverName}' when fetching prompt skill '${skillName}'`);
1682
+ return;
1683
+ }
1684
+ try {
1685
+ const rawInstructions = (await client.getPrompt(promptSkill.promptName)).messages?.map((m) => {
1686
+ const content = m.content;
1687
+ if (typeof content === "string") return content;
1688
+ if (content && typeof content === "object" && "text" in content) return String(content.text);
1689
+ return "";
1690
+ }).join("\n") || "";
1353
1691
  return {
1354
- name: skill.name,
1355
- displayName: needsPrefix ? `${SKILL_PREFIX$1}${skill.name}` : skill.name,
1356
- description: skill.description
1692
+ name: promptSkill.skill.name,
1693
+ location: promptSkill.skill.folder || `${PROMPT_LOCATION_PREFIX}${promptSkill.serverName}/${promptSkill.promptName}`,
1694
+ instructions: formatSkillInstructions(promptSkill.skill.name, rawInstructions)
1357
1695
  };
1358
- });
1359
- return this.liquid.parseAndRender(skills_description_default, { skills });
1696
+ } catch (error) {
1697
+ console.error(`Failed to get prompt-based skill '${skillName}': ${error instanceof Error ? error.message : "Unknown error"}`);
1698
+ return;
1699
+ }
1360
1700
  }
1361
1701
  /**
1362
- * Builds the MCP servers section of the tool description using a Liquid template.
1702
+ * Builds the combined toolkit description using a single Liquid template.
1363
1703
  *
1364
- * Collects all tools from connected MCP servers, detects name clashes,
1365
- * and renders them using the mcp-servers-description.liquid template.
1704
+ * Collects all tools from connected MCP servers and all skills, then renders
1705
+ * them together using the toolkit-description.liquid template.
1366
1706
  *
1367
1707
  * Tool names are prefixed with serverName__ when the same tool exists
1368
- * on multiple servers to avoid ambiguity.
1708
+ * on multiple servers. Skill names are prefixed with skill__ when they
1709
+ * clash with MCP tools or other skills.
1369
1710
  *
1370
- * @returns Object with rendered servers section and set of all tool names for skill clash detection
1711
+ * @returns Object with rendered description and set of all tool names
1371
1712
  */
1372
- async buildServersSection() {
1713
+ async buildToolkitDescription() {
1373
1714
  const clients = this.clientManager.getAllClients();
1374
1715
  const toolToServers = /* @__PURE__ */ new Map();
1375
1716
  const serverToolsMap = /* @__PURE__ */ new Map();
@@ -1390,9 +1731,6 @@ var DescribeToolsTool = class DescribeToolsTool {
1390
1731
  }));
1391
1732
  /**
1392
1733
  * Formats tool name with server prefix if the tool exists on multiple servers
1393
- * @param toolName - The original tool name
1394
- * @param serverName - The server providing this tool
1395
- * @returns Tool name prefixed with serverName__ if clashing, otherwise plain name
1396
1734
  */
1397
1735
  const formatToolName = (toolName, serverName) => {
1398
1736
  return (toolToServers.get(toolName) || []).length > 1 ? `${serverName}__${toolName}` : toolName;
@@ -1412,31 +1750,56 @@ var DescribeToolsTool = class DescribeToolsTool {
1412
1750
  toolNames: formattedTools.map((t) => t.displayName)
1413
1751
  };
1414
1752
  });
1753
+ const rawSkills = this.skillService ? await this.skillService.getSkills() : [];
1754
+ const promptSkills = await this.collectPromptSkills();
1755
+ const seenSkillNames = /* @__PURE__ */ new Set();
1756
+ const allSkillsData = [];
1757
+ for (const skill of rawSkills) if (!seenSkillNames.has(skill.name)) {
1758
+ seenSkillNames.add(skill.name);
1759
+ allSkillsData.push({
1760
+ name: skill.name,
1761
+ displayName: skill.name,
1762
+ description: skill.description
1763
+ });
1764
+ }
1765
+ for (const skill of promptSkills) if (!seenSkillNames.has(skill.name)) {
1766
+ seenSkillNames.add(skill.name);
1767
+ allSkillsData.push(skill);
1768
+ }
1769
+ const skills = allSkillsData.map((skill) => {
1770
+ const clashesWithMcpTool = allToolNames.has(skill.name);
1771
+ return {
1772
+ name: skill.name,
1773
+ displayName: clashesWithMcpTool ? `${SKILL_PREFIX$1}${skill.name}` : skill.name,
1774
+ description: skill.description
1775
+ };
1776
+ });
1415
1777
  return {
1416
- content: await this.liquid.parseAndRender(mcp_servers_description_default, { servers }),
1778
+ content: await this.liquid.parseAndRender(toolkit_description_default, {
1779
+ servers,
1780
+ skills
1781
+ }),
1417
1782
  toolNames: allToolNames
1418
1783
  };
1419
1784
  }
1420
1785
  /**
1421
- * Gets the tool definition including available servers, tools, and skills.
1786
+ * Gets the tool definition including available tools and skills in a unified format.
1422
1787
  *
1423
1788
  * The definition includes:
1424
- * - List of all connected MCP servers with their available tools
1425
- * - List of available skills with skill__ prefix
1426
- * - Usage instructions for querying tool/skill details
1789
+ * - All MCP tools from connected servers
1790
+ * - All available skills (file-based and prompt-based)
1791
+ * - Unified instructions for querying capability details
1427
1792
  *
1428
- * Tool names are prefixed with serverName__ when the same tool name
1429
- * exists on multiple servers to avoid ambiguity.
1793
+ * Tool names are prefixed with serverName__ when clashing.
1794
+ * Skill names are prefixed with skill__ when clashing.
1430
1795
  *
1431
1796
  * @returns Tool definition with description and input schema
1432
1797
  */
1433
1798
  async getDefinition() {
1434
- const serversResult = await this.buildServersSection();
1435
- const skillsSection = await this.buildSkillsSection(serversResult.toolNames);
1799
+ const { content } = await this.buildToolkitDescription();
1436
1800
  return {
1437
1801
  name: DescribeToolsTool.TOOL_NAME,
1438
- description: `${serversResult.content}
1439
- ${skillsSection}`,
1802
+ description: content,
1440
1803
  inputSchema: {
1441
1804
  type: "object",
1442
1805
  properties: { toolNames: {
@@ -1501,16 +1864,24 @@ ${skillsSection}`,
1501
1864
  const notFoundItems = [];
1502
1865
  for (const requestedName of toolNames) {
1503
1866
  if (requestedName.startsWith(SKILL_PREFIX$1)) {
1504
- const skillName = requestedName.slice(7);
1867
+ const skillName = requestedName.slice(SKILL_PREFIX$1.length);
1505
1868
  if (this.skillService) {
1506
1869
  const skill = await this.skillService.getSkill(skillName);
1507
- if (skill) foundSkills.push({
1508
- name: skill.name,
1509
- location: skill.basePath,
1510
- instructions: skill.content
1511
- });
1512
- else notFoundItems.push(requestedName);
1513
- } else notFoundItems.push(requestedName);
1870
+ if (skill) {
1871
+ foundSkills.push({
1872
+ name: skill.name,
1873
+ location: skill.basePath,
1874
+ instructions: formatSkillInstructions(skill.name, skill.content)
1875
+ });
1876
+ continue;
1877
+ }
1878
+ }
1879
+ const promptSkillContent = await this.getPromptSkillContent(skillName);
1880
+ if (promptSkillContent) {
1881
+ foundSkills.push(promptSkillContent);
1882
+ continue;
1883
+ }
1884
+ notFoundItems.push(requestedName);
1514
1885
  continue;
1515
1886
  }
1516
1887
  const { serverName, actualToolName } = parseToolName(requestedName);
@@ -1539,11 +1910,16 @@ ${skillsSection}`,
1539
1910
  foundSkills.push({
1540
1911
  name: skill.name,
1541
1912
  location: skill.basePath,
1542
- instructions: skill.content
1913
+ instructions: formatSkillInstructions(skill.name, skill.content)
1543
1914
  });
1544
1915
  continue;
1545
1916
  }
1546
1917
  }
1918
+ const promptSkillContent = await this.getPromptSkillContent(actualToolName);
1919
+ if (promptSkillContent) {
1920
+ foundSkills.push(promptSkillContent);
1921
+ continue;
1922
+ }
1547
1923
  notFoundItems.push(requestedName);
1548
1924
  continue;
1549
1925
  }
@@ -1652,7 +2028,7 @@ var UseToolTool = class UseToolTool {
1652
2028
  getDefinition() {
1653
2029
  return {
1654
2030
  name: UseToolTool.TOOL_NAME,
1655
- 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:
2031
+ 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:
1656
2032
  - Provide toolName and toolArgs based on the schema
1657
2033
  - If multiple servers provide the same tool, specify serverName
1658
2034
  `,
@@ -1691,6 +2067,37 @@ var UseToolTool = class UseToolTool {
1691
2067
  }] };
1692
2068
  }
1693
2069
  /**
2070
+ * Finds a prompt-based skill by name from all connected MCP servers.
2071
+ *
2072
+ * @param skillName - The skill name to search for
2073
+ * @returns PromptSkillMatch if found, undefined otherwise
2074
+ */
2075
+ findPromptSkill(skillName) {
2076
+ if (!skillName) return void 0;
2077
+ const clients = this.clientManager.getAllClients();
2078
+ for (const client of clients) {
2079
+ if (!client.prompts) continue;
2080
+ for (const [promptName, promptConfig] of Object.entries(client.prompts)) if (promptConfig.skill && promptConfig.skill.name === skillName) return {
2081
+ serverName: client.serverName,
2082
+ promptName,
2083
+ skill: promptConfig.skill
2084
+ };
2085
+ }
2086
+ }
2087
+ /**
2088
+ * Returns guidance message for prompt-based skill invocation.
2089
+ *
2090
+ * @param promptSkill - The prompt skill match that was found
2091
+ * @returns CallToolResult with guidance message
2092
+ */
2093
+ executePromptSkill(promptSkill) {
2094
+ const location = promptSkill.skill.folder || `prompt:${promptSkill.serverName}/${promptSkill.promptName}`;
2095
+ return { content: [{
2096
+ type: "text",
2097
+ 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.`
2098
+ }] };
2099
+ }
2100
+ /**
1694
2101
  * Executes a tool or skill based on the provided input.
1695
2102
  *
1696
2103
  * Handles three invocation patterns:
@@ -1711,22 +2118,19 @@ var UseToolTool = class UseToolTool {
1711
2118
  const { toolName: inputToolName, toolArgs = {} } = input;
1712
2119
  if (inputToolName.startsWith(SKILL_PREFIX)) {
1713
2120
  const skillName = inputToolName.slice(7);
1714
- if (!this.skillService) return {
1715
- content: [{
1716
- type: "text",
1717
- text: `Skills are not configured. Cannot execute skill "${skillName}".`
1718
- }],
1719
- isError: true
1720
- };
1721
- const skill = await this.skillService.getSkill(skillName);
1722
- if (!skill) return {
2121
+ if (this.skillService) {
2122
+ const skill = await this.skillService.getSkill(skillName);
2123
+ if (skill) return this.executeSkill(skill);
2124
+ }
2125
+ const promptSkill = this.findPromptSkill(skillName);
2126
+ if (promptSkill) return this.executePromptSkill(promptSkill);
2127
+ return {
1723
2128
  content: [{
1724
2129
  type: "text",
1725
2130
  text: `Skill "${skillName}" not found. Use describe_tools to see available skills.`
1726
2131
  }],
1727
2132
  isError: true
1728
2133
  };
1729
- return this.executeSkill(skill);
1730
2134
  }
1731
2135
  const clients = this.clientManager.getAllClients();
1732
2136
  const { serverName, actualToolName } = parseToolName(inputToolName);
@@ -1774,6 +2178,8 @@ var UseToolTool = class UseToolTool {
1774
2178
  const skill = await this.skillService.getSkill(actualToolName);
1775
2179
  if (skill) return this.executeSkill(skill);
1776
2180
  }
2181
+ const promptSkill = this.findPromptSkill(actualToolName);
2182
+ if (promptSkill) return this.executePromptSkill(promptSkill);
1777
2183
  return {
1778
2184
  content: [{
1779
2185
  type: "text",
@@ -1840,7 +2246,10 @@ async function createServer(options) {
1840
2246
  const server = new Server({
1841
2247
  name: "@agiflowai/one-mcp",
1842
2248
  version: "0.1.0"
1843
- }, { capabilities: { tools: {} } });
2249
+ }, { capabilities: {
2250
+ tools: {},
2251
+ prompts: {}
2252
+ } });
1844
2253
  const clientManager = new McpClientManagerService();
1845
2254
  let configSkills;
1846
2255
  if (options?.configFilePath) {
@@ -1873,9 +2282,16 @@ async function createServer(options) {
1873
2282
  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(", ")}`);
1874
2283
  }
1875
2284
  const skillsConfig = options?.skills || configSkills;
1876
- const skillService = skillsConfig && skillsConfig.paths.length > 0 ? new SkillService(process.cwd(), skillsConfig.paths) : void 0;
2285
+ const toolsRef = { describeTools: null };
2286
+ const skillService = skillsConfig && skillsConfig.paths.length > 0 ? new SkillService(process.cwd(), skillsConfig.paths, { onCacheInvalidated: () => {
2287
+ toolsRef.describeTools?.clearAutoDetectedSkillsCache();
2288
+ } }) : void 0;
1877
2289
  const describeTools = new DescribeToolsTool(clientManager, skillService);
1878
2290
  const useTool = new UseToolTool(clientManager, skillService);
2291
+ toolsRef.describeTools = describeTools;
2292
+ if (skillService) skillService.startWatching().catch((error) => {
2293
+ console.error(`[skill-watcher] File watcher failed (non-critical): ${error instanceof Error ? error.message : "Unknown error"}`);
2294
+ });
1879
2295
  server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: [await describeTools.getDefinition(), useTool.getDefinition()] }));
1880
2296
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
1881
2297
  const { name, arguments: args } = request.params;
@@ -1891,6 +2307,60 @@ async function createServer(options) {
1891
2307
  }
1892
2308
  throw new Error(`Unknown tool: ${name}`);
1893
2309
  });
2310
+ server.setRequestHandler(ListPromptsRequestSchema, async () => {
2311
+ const clients = clientManager.getAllClients();
2312
+ const promptToServers = /* @__PURE__ */ new Map();
2313
+ const serverPromptsMap = /* @__PURE__ */ new Map();
2314
+ await Promise.all(clients.map(async (client) => {
2315
+ try {
2316
+ const prompts = await client.listPrompts();
2317
+ serverPromptsMap.set(client.serverName, prompts);
2318
+ for (const prompt of prompts) {
2319
+ if (!promptToServers.has(prompt.name)) promptToServers.set(prompt.name, []);
2320
+ promptToServers.get(prompt.name).push(client.serverName);
2321
+ }
2322
+ } catch (error) {
2323
+ console.error(`Failed to list prompts from ${client.serverName}:`, error);
2324
+ serverPromptsMap.set(client.serverName, []);
2325
+ }
2326
+ }));
2327
+ const aggregatedPrompts = [];
2328
+ for (const client of clients) {
2329
+ const prompts = serverPromptsMap.get(client.serverName) || [];
2330
+ for (const prompt of prompts) {
2331
+ const hasClash = (promptToServers.get(prompt.name) || []).length > 1;
2332
+ aggregatedPrompts.push({
2333
+ name: hasClash ? `${client.serverName}__${prompt.name}` : prompt.name,
2334
+ description: prompt.description,
2335
+ arguments: prompt.arguments
2336
+ });
2337
+ }
2338
+ }
2339
+ return { prompts: aggregatedPrompts };
2340
+ });
2341
+ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
2342
+ const { name, arguments: args } = request.params;
2343
+ const clients = clientManager.getAllClients();
2344
+ const { serverName, actualToolName: actualPromptName } = parseToolName(name);
2345
+ if (serverName) {
2346
+ const client$1 = clientManager.getClient(serverName);
2347
+ if (!client$1) throw new Error(`Server not found: ${serverName}`);
2348
+ return await client$1.getPrompt(actualPromptName, args);
2349
+ }
2350
+ const serversWithPrompt = [];
2351
+ await Promise.all(clients.map(async (client$1) => {
2352
+ try {
2353
+ if ((await client$1.listPrompts()).some((p) => p.name === name)) serversWithPrompt.push(client$1.serverName);
2354
+ } catch (error) {
2355
+ console.error(`Failed to list prompts from ${client$1.serverName}:`, error);
2356
+ }
2357
+ }));
2358
+ if (serversWithPrompt.length === 0) throw new Error(`Prompt not found: ${name}`);
2359
+ 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.`);
2360
+ const client = clientManager.getClient(serversWithPrompt[0]);
2361
+ if (!client) throw new Error(`Server not found: ${serversWithPrompt[0]}`);
2362
+ return await client.getPrompt(name, args);
2363
+ });
1894
2364
  return server;
1895
2365
  }
1896
2366
 
@@ -2240,4 +2710,4 @@ var HttpTransportHandler = class {
2240
2710
  };
2241
2711
 
2242
2712
  //#endregion
2243
- 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 };
2713
+ export { SkillService as a, ConfigFetcherService as c, createServer as i, SseTransportHandler as n, findConfigFile as o, StdioTransportHandler as r, McpClientManagerService as s, HttpTransportHandler as t };