@letta-ai/letta-code 0.14.7 → 0.14.9
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/letta.js +1205 -649
- package/package.json +2 -2
- package/skills/converting-mcps-to-skills/SKILL.md +171 -0
- package/skills/converting-mcps-to-skills/references/skill-templates.md +141 -0
- package/skills/converting-mcps-to-skills/scripts/mcp-http.ts +429 -0
- package/skills/converting-mcps-to-skills/scripts/mcp-stdio.ts +359 -0
- package/skills/converting-mcps-to-skills/scripts/package.json +13 -0
- package/vendor/ink/build/hooks/use-input.js +23 -11
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
#!/usr/bin/env npx tsx
|
|
2
|
+
/**
|
|
3
|
+
* MCP stdio Client - Connect to any MCP server over stdio
|
|
4
|
+
*
|
|
5
|
+
* NOTE: Requires npm install in this directory first:
|
|
6
|
+
* cd <this-directory> && npm install
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* npx tsx mcp-stdio.ts "<command>" <action> [args]
|
|
10
|
+
*
|
|
11
|
+
* Commands:
|
|
12
|
+
* list-tools List available tools
|
|
13
|
+
* list-resources List available resources
|
|
14
|
+
* info <tool> Show tool schema
|
|
15
|
+
* call <tool> '<json>' Call a tool with JSON arguments
|
|
16
|
+
*
|
|
17
|
+
* Options:
|
|
18
|
+
* --env "KEY=VALUE" Set environment variable (can be repeated)
|
|
19
|
+
* --cwd <path> Set working directory for server
|
|
20
|
+
*
|
|
21
|
+
* Examples:
|
|
22
|
+
* npx tsx mcp-stdio.ts "node server.js" list-tools
|
|
23
|
+
* npx tsx mcp-stdio.ts "npx -y @modelcontextprotocol/server-filesystem ." list-tools
|
|
24
|
+
* npx tsx mcp-stdio.ts "python server.py" call my_tool '{"arg":"value"}'
|
|
25
|
+
* npx tsx mcp-stdio.ts "node server.js" --env "API_KEY=xxx" list-tools
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
29
|
+
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
30
|
+
|
|
31
|
+
interface ParsedArgs {
|
|
32
|
+
serverCommand: string;
|
|
33
|
+
action: string;
|
|
34
|
+
actionArgs: string[];
|
|
35
|
+
env: Record<string, string>;
|
|
36
|
+
cwd?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function parseArgs(): ParsedArgs {
|
|
40
|
+
const args = process.argv.slice(2);
|
|
41
|
+
const env: Record<string, string> = {};
|
|
42
|
+
let cwd: string | undefined;
|
|
43
|
+
let serverCommand = "";
|
|
44
|
+
let action = "";
|
|
45
|
+
const actionArgs: string[] = [];
|
|
46
|
+
|
|
47
|
+
let i = 0;
|
|
48
|
+
while (i < args.length) {
|
|
49
|
+
const arg = args[i];
|
|
50
|
+
if (!arg) {
|
|
51
|
+
i++;
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (arg === "--env" || arg === "-e") {
|
|
56
|
+
const envValue = args[++i];
|
|
57
|
+
if (envValue) {
|
|
58
|
+
const eqIndex = envValue.indexOf("=");
|
|
59
|
+
if (eqIndex > 0) {
|
|
60
|
+
const key = envValue.slice(0, eqIndex);
|
|
61
|
+
const value = envValue.slice(eqIndex + 1);
|
|
62
|
+
env[key] = value;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
} else if (arg === "--cwd") {
|
|
66
|
+
cwd = args[++i];
|
|
67
|
+
} else if (arg === "--help" || arg === "-h") {
|
|
68
|
+
printUsage();
|
|
69
|
+
process.exit(0);
|
|
70
|
+
} else if (!serverCommand) {
|
|
71
|
+
serverCommand = arg;
|
|
72
|
+
} else if (!action) {
|
|
73
|
+
action = arg;
|
|
74
|
+
} else {
|
|
75
|
+
actionArgs.push(arg);
|
|
76
|
+
}
|
|
77
|
+
i++;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return { serverCommand, action, actionArgs, env, cwd };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function parseCommand(commandStr: string): { command: string; args: string[] } {
|
|
84
|
+
// Simple parsing - split on spaces, respecting quotes
|
|
85
|
+
const parts: string[] = [];
|
|
86
|
+
let current = "";
|
|
87
|
+
let inQuote = false;
|
|
88
|
+
let quoteChar = "";
|
|
89
|
+
|
|
90
|
+
for (const char of commandStr) {
|
|
91
|
+
if ((char === '"' || char === "'") && !inQuote) {
|
|
92
|
+
inQuote = true;
|
|
93
|
+
quoteChar = char;
|
|
94
|
+
} else if (char === quoteChar && inQuote) {
|
|
95
|
+
inQuote = false;
|
|
96
|
+
quoteChar = "";
|
|
97
|
+
} else if (char === " " && !inQuote) {
|
|
98
|
+
if (current) {
|
|
99
|
+
parts.push(current);
|
|
100
|
+
current = "";
|
|
101
|
+
}
|
|
102
|
+
} else {
|
|
103
|
+
current += char;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (current) {
|
|
107
|
+
parts.push(current);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
command: parts[0] || "",
|
|
112
|
+
args: parts.slice(1),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
let client: Client | null = null;
|
|
117
|
+
let transport: StdioClientTransport | null = null;
|
|
118
|
+
|
|
119
|
+
async function connect(
|
|
120
|
+
serverCommand: string,
|
|
121
|
+
env: Record<string, string>,
|
|
122
|
+
cwd?: string,
|
|
123
|
+
): Promise<Client> {
|
|
124
|
+
const { command, args } = parseCommand(serverCommand);
|
|
125
|
+
|
|
126
|
+
if (!command) {
|
|
127
|
+
throw new Error("No command specified");
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Merge with process.env
|
|
131
|
+
const mergedEnv: Record<string, string> = {};
|
|
132
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
133
|
+
if (value !== undefined) {
|
|
134
|
+
mergedEnv[key] = value;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
Object.assign(mergedEnv, env);
|
|
138
|
+
|
|
139
|
+
transport = new StdioClientTransport({
|
|
140
|
+
command,
|
|
141
|
+
args,
|
|
142
|
+
env: mergedEnv,
|
|
143
|
+
cwd,
|
|
144
|
+
stderr: "pipe",
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// Forward stderr for debugging
|
|
148
|
+
if (transport.stderr) {
|
|
149
|
+
transport.stderr.on("data", (chunk: Buffer) => {
|
|
150
|
+
process.stderr.write(`[server] ${chunk.toString()}`);
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
client = new Client(
|
|
155
|
+
{
|
|
156
|
+
name: "mcp-stdio-cli",
|
|
157
|
+
version: "1.0.0",
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
capabilities: {},
|
|
161
|
+
},
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
await client.connect(transport);
|
|
165
|
+
return client;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
async function cleanup(): Promise<void> {
|
|
169
|
+
if (client) {
|
|
170
|
+
try {
|
|
171
|
+
await client.close();
|
|
172
|
+
} catch {
|
|
173
|
+
// Ignore cleanup errors
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async function listTools(client: Client): Promise<void> {
|
|
179
|
+
const result = await client.listTools();
|
|
180
|
+
|
|
181
|
+
console.log("Available tools:\n");
|
|
182
|
+
for (const tool of result.tools) {
|
|
183
|
+
console.log(` ${tool.name}`);
|
|
184
|
+
if (tool.description) {
|
|
185
|
+
console.log(` ${tool.description}\n`);
|
|
186
|
+
} else {
|
|
187
|
+
console.log();
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
console.log(`\nTotal: ${result.tools.length} tools`);
|
|
192
|
+
console.log("\nUse 'call <tool> <json-args>' to invoke a tool");
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
async function listResources(client: Client): Promise<void> {
|
|
196
|
+
const result = await client.listResources();
|
|
197
|
+
|
|
198
|
+
if (!result.resources || result.resources.length === 0) {
|
|
199
|
+
console.log("No resources available.");
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
console.log("Available resources:\n");
|
|
204
|
+
for (const resource of result.resources) {
|
|
205
|
+
console.log(` ${resource.uri}`);
|
|
206
|
+
console.log(` ${resource.name}`);
|
|
207
|
+
if (resource.description) {
|
|
208
|
+
console.log(` ${resource.description}`);
|
|
209
|
+
}
|
|
210
|
+
console.log();
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async function getToolSchema(client: Client, toolName: string): Promise<void> {
|
|
215
|
+
const result = await client.listTools();
|
|
216
|
+
|
|
217
|
+
const tool = result.tools.find((t) => t.name === toolName);
|
|
218
|
+
if (!tool) {
|
|
219
|
+
console.error(`Tool not found: ${toolName}`);
|
|
220
|
+
console.error(
|
|
221
|
+
`Available tools: ${result.tools.map((t) => t.name).join(", ")}`,
|
|
222
|
+
);
|
|
223
|
+
process.exit(1);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
console.log(`Tool: ${tool.name}\n`);
|
|
227
|
+
if (tool.description) {
|
|
228
|
+
console.log(`Description: ${tool.description}\n`);
|
|
229
|
+
}
|
|
230
|
+
console.log("Input Schema:");
|
|
231
|
+
console.log(JSON.stringify(tool.inputSchema, null, 2));
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
async function callTool(
|
|
235
|
+
client: Client,
|
|
236
|
+
toolName: string,
|
|
237
|
+
argsJson: string,
|
|
238
|
+
): Promise<void> {
|
|
239
|
+
let args: Record<string, unknown>;
|
|
240
|
+
try {
|
|
241
|
+
args = JSON.parse(argsJson || "{}");
|
|
242
|
+
} catch {
|
|
243
|
+
console.error(`Invalid JSON: ${argsJson}`);
|
|
244
|
+
process.exit(1);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const result = await client.callTool({
|
|
248
|
+
name: toolName,
|
|
249
|
+
arguments: args,
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
console.log(JSON.stringify(result, null, 2));
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function printUsage(): void {
|
|
256
|
+
console.log(`MCP stdio Client - Connect to any MCP server over stdio
|
|
257
|
+
|
|
258
|
+
NOTE: Requires npm install in this directory first:
|
|
259
|
+
cd <this-directory> && npm install
|
|
260
|
+
|
|
261
|
+
Usage: npx tsx mcp-stdio.ts "<command>" [options] <action> [args]
|
|
262
|
+
|
|
263
|
+
Actions:
|
|
264
|
+
list-tools List available tools with descriptions
|
|
265
|
+
list-resources List available resources
|
|
266
|
+
info <tool> Show tool schema/parameters
|
|
267
|
+
call <tool> '<json>' Call a tool with JSON arguments
|
|
268
|
+
|
|
269
|
+
Options:
|
|
270
|
+
--env, -e "KEY=VALUE" Set environment variable (repeatable)
|
|
271
|
+
--cwd <path> Set working directory for server
|
|
272
|
+
--help, -h Show this help
|
|
273
|
+
|
|
274
|
+
Examples:
|
|
275
|
+
# List tools from filesystem server
|
|
276
|
+
npx tsx mcp-stdio.ts "npx -y @modelcontextprotocol/server-filesystem ." list-tools
|
|
277
|
+
|
|
278
|
+
# With environment variable
|
|
279
|
+
npx tsx mcp-stdio.ts "node server.js" --env "API_KEY=xxx" list-tools
|
|
280
|
+
|
|
281
|
+
# Call a tool
|
|
282
|
+
npx tsx mcp-stdio.ts "python server.py" call read_file '{"path":"./README.md"}'
|
|
283
|
+
`);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
async function main(): Promise<void> {
|
|
287
|
+
const { serverCommand, action, actionArgs, env, cwd } = parseArgs();
|
|
288
|
+
|
|
289
|
+
if (!serverCommand) {
|
|
290
|
+
console.error("Error: Server command is required\n");
|
|
291
|
+
printUsage();
|
|
292
|
+
process.exit(1);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (!action) {
|
|
296
|
+
console.error("Error: Action is required\n");
|
|
297
|
+
printUsage();
|
|
298
|
+
process.exit(1);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Handle process exit
|
|
302
|
+
process.on("SIGINT", async () => {
|
|
303
|
+
await cleanup();
|
|
304
|
+
process.exit(0);
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
process.on("SIGTERM", async () => {
|
|
308
|
+
await cleanup();
|
|
309
|
+
process.exit(0);
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
try {
|
|
313
|
+
const connectedClient = await connect(serverCommand, env, cwd);
|
|
314
|
+
|
|
315
|
+
switch (action) {
|
|
316
|
+
case "list-tools":
|
|
317
|
+
await listTools(connectedClient);
|
|
318
|
+
break;
|
|
319
|
+
|
|
320
|
+
case "list-resources":
|
|
321
|
+
await listResources(connectedClient);
|
|
322
|
+
break;
|
|
323
|
+
|
|
324
|
+
case "info": {
|
|
325
|
+
const [toolName] = actionArgs;
|
|
326
|
+
if (!toolName) {
|
|
327
|
+
console.error("Error: Tool name required");
|
|
328
|
+
console.error("Usage: info <tool>");
|
|
329
|
+
process.exit(1);
|
|
330
|
+
}
|
|
331
|
+
await getToolSchema(connectedClient, toolName);
|
|
332
|
+
break;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
case "call": {
|
|
336
|
+
const [toolName, argsJson] = actionArgs;
|
|
337
|
+
if (!toolName) {
|
|
338
|
+
console.error("Error: Tool name required");
|
|
339
|
+
console.error("Usage: call <tool> '<json-args>'");
|
|
340
|
+
process.exit(1);
|
|
341
|
+
}
|
|
342
|
+
await callTool(connectedClient, toolName, argsJson || "{}");
|
|
343
|
+
break;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
default:
|
|
347
|
+
console.error(`Unknown action: ${action}\n`);
|
|
348
|
+
printUsage();
|
|
349
|
+
process.exit(1);
|
|
350
|
+
}
|
|
351
|
+
} catch (error) {
|
|
352
|
+
console.error("Error:", error instanceof Error ? error.message : error);
|
|
353
|
+
process.exit(1);
|
|
354
|
+
} finally {
|
|
355
|
+
await cleanup();
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
main();
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mcp-client-scripts",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "MCP client scripts for converting-mcps-to-skills",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"http": "npx tsx mcp-http.ts",
|
|
8
|
+
"stdio": "npx tsx mcp-stdio.ts"
|
|
9
|
+
},
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"@modelcontextprotocol/sdk": "^1.25.0"
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -60,18 +60,32 @@ const useInput = (inputHandler, options = {}) => {
|
|
|
60
60
|
// or with event type: ESC [ keycode ; modifier : event u
|
|
61
61
|
// parseKeypress doesn't handle this, so we parse it ourselves as a fallback
|
|
62
62
|
if (!keypress.name && typeof data === 'string') {
|
|
63
|
+
let keycode = null;
|
|
64
|
+
let modifier = 0;
|
|
65
|
+
let event = 1;
|
|
66
|
+
|
|
63
67
|
// Match CSI u: ESC [ keycode ; modifier u OR ESC [ keycode ; modifier : event u
|
|
64
68
|
const csiUMatch = data.match(/^\x1b\[(\d+)(?:;(\d+))?(?::(\d+))?u$/);
|
|
65
69
|
if (csiUMatch) {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
+
keycode = parseInt(csiUMatch[1], 10);
|
|
71
|
+
modifier = parseInt(csiUMatch[2] || '1', 10) - 1;
|
|
72
|
+
event = csiUMatch[3] ? parseInt(csiUMatch[3], 10) : 1;
|
|
73
|
+
} else {
|
|
74
|
+
// modifyOtherKeys format: CSI 27 ; modifier ; key ~
|
|
75
|
+
// Treat it like CSI u (key + 'u')
|
|
76
|
+
const modifyOtherKeysMatch = data.match(/^\x1b\[27;(\d+);(\d+)~$/);
|
|
77
|
+
if (modifyOtherKeysMatch) {
|
|
78
|
+
modifier = parseInt(modifyOtherKeysMatch[1], 10) - 1;
|
|
79
|
+
keycode = parseInt(modifyOtherKeysMatch[2], 10);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (keycode !== null) {
|
|
70
84
|
// Ignore key release events (event=3)
|
|
71
85
|
if (event === 3) {
|
|
72
86
|
return;
|
|
73
87
|
}
|
|
74
|
-
|
|
88
|
+
|
|
75
89
|
// Map keycodes to names
|
|
76
90
|
const csiUKeyMap = {
|
|
77
91
|
9: 'tab',
|
|
@@ -79,16 +93,16 @@ const useInput = (inputHandler, options = {}) => {
|
|
|
79
93
|
27: 'escape',
|
|
80
94
|
127: 'backspace',
|
|
81
95
|
};
|
|
82
|
-
|
|
96
|
+
|
|
83
97
|
let name = csiUKeyMap[keycode] || '';
|
|
84
|
-
|
|
98
|
+
|
|
85
99
|
// Handle letter keycodes (a-z: 97-122, A-Z: 65-90)
|
|
86
100
|
if (!name && keycode >= 97 && keycode <= 122) {
|
|
87
101
|
name = String.fromCharCode(keycode); // lowercase letter
|
|
88
102
|
} else if (!name && keycode >= 65 && keycode <= 90) {
|
|
89
103
|
name = String.fromCharCode(keycode + 32); // convert to lowercase
|
|
90
104
|
}
|
|
91
|
-
|
|
105
|
+
|
|
92
106
|
if (name) {
|
|
93
107
|
keypress = {
|
|
94
108
|
name,
|
|
@@ -110,7 +124,7 @@ const useInput = (inputHandler, options = {}) => {
|
|
|
110
124
|
rightArrow: keypress.name === 'right',
|
|
111
125
|
pageDown: keypress.name === 'pagedown',
|
|
112
126
|
pageUp: keypress.name === 'pageup',
|
|
113
|
-
return: keypress.name === 'return',
|
|
127
|
+
return: keypress.name === 'return' || keypress.name === 'enter',
|
|
114
128
|
escape: keypress.name === 'escape',
|
|
115
129
|
ctrl: keypress.ctrl,
|
|
116
130
|
shift: keypress.shift,
|
|
@@ -160,5 +174,3 @@ const useInput = (inputHandler, options = {}) => {
|
|
|
160
174
|
};
|
|
161
175
|
|
|
162
176
|
export default useInput;
|
|
163
|
-
|
|
164
|
-
|