mcp-use 0.1.6 → 0.1.8

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 (32) hide show
  1. package/README.md +196 -0
  2. package/dist/examples/ai_sdk_example.d.ts +23 -0
  3. package/dist/examples/ai_sdk_example.d.ts.map +1 -0
  4. package/dist/examples/ai_sdk_example.js +213 -0
  5. package/dist/examples/stream_example.d.ts +12 -0
  6. package/dist/examples/stream_example.d.ts.map +1 -0
  7. package/dist/examples/stream_example.js +198 -0
  8. package/dist/index.d.ts +2 -0
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +2 -0
  11. package/dist/src/agents/mcp_agent.d.ts +9 -3
  12. package/dist/src/agents/mcp_agent.d.ts.map +1 -1
  13. package/dist/src/agents/mcp_agent.js +175 -42
  14. package/dist/src/agents/utils/ai_sdk.d.ts +22 -0
  15. package/dist/src/agents/utils/ai_sdk.d.ts.map +1 -0
  16. package/dist/src/agents/utils/ai_sdk.js +62 -0
  17. package/dist/src/agents/utils/index.d.ts +2 -0
  18. package/dist/src/agents/utils/index.d.ts.map +1 -0
  19. package/dist/src/agents/utils/index.js +1 -0
  20. package/dist/tests/ai_sdk_compatibility.test.d.ts +13 -0
  21. package/dist/tests/ai_sdk_compatibility.test.d.ts.map +1 -0
  22. package/dist/tests/ai_sdk_compatibility.test.js +214 -0
  23. package/dist/tests/stream_events.test.d.ts +2 -0
  24. package/dist/tests/stream_events.test.d.ts.map +1 -0
  25. package/dist/tests/stream_events.test.js +306 -0
  26. package/dist/tests/stream_events_simple.test.d.ts +7 -0
  27. package/dist/tests/stream_events_simple.test.d.ts.map +1 -0
  28. package/dist/tests/stream_events_simple.test.js +179 -0
  29. package/dist/vitest.config.d.ts +3 -0
  30. package/dist/vitest.config.d.ts.map +1 -0
  31. package/dist/vitest.config.js +21 -0
  32. package/package.json +14 -4
