@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,429 @@
|
|
|
1
|
+
#!/usr/bin/env npx tsx
|
|
2
|
+
/**
|
|
3
|
+
* MCP HTTP Client - Connect to any MCP server over HTTP
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* npx tsx mcp-http.ts <url> <command> [args]
|
|
7
|
+
*
|
|
8
|
+
* Commands:
|
|
9
|
+
* list-tools List available tools
|
|
10
|
+
* list-resources List available resources
|
|
11
|
+
* call <tool> '<json>' Call a tool with JSON arguments
|
|
12
|
+
*
|
|
13
|
+
* Options:
|
|
14
|
+
* --header "Key: Value" Add HTTP header (can be repeated)
|
|
15
|
+
*
|
|
16
|
+
* Examples:
|
|
17
|
+
* npx tsx mcp-http.ts http://localhost:3001/mcp list-tools
|
|
18
|
+
* npx tsx mcp-http.ts http://localhost:3001/mcp call vault '{"action":"list"}'
|
|
19
|
+
* npx tsx mcp-http.ts http://localhost:3001/mcp --header "Authorization: Bearer KEY" list-tools
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
interface JsonRpcRequest {
|
|
23
|
+
jsonrpc: "2.0";
|
|
24
|
+
method: string;
|
|
25
|
+
params?: object;
|
|
26
|
+
id: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface JsonRpcResponse {
|
|
30
|
+
jsonrpc: "2.0";
|
|
31
|
+
result?: unknown;
|
|
32
|
+
error?: {
|
|
33
|
+
code: number;
|
|
34
|
+
message: string;
|
|
35
|
+
data?: unknown;
|
|
36
|
+
};
|
|
37
|
+
id: number;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
interface ParsedArgs {
|
|
41
|
+
url: string;
|
|
42
|
+
command: string;
|
|
43
|
+
commandArgs: string[];
|
|
44
|
+
headers: Record<string, string>;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function parseArgs(): ParsedArgs {
|
|
48
|
+
const args = process.argv.slice(2);
|
|
49
|
+
const headers: Record<string, string> = {};
|
|
50
|
+
let url = "";
|
|
51
|
+
let command = "";
|
|
52
|
+
const commandArgs: string[] = [];
|
|
53
|
+
|
|
54
|
+
let i = 0;
|
|
55
|
+
while (i < args.length) {
|
|
56
|
+
const arg = args[i];
|
|
57
|
+
if (!arg) {
|
|
58
|
+
i++;
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (arg === "--header" || arg === "-H") {
|
|
63
|
+
const headerValue = args[++i];
|
|
64
|
+
if (headerValue) {
|
|
65
|
+
const colonIndex = headerValue.indexOf(":");
|
|
66
|
+
if (colonIndex > 0) {
|
|
67
|
+
const key = headerValue.slice(0, colonIndex).trim();
|
|
68
|
+
const value = headerValue.slice(colonIndex + 1).trim();
|
|
69
|
+
headers[key] = value;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
} else if (arg === "--help" || arg === "-h") {
|
|
73
|
+
printUsage();
|
|
74
|
+
process.exit(0);
|
|
75
|
+
} else if (!url && arg.startsWith("http")) {
|
|
76
|
+
url = arg;
|
|
77
|
+
} else if (!command) {
|
|
78
|
+
command = arg;
|
|
79
|
+
} else {
|
|
80
|
+
commandArgs.push(arg);
|
|
81
|
+
}
|
|
82
|
+
i++;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return { url, command, commandArgs, headers };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Session state
|
|
89
|
+
let sessionId: string | null = null;
|
|
90
|
+
let initialized = false;
|
|
91
|
+
let requestHeaders: Record<string, string> = {};
|
|
92
|
+
let serverUrl = "";
|
|
93
|
+
|
|
94
|
+
async function rawMcpRequest(
|
|
95
|
+
method: string,
|
|
96
|
+
params?: object,
|
|
97
|
+
): Promise<{ response: JsonRpcResponse; newSessionId?: string }> {
|
|
98
|
+
const request: JsonRpcRequest = {
|
|
99
|
+
jsonrpc: "2.0",
|
|
100
|
+
method,
|
|
101
|
+
params,
|
|
102
|
+
id: Date.now(),
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const headers: Record<string, string> = {
|
|
106
|
+
"Content-Type": "application/json",
|
|
107
|
+
Accept: "application/json, text/event-stream",
|
|
108
|
+
...requestHeaders,
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
if (sessionId) {
|
|
112
|
+
headers["Mcp-Session-Id"] = sessionId;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
const fetchResponse = await fetch(serverUrl, {
|
|
117
|
+
method: "POST",
|
|
118
|
+
headers,
|
|
119
|
+
body: JSON.stringify(request),
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// Capture session ID from response
|
|
123
|
+
const newSessionId =
|
|
124
|
+
fetchResponse.headers.get("Mcp-Session-Id") || undefined;
|
|
125
|
+
|
|
126
|
+
if (!fetchResponse.ok) {
|
|
127
|
+
const text = await fetchResponse.text();
|
|
128
|
+
if (fetchResponse.status === 401) {
|
|
129
|
+
throw new Error(
|
|
130
|
+
`Authentication required.\n` +
|
|
131
|
+
`Add --header "Authorization: Bearer YOUR_KEY" or similar.`,
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Try to parse as JSON-RPC error
|
|
136
|
+
try {
|
|
137
|
+
const errorResponse = JSON.parse(text) as JsonRpcResponse;
|
|
138
|
+
return { response: errorResponse, newSessionId };
|
|
139
|
+
} catch {
|
|
140
|
+
throw new Error(
|
|
141
|
+
`HTTP ${fetchResponse.status}: ${fetchResponse.statusText}\n${text}`,
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const contentType = fetchResponse.headers.get("content-type") || "";
|
|
147
|
+
|
|
148
|
+
// Handle JSON response
|
|
149
|
+
if (contentType.includes("application/json")) {
|
|
150
|
+
const jsonResponse = (await fetchResponse.json()) as JsonRpcResponse;
|
|
151
|
+
return { response: jsonResponse, newSessionId };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Handle SSE stream (simplified - just collect all events)
|
|
155
|
+
if (contentType.includes("text/event-stream")) {
|
|
156
|
+
const text = await fetchResponse.text();
|
|
157
|
+
const dataLines = text
|
|
158
|
+
.split("\n")
|
|
159
|
+
.filter((line) => line.startsWith("data: "))
|
|
160
|
+
.map((line) => line.slice(6));
|
|
161
|
+
|
|
162
|
+
for (let i = dataLines.length - 1; i >= 0; i--) {
|
|
163
|
+
const line = dataLines[i];
|
|
164
|
+
if (!line) continue;
|
|
165
|
+
try {
|
|
166
|
+
const parsed = JSON.parse(line);
|
|
167
|
+
if (parsed.jsonrpc === "2.0") {
|
|
168
|
+
return { response: parsed as JsonRpcResponse, newSessionId };
|
|
169
|
+
}
|
|
170
|
+
} catch {
|
|
171
|
+
// Continue to previous line
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
throw new Error("No valid JSON-RPC response found in SSE stream");
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
throw new Error(`Unexpected content type: ${contentType}`);
|
|
178
|
+
} catch (error) {
|
|
179
|
+
if (error instanceof TypeError && error.message.includes("fetch")) {
|
|
180
|
+
throw new Error(
|
|
181
|
+
`Cannot connect to ${serverUrl}\nIs the MCP server running?`,
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
throw error;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
async function ensureInitialized(): Promise<void> {
|
|
189
|
+
if (initialized) return;
|
|
190
|
+
|
|
191
|
+
const { response, newSessionId } = await rawMcpRequest("initialize", {
|
|
192
|
+
protocolVersion: "2024-11-05",
|
|
193
|
+
capabilities: {},
|
|
194
|
+
clientInfo: {
|
|
195
|
+
name: "mcp-http-cli",
|
|
196
|
+
version: "1.0.0",
|
|
197
|
+
},
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
if (newSessionId) {
|
|
201
|
+
sessionId = newSessionId;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (response.error) {
|
|
205
|
+
throw new Error(`Initialization failed: ${response.error.message}`);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Send initialized notification
|
|
209
|
+
await rawMcpRequest("notifications/initialized", {});
|
|
210
|
+
|
|
211
|
+
initialized = true;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async function mcpRequest(
|
|
215
|
+
method: string,
|
|
216
|
+
params?: object,
|
|
217
|
+
): Promise<JsonRpcResponse> {
|
|
218
|
+
await ensureInitialized();
|
|
219
|
+
|
|
220
|
+
const { response, newSessionId } = await rawMcpRequest(method, params);
|
|
221
|
+
|
|
222
|
+
if (newSessionId) {
|
|
223
|
+
sessionId = newSessionId;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return response;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
async function listTools(): Promise<void> {
|
|
230
|
+
const response = await mcpRequest("tools/list");
|
|
231
|
+
|
|
232
|
+
if (response.error) {
|
|
233
|
+
console.error("Error:", response.error.message);
|
|
234
|
+
process.exit(1);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const result = response.result as {
|
|
238
|
+
tools: Array<{ name: string; description: string; inputSchema: object }>;
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
console.log("Available tools:\n");
|
|
242
|
+
for (const tool of result.tools) {
|
|
243
|
+
console.log(` ${tool.name}`);
|
|
244
|
+
if (tool.description) {
|
|
245
|
+
console.log(` ${tool.description}\n`);
|
|
246
|
+
} else {
|
|
247
|
+
console.log();
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
console.log(`\nTotal: ${result.tools.length} tools`);
|
|
252
|
+
console.log("\nUse 'call <tool> <json-args>' to invoke a tool");
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
async function listResources(): Promise<void> {
|
|
256
|
+
const response = await mcpRequest("resources/list");
|
|
257
|
+
|
|
258
|
+
if (response.error) {
|
|
259
|
+
console.error("Error:", response.error.message);
|
|
260
|
+
process.exit(1);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const result = response.result as {
|
|
264
|
+
resources: Array<{ uri: string; name: string; description?: string }>;
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
if (!result.resources || result.resources.length === 0) {
|
|
268
|
+
console.log("No resources available.");
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
console.log("Available resources:\n");
|
|
273
|
+
for (const resource of result.resources) {
|
|
274
|
+
console.log(` ${resource.uri}`);
|
|
275
|
+
console.log(` ${resource.name}`);
|
|
276
|
+
if (resource.description) {
|
|
277
|
+
console.log(` ${resource.description}`);
|
|
278
|
+
}
|
|
279
|
+
console.log();
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
async function callTool(toolName: string, argsJson: string): Promise<void> {
|
|
284
|
+
let args: object;
|
|
285
|
+
try {
|
|
286
|
+
args = JSON.parse(argsJson || "{}");
|
|
287
|
+
} catch {
|
|
288
|
+
console.error(`Invalid JSON: ${argsJson}`);
|
|
289
|
+
process.exit(1);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const response = await mcpRequest("tools/call", {
|
|
293
|
+
name: toolName,
|
|
294
|
+
arguments: args,
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
if (response.error) {
|
|
298
|
+
console.error("Error:", response.error.message);
|
|
299
|
+
if (response.error.data) {
|
|
300
|
+
console.error("Details:", JSON.stringify(response.error.data, null, 2));
|
|
301
|
+
}
|
|
302
|
+
process.exit(1);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
console.log(JSON.stringify(response.result, null, 2));
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
async function getToolSchema(toolName: string): Promise<void> {
|
|
309
|
+
const response = await mcpRequest("tools/list");
|
|
310
|
+
|
|
311
|
+
if (response.error) {
|
|
312
|
+
console.error("Error:", response.error.message);
|
|
313
|
+
process.exit(1);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const result = response.result as {
|
|
317
|
+
tools: Array<{ name: string; description: string; inputSchema: object }>;
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
const tool = result.tools.find((t) => t.name === toolName);
|
|
321
|
+
if (!tool) {
|
|
322
|
+
console.error(`Tool not found: ${toolName}`);
|
|
323
|
+
console.error(
|
|
324
|
+
`Available tools: ${result.tools.map((t) => t.name).join(", ")}`,
|
|
325
|
+
);
|
|
326
|
+
process.exit(1);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
console.log(`Tool: ${tool.name}\n`);
|
|
330
|
+
if (tool.description) {
|
|
331
|
+
console.log(`Description: ${tool.description}\n`);
|
|
332
|
+
}
|
|
333
|
+
console.log("Input Schema:");
|
|
334
|
+
console.log(JSON.stringify(tool.inputSchema, null, 2));
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function printUsage(): void {
|
|
338
|
+
console.log(`MCP HTTP Client - Connect to any MCP server over HTTP
|
|
339
|
+
|
|
340
|
+
Usage: npx tsx mcp-http.ts <url> [options] <command> [args]
|
|
341
|
+
|
|
342
|
+
Commands:
|
|
343
|
+
list-tools List available tools with descriptions
|
|
344
|
+
list-resources List available resources
|
|
345
|
+
info <tool> Show tool schema/parameters
|
|
346
|
+
call <tool> '<json>' Call a tool with JSON arguments
|
|
347
|
+
|
|
348
|
+
Options:
|
|
349
|
+
--header, -H "K: V" Add HTTP header (repeatable)
|
|
350
|
+
--help, -h Show this help
|
|
351
|
+
|
|
352
|
+
Examples:
|
|
353
|
+
# List tools from a server
|
|
354
|
+
npx tsx mcp-http.ts http://localhost:3001/mcp list-tools
|
|
355
|
+
|
|
356
|
+
# With authentication
|
|
357
|
+
npx tsx mcp-http.ts http://localhost:3001/mcp --header "Authorization: Bearer KEY" list-tools
|
|
358
|
+
|
|
359
|
+
# Get tool schema
|
|
360
|
+
npx tsx mcp-http.ts http://localhost:3001/mcp info vault
|
|
361
|
+
|
|
362
|
+
# Call a tool
|
|
363
|
+
npx tsx mcp-http.ts http://localhost:3001/mcp call vault '{"action":"list"}'
|
|
364
|
+
`);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
async function main(): Promise<void> {
|
|
368
|
+
const { url, command, commandArgs, headers } = parseArgs();
|
|
369
|
+
|
|
370
|
+
if (!url) {
|
|
371
|
+
console.error("Error: URL is required\n");
|
|
372
|
+
printUsage();
|
|
373
|
+
process.exit(1);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if (!command) {
|
|
377
|
+
console.error("Error: Command is required\n");
|
|
378
|
+
printUsage();
|
|
379
|
+
process.exit(1);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// Set globals
|
|
383
|
+
serverUrl = url;
|
|
384
|
+
requestHeaders = headers;
|
|
385
|
+
|
|
386
|
+
try {
|
|
387
|
+
switch (command) {
|
|
388
|
+
case "list-tools":
|
|
389
|
+
await listTools();
|
|
390
|
+
break;
|
|
391
|
+
|
|
392
|
+
case "list-resources":
|
|
393
|
+
await listResources();
|
|
394
|
+
break;
|
|
395
|
+
|
|
396
|
+
case "info": {
|
|
397
|
+
const [toolName] = commandArgs;
|
|
398
|
+
if (!toolName) {
|
|
399
|
+
console.error("Error: Tool name required");
|
|
400
|
+
console.error("Usage: info <tool>");
|
|
401
|
+
process.exit(1);
|
|
402
|
+
}
|
|
403
|
+
await getToolSchema(toolName);
|
|
404
|
+
break;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
case "call": {
|
|
408
|
+
const [toolName, argsJson] = commandArgs;
|
|
409
|
+
if (!toolName) {
|
|
410
|
+
console.error("Error: Tool name required");
|
|
411
|
+
console.error("Usage: call <tool> '<json-args>'");
|
|
412
|
+
process.exit(1);
|
|
413
|
+
}
|
|
414
|
+
await callTool(toolName, argsJson || "{}");
|
|
415
|
+
break;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
default:
|
|
419
|
+
console.error(`Unknown command: ${command}\n`);
|
|
420
|
+
printUsage();
|
|
421
|
+
process.exit(1);
|
|
422
|
+
}
|
|
423
|
+
} catch (error) {
|
|
424
|
+
console.error("Error:", error instanceof Error ? error.message : error);
|
|
425
|
+
process.exit(1);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
main();
|