@youdotcom-oss/mcp 2.1.0 → 3.1.0
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 +27 -72
- package/bin/stdio.js +6699 -28740
- package/package.json +10 -22
- package/server.json +51 -0
- package/src/contents/contents.schemas.ts +0 -30
- package/src/contents/contents.utils.ts +0 -84
- package/src/contents/register-contents-tool.ts +0 -94
- package/src/contents/tests/contents.utils.spec.ts +0 -123
- package/src/get-mcp-server.ts +0 -17
- package/src/http.ts +0 -94
- package/src/research/register-research-tool.ts +0 -69
- package/src/research/research.schemas.ts +0 -19
- package/src/research/research.utils.ts +0 -30
- package/src/search/register-search-tool.ts +0 -87
- package/src/search/search.schema.ts +0 -38
- package/src/search/search.utils.ts +0 -70
- package/src/search/tests/search.utils.spec.ts +0 -156
- package/src/shared/format-search-results-text.ts +0 -49
- package/src/shared/get-logger.ts +0 -10
- package/src/shared/tests/shared.utils.spec.ts +0 -160
- package/src/shared/use-client-version.ts +0 -21
- package/src/stdio.ts +0 -24
- package/src/tests/http.spec.ts +0 -583
- package/src/tests/tool.spec.ts +0 -649
package/src/tests/http.spec.ts
DELETED
|
@@ -1,583 +0,0 @@
|
|
|
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'
|
|
4
|
-
import httpApp from '../http.ts'
|
|
5
|
-
import type { SearchStructuredContent } from '../search/search.schema.ts'
|
|
6
|
-
|
|
7
|
-
// Increase default timeout for hooks to prevent intermittent failures
|
|
8
|
-
setDefaultTimeout(15_000)
|
|
9
|
-
|
|
10
|
-
let server: ReturnType<typeof Bun.serve>
|
|
11
|
-
let baseUrl: string
|
|
12
|
-
let mcpClient: Client
|
|
13
|
-
const testApiKey = process.env.YDC_API_KEY
|
|
14
|
-
|
|
15
|
-
beforeAll(async () => {
|
|
16
|
-
// Start HTTP server on random port
|
|
17
|
-
const port = Math.floor(Math.random() * 10000) + 20000
|
|
18
|
-
baseUrl = `http://localhost:${port}`
|
|
19
|
-
|
|
20
|
-
// Start actual HTTP server using Bun
|
|
21
|
-
server = Bun.serve({
|
|
22
|
-
port,
|
|
23
|
-
fetch: httpApp.fetch.bind(httpApp),
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
// Wait a bit for server to start
|
|
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)
|
|
44
|
-
})
|
|
45
|
-
|
|
46
|
-
afterAll(async () => {
|
|
47
|
-
if (mcpClient) {
|
|
48
|
-
await mcpClient.close()
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
if (server) {
|
|
52
|
-
server.stop()
|
|
53
|
-
// Wait a bit for server to fully stop
|
|
54
|
-
await new Promise((resolve) => setTimeout(resolve, 100))
|
|
55
|
-
}
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
describe('HTTP Server Endpoints', () => {
|
|
59
|
-
test('health endpoint returns service status', async () => {
|
|
60
|
-
const response = await fetch(`${baseUrl}/mcp-health`)
|
|
61
|
-
|
|
62
|
-
expect(response.status).toBe(200)
|
|
63
|
-
expect(response.headers.get('content-type')).toContain('application/json')
|
|
64
|
-
|
|
65
|
-
const data = (await response.json()) as {
|
|
66
|
-
status: string
|
|
67
|
-
timestamp: string
|
|
68
|
-
version: string
|
|
69
|
-
service: string
|
|
70
|
-
}
|
|
71
|
-
expect(data).toHaveProperty('status', 'healthy')
|
|
72
|
-
expect(data).toHaveProperty('timestamp')
|
|
73
|
-
expect(data).toHaveProperty('version')
|
|
74
|
-
expect(data).toHaveProperty('service', 'youdotcom-mcp-server')
|
|
75
|
-
expect(typeof data.timestamp).toBe('string')
|
|
76
|
-
expect(typeof data.version).toBe('string')
|
|
77
|
-
})
|
|
78
|
-
|
|
79
|
-
test('mcp endpoint allows requests without authorization (free tier)', async () => {
|
|
80
|
-
const response = await fetch(`${baseUrl}/mcp`, {
|
|
81
|
-
method: 'POST',
|
|
82
|
-
headers: {
|
|
83
|
-
'Content-Type': 'application/json',
|
|
84
|
-
Accept: 'application/json, text/event-stream',
|
|
85
|
-
},
|
|
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
|
-
}),
|
|
99
|
-
})
|
|
100
|
-
|
|
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')
|
|
104
|
-
|
|
105
|
-
const text = await response.text()
|
|
106
|
-
expect(text).toContain('data:')
|
|
107
|
-
expect(text).toContain('jsonrpc')
|
|
108
|
-
expect(text).toContain('result')
|
|
109
|
-
})
|
|
110
|
-
|
|
111
|
-
test('mcp endpoint requires Bearer token format when auth header provided', async () => {
|
|
112
|
-
const response = await fetch(`${baseUrl}/mcp`, {
|
|
113
|
-
method: 'POST',
|
|
114
|
-
headers: {
|
|
115
|
-
'Content-Type': 'application/json',
|
|
116
|
-
Authorization: 'InvalidFormat token123',
|
|
117
|
-
},
|
|
118
|
-
body: JSON.stringify({}),
|
|
119
|
-
})
|
|
120
|
-
|
|
121
|
-
expect(response.status).toBe(401)
|
|
122
|
-
expect(response.headers.get('content-type')).toContain('text/plain')
|
|
123
|
-
|
|
124
|
-
const text = await response.text()
|
|
125
|
-
expect(text).toBe('Unauthorized: Invalid Bearer token format')
|
|
126
|
-
})
|
|
127
|
-
|
|
128
|
-
test('mcp endpoint accepts valid Bearer token', async () => {
|
|
129
|
-
const response = await fetch(`${baseUrl}/mcp`, {
|
|
130
|
-
method: 'POST',
|
|
131
|
-
headers: {
|
|
132
|
-
'Content-Type': 'application/json',
|
|
133
|
-
Accept: 'application/json, text/event-stream',
|
|
134
|
-
Authorization: `Bearer ${testApiKey}`,
|
|
135
|
-
},
|
|
136
|
-
body: JSON.stringify({
|
|
137
|
-
jsonrpc: '2.0',
|
|
138
|
-
method: 'initialize',
|
|
139
|
-
id: 1,
|
|
140
|
-
params: {
|
|
141
|
-
protocolVersion: '2024-11-05',
|
|
142
|
-
capabilities: {},
|
|
143
|
-
clientInfo: {
|
|
144
|
-
name: 'test-client',
|
|
145
|
-
version: '1.0.0',
|
|
146
|
-
},
|
|
147
|
-
},
|
|
148
|
-
}),
|
|
149
|
-
})
|
|
150
|
-
|
|
151
|
-
expect(response.status).toBe(200)
|
|
152
|
-
expect(response.headers.get('content-type')).toContain('text/event-stream')
|
|
153
|
-
|
|
154
|
-
// StreamableHTTPTransport uses SSE format, so response will be streaming
|
|
155
|
-
const text = await response.text()
|
|
156
|
-
expect(text).toContain('data:')
|
|
157
|
-
expect(text).toContain('jsonrpc')
|
|
158
|
-
expect(text).toContain('result')
|
|
159
|
-
expect(text).toContain('protocolVersion')
|
|
160
|
-
expect(text).toContain('capabilities')
|
|
161
|
-
})
|
|
162
|
-
|
|
163
|
-
test('mcp endpoint with trailing slash works identically', async () => {
|
|
164
|
-
const response = await fetch(`${baseUrl}/mcp/`, {
|
|
165
|
-
method: 'POST',
|
|
166
|
-
headers: {
|
|
167
|
-
'Content-Type': 'application/json',
|
|
168
|
-
Accept: 'application/json, text/event-stream',
|
|
169
|
-
Authorization: `Bearer ${testApiKey}`,
|
|
170
|
-
},
|
|
171
|
-
body: JSON.stringify({
|
|
172
|
-
jsonrpc: '2.0',
|
|
173
|
-
method: 'initialize',
|
|
174
|
-
id: 1,
|
|
175
|
-
params: {
|
|
176
|
-
protocolVersion: '2024-11-05',
|
|
177
|
-
capabilities: {},
|
|
178
|
-
clientInfo: {
|
|
179
|
-
name: 'test-client',
|
|
180
|
-
version: '1.0.0',
|
|
181
|
-
},
|
|
182
|
-
},
|
|
183
|
-
}),
|
|
184
|
-
})
|
|
185
|
-
|
|
186
|
-
expect(response.status).toBe(200)
|
|
187
|
-
expect(response.headers.get('content-type')).toContain('text/event-stream')
|
|
188
|
-
|
|
189
|
-
const text = await response.text()
|
|
190
|
-
expect(text).toContain('data:')
|
|
191
|
-
expect(text).toContain('jsonrpc')
|
|
192
|
-
expect(text).toContain('result')
|
|
193
|
-
expect(text).toContain('protocolVersion')
|
|
194
|
-
expect(text).toContain('capabilities')
|
|
195
|
-
})
|
|
196
|
-
|
|
197
|
-
test('mcp endpoint with trailing slash allows requests without authorization', async () => {
|
|
198
|
-
const response = await fetch(`${baseUrl}/mcp/`, {
|
|
199
|
-
method: 'POST',
|
|
200
|
-
headers: {
|
|
201
|
-
'Content-Type': 'application/json',
|
|
202
|
-
Accept: 'application/json, text/event-stream',
|
|
203
|
-
},
|
|
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
|
-
}),
|
|
217
|
-
})
|
|
218
|
-
|
|
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')
|
|
222
|
-
|
|
223
|
-
const text = await response.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')
|
|
295
|
-
})
|
|
296
|
-
})
|
|
297
|
-
|
|
298
|
-
describe('HTTP MCP Endpoint Basic Functionality', () => {
|
|
299
|
-
test('mcp endpoint responds to valid Bearer token', async () => {
|
|
300
|
-
// Test that the endpoint accepts valid Bearer token and doesn't return auth error
|
|
301
|
-
const response = await fetch(`${baseUrl}/mcp`, {
|
|
302
|
-
method: 'POST',
|
|
303
|
-
headers: {
|
|
304
|
-
'Content-Type': 'application/json',
|
|
305
|
-
Accept: 'application/json, text/event-stream',
|
|
306
|
-
Authorization: `Bearer ${testApiKey}`,
|
|
307
|
-
},
|
|
308
|
-
body: JSON.stringify({
|
|
309
|
-
jsonrpc: '2.0',
|
|
310
|
-
method: 'ping',
|
|
311
|
-
id: 1,
|
|
312
|
-
}),
|
|
313
|
-
})
|
|
314
|
-
|
|
315
|
-
// Should get a response (not 401/403), even if the method isn't supported
|
|
316
|
-
expect(response.status).not.toBe(401)
|
|
317
|
-
expect(response.status).not.toBe(403)
|
|
318
|
-
|
|
319
|
-
// Should be SSE response for StreamableHTTPTransport
|
|
320
|
-
expect(response.headers.get('content-type')).toContain('text/event-stream')
|
|
321
|
-
})
|
|
322
|
-
|
|
323
|
-
test('mcp endpoint processes JSON-RPC requests', async () => {
|
|
324
|
-
// Test basic JSON-RPC structure handling
|
|
325
|
-
const response = await fetch(`${baseUrl}/mcp`, {
|
|
326
|
-
method: 'POST',
|
|
327
|
-
headers: {
|
|
328
|
-
'Content-Type': 'application/json',
|
|
329
|
-
Accept: 'application/json, text/event-stream',
|
|
330
|
-
Authorization: `Bearer ${testApiKey}`,
|
|
331
|
-
},
|
|
332
|
-
body: JSON.stringify({
|
|
333
|
-
jsonrpc: '2.0',
|
|
334
|
-
method: 'unknown-method',
|
|
335
|
-
id: 123,
|
|
336
|
-
}),
|
|
337
|
-
})
|
|
338
|
-
|
|
339
|
-
expect(response.status).toBe(200)
|
|
340
|
-
expect(response.headers.get('content-type')).toContain('text/event-stream')
|
|
341
|
-
|
|
342
|
-
// StreamableHTTPTransport uses SSE format
|
|
343
|
-
const text = await response.text()
|
|
344
|
-
expect(text).toContain('data:')
|
|
345
|
-
expect(text).toContain('jsonrpc')
|
|
346
|
-
expect(text).toContain('123')
|
|
347
|
-
})
|
|
348
|
-
|
|
349
|
-
test('mcp endpoint extracts Bearer token correctly', async () => {
|
|
350
|
-
// Test that different tokens are processed
|
|
351
|
-
const response1 = await fetch(`${baseUrl}/mcp`, {
|
|
352
|
-
method: 'POST',
|
|
353
|
-
headers: {
|
|
354
|
-
'Content-Type': 'application/json',
|
|
355
|
-
Accept: 'application/json, text/event-stream',
|
|
356
|
-
Authorization: `Bearer token123`,
|
|
357
|
-
},
|
|
358
|
-
body: JSON.stringify({
|
|
359
|
-
jsonrpc: '2.0',
|
|
360
|
-
method: 'test',
|
|
361
|
-
id: 1,
|
|
362
|
-
}),
|
|
363
|
-
})
|
|
364
|
-
|
|
365
|
-
const response2 = await fetch(`${baseUrl}/mcp`, {
|
|
366
|
-
method: 'POST',
|
|
367
|
-
headers: {
|
|
368
|
-
'Content-Type': 'application/json',
|
|
369
|
-
Accept: 'application/json, text/event-stream',
|
|
370
|
-
Authorization: `Bearer different-token`,
|
|
371
|
-
},
|
|
372
|
-
body: JSON.stringify({
|
|
373
|
-
jsonrpc: '2.0',
|
|
374
|
-
method: 'test',
|
|
375
|
-
id: 2,
|
|
376
|
-
}),
|
|
377
|
-
})
|
|
378
|
-
|
|
379
|
-
// Both should be processed (not authentication errors)
|
|
380
|
-
expect(response1.status).not.toBe(401)
|
|
381
|
-
expect(response2.status).not.toBe(401)
|
|
382
|
-
})
|
|
383
|
-
|
|
384
|
-
test('mcp endpoint uses StreamableHTTPTransport', async () => {
|
|
385
|
-
// Test that the transport is properly handling requests
|
|
386
|
-
const response = await fetch(`${baseUrl}/mcp`, {
|
|
387
|
-
method: 'POST',
|
|
388
|
-
headers: {
|
|
389
|
-
'Content-Type': 'application/json',
|
|
390
|
-
Accept: 'application/json, text/event-stream',
|
|
391
|
-
Authorization: `Bearer ${testApiKey}`,
|
|
392
|
-
},
|
|
393
|
-
body: JSON.stringify({
|
|
394
|
-
jsonrpc: '2.0',
|
|
395
|
-
method: 'test',
|
|
396
|
-
id: 42,
|
|
397
|
-
}),
|
|
398
|
-
})
|
|
399
|
-
|
|
400
|
-
expect(response.status).toBe(200)
|
|
401
|
-
expect(response.headers.get('content-type')).toContain('text/event-stream')
|
|
402
|
-
|
|
403
|
-
// StreamableHTTPTransport uses SSE format
|
|
404
|
-
const text = await response.text()
|
|
405
|
-
expect(text).toContain('data:')
|
|
406
|
-
expect(text).toContain('jsonrpc')
|
|
407
|
-
expect(text).toContain('42')
|
|
408
|
-
})
|
|
409
|
-
|
|
410
|
-
test(
|
|
411
|
-
'mcp server handles search tool request for latest tech news',
|
|
412
|
-
async () => {
|
|
413
|
-
const response = await fetch(`${baseUrl}/mcp`, {
|
|
414
|
-
method: 'POST',
|
|
415
|
-
headers: {
|
|
416
|
-
'Content-Type': 'application/json',
|
|
417
|
-
Accept: 'application/json, text/event-stream',
|
|
418
|
-
Authorization: `Bearer ${testApiKey}`,
|
|
419
|
-
},
|
|
420
|
-
body: JSON.stringify({
|
|
421
|
-
jsonrpc: '2.0',
|
|
422
|
-
method: 'tools/call',
|
|
423
|
-
id: 100,
|
|
424
|
-
params: {
|
|
425
|
-
name: 'you-search',
|
|
426
|
-
arguments: {
|
|
427
|
-
query: 'latest tech news',
|
|
428
|
-
count: 3,
|
|
429
|
-
},
|
|
430
|
-
},
|
|
431
|
-
}),
|
|
432
|
-
})
|
|
433
|
-
|
|
434
|
-
expect(response.status).toBe(200)
|
|
435
|
-
expect(response.headers.get('content-type')).toContain('text/event-stream')
|
|
436
|
-
|
|
437
|
-
const text = await response.text()
|
|
438
|
-
expect(text).toContain('data:')
|
|
439
|
-
expect(text).toContain('jsonrpc')
|
|
440
|
-
expect(text).toContain('result')
|
|
441
|
-
expect(text).toContain('latest tech news')
|
|
442
|
-
expect(text).toContain('Search Results for')
|
|
443
|
-
},
|
|
444
|
-
{ retry: 2 },
|
|
445
|
-
)
|
|
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
|
-
})
|