Skip to content
This repository was archived by the owner on May 21, 2026. It is now read-only.

Commit 4d16870

Browse files
committed
Add stream examples
1 parent 505eaff commit 4d16870

2 files changed

Lines changed: 507 additions & 0 deletions

File tree

examples/ai_sdk_example.ts

Lines changed: 267 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
/**
2+
* AI SDK Integration Example
3+
*
4+
* This example demonstrates how to use MCPAgent's streamEvents() method
5+
* with Vercel AI SDK's LangChainAdapter for building streaming UIs.
6+
*
7+
* This pattern is useful for:
8+
* - Next.js API routes with useCompletion/useChat hooks
9+
* - Real-time streaming applications
10+
* - Building chat interfaces with token-by-token updates
11+
*/
12+
13+
import type { StreamEvent } from '../index.js'
14+
import { ChatAnthropic } from '@langchain/anthropic'
15+
import { LangChainAdapter } from 'ai'
16+
import { config } from 'dotenv'
17+
import { MCPAgent, MCPClient } from '../index.js'
18+
19+
// Load environment variables
20+
config()
21+
22+
// Utility function to convert streamEvents to AI SDK compatible stream
23+
async function* streamEventsToAISDK(
24+
streamEvents: AsyncGenerator<StreamEvent, void, void>,
25+
): AsyncGenerator<string, void, void> {
26+
for await (const event of streamEvents) {
27+
// Only yield the actual content tokens from chat model streams
28+
if (event.event === 'on_chat_model_stream' && event.data?.chunk?.text) {
29+
const textContent = event.data.chunk.text
30+
if (typeof textContent === 'string' && textContent.length > 0) {
31+
yield textContent
32+
}
33+
}
34+
}
35+
}
36+
37+
// Convert async generator to ReadableStream for AI SDK compatibility
38+
function createReadableStreamFromGenerator(
39+
generator: AsyncGenerator<string, void, void>,
40+
): ReadableStream<string> {
41+
return new ReadableStream({
42+
async start(controller) {
43+
try {
44+
for await (const chunk of generator) {
45+
controller.enqueue(chunk)
46+
}
47+
controller.close()
48+
}
49+
catch (error) {
50+
controller.error(error)
51+
}
52+
},
53+
})
54+
}
55+
56+
// Enhanced adapter that includes tool information
57+
async function* streamEventsToAISDKWithTools(
58+
streamEvents: AsyncGenerator<StreamEvent, void, void>,
59+
): AsyncGenerator<string, void, void> {
60+
for await (const event of streamEvents) {
61+
switch (event.event) {
62+
case 'on_chat_model_stream':
63+
if (event.data?.chunk?.text) {
64+
const textContent = event.data.chunk.text
65+
if (typeof textContent === 'string' && textContent.length > 0) {
66+
yield textContent
67+
}
68+
}
69+
break
70+
71+
case 'on_tool_start':
72+
yield `\n🔧 Using tool: ${event.name}\n`
73+
break
74+
75+
case 'on_tool_end':
76+
yield `\n✅ Tool completed: ${event.name}\n`
77+
break
78+
}
79+
}
80+
}
81+
82+
// Example: Basic AI SDK API route handler
83+
async function createApiHandler() {
84+
const everythingServer = {
85+
mcpServers: {
86+
everything: {
87+
command: 'npx',
88+
args: ['-y', '@modelcontextprotocol/server-everything'],
89+
},
90+
},
91+
}
92+
93+
const client = new MCPClient(everythingServer)
94+
const llm = new ChatAnthropic({
95+
model: 'claude-sonnet-4-20250514',
96+
temperature: 0.1,
97+
})
98+
99+
const agent = new MCPAgent({
100+
llm,
101+
client,
102+
maxSteps: 5,
103+
verbose: false,
104+
})
105+
106+
// Simulate an API route handler
107+
const apiHandler = async (request: { prompt: string }) => {
108+
try {
109+
// Get streamEvents from MCPAgent
110+
const streamEvents = agent.streamEvents(request.prompt)
111+
112+
// Convert to AI SDK compatible format
113+
const aiSDKStream = streamEventsToAISDK(streamEvents)
114+
const readableStream = createReadableStreamFromGenerator(aiSDKStream)
115+
116+
// Use LangChainAdapter to create a Response compatible with AI SDK
117+
return LangChainAdapter.toDataStreamResponse(readableStream)
118+
}
119+
catch (error) {
120+
console.error('Error in API handler:', error)
121+
throw error
122+
}
123+
finally {
124+
await client.closeAllSessions()
125+
}
126+
}
127+
128+
return apiHandler
129+
}
130+
131+
// Example: Enhanced API handler with tool visibility
132+
async function createEnhancedApiHandler() {
133+
const everythingServer = {
134+
mcpServers: {
135+
everything: {
136+
command: 'npx',
137+
args: ['-y', '@modelcontextprotocol/server-everything'],
138+
},
139+
},
140+
}
141+
142+
const client = new MCPClient(everythingServer)
143+
const llm = new ChatAnthropic({
144+
model: 'claude-sonnet-4-20250514',
145+
temperature: 0.1,
146+
})
147+
148+
const agent = new MCPAgent({
149+
llm,
150+
client,
151+
maxSteps: 8,
152+
verbose: false,
153+
})
154+
155+
const enhancedApiHandler = async (request: { prompt: string }) => {
156+
try {
157+
const streamEvents = agent.streamEvents(request.prompt)
158+
const enhancedStream = streamEventsToAISDKWithTools(streamEvents)
159+
const readableStream = createReadableStreamFromGenerator(enhancedStream)
160+
161+
return LangChainAdapter.toDataStreamResponse(readableStream)
162+
}
163+
catch (error) {
164+
console.error('Error in enhanced API handler:', error)
165+
throw error
166+
}
167+
finally {
168+
await client.closeAllSessions()
169+
}
170+
}
171+
172+
return enhancedApiHandler
173+
}
174+
175+
// Example: Simulated Next.js API route
176+
async function simulateNextJSApiRoute() {
177+
console.log('🚀 Simulating Next.js API Route with AI SDK Integration\n')
178+
179+
const apiHandler = await createApiHandler()
180+
181+
// Simulate a request
182+
const request = {
183+
prompt: 'What\'s the current time? Also, list the files in the current directory.',
184+
}
185+
186+
console.log(`📝 Request: ${request.prompt}\n`)
187+
console.log('📡 Streaming response:\n')
188+
189+
try {
190+
const response = await apiHandler(request)
191+
192+
if (response.body) {
193+
const reader = response.body.getReader()
194+
const decoder = new TextDecoder()
195+
196+
while (true) {
197+
const { done, value } = await reader.read()
198+
if (done)
199+
break
200+
201+
const chunk = decoder.decode(value)
202+
process.stdout.write(chunk)
203+
}
204+
}
205+
}
206+
catch (error) {
207+
console.error('❌ Error:', error)
208+
}
209+
210+
console.log('\n\n✅ API Route simulation complete')
211+
}
212+
213+
// Example: Enhanced streaming with tool visibility
214+
async function simulateEnhancedStreaming() {
215+
console.log('\n\n🚀 Enhanced Streaming with Tool Visibility\n')
216+
217+
const enhancedHandler = await createEnhancedApiHandler()
218+
219+
const request = {
220+
prompt: 'Check the current time and create a file with a timestamp. Then tell me what tools you used.',
221+
}
222+
223+
console.log(`📝 Request: ${request.prompt}\n`)
224+
console.log('📡 Enhanced streaming response:\n')
225+
226+
try {
227+
const response = await enhancedHandler(request)
228+
229+
if (response.body) {
230+
const reader = response.body.getReader()
231+
const decoder = new TextDecoder()
232+
233+
while (true) {
234+
const { done, value } = await reader.read()
235+
if (done)
236+
break
237+
238+
const chunk = decoder.decode(value)
239+
process.stdout.write(chunk)
240+
}
241+
}
242+
}
243+
catch (error) {
244+
console.error('❌ Error:', error)
245+
}
246+
247+
console.log('\n\n✅ Enhanced streaming complete')
248+
}
249+
250+
// Run all examples
251+
async function runAllExamples() {
252+
await simulateNextJSApiRoute()
253+
await simulateEnhancedStreaming()
254+
}
255+
256+
// Export utilities for reuse
257+
export {
258+
createApiHandler,
259+
createEnhancedApiHandler,
260+
createReadableStreamFromGenerator,
261+
streamEventsToAISDK,
262+
streamEventsToAISDKWithTools,
263+
}
264+
265+
if (import.meta.url === `file://${process.argv[1]}`) {
266+
runAllExamples().catch(console.error)
267+
}

0 commit comments

Comments
 (0)