opencode-lazy-loader 1.0.0 → 1.0.2
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 +81 -3
- package/dist/__tests__/normalize-command.test.d.ts +2 -0
- package/dist/__tests__/normalize-command.test.d.ts.map +1 -0
- package/dist/__tests__/normalize-command.test.js +187 -0
- package/dist/__tests__/normalize-command.test.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +63 -0
- package/dist/index.js.map +1 -0
- package/dist/skill-loader.d.ts +27 -0
- package/dist/skill-loader.d.ts.map +1 -0
- package/dist/skill-loader.js +197 -0
- package/dist/skill-loader.js.map +1 -0
- package/dist/skill-mcp-manager.d.ts +26 -0
- package/dist/skill-mcp-manager.d.ts.map +1 -0
- package/dist/skill-mcp-manager.js +279 -0
- package/dist/skill-mcp-manager.js.map +1 -0
- package/dist/tools/skill-mcp.d.ts +13 -0
- package/dist/tools/skill-mcp.d.ts.map +1 -0
- package/dist/tools/skill-mcp.js +162 -0
- package/dist/tools/skill-mcp.js.map +1 -0
- package/dist/tools/skill.d.ts +13 -0
- package/dist/tools/skill.d.ts.map +1 -0
- package/dist/tools/skill.js +160 -0
- package/dist/tools/skill.js.map +1 -0
- package/dist/types.d.ts +88 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/env-vars.d.ts +18 -0
- package/dist/utils/env-vars.d.ts.map +1 -0
- package/dist/utils/env-vars.js +100 -0
- package/dist/utils/env-vars.js.map +1 -0
- package/dist/utils/frontmatter.d.ts +10 -0
- package/dist/utils/frontmatter.d.ts.map +1 -0
- package/dist/utils/frontmatter.js +44 -0
- package/dist/utils/frontmatter.js.map +1 -0
- package/package.json +18 -5
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skill-mcp-manager.d.ts","sourceRoot":"","sources":["../src/skill-mcp-manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAA;AAElE,OAAO,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAU5E,MAAM,WAAW,eAAe;IAC9B,iBAAiB,CAAC,IAAI,EAAE,aAAa,EAAE,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IAChF,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACnD,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IAC9B,SAAS,CAAC,IAAI,EAAE,aAAa,EAAE,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,CAAA;IACvE,aAAa,CAAC,IAAI,EAAE,aAAa,EAAE,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,CAAA;IAC3E,WAAW,CAAC,IAAI,EAAE,aAAa,EAAE,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,CAAA;IACzE,QAAQ,CACN,IAAI,EAAE,aAAa,EACnB,OAAO,EAAE,UAAU,EACnB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,OAAO,CAAC,OAAO,CAAC,CAAA;IACnB,YAAY,CAAC,IAAI,EAAE,aAAa,EAAE,OAAO,EAAE,UAAU,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IACrF,SAAS,CACP,IAAI,EAAE,aAAa,EACnB,OAAO,EAAE,UAAU,EACnB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC3B,OAAO,CAAC,OAAO,CAAC,CAAA;IACnB,mBAAmB,IAAI,MAAM,EAAE,CAAA;IAC/B,WAAW,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAA;CAC1C;AAED;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,IAAI,eAAe,CAoUvD"}
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
2
|
+
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
|
3
|
+
import { expandEnvVarsInObject, createCleanMcpEnvironment, normalizeCommand, normalizeEnv } from './utils/env-vars.js';
|
|
4
|
+
/**
|
|
5
|
+
* Create a SkillMcpManager instance
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Connection pooling keyed by session/skill/server
|
|
9
|
+
* - Lazy connection creation
|
|
10
|
+
* - Idle cleanup after 5 minutes
|
|
11
|
+
* - Session/process cleanup
|
|
12
|
+
*/
|
|
13
|
+
export function createSkillMcpManager() {
|
|
14
|
+
const clients = new Map();
|
|
15
|
+
const pendingConnections = new Map();
|
|
16
|
+
let cleanupRegistered = false;
|
|
17
|
+
let cleanupInterval = null;
|
|
18
|
+
const IDLE_TIMEOUT = 5 * 60 * 1000; // 5 minutes
|
|
19
|
+
const getClientKey = (info) => {
|
|
20
|
+
return `${info.sessionID}:${info.skillName}:${info.serverName}`;
|
|
21
|
+
};
|
|
22
|
+
const registerProcessCleanup = () => {
|
|
23
|
+
if (cleanupRegistered) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
cleanupRegistered = true;
|
|
27
|
+
const cleanup = async () => {
|
|
28
|
+
for (const [, managed] of clients) {
|
|
29
|
+
try {
|
|
30
|
+
await managed.client.close();
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
// Ignore cleanup errors
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
await managed.transport.close();
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
// Ignore cleanup errors
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
clients.clear();
|
|
43
|
+
pendingConnections.clear();
|
|
44
|
+
};
|
|
45
|
+
process.on('SIGINT', async () => {
|
|
46
|
+
await cleanup();
|
|
47
|
+
process.exit(0);
|
|
48
|
+
});
|
|
49
|
+
process.on('SIGTERM', async () => {
|
|
50
|
+
await cleanup();
|
|
51
|
+
process.exit(0);
|
|
52
|
+
});
|
|
53
|
+
if (process.platform === 'win32') {
|
|
54
|
+
process.on('SIGBREAK', async () => {
|
|
55
|
+
await cleanup();
|
|
56
|
+
process.exit(0);
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
const createClient = async (info, config) => {
|
|
61
|
+
const key = getClientKey(info);
|
|
62
|
+
if (!config.command) {
|
|
63
|
+
throw new Error(`MCP server "${info.serverName}" is missing required 'command' field.\n\n` +
|
|
64
|
+
`The MCP configuration in skill "${info.skillName}" must specify a command to execute.\n\n` +
|
|
65
|
+
`Supported formats:\n` +
|
|
66
|
+
` Format A (array): command: ["npx", "-y", "@some/mcp-server"]\n` +
|
|
67
|
+
` Format B (string): command: "npx", args: ["-y", "@some/mcp-server"]`);
|
|
68
|
+
}
|
|
69
|
+
const { command, args } = normalizeCommand(config);
|
|
70
|
+
const { env } = normalizeEnv(config);
|
|
71
|
+
const mergedEnv = createCleanMcpEnvironment(env);
|
|
72
|
+
registerProcessCleanup();
|
|
73
|
+
const transport = new StdioClientTransport({
|
|
74
|
+
command,
|
|
75
|
+
args,
|
|
76
|
+
env: mergedEnv,
|
|
77
|
+
stderr: 'ignore'
|
|
78
|
+
});
|
|
79
|
+
const client = new Client({ name: `skill-mcp-${info.skillName}-${info.serverName}`, version: '1.0.0' }, { capabilities: {} });
|
|
80
|
+
try {
|
|
81
|
+
await client.connect(transport);
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
try {
|
|
85
|
+
await transport.close();
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
// Ignore cleanup errors
|
|
89
|
+
}
|
|
90
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
91
|
+
throw new Error(`Failed to connect to MCP server "${info.serverName}".\n\n` +
|
|
92
|
+
`Command: ${command} ${args.join(' ')}\n` +
|
|
93
|
+
`Reason: ${errorMessage}\n\n` +
|
|
94
|
+
`Hints:\n` +
|
|
95
|
+
` - Ensure the command is installed and available in PATH\n` +
|
|
96
|
+
` - Check if the MCP server package exists\n` +
|
|
97
|
+
` - Verify the args are correct for this server`);
|
|
98
|
+
}
|
|
99
|
+
clients.set(key, {
|
|
100
|
+
client,
|
|
101
|
+
transport,
|
|
102
|
+
skillName: info.skillName,
|
|
103
|
+
lastUsedAt: Date.now()
|
|
104
|
+
});
|
|
105
|
+
startCleanupTimer();
|
|
106
|
+
return client;
|
|
107
|
+
};
|
|
108
|
+
const getOrCreateClient = async (info, config) => {
|
|
109
|
+
const key = getClientKey(info);
|
|
110
|
+
const existing = clients.get(key);
|
|
111
|
+
if (existing) {
|
|
112
|
+
existing.lastUsedAt = Date.now();
|
|
113
|
+
return existing.client;
|
|
114
|
+
}
|
|
115
|
+
const pending = pendingConnections.get(key);
|
|
116
|
+
if (pending) {
|
|
117
|
+
return pending;
|
|
118
|
+
}
|
|
119
|
+
const expandedConfig = expandEnvVarsInObject(config);
|
|
120
|
+
const connectionPromise = createClient(info, expandedConfig);
|
|
121
|
+
pendingConnections.set(key, connectionPromise);
|
|
122
|
+
try {
|
|
123
|
+
const client = await connectionPromise;
|
|
124
|
+
return client;
|
|
125
|
+
}
|
|
126
|
+
finally {
|
|
127
|
+
pendingConnections.delete(key);
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
const disconnectSession = async (sessionID) => {
|
|
131
|
+
for (const [key, managed] of clients.entries()) {
|
|
132
|
+
if (key.startsWith(`${sessionID}:`)) {
|
|
133
|
+
clients.delete(key);
|
|
134
|
+
try {
|
|
135
|
+
await managed.client.close();
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
// Ignore cleanup errors
|
|
139
|
+
}
|
|
140
|
+
try {
|
|
141
|
+
await managed.transport.close();
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
// Ignore cleanup errors
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
const disconnectAll = async () => {
|
|
150
|
+
stopCleanupTimer();
|
|
151
|
+
const allClients = Array.from(clients.values());
|
|
152
|
+
clients.clear();
|
|
153
|
+
for (const managed of allClients) {
|
|
154
|
+
try {
|
|
155
|
+
await managed.client.close();
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
// Ignore cleanup errors
|
|
159
|
+
}
|
|
160
|
+
try {
|
|
161
|
+
await managed.transport.close();
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
// Ignore cleanup errors
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
const startCleanupTimer = () => {
|
|
169
|
+
if (cleanupInterval) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
cleanupInterval = setInterval(() => {
|
|
173
|
+
cleanupIdleClients();
|
|
174
|
+
}, 60000);
|
|
175
|
+
cleanupInterval.unref();
|
|
176
|
+
};
|
|
177
|
+
const stopCleanupTimer = () => {
|
|
178
|
+
if (cleanupInterval) {
|
|
179
|
+
clearInterval(cleanupInterval);
|
|
180
|
+
cleanupInterval = null;
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
const cleanupIdleClients = async () => {
|
|
184
|
+
const now = Date.now();
|
|
185
|
+
for (const [key, managed] of clients) {
|
|
186
|
+
if (now - managed.lastUsedAt > IDLE_TIMEOUT) {
|
|
187
|
+
clients.delete(key);
|
|
188
|
+
try {
|
|
189
|
+
await managed.client.close();
|
|
190
|
+
}
|
|
191
|
+
catch {
|
|
192
|
+
// Ignore cleanup errors
|
|
193
|
+
}
|
|
194
|
+
try {
|
|
195
|
+
await managed.transport.close();
|
|
196
|
+
}
|
|
197
|
+
catch {
|
|
198
|
+
// Ignore cleanup errors
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
const getOrCreateClientWithRetry = async (info, config) => {
|
|
204
|
+
try {
|
|
205
|
+
return await getOrCreateClient(info, config);
|
|
206
|
+
}
|
|
207
|
+
catch (error) {
|
|
208
|
+
const key = getClientKey(info);
|
|
209
|
+
const existing = clients.get(key);
|
|
210
|
+
if (existing) {
|
|
211
|
+
clients.delete(key);
|
|
212
|
+
try {
|
|
213
|
+
await existing.client.close();
|
|
214
|
+
}
|
|
215
|
+
catch {
|
|
216
|
+
// Ignore
|
|
217
|
+
}
|
|
218
|
+
try {
|
|
219
|
+
await existing.transport.close();
|
|
220
|
+
}
|
|
221
|
+
catch {
|
|
222
|
+
// Ignore
|
|
223
|
+
}
|
|
224
|
+
return await getOrCreateClient(info, config);
|
|
225
|
+
}
|
|
226
|
+
throw error;
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
const listTools = async (info, context) => {
|
|
230
|
+
const client = await getOrCreateClientWithRetry(info, context.config);
|
|
231
|
+
const result = await client.listTools();
|
|
232
|
+
return result.tools;
|
|
233
|
+
};
|
|
234
|
+
const listResources = async (info, context) => {
|
|
235
|
+
const client = await getOrCreateClientWithRetry(info, context.config);
|
|
236
|
+
const result = await client.listResources();
|
|
237
|
+
return result.resources;
|
|
238
|
+
};
|
|
239
|
+
const listPrompts = async (info, context) => {
|
|
240
|
+
const client = await getOrCreateClientWithRetry(info, context.config);
|
|
241
|
+
const result = await client.listPrompts();
|
|
242
|
+
return result.prompts;
|
|
243
|
+
};
|
|
244
|
+
const callTool = async (info, context, name, args) => {
|
|
245
|
+
const client = await getOrCreateClientWithRetry(info, context.config);
|
|
246
|
+
const result = await client.callTool({ name, arguments: args });
|
|
247
|
+
return result.content;
|
|
248
|
+
};
|
|
249
|
+
const readResource = async (info, context, uri) => {
|
|
250
|
+
const client = await getOrCreateClientWithRetry(info, context.config);
|
|
251
|
+
const result = await client.readResource({ uri });
|
|
252
|
+
return result.contents;
|
|
253
|
+
};
|
|
254
|
+
const getPrompt = async (info, context, name, args) => {
|
|
255
|
+
const client = await getOrCreateClientWithRetry(info, context.config);
|
|
256
|
+
const result = await client.getPrompt({ name, arguments: args });
|
|
257
|
+
return result.messages;
|
|
258
|
+
};
|
|
259
|
+
const getConnectedServers = () => {
|
|
260
|
+
return Array.from(clients.keys());
|
|
261
|
+
};
|
|
262
|
+
const isConnected = (info) => {
|
|
263
|
+
return clients.has(getClientKey(info));
|
|
264
|
+
};
|
|
265
|
+
return {
|
|
266
|
+
getOrCreateClient,
|
|
267
|
+
disconnectSession,
|
|
268
|
+
disconnectAll,
|
|
269
|
+
listTools,
|
|
270
|
+
listResources,
|
|
271
|
+
listPrompts,
|
|
272
|
+
callTool,
|
|
273
|
+
readResource,
|
|
274
|
+
getPrompt,
|
|
275
|
+
getConnectedServers,
|
|
276
|
+
isConnected
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
//# sourceMappingURL=skill-mcp-manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skill-mcp-manager.js","sourceRoot":"","sources":["../src/skill-mcp-manager.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type ToolDefinition } from '@opencode-ai/plugin/tool';
|
|
2
|
+
import type { LoadedSkill } from '../types.js';
|
|
3
|
+
import type { SkillMcpManager } from '../skill-mcp-manager.js';
|
|
4
|
+
export interface CreateSkillMcpToolOptions {
|
|
5
|
+
manager: SkillMcpManager;
|
|
6
|
+
getLoadedSkills: () => LoadedSkill[];
|
|
7
|
+
getSessionID: () => string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Create the skill_mcp tool
|
|
11
|
+
*/
|
|
12
|
+
export declare function createSkillMcpTool(options: CreateSkillMcpToolOptions): ToolDefinition;
|
|
13
|
+
//# sourceMappingURL=skill-mcp.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skill-mcp.d.ts","sourceRoot":"","sources":["../../src/tools/skill-mcp.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,cAAc,EAAE,MAAM,0BAA0B,CAAA;AACpE,OAAO,KAAK,EAAE,WAAW,EAAmB,MAAM,aAAa,CAAA;AAC/D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAA;AAqI9D,MAAM,WAAW,yBAAyB;IACxC,OAAO,EAAE,eAAe,CAAA;IACxB,eAAe,EAAE,MAAM,WAAW,EAAE,CAAA;IACpC,YAAY,EAAE,MAAM,MAAM,CAAA;CAC3B;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,yBAAyB,GAAG,cAAc,CAoErF"}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { tool } from '@opencode-ai/plugin/tool';
|
|
2
|
+
const SKILL_MCP_DESCRIPTION = `Invoke MCP server operations from skill-embedded MCPs. Requires mcp_name plus exactly one of: tool_name, resource_name, or prompt_name.`;
|
|
3
|
+
/**
|
|
4
|
+
* Validate that exactly one operation parameter is provided
|
|
5
|
+
*/
|
|
6
|
+
function validateOperationParams(args) {
|
|
7
|
+
const operations = [];
|
|
8
|
+
if (args.tool_name) {
|
|
9
|
+
operations.push({ type: 'tool', name: args.tool_name });
|
|
10
|
+
}
|
|
11
|
+
if (args.resource_name) {
|
|
12
|
+
operations.push({ type: 'resource', name: args.resource_name });
|
|
13
|
+
}
|
|
14
|
+
if (args.prompt_name) {
|
|
15
|
+
operations.push({ type: 'prompt', name: args.prompt_name });
|
|
16
|
+
}
|
|
17
|
+
if (operations.length === 0) {
|
|
18
|
+
throw new Error(`Missing operation. Exactly one of tool_name, resource_name, or prompt_name must be specified.\n\n` +
|
|
19
|
+
`Examples:\n` +
|
|
20
|
+
` skill_mcp(mcp_name="sqlite", tool_name="query", arguments='{"sql": "SELECT * FROM users"}')\n` +
|
|
21
|
+
` skill_mcp(mcp_name="memory", resource_name="memory://notes")\n` +
|
|
22
|
+
` skill_mcp(mcp_name="helper", prompt_name="summarize", arguments='{"text": "..."}')`);
|
|
23
|
+
}
|
|
24
|
+
if (operations.length > 1) {
|
|
25
|
+
const provided = [
|
|
26
|
+
args.tool_name && `tool_name="${args.tool_name}"`,
|
|
27
|
+
args.resource_name && `resource_name="${args.resource_name}"`,
|
|
28
|
+
args.prompt_name && `prompt_name="${args.prompt_name}"`
|
|
29
|
+
].filter(Boolean).join(', ');
|
|
30
|
+
throw new Error(`Multiple operations specified. Exactly one of tool_name, resource_name, or prompt_name must be provided.\n\n` +
|
|
31
|
+
`Received: ${provided}\n\n` +
|
|
32
|
+
`Use separate calls for each operation.`);
|
|
33
|
+
}
|
|
34
|
+
return operations[0];
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Find an MCP server configuration by name across all skills
|
|
38
|
+
*/
|
|
39
|
+
function findMcpServer(mcpName, skills) {
|
|
40
|
+
for (const skill of skills) {
|
|
41
|
+
if (skill.mcpConfig && mcpName in skill.mcpConfig) {
|
|
42
|
+
return { skill, config: skill.mcpConfig[mcpName] };
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Format available MCPs for error message
|
|
49
|
+
*/
|
|
50
|
+
function formatAvailableMcps(skills) {
|
|
51
|
+
const mcps = [];
|
|
52
|
+
for (const skill of skills) {
|
|
53
|
+
if (skill.mcpConfig) {
|
|
54
|
+
for (const serverName of Object.keys(skill.mcpConfig)) {
|
|
55
|
+
mcps.push(` - "${serverName}" from skill "${skill.name}"`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return mcps.length > 0 ? mcps.join('\n') : ' (none found)';
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Parse JSON arguments string
|
|
63
|
+
*/
|
|
64
|
+
function parseArguments(argsJson) {
|
|
65
|
+
if (!argsJson) {
|
|
66
|
+
return {};
|
|
67
|
+
}
|
|
68
|
+
try {
|
|
69
|
+
const parsed = JSON.parse(argsJson);
|
|
70
|
+
if (typeof parsed !== 'object' || parsed === null) {
|
|
71
|
+
throw new Error('Arguments must be a JSON object');
|
|
72
|
+
}
|
|
73
|
+
return parsed;
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
77
|
+
throw new Error(`Invalid arguments JSON: ${errorMessage}\n\n` +
|
|
78
|
+
`Expected a valid JSON object, e.g.: '{"key": "value"}'\n` +
|
|
79
|
+
`Received: ${argsJson}`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Apply grep filter to output
|
|
84
|
+
*/
|
|
85
|
+
function applyGrepFilter(output, pattern) {
|
|
86
|
+
if (!pattern) {
|
|
87
|
+
return output;
|
|
88
|
+
}
|
|
89
|
+
try {
|
|
90
|
+
const regex = new RegExp(pattern, 'i');
|
|
91
|
+
const lines = output.split('\n');
|
|
92
|
+
const filtered = lines.filter((line) => regex.test(line));
|
|
93
|
+
return filtered.length > 0
|
|
94
|
+
? filtered.join('\n')
|
|
95
|
+
: `[grep] No lines matched pattern: ${pattern}`;
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
return output;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Create the skill_mcp tool
|
|
103
|
+
*/
|
|
104
|
+
export function createSkillMcpTool(options) {
|
|
105
|
+
const { manager, getLoadedSkills, getSessionID } = options;
|
|
106
|
+
return tool({
|
|
107
|
+
description: SKILL_MCP_DESCRIPTION,
|
|
108
|
+
args: {
|
|
109
|
+
mcp_name: tool.schema.string().describe('Name of the MCP server from skill config'),
|
|
110
|
+
tool_name: tool.schema.string().optional().describe('MCP tool to call'),
|
|
111
|
+
resource_name: tool.schema.string().optional().describe('MCP resource URI to read'),
|
|
112
|
+
prompt_name: tool.schema.string().optional().describe('MCP prompt to get'),
|
|
113
|
+
arguments: tool.schema.string().optional().describe('JSON string of arguments'),
|
|
114
|
+
grep: tool.schema.string().optional().describe('Regex pattern to filter output lines (only matching lines returned)')
|
|
115
|
+
},
|
|
116
|
+
async execute(args) {
|
|
117
|
+
const operation = validateOperationParams(args);
|
|
118
|
+
const skills = getLoadedSkills();
|
|
119
|
+
const found = findMcpServer(args.mcp_name, skills);
|
|
120
|
+
if (!found) {
|
|
121
|
+
throw new Error(`MCP server "${args.mcp_name}" not found.\n\n` +
|
|
122
|
+
`Available MCP servers in loaded skills:\n` +
|
|
123
|
+
formatAvailableMcps(skills) + '\n\n' +
|
|
124
|
+
`Hint: Load the skill first using the 'skill' tool, then call skill_mcp.`);
|
|
125
|
+
}
|
|
126
|
+
const info = {
|
|
127
|
+
serverName: args.mcp_name,
|
|
128
|
+
skillName: found.skill.name,
|
|
129
|
+
sessionID: getSessionID()
|
|
130
|
+
};
|
|
131
|
+
const context = {
|
|
132
|
+
config: found.config,
|
|
133
|
+
skillName: found.skill.name
|
|
134
|
+
};
|
|
135
|
+
const parsedArgs = parseArguments(args.arguments);
|
|
136
|
+
let output;
|
|
137
|
+
switch (operation.type) {
|
|
138
|
+
case 'tool': {
|
|
139
|
+
const result = await manager.callTool(info, context, operation.name, parsedArgs);
|
|
140
|
+
output = JSON.stringify(result, null, 2);
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
case 'resource': {
|
|
144
|
+
const result = await manager.readResource(info, context, operation.name);
|
|
145
|
+
output = JSON.stringify(result, null, 2);
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
case 'prompt': {
|
|
149
|
+
const stringArgs = {};
|
|
150
|
+
for (const [key, value] of Object.entries(parsedArgs)) {
|
|
151
|
+
stringArgs[key] = String(value);
|
|
152
|
+
}
|
|
153
|
+
const result = await manager.getPrompt(info, context, operation.name, stringArgs);
|
|
154
|
+
output = JSON.stringify(result, null, 2);
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return applyGrepFilter(output, args.grep);
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
//# sourceMappingURL=skill-mcp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skill-mcp.js","sourceRoot":"","sources":["../../src/tools/skill-mcp.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type ToolDefinition } from '@opencode-ai/plugin/tool';
|
|
2
|
+
import type { LoadedSkill } from '../types.js';
|
|
3
|
+
import type { SkillMcpManager } from '../skill-mcp-manager.js';
|
|
4
|
+
export interface CreateSkillToolOptions {
|
|
5
|
+
skills: LoadedSkill[];
|
|
6
|
+
mcpManager?: SkillMcpManager;
|
|
7
|
+
getSessionID?: () => string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Create the skill tool
|
|
11
|
+
*/
|
|
12
|
+
export declare function createSkillTool(options: CreateSkillToolOptions): ToolDefinition;
|
|
13
|
+
//# sourceMappingURL=skill.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skill.d.ts","sourceRoot":"","sources":["../../src/tools/skill.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,cAAc,EAAE,MAAM,0BAA0B,CAAA;AAEpE,OAAO,KAAK,EAAE,WAAW,EAAmB,MAAM,aAAa,CAAA;AAC/D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAA;AAqK9D,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,WAAW,EAAE,CAAA;IACrB,UAAU,CAAC,EAAE,eAAe,CAAA;IAC5B,YAAY,CAAC,EAAE,MAAM,MAAM,CAAA;CAC5B;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,sBAAsB,GAAG,cAAc,CAgD/E"}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { tool } from '@opencode-ai/plugin/tool';
|
|
2
|
+
import { dirname } from 'path';
|
|
3
|
+
import { parseFrontmatter } from '../utils/frontmatter.js';
|
|
4
|
+
const TOOL_DESCRIPTION_NO_SKILLS = 'Load a skill to get detailed instructions for a specific task. No skills are currently available.';
|
|
5
|
+
const TOOL_DESCRIPTION_PREFIX = `Load a skill to get detailed instructions for a specific task.
|
|
6
|
+
|
|
7
|
+
Skills provide specialized knowledge and step-by-step guidance.
|
|
8
|
+
Use this when a task matches an available skill's description.`;
|
|
9
|
+
/**
|
|
10
|
+
* Convert loaded skill to info for display
|
|
11
|
+
*/
|
|
12
|
+
function loadedSkillToInfo(skill) {
|
|
13
|
+
return {
|
|
14
|
+
name: skill.name,
|
|
15
|
+
description: skill.definition.description || '',
|
|
16
|
+
scope: skill.scope
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Format skills as XML for tool description
|
|
21
|
+
*/
|
|
22
|
+
function formatSkillsXml(skills) {
|
|
23
|
+
if (skills.length === 0)
|
|
24
|
+
return '';
|
|
25
|
+
const skillsXml = skills.map((skill) => {
|
|
26
|
+
return [
|
|
27
|
+
' <skill>',
|
|
28
|
+
` <name>${skill.name}</name>`,
|
|
29
|
+
` <description>${skill.description}</description>`,
|
|
30
|
+
' </skill>'
|
|
31
|
+
].join('\n');
|
|
32
|
+
}).join('\n');
|
|
33
|
+
return `
|
|
34
|
+
|
|
35
|
+
<available_skills>
|
|
36
|
+
${skillsXml}
|
|
37
|
+
</available_skills>`;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Extract skill body content
|
|
41
|
+
*/
|
|
42
|
+
async function extractSkillBody(skill) {
|
|
43
|
+
if (skill.lazyContent) {
|
|
44
|
+
const fullTemplate = await skill.lazyContent.load();
|
|
45
|
+
const templateMatch = fullTemplate.match(/<skill-instruction>([\s\S]*?)<\/skill-instruction>/);
|
|
46
|
+
return templateMatch ? templateMatch[1].trim() : fullTemplate;
|
|
47
|
+
}
|
|
48
|
+
if (skill.path) {
|
|
49
|
+
const { readFileSync } = await import('fs');
|
|
50
|
+
const content = readFileSync(skill.path, 'utf-8');
|
|
51
|
+
const { body } = parseFrontmatter(content);
|
|
52
|
+
return body.trim();
|
|
53
|
+
}
|
|
54
|
+
const templateMatch = skill.definition.template?.match(/<skill-instruction>([\s\S]*?)<\/skill-instruction>/);
|
|
55
|
+
return templateMatch ? templateMatch[1].trim() : skill.definition.template || '';
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Format MCP capabilities for display
|
|
59
|
+
*/
|
|
60
|
+
async function formatMcpCapabilities(skill, manager, sessionID) {
|
|
61
|
+
if (!skill.mcpConfig || Object.keys(skill.mcpConfig).length === 0) {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
const sections = ['', '## Available MCP Servers', ''];
|
|
65
|
+
for (const [serverName, config] of Object.entries(skill.mcpConfig)) {
|
|
66
|
+
const info = {
|
|
67
|
+
serverName,
|
|
68
|
+
skillName: skill.name,
|
|
69
|
+
sessionID
|
|
70
|
+
};
|
|
71
|
+
const context = {
|
|
72
|
+
config: config,
|
|
73
|
+
skillName: skill.name
|
|
74
|
+
};
|
|
75
|
+
sections.push(`### ${serverName}`);
|
|
76
|
+
sections.push('');
|
|
77
|
+
try {
|
|
78
|
+
const [tools, resources, prompts] = await Promise.all([
|
|
79
|
+
manager.listTools(info, context).catch(() => []),
|
|
80
|
+
manager.listResources(info, context).catch(() => []),
|
|
81
|
+
manager.listPrompts(info, context).catch(() => [])
|
|
82
|
+
]);
|
|
83
|
+
if (tools.length > 0) {
|
|
84
|
+
sections.push('**Tools:**');
|
|
85
|
+
sections.push('');
|
|
86
|
+
for (const t of tools) {
|
|
87
|
+
sections.push(`#### \`${t.name}\``);
|
|
88
|
+
if (t.description) {
|
|
89
|
+
sections.push(t.description);
|
|
90
|
+
}
|
|
91
|
+
sections.push('');
|
|
92
|
+
sections.push('**inputSchema:**');
|
|
93
|
+
sections.push('```json');
|
|
94
|
+
sections.push(JSON.stringify(t.inputSchema, null, 2));
|
|
95
|
+
sections.push('```');
|
|
96
|
+
sections.push('');
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (resources.length > 0) {
|
|
100
|
+
sections.push(`**Resources**: ${resources.map((r) => r.uri).join(', ')}`);
|
|
101
|
+
}
|
|
102
|
+
if (prompts.length > 0) {
|
|
103
|
+
sections.push(`**Prompts**: ${prompts.map((p) => p.name).join(', ')}`);
|
|
104
|
+
}
|
|
105
|
+
if (tools.length === 0 && resources.length === 0 && prompts.length === 0) {
|
|
106
|
+
sections.push('*No capabilities discovered*');
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
111
|
+
sections.push(`*Failed to connect: ${errorMessage.split('\n')[0]}*`);
|
|
112
|
+
}
|
|
113
|
+
sections.push('');
|
|
114
|
+
sections.push(`Use \`skill_mcp\` tool with \`mcp_name="${serverName}"\` to invoke.`);
|
|
115
|
+
sections.push('');
|
|
116
|
+
}
|
|
117
|
+
return sections.join('\n');
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Create the skill tool
|
|
121
|
+
*/
|
|
122
|
+
export function createSkillTool(options) {
|
|
123
|
+
const { skills, mcpManager, getSessionID } = options;
|
|
124
|
+
// Build description with available skills
|
|
125
|
+
const skillInfos = skills.map(loadedSkillToInfo);
|
|
126
|
+
const description = skillInfos.length === 0
|
|
127
|
+
? TOOL_DESCRIPTION_NO_SKILLS
|
|
128
|
+
: TOOL_DESCRIPTION_PREFIX + formatSkillsXml(skillInfos);
|
|
129
|
+
return tool({
|
|
130
|
+
description,
|
|
131
|
+
args: {
|
|
132
|
+
name: tool.schema.string().describe("The skill identifier from available_skills (e.g., 'code-review' or 'my-skill')")
|
|
133
|
+
},
|
|
134
|
+
async execute(args) {
|
|
135
|
+
const skill = skills.find((s) => s.name === args.name);
|
|
136
|
+
if (!skill) {
|
|
137
|
+
const available = skills.map((s) => s.name).join(', ');
|
|
138
|
+
throw new Error(`Skill "${args.name}" not found. Available skills: ${available || 'none'}`);
|
|
139
|
+
}
|
|
140
|
+
const body = await extractSkillBody(skill);
|
|
141
|
+
const dir = skill.path ? dirname(skill.path) : skill.resolvedPath || process.cwd();
|
|
142
|
+
const output = [
|
|
143
|
+
`## Skill: ${skill.name}`,
|
|
144
|
+
'',
|
|
145
|
+
`**Base directory**: ${dir}`,
|
|
146
|
+
'',
|
|
147
|
+
body
|
|
148
|
+
];
|
|
149
|
+
// Add MCP capabilities if manager available
|
|
150
|
+
if (mcpManager && getSessionID && skill.mcpConfig) {
|
|
151
|
+
const mcpInfo = await formatMcpCapabilities(skill, mcpManager, getSessionID());
|
|
152
|
+
if (mcpInfo) {
|
|
153
|
+
output.push(mcpInfo);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return output.join('\n');
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
//# sourceMappingURL=skill.js.map
|