@youdotcom-oss/mcp 2.0.2 → 2.0.4
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/bin/stdio.js +22 -4
- package/package.json +2 -2
- package/src/http.ts +34 -12
- package/src/tests/http.spec.ts +273 -12
package/bin/stdio.js
CHANGED
|
@@ -12456,8 +12456,9 @@ var ContentsItemSchema = object({
|
|
|
12456
12456
|
});
|
|
12457
12457
|
var ContentsApiResponseSchema = array(ContentsItemSchema);
|
|
12458
12458
|
// ../api/src/shared/api.constants.ts
|
|
12459
|
-
var SEARCH_API_URL = "https://ydc-index.io/v1/search";
|
|
12460
|
-
var
|
|
12459
|
+
var SEARCH_API_URL = process.env.YDC_SEARCH_API_URL || "https://ydc-index.io/v1/search";
|
|
12460
|
+
var DEEP_SEARCH_API_URL = process.env.YDC_DEEP_SEARCH_API_URL || "https://api.you.com/v1/deep_search";
|
|
12461
|
+
var CONTENTS_API_URL = process.env.YDC_CONTENTS_API_URL || "https://ydc-index.io/v1/contents";
|
|
12461
12462
|
|
|
12462
12463
|
// ../api/src/shared/check-response-for-errors.ts
|
|
12463
12464
|
var checkResponseForErrors = (responseData) => {
|
|
@@ -12705,6 +12706,23 @@ var fetchSearchResults = async ({
|
|
|
12705
12706
|
throw new Error("Rate limited by You.com API. Please try again later.");
|
|
12706
12707
|
} else if (errorCode === 403) {
|
|
12707
12708
|
throw new Error("Forbidden. Please check your You.com API key.");
|
|
12709
|
+
} else if (errorCode === 402) {
|
|
12710
|
+
let errorMessage = "Free tier limit exceeded. Please upgrade to continue.";
|
|
12711
|
+
let upgradeUrl = "https://you.com/platform";
|
|
12712
|
+
try {
|
|
12713
|
+
const errorBody = await response.json();
|
|
12714
|
+
if (errorBody?.message) {
|
|
12715
|
+
errorMessage = errorBody.message;
|
|
12716
|
+
}
|
|
12717
|
+
if (errorBody?.upgrade_url) {
|
|
12718
|
+
upgradeUrl = errorBody.upgrade_url;
|
|
12719
|
+
}
|
|
12720
|
+
if (errorBody?.reset_at) {
|
|
12721
|
+
const resetDate = new Date(errorBody.reset_at).toLocaleDateString();
|
|
12722
|
+
errorMessage += ` Limit resets on ${resetDate}.`;
|
|
12723
|
+
}
|
|
12724
|
+
} catch {}
|
|
12725
|
+
throw new Error(`${errorMessage} Upgrade at: ${upgradeUrl}`);
|
|
12708
12726
|
}
|
|
12709
12727
|
throw new Error(`Failed to perform search. Error code: ${errorCode}`);
|
|
12710
12728
|
}
|
|
@@ -20252,7 +20270,7 @@ var EMPTY_COMPLETION_RESULT = {
|
|
|
20252
20270
|
// package.json
|
|
20253
20271
|
var package_default = {
|
|
20254
20272
|
name: "@youdotcom-oss/mcp",
|
|
20255
|
-
version: "2.0.
|
|
20273
|
+
version: "2.0.4",
|
|
20256
20274
|
description: "You.com API Model Context Protocol Server - For programmatic API access, use @youdotcom-oss/api",
|
|
20257
20275
|
license: "MIT",
|
|
20258
20276
|
engines: {
|
|
@@ -20308,7 +20326,7 @@ var package_default = {
|
|
|
20308
20326
|
},
|
|
20309
20327
|
mcpName: "io.github.youdotcom-oss/mcp",
|
|
20310
20328
|
dependencies: {
|
|
20311
|
-
"@youdotcom-oss/api": "0.
|
|
20329
|
+
"@youdotcom-oss/api": "0.3.0",
|
|
20312
20330
|
zod: "^4.3.6",
|
|
20313
20331
|
"@hono/mcp": "^0.2.3",
|
|
20314
20332
|
"@modelcontextprotocol/sdk": "^1.25.3",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@youdotcom-oss/mcp",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.4",
|
|
4
4
|
"description": "You.com API Model Context Protocol Server - For programmatic API access, use @youdotcom-oss/api",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"engines": {
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
},
|
|
57
57
|
"mcpName": "io.github.youdotcom-oss/mcp",
|
|
58
58
|
"dependencies": {
|
|
59
|
-
"@youdotcom-oss/api": "0.
|
|
59
|
+
"@youdotcom-oss/api": "0.3.0",
|
|
60
60
|
"zod": "^4.3.6",
|
|
61
61
|
"@hono/mcp": "^0.2.3",
|
|
62
62
|
"@modelcontextprotocol/sdk": "^1.25.3",
|
package/src/http.ts
CHANGED
|
@@ -17,19 +17,20 @@ const extractBearerToken = (authHeader: string | null): string | null => {
|
|
|
17
17
|
const handleMcpRequest = async (c: Context) => {
|
|
18
18
|
const authHeader = c.req.header('Authorization')
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
20
|
+
let YDC_API_KEY: string | undefined
|
|
21
|
+
|
|
22
|
+
if (authHeader) {
|
|
23
|
+
const token = extractBearerToken(authHeader)
|
|
25
24
|
|
|
26
|
-
|
|
25
|
+
if (!token) {
|
|
26
|
+
c.status(401)
|
|
27
|
+
c.header('Content-Type', 'text/plain')
|
|
28
|
+
return c.text('Unauthorized: Invalid Bearer token format')
|
|
29
|
+
}
|
|
27
30
|
|
|
28
|
-
|
|
29
|
-
c.status(401)
|
|
30
|
-
c.header('Content-Type', 'text/plain')
|
|
31
|
-
return c.text('Unauthorized: Bearer token required')
|
|
31
|
+
YDC_API_KEY = token
|
|
32
32
|
}
|
|
33
|
+
|
|
33
34
|
const mcp = getMCpServer()
|
|
34
35
|
const getUserAgent = useGetClientVersion(mcp)
|
|
35
36
|
|
|
@@ -52,6 +53,20 @@ const handleMcpRequest = async (c: Context) => {
|
|
|
52
53
|
return response
|
|
53
54
|
}
|
|
54
55
|
|
|
56
|
+
const return405MethodNotAllowed = (c: Context) => {
|
|
57
|
+
c.status(405)
|
|
58
|
+
c.header('Allow', 'POST')
|
|
59
|
+
c.header('Content-Type', 'application/json')
|
|
60
|
+
return c.json({
|
|
61
|
+
jsonrpc: '2.0',
|
|
62
|
+
error: {
|
|
63
|
+
code: -32000,
|
|
64
|
+
message: 'Method Not Allowed: Use POST to send MCP requests',
|
|
65
|
+
},
|
|
66
|
+
id: null,
|
|
67
|
+
})
|
|
68
|
+
}
|
|
69
|
+
|
|
55
70
|
const app = new Hono()
|
|
56
71
|
app.use(trimTrailingSlash())
|
|
57
72
|
|
|
@@ -64,7 +79,14 @@ app.get('/mcp-health', async (c) => {
|
|
|
64
79
|
})
|
|
65
80
|
})
|
|
66
81
|
|
|
67
|
-
|
|
68
|
-
app.
|
|
82
|
+
// POST handler for MCP requests (per MCP Streamable HTTP spec)
|
|
83
|
+
app.post('/mcp', handleMcpRequest)
|
|
84
|
+
app.post('/mcp/', handleMcpRequest)
|
|
85
|
+
|
|
86
|
+
// Fallback for other methods - returns 405 per MCP spec
|
|
87
|
+
// Spec: "The server MUST either return Content-Type: text/event-stream
|
|
88
|
+
// or else return HTTP 405 Method Not Allowed"
|
|
89
|
+
app.all('/mcp', return405MethodNotAllowed)
|
|
90
|
+
app.all('/mcp/', return405MethodNotAllowed)
|
|
69
91
|
|
|
70
92
|
export default app
|
package/src/tests/http.spec.ts
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import { afterAll, beforeAll, describe, expect, setDefaultTimeout, test } from 'bun:test'
|
|
2
|
+
import { Client } from '@modelcontextprotocol/sdk/client/index.js'
|
|
3
|
+
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'
|
|
2
4
|
import httpApp from '../http.ts'
|
|
5
|
+
import type { SearchStructuredContent } from '../search/search.schema.ts'
|
|
3
6
|
|
|
4
7
|
// Increase default timeout for hooks to prevent intermittent failures
|
|
5
8
|
setDefaultTimeout(15_000)
|
|
6
9
|
|
|
7
10
|
let server: ReturnType<typeof Bun.serve>
|
|
8
11
|
let baseUrl: string
|
|
12
|
+
let mcpClient: Client
|
|
9
13
|
const testApiKey = process.env.YDC_API_KEY
|
|
10
14
|
|
|
11
15
|
beforeAll(async () => {
|
|
@@ -21,9 +25,29 @@ beforeAll(async () => {
|
|
|
21
25
|
|
|
22
26
|
// Wait a bit for server to start
|
|
23
27
|
await new Promise((resolve) => setTimeout(resolve, 500))
|
|
28
|
+
|
|
29
|
+
// Create MCP client with HTTP transport for e2e testing
|
|
30
|
+
const transport = new StreamableHTTPClientTransport(new URL(`${baseUrl}/mcp`), {
|
|
31
|
+
requestInit: {
|
|
32
|
+
headers: {
|
|
33
|
+
Authorization: `Bearer ${testApiKey}`,
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
mcpClient = new Client({
|
|
39
|
+
name: 'test-http-client',
|
|
40
|
+
version: '1.0.0',
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
await mcpClient.connect(transport)
|
|
24
44
|
})
|
|
25
45
|
|
|
26
46
|
afterAll(async () => {
|
|
47
|
+
if (mcpClient) {
|
|
48
|
+
await mcpClient.close()
|
|
49
|
+
}
|
|
50
|
+
|
|
27
51
|
if (server) {
|
|
28
52
|
server.stop()
|
|
29
53
|
// Wait a bit for server to fully stop
|
|
@@ -52,23 +76,39 @@ describe('HTTP Server Endpoints', () => {
|
|
|
52
76
|
expect(typeof data.version).toBe('string')
|
|
53
77
|
})
|
|
54
78
|
|
|
55
|
-
test('mcp endpoint
|
|
79
|
+
test('mcp endpoint allows requests without authorization (free tier)', async () => {
|
|
56
80
|
const response = await fetch(`${baseUrl}/mcp`, {
|
|
57
81
|
method: 'POST',
|
|
58
82
|
headers: {
|
|
59
83
|
'Content-Type': 'application/json',
|
|
84
|
+
Accept: 'application/json, text/event-stream',
|
|
60
85
|
},
|
|
61
|
-
body: JSON.stringify({
|
|
86
|
+
body: JSON.stringify({
|
|
87
|
+
jsonrpc: '2.0',
|
|
88
|
+
method: 'initialize',
|
|
89
|
+
id: 1,
|
|
90
|
+
params: {
|
|
91
|
+
protocolVersion: '2024-11-05',
|
|
92
|
+
capabilities: {},
|
|
93
|
+
clientInfo: {
|
|
94
|
+
name: 'test-client',
|
|
95
|
+
version: '1.0.0',
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
}),
|
|
62
99
|
})
|
|
63
100
|
|
|
64
|
-
|
|
65
|
-
expect(response.
|
|
101
|
+
// Should succeed without auth header (free tier)
|
|
102
|
+
expect(response.status).toBe(200)
|
|
103
|
+
expect(response.headers.get('content-type')).toContain('text/event-stream')
|
|
66
104
|
|
|
67
105
|
const text = await response.text()
|
|
68
|
-
expect(text).
|
|
106
|
+
expect(text).toContain('data:')
|
|
107
|
+
expect(text).toContain('jsonrpc')
|
|
108
|
+
expect(text).toContain('result')
|
|
69
109
|
})
|
|
70
110
|
|
|
71
|
-
test('mcp endpoint requires Bearer token format', async () => {
|
|
111
|
+
test('mcp endpoint requires Bearer token format when auth header provided', async () => {
|
|
72
112
|
const response = await fetch(`${baseUrl}/mcp`, {
|
|
73
113
|
method: 'POST',
|
|
74
114
|
headers: {
|
|
@@ -82,7 +122,7 @@ describe('HTTP Server Endpoints', () => {
|
|
|
82
122
|
expect(response.headers.get('content-type')).toContain('text/plain')
|
|
83
123
|
|
|
84
124
|
const text = await response.text()
|
|
85
|
-
expect(text).toBe('Unauthorized: Bearer token
|
|
125
|
+
expect(text).toBe('Unauthorized: Invalid Bearer token format')
|
|
86
126
|
})
|
|
87
127
|
|
|
88
128
|
test('mcp endpoint accepts valid Bearer token', async () => {
|
|
@@ -154,20 +194,104 @@ describe('HTTP Server Endpoints', () => {
|
|
|
154
194
|
expect(text).toContain('capabilities')
|
|
155
195
|
})
|
|
156
196
|
|
|
157
|
-
test('mcp endpoint with trailing slash
|
|
197
|
+
test('mcp endpoint with trailing slash allows requests without authorization', async () => {
|
|
158
198
|
const response = await fetch(`${baseUrl}/mcp/`, {
|
|
159
199
|
method: 'POST',
|
|
160
200
|
headers: {
|
|
161
201
|
'Content-Type': 'application/json',
|
|
202
|
+
Accept: 'application/json, text/event-stream',
|
|
162
203
|
},
|
|
163
|
-
body: JSON.stringify({
|
|
204
|
+
body: JSON.stringify({
|
|
205
|
+
jsonrpc: '2.0',
|
|
206
|
+
method: 'initialize',
|
|
207
|
+
id: 1,
|
|
208
|
+
params: {
|
|
209
|
+
protocolVersion: '2024-11-05',
|
|
210
|
+
capabilities: {},
|
|
211
|
+
clientInfo: {
|
|
212
|
+
name: 'test-client',
|
|
213
|
+
version: '1.0.0',
|
|
214
|
+
},
|
|
215
|
+
},
|
|
216
|
+
}),
|
|
164
217
|
})
|
|
165
218
|
|
|
166
|
-
|
|
167
|
-
expect(response.
|
|
219
|
+
// Should succeed without auth header (free tier)
|
|
220
|
+
expect(response.status).toBe(200)
|
|
221
|
+
expect(response.headers.get('content-type')).toContain('text/event-stream')
|
|
168
222
|
|
|
169
223
|
const text = await response.text()
|
|
170
|
-
expect(text).
|
|
224
|
+
expect(text).toContain('data:')
|
|
225
|
+
expect(text).toContain('jsonrpc')
|
|
226
|
+
})
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
describe('HTTP Method Handling', () => {
|
|
230
|
+
test('mcp endpoint returns 405 for GET requests per MCP spec', async () => {
|
|
231
|
+
const response = await fetch(`${baseUrl}/mcp`, {
|
|
232
|
+
method: 'GET',
|
|
233
|
+
headers: {
|
|
234
|
+
Accept: 'text/event-stream',
|
|
235
|
+
Authorization: `Bearer ${testApiKey}`,
|
|
236
|
+
},
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
// Verify 405 status
|
|
240
|
+
expect(response.status).toBe(405)
|
|
241
|
+
|
|
242
|
+
// Verify Allow header (RFC 9110 §15.5.6)
|
|
243
|
+
expect(response.headers.get('allow')).toBe('POST')
|
|
244
|
+
|
|
245
|
+
// Verify JSON-RPC error format
|
|
246
|
+
expect(response.headers.get('content-type')).toContain('application/json')
|
|
247
|
+
|
|
248
|
+
const data = (await response.json()) as {
|
|
249
|
+
jsonrpc: string
|
|
250
|
+
error: { code: number; message: string }
|
|
251
|
+
id: null
|
|
252
|
+
}
|
|
253
|
+
expect(data).toHaveProperty('jsonrpc', '2.0')
|
|
254
|
+
expect(data).toHaveProperty('error')
|
|
255
|
+
expect(data.error).toHaveProperty('code', -32000)
|
|
256
|
+
expect(data.error.message).toContain('Method Not Allowed')
|
|
257
|
+
})
|
|
258
|
+
|
|
259
|
+
test('mcp endpoint returns 405 for DELETE requests', async () => {
|
|
260
|
+
const response = await fetch(`${baseUrl}/mcp`, {
|
|
261
|
+
method: 'DELETE',
|
|
262
|
+
headers: {
|
|
263
|
+
Authorization: `Bearer ${testApiKey}`,
|
|
264
|
+
},
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
expect(response.status).toBe(405)
|
|
268
|
+
expect(response.headers.get('allow')).toBe('POST')
|
|
269
|
+
})
|
|
270
|
+
|
|
271
|
+
test('mcp endpoint returns 405 for PUT requests', async () => {
|
|
272
|
+
const response = await fetch(`${baseUrl}/mcp`, {
|
|
273
|
+
method: 'PUT',
|
|
274
|
+
headers: {
|
|
275
|
+
'Content-Type': 'application/json',
|
|
276
|
+
Authorization: `Bearer ${testApiKey}`,
|
|
277
|
+
},
|
|
278
|
+
body: JSON.stringify({}),
|
|
279
|
+
})
|
|
280
|
+
|
|
281
|
+
expect(response.status).toBe(405)
|
|
282
|
+
expect(response.headers.get('allow')).toBe('POST')
|
|
283
|
+
})
|
|
284
|
+
|
|
285
|
+
test('mcp endpoint with trailing slash returns 405 for GET', async () => {
|
|
286
|
+
const response = await fetch(`${baseUrl}/mcp/`, {
|
|
287
|
+
method: 'GET',
|
|
288
|
+
headers: {
|
|
289
|
+
Accept: 'text/event-stream',
|
|
290
|
+
},
|
|
291
|
+
})
|
|
292
|
+
|
|
293
|
+
expect(response.status).toBe(405)
|
|
294
|
+
expect(response.headers.get('allow')).toBe('POST')
|
|
171
295
|
})
|
|
172
296
|
})
|
|
173
297
|
|
|
@@ -320,3 +444,140 @@ describe('HTTP MCP Endpoint Basic Functionality', () => {
|
|
|
320
444
|
{ retry: 2 },
|
|
321
445
|
)
|
|
322
446
|
})
|
|
447
|
+
|
|
448
|
+
describe('HTTP MCP Client E2E Tests', () => {
|
|
449
|
+
test('mcp client can initialize and list tools', async () => {
|
|
450
|
+
const tools = await mcpClient.listTools()
|
|
451
|
+
|
|
452
|
+
expect(tools.tools).toBeDefined()
|
|
453
|
+
expect(Array.isArray(tools.tools)).toBe(true)
|
|
454
|
+
expect(tools.tools.length).toBeGreaterThan(0)
|
|
455
|
+
|
|
456
|
+
// Verify search tool is available
|
|
457
|
+
const searchTool = tools.tools.find((t) => t.name === 'you-search')
|
|
458
|
+
expect(searchTool).toBeDefined()
|
|
459
|
+
expect(searchTool?.title).toBe('Web Search')
|
|
460
|
+
|
|
461
|
+
// Verify contents tool is available
|
|
462
|
+
const contentsTool = tools.tools.find((t) => t.name === 'you-contents')
|
|
463
|
+
expect(contentsTool).toBeDefined()
|
|
464
|
+
expect(contentsTool?.title).toBe('Extract Web Page Contents')
|
|
465
|
+
})
|
|
466
|
+
|
|
467
|
+
test(
|
|
468
|
+
'mcp client can call search tool successfully',
|
|
469
|
+
async () => {
|
|
470
|
+
const result = await mcpClient.callTool({
|
|
471
|
+
name: 'you-search',
|
|
472
|
+
arguments: {
|
|
473
|
+
query: 'typescript tutorial',
|
|
474
|
+
count: 2,
|
|
475
|
+
},
|
|
476
|
+
})
|
|
477
|
+
|
|
478
|
+
expect(result).toHaveProperty('content')
|
|
479
|
+
expect(result).toHaveProperty('structuredContent')
|
|
480
|
+
|
|
481
|
+
const content = result.content as { type: string; text: string }[]
|
|
482
|
+
expect(Array.isArray(content)).toBe(true)
|
|
483
|
+
expect(content[0]).toHaveProperty('type', 'text')
|
|
484
|
+
expect(content[0]).toHaveProperty('text')
|
|
485
|
+
|
|
486
|
+
const text = content[0]?.text
|
|
487
|
+
expect(text).toContain('Search Results for')
|
|
488
|
+
expect(text).toContain('typescript tutorial')
|
|
489
|
+
|
|
490
|
+
const structuredContent = result.structuredContent as SearchStructuredContent
|
|
491
|
+
expect(structuredContent).toHaveProperty('resultCounts')
|
|
492
|
+
expect(structuredContent.resultCounts).toHaveProperty('web')
|
|
493
|
+
expect(structuredContent.resultCounts).toHaveProperty('total')
|
|
494
|
+
},
|
|
495
|
+
{ retry: 2 },
|
|
496
|
+
)
|
|
497
|
+
|
|
498
|
+
test(
|
|
499
|
+
'mcp client handles search with multiple results',
|
|
500
|
+
async () => {
|
|
501
|
+
const result = await mcpClient.callTool({
|
|
502
|
+
name: 'you-search',
|
|
503
|
+
arguments: {
|
|
504
|
+
query: 'javascript frameworks',
|
|
505
|
+
count: 3,
|
|
506
|
+
},
|
|
507
|
+
})
|
|
508
|
+
|
|
509
|
+
const content = result.content as { type: string; text: string }[]
|
|
510
|
+
const text = content[0]?.text
|
|
511
|
+
|
|
512
|
+
expect(text).toContain('WEB RESULTS:')
|
|
513
|
+
expect(text).toContain('Title:')
|
|
514
|
+
expect(text).toContain('URL:')
|
|
515
|
+
|
|
516
|
+
const structuredContent = result.structuredContent as SearchStructuredContent
|
|
517
|
+
expect(structuredContent.resultCounts.web).toBeGreaterThan(0)
|
|
518
|
+
expect(structuredContent.results?.web).toBeDefined()
|
|
519
|
+
expect(structuredContent.results?.web?.length).toBeGreaterThanOrEqual(1)
|
|
520
|
+
},
|
|
521
|
+
{ retry: 2 },
|
|
522
|
+
)
|
|
523
|
+
|
|
524
|
+
test(
|
|
525
|
+
'mcp client handles search parameters correctly',
|
|
526
|
+
async () => {
|
|
527
|
+
const result = await mcpClient.callTool({
|
|
528
|
+
name: 'you-search',
|
|
529
|
+
arguments: {
|
|
530
|
+
query: 'programming tutorials',
|
|
531
|
+
count: 2,
|
|
532
|
+
safesearch: 'strict',
|
|
533
|
+
freshness: 'month',
|
|
534
|
+
},
|
|
535
|
+
})
|
|
536
|
+
|
|
537
|
+
const content = result.content as { type: string; text: string }[]
|
|
538
|
+
expect(content[0]?.text).toContain('programming tutorials')
|
|
539
|
+
|
|
540
|
+
const structuredContent = result.structuredContent as SearchStructuredContent
|
|
541
|
+
expect(structuredContent).toHaveProperty('resultCounts')
|
|
542
|
+
},
|
|
543
|
+
{ retry: 2 },
|
|
544
|
+
)
|
|
545
|
+
|
|
546
|
+
test('mcp client maintains connection across multiple requests', async () => {
|
|
547
|
+
// First request
|
|
548
|
+
const result1 = await mcpClient.callTool({
|
|
549
|
+
name: 'you-search',
|
|
550
|
+
arguments: {
|
|
551
|
+
query: 'test query 1',
|
|
552
|
+
count: 1,
|
|
553
|
+
},
|
|
554
|
+
})
|
|
555
|
+
|
|
556
|
+
expect(result1).toHaveProperty('content')
|
|
557
|
+
|
|
558
|
+
// Second request - should work without reconnecting
|
|
559
|
+
const result2 = await mcpClient.callTool({
|
|
560
|
+
name: 'you-search',
|
|
561
|
+
arguments: {
|
|
562
|
+
query: 'test query 2',
|
|
563
|
+
count: 1,
|
|
564
|
+
},
|
|
565
|
+
})
|
|
566
|
+
|
|
567
|
+
expect(result2).toHaveProperty('content')
|
|
568
|
+
|
|
569
|
+
// Both should have succeeded
|
|
570
|
+
const content1 = result1.content as { type: string; text: string }[]
|
|
571
|
+
const content2 = result2.content as { type: string; text: string }[]
|
|
572
|
+
|
|
573
|
+
expect(content1[0]?.text).toContain('test query 1')
|
|
574
|
+
expect(content2[0]?.text).toContain('test query 2')
|
|
575
|
+
})
|
|
576
|
+
|
|
577
|
+
test('mcp client can list server capabilities', async () => {
|
|
578
|
+
// The client should have received server info during initialization
|
|
579
|
+
// We can verify this by checking that tools are available
|
|
580
|
+
const tools = await mcpClient.listTools()
|
|
581
|
+
expect(tools.tools.length).toBeGreaterThan(0)
|
|
582
|
+
})
|
|
583
|
+
})
|