@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.
@@ -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
- const keycode = parseInt(csiUMatch[1], 10);
67
- const modifier = parseInt(csiUMatch[2] || '1', 10) - 1;
68
- const event = csiUMatch[3] ? parseInt(csiUMatch[3], 10) : 1;
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
-