@xalia/agent 0.6.4 → 0.6.5

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.
Files changed (74) hide show
  1. package/dist/agent/src/agent/agent.js +12 -11
  2. package/dist/agent/src/agent/dummyLLM.js +24 -15
  3. package/dist/agent/src/agent/llm.js +0 -22
  4. package/dist/agent/src/agent/mcpServerManager.js +73 -15
  5. package/dist/agent/src/agent/openAI.js +32 -0
  6. package/dist/agent/src/agent/openAILLM.js +25 -1
  7. package/dist/agent/src/agent/openAILLMStreaming.js +8 -3
  8. package/dist/agent/src/agent/sudoMcpServerManager.js +22 -9
  9. package/dist/agent/src/chat/client/chatClient.js +2 -1
  10. package/dist/agent/src/chat/client/sessionClient.js +28 -3
  11. package/dist/agent/src/chat/data/dbSessionFileModels.js +10 -0
  12. package/dist/agent/src/chat/protocol/messages.js +1 -0
  13. package/dist/agent/src/chat/server/chatContextManager.js +4 -4
  14. package/dist/agent/src/chat/server/conversation.js +1 -1
  15. package/dist/agent/src/chat/server/imageGeneratorTools.js +7 -4
  16. package/dist/agent/src/chat/server/openSession.js +85 -12
  17. package/dist/agent/src/chat/server/sessionFileManager.js +17 -6
  18. package/dist/agent/src/chat/server/sessionRegistry.js +1 -1
  19. package/dist/agent/src/chat/server/tools.js +58 -10
  20. package/dist/agent/src/test/agent.test.js +26 -2
  21. package/dist/agent/src/test/chatContextManager.test.js +5 -5
  22. package/dist/agent/src/test/mcpServerManager.test.js +5 -1
  23. package/dist/agent/src/test/testTools.js +5 -2
  24. package/dist/agent/src/tool/chatMain.js +23 -3
  25. package/dist/agent/src/tool/files.js +0 -27
  26. package/package.json +3 -3
  27. package/scripts/test_chat +3 -1
  28. package/src/agent/agent.ts +53 -47
  29. package/src/agent/agentUtils.ts +7 -7
  30. package/src/agent/compressingContextManager.ts +4 -9
  31. package/src/agent/context.ts +28 -37
  32. package/src/agent/dummyLLM.ts +38 -28
  33. package/src/agent/iAgentEventHandler.ts +6 -9
  34. package/src/agent/imageGenLLM.ts +11 -5
  35. package/src/agent/llm.ts +41 -106
  36. package/src/agent/mcpServerManager.ts +145 -29
  37. package/src/agent/openAI.ts +123 -0
  38. package/src/agent/openAILLM.ts +52 -5
  39. package/src/agent/openAILLMStreaming.ts +36 -32
  40. package/src/agent/repeatLLM.ts +5 -6
  41. package/src/agent/sudoMcpServerManager.ts +48 -16
  42. package/src/agent/tools.ts +3 -5
  43. package/src/chat/client/chatClient.ts +3 -1
  44. package/src/chat/client/sessionClient.ts +47 -7
  45. package/src/chat/data/dataModels.ts +3 -3
  46. package/src/chat/data/dbSessionFileModels.ts +22 -0
  47. package/src/chat/protocol/messages.ts +39 -13
  48. package/src/chat/server/chatContextManager.ts +20 -24
  49. package/src/chat/server/conversation.ts +10 -10
  50. package/src/chat/server/imageGeneratorTools.ts +18 -9
  51. package/src/chat/server/openSession.ts +111 -22
  52. package/src/chat/server/sessionFileManager.ts +33 -10
  53. package/src/chat/server/sessionRegistry.ts +1 -1
  54. package/src/chat/server/tools.ts +77 -18
  55. package/src/chat/utils/approvalManager.ts +2 -2
  56. package/src/test/agent.test.ts +56 -31
  57. package/src/test/approvalManager.test.ts +2 -2
  58. package/src/test/chatContextManager.test.ts +11 -14
  59. package/src/test/compressingContextManager.test.ts +3 -3
  60. package/src/test/context.test.ts +3 -3
  61. package/src/test/conversation.test.ts +7 -7
  62. package/src/test/dbSessionMessages.test.ts +3 -3
  63. package/src/test/mcpServerManager.test.ts +10 -1
  64. package/src/test/testTools.ts +44 -33
  65. package/src/tool/agentChat.ts +10 -8
  66. package/src/tool/agentMain.ts +2 -2
  67. package/src/tool/chatMain.ts +38 -6
  68. package/src/tool/commandPrompt.ts +2 -4
  69. package/src/tool/files.ts +0 -34
  70. package/test_data/dummyllm_script_image_gen.json +27 -17
  71. package/test_data/dummyllm_script_invoke_image_gen_tool.json +9 -2
  72. package/test_data/dummyllm_script_render_tool.json +29 -0
  73. package/test_data/dummyllm_script_test_auto_approve.json +81 -0
  74. package/test_data/dummyllm_script_test_simplecalc_addition.json +29 -0
