@letta-ai/letta-agent-sdk 0.5.6 → 0.5.7
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 +28 -11
- package/dist/app-server-session.d.ts +3 -0
- package/dist/app-server-session.d.ts.map +1 -1
- package/dist/client-entry.js +88 -16
- package/dist/client-entry.js.map +7 -7
- package/dist/cloud-session.d.ts +3 -0
- package/dist/cloud-session.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +19103 -17021
- package/dist/index.js.map +11 -124
- package/dist/mcp-runtime.d.ts +6 -4
- package/dist/mcp-runtime.d.ts.map +1 -1
- package/dist/mcp.d.ts +5 -5
- package/dist/mcp.d.ts.map +1 -1
- package/dist/remote-client-session-core.d.ts +1 -0
- package/dist/remote-client-session-core.d.ts.map +1 -1
- package/dist/types.d.ts +26 -17
- package/dist/types.d.ts.map +1 -1
- package/dist/validation.d.ts.map +1 -1
- package/package.json +2 -3
- package/src/app-server-session.ts +32 -7
- package/src/cloud-session.ts +31 -7
- package/src/index.ts +4 -1
- package/src/mcp-runtime.ts +25 -6
- package/src/mcp.ts +102 -92
- package/src/remote-client-session-core.ts +5 -0
- package/src/types.ts +33 -18
- package/src/validation.ts +31 -0
package/src/mcp.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
2
1
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
type ConnectedMcpServer,
|
|
3
|
+
connectMcpServer,
|
|
4
|
+
type McpServerConfig as LettaMcpServerConfig,
|
|
5
|
+
type McpToolDefinition,
|
|
6
|
+
} from "@letta-ai/letta-code/mcp-client";
|
|
6
7
|
import type {
|
|
7
8
|
ConnectMcpServersOptions,
|
|
8
9
|
McpToolBridge,
|
|
@@ -11,6 +12,7 @@ import type {
|
|
|
11
12
|
AgentToolResultContent,
|
|
12
13
|
AnyAgentTool,
|
|
13
14
|
McpServerConfig,
|
|
15
|
+
McpServers,
|
|
14
16
|
} from "./types.js";
|
|
15
17
|
|
|
16
18
|
const EMPTY_BRIDGE: McpToolBridge = {
|
|
@@ -23,98 +25,119 @@ const CLIENT_INFO = {
|
|
|
23
25
|
version: "1",
|
|
24
26
|
};
|
|
25
27
|
|
|
28
|
+
type NamedMcpServer = {
|
|
29
|
+
name: string;
|
|
30
|
+
config: McpServerConfig;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
type ConnectionResult =
|
|
34
|
+
| {
|
|
35
|
+
server: NamedMcpServer;
|
|
36
|
+
connection: ConnectedMcpServer;
|
|
37
|
+
}
|
|
38
|
+
| {
|
|
39
|
+
server: NamedMcpServer;
|
|
40
|
+
error: unknown;
|
|
41
|
+
};
|
|
42
|
+
|
|
26
43
|
/**
|
|
27
|
-
* Connect
|
|
28
|
-
* external-tool protocol. A broken
|
|
29
|
-
*
|
|
44
|
+
* Connect session-scoped MCP servers through Letta Code's transport-neutral
|
|
45
|
+
* client and expose their tools through the external-tool protocol. A broken
|
|
46
|
+
* server is reported but cannot prevent healthy servers from loading.
|
|
30
47
|
*/
|
|
31
48
|
export async function connectMcpServers(
|
|
32
|
-
servers:
|
|
49
|
+
servers: McpServers | undefined,
|
|
33
50
|
options: ConnectMcpServersOptions = {},
|
|
34
51
|
): Promise<McpToolBridge> {
|
|
35
|
-
|
|
52
|
+
const namedServers = Object.entries(servers ?? {}).map(([name, config]) => ({
|
|
53
|
+
name,
|
|
54
|
+
config,
|
|
55
|
+
}));
|
|
56
|
+
if (namedServers.length === 0) return EMPTY_BRIDGE;
|
|
36
57
|
|
|
37
58
|
const log = options.log ?? ((message: string) => console.error(message));
|
|
38
|
-
const
|
|
59
|
+
const results = await Promise.all(
|
|
60
|
+
namedServers.map(async (server): Promise<ConnectionResult> => {
|
|
61
|
+
try {
|
|
62
|
+
const connection = await connectMcpServer(
|
|
63
|
+
toLettaMcpServerConfig(server, options.cwd),
|
|
64
|
+
{
|
|
65
|
+
clientInfo: CLIENT_INFO,
|
|
66
|
+
stderr: "inherit",
|
|
67
|
+
},
|
|
68
|
+
);
|
|
69
|
+
return { server, connection };
|
|
70
|
+
} catch (error) {
|
|
71
|
+
return { server, error };
|
|
72
|
+
}
|
|
73
|
+
}),
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
const connections: ConnectedMcpServer[] = [];
|
|
39
77
|
const tools: AnyAgentTool[] = [];
|
|
40
78
|
const taken = new Set(options.reservedToolNames ?? []);
|
|
41
79
|
|
|
42
|
-
for (const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
await client.connect(
|
|
47
|
-
new StdioClientTransport({
|
|
48
|
-
command: server.command,
|
|
49
|
-
args: server.args ?? [],
|
|
50
|
-
env: {
|
|
51
|
-
...getDefaultEnvironment(),
|
|
52
|
-
...normalizeEnvironment(server.env),
|
|
53
|
-
},
|
|
54
|
-
cwd: server.cwd ?? options.cwd,
|
|
55
|
-
stderr: "inherit",
|
|
56
|
-
}),
|
|
80
|
+
for (const result of results) {
|
|
81
|
+
if ("error" in result) {
|
|
82
|
+
log(
|
|
83
|
+
`MCP server "${result.server.name}" unavailable: ${errorMessage(result.error)}`,
|
|
57
84
|
);
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
58
87
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
taken,
|
|
65
|
-
);
|
|
66
|
-
tools.push(
|
|
67
|
-
bridgeTool(client, server.name, tool, name),
|
|
68
|
-
);
|
|
69
|
-
}
|
|
70
|
-
log(
|
|
71
|
-
`MCP server "${server.name}" connected (${listed.tools.length} tool${listed.tools.length === 1 ? "" : "s"})`,
|
|
88
|
+
connections.push(result.connection);
|
|
89
|
+
for (const tool of result.connection.tools) {
|
|
90
|
+
const name = uniqueName(
|
|
91
|
+
`mcp__${sanitize(result.server.name)}__${sanitize(tool.name)}`,
|
|
92
|
+
taken,
|
|
72
93
|
);
|
|
73
|
-
|
|
74
|
-
if (client && !clients.includes(client)) {
|
|
75
|
-
await client.close().catch(() => undefined);
|
|
76
|
-
}
|
|
77
|
-
log(`MCP server "${server.name}" unavailable: ${String(error)}`);
|
|
94
|
+
tools.push(bridgeTool(result.connection, result.server.name, tool, name));
|
|
78
95
|
}
|
|
96
|
+
log(
|
|
97
|
+
`MCP server "${result.server.name}" connected (${result.connection.tools.length} tool${result.connection.tools.length === 1 ? "" : "s"})`,
|
|
98
|
+
);
|
|
79
99
|
}
|
|
80
100
|
|
|
81
|
-
if (clients.length === 0) return EMPTY_BRIDGE;
|
|
82
|
-
|
|
83
101
|
let closed = false;
|
|
84
102
|
return {
|
|
85
103
|
tools,
|
|
86
104
|
close: async () => {
|
|
87
105
|
if (closed) return;
|
|
88
106
|
closed = true;
|
|
89
|
-
await Promise.
|
|
90
|
-
|
|
107
|
+
await Promise.allSettled(
|
|
108
|
+
connections.map((connection) => connection.close()),
|
|
91
109
|
);
|
|
92
110
|
},
|
|
93
111
|
};
|
|
94
112
|
}
|
|
95
113
|
|
|
96
|
-
function
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
114
|
+
function toLettaMcpServerConfig(
|
|
115
|
+
server: NamedMcpServer,
|
|
116
|
+
sessionCwd: string | undefined,
|
|
117
|
+
): LettaMcpServerConfig {
|
|
118
|
+
const config = server.config;
|
|
119
|
+
if (config.type === "http" || config.type === "sse") {
|
|
120
|
+
return {
|
|
121
|
+
name: server.name,
|
|
122
|
+
transport: config.type,
|
|
123
|
+
url: config.url,
|
|
124
|
+
...(config.headers ? { headers: config.headers } : {}),
|
|
125
|
+
};
|
|
105
126
|
}
|
|
106
|
-
return
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
127
|
+
return {
|
|
128
|
+
name: server.name,
|
|
129
|
+
transport: "stdio",
|
|
130
|
+
command: config.command,
|
|
131
|
+
args: config.args ?? [],
|
|
132
|
+
...(config.env ? { env: config.env } : {}),
|
|
133
|
+
...(config.cwd ?? sessionCwd
|
|
134
|
+
? { cwd: config.cwd ?? sessionCwd }
|
|
135
|
+
: {}),
|
|
136
|
+
};
|
|
114
137
|
}
|
|
115
138
|
|
|
116
139
|
function bridgeTool(
|
|
117
|
-
|
|
140
|
+
connection: ConnectedMcpServer,
|
|
118
141
|
serverName: string,
|
|
119
142
|
tool: McpToolDefinition,
|
|
120
143
|
name: string,
|
|
@@ -126,39 +149,22 @@ function bridgeTool(
|
|
|
126
149
|
tool.description && tool.description.trim().length > 0
|
|
127
150
|
? tool.description
|
|
128
151
|
: `The ${tool.name} tool from the ${serverName} MCP server.`,
|
|
129
|
-
parameters:
|
|
152
|
+
parameters: tool.inputSchema,
|
|
130
153
|
execute: async (_toolCallId, args, signal) => {
|
|
131
|
-
const result = await
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
arguments: isRecord(args) ? args : {},
|
|
135
|
-
},
|
|
136
|
-
undefined,
|
|
154
|
+
const result = await connection.callTool(
|
|
155
|
+
tool.name,
|
|
156
|
+
isRecord(args) ? args : {},
|
|
137
157
|
signal ? { signal } : undefined,
|
|
138
158
|
);
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
.map((item) => item.text ?? "")
|
|
144
|
-
.filter(Boolean)
|
|
145
|
-
.join("\n") || `${tool.name} failed`,
|
|
146
|
-
);
|
|
147
|
-
}
|
|
148
|
-
return { content };
|
|
159
|
+
return {
|
|
160
|
+
content: toToolResultContent(result.content),
|
|
161
|
+
...(result.isError === true ? { isError: true } : {}),
|
|
162
|
+
};
|
|
149
163
|
},
|
|
150
164
|
};
|
|
151
165
|
}
|
|
152
166
|
|
|
153
|
-
function
|
|
154
|
-
if (isRecord(inputSchema) && inputSchema.type === "object") {
|
|
155
|
-
return inputSchema;
|
|
156
|
-
}
|
|
157
|
-
return { type: "object", properties: {} };
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
function toToolResultContent(content: unknown): AgentToolResultContent[] {
|
|
161
|
-
if (!Array.isArray(content)) return [];
|
|
167
|
+
function toToolResultContent(content: unknown[]): AgentToolResultContent[] {
|
|
162
168
|
const mapped: AgentToolResultContent[] = [];
|
|
163
169
|
for (const block of content) {
|
|
164
170
|
if (!isRecord(block)) continue;
|
|
@@ -184,7 +190,7 @@ function toToolResultContent(content: unknown): AgentToolResultContent[] {
|
|
|
184
190
|
}
|
|
185
191
|
|
|
186
192
|
function sanitize(name: string): string {
|
|
187
|
-
return name.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
193
|
+
return name.replace(/[^a-zA-Z0-9_-]/g, "_") || "tool";
|
|
188
194
|
}
|
|
189
195
|
|
|
190
196
|
function uniqueName(name: string, taken: Set<string>): string {
|
|
@@ -198,6 +204,10 @@ function uniqueName(name: string, taken: Set<string>): string {
|
|
|
198
204
|
return candidate;
|
|
199
205
|
}
|
|
200
206
|
|
|
207
|
+
function errorMessage(error: unknown): string {
|
|
208
|
+
return error instanceof Error ? error.message : String(error);
|
|
209
|
+
}
|
|
210
|
+
|
|
201
211
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
202
212
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
203
213
|
}
|
|
@@ -572,6 +572,7 @@ export abstract class RemoteClientSessionCore implements LettaCodeSession {
|
|
|
572
572
|
|
|
573
573
|
async [Symbol.asyncDispose](): Promise<void> {
|
|
574
574
|
this.close();
|
|
575
|
+
await this.onCoreDisposed();
|
|
575
576
|
}
|
|
576
577
|
|
|
577
578
|
async changeDeviceState(
|
|
@@ -634,6 +635,10 @@ export abstract class RemoteClientSessionCore implements LettaCodeSession {
|
|
|
634
635
|
// Optional hook for subclass-owned resources outside the controller.
|
|
635
636
|
}
|
|
636
637
|
|
|
638
|
+
protected async onCoreDisposed(): Promise<void> {
|
|
639
|
+
// Optional hook for awaiting asynchronous cleanup started by onCoreClose().
|
|
640
|
+
}
|
|
641
|
+
|
|
637
642
|
protected currentOptions(): LettaCodeClientSessionOptions | CreateAgentOptions {
|
|
638
643
|
return this.mode.options;
|
|
639
644
|
}
|
package/src/types.ts
CHANGED
|
@@ -227,6 +227,8 @@ export interface AgentToolResultContent {
|
|
|
227
227
|
*/
|
|
228
228
|
export interface AgentToolResult<T> {
|
|
229
229
|
content: AgentToolResultContent[];
|
|
230
|
+
/** Whether the tool completed with a model-visible error result. */
|
|
231
|
+
isError?: boolean;
|
|
230
232
|
details?: T;
|
|
231
233
|
}
|
|
232
234
|
|
|
@@ -266,27 +268,39 @@ export interface AgentTool<TParams, TResult> {
|
|
|
266
268
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
267
269
|
export type AnyAgentTool = AgentTool<any, unknown>;
|
|
268
270
|
|
|
269
|
-
/**
|
|
270
|
-
export interface
|
|
271
|
-
|
|
272
|
-
value: string;
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
/**
|
|
276
|
-
* A stdio MCP server whose tools should be exposed to a Letta Code session.
|
|
277
|
-
*
|
|
278
|
-
* The environment accepts either the object form commonly used by MCP clients
|
|
279
|
-
* or the name/value array used by ACP clients such as Zed.
|
|
280
|
-
*/
|
|
281
|
-
export interface McpServerConfig {
|
|
282
|
-
name: string;
|
|
271
|
+
/** A local MCP process connected over stdin/stdout. */
|
|
272
|
+
export interface McpStdioServerConfig {
|
|
273
|
+
type?: "stdio";
|
|
283
274
|
command: string;
|
|
284
275
|
args?: string[];
|
|
285
|
-
env?: Record<string, string
|
|
276
|
+
env?: Record<string, string>;
|
|
286
277
|
/** Override the session cwd for this server process. */
|
|
287
278
|
cwd?: string;
|
|
288
279
|
}
|
|
289
280
|
|
|
281
|
+
/** A remote MCP server using Streamable HTTP. */
|
|
282
|
+
export interface McpHttpServerConfig {
|
|
283
|
+
type: "http";
|
|
284
|
+
url: string;
|
|
285
|
+
headers?: Record<string, string>;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/** A remote MCP server using the legacy SSE transport. */
|
|
289
|
+
export interface McpSseServerConfig {
|
|
290
|
+
type: "sse";
|
|
291
|
+
url: string;
|
|
292
|
+
headers?: Record<string, string>;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/** One MCP server definition. The key in {@link McpServers} supplies its name. */
|
|
296
|
+
export type McpServerConfig =
|
|
297
|
+
| McpStdioServerConfig
|
|
298
|
+
| McpHttpServerConfig
|
|
299
|
+
| McpSseServerConfig;
|
|
300
|
+
|
|
301
|
+
/** MCP servers keyed by the name used in `mcp__<server>__<tool>`. */
|
|
302
|
+
export type McpServers = Record<string, McpServerConfig>;
|
|
303
|
+
|
|
290
304
|
// ═══════════════════════════════════════════════════════════════
|
|
291
305
|
// TOP-LEVEL CLIENT TYPES
|
|
292
306
|
// ═══════════════════════════════════════════════════════════════
|
|
@@ -668,10 +682,11 @@ export interface CreateSessionOptions {
|
|
|
668
682
|
tools?: AnyAgentTool[];
|
|
669
683
|
|
|
670
684
|
/**
|
|
671
|
-
*
|
|
672
|
-
*
|
|
685
|
+
* Session-scoped MCP servers keyed by server name. Stdio processes and
|
|
686
|
+
* remote HTTP/SSE connections run in the Node SDK process and are exposed
|
|
687
|
+
* through Letta Code's external-tool protocol.
|
|
673
688
|
*/
|
|
674
|
-
mcpServers?:
|
|
689
|
+
mcpServers?: McpServers;
|
|
675
690
|
|
|
676
691
|
/**
|
|
677
692
|
* Max automatic approval-conflict recovery attempts per runTurn() call.
|
package/src/validation.ts
CHANGED
|
@@ -8,6 +8,7 @@ import type {
|
|
|
8
8
|
CreateSessionOptions,
|
|
9
9
|
CreateAgentOptions,
|
|
10
10
|
MemoryItem,
|
|
11
|
+
McpServers,
|
|
11
12
|
CreateBlock,
|
|
12
13
|
SystemPromptPreset,
|
|
13
14
|
SkillSource,
|
|
@@ -173,11 +174,41 @@ function validateReasoningEffort(value: unknown): void {
|
|
|
173
174
|
}
|
|
174
175
|
}
|
|
175
176
|
|
|
177
|
+
function validateMcpServers(servers: McpServers | undefined): void {
|
|
178
|
+
if (servers === undefined) return;
|
|
179
|
+
if (!servers || typeof servers !== "object" || Array.isArray(servers)) {
|
|
180
|
+
throw new Error("Invalid mcpServers. Expected an object keyed by server name.");
|
|
181
|
+
}
|
|
182
|
+
for (const [name, config] of Object.entries(servers)) {
|
|
183
|
+
if (name.length === 0) {
|
|
184
|
+
throw new Error("Invalid mcpServers. Server names must be non-empty.");
|
|
185
|
+
}
|
|
186
|
+
if (!config || typeof config !== "object" || Array.isArray(config)) {
|
|
187
|
+
throw new Error(`Invalid MCP server '${name}'. Expected a configuration object.`);
|
|
188
|
+
}
|
|
189
|
+
if (config.type === "http" || config.type === "sse") {
|
|
190
|
+
if (typeof config.url !== "string" || config.url.length === 0) {
|
|
191
|
+
throw new Error(`Invalid MCP server '${name}'. Expected a non-empty url.`);
|
|
192
|
+
}
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
if (config.type !== undefined && config.type !== "stdio") {
|
|
196
|
+
throw new Error(
|
|
197
|
+
`Invalid MCP server '${name}' type '${String(config.type)}'. Valid values: stdio, http, sse.`,
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
if (typeof config.command !== "string" || config.command.length === 0) {
|
|
201
|
+
throw new Error(`Invalid MCP server '${name}'. Expected a non-empty command.`);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
176
206
|
/**
|
|
177
207
|
* Validate CreateSessionOptions (used by createSession and resumeSession).
|
|
178
208
|
*/
|
|
179
209
|
export function validateCreateSessionOptions(options: CreateSessionOptions): void {
|
|
180
210
|
validateSkillSources(options.skillSources);
|
|
211
|
+
validateMcpServers(options.mcpServers);
|
|
181
212
|
validateReasoningEffort(options.reasoningEffort);
|
|
182
213
|
validateDreamingOptions(options.dreaming);
|
|
183
214
|
validateApprovalRecoveryOptions(options);
|