@letta-ai/letta-agent-sdk 0.5.5 → 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 +43 -8
- package/dist/app-server-session.d.ts +4 -0
- package/dist/app-server-session.d.ts.map +1 -1
- package/dist/client-entry.js +120 -8
- package/dist/client-entry.js.map +8 -7
- package/dist/cloud-session.d.ts +4 -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 +17021 -29
- package/dist/index.js.map +11 -8
- package/dist/mcp-runtime.d.ts +17 -0
- package/dist/mcp-runtime.d.ts.map +1 -0
- package/dist/mcp.d.ts +9 -0
- package/dist/mcp.d.ts.map +1 -0
- 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 +33 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/validation.d.ts.map +1 -1
- package/package.json +3 -2
- package/src/app-server-session.ts +46 -3
- package/src/cloud-session.ts +44 -3
- package/src/index.ts +9 -0
- package/src/mcp-runtime.ts +58 -0
- package/src/mcp.ts +213 -0
- package/src/remote-client-session-core.ts +5 -0
- package/src/types.ts +42 -0
- package/src/validation.ts +31 -0
package/src/mcp.ts
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type ConnectedMcpServer,
|
|
3
|
+
connectMcpServer,
|
|
4
|
+
type McpServerConfig as LettaMcpServerConfig,
|
|
5
|
+
type McpToolDefinition,
|
|
6
|
+
} from "@letta-ai/letta-code/mcp-client";
|
|
7
|
+
import type {
|
|
8
|
+
ConnectMcpServersOptions,
|
|
9
|
+
McpToolBridge,
|
|
10
|
+
} from "./mcp-runtime.js";
|
|
11
|
+
import type {
|
|
12
|
+
AgentToolResultContent,
|
|
13
|
+
AnyAgentTool,
|
|
14
|
+
McpServerConfig,
|
|
15
|
+
McpServers,
|
|
16
|
+
} from "./types.js";
|
|
17
|
+
|
|
18
|
+
const EMPTY_BRIDGE: McpToolBridge = {
|
|
19
|
+
tools: [],
|
|
20
|
+
close: async () => undefined,
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const CLIENT_INFO = {
|
|
24
|
+
name: "@letta-ai/letta-agent-sdk",
|
|
25
|
+
version: "1",
|
|
26
|
+
};
|
|
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
|
+
|
|
43
|
+
/**
|
|
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.
|
|
47
|
+
*/
|
|
48
|
+
export async function connectMcpServers(
|
|
49
|
+
servers: McpServers | undefined,
|
|
50
|
+
options: ConnectMcpServersOptions = {},
|
|
51
|
+
): Promise<McpToolBridge> {
|
|
52
|
+
const namedServers = Object.entries(servers ?? {}).map(([name, config]) => ({
|
|
53
|
+
name,
|
|
54
|
+
config,
|
|
55
|
+
}));
|
|
56
|
+
if (namedServers.length === 0) return EMPTY_BRIDGE;
|
|
57
|
+
|
|
58
|
+
const log = options.log ?? ((message: string) => console.error(message));
|
|
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[] = [];
|
|
77
|
+
const tools: AnyAgentTool[] = [];
|
|
78
|
+
const taken = new Set(options.reservedToolNames ?? []);
|
|
79
|
+
|
|
80
|
+
for (const result of results) {
|
|
81
|
+
if ("error" in result) {
|
|
82
|
+
log(
|
|
83
|
+
`MCP server "${result.server.name}" unavailable: ${errorMessage(result.error)}`,
|
|
84
|
+
);
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
|
|
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,
|
|
93
|
+
);
|
|
94
|
+
tools.push(bridgeTool(result.connection, result.server.name, tool, name));
|
|
95
|
+
}
|
|
96
|
+
log(
|
|
97
|
+
`MCP server "${result.server.name}" connected (${result.connection.tools.length} tool${result.connection.tools.length === 1 ? "" : "s"})`,
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
let closed = false;
|
|
102
|
+
return {
|
|
103
|
+
tools,
|
|
104
|
+
close: async () => {
|
|
105
|
+
if (closed) return;
|
|
106
|
+
closed = true;
|
|
107
|
+
await Promise.allSettled(
|
|
108
|
+
connections.map((connection) => connection.close()),
|
|
109
|
+
);
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
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
|
+
};
|
|
126
|
+
}
|
|
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
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function bridgeTool(
|
|
140
|
+
connection: ConnectedMcpServer,
|
|
141
|
+
serverName: string,
|
|
142
|
+
tool: McpToolDefinition,
|
|
143
|
+
name: string,
|
|
144
|
+
): AnyAgentTool {
|
|
145
|
+
return {
|
|
146
|
+
name,
|
|
147
|
+
label: tool.title ?? tool.name,
|
|
148
|
+
description:
|
|
149
|
+
tool.description && tool.description.trim().length > 0
|
|
150
|
+
? tool.description
|
|
151
|
+
: `The ${tool.name} tool from the ${serverName} MCP server.`,
|
|
152
|
+
parameters: tool.inputSchema,
|
|
153
|
+
execute: async (_toolCallId, args, signal) => {
|
|
154
|
+
const result = await connection.callTool(
|
|
155
|
+
tool.name,
|
|
156
|
+
isRecord(args) ? args : {},
|
|
157
|
+
signal ? { signal } : undefined,
|
|
158
|
+
);
|
|
159
|
+
return {
|
|
160
|
+
content: toToolResultContent(result.content),
|
|
161
|
+
...(result.isError === true ? { isError: true } : {}),
|
|
162
|
+
};
|
|
163
|
+
},
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function toToolResultContent(content: unknown[]): AgentToolResultContent[] {
|
|
168
|
+
const mapped: AgentToolResultContent[] = [];
|
|
169
|
+
for (const block of content) {
|
|
170
|
+
if (!isRecord(block)) continue;
|
|
171
|
+
if (block.type === "text" && typeof block.text === "string") {
|
|
172
|
+
mapped.push({ type: "text", text: block.text });
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
if (
|
|
176
|
+
block.type === "image" &&
|
|
177
|
+
typeof block.data === "string" &&
|
|
178
|
+
typeof block.mimeType === "string"
|
|
179
|
+
) {
|
|
180
|
+
mapped.push({
|
|
181
|
+
type: "image",
|
|
182
|
+
data: block.data,
|
|
183
|
+
mimeType: block.mimeType,
|
|
184
|
+
});
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
mapped.push({ type: "text", text: JSON.stringify(block) });
|
|
188
|
+
}
|
|
189
|
+
return mapped;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function sanitize(name: string): string {
|
|
193
|
+
return name.replace(/[^a-zA-Z0-9_-]/g, "_") || "tool";
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function uniqueName(name: string, taken: Set<string>): string {
|
|
197
|
+
let candidate = name;
|
|
198
|
+
let suffix = 2;
|
|
199
|
+
while (taken.has(candidate)) {
|
|
200
|
+
candidate = `${name}_${suffix}`;
|
|
201
|
+
suffix += 1;
|
|
202
|
+
}
|
|
203
|
+
taken.add(candidate);
|
|
204
|
+
return candidate;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function errorMessage(error: unknown): string {
|
|
208
|
+
return error instanceof Error ? error.message : String(error);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
212
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
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,6 +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
|
|
|
271
|
+
/** A local MCP process connected over stdin/stdout. */
|
|
272
|
+
export interface McpStdioServerConfig {
|
|
273
|
+
type?: "stdio";
|
|
274
|
+
command: string;
|
|
275
|
+
args?: string[];
|
|
276
|
+
env?: Record<string, string>;
|
|
277
|
+
/** Override the session cwd for this server process. */
|
|
278
|
+
cwd?: string;
|
|
279
|
+
}
|
|
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
|
+
|
|
269
304
|
// ═══════════════════════════════════════════════════════════════
|
|
270
305
|
// TOP-LEVEL CLIENT TYPES
|
|
271
306
|
// ═══════════════════════════════════════════════════════════════
|
|
@@ -646,6 +681,13 @@ export interface CreateSessionOptions {
|
|
|
646
681
|
*/
|
|
647
682
|
tools?: AnyAgentTool[];
|
|
648
683
|
|
|
684
|
+
/**
|
|
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.
|
|
688
|
+
*/
|
|
689
|
+
mcpServers?: McpServers;
|
|
690
|
+
|
|
649
691
|
/**
|
|
650
692
|
* Max automatic approval-conflict recovery attempts per runTurn() call.
|
|
651
693
|
* Set to 0 to disable automatic recovery.
|
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);
|