@agiflowai/one-mcp 0.2.4 → 0.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +129 -0
- package/dist/cli.cjs +95 -25
- package/dist/cli.mjs +95 -25
- package/dist/{http-CzQfsUEI.mjs → http-D9BDXhHn.mjs} +813 -64
- package/dist/{http-3v8zyDO3.cjs → http-xSfxBa8A.cjs} +825 -66
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +182 -18
- package/dist/index.d.mts +182 -18
- package/dist/index.mjs +1 -1
- package/package.json +5 -4
|
@@ -6,12 +6,16 @@ var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
|
6
6
|
var __getProtoOf = Object.getPrototypeOf;
|
|
7
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
8
|
var __copyProps = (to, from, except, desc) => {
|
|
9
|
-
if (from && typeof from === "object" || typeof from === "function")
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
10
|
+
for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
11
|
+
key = keys[i];
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except) {
|
|
13
|
+
__defProp(to, key, {
|
|
14
|
+
get: ((k) => from[k]).bind(null, key),
|
|
15
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
}
|
|
15
19
|
}
|
|
16
20
|
return to;
|
|
17
21
|
};
|
|
@@ -34,6 +38,7 @@ let node_os = require("node:os");
|
|
|
34
38
|
let __modelcontextprotocol_sdk_client_index_js = require("@modelcontextprotocol/sdk/client/index.js");
|
|
35
39
|
let __modelcontextprotocol_sdk_client_stdio_js = require("@modelcontextprotocol/sdk/client/stdio.js");
|
|
36
40
|
let __modelcontextprotocol_sdk_client_sse_js = require("@modelcontextprotocol/sdk/client/sse.js");
|
|
41
|
+
let liquidjs = require("liquidjs");
|
|
37
42
|
let __modelcontextprotocol_sdk_server_stdio_js = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
38
43
|
let __modelcontextprotocol_sdk_server_sse_js = require("@modelcontextprotocol/sdk/server/sse.js");
|
|
39
44
|
let express = require("express");
|
|
@@ -199,10 +204,25 @@ function validateRemoteConfigSource(source) {
|
|
|
199
204
|
* Claude Code / Claude Desktop standard MCP config format
|
|
200
205
|
* This is the format users write in their config files
|
|
201
206
|
*/
|
|
207
|
+
/**
|
|
208
|
+
* Prompt skill configuration schema
|
|
209
|
+
* Converts a prompt to an executable skill
|
|
210
|
+
*/
|
|
211
|
+
const PromptSkillConfigSchema = zod.z.object({
|
|
212
|
+
name: zod.z.string(),
|
|
213
|
+
description: zod.z.string(),
|
|
214
|
+
folder: zod.z.string().optional()
|
|
215
|
+
});
|
|
216
|
+
/**
|
|
217
|
+
* Prompt configuration schema
|
|
218
|
+
* Supports converting prompts to skills
|
|
219
|
+
*/
|
|
220
|
+
const PromptConfigSchema = zod.z.object({ skill: PromptSkillConfigSchema.optional() });
|
|
202
221
|
const AdditionalConfigSchema = zod.z.object({
|
|
203
222
|
instruction: zod.z.string().optional(),
|
|
204
223
|
toolBlacklist: zod.z.array(zod.z.string()).optional(),
|
|
205
|
-
omitToolDescription: zod.z.boolean().optional()
|
|
224
|
+
omitToolDescription: zod.z.boolean().optional(),
|
|
225
|
+
prompts: zod.z.record(zod.z.string(), PromptConfigSchema).optional()
|
|
206
226
|
}).optional();
|
|
207
227
|
const ClaudeCodeStdioServerSchema = zod.z.object({
|
|
208
228
|
command: zod.z.string(),
|
|
@@ -241,11 +261,16 @@ const RemoteConfigSourceSchema = zod.z.object({
|
|
|
241
261
|
]).optional()
|
|
242
262
|
});
|
|
243
263
|
/**
|
|
264
|
+
* Skills configuration schema
|
|
265
|
+
*/
|
|
266
|
+
const SkillsConfigSchema = zod.z.object({ paths: zod.z.array(zod.z.string()) });
|
|
267
|
+
/**
|
|
244
268
|
* Full Claude Code MCP configuration schema
|
|
245
269
|
*/
|
|
246
270
|
const ClaudeCodeMcpConfigSchema = zod.z.object({
|
|
247
271
|
mcpServers: zod.z.record(zod.z.string(), ClaudeCodeServerConfigSchema),
|
|
248
|
-
remoteConfigs: zod.z.array(RemoteConfigSourceSchema).optional()
|
|
272
|
+
remoteConfigs: zod.z.array(RemoteConfigSourceSchema).optional(),
|
|
273
|
+
skills: SkillsConfigSchema.optional()
|
|
249
274
|
});
|
|
250
275
|
/**
|
|
251
276
|
* Internal MCP config format
|
|
@@ -264,12 +289,25 @@ const McpSseConfigSchema = zod.z.object({
|
|
|
264
289
|
url: zod.z.string().url(),
|
|
265
290
|
headers: zod.z.record(zod.z.string(), zod.z.string()).optional()
|
|
266
291
|
});
|
|
292
|
+
/**
|
|
293
|
+
* Internal prompt skill configuration schema
|
|
294
|
+
*/
|
|
295
|
+
const InternalPromptSkillConfigSchema = zod.z.object({
|
|
296
|
+
name: zod.z.string(),
|
|
297
|
+
description: zod.z.string(),
|
|
298
|
+
folder: zod.z.string().optional()
|
|
299
|
+
});
|
|
300
|
+
/**
|
|
301
|
+
* Internal prompt configuration schema
|
|
302
|
+
*/
|
|
303
|
+
const InternalPromptConfigSchema = zod.z.object({ skill: InternalPromptSkillConfigSchema.optional() });
|
|
267
304
|
const McpServerConfigSchema = zod.z.discriminatedUnion("transport", [
|
|
268
305
|
zod.z.object({
|
|
269
306
|
name: zod.z.string(),
|
|
270
307
|
instruction: zod.z.string().optional(),
|
|
271
308
|
toolBlacklist: zod.z.array(zod.z.string()).optional(),
|
|
272
309
|
omitToolDescription: zod.z.boolean().optional(),
|
|
310
|
+
prompts: zod.z.record(zod.z.string(), InternalPromptConfigSchema).optional(),
|
|
273
311
|
transport: zod.z.literal("stdio"),
|
|
274
312
|
config: McpStdioConfigSchema
|
|
275
313
|
}),
|
|
@@ -278,6 +316,7 @@ const McpServerConfigSchema = zod.z.discriminatedUnion("transport", [
|
|
|
278
316
|
instruction: zod.z.string().optional(),
|
|
279
317
|
toolBlacklist: zod.z.array(zod.z.string()).optional(),
|
|
280
318
|
omitToolDescription: zod.z.boolean().optional(),
|
|
319
|
+
prompts: zod.z.record(zod.z.string(), InternalPromptConfigSchema).optional(),
|
|
281
320
|
transport: zod.z.literal("http"),
|
|
282
321
|
config: McpHttpConfigSchema
|
|
283
322
|
}),
|
|
@@ -286,6 +325,7 @@ const McpServerConfigSchema = zod.z.discriminatedUnion("transport", [
|
|
|
286
325
|
instruction: zod.z.string().optional(),
|
|
287
326
|
toolBlacklist: zod.z.array(zod.z.string()).optional(),
|
|
288
327
|
omitToolDescription: zod.z.boolean().optional(),
|
|
328
|
+
prompts: zod.z.record(zod.z.string(), InternalPromptConfigSchema).optional(),
|
|
289
329
|
transport: zod.z.literal("sse"),
|
|
290
330
|
config: McpSseConfigSchema
|
|
291
331
|
})
|
|
@@ -293,7 +333,10 @@ const McpServerConfigSchema = zod.z.discriminatedUnion("transport", [
|
|
|
293
333
|
/**
|
|
294
334
|
* Full internal MCP configuration schema
|
|
295
335
|
*/
|
|
296
|
-
const InternalMcpConfigSchema = zod.z.object({
|
|
336
|
+
const InternalMcpConfigSchema = zod.z.object({
|
|
337
|
+
mcpServers: zod.z.record(zod.z.string(), McpServerConfigSchema),
|
|
338
|
+
skills: SkillsConfigSchema.optional()
|
|
339
|
+
});
|
|
297
340
|
/**
|
|
298
341
|
* Transform Claude Code config to internal format
|
|
299
342
|
* Converts standard Claude Code MCP configuration to normalized internal format
|
|
@@ -315,6 +358,7 @@ function transformClaudeCodeConfig(claudeConfig) {
|
|
|
315
358
|
instruction: stdioConfig.instruction || stdioConfig.config?.instruction,
|
|
316
359
|
toolBlacklist: stdioConfig.config?.toolBlacklist,
|
|
317
360
|
omitToolDescription: stdioConfig.config?.omitToolDescription,
|
|
361
|
+
prompts: stdioConfig.config?.prompts,
|
|
318
362
|
transport: "stdio",
|
|
319
363
|
config: {
|
|
320
364
|
command: interpolatedCommand,
|
|
@@ -332,6 +376,7 @@ function transformClaudeCodeConfig(claudeConfig) {
|
|
|
332
376
|
instruction: httpConfig.instruction || httpConfig.config?.instruction,
|
|
333
377
|
toolBlacklist: httpConfig.config?.toolBlacklist,
|
|
334
378
|
omitToolDescription: httpConfig.config?.omitToolDescription,
|
|
379
|
+
prompts: httpConfig.config?.prompts,
|
|
335
380
|
transport,
|
|
336
381
|
config: {
|
|
337
382
|
url: interpolatedUrl,
|
|
@@ -340,7 +385,10 @@ function transformClaudeCodeConfig(claudeConfig) {
|
|
|
340
385
|
};
|
|
341
386
|
}
|
|
342
387
|
}
|
|
343
|
-
return {
|
|
388
|
+
return {
|
|
389
|
+
mcpServers: transformedServers,
|
|
390
|
+
skills: claudeConfig.skills
|
|
391
|
+
};
|
|
344
392
|
}
|
|
345
393
|
/**
|
|
346
394
|
* Parse and validate MCP config from raw JSON
|
|
@@ -789,12 +837,14 @@ var ConfigFetcherService = class {
|
|
|
789
837
|
//#region src/services/McpClientManagerService.ts
|
|
790
838
|
/**
|
|
791
839
|
* MCP Client wrapper for managing individual server connections
|
|
840
|
+
* This is an internal class used by McpClientManagerService
|
|
792
841
|
*/
|
|
793
842
|
var McpClient = class {
|
|
794
843
|
serverName;
|
|
795
844
|
serverInstruction;
|
|
796
845
|
toolBlacklist;
|
|
797
846
|
omitToolDescription;
|
|
847
|
+
prompts;
|
|
798
848
|
transport;
|
|
799
849
|
client;
|
|
800
850
|
childProcess;
|
|
@@ -804,6 +854,7 @@ var McpClient = class {
|
|
|
804
854
|
this.serverInstruction = config.instruction;
|
|
805
855
|
this.toolBlacklist = config.toolBlacklist;
|
|
806
856
|
this.omitToolDescription = config.omitToolDescription;
|
|
857
|
+
this.prompts = config.prompts;
|
|
807
858
|
this.transport = transport;
|
|
808
859
|
this.client = client;
|
|
809
860
|
}
|
|
@@ -899,7 +950,8 @@ var McpClientManagerService = class {
|
|
|
899
950
|
const mcpClient = new McpClient(serverName, config.transport, client, {
|
|
900
951
|
instruction: config.instruction,
|
|
901
952
|
toolBlacklist: config.toolBlacklist,
|
|
902
|
-
omitToolDescription: config.omitToolDescription
|
|
953
|
+
omitToolDescription: config.omitToolDescription,
|
|
954
|
+
prompts: config.prompts
|
|
903
955
|
});
|
|
904
956
|
try {
|
|
905
957
|
await Promise.race([this.performConnection(mcpClient, config), new Promise((_, reject) => setTimeout(() => reject(/* @__PURE__ */ new Error(`Connection timeout after ${timeoutMs}ms`)), timeoutMs))]);
|
|
@@ -976,6 +1028,261 @@ var McpClientManagerService = class {
|
|
|
976
1028
|
}
|
|
977
1029
|
};
|
|
978
1030
|
|
|
1031
|
+
//#endregion
|
|
1032
|
+
//#region src/services/SkillService.ts
|
|
1033
|
+
/**
|
|
1034
|
+
* SkillService
|
|
1035
|
+
*
|
|
1036
|
+
* DESIGN PATTERNS:
|
|
1037
|
+
* - Service pattern for business logic encapsulation
|
|
1038
|
+
* - Single responsibility principle
|
|
1039
|
+
* - Lazy loading pattern for skill discovery
|
|
1040
|
+
*
|
|
1041
|
+
* CODING STANDARDS:
|
|
1042
|
+
* - Use async/await for asynchronous operations
|
|
1043
|
+
* - Throw descriptive errors for error cases
|
|
1044
|
+
* - Keep methods focused and well-named
|
|
1045
|
+
* - Document complex logic with comments
|
|
1046
|
+
*
|
|
1047
|
+
* AVOID:
|
|
1048
|
+
* - Mixing concerns (keep focused on single domain)
|
|
1049
|
+
* - Direct tool implementation (services should be tool-agnostic)
|
|
1050
|
+
*/
|
|
1051
|
+
/**
|
|
1052
|
+
* Error thrown when skill loading fails
|
|
1053
|
+
*/
|
|
1054
|
+
var SkillLoadError = class extends Error {
|
|
1055
|
+
constructor(message, filePath, cause) {
|
|
1056
|
+
super(message);
|
|
1057
|
+
this.filePath = filePath;
|
|
1058
|
+
this.cause = cause;
|
|
1059
|
+
this.name = "SkillLoadError";
|
|
1060
|
+
}
|
|
1061
|
+
};
|
|
1062
|
+
/**
|
|
1063
|
+
* Check if a path exists asynchronously
|
|
1064
|
+
* @param path - Path to check
|
|
1065
|
+
* @returns true if path exists, false otherwise
|
|
1066
|
+
* @throws Error for unexpected filesystem errors (permission denied, etc.)
|
|
1067
|
+
*/
|
|
1068
|
+
async function pathExists(path) {
|
|
1069
|
+
try {
|
|
1070
|
+
await (0, node_fs_promises.access)(path);
|
|
1071
|
+
return true;
|
|
1072
|
+
} catch (error) {
|
|
1073
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") return false;
|
|
1074
|
+
throw new Error(`Failed to check path existence for "${path}": ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
/**
|
|
1078
|
+
* Service for loading and managing skills from configured skill directories.
|
|
1079
|
+
*
|
|
1080
|
+
* Skills are markdown files with YAML frontmatter that can be invoked via
|
|
1081
|
+
* the skill__ prefix in describe_tools and use_tool.
|
|
1082
|
+
*
|
|
1083
|
+
* Skills are only enabled when explicitly configured via the `skills.paths` array
|
|
1084
|
+
* in the MCP config.
|
|
1085
|
+
*
|
|
1086
|
+
* @example
|
|
1087
|
+
* // Config with skills enabled:
|
|
1088
|
+
* // skills:
|
|
1089
|
+
* // paths:
|
|
1090
|
+
* // - ".claude/skills"
|
|
1091
|
+
* // - "/absolute/path/to/skills"
|
|
1092
|
+
*
|
|
1093
|
+
* const skillService = new SkillService('/project/root', ['.claude/skills']);
|
|
1094
|
+
* const skills = await skillService.getSkills();
|
|
1095
|
+
*/
|
|
1096
|
+
var SkillService = class {
|
|
1097
|
+
cwd;
|
|
1098
|
+
skillPaths;
|
|
1099
|
+
cachedSkills = null;
|
|
1100
|
+
skillsByName = null;
|
|
1101
|
+
/**
|
|
1102
|
+
* Creates a new SkillService instance
|
|
1103
|
+
* @param cwd - Current working directory for resolving relative paths
|
|
1104
|
+
* @param skillPaths - Array of paths to skills directories
|
|
1105
|
+
*/
|
|
1106
|
+
constructor(cwd, skillPaths) {
|
|
1107
|
+
this.cwd = cwd;
|
|
1108
|
+
this.skillPaths = skillPaths;
|
|
1109
|
+
}
|
|
1110
|
+
/**
|
|
1111
|
+
* Get all available skills from configured directories.
|
|
1112
|
+
* Results are cached after first load.
|
|
1113
|
+
*
|
|
1114
|
+
* Skills from earlier entries in the config take precedence over
|
|
1115
|
+
* skills with the same name from later entries.
|
|
1116
|
+
*
|
|
1117
|
+
* @returns Array of loaded skills
|
|
1118
|
+
* @throws SkillLoadError if a critical error occurs during loading
|
|
1119
|
+
*/
|
|
1120
|
+
async getSkills() {
|
|
1121
|
+
if (this.cachedSkills !== null) return this.cachedSkills;
|
|
1122
|
+
const skills = [];
|
|
1123
|
+
const loadedSkillNames = /* @__PURE__ */ new Set();
|
|
1124
|
+
for (const skillPath of this.skillPaths) {
|
|
1125
|
+
const skillsDir = (0, node_path.isAbsolute)(skillPath) ? skillPath : (0, node_path.join)(this.cwd, skillPath);
|
|
1126
|
+
const dirSkills = await this.loadSkillsFromDirectory(skillsDir, "project");
|
|
1127
|
+
for (const skill of dirSkills) if (!loadedSkillNames.has(skill.name)) {
|
|
1128
|
+
skills.push(skill);
|
|
1129
|
+
loadedSkillNames.add(skill.name);
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
this.cachedSkills = skills;
|
|
1133
|
+
this.skillsByName = new Map(skills.map((skill) => [skill.name, skill]));
|
|
1134
|
+
return skills;
|
|
1135
|
+
}
|
|
1136
|
+
/**
|
|
1137
|
+
* Get a specific skill by name with O(1) lookup from cache.
|
|
1138
|
+
* @param name - The skill name (without skill__ prefix)
|
|
1139
|
+
* @returns The skill if found, undefined otherwise
|
|
1140
|
+
*/
|
|
1141
|
+
async getSkill(name) {
|
|
1142
|
+
if (this.skillsByName === null) await this.getSkills();
|
|
1143
|
+
return this.skillsByName?.get(name);
|
|
1144
|
+
}
|
|
1145
|
+
/**
|
|
1146
|
+
* Clears the cached skills to force a fresh reload on the next getSkills() or getSkill() call.
|
|
1147
|
+
* Use this when skill files have been modified on disk.
|
|
1148
|
+
*/
|
|
1149
|
+
clearCache() {
|
|
1150
|
+
this.cachedSkills = null;
|
|
1151
|
+
this.skillsByName = null;
|
|
1152
|
+
}
|
|
1153
|
+
/**
|
|
1154
|
+
* Load skills from a directory.
|
|
1155
|
+
* Supports both flat structure (SKILL.md) and nested structure (name/SKILL.md).
|
|
1156
|
+
*
|
|
1157
|
+
* @param dirPath - Path to the skills directory
|
|
1158
|
+
* @param location - Whether this is a 'project' or 'user' skill directory
|
|
1159
|
+
* @returns Array of successfully loaded skills (skips invalid skills)
|
|
1160
|
+
* @throws SkillLoadError if there's a critical I/O error
|
|
1161
|
+
*
|
|
1162
|
+
* @example
|
|
1163
|
+
* // Load skills from project directory
|
|
1164
|
+
* const skills = await this.loadSkillsFromDirectory('/path/to/.claude/skills', 'project');
|
|
1165
|
+
* // Returns: [{ name: 'pdf', description: '...', location: 'project', ... }]
|
|
1166
|
+
*/
|
|
1167
|
+
async loadSkillsFromDirectory(dirPath, location) {
|
|
1168
|
+
const skills = [];
|
|
1169
|
+
try {
|
|
1170
|
+
if (!await pathExists(dirPath)) return skills;
|
|
1171
|
+
} catch (error) {
|
|
1172
|
+
throw new SkillLoadError(`Cannot access skills directory: ${error instanceof Error ? error.message : "Unknown error"}`, dirPath, error instanceof Error ? error : void 0);
|
|
1173
|
+
}
|
|
1174
|
+
let entries;
|
|
1175
|
+
try {
|
|
1176
|
+
entries = await (0, node_fs_promises.readdir)(dirPath);
|
|
1177
|
+
} catch (error) {
|
|
1178
|
+
throw new SkillLoadError(`Failed to read skills directory: ${error instanceof Error ? error.message : "Unknown error"}`, dirPath, error instanceof Error ? error : void 0);
|
|
1179
|
+
}
|
|
1180
|
+
for (const entry of entries) {
|
|
1181
|
+
const entryPath = (0, node_path.join)(dirPath, entry);
|
|
1182
|
+
let entryStat;
|
|
1183
|
+
try {
|
|
1184
|
+
entryStat = await (0, node_fs_promises.stat)(entryPath);
|
|
1185
|
+
} catch (error) {
|
|
1186
|
+
console.warn(`Skipping entry ${entryPath}: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
1187
|
+
continue;
|
|
1188
|
+
}
|
|
1189
|
+
if (entryStat.isDirectory()) {
|
|
1190
|
+
const skillFilePath = (0, node_path.join)(entryPath, "SKILL.md");
|
|
1191
|
+
try {
|
|
1192
|
+
if (await pathExists(skillFilePath)) {
|
|
1193
|
+
const skill = await this.loadSkillFile(skillFilePath, location);
|
|
1194
|
+
if (skill) skills.push(skill);
|
|
1195
|
+
}
|
|
1196
|
+
} catch (error) {
|
|
1197
|
+
console.warn(`Skipping skill at ${skillFilePath}: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
1198
|
+
continue;
|
|
1199
|
+
}
|
|
1200
|
+
} else if (entry === "SKILL.md") try {
|
|
1201
|
+
const skill = await this.loadSkillFile(entryPath, location);
|
|
1202
|
+
if (skill) skills.push(skill);
|
|
1203
|
+
} catch (error) {
|
|
1204
|
+
console.warn(`Skipping skill at ${entryPath}: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
1205
|
+
continue;
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
return skills;
|
|
1209
|
+
}
|
|
1210
|
+
/**
|
|
1211
|
+
* Load a single skill file and parse its frontmatter.
|
|
1212
|
+
*
|
|
1213
|
+
* @param filePath - Path to the SKILL.md file
|
|
1214
|
+
* @param location - Whether this is a 'project' or 'user' skill
|
|
1215
|
+
* @returns The loaded skill, or null if the file is invalid (missing required frontmatter)
|
|
1216
|
+
* @throws SkillLoadError if there's an I/O error reading the file
|
|
1217
|
+
*
|
|
1218
|
+
* @example
|
|
1219
|
+
* // Load a skill from a file
|
|
1220
|
+
* const skill = await this.loadSkillFile('/path/to/pdf/SKILL.md', 'project');
|
|
1221
|
+
* // Returns: { name: 'pdf', description: 'PDF skill', location: 'project', content: '...', basePath: '/path/to/pdf' }
|
|
1222
|
+
* // Returns null if frontmatter is missing name or description
|
|
1223
|
+
*/
|
|
1224
|
+
async loadSkillFile(filePath, location) {
|
|
1225
|
+
let content;
|
|
1226
|
+
try {
|
|
1227
|
+
content = await (0, node_fs_promises.readFile)(filePath, "utf-8");
|
|
1228
|
+
} catch (error) {
|
|
1229
|
+
throw new SkillLoadError(`Failed to read skill file: ${error instanceof Error ? error.message : "Unknown error"}`, filePath, error instanceof Error ? error : void 0);
|
|
1230
|
+
}
|
|
1231
|
+
const { metadata, body } = this.parseFrontmatter(content);
|
|
1232
|
+
if (!metadata.name || !metadata.description) return null;
|
|
1233
|
+
return {
|
|
1234
|
+
name: metadata.name,
|
|
1235
|
+
description: metadata.description,
|
|
1236
|
+
location,
|
|
1237
|
+
content: body,
|
|
1238
|
+
basePath: (0, node_path.dirname)(filePath)
|
|
1239
|
+
};
|
|
1240
|
+
}
|
|
1241
|
+
/**
|
|
1242
|
+
* Parse YAML frontmatter from markdown content.
|
|
1243
|
+
* Frontmatter is delimited by --- at start and end.
|
|
1244
|
+
*
|
|
1245
|
+
* @param content - Full markdown content with frontmatter
|
|
1246
|
+
* @returns Parsed metadata and body content
|
|
1247
|
+
*
|
|
1248
|
+
* @example
|
|
1249
|
+
* // Input content:
|
|
1250
|
+
* // ---
|
|
1251
|
+
* // name: my-skill
|
|
1252
|
+
* // description: A sample skill
|
|
1253
|
+
* // ---
|
|
1254
|
+
* // # Skill Content
|
|
1255
|
+
* // This is the skill body.
|
|
1256
|
+
*
|
|
1257
|
+
* const result = parseFrontmatter(content);
|
|
1258
|
+
* // result.metadata = { name: 'my-skill', description: 'A sample skill' }
|
|
1259
|
+
* // result.body = '# Skill Content\nThis is the skill body.'
|
|
1260
|
+
*/
|
|
1261
|
+
parseFrontmatter(content) {
|
|
1262
|
+
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/);
|
|
1263
|
+
if (!match) return {
|
|
1264
|
+
metadata: {},
|
|
1265
|
+
body: content
|
|
1266
|
+
};
|
|
1267
|
+
const [, frontmatter, body] = match;
|
|
1268
|
+
const metadata = {};
|
|
1269
|
+
const lines = frontmatter.split("\n");
|
|
1270
|
+
for (const line of lines) {
|
|
1271
|
+
const colonIndex = line.indexOf(":");
|
|
1272
|
+
if (colonIndex > 0) {
|
|
1273
|
+
const key = line.slice(0, colonIndex).trim();
|
|
1274
|
+
let value = line.slice(colonIndex + 1).trim();
|
|
1275
|
+
if (value.startsWith("\"") && value.endsWith("\"") || value.startsWith("'") && value.endsWith("'")) value = value.slice(1, -1);
|
|
1276
|
+
if (key === "name" || key === "description" || key === "license") metadata[key] = value;
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
return {
|
|
1280
|
+
metadata,
|
|
1281
|
+
body: body.trim()
|
|
1282
|
+
};
|
|
1283
|
+
}
|
|
1284
|
+
};
|
|
1285
|
+
|
|
979
1286
|
//#endregion
|
|
980
1287
|
//#region src/utils/findConfigFile.ts
|
|
981
1288
|
/**
|
|
@@ -1045,15 +1352,165 @@ function parseToolName(toolName) {
|
|
|
1045
1352
|
return { actualToolName: toolName };
|
|
1046
1353
|
}
|
|
1047
1354
|
|
|
1355
|
+
//#endregion
|
|
1356
|
+
//#region src/templates/skills-description.liquid?raw
|
|
1357
|
+
var skills_description_default = "{% if skills.size > 0 %}\n<skills>\n<instructions>\nWhen users ask you to perform tasks, check if any of the available skills below can help complete the task more effectively. Skills provide specialized capabilities and domain knowledge.\n\nHow to use skills:\n- Invoke skills using this tool with the skill name only (no arguments)\n- When you invoke a skill, you will see <command-message>The \"{name}\" skill is loading</command-message>\n- The skill's prompt will expand and provide detailed instructions on how to complete the task\n- Examples:\n - `skill: \"pdf\"` - invoke the pdf skill\n - `skill: \"xlsx\"` - invoke the xlsx skill\n - `skill: \"ms-office-suite:pdf\"` - invoke using fully qualified name\n\nImportant:\n- Only use skills listed in <available_skills> below\n- Do not invoke a skill that is already running\n- Do not use this tool for built-in CLI commands (like /help, /clear, etc.)\n</instructions>\n\n<available_skills>\n{% for skill in skills -%}\n<item><name>{{ skill.displayName }}</name><description>{{ skill.description }}</description></item>\n{% endfor -%}\n</available_skills>\n</skills>\n{% endif %}\n";
|
|
1358
|
+
|
|
1359
|
+
//#endregion
|
|
1360
|
+
//#region src/templates/mcp-servers-description.liquid?raw
|
|
1361
|
+
var mcp_servers_description_default = "<mcp_servers>\n<instructions>\nBefore you use any tools above, you MUST call this tool with a list of tool names to learn how to use them properly before use_tool; this includes:\n- Arguments schema needed to pass to the tool use\n- Description about each tool\n\nThis tool is optimized for batch queries - you can request multiple tools at once for better performance.\n</instructions>\n\n{% for server in servers -%}\n<server name=\"{{ server.name }}\">\n{% if server.instruction -%}\n<instruction>{{ server.instruction }}</instruction>\n{% endif -%}\n<tools>\n{% if server.omitToolDescription -%}\n{{ server.toolNames | join: \", \" }}\n{% else -%}\n{% for tool in server.tools -%}\n<item><name>{{ tool.displayName }}</name><description>{{ tool.description | default: \"No description\" }}</description></item>\n{% endfor -%}\n{% endif -%}\n</tools>\n</server>\n{% endfor -%}\n</mcp_servers>\n";
|
|
1362
|
+
|
|
1048
1363
|
//#endregion
|
|
1049
1364
|
//#region src/tools/DescribeToolsTool.ts
|
|
1365
|
+
/**
|
|
1366
|
+
* Prefix used to identify skill invocations
|
|
1367
|
+
*/
|
|
1368
|
+
const SKILL_PREFIX$1 = "skill__";
|
|
1369
|
+
/**
|
|
1370
|
+
* DescribeToolsTool provides progressive disclosure of MCP tools and skills.
|
|
1371
|
+
*
|
|
1372
|
+
* This tool lists available tools from all connected MCP servers and skills
|
|
1373
|
+
* from the configured skills directories. Users can query for specific tools
|
|
1374
|
+
* or skills to get detailed input schemas and descriptions.
|
|
1375
|
+
*
|
|
1376
|
+
* Tool naming conventions:
|
|
1377
|
+
* - Unique tools: use plain name (e.g., "browser_click")
|
|
1378
|
+
* - Clashing tools: use serverName__toolName format (e.g., "playwright__click")
|
|
1379
|
+
* - Skills: use skill__skillName format (e.g., "skill__pdf")
|
|
1380
|
+
*
|
|
1381
|
+
* @example
|
|
1382
|
+
* const tool = new DescribeToolsTool(clientManager, skillService);
|
|
1383
|
+
* const definition = await tool.getDefinition();
|
|
1384
|
+
* const result = await tool.execute({ toolNames: ['browser_click', 'skill__pdf'] });
|
|
1385
|
+
*/
|
|
1050
1386
|
var DescribeToolsTool = class DescribeToolsTool {
|
|
1051
1387
|
static TOOL_NAME = "describe_tools";
|
|
1052
1388
|
clientManager;
|
|
1053
|
-
|
|
1389
|
+
skillService;
|
|
1390
|
+
liquid = new liquidjs.Liquid();
|
|
1391
|
+
/**
|
|
1392
|
+
* Creates a new DescribeToolsTool instance
|
|
1393
|
+
* @param clientManager - The MCP client manager for accessing remote servers
|
|
1394
|
+
* @param skillService - Optional skill service for loading skills
|
|
1395
|
+
*/
|
|
1396
|
+
constructor(clientManager, skillService) {
|
|
1054
1397
|
this.clientManager = clientManager;
|
|
1398
|
+
this.skillService = skillService;
|
|
1055
1399
|
}
|
|
1056
|
-
|
|
1400
|
+
/**
|
|
1401
|
+
* Collects skills derived from prompt configurations across all connected MCP servers.
|
|
1402
|
+
* Prompts with a skill configuration are converted to skill format for display.
|
|
1403
|
+
*
|
|
1404
|
+
* @returns Array of skill template data derived from prompts
|
|
1405
|
+
*/
|
|
1406
|
+
collectPromptSkills() {
|
|
1407
|
+
const clients = this.clientManager.getAllClients();
|
|
1408
|
+
const promptSkills = [];
|
|
1409
|
+
for (const client of clients) {
|
|
1410
|
+
if (!client.prompts) continue;
|
|
1411
|
+
for (const promptConfig of Object.values(client.prompts)) if (promptConfig.skill) promptSkills.push({
|
|
1412
|
+
name: promptConfig.skill.name,
|
|
1413
|
+
displayName: promptConfig.skill.name,
|
|
1414
|
+
description: promptConfig.skill.description
|
|
1415
|
+
});
|
|
1416
|
+
}
|
|
1417
|
+
return promptSkills;
|
|
1418
|
+
}
|
|
1419
|
+
/**
|
|
1420
|
+
* Finds a prompt-based skill by name from all connected MCP servers.
|
|
1421
|
+
* Returns the prompt name and skill config for fetching the prompt content.
|
|
1422
|
+
*
|
|
1423
|
+
* @param skillName - The skill name to search for
|
|
1424
|
+
* @returns Object with serverName, promptName, and skill config, or undefined if not found
|
|
1425
|
+
*/
|
|
1426
|
+
findPromptSkill(skillName) {
|
|
1427
|
+
if (!skillName) return void 0;
|
|
1428
|
+
const clients = this.clientManager.getAllClients();
|
|
1429
|
+
for (const client of clients) {
|
|
1430
|
+
if (!client.prompts) continue;
|
|
1431
|
+
for (const [promptName, promptConfig] of Object.entries(client.prompts)) if (promptConfig.skill && promptConfig.skill.name === skillName) return {
|
|
1432
|
+
serverName: client.serverName,
|
|
1433
|
+
promptName,
|
|
1434
|
+
skill: promptConfig.skill
|
|
1435
|
+
};
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
/**
|
|
1439
|
+
* Retrieves skill content from a prompt-based skill configuration.
|
|
1440
|
+
* Fetches the prompt from the MCP server and extracts text content.
|
|
1441
|
+
*
|
|
1442
|
+
* @param skillName - The skill name being requested
|
|
1443
|
+
* @returns SkillDescription if found and successfully fetched, undefined otherwise
|
|
1444
|
+
*/
|
|
1445
|
+
async getPromptSkillContent(skillName) {
|
|
1446
|
+
const promptSkill = this.findPromptSkill(skillName);
|
|
1447
|
+
if (!promptSkill) return void 0;
|
|
1448
|
+
const client = this.clientManager.getClient(promptSkill.serverName);
|
|
1449
|
+
if (!client) {
|
|
1450
|
+
console.error(`Client not found for server '${promptSkill.serverName}' when fetching prompt skill '${skillName}'`);
|
|
1451
|
+
return;
|
|
1452
|
+
}
|
|
1453
|
+
try {
|
|
1454
|
+
const instructions = (await client.getPrompt(promptSkill.promptName)).messages?.map((m) => {
|
|
1455
|
+
const content = m.content;
|
|
1456
|
+
if (typeof content === "string") return content;
|
|
1457
|
+
if (content && typeof content === "object" && "text" in content) return String(content.text);
|
|
1458
|
+
return "";
|
|
1459
|
+
}).join("\n") || "";
|
|
1460
|
+
return {
|
|
1461
|
+
name: promptSkill.skill.name,
|
|
1462
|
+
location: promptSkill.skill.folder || `prompt:${promptSkill.serverName}/${promptSkill.promptName}`,
|
|
1463
|
+
instructions
|
|
1464
|
+
};
|
|
1465
|
+
} catch (error) {
|
|
1466
|
+
console.error(`Failed to get prompt-based skill '${skillName}': ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
1467
|
+
return;
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
/**
|
|
1471
|
+
* Builds the skills section of the tool description using a Liquid template.
|
|
1472
|
+
*
|
|
1473
|
+
* Retrieves all available skills from the SkillService and prompt-based skills,
|
|
1474
|
+
* then renders them using the skills-description.liquid template. Skills are only
|
|
1475
|
+
* prefixed with skill__ when their name clashes with an MCP tool or another skill.
|
|
1476
|
+
*
|
|
1477
|
+
* @param mcpToolNames - Set of MCP tool names to check for clashes
|
|
1478
|
+
* @returns Rendered skills section string with available skills list
|
|
1479
|
+
*/
|
|
1480
|
+
async buildSkillsSection(mcpToolNames) {
|
|
1481
|
+
const rawSkills = this.skillService ? await this.skillService.getSkills() : [];
|
|
1482
|
+
const promptSkills = this.collectPromptSkills();
|
|
1483
|
+
const allSkillsData = [...rawSkills.map((skill) => ({
|
|
1484
|
+
name: skill.name,
|
|
1485
|
+
displayName: skill.name,
|
|
1486
|
+
description: skill.description
|
|
1487
|
+
})), ...promptSkills];
|
|
1488
|
+
const skillNameCounts = /* @__PURE__ */ new Map();
|
|
1489
|
+
for (const skill of allSkillsData) skillNameCounts.set(skill.name, (skillNameCounts.get(skill.name) || 0) + 1);
|
|
1490
|
+
const skills = allSkillsData.map((skill) => {
|
|
1491
|
+
const clashesWithMcpTool = mcpToolNames.has(skill.name);
|
|
1492
|
+
const clashesWithOtherSkill = (skillNameCounts.get(skill.name) || 0) > 1;
|
|
1493
|
+
const needsPrefix = clashesWithMcpTool || clashesWithOtherSkill;
|
|
1494
|
+
return {
|
|
1495
|
+
name: skill.name,
|
|
1496
|
+
displayName: needsPrefix ? `${SKILL_PREFIX$1}${skill.name}` : skill.name,
|
|
1497
|
+
description: skill.description
|
|
1498
|
+
};
|
|
1499
|
+
});
|
|
1500
|
+
return this.liquid.parseAndRender(skills_description_default, { skills });
|
|
1501
|
+
}
|
|
1502
|
+
/**
|
|
1503
|
+
* Builds the MCP servers section of the tool description using a Liquid template.
|
|
1504
|
+
*
|
|
1505
|
+
* Collects all tools from connected MCP servers, detects name clashes,
|
|
1506
|
+
* and renders them using the mcp-servers-description.liquid template.
|
|
1507
|
+
*
|
|
1508
|
+
* Tool names are prefixed with serverName__ when the same tool exists
|
|
1509
|
+
* on multiple servers to avoid ambiguity.
|
|
1510
|
+
*
|
|
1511
|
+
* @returns Object with rendered servers section and set of all tool names for skill clash detection
|
|
1512
|
+
*/
|
|
1513
|
+
async buildServersSection() {
|
|
1057
1514
|
const clients = this.clientManager.getAllClients();
|
|
1058
1515
|
const toolToServers = /* @__PURE__ */ new Map();
|
|
1059
1516
|
const serverToolsMap = /* @__PURE__ */ new Map();
|
|
@@ -1072,30 +1529,63 @@ var DescribeToolsTool = class DescribeToolsTool {
|
|
|
1072
1529
|
serverToolsMap.set(client.serverName, []);
|
|
1073
1530
|
}
|
|
1074
1531
|
}));
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1532
|
+
/**
|
|
1533
|
+
* Formats tool name with server prefix if the tool exists on multiple servers
|
|
1534
|
+
* @param toolName - The original tool name
|
|
1535
|
+
* @param serverName - The server providing this tool
|
|
1536
|
+
* @returns Tool name prefixed with serverName__ if clashing, otherwise plain name
|
|
1537
|
+
*/
|
|
1538
|
+
const formatToolName = (toolName, serverName) => {
|
|
1539
|
+
return (toolToServers.get(toolName) || []).length > 1 ? `${serverName}__${toolName}` : toolName;
|
|
1540
|
+
};
|
|
1541
|
+
const allToolNames = /* @__PURE__ */ new Set();
|
|
1542
|
+
const servers = clients.map((client) => {
|
|
1543
|
+
const formattedTools = (serverToolsMap.get(client.serverName) || []).map((t) => ({
|
|
1544
|
+
displayName: formatToolName(t.name, client.serverName),
|
|
1545
|
+
description: t.description
|
|
1546
|
+
}));
|
|
1547
|
+
for (const tool of formattedTools) allToolNames.add(tool.displayName);
|
|
1548
|
+
return {
|
|
1549
|
+
name: client.serverName,
|
|
1550
|
+
instruction: client.serverInstruction,
|
|
1551
|
+
omitToolDescription: client.omitToolDescription || false,
|
|
1552
|
+
tools: formattedTools,
|
|
1553
|
+
toolNames: formattedTools.map((t) => t.displayName)
|
|
1079
1554
|
};
|
|
1080
|
-
const toolList = client.omitToolDescription ? tools.map((t) => formatToolName(t.name)).join(", ") : tools.map((t) => `${formatToolName(t.name)}: """${t.description || "No description"}"""`).join("\n");
|
|
1081
|
-
const instructionLine = client.serverInstruction ? `\n"""${client.serverInstruction}"""` : "";
|
|
1082
|
-
return `\n\n### Server: ${client.serverName}${instructionLine}\n\n- Available tools:\n${toolList || "No tools available"}`;
|
|
1083
1555
|
});
|
|
1556
|
+
return {
|
|
1557
|
+
content: await this.liquid.parseAndRender(mcp_servers_description_default, { servers }),
|
|
1558
|
+
toolNames: allToolNames
|
|
1559
|
+
};
|
|
1560
|
+
}
|
|
1561
|
+
/**
|
|
1562
|
+
* Gets the tool definition including available servers, tools, and skills.
|
|
1563
|
+
*
|
|
1564
|
+
* The definition includes:
|
|
1565
|
+
* - List of all connected MCP servers with their available tools
|
|
1566
|
+
* - List of available skills with skill__ prefix
|
|
1567
|
+
* - Usage instructions for querying tool/skill details
|
|
1568
|
+
*
|
|
1569
|
+
* Tool names are prefixed with serverName__ when the same tool name
|
|
1570
|
+
* exists on multiple servers to avoid ambiguity.
|
|
1571
|
+
*
|
|
1572
|
+
* @returns Tool definition with description and input schema
|
|
1573
|
+
*/
|
|
1574
|
+
async getDefinition() {
|
|
1575
|
+
const serversResult = await this.buildServersSection();
|
|
1576
|
+
const skillsSection = await this.buildSkillsSection(serversResult.toolNames);
|
|
1084
1577
|
return {
|
|
1085
1578
|
name: DescribeToolsTool.TOOL_NAME,
|
|
1086
|
-
description:
|
|
1087
|
-
|
|
1088
|
-
## Usage:
|
|
1089
|
-
Before you use any tools above, you MUST call this tool with a list of tool names to learn how to use them properly before use_tool; this includes:
|
|
1090
|
-
- Arguments schema needed to pass to the tool use
|
|
1091
|
-
- Description about each tool
|
|
1092
|
-
|
|
1093
|
-
This tool is optimized for batch queries - you can request multiple tools at once for better performance.`,
|
|
1579
|
+
description: `${serversResult.content}
|
|
1580
|
+
${skillsSection}`,
|
|
1094
1581
|
inputSchema: {
|
|
1095
1582
|
type: "object",
|
|
1096
1583
|
properties: { toolNames: {
|
|
1097
1584
|
type: "array",
|
|
1098
|
-
items: {
|
|
1585
|
+
items: {
|
|
1586
|
+
type: "string",
|
|
1587
|
+
minLength: 1
|
|
1588
|
+
},
|
|
1099
1589
|
description: "List of tool names to get detailed information about",
|
|
1100
1590
|
minItems: 1
|
|
1101
1591
|
} },
|
|
@@ -1104,6 +1594,17 @@ This tool is optimized for batch queries - you can request multiple tools at onc
|
|
|
1104
1594
|
}
|
|
1105
1595
|
};
|
|
1106
1596
|
}
|
|
1597
|
+
/**
|
|
1598
|
+
* Executes tool description lookup for the requested tool and skill names.
|
|
1599
|
+
*
|
|
1600
|
+
* Handles three types of lookups:
|
|
1601
|
+
* 1. skill__name - Returns skill information from SkillService
|
|
1602
|
+
* 2. serverName__toolName - Returns tool from specific server
|
|
1603
|
+
* 3. plainToolName - Returns tool(s) from all servers (multiple if clashing)
|
|
1604
|
+
*
|
|
1605
|
+
* @param input - Object containing toolNames array
|
|
1606
|
+
* @returns CallToolResult with tool/skill descriptions or error
|
|
1607
|
+
*/
|
|
1107
1608
|
async execute(input) {
|
|
1108
1609
|
try {
|
|
1109
1610
|
const { toolNames } = input;
|
|
@@ -1121,9 +1622,13 @@ This tool is optimized for batch queries - you can request multiple tools at onc
|
|
|
1121
1622
|
try {
|
|
1122
1623
|
const tools = await client.listTools();
|
|
1123
1624
|
const blacklist = new Set(client.toolBlacklist || []);
|
|
1124
|
-
const
|
|
1125
|
-
|
|
1126
|
-
|
|
1625
|
+
const typedTools = tools.filter((t) => !blacklist.has(t.name)).map((t) => ({
|
|
1626
|
+
name: t.name,
|
|
1627
|
+
description: t.description,
|
|
1628
|
+
inputSchema: t.inputSchema
|
|
1629
|
+
}));
|
|
1630
|
+
serverToolsMap.set(client.serverName, typedTools);
|
|
1631
|
+
for (const tool of typedTools) {
|
|
1127
1632
|
if (!toolToServers.has(tool.name)) toolToServers.set(tool.name, []);
|
|
1128
1633
|
toolToServers.get(tool.name).push(client.serverName);
|
|
1129
1634
|
}
|
|
@@ -1133,13 +1638,35 @@ This tool is optimized for batch queries - you can request multiple tools at onc
|
|
|
1133
1638
|
}
|
|
1134
1639
|
}));
|
|
1135
1640
|
const foundTools = [];
|
|
1136
|
-
const
|
|
1137
|
-
|
|
1138
|
-
|
|
1641
|
+
const foundSkills = [];
|
|
1642
|
+
const notFoundItems = [];
|
|
1643
|
+
for (const requestedName of toolNames) {
|
|
1644
|
+
if (requestedName.startsWith(SKILL_PREFIX$1)) {
|
|
1645
|
+
const skillName = requestedName.slice(7);
|
|
1646
|
+
if (this.skillService) {
|
|
1647
|
+
const skill = await this.skillService.getSkill(skillName);
|
|
1648
|
+
if (skill) {
|
|
1649
|
+
foundSkills.push({
|
|
1650
|
+
name: skill.name,
|
|
1651
|
+
location: skill.basePath,
|
|
1652
|
+
instructions: skill.content
|
|
1653
|
+
});
|
|
1654
|
+
continue;
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1657
|
+
const promptSkillContent = await this.getPromptSkillContent(skillName);
|
|
1658
|
+
if (promptSkillContent) {
|
|
1659
|
+
foundSkills.push(promptSkillContent);
|
|
1660
|
+
continue;
|
|
1661
|
+
}
|
|
1662
|
+
notFoundItems.push(requestedName);
|
|
1663
|
+
continue;
|
|
1664
|
+
}
|
|
1665
|
+
const { serverName, actualToolName } = parseToolName(requestedName);
|
|
1139
1666
|
if (serverName) {
|
|
1140
1667
|
const serverTools = serverToolsMap.get(serverName);
|
|
1141
1668
|
if (!serverTools) {
|
|
1142
|
-
|
|
1669
|
+
notFoundItems.push(requestedName);
|
|
1143
1670
|
continue;
|
|
1144
1671
|
}
|
|
1145
1672
|
const tool = serverTools.find((t) => t.name === actualToolName);
|
|
@@ -1151,11 +1678,27 @@ This tool is optimized for batch queries - you can request multiple tools at onc
|
|
|
1151
1678
|
inputSchema: tool.inputSchema
|
|
1152
1679
|
}
|
|
1153
1680
|
});
|
|
1154
|
-
else
|
|
1681
|
+
else notFoundItems.push(requestedName);
|
|
1155
1682
|
} else {
|
|
1156
1683
|
const servers = toolToServers.get(actualToolName);
|
|
1157
1684
|
if (!servers || servers.length === 0) {
|
|
1158
|
-
|
|
1685
|
+
if (this.skillService) {
|
|
1686
|
+
const skill = await this.skillService.getSkill(actualToolName);
|
|
1687
|
+
if (skill) {
|
|
1688
|
+
foundSkills.push({
|
|
1689
|
+
name: skill.name,
|
|
1690
|
+
location: skill.basePath,
|
|
1691
|
+
instructions: skill.content
|
|
1692
|
+
});
|
|
1693
|
+
continue;
|
|
1694
|
+
}
|
|
1695
|
+
}
|
|
1696
|
+
const promptSkillContent = await this.getPromptSkillContent(actualToolName);
|
|
1697
|
+
if (promptSkillContent) {
|
|
1698
|
+
foundSkills.push(promptSkillContent);
|
|
1699
|
+
continue;
|
|
1700
|
+
}
|
|
1701
|
+
notFoundItems.push(requestedName);
|
|
1159
1702
|
continue;
|
|
1160
1703
|
}
|
|
1161
1704
|
if (servers.length === 1) {
|
|
@@ -1182,17 +1725,27 @@ This tool is optimized for batch queries - you can request multiple tools at onc
|
|
|
1182
1725
|
}
|
|
1183
1726
|
}
|
|
1184
1727
|
}
|
|
1185
|
-
if (foundTools.length === 0) return {
|
|
1728
|
+
if (foundTools.length === 0 && foundSkills.length === 0) return {
|
|
1186
1729
|
content: [{
|
|
1187
1730
|
type: "text",
|
|
1188
|
-
text: `None of the requested tools found
|
|
1731
|
+
text: `None of the requested tools/skills found.\nRequested: ${toolNames.join(", ")}\nUse describe_tools to see available tools and skills.`
|
|
1189
1732
|
}],
|
|
1190
1733
|
isError: true
|
|
1191
1734
|
};
|
|
1192
|
-
const result = {
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
result.
|
|
1735
|
+
const result = {};
|
|
1736
|
+
const nextSteps = [];
|
|
1737
|
+
if (foundTools.length > 0) {
|
|
1738
|
+
result.tools = foundTools;
|
|
1739
|
+
nextSteps.push("For MCP tools: Use the use_tool function with toolName and toolArgs based on the inputSchema above.");
|
|
1740
|
+
}
|
|
1741
|
+
if (foundSkills.length > 0) {
|
|
1742
|
+
result.skills = foundSkills;
|
|
1743
|
+
nextSteps.push(`For skill, just follow skill's description to continue.`);
|
|
1744
|
+
}
|
|
1745
|
+
if (nextSteps.length > 0) result.nextSteps = nextSteps;
|
|
1746
|
+
if (notFoundItems.length > 0) {
|
|
1747
|
+
result.notFound = notFoundItems;
|
|
1748
|
+
result.warnings = [`Items not found: ${notFoundItems.join(", ")}`];
|
|
1196
1749
|
}
|
|
1197
1750
|
return { content: [{
|
|
1198
1751
|
type: "text",
|
|
@@ -1212,16 +1765,48 @@ This tool is optimized for batch queries - you can request multiple tools at onc
|
|
|
1212
1765
|
|
|
1213
1766
|
//#endregion
|
|
1214
1767
|
//#region src/tools/UseToolTool.ts
|
|
1768
|
+
/**
|
|
1769
|
+
* Prefix used to identify skill invocations (e.g., skill__pdf)
|
|
1770
|
+
*/
|
|
1771
|
+
const SKILL_PREFIX = "skill__";
|
|
1772
|
+
/**
|
|
1773
|
+
* UseToolTool executes MCP tools and skills with proper error handling.
|
|
1774
|
+
*
|
|
1775
|
+
* This tool supports three invocation patterns:
|
|
1776
|
+
* 1. skill__skillName - Invokes a skill from the configured skills directory
|
|
1777
|
+
* 2. serverName__toolName - Invokes a tool on a specific MCP server
|
|
1778
|
+
* 3. plainToolName - Searches all servers for a unique tool match
|
|
1779
|
+
*
|
|
1780
|
+
* @example
|
|
1781
|
+
* const tool = new UseToolTool(clientManager, skillService);
|
|
1782
|
+
* await tool.execute({ toolName: 'skill__pdf' }); // Invoke a skill
|
|
1783
|
+
* await tool.execute({ toolName: 'playwright__browser_click', toolArgs: { ref: 'btn' } });
|
|
1784
|
+
*/
|
|
1215
1785
|
var UseToolTool = class UseToolTool {
|
|
1216
1786
|
static TOOL_NAME = "use_tool";
|
|
1217
1787
|
clientManager;
|
|
1218
|
-
|
|
1788
|
+
skillService;
|
|
1789
|
+
/**
|
|
1790
|
+
* Creates a new UseToolTool instance
|
|
1791
|
+
* @param clientManager - The MCP client manager for accessing remote servers
|
|
1792
|
+
* @param skillService - Optional skill service for loading and executing skills
|
|
1793
|
+
*/
|
|
1794
|
+
constructor(clientManager, skillService) {
|
|
1219
1795
|
this.clientManager = clientManager;
|
|
1796
|
+
this.skillService = skillService;
|
|
1220
1797
|
}
|
|
1798
|
+
/**
|
|
1799
|
+
* Returns the MCP tool definition with name, description, and input schema.
|
|
1800
|
+
*
|
|
1801
|
+
* The definition describes how to use this tool to execute MCP tools or skills,
|
|
1802
|
+
* including the skill__ prefix format for skill invocations.
|
|
1803
|
+
*
|
|
1804
|
+
* @returns The tool definition conforming to MCP spec
|
|
1805
|
+
*/
|
|
1221
1806
|
getDefinition() {
|
|
1222
1807
|
return {
|
|
1223
1808
|
name: UseToolTool.TOOL_NAME,
|
|
1224
|
-
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:
|
|
1809
|
+
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:
|
|
1225
1810
|
- Provide toolName and toolArgs based on the schema
|
|
1226
1811
|
- If multiple servers provide the same tool, specify serverName
|
|
1227
1812
|
`,
|
|
@@ -1230,7 +1815,8 @@ var UseToolTool = class UseToolTool {
|
|
|
1230
1815
|
properties: {
|
|
1231
1816
|
toolName: {
|
|
1232
1817
|
type: "string",
|
|
1233
|
-
description: "Name of the tool to execute"
|
|
1818
|
+
description: "Name of the tool to execute",
|
|
1819
|
+
minLength: 1
|
|
1234
1820
|
},
|
|
1235
1821
|
toolArgs: {
|
|
1236
1822
|
type: "object",
|
|
@@ -1242,9 +1828,88 @@ var UseToolTool = class UseToolTool {
|
|
|
1242
1828
|
}
|
|
1243
1829
|
};
|
|
1244
1830
|
}
|
|
1831
|
+
/**
|
|
1832
|
+
* Returns guidance message for skill invocation.
|
|
1833
|
+
*
|
|
1834
|
+
* Skills are not executed via use_tool - they provide instructions that should
|
|
1835
|
+
* be followed directly. This method returns a message directing users to use
|
|
1836
|
+
* describe_tools to get the skill details and follow its instructions.
|
|
1837
|
+
*
|
|
1838
|
+
* @param skill - The skill that was requested
|
|
1839
|
+
* @returns CallToolResult with guidance message
|
|
1840
|
+
*/
|
|
1841
|
+
executeSkill(skill) {
|
|
1842
|
+
return { content: [{
|
|
1843
|
+
type: "text",
|
|
1844
|
+
text: `Skill "${skill.name}" found. Skills provide instructions and should not be executed via use_tool.\n\nUse describe_tools to view the skill details at: ${skill.basePath}\n\nThen follow the skill's instructions directly.`
|
|
1845
|
+
}] };
|
|
1846
|
+
}
|
|
1847
|
+
/**
|
|
1848
|
+
* Finds a prompt-based skill by name from all connected MCP servers.
|
|
1849
|
+
*
|
|
1850
|
+
* @param skillName - The skill name to search for
|
|
1851
|
+
* @returns PromptSkillMatch if found, undefined otherwise
|
|
1852
|
+
*/
|
|
1853
|
+
findPromptSkill(skillName) {
|
|
1854
|
+
if (!skillName) return void 0;
|
|
1855
|
+
const clients = this.clientManager.getAllClients();
|
|
1856
|
+
for (const client of clients) {
|
|
1857
|
+
if (!client.prompts) continue;
|
|
1858
|
+
for (const [promptName, promptConfig] of Object.entries(client.prompts)) if (promptConfig.skill && promptConfig.skill.name === skillName) return {
|
|
1859
|
+
serverName: client.serverName,
|
|
1860
|
+
promptName,
|
|
1861
|
+
skill: promptConfig.skill
|
|
1862
|
+
};
|
|
1863
|
+
}
|
|
1864
|
+
}
|
|
1865
|
+
/**
|
|
1866
|
+
* Returns guidance message for prompt-based skill invocation.
|
|
1867
|
+
*
|
|
1868
|
+
* @param promptSkill - The prompt skill match that was found
|
|
1869
|
+
* @returns CallToolResult with guidance message
|
|
1870
|
+
*/
|
|
1871
|
+
executePromptSkill(promptSkill) {
|
|
1872
|
+
const location = promptSkill.skill.folder || `prompt:${promptSkill.serverName}/${promptSkill.promptName}`;
|
|
1873
|
+
return { content: [{
|
|
1874
|
+
type: "text",
|
|
1875
|
+
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.`
|
|
1876
|
+
}] };
|
|
1877
|
+
}
|
|
1878
|
+
/**
|
|
1879
|
+
* Executes a tool or skill based on the provided input.
|
|
1880
|
+
*
|
|
1881
|
+
* Handles three invocation patterns:
|
|
1882
|
+
* 1. skill__skillName - Routes to skill execution via SkillService
|
|
1883
|
+
* 2. serverName__toolName - Routes to specific MCP server
|
|
1884
|
+
* 3. plainToolName - Searches all servers for unique match
|
|
1885
|
+
*
|
|
1886
|
+
* Edge cases:
|
|
1887
|
+
* - Returns error if skill not found when using skill__ prefix
|
|
1888
|
+
* - Returns error if tool is blacklisted on target server
|
|
1889
|
+
* - Returns disambiguation message if tool exists on multiple servers
|
|
1890
|
+
*
|
|
1891
|
+
* @param input - The tool/skill name and optional arguments
|
|
1892
|
+
* @returns CallToolResult with execution output or error
|
|
1893
|
+
*/
|
|
1245
1894
|
async execute(input) {
|
|
1246
1895
|
try {
|
|
1247
1896
|
const { toolName: inputToolName, toolArgs = {} } = input;
|
|
1897
|
+
if (inputToolName.startsWith(SKILL_PREFIX)) {
|
|
1898
|
+
const skillName = inputToolName.slice(7);
|
|
1899
|
+
if (this.skillService) {
|
|
1900
|
+
const skill = await this.skillService.getSkill(skillName);
|
|
1901
|
+
if (skill) return this.executeSkill(skill);
|
|
1902
|
+
}
|
|
1903
|
+
const promptSkill = this.findPromptSkill(skillName);
|
|
1904
|
+
if (promptSkill) return this.executePromptSkill(promptSkill);
|
|
1905
|
+
return {
|
|
1906
|
+
content: [{
|
|
1907
|
+
type: "text",
|
|
1908
|
+
text: `Skill "${skillName}" not found. Use describe_tools to see available skills.`
|
|
1909
|
+
}],
|
|
1910
|
+
isError: true
|
|
1911
|
+
};
|
|
1912
|
+
}
|
|
1248
1913
|
const clients = this.clientManager.getAllClients();
|
|
1249
1914
|
const { serverName, actualToolName } = parseToolName(inputToolName);
|
|
1250
1915
|
if (serverName) {
|
|
@@ -1286,13 +1951,21 @@ var UseToolTool = class UseToolTool {
|
|
|
1286
1951
|
return null;
|
|
1287
1952
|
}));
|
|
1288
1953
|
matchingServers.push(...results.filter((r) => r !== null));
|
|
1289
|
-
if (matchingServers.length === 0)
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
}
|
|
1294
|
-
|
|
1295
|
-
|
|
1954
|
+
if (matchingServers.length === 0) {
|
|
1955
|
+
if (this.skillService) {
|
|
1956
|
+
const skill = await this.skillService.getSkill(actualToolName);
|
|
1957
|
+
if (skill) return this.executeSkill(skill);
|
|
1958
|
+
}
|
|
1959
|
+
const promptSkill = this.findPromptSkill(actualToolName);
|
|
1960
|
+
if (promptSkill) return this.executePromptSkill(promptSkill);
|
|
1961
|
+
return {
|
|
1962
|
+
content: [{
|
|
1963
|
+
type: "text",
|
|
1964
|
+
text: `Tool or skill "${actualToolName}" not found. Use describe_tools to see available tools and skills.`
|
|
1965
|
+
}],
|
|
1966
|
+
isError: true
|
|
1967
|
+
};
|
|
1968
|
+
}
|
|
1296
1969
|
if (matchingServers.length > 1) return {
|
|
1297
1970
|
content: [{
|
|
1298
1971
|
type: "text",
|
|
@@ -1351,34 +2024,114 @@ async function createServer(options) {
|
|
|
1351
2024
|
const server = new __modelcontextprotocol_sdk_server_index_js.Server({
|
|
1352
2025
|
name: "@agiflowai/one-mcp",
|
|
1353
2026
|
version: "0.1.0"
|
|
1354
|
-
}, { capabilities: {
|
|
2027
|
+
}, { capabilities: {
|
|
2028
|
+
tools: {},
|
|
2029
|
+
prompts: {}
|
|
2030
|
+
} });
|
|
1355
2031
|
const clientManager = new McpClientManagerService();
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
2032
|
+
let configSkills;
|
|
2033
|
+
if (options?.configFilePath) {
|
|
2034
|
+
let config;
|
|
2035
|
+
try {
|
|
2036
|
+
config = await new ConfigFetcherService({
|
|
2037
|
+
configFilePath: options.configFilePath,
|
|
2038
|
+
useCache: !options.noCache
|
|
2039
|
+
}).fetchConfiguration(options.noCache || false);
|
|
2040
|
+
} catch (error) {
|
|
2041
|
+
throw new Error(`Failed to load MCP configuration from '${options.configFilePath}': ${error instanceof Error ? error.message : String(error)}`);
|
|
2042
|
+
}
|
|
2043
|
+
configSkills = config.skills;
|
|
2044
|
+
const failedConnections = [];
|
|
1361
2045
|
const connectionPromises = Object.entries(config.mcpServers).map(async ([serverName, serverConfig]) => {
|
|
1362
2046
|
try {
|
|
1363
2047
|
await clientManager.connectToServer(serverName, serverConfig);
|
|
1364
2048
|
console.error(`Connected to MCP server: ${serverName}`);
|
|
1365
2049
|
} catch (error) {
|
|
2050
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
2051
|
+
failedConnections.push({
|
|
2052
|
+
serverName,
|
|
2053
|
+
error: err
|
|
2054
|
+
});
|
|
1366
2055
|
console.error(`Failed to connect to ${serverName}:`, error);
|
|
1367
2056
|
}
|
|
1368
2057
|
});
|
|
1369
2058
|
await Promise.all(connectionPromises);
|
|
1370
|
-
|
|
1371
|
-
|
|
2059
|
+
if (failedConnections.length > 0 && failedConnections.length < Object.keys(config.mcpServers).length) console.error(`Warning: Some MCP server connections failed: ${failedConnections.map((f) => f.serverName).join(", ")}`);
|
|
2060
|
+
if (failedConnections.length > 0 && failedConnections.length === Object.keys(config.mcpServers).length) throw new Error(`All MCP server connections failed: ${failedConnections.map((f) => `${f.serverName}: ${f.error.message}`).join(", ")}`);
|
|
1372
2061
|
}
|
|
1373
|
-
const
|
|
1374
|
-
const
|
|
2062
|
+
const skillsConfig = options?.skills || configSkills;
|
|
2063
|
+
const skillService = skillsConfig && skillsConfig.paths.length > 0 ? new SkillService(process.cwd(), skillsConfig.paths) : void 0;
|
|
2064
|
+
const describeTools = new DescribeToolsTool(clientManager, skillService);
|
|
2065
|
+
const useTool = new UseToolTool(clientManager, skillService);
|
|
1375
2066
|
server.setRequestHandler(__modelcontextprotocol_sdk_types_js.ListToolsRequestSchema, async () => ({ tools: [await describeTools.getDefinition(), useTool.getDefinition()] }));
|
|
1376
2067
|
server.setRequestHandler(__modelcontextprotocol_sdk_types_js.CallToolRequestSchema, async (request) => {
|
|
1377
2068
|
const { name, arguments: args } = request.params;
|
|
1378
|
-
if (name === DescribeToolsTool.TOOL_NAME)
|
|
1379
|
-
|
|
2069
|
+
if (name === DescribeToolsTool.TOOL_NAME) try {
|
|
2070
|
+
return await describeTools.execute(args);
|
|
2071
|
+
} catch (error) {
|
|
2072
|
+
throw new Error(`Failed to execute ${name}: ${error instanceof Error ? error.message : String(error)}`);
|
|
2073
|
+
}
|
|
2074
|
+
if (name === UseToolTool.TOOL_NAME) try {
|
|
2075
|
+
return await useTool.execute(args);
|
|
2076
|
+
} catch (error) {
|
|
2077
|
+
throw new Error(`Failed to execute ${name}: ${error instanceof Error ? error.message : String(error)}`);
|
|
2078
|
+
}
|
|
1380
2079
|
throw new Error(`Unknown tool: ${name}`);
|
|
1381
2080
|
});
|
|
2081
|
+
server.setRequestHandler(__modelcontextprotocol_sdk_types_js.ListPromptsRequestSchema, async () => {
|
|
2082
|
+
const clients = clientManager.getAllClients();
|
|
2083
|
+
const promptToServers = /* @__PURE__ */ new Map();
|
|
2084
|
+
const serverPromptsMap = /* @__PURE__ */ new Map();
|
|
2085
|
+
await Promise.all(clients.map(async (client) => {
|
|
2086
|
+
try {
|
|
2087
|
+
const prompts = await client.listPrompts();
|
|
2088
|
+
serverPromptsMap.set(client.serverName, prompts);
|
|
2089
|
+
for (const prompt of prompts) {
|
|
2090
|
+
if (!promptToServers.has(prompt.name)) promptToServers.set(prompt.name, []);
|
|
2091
|
+
promptToServers.get(prompt.name).push(client.serverName);
|
|
2092
|
+
}
|
|
2093
|
+
} catch (error) {
|
|
2094
|
+
console.error(`Failed to list prompts from ${client.serverName}:`, error);
|
|
2095
|
+
serverPromptsMap.set(client.serverName, []);
|
|
2096
|
+
}
|
|
2097
|
+
}));
|
|
2098
|
+
const aggregatedPrompts = [];
|
|
2099
|
+
for (const client of clients) {
|
|
2100
|
+
const prompts = serverPromptsMap.get(client.serverName) || [];
|
|
2101
|
+
for (const prompt of prompts) {
|
|
2102
|
+
const hasClash = (promptToServers.get(prompt.name) || []).length > 1;
|
|
2103
|
+
aggregatedPrompts.push({
|
|
2104
|
+
name: hasClash ? `${client.serverName}__${prompt.name}` : prompt.name,
|
|
2105
|
+
description: prompt.description,
|
|
2106
|
+
arguments: prompt.arguments
|
|
2107
|
+
});
|
|
2108
|
+
}
|
|
2109
|
+
}
|
|
2110
|
+
return { prompts: aggregatedPrompts };
|
|
2111
|
+
});
|
|
2112
|
+
server.setRequestHandler(__modelcontextprotocol_sdk_types_js.GetPromptRequestSchema, async (request) => {
|
|
2113
|
+
const { name, arguments: args } = request.params;
|
|
2114
|
+
const clients = clientManager.getAllClients();
|
|
2115
|
+
const { serverName, actualToolName: actualPromptName } = parseToolName(name);
|
|
2116
|
+
if (serverName) {
|
|
2117
|
+
const client$1 = clientManager.getClient(serverName);
|
|
2118
|
+
if (!client$1) throw new Error(`Server not found: ${serverName}`);
|
|
2119
|
+
return await client$1.getPrompt(actualPromptName, args);
|
|
2120
|
+
}
|
|
2121
|
+
const serversWithPrompt = [];
|
|
2122
|
+
await Promise.all(clients.map(async (client$1) => {
|
|
2123
|
+
try {
|
|
2124
|
+
if ((await client$1.listPrompts()).some((p) => p.name === name)) serversWithPrompt.push(client$1.serverName);
|
|
2125
|
+
} catch (error) {
|
|
2126
|
+
console.error(`Failed to list prompts from ${client$1.serverName}:`, error);
|
|
2127
|
+
}
|
|
2128
|
+
}));
|
|
2129
|
+
if (serversWithPrompt.length === 0) throw new Error(`Prompt not found: ${name}`);
|
|
2130
|
+
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.`);
|
|
2131
|
+
const client = clientManager.getClient(serversWithPrompt[0]);
|
|
2132
|
+
if (!client) throw new Error(`Server not found: ${serversWithPrompt[0]}`);
|
|
2133
|
+
return await client.getPrompt(name, args);
|
|
2134
|
+
});
|
|
1382
2135
|
return server;
|
|
1383
2136
|
}
|
|
1384
2137
|
|
|
@@ -1746,6 +2499,12 @@ Object.defineProperty(exports, 'McpClientManagerService', {
|
|
|
1746
2499
|
return McpClientManagerService;
|
|
1747
2500
|
}
|
|
1748
2501
|
});
|
|
2502
|
+
Object.defineProperty(exports, 'SkillService', {
|
|
2503
|
+
enumerable: true,
|
|
2504
|
+
get: function () {
|
|
2505
|
+
return SkillService;
|
|
2506
|
+
}
|
|
2507
|
+
});
|
|
1749
2508
|
Object.defineProperty(exports, 'SseTransportHandler', {
|
|
1750
2509
|
enumerable: true,
|
|
1751
2510
|
get: function () {
|