@@ -1,8 +1,8 @@
1
1
  import { OpenAI } from "openai";
2
2
  import {
3
- ChatCompletionAssistantMessageParam,
4
- ChatCompletionMessageToolCall,
5
- ChatCompletionToolMessageParam,
3
+ AssistantMessageParam,
4
+ MessageToolCall,
5
+ ToolMessageParam,
6
6
  } from "./llm";
7
7
 
8
8
  /**
@@ -17,7 +17,7 @@ export interface IAgentEventHandler {
17
17
  * `onAgentMessage`. This call is for client code which wishes to track the
18
18
  * LLM context.
19
19
  */
20
- onCompletion(result: ChatCompletionAssistantMessageParam): void;
20
+ onCompletion(result: AssistantMessageParam): void;
21
21
 
22
22
  /**
23
23
  * Images do not appear as part of `ChatCompletionAssistantMessageParam`,
@@ -30,7 +30,7 @@ export interface IAgentEventHandler {
30
30
  * Called when a tool call execution completes (success, error, or denial).
31
31
  * These messages are insertes into the LLM context.
32
32
  */
33
- onToolCallResult(result: ChatCompletionToolMessageParam): void;
33
+ onToolCallResult(result: ToolMessageParam): void;
34
34
 
35
35
  /**
36
36
  * Called when the agent produces a message chunk (streaming). Calls here
@@ -55,10 +55,7 @@ export interface IAgentEventHandler {
55
55
  * @param internal - If true, this is an "internal" or "Agent" tool.
56
56
  * @returns Promise<boolean> - true if approved, false if denied
57
57
  */
58
- onToolCall(
59
- toolCall: ChatCompletionMessageToolCall,
60
- agentTool: boolean
61
- ): Promise<boolean>;
58
+ onToolCall(toolCall: MessageToolCall, agentTool: boolean): Promise<boolean>;
62
59
 
63
60
  // Future extensibility examples:
64
61
  // onError?(error: AgentError): void;
@@ -4,7 +4,13 @@ import { writeFileSync } from "fs";
4
4
 
5
5
  import { getLogger } from "@xalia/xmcp/sdk";
6
6
 
7
- import { ILLM, ChatCompletion, XALIA_APP_HEADER } from "./llm";
7
+ import {
8
+ ILLM,
9
+ Completion,
10
+ XALIA_APP_HEADER,
11
+ ToolDescriptor,
12
+ MessageParam,
13
+ } from "./llm";
8
14
 
9
15
  const logger = getLogger();
10
16
 
@@ -49,10 +55,10 @@ export class ImageGenLLM implements ILLM {
49
55
  }
50
56
 
51
57
  public async getConversationResponse(
52
- messages: OpenAI.Chat.Completions.ChatCompletionMessageParam[],
53
- tools?: OpenAI.Chat.Completions.ChatCompletionTool[],
58
+ messages: MessageParam[],
59
+ tools?: ToolDescriptor[],
54
60
  onMessage?: (msg: string, end: boolean) => Promise<void>
55
- ): Promise<ChatCompletion> {
61
+ ): Promise<Completion> {
56
62
  assert(!tools || tools.length === 0, "tools not supported in ImageGenLLM");
57
63
 
58
64
  // Designed for image generation using openrouter, which tweaks the Create
@@ -67,7 +73,7 @@ export class ImageGenLLM implements ILLM {
67
73
 
68
74
  const completion = (await this.openai.chat.completions.create(
69
75
  params as OpenAI.Chat.Completions.ChatCompletionCreateParamsNonStreaming
70
- )) as ChatCompletion;
76
+ )) as Completion;
71
77
 