@@ -0,0 +1,214 @@
1
+ /**
2
+ * Tests for AI SDK compatibility with MCPAgent streamEvents()
3
+ *
4
+ * These tests verify that streamEvents() can be used with the AI SDK's
5
+ * LangChainAdapter for creating data stream responses compatible with
6
+ * Vercel AI SDK hooks like useCompletion and useChat.
7
+ */
8
+ import { LangChainAdapter } from 'ai';
9
+ import { describe, expect, it } from 'vitest';
10
+ // Mock an async generator that simulates our streamEvents output
11
+ async function* mockStreamEvents() {
12
+ // Simulate typical events from streamEvents
13
+ yield {
14
+ event: 'on_chain_start',
15
+ name: 'AgentExecutor',
16
+ data: { input: { input: 'test query' } },
17
+ };
18
+ yield {
19
+ event: 'on_chat_model_stream',
20
+ name: 'ChatAnthropic',
21
+ data: { chunk: { content: 'Hello' } },
22
+ };
23
+ yield {
24
+ event: 'on_chat_model_stream',
25
+ name: 'ChatAnthropic',
26
+ data: { chunk: { content: ' world' } },
27
+ };
28
+ yield {
29
+ event: 'on_chat_model_stream',
30
+ name: 'ChatAnthropic',
31
+ data: { chunk: { content: '!' } },
32
+ };
33
+ yield {
34
+ event: 'on_tool_start',
35
+ name: 'test_tool',
36
+ data: { input: { query: 'test' } },
37
+ };
38
+ yield {
39
+ event: 'on_tool_end',
40
+ name: 'test_tool',
41
+ data: { output: 'Tool executed successfully' },
42
+ };
43
+ yield {
44
+ event: 'on_chain_end',
45
+ name: 'AgentExecutor',
46
+ data: { output: 'Hello world!' },
47
+ };
48
+ }
49
+ // Function to convert streamEvents to a format compatible with AI SDK
50
+ async function* streamEventsToAISDK(streamEvents) {
51
+ for await (const event of streamEvents) {
52
+ // Only yield the actual content tokens from chat model streams
53
+ if (event.event === 'on_chat_model_stream' && event.data?.chunk?.content) {
54
+ yield event.data.chunk.content;
55
+ }
56
+ }
57
+ }
58
+ // Alternative adapter that yields complete content at the end
59
+ async function* streamEventsToCompleteContent(streamEvents) {
60
+ let fullContent = '';
61
+ for await (const event of streamEvents) {
62
+ if (event.event === 'on_chat_model_stream' && event.data?.chunk?.content) {
63
+ fullContent += event.data.chunk.content;
64
+ }
65
+ // For tool events, we could add additional formatting
66
+ else if (event.event === 'on_tool_start') {
67
+ // Could add tool start indicators if needed
68
+ }
69
+ else if (event.event === 'on_tool_end') {
70
+ // Could add tool completion indicators if needed
71
+ }
72
+ }
73
+ // Yield the complete content at the end
74
+ if (fullContent) {
75
+ yield fullContent;
76
+ }
77
+ }
78
+ describe('aI SDK Compatibility', () => {
79
+ it('should convert streamEvents to AI SDK compatible stream', async () => {
80
+ const mockEvents = mockStreamEvents();
81
+ const aiSDKStream = streamEventsToAISDK(mockEvents);
82
+ const tokens = [];
83
+ for await (const token of aiSDKStream) {
84
+ tokens.push(token);
85
+ }
86
+ expect(tokens).toEqual(['Hello', ' world', '!']);
87
+ });
88
+ it('should work with LangChainAdapter.toDataStreamResponse', async () => {
89
+ const mockEvents = mockStreamEvents();
90
+ const aiSDKStream = streamEventsToAISDK(mockEvents);
91
+ // Convert async generator to ReadableStream for AI SDK compatibility
92
+ const readableStream = new ReadableStream({
93
+ async start(controller) {
94
+ try {
95
+ for await (const token of aiSDKStream) {
96
+ controller.enqueue(token);
97
+ }
98
+ controller.close();
99
+ }
100
+ catch (error) {
101
+ controller.error(error);
102
+ }
103
+ },
104
+ });
105
+ // Test that we can create a data stream response
106
+ const response = LangChainAdapter.toDataStreamResponse(readableStream);
107
+ expect(response).toBeInstanceOf(Response);
108
+ expect(response.headers.get('Content-Type')).toBe('text/plain; charset=utf-8');
109
+ });
110
+ it('should convert streamEvents to complete content stream', async () => {
111
+ const mockEvents = mockStreamEvents();
112
+ const contentStream = streamEventsToCompleteContent(mockEvents);
113
+ const content = [];
114
+ for await (const chunk of contentStream) {
115
+ content.push(chunk);
116
+ }
117
+ expect(content).toEqual(['Hello world!']);
118
+ });
119
+ it('should handle empty streams gracefully', async () => {
120
+ async function* emptyStreamEvents() {
121
+ // Empty generator
122
+ }
123
+ const emptyEvents = emptyStreamEvents();
124
+ const aiSDKStream = streamEventsToAISDK(emptyEvents);
125
+ const tokens = [];
126
+ for await (const token of aiSDKStream) {
127
+ tokens.push(token);
128
+ }
129
+ expect(tokens).toEqual([]);
130
+ });
131
+ it('should filter non-content events correctly', async () => {
132
+ async function* mixedEvents() {
133
+ yield {
134
+ event: 'on_chain_start',
135
+ name: 'Test',
136
+ data: { input: 'test' },
137
+ };
138
+ yield {
139
+ event: 'on_chat_model_stream',
140
+ name: 'ChatModel',
141
+ data: { chunk: { content: 'Content' } },
142
+ };
143
+ yield {
144
+ event: 'on_tool_start',
145
+ name: 'Tool',
146
+ data: { input: 'test' },
147
+ };
148
+ yield {
149
+ event: 'on_chat_model_stream',
150
+ name: 'ChatModel',
151
+ data: { chunk: { content: ' token' } },
152
+ };
153
+ yield {
154
+ event: 'on_chain_end',
155
+ name: 'Test',
156
+ data: { output: 'result' },
157
+ };
158
+ }
159
+ const events = mixedEvents();
160
+ const aiSDKStream = streamEventsToAISDK(events);
161
+ const tokens = [];
162
+ for await (const token of aiSDKStream) {
163
+ tokens.push(token);
164
+ }
165
+ expect(tokens).toEqual(['Content', ' token']);
166
+ });
167
+ it('should create readable stream from streamEvents', async () => {
168
+ const mockEvents = mockStreamEvents();
169
+ // Create a ReadableStream from our async generator
170
+ const readableStream = new ReadableStream({
171
+ async start(controller) {
172
+ try {
173
+ for await (const event of streamEventsToAISDK(mockEvents)) {
174
+ controller.enqueue(new TextEncoder().encode(event));
175
+ }
176
+ controller.close();
177
+ }
178
+ catch (error) {
179
+ controller.error(error);
180
+ }
181
+ },
182
+ });
183
+ expect(readableStream).toBeInstanceOf(ReadableStream);
184
+ // Test that we can read from the stream
185
+ const reader = readableStream.getReader();
186
+ const decoder = new TextDecoder();
187
+ const chunks = [];
188
+ while (true) {
189
+ const { done, value } = await reader.read();
190
+ if (done)
191
+ break;
192
+ chunks.push(decoder.decode(value));
193
+ }
194
+ expect(chunks).toEqual(['Hello', ' world', '!']);
195
+ });
196
+ });
197
+ // Convert async generator to ReadableStream for AI SDK compatibility
198
+ function createReadableStreamFromGenerator(generator) {
199
+ return new ReadableStream({
200
+ async start(controller) {
201
+ try {
202
+ for await (const chunk of generator) {
203
+ controller.enqueue(chunk);
204
+ }
205
+ controller.close();
206
+ }
207
+ catch (error) {
208
+ controller.error(error);
209
+ }
210
+ },
211
+ });
212
+ }
213
+ // Export the adapter functions for use in examples
214
+ export { createReadableStreamFromGenerator, streamEventsToAISDK, streamEventsToCompleteContent };
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=stream_events.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stream_events.test.d.ts","sourceRoot":"","sources":["../../tests/stream_events.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,306 @@
1
+ /* eslint-disable no-unreachable-loop, unused-imports/no-unused-vars */
2
+ import { HumanMessage } from '@langchain/core/messages';
3
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
4
+ import { MCPAgent, MCPClient } from '../index.js';
5
+ // Mock the MCP client for testing
6
+ vi.mock('../src/client.js', () => ({
7
+ MCPClient: vi.fn().mockImplementation(() => ({
8
+ getAllActiveSessions: vi.fn().mockResolvedValue({}),
9
+ createAllSessions: vi.fn().mockResolvedValue({}),
10
+ closeAllSessions: vi.fn().mockResolvedValue(undefined),
11
+ })),
12
+ }));
13
+ // Mock the LangChain adapter
14
+ vi.mock('../src/adapters/langchain_adapter.js', () => ({
15
+ LangChainAdapter: vi.fn().mockImplementation(() => ({
16
+ createToolsFromConnectors: vi.fn().mockResolvedValue([
17
+ {
18
+ name: 'test_tool',
19
+ description: 'A test tool',
20
+ schema: {},
21
+ func: vi.fn().mockResolvedValue('Test tool result'),
22
+ },
23
+ ]),
24
+ })),
25
+ }));
26
+ describe('mCPAgent streamEvents()', () => {
27
+ let agent;
28
+ let mockClient;
29
+ let mockLLM;
30
+ beforeEach(() => {
31
+ // Create mock LLM that supports streamEvents
32
+ mockLLM = {
33
+ invoke: vi.fn().mockResolvedValue({ content: 'Test response' }),
34
+ _modelType: 'chat_anthropic',
35
+ _llmType: 'anthropic',
36
+ };
37
+ // Create mock client
38
+ mockClient = new MCPClient({});
39
+ // Create agent with mocked dependencies
40
+ agent = new MCPAgent({
41
+ llm: mockLLM,
42
+ client: mockClient,
43
+ maxSteps: 3,
44
+ memoryEnabled: true,
45
+ verbose: false,
46
+ });
47
+ // Mock the agent executor's streamEvents method
48
+ const mockStreamEvents = vi.fn().mockImplementation(async function* () {
49
+ // Simulate typical event sequence
50
+ yield {
51
+ event: 'on_chain_start',
52
+ name: 'AgentExecutor',
53
+ data: { input: { input: 'test query' } },
54
+ };
55
+ yield {
56
+ event: 'on_chat_model_stream',
57
+ name: 'ChatAnthropic',
58
+ data: { chunk: { content: 'Hello' } },
59
+ };
60
+ yield {
61
+ event: 'on_chat_model_stream',
62
+ name: 'ChatAnthropic',
63
+ data: { chunk: { content: ' world' } },
64
+ };
65
+ yield {
66
+ event: 'on_tool_start',
67
+ name: 'test_tool',
68
+ data: { input: { query: 'test' } },
69
+ };
70
+ yield {
71
+ event: 'on_tool_end',
72
+ name: 'test_tool',
73
+ data: { output: 'Tool result' },
74
+ };
75
+ yield {
76
+ event: 'on_chain_end',
77
+ name: 'AgentExecutor',
78
+ data: { output: 'Hello world' },
79
+ };
80
+ });
81
+ // Mock initialize method
82
+ vi.spyOn(agent, 'initialize').mockResolvedValue(undefined);
83
+ // Mock agentExecutor after initialization
84
+ Object.defineProperty(agent, 'agentExecutor', {
85
+ get: () => ({
86
+ streamEvents: mockStreamEvents,
87
+ maxIterations: 3,
88
+ }),
89
+ configurable: true,
90
+ });
91
+ // Mock tools
92
+ Object.defineProperty(agent, 'tools', {
93
+ get: () => [{ name: 'test_tool' }],
94
+ configurable: true,
95
+ });
96
+ // Mock telemetry using bracket notation to access private property
97
+ vi.spyOn(agent.telemetry, 'trackAgentExecution').mockResolvedValue(undefined);
98
+ });
99
+ afterEach(() => {
100
+ vi.clearAllMocks();
101
+ });
102
+ it('should yield StreamEvent objects', async () => {
103
+ const events = [];
104
+ for await (const event of agent.streamEvents('test query')) {
105
+ events.push(event);
106
+ }
107
+ expect(events).toHaveLength(6);
108
+ // Check event structure
109
+ events.forEach((event) => {
110
+ expect(event).toHaveProperty('event');
111
+ expect(event).toHaveProperty('name');
112
+ expect(event).toHaveProperty('data');
113
+ });
114
+ });
115
+ it('should handle token streaming correctly', async () => {
116
+ const tokens = [];
117
+ for await (const event of agent.streamEvents('test query')) {
118
+ if (event.event === 'on_chat_model_stream' && event.data?.chunk?.content) {
119
+ tokens.push(event.data.chunk.content);
120
+ }
121
+ }
122
+ expect(tokens).toEqual(['Hello', ' world']);
123
+ });
124
+ it('should track tool execution events', async () => {
125
+ const toolEvents = [];
126
+ for await (const event of agent.streamEvents('test query')) {
127
+ if (event.event.includes('tool')) {
128
+ toolEvents.push(event);
129
+ }
130
+ }
131
+ expect(toolEvents).toHaveLength(2);
132
+ expect(toolEvents[0].event).toBe('on_tool_start');
133
+ expect(toolEvents[0].name).toBe('test_tool');
134
+ expect(toolEvents[1].event).toBe('on_tool_end');
135
+ expect(toolEvents[1].name).toBe('test_tool');
136
+ });
137
+ it('should initialize agent if not already initialized', async () => {
138
+ const initializeSpy = vi.spyOn(agent, 'initialize');
139
+ // Set initialized to false
140
+ Object.defineProperty(agent, 'initialized', {
141
+ get: () => false,
142
+ configurable: true,
143
+ });
144
+ const events = [];
145
+ for await (const event of agent.streamEvents('test query')) {
146
+ events.push(event);
147
+ break; // Just get first event
148
+ }
149
+ expect(initializeSpy).toHaveBeenCalled();
150
+ });
151
+ it('should handle memory correctly when enabled', async () => {
152
+ const addToHistorySpy = vi.spyOn(agent, 'addToHistory');
153
+ // Consume all events
154
+ const events = [];
155
+ for await (const event of agent.streamEvents('test query')) {
156
+ events.push(event);
157
+ }
158
+ // Should add user message and AI response to history
159
+ expect(addToHistorySpy).toHaveBeenCalledTimes(2);
160
+ });
161
+ it('should track telemetry', async () => {
162
+ const telemetrySpy = vi.spyOn(agent.telemetry, 'trackAgentExecution');
163
+ // Consume all events
164
+ for await (const event of agent.streamEvents('test query')) {
165
+ // Just consume events
166
+ }
167
+ expect(telemetrySpy).toHaveBeenCalledWith(expect.objectContaining({
168
+ executionMethod: 'streamEvents',
169
+ query: 'test query',
170
+ success: true,
171
+ }));
172
+ });
173
+ it('should handle errors gracefully', async () => {
174
+ // Mock agent executor to throw error
175
+ Object.defineProperty(agent, 'agentExecutor', {
176
+ get: () => ({
177
+ streamEvents: vi.fn().mockImplementation(async function* () {
178
+ throw new Error('Test error');
179
+ }),
180
+ maxIterations: 3,
181
+ }),
182
+ configurable: true,
183
+ });
184
+ await expect(async () => {
185
+ for await (const event of agent.streamEvents('test query')) {
186
+ // Should not reach here
187
+ }
188
+ }).rejects.toThrow('Test error');
189
+ });
190
+ it('should respect maxSteps parameter', async () => {
191
+ const mockAgentExecutor = {
192
+ streamEvents: vi.fn().mockImplementation(async function* () {
193
+ yield { event: 'test', name: 'test', data: {} };
194
+ }),
195
+ maxIterations: 3,
196
+ };
197
+ Object.defineProperty(agent, 'agentExecutor', {
198
+ get: () => mockAgentExecutor,
199
+ configurable: true,
200
+ });
201
+ for await (const event of agent.streamEvents('test query', 5)) {
202
+ break;
203
+ }
204
+ expect(mockAgentExecutor.maxIterations).toBe(5);
205
+ });
206
+ it('should handle external history', async () => {
207
+ const externalHistory = [
208
+ new HumanMessage('Previous message'),
209
+ ];
210
+ // Mock the agent executor to capture inputs
211
+ let capturedInputs;
212
+ const mockStreamEvents = vi.fn().mockImplementation(async function* (inputs) {
213
+ capturedInputs = inputs;
214
+ yield { event: 'test', name: 'test', data: {} };
215
+ });
216
+ Object.defineProperty(agent, 'agentExecutor', {
217
+ get: () => ({
218
+ streamEvents: mockStreamEvents,
219
+ maxIterations: 3,
220
+ }),
221
+ configurable: true,
222
+ });
223
+ // Mock initialize method
224
+ vi.spyOn(agent, 'initialize').mockResolvedValue(undefined);
225
+ for await (const event of agent.streamEvents('test query', undefined, true, externalHistory)) {
226
+ break;
227
+ }
228
+ expect(capturedInputs.chat_history).toEqual(externalHistory);
229
+ });
230
+ it('should clean up resources on completion', async () => {
231
+ const closeSpy = vi.spyOn(agent, 'close').mockResolvedValue(undefined);
232
+ // Test with manageConnector=true and no client
233
+ Object.defineProperty(agent, 'client', {
234
+ get: () => undefined,
235
+ configurable: true,
236
+ });
237
+ // Consume all events
238
+ for await (const event of agent.streamEvents('test query', undefined, true)) {
239
+ // Just consume events
240
+ }
241
+ // Note: cleanup only happens if initialized in this call and no client
242
+ // This is hard to test with our current mocking setup, but the logic is there
243
+ });
244
+ });
245
+ describe('mCPAgent streamEvents() edge cases', () => {
246
+ it('should handle empty event stream', async () => {
247
+ const mockLLM = {
248
+ invoke: vi.fn().mockResolvedValue({ content: 'Test response' }),
249
+ _modelType: 'chat_anthropic',
250
+ };
251
+ const mockClient = new MCPClient({});
252
+ const agent = new MCPAgent({
253
+ llm: mockLLM,
254
+ client: mockClient,
255
+ maxSteps: 3,
256
+ });
257
+ // Mock empty event stream
258
+ Object.defineProperty(agent, 'agentExecutor', {
259
+ get: () => ({
260
+ streamEvents: vi.fn().mockImplementation(async function* () {
261
+ // Empty generator
262
+ }),
263
+ maxIterations: 3,
264
+ }),
265
+ configurable: true,
266
+ });
267
+ vi.spyOn(agent, 'initialize').mockResolvedValue(undefined);
268
+ vi.spyOn(agent.telemetry, 'trackAgentExecution').mockResolvedValue(undefined);
269
+ const events = [];
270
+ for await (const event of agent.streamEvents('test query')) {
271
+ events.push(event);
272
+ }
273
+ expect(events).toHaveLength(0);
274
+ });
275
+ it('should handle malformed events gracefully', async () => {
276
+ const mockLLM = {
277
+ invoke: vi.fn().mockResolvedValue({ content: 'Test response' }),
278
+ _modelType: 'chat_anthropic',
279
+ };
280
+ const mockClient = new MCPClient({});
281
+ const agent = new MCPAgent({
282
+ llm: mockLLM,
283
+ client: mockClient,
284
+ maxSteps: 3,
285
+ });
286
+ // Mock malformed event stream
287
+ Object.defineProperty(agent, 'agentExecutor', {
288
+ get: () => ({
289
+ streamEvents: vi.fn().mockImplementation(async function* () {
290
+ yield { event: 'malformed' }; // Missing required fields
291
+ yield null; // Invalid event
292
+ yield { event: 'on_chat_model_stream', data: { chunk: { content: 'test' } } };
293
+ }),
294
+ maxIterations: 3,
295
+ }),
296
+ configurable: true,
297
+ });
298
+ vi.spyOn(agent, 'initialize').mockResolvedValue(undefined);
299
+ vi.spyOn(agent.telemetry, 'trackAgentExecution').mockResolvedValue(undefined);
300
+ const events = [];
301
+ for await (const event of agent.streamEvents('test query')) {
302
+ events.push(event);
303
+ }
304
+ expect(events).toHaveLength(3); // Should still yield all events, even malformed ones
305
+ });
306
+ });
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Simple tests for MCPAgent streamEvents() method
3
+ *
4
+ * These tests verify the basic functionality of streamEvents() using minimal mocking
5
+ */
6
+ export {};
7
+ //# sourceMappingURL=stream_events_simple.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stream_events_simple.test.d.ts","sourceRoot":"","sources":["../../tests/stream_events_simple.test.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}