72
78
  // const completion = {} as unknown as ChatCompletion;
73
79
 
package/src/agent/llm.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import * as openai from "./openAI";
1
2
  import { OpenAI } from "openai";
2
3
 
3
4
  export const XALIA_APP_HEADER = {
@@ -5,123 +6,57 @@ export const XALIA_APP_HEADER = {
5
6
  "X-Title": "Xalia",
6
7
  };
7
8
 
8
- // Extend the ChatCompletionMessage type with an `images` value, compatible
9
- // with google/gemini-2.5-flash-image-preview.
9
+ export type ContentPartImage = openai.ChatCompletionContentPartImage;
10
10
 
11
- export type ChatCompletionMessage =
12
- OpenAI.Chat.Completions.ChatCompletionMessage & {
13
- images?: OpenAI.Chat.Completions.ChatCompletionContentPartImage[];
14
- };
11
+ // For now, internally we only support "function" tool call requests, not
12
+ // "custom" (impacts AssistantMessageParam).
13
+ export type MessageToolCall = OpenAI.ChatCompletionMessageFunctionToolCall;
15
14
 
16
- export type ChatCompletionChoice = Omit<
17
- OpenAI.Chat.Completions.ChatCompletion.Choice,
18
- "message"
19
- > & { message: ChatCompletionMessage };
20
-
21
- export type ChatCompletion = Omit<
22
- OpenAI.Chat.Completions.ChatCompletion,
23
- "choices"
24
- > & { choices: Array<ChatCompletionChoice> };
25
-
26
- // Shortcuts to other useful OpenAI types.
27
-
28
- export type ChatCompletionMessageToolCall =
29
- OpenAI.ChatCompletionMessageToolCall;
30
-
31
- export type ChatCompletionAssistantMessageParam =
32
- OpenAI.ChatCompletionAssistantMessageParam;
15
+ // ChatCompletionAssistantMessageParam, but with only "function" tool calls.
16
+ export interface AssistantMessageParam
17
+ extends openai.ChatCompletionAssistantMessageParam {
18
+ tool_calls?: Array<MessageToolCall>;
19
+ }
33
20
 
34
- export type ChatCompletionUserMessageParam =
35
- OpenAI.ChatCompletionUserMessageParam;
21
+ export type UserMessageParam = openai.ChatCompletionUserMessageParam;
36
22
 
37
- export type ChatCompletionToolMessageParam =
38
- OpenAI.ChatCompletionToolMessageParam & {
39
- metadata?: Record<string, string>;
40
- };
23
+ // Tool call results `ToolMessageParam` are only "function" results for now
24
+ export interface ToolMessageParam
25
+ extends OpenAI.ChatCompletionToolMessageParam {
26
+ _meta?: Record<string, string>;
27
+ structuredContent?: unknown;
28
+ }
41
29
 
42
- export type ChatCompletionMessageParam =
30
+ export type MessageParam =
43
31
  | OpenAI.Chat.Completions.ChatCompletionSystemMessageParam
44
- | ChatCompletionAssistantMessageParam
45
- | ChatCompletionUserMessageParam
46
- | ChatCompletionToolMessageParam;
32
+ | AssistantMessageParam
33
+ | UserMessageParam
34
+ | ToolMessageParam;
47
35
 
48
36
  // The tool description type
49
37
 
50
- export type ChatCompletionTool = OpenAI.Chat.Completions.ChatCompletionTool;
51
-
52
- // CompletionCreate params
53
-
54
- // openrouter reasoning type
55
-
56
- export type ReasoningEffort = {
57
- effort?: OpenAI.ReasoningEffort;
58
- max_tokens?: never;
59
- };
60
-
61
- export type ReasoningMaxTokens = { effort?: never; max_tokens?: number };
62
-
63
- export type ReasoningExclude = { exclude?: boolean; enabled?: never };
64
-
65
- export type ReasoningEnabled = {
66
- exclude?: never;
67
- enabled?: boolean;
68
- };
69
-
70
- export type Reasoning = (ReasoningEffort | ReasoningMaxTokens) &
71
- (ReasoningExclude | ReasoningEnabled);
72
-
73
- export type ReasoningDetails = {
74
- type: "reasoning.text" | "<unknown>";
75
- text?: string;
76
- signature?: string;
77
- format?: string;
78
- index?: number;
79
- };
38
+ export type ToolDescriptor = OpenAI.Chat.Completions.ChatCompletionFunctionTool;
80
39
 
81
40
  /**
82
- * A (openrouter-specific) stream chunk possibly containing reasoning tokens.
41
+ *
83
42
  */
84
- export type ChatCompletionChunkChoiceDeltaWithReasoning =
85
- OpenAI.Chat.Completions.ChatCompletionChunk.Choice.Delta & {
86
- reasoning?: string;
87
- reasoning_details?: ReasoningDetails[];
88
- };
89
-
90
- export function choiceDeltaExtractReasoning(
91
- delta: ChatCompletionChunkChoiceDeltaWithReasoning
92
- ): string | undefined {
93
- if (delta.reasoning) {
94
- return delta.reasoning;
95
- }
96
-
97
- if (delta.reasoning_details) {
98
- let reasoning = "";
99
- for (const details of delta.reasoning_details) {
100
- if (details.type !== "reasoning.text") {
101
- throw new Error(`unexpected details.type: ${details.type}`);
102
- }
103
- if (details.text) {
104
- if (typeof details.text !== "string") {
105
- throw new Error(
106
- `unexpected typeof details.text: ${typeof details.text}`
107
- );
108
- }
109
- reasoning += details.text;
110
- }
111
- }
112
- return reasoning;
113
- }
114
-
115
- return undefined;
43
+ export interface Message /* WithReasoning */
44
+ extends openai.ChatCompletionMessage {
45
+ reasoning?: string;
46
+ tool_calls?: Array<MessageToolCall>;
116
47
  }
117
48
 
118
- /**
119
- * A chat completion message with extra reasoning tokens.
120
- */
121
- export type ChatCompletionMessageWithReasoning =
122
- OpenAI.Chat.Completions.ChatCompletionMessage & {
123
- reasoning?: string;
124
- };
49
+ // export interface Message extends MessageWithReasoning {}
50
+
51
+ // Extend ChatCompletionChoice to only hold function tool calls
52
+ export interface Choice extends openai.ChatCompletionChoice {
53
+ message: Message;
54
+ }
55
+
56
+ // Extends ChatCompletion to only hold function tool calls.
57
+ export interface Completion extends openai.ChatCompletion {
58
+ choices: Array<Choice>;
59
+ }
125
60
 
126
61
  export interface ILLM {
127
62
  getModel(): string;
@@ -129,11 +64,11 @@ export interface ILLM {
129
64
  getUrl(): string;
130
65
 
131
66
  getConversationResponse(
132
- messages: ChatCompletionMessageParam[],
133
- tools?: ChatCompletionTool[],
67
+ messages: MessageParam[],
68
+ tools?: ToolDescriptor[],
134
69
  onMessage?: (msg: string, end: boolean) => Promise<void>,
135
70
  onReasoning?: (reasoning: string) => Promise<void>
136
- ): Promise<ChatCompletion>;
71
+ ): Promise<Completion>;
137
72
 
138
73
  setModel(model: string): void;
139
74
  }
@@ -1,25 +1,61 @@
1
- import { ChatCompletionTool } from "openai/resources.mjs";
1
+ import { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
2
2
  import {
3
3
  SSEClientTransport,
4
4
  SSEClientTransportOptions,
5
5
  } from "@modelcontextprotocol/sdk/client/sse.js";
6
+ import {
7
+ StreamableHTTPClientTransport,
8
+ StreamableHTTPClientTransportOptions,
9
+ } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
6
10
  import { Client as McpClient } from "@modelcontextprotocol/sdk/client/index.js";
7
- import { TokenAuth } from "./tokenAuth";
8
- import { Tool } from "@modelcontextprotocol/sdk/types.js";
9
- import { strict as assert } from "assert";
11
+ import { Tool, Resource } from "@modelcontextprotocol/sdk/types.js";
12
+
10
13
  import { McpServerSettings, getLogger } from "@xalia/xmcp/sdk";
11
14
  export type { McpServerSettings } from "@xalia/xmcp/sdk";
12
15
 
16
+ import { TokenAuth } from "./tokenAuth";
17
+ import { strict as assert } from "assert";
18
+ import { ToolDescriptor } from "./llm";
19
+ import { ToolCallResult } from "./agent";
20
+
13
21
  const logger = getLogger();
14
22
 
23
+ export type McpServerToolCallMetaData = {
24
+ "xalia/mcpServerName": string;
25
+ };
26
+
27
+ const mcpServerToolCallMetaKeys: (keyof McpServerToolCallMetaData)[] = [
28
+ "xalia/mcpServerName",
29
+ ];
30
+
31
+ export function isMcpServerToolCallMetaData(
32
+ meta: unknown
33
+ ): meta is McpServerToolCallMetaData {
34
+ return (
35
+ !!meta &&
36
+ typeof meta === "object" &&
37
+ mcpServerToolCallMetaKeys.every((k) => k in meta)
38
+ );
39
+ }
40
+
41
+ export type McpServerToolCallResult = ToolCallResult<McpServerToolCallMetaData>;
42
+
15
43
  export type VerifiedMcpToolCall = {
16
44
  mcpServerName: string;
17
45
  toolName: string;
18
46
  args: unknown;
19
47
  };
20
48
 
49
+ export type ResourceContent = {
50
+ _meta?: Record<string, unknown>;
51
+ uri: string;
52
+ mimeType?: string;
53
+ text?: string;
54
+ blob?: string;
55
+ };
56
+
21
57
  /// Callback into an Mcp server
22
- export type McpCallback = { (args: string): Promise<string> };
58
+ export type McpCallback = { (args: string): Promise<McpServerToolCallResult> };
23
59
 
24
60
  /// Map of tool name to callback
25
61
  export type McpCallbacks = Map<string, McpCallback>;
@@ -35,11 +71,13 @@ export type EnabledToolsMap = Map<string, boolean>;
35
71
  * class.
36
72
  */
37
73
  export class McpServerInfo {
74
+ private readonly name: string;
38
75
  private readonly tools: Tool[]; // TODO: May not need both tools and toolsMap
76
+ private readonly resources: Resource[];
39
77
  private readonly toolsMap: { [toolName: string]: Tool };
40
78
  protected enabledToolsMap: EnabledToolsMap;
41
79
 
42
- constructor(tools: Tool[]) {
80
+ constructor(name: string, tools: Tool[], resources: Resource[]) {
43
81
  const toolsMap: { [toolName: string]: Tool } = {};
44
82
 
45
83
  for (const mcpTool of tools) {
@@ -47,11 +85,17 @@ export class McpServerInfo {
47
85
  toolsMap[toolName] = mcpTool;
48
86
  }
49
87
 
88
+ this.name = name;
50
89
  this.tools = tools;
90
+ this.resources = resources;
51
91
  this.toolsMap = toolsMap;
52
92
  this.enabledToolsMap = new Map();
53
93
  }
54
94
 
95
+ public getName(): string {
96
+ return this.name;
97
+ }
98
+
55
99
  public getEnabledTools(): EnabledToolsMap {
56
100
  return this.enabledToolsMap;
57
101
  }
@@ -60,6 +104,10 @@ export class McpServerInfo {
60
104
  return this.tools;
61
105
  }
62
106
 
107
+ public getResources(): Resource[] {
108
+ return this.resources;
109
+ }
110
+
63
111
  public getTool(toolName: string): Tool | undefined {
64
112
  return this.toolsMap[toolName];
65
113
  }
@@ -88,8 +136,15 @@ class McpServerInfoInternal extends McpServerInfoRW {
88
136
  private readonly client: McpClient;
89
137
  private readonly callbacks: McpCallbacks;
90
138
 
91
- constructor(client: McpClient, tools: Tool[]) {
92
- super(tools);
139
+ constructor(
140
+ name: string,
141
+ client: McpClient,
142
+ tools: Tool[],
143
+ resources: Resource[]
144
+ ) {
145
+ super(name, tools, resources);
146
+
147
+ logger.debug(`[McpServerInfoInternal] tools: ${JSON.stringify(tools)}`);
93
148
 
94
149
  const callbacks: McpCallbacks = new Map();
95
150
 
@@ -97,7 +152,9 @@ class McpServerInfoInternal extends McpServerInfoRW {
97
152
  const toolName = mcpTool.name;
98
153
 
99
154
  // Create callback
100
- const callback = async (argStr: string): Promise<string> => {
155
+ const callback = async (
156
+ argStr: string
157
+ ): Promise<McpServerToolCallResult> => {
101
158
  logger.debug(
102
159
  `cb for ${toolName} invoked with args (${typeof argStr}): ` +
103
160
  JSON.stringify(argStr)
@@ -120,7 +177,19 @@ class McpServerInfoInternal extends McpServerInfoRW {
120
177
  assert(typeof content0 === "object");
121
178
  const content0Text = content0.text;
122
179
  assert(typeof content0Text === "string");
123
- return content0Text;
180
+
181
+ const meta: Record<string, string> = (toolResult._meta ||
182
+ {}) as McpServerToolCallMetaData;
183
+ meta["xalia/mcpServerName"] = this.getName();
184
+ assert(isMcpServerToolCallMetaData(meta));
185
+
186
+ return {
187
+ response: content0Text,
188
+ _meta: meta,
189
+ ...(toolResult.structuredContent
190
+ ? { structuredContent: toolResult.structuredContent }
191
+ : {}),
192
+ };
124
193
  };
125
194
 
126
195
  callbacks.set(toolName, callback);
@@ -134,9 +203,18 @@ class McpServerInfoInternal extends McpServerInfoRW {
134
203
  await this.client.close();
135
204
  }
136
205
 
137
- public getCallback(toolName: string) {
206
+ public getCallback(toolName: string): McpCallback | undefined {
138
207
  return this.callbacks.get(toolName);
139
208
  }
209
+
210
+ public async readResource(uri: string): Promise<ResourceContent[]> {
211
+ const res = await this.client.readResource({
212
+ uri,
213
+ });
214
+
215
+ logger.info(`readResource: got: ${JSON.stringify(res)}`);
216
+ return res.contents;
217
+ }
140
218
  }
141
219
 
142
220
  /**
@@ -169,7 +247,7 @@ export interface IMcpServerManager {
169
247
  export class McpServerManager implements IMcpServerManager {
170
248
  private mcpServers = new Map<string, McpServerInfoInternal>();
171
249
  private enabledToolsDirty: boolean = true;
172
- private enabledOpenAITools: ChatCompletionTool[] = [];
250
+ private enabledOpenAITools: ToolDescriptor[] = [];
173
251
 
174
252
  public async shutdown() {
175
253
  await Promise.all(
@@ -194,19 +272,11 @@ export class McpServerManager implements IMcpServerManager {
194
272
  return this.getMcpServerInternal(mcpServerName);
195
273
  }
196
274
 
197
- public async addMcpServerWithSSEUrl(
275
+ public async addMcpServerWithTransport(
198
276
  mcpServerName: string,
199
- url: string,
200
- apiKey?: string,
277
+ transport: Transport,
201
278
  tools?: Tool[]
202
279
  ): Promise<void> {
203
- logger.debug(`Adding mcp server ${mcpServerName}: ${url}`);
204
- const sseTransportOptions: SSEClientTransportOptions = {};
205
- if (apiKey) {
206
- sseTransportOptions.authProvider = new TokenAuth(apiKey);
207
- }
208
- const urlO = new URL(url);
209
- const transport = new SSEClientTransport(urlO, sseTransportOptions);
210
280
  const client = new McpClient({
211
281
  name: "@xalia/agent",
212
282
  version: "1.0.0",
@@ -223,6 +293,37 @@ export class McpServerManager implements IMcpServerManager {
223
293
  await this.addMcpServerWithClient(client, mcpServerName, tools);
224
294
  }
225
295
 
296
+ public async addMcpServerWithSSEUrl(
297
+ mcpServerName: string,
298
+ url: string,
299
+ apiKey?: string,
300
+ tools?: Tool[]
301
+ ): Promise<void> {
302
+ logger.debug(`Adding mcp server ${mcpServerName}: ${url}`);
303
+ const sseTransportOptions: SSEClientTransportOptions = {};
304
+ if (apiKey) {
305
+ sseTransportOptions.authProvider = new TokenAuth(apiKey);
306
+ }
307
+ const urlO = new URL(url);
308
+ const transport = new SSEClientTransport(urlO, sseTransportOptions);
309
+ return this.addMcpServerWithTransport(mcpServerName, transport, tools);
310
+ }
311
+
312
+ public async addMcpServerWithStreamableHTTPUrl(
313
+ mcpServerName: string,
314
+ url: string,
315
+ apiKey?: string
316
+ ): Promise<void> {
317
+ logger.debug(`Adding mcp (s-http) server ${mcpServerName}: ${url}`);
318
+ const transportOptions: StreamableHTTPClientTransportOptions = {};
319
+ if (apiKey) {
320
+ transportOptions.authProvider = new TokenAuth(apiKey);
321
+ }
322
+ const urlO = new URL(url);
323
+ const transport = new StreamableHTTPClientTransport(urlO, transportOptions);
324
+ return this.addMcpServerWithTransport(mcpServerName, transport);
325
+ }
326
+
226
327
  /**
227
328
  * Add MCP server from an already connected McpClient.
228
329
  */
@@ -232,16 +333,23 @@ export class McpServerManager implements IMcpServerManager {
232
333
  tools?: Tool[]
233
334
  ): Promise<void> {
234
335
  try {
235
- // TODO; require the tools to be passed in.
336
+ // TODO: require the tools to be passed in.
236
337
 
338
+ const resourcesP = client.listResources().catch((err: unknown) => {
339
+ logger.warn(
340
+ `resources ${mcpServerName}: ${JSON.stringify(err)} ${String(err)}`
341
+ );
342
+ return { resources: [] };
343
+ });
237
344
  if (!tools) {
238
345
  const mcpTools = await client.listTools();
239
346
  tools = mcpTools.tools;
240
347
  }
241
348
 
349
+ const resources: Resource[] = (await resourcesP).resources;
242
350
  this.mcpServers.set(
243
351
  mcpServerName,
244
- new McpServerInfoInternal(client, tools)
352
+ new McpServerInfoInternal(mcpServerName, client, tools, resources)
245
353
  );
246
354
  } catch (e) {
247
355
  await client.close();
@@ -289,7 +397,7 @@ export class McpServerManager implements IMcpServerManager {
289
397
  this.enabledToolsDirty = true;
290
398
  }
291
399
 
292
- public getOpenAITools(): ChatCompletionTool[] {
400
+ public getOpenAITools(): ToolDescriptor[] {
293
401
  if (this.enabledToolsDirty) {
294
402
  this.enabledOpenAITools = computeOpenAIToolList(this.mcpServers);
295
403
  this.enabledToolsDirty = false;
@@ -325,7 +433,7 @@ export class McpServerManager implements IMcpServerManager {
325
433
  * Note the `qualifiedToolName` is the full `{mcpServerName}/{toolName}` as
326
434
  * in the openai spec.
327
435
  */
328
- public async invoke(toolCall: VerifiedMcpToolCall): Promise<string> {
436
+ public async invoke(toolCall: VerifiedMcpToolCall): Promise<ToolCallResult> {
329
437
  const server = this.getMcpServerInternal(toolCall.mcpServerName);
330
438
  const cb = server.getCallback(toolCall.toolName);
331
439
  if (!cb) {
@@ -335,6 +443,14 @@ export class McpServerManager implements IMcpServerManager {
335
443
  return cb(JSON.stringify(toolCall.args));
336
444
  }
337
445
 
446
+ public async getResource(
447
+ serverName: string,
448
+ uri: string
449
+ ): Promise<ResourceContent[]> {
450
+ const server = this.getMcpServerInternal(serverName);
451
+ return server.readResource(uri);
452
+ }
453
+
338
454
  /**
339
455
  * "Settings" refers to the set of added servers and enabled tools.
340
456
  */
@@ -390,8 +506,8 @@ export function splitQualifiedName(
390
506
 
391
507
  export function computeOpenAIToolList(
392
508
  mcpServers: Map<string, McpServerInfoInternal>
393
- ): ChatCompletionTool[] {
394
- const openaiTools: ChatCompletionTool[] = [];
509
+ ): ToolDescriptor[] {
510
+ const openaiTools: ToolDescriptor[] = [];
395
511
 
396
512
  for (const [mcpServerName, mcpServer] of mcpServers) {
397
513
  const tools = mcpServer.getTools();
@@ -413,7 +529,7 @@ export function computeOpenAIToolList(
413
529
  export function mcpToolToOpenAITool(
414
530
  tool: Tool,
415
531
  qualifiedName?: string
416
- ): ChatCompletionTool {
532
+ ): ToolDescriptor {
417
533
  return {
418
534
  type: "function",
419
535
  function: {