@youdotcom-oss/mcp 1.3.8 → 1.3.9
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 +879 -774
- package/dist/contents/contents.schemas.d.ts +39 -0
- package/dist/contents/contents.utils.d.ts +28 -0
- package/dist/contents/register-contents-tool.d.ts +10 -0
- package/dist/express/express.schemas.d.ts +56 -0
- package/dist/express/express.utils.d.ts +45 -0
- package/dist/express/register-express-tool.d.ts +6 -0
- package/dist/get-mcp-server.d.ts +2 -0
- package/dist/http.d.ts +3 -0
- package/{src/utils.ts → dist/main.d.ts} +1 -0
- package/dist/search/register-search-tool.d.ts +6 -0
- package/dist/search/search.schemas.d.ts +131 -0
- package/dist/search/search.utils.d.ts +96 -0
- package/dist/shared/api-constants.d.ts +9 -0
- package/dist/shared/check-response-for-errors.d.ts +6 -0
- package/dist/shared/format-search-results-text.d.ts +19 -0
- package/dist/shared/generate-error-report-link.d.ts +9 -0
- package/dist/shared/get-logger.d.ts +7 -0
- package/dist/shared/use-client-version.d.ts +6 -0
- package/dist/stdio.d.ts +2 -0
- package/package.json +14 -7
- package/src/contents/contents.utils.ts +1 -2
- package/src/contents/tests/contents.utils.spec.ts +69 -57
- package/src/express/express.utils.ts +2 -4
- package/src/express/tests/express.utils.spec.ts +74 -62
- package/src/main.ts +9 -0
- package/src/search/search.utils.ts +2 -1
- package/src/search/tests/search.utils.spec.ts +114 -92
- package/src/shared/api-constants.ts +10 -0
- package/src/tests/http.spec.ts +35 -31
- package/src/tests/tool.spec.ts +490 -402
package/src/tests/tool.spec.ts
CHANGED
|
@@ -40,339 +40,415 @@ describe('registerSearchTool', () => {
|
|
|
40
40
|
expect(searchTool?.description).toContain('Web and news search');
|
|
41
41
|
});
|
|
42
42
|
|
|
43
|
-
test(
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
43
|
+
test(
|
|
44
|
+
'performs basic search successfully',
|
|
45
|
+
async () => {
|
|
46
|
+
const result = await client.callTool({
|
|
47
|
+
name: 'you-search',
|
|
48
|
+
arguments: {
|
|
49
|
+
query: 'javascript tutorial',
|
|
50
|
+
count: 3,
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
const content = result.content as { type: string; text: string }[];
|
|
54
|
+
expect(result).toHaveProperty('content');
|
|
55
|
+
expect(Array.isArray(content)).toBe(true);
|
|
56
|
+
expect(content[0]).toHaveProperty('type', 'text');
|
|
57
|
+
expect(content[0]).toHaveProperty('text');
|
|
58
|
+
|
|
59
|
+
const text = content[0]?.text;
|
|
60
|
+
expect(text).toContain('Search Results for');
|
|
61
|
+
expect(text).toContain('javascript tutorial');
|
|
62
|
+
const structuredContent = result.structuredContent as SearchStructuredContent;
|
|
63
|
+
// Should have structured content with minimal format
|
|
64
|
+
expect(result).toHaveProperty('structuredContent');
|
|
65
|
+
expect(structuredContent).toHaveProperty('resultCounts');
|
|
66
|
+
expect(structuredContent.resultCounts).toHaveProperty('web');
|
|
67
|
+
expect(structuredContent.resultCounts).toHaveProperty('news');
|
|
68
|
+
expect(structuredContent.resultCounts).toHaveProperty('total');
|
|
69
|
+
},
|
|
70
|
+
{ retry: 2 },
|
|
71
|
+
);
|
|
68
72
|
|
|
69
|
-
test(
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
const text = content[0]?.text;
|
|
80
|
-
expect(text).toContain('WEB RESULTS:');
|
|
81
|
-
expect(text).toContain('Title:');
|
|
82
|
-
// URL should NOT be in text content anymore
|
|
83
|
-
expect(text).not.toContain('URL:');
|
|
84
|
-
expect(text).toContain('Description:');
|
|
85
|
-
expect(text).toContain('Snippets:');
|
|
86
|
-
|
|
87
|
-
// Verify structured data has result counts
|
|
88
|
-
const structuredContent = result.structuredContent as SearchStructuredContent;
|
|
89
|
-
expect(structuredContent.resultCounts.web).toBeGreaterThan(0);
|
|
90
|
-
expect(structuredContent.resultCounts.total).toBeGreaterThan(0);
|
|
91
|
-
|
|
92
|
-
// URLs should be in structuredContent.results
|
|
93
|
-
expect(structuredContent.results).toBeDefined();
|
|
94
|
-
expect(structuredContent.results?.web).toBeDefined();
|
|
95
|
-
expect(structuredContent.results?.web?.length).toBeGreaterThan(0);
|
|
96
|
-
expect(structuredContent.results?.web?.[0]).toHaveProperty('url');
|
|
97
|
-
expect(structuredContent.results?.web?.[0]).toHaveProperty('title');
|
|
98
|
-
});
|
|
73
|
+
test(
|
|
74
|
+
'handles search with web results formatting',
|
|
75
|
+
async () => {
|
|
76
|
+
const result = await client.callTool({
|
|
77
|
+
name: 'you-search',
|
|
78
|
+
arguments: {
|
|
79
|
+
query: 'react components',
|
|
80
|
+
count: 2,
|
|
81
|
+
},
|
|
82
|
+
});
|
|
99
83
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
expect(
|
|
116
|
-
expect(
|
|
117
|
-
expect(structuredContent.
|
|
118
|
-
|
|
119
|
-
|
|
84
|
+
const content = result.content as { type: string; text: string }[];
|
|
85
|
+
const text = content[0]?.text;
|
|
86
|
+
expect(text).toContain('WEB RESULTS:');
|
|
87
|
+
expect(text).toContain('Title:');
|
|
88
|
+
// URL should NOT be in text content anymore
|
|
89
|
+
expect(text).not.toContain('URL:');
|
|
90
|
+
expect(text).toContain('Description:');
|
|
91
|
+
expect(text).toContain('Snippets:');
|
|
92
|
+
|
|
93
|
+
// Verify structured data has result counts
|
|
94
|
+
const structuredContent = result.structuredContent as SearchStructuredContent;
|
|
95
|
+
expect(structuredContent.resultCounts.web).toBeGreaterThan(0);
|
|
96
|
+
expect(structuredContent.resultCounts.total).toBeGreaterThan(0);
|
|
97
|
+
|
|
98
|
+
// URLs should be in structuredContent.results
|
|
99
|
+
expect(structuredContent.results).toBeDefined();
|
|
100
|
+
expect(structuredContent.results?.web).toBeDefined();
|
|
101
|
+
expect(structuredContent.results?.web?.length).toBeGreaterThan(0);
|
|
102
|
+
expect(structuredContent.results?.web?.[0]).toHaveProperty('url');
|
|
103
|
+
expect(structuredContent.results?.web?.[0]).toHaveProperty('title');
|
|
104
|
+
},
|
|
105
|
+
{ retry: 2 },
|
|
106
|
+
);
|
|
120
107
|
|
|
121
|
-
test(
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
const text = content[0]?.text;
|
|
132
|
-
|
|
133
|
-
// Should have web results
|
|
134
|
-
expect(text).toContain('WEB RESULTS:');
|
|
135
|
-
|
|
136
|
-
const structuredContent = result.structuredContent as SearchStructuredContent;
|
|
137
|
-
// If both web and news results exist, check for separator
|
|
138
|
-
if (structuredContent.resultCounts.news > 0) {
|
|
139
|
-
expect(text).toContain('NEWS RESULTS:');
|
|
140
|
-
expect(text).toContain('='.repeat(50));
|
|
141
|
-
}
|
|
142
|
-
});
|
|
108
|
+
test(
|
|
109
|
+
'handles search with news results',
|
|
110
|
+
async () => {
|
|
111
|
+
const result = await client.callTool({
|
|
112
|
+
name: 'you-search',
|
|
113
|
+
arguments: {
|
|
114
|
+
query: 'technology news',
|
|
115
|
+
count: 2,
|
|
116
|
+
},
|
|
117
|
+
});
|
|
143
118
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
name: 'you-search',
|
|
147
|
-
arguments: {
|
|
148
|
-
query: 'recent news',
|
|
149
|
-
freshness: 'week',
|
|
150
|
-
},
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
const content = result.content as { type: string; text: string }[];
|
|
154
|
-
expect(content[0]).toHaveProperty('text');
|
|
155
|
-
expect(content[0]?.text).toContain('recent news');
|
|
156
|
-
});
|
|
119
|
+
const content = result.content as { type: string; text: string }[];
|
|
120
|
+
const text = content[0]?.text;
|
|
157
121
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
expect(content[0]).toHaveProperty('text');
|
|
169
|
-
expect(content[0]?.text).toContain('local news');
|
|
170
|
-
});
|
|
122
|
+
const structuredContent = result.structuredContent as SearchStructuredContent;
|
|
123
|
+
// Check if news results are included
|
|
124
|
+
if (structuredContent.resultCounts.news > 0) {
|
|
125
|
+
expect(text).toContain('NEWS RESULTS:');
|
|
126
|
+
expect(text).toContain('Published:');
|
|
127
|
+
expect(structuredContent.resultCounts.news).toBeGreaterThan(0);
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
{ retry: 2 },
|
|
131
|
+
);
|
|
171
132
|
|
|
172
|
-
test(
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
expect(content[0]).toHaveProperty('text');
|
|
183
|
-
expect(content[0]?.text).toContain('educational content');
|
|
184
|
-
});
|
|
133
|
+
test(
|
|
134
|
+
'handles mixed web and news results with proper separation',
|
|
135
|
+
async () => {
|
|
136
|
+
const result = await client.callTool({
|
|
137
|
+
name: 'you-search',
|
|
138
|
+
arguments: {
|
|
139
|
+
query: 'artificial intelligence',
|
|
140
|
+
count: 3,
|
|
141
|
+
},
|
|
142
|
+
});
|
|
185
143
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
name: 'you-search',
|
|
189
|
-
arguments: {
|
|
190
|
-
query: 'react components',
|
|
191
|
-
site: 'github.com',
|
|
192
|
-
},
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
const content = result.content as { type: string; text: string }[];
|
|
196
|
-
expect(content[0]?.text).toContain('react components');
|
|
197
|
-
});
|
|
144
|
+
const content = result.content as { type: string; text: string }[];
|
|
145
|
+
const text = content[0]?.text;
|
|
198
146
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
name: 'you-search',
|
|
202
|
-
arguments: {
|
|
203
|
-
query: 'documentation',
|
|
204
|
-
fileType: 'pdf',
|
|
205
|
-
},
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
const content = result.content as { type: string; text: string }[];
|
|
209
|
-
expect(content[0]?.text).toContain('documentation');
|
|
210
|
-
});
|
|
147
|
+
// Should have web results
|
|
148
|
+
expect(text).toContain('WEB RESULTS:');
|
|
211
149
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
const content = result.content as { type: string; text: string }[];
|
|
222
|
-
expect(content[0]?.text).toContain('tutorial');
|
|
223
|
-
});
|
|
150
|
+
const structuredContent = result.structuredContent as SearchStructuredContent;
|
|
151
|
+
// If both web and news results exist, check for separator
|
|
152
|
+
if (structuredContent.resultCounts.news > 0) {
|
|
153
|
+
expect(text).toContain('NEWS RESULTS:');
|
|
154
|
+
expect(text).toContain('='.repeat(50));
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
{ retry: 2 },
|
|
158
|
+
);
|
|
224
159
|
|
|
225
|
-
test(
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
expect(content[0]?.text).toContain('programming');
|
|
236
|
-
});
|
|
160
|
+
test(
|
|
161
|
+
'handles freshness parameter',
|
|
162
|
+
async () => {
|
|
163
|
+
const result = await client.callTool({
|
|
164
|
+
name: 'you-search',
|
|
165
|
+
arguments: {
|
|
166
|
+
query: 'recent news',
|
|
167
|
+
freshness: 'week',
|
|
168
|
+
},
|
|
169
|
+
});
|
|
237
170
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
},
|
|
245
|
-
});
|
|
246
|
-
|
|
247
|
-
const content = result.content as { type: string; text: string }[];
|
|
248
|
-
expect(content[0]?.text).toContain('tutorial');
|
|
249
|
-
});
|
|
171
|
+
const content = result.content as { type: string; text: string }[];
|
|
172
|
+
expect(content[0]).toHaveProperty('text');
|
|
173
|
+
expect(content[0]?.text).toContain('recent news');
|
|
174
|
+
},
|
|
175
|
+
{ retry: 2 },
|
|
176
|
+
);
|
|
250
177
|
|
|
251
|
-
test(
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
expect(content[0]?.text).toContain('programming');
|
|
262
|
-
});
|
|
178
|
+
test(
|
|
179
|
+
'handles country parameter',
|
|
180
|
+
async () => {
|
|
181
|
+
const result = await client.callTool({
|
|
182
|
+
name: 'you-search',
|
|
183
|
+
arguments: {
|
|
184
|
+
query: 'local news',
|
|
185
|
+
country: 'US',
|
|
186
|
+
},
|
|
187
|
+
});
|
|
263
188
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
},
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
const content = result.content as { type: string; text: string }[];
|
|
274
|
-
expect(content[0]?.text).toContain('programming');
|
|
275
|
-
});
|
|
189
|
+
const content = result.content as { type: string; text: string }[];
|
|
190
|
+
expect(content[0]).toHaveProperty('text');
|
|
191
|
+
expect(content[0]?.text).toContain('local news');
|
|
192
|
+
},
|
|
193
|
+
{ retry: 2 },
|
|
194
|
+
);
|
|
276
195
|
|
|
277
|
-
test(
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
site: 'github.com',
|
|
288
|
-
fileType: 'md',
|
|
289
|
-
language: 'en',
|
|
290
|
-
},
|
|
291
|
-
});
|
|
292
|
-
|
|
293
|
-
const content = result.content as { type: string; text: string }[];
|
|
294
|
-
expect(content[0]).toHaveProperty('text');
|
|
295
|
-
// Test should pass even if no results (very specific query might have no results)
|
|
296
|
-
|
|
297
|
-
// Verify results are limited by count if there are results
|
|
298
|
-
const structuredContent = result.structuredContent as SearchStructuredContent;
|
|
299
|
-
if (structuredContent.resultCounts.web > 0) {
|
|
300
|
-
expect(structuredContent.resultCounts.web).toBeLessThanOrEqual(5);
|
|
301
|
-
}
|
|
302
|
-
});
|
|
196
|
+
test(
|
|
197
|
+
'handles safesearch parameter',
|
|
198
|
+
async () => {
|
|
199
|
+
const result = await client.callTool({
|
|
200
|
+
name: 'you-search',
|
|
201
|
+
arguments: {
|
|
202
|
+
query: 'educational content',
|
|
203
|
+
safesearch: 'strict',
|
|
204
|
+
},
|
|
205
|
+
});
|
|
303
206
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
});
|
|
311
|
-
|
|
312
|
-
const content = result.content as { type: string; text: string }[];
|
|
313
|
-
expect(content[0]).toHaveProperty('text');
|
|
314
|
-
expect(content[0]?.text).toContain('C++');
|
|
315
|
-
});
|
|
207
|
+
const content = result.content as { type: string; text: string }[];
|
|
208
|
+
expect(content[0]).toHaveProperty('text');
|
|
209
|
+
expect(content[0]?.text).toContain('educational content');
|
|
210
|
+
},
|
|
211
|
+
{ retry: 2 },
|
|
212
|
+
);
|
|
316
213
|
|
|
317
|
-
test(
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
// Should still have content even if no results
|
|
328
|
-
expect(content[0]).toHaveProperty('text');
|
|
329
|
-
|
|
330
|
-
const text = content[0]?.text;
|
|
331
|
-
const structuredContent = result.structuredContent as SearchStructuredContent;
|
|
332
|
-
expect(structuredContent.resultCounts.web).toBe(0);
|
|
333
|
-
expect(structuredContent.resultCounts.news).toBe(0);
|
|
334
|
-
expect(structuredContent.resultCounts.total).toBe(0);
|
|
335
|
-
expect(text).toContain('No results found');
|
|
336
|
-
});
|
|
214
|
+
test(
|
|
215
|
+
'handles site parameter',
|
|
216
|
+
async () => {
|
|
217
|
+
const result = await client.callTool({
|
|
218
|
+
name: 'you-search',
|
|
219
|
+
arguments: {
|
|
220
|
+
query: 'react components',
|
|
221
|
+
site: 'github.com',
|
|
222
|
+
},
|
|
223
|
+
});
|
|
337
224
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
count: 2,
|
|
344
|
-
},
|
|
345
|
-
});
|
|
346
|
-
|
|
347
|
-
const structuredContent = result.structuredContent as SearchStructuredContent;
|
|
348
|
-
// Validate minimal structured content schema
|
|
349
|
-
expect(structuredContent).toHaveProperty('resultCounts');
|
|
350
|
-
|
|
351
|
-
// Check result counts structure
|
|
352
|
-
const resultCounts = structuredContent.resultCounts;
|
|
353
|
-
expect(resultCounts).toHaveProperty('web');
|
|
354
|
-
expect(resultCounts).toHaveProperty('news');
|
|
355
|
-
expect(resultCounts).toHaveProperty('total');
|
|
356
|
-
expect(typeof resultCounts.web).toBe('number');
|
|
357
|
-
expect(typeof resultCounts.news).toBe('number');
|
|
358
|
-
expect(typeof resultCounts.total).toBe('number');
|
|
359
|
-
expect(resultCounts.total).toBe(resultCounts.web + resultCounts.news);
|
|
360
|
-
});
|
|
225
|
+
const content = result.content as { type: string; text: string }[];
|
|
226
|
+
expect(content[0]?.text).toContain('react components');
|
|
227
|
+
},
|
|
228
|
+
{ retry: 2 },
|
|
229
|
+
);
|
|
361
230
|
|
|
362
|
-
test(
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
231
|
+
test(
|
|
232
|
+
'handles fileType parameter',
|
|
233
|
+
async () => {
|
|
234
|
+
const result = await client.callTool({
|
|
235
|
+
name: 'you-search',
|
|
236
|
+
arguments: {
|
|
237
|
+
query: 'documentation',
|
|
238
|
+
fileType: 'pdf',
|
|
239
|
+
},
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
const content = result.content as { type: string; text: string }[];
|
|
243
|
+
expect(content[0]?.text).toContain('documentation');
|
|
244
|
+
},
|
|
245
|
+
{ retry: 2 },
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
test(
|
|
249
|
+
'handles language parameter',
|
|
250
|
+
async () => {
|
|
251
|
+
const result = await client.callTool({
|
|
252
|
+
name: 'you-search',
|
|
253
|
+
arguments: {
|
|
254
|
+
query: 'tutorial',
|
|
255
|
+
language: 'es',
|
|
256
|
+
},
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
const content = result.content as { type: string; text: string }[];
|
|
260
|
+
expect(content[0]?.text).toContain('tutorial');
|
|
261
|
+
},
|
|
262
|
+
{ retry: 2 },
|
|
263
|
+
);
|
|
264
|
+
|
|
265
|
+
test(
|
|
266
|
+
'handles exactTerms parameter',
|
|
267
|
+
async () => {
|
|
268
|
+
const result = await client.callTool({
|
|
269
|
+
name: 'you-search',
|
|
270
|
+
arguments: {
|
|
271
|
+
query: 'programming',
|
|
272
|
+
exactTerms: 'javascript|typescript',
|
|
273
|
+
},
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
const content = result.content as { type: string; text: string }[];
|
|
277
|
+
expect(content[0]?.text).toContain('programming');
|
|
278
|
+
},
|
|
279
|
+
{ retry: 2 },
|
|
280
|
+
);
|
|
281
|
+
|
|
282
|
+
test(
|
|
283
|
+
'handles excludeTerms parameter',
|
|
284
|
+
async () => {
|
|
285
|
+
const result = await client.callTool({
|
|
286
|
+
name: 'you-search',
|
|
287
|
+
arguments: {
|
|
288
|
+
query: 'tutorial',
|
|
289
|
+
excludeTerms: 'beginner|basic',
|
|
290
|
+
},
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
const content = result.content as { type: string; text: string }[];
|
|
294
|
+
expect(content[0]?.text).toContain('tutorial');
|
|
295
|
+
},
|
|
296
|
+
{ retry: 2 },
|
|
297
|
+
);
|
|
298
|
+
|
|
299
|
+
test(
|
|
300
|
+
'handles multi-word phrases with parentheses in exactTerms',
|
|
301
|
+
async () => {
|
|
302
|
+
const result = await client.callTool({
|
|
303
|
+
name: 'you-search',
|
|
304
|
+
arguments: {
|
|
305
|
+
query: 'programming',
|
|
306
|
+
exactTerms: '(machine learning)|typescript',
|
|
307
|
+
},
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
const content = result.content as { type: string; text: string }[];
|
|
311
|
+
expect(content[0]?.text).toContain('programming');
|
|
312
|
+
},
|
|
313
|
+
{ retry: 2 },
|
|
314
|
+
);
|
|
315
|
+
|
|
316
|
+
test(
|
|
317
|
+
'handles multi-word phrases with parentheses in excludeTerms',
|
|
318
|
+
async () => {
|
|
319
|
+
const result = await client.callTool({
|
|
320
|
+
name: 'you-search',
|
|
321
|
+
arguments: {
|
|
322
|
+
query: 'programming',
|
|
323
|
+
excludeTerms: '(social media)|ads',
|
|
324
|
+
},
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
const content = result.content as { type: string; text: string }[];
|
|
328
|
+
expect(content[0]?.text).toContain('programming');
|
|
329
|
+
},
|
|
330
|
+
{ retry: 2 },
|
|
331
|
+
);
|
|
332
|
+
|
|
333
|
+
test(
|
|
334
|
+
'handles complex search with multiple parameters',
|
|
335
|
+
async () => {
|
|
336
|
+
const result = await client.callTool({
|
|
337
|
+
name: 'you-search',
|
|
338
|
+
arguments: {
|
|
339
|
+
query: 'machine learning tutorial',
|
|
340
|
+
count: 5,
|
|
341
|
+
offset: 1,
|
|
342
|
+
freshness: 'month',
|
|
343
|
+
country: 'US',
|
|
344
|
+
safesearch: 'moderate',
|
|
345
|
+
site: 'github.com',
|
|
346
|
+
fileType: 'md',
|
|
347
|
+
language: 'en',
|
|
348
|
+
},
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
const content = result.content as { type: string; text: string }[];
|
|
352
|
+
expect(content[0]).toHaveProperty('text');
|
|
353
|
+
// Test should pass even if no results (very specific query might have no results)
|
|
354
|
+
|
|
355
|
+
// Verify results are limited by count if there are results
|
|
356
|
+
const structuredContent = result.structuredContent as SearchStructuredContent;
|
|
357
|
+
if (structuredContent.resultCounts.web > 0) {
|
|
358
|
+
expect(structuredContent.resultCounts.web).toBeLessThanOrEqual(5);
|
|
359
|
+
}
|
|
360
|
+
},
|
|
361
|
+
{ retry: 2 },
|
|
362
|
+
);
|
|
363
|
+
|
|
364
|
+
test(
|
|
365
|
+
'handles special characters in query',
|
|
366
|
+
async () => {
|
|
367
|
+
const result = await client.callTool({
|
|
368
|
+
name: 'you-search',
|
|
369
|
+
arguments: {
|
|
370
|
+
query: 'C++ programming "hello world"',
|
|
371
|
+
},
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
const content = result.content as { type: string; text: string }[];
|
|
375
|
+
expect(content[0]).toHaveProperty('text');
|
|
376
|
+
expect(content[0]?.text).toContain('C++');
|
|
377
|
+
},
|
|
378
|
+
{ retry: 2 },
|
|
379
|
+
);
|
|
380
|
+
|
|
381
|
+
test(
|
|
382
|
+
'handles empty search results gracefully',
|
|
383
|
+
async () => {
|
|
384
|
+
const result = await client.callTool({
|
|
385
|
+
name: 'you-search',
|
|
386
|
+
arguments: {
|
|
387
|
+
query: '_',
|
|
388
|
+
},
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
const content = result.content as { type: string; text: string }[];
|
|
392
|
+
|
|
393
|
+
// Should still have content even if no results
|
|
394
|
+
expect(content[0]).toHaveProperty('text');
|
|
395
|
+
|
|
396
|
+
const text = content[0]?.text;
|
|
397
|
+
const structuredContent = result.structuredContent as SearchStructuredContent;
|
|
398
|
+
expect(structuredContent.resultCounts.web).toBe(0);
|
|
399
|
+
expect(structuredContent.resultCounts.news).toBe(0);
|
|
400
|
+
expect(structuredContent.resultCounts.total).toBe(0);
|
|
401
|
+
expect(text).toContain('No results found');
|
|
402
|
+
},
|
|
403
|
+
{ retry: 2 },
|
|
404
|
+
);
|
|
405
|
+
|
|
406
|
+
test(
|
|
407
|
+
'validates structured response format',
|
|
408
|
+
async () => {
|
|
409
|
+
const result = await client.callTool({
|
|
410
|
+
name: 'you-search',
|
|
411
|
+
arguments: {
|
|
412
|
+
query: `what's the latest tech news`,
|
|
413
|
+
count: 2,
|
|
414
|
+
},
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
const structuredContent = result.structuredContent as SearchStructuredContent;
|
|
418
|
+
// Validate minimal structured content schema
|
|
419
|
+
expect(structuredContent).toHaveProperty('resultCounts');
|
|
420
|
+
|
|
421
|
+
// Check result counts structure
|
|
422
|
+
const resultCounts = structuredContent.resultCounts;
|
|
423
|
+
expect(resultCounts).toHaveProperty('web');
|
|
424
|
+
expect(resultCounts).toHaveProperty('news');
|
|
425
|
+
expect(resultCounts).toHaveProperty('total');
|
|
426
|
+
expect(typeof resultCounts.web).toBe('number');
|
|
427
|
+
expect(typeof resultCounts.news).toBe('number');
|
|
428
|
+
expect(typeof resultCounts.total).toBe('number');
|
|
429
|
+
expect(resultCounts.total).toBe(resultCounts.web + resultCounts.news);
|
|
430
|
+
},
|
|
431
|
+
{ retry: 2 },
|
|
432
|
+
);
|
|
433
|
+
|
|
434
|
+
test(
|
|
435
|
+
'returns error when both exactTerms and excludeTerms are provided',
|
|
436
|
+
async () => {
|
|
437
|
+
const result = await client.callTool({
|
|
438
|
+
name: 'you-search',
|
|
439
|
+
arguments: {
|
|
440
|
+
query: 'programming',
|
|
441
|
+
exactTerms: 'javascript',
|
|
442
|
+
excludeTerms: 'beginner',
|
|
443
|
+
},
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
expect(result.isError).toBe(true);
|
|
447
|
+
const content = result.content as { type: string; text: string }[];
|
|
448
|
+
expect(content[0]?.text).toContain('Cannot specify both exactTerms and excludeTerms - please use only one');
|
|
449
|
+
},
|
|
450
|
+
{ retry: 2 },
|
|
451
|
+
);
|
|
376
452
|
|
|
377
453
|
test('handles API errors gracefully', async () => {
|
|
378
454
|
try {
|
|
@@ -387,10 +463,6 @@ describe('registerSearchTool', () => {
|
|
|
387
463
|
expect(error).toBeDefined();
|
|
388
464
|
}
|
|
389
465
|
});
|
|
390
|
-
|
|
391
|
-
test.skip('handles network timeout scenarios', async () => {
|
|
392
|
-
// TODO: How do we test this?
|
|
393
|
-
});
|
|
394
466
|
});
|
|
395
467
|
|
|
396
468
|
// NOTE: The following tests require a You.com API key with access to the Contents API
|
|
@@ -406,91 +478,107 @@ describe('registerContentsTool', () => {
|
|
|
406
478
|
expect(contentsTool?.description).toContain('Extract page content');
|
|
407
479
|
});
|
|
408
480
|
|
|
409
|
-
test(
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
expect(result).toHaveProperty('structuredContent');
|
|
420
|
-
|
|
421
|
-
const content = result.content as { type: string; text: string }[];
|
|
422
|
-
expect(Array.isArray(content)).toBe(true);
|
|
423
|
-
expect(content[0]).toHaveProperty('type', 'text');
|
|
424
|
-
expect(content[0]).toHaveProperty('text');
|
|
425
|
-
|
|
426
|
-
const text = content[0]?.text;
|
|
427
|
-
expect(text).toContain('Successfully extracted content');
|
|
428
|
-
expect(text).toContain('https://documentation.you.com/developer-resources/mcp-server');
|
|
429
|
-
expect(text).toContain('Format: markdown');
|
|
430
|
-
|
|
431
|
-
const structuredContent = result.structuredContent as ContentsStructuredContent;
|
|
432
|
-
expect(structuredContent).toHaveProperty('count', 1);
|
|
433
|
-
expect(structuredContent).toHaveProperty('format', 'markdown');
|
|
434
|
-
expect(structuredContent).toHaveProperty('items');
|
|
435
|
-
expect(structuredContent.items).toHaveLength(1);
|
|
436
|
-
|
|
437
|
-
const item = structuredContent.items[0];
|
|
438
|
-
expect(item).toBeDefined();
|
|
439
|
-
|
|
440
|
-
expect(item).toHaveProperty('url', 'https://documentation.you.com/developer-resources/mcp-server');
|
|
441
|
-
expect(item).toHaveProperty('content');
|
|
442
|
-
expect(item).toHaveProperty('contentLength');
|
|
443
|
-
expect(typeof item?.content).toBe('string');
|
|
444
|
-
expect(item?.content.length).toBeGreaterThan(0);
|
|
445
|
-
});
|
|
481
|
+
test(
|
|
482
|
+
'extracts content from a single URL',
|
|
483
|
+
async () => {
|
|
484
|
+
const result = await client.callTool({
|
|
485
|
+
name: 'you-contents',
|
|
486
|
+
arguments: {
|
|
487
|
+
urls: ['https://documentation.you.com/developer-resources/mcp-server'],
|
|
488
|
+
format: 'markdown',
|
|
489
|
+
},
|
|
490
|
+
});
|
|
446
491
|
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
492
|
+
expect(result).toHaveProperty('content');
|
|
493
|
+
expect(result).toHaveProperty('structuredContent');
|
|
494
|
+
|
|
495
|
+
const content = result.content as { type: string; text: string }[];
|
|
496
|
+
expect(Array.isArray(content)).toBe(true);
|
|
497
|
+
expect(content[0]).toHaveProperty('type', 'text');
|
|
498
|
+
expect(content[0]).toHaveProperty('text');
|
|
499
|
+
|
|
500
|
+
const text = content[0]?.text;
|
|
501
|
+
expect(text).toContain('Successfully extracted content');
|
|
502
|
+
expect(text).toContain('https://documentation.you.com/developer-resources/mcp-server');
|
|
503
|
+
expect(text).toContain('Format: markdown');
|
|
504
|
+
|
|
505
|
+
const structuredContent = result.structuredContent as ContentsStructuredContent;
|
|
506
|
+
expect(structuredContent).toHaveProperty('count', 1);
|
|
507
|
+
expect(structuredContent).toHaveProperty('format', 'markdown');
|
|
508
|
+
expect(structuredContent).toHaveProperty('items');
|
|
509
|
+
expect(structuredContent.items).toHaveLength(1);
|
|
510
|
+
|
|
511
|
+
const item = structuredContent.items[0];
|
|
512
|
+
expect(item).toBeDefined();
|
|
513
|
+
|
|
514
|
+
expect(item).toHaveProperty('url', 'https://documentation.you.com/developer-resources/mcp-server');
|
|
515
|
+
expect(item).toHaveProperty('content');
|
|
516
|
+
expect(item).toHaveProperty('contentLength');
|
|
517
|
+
expect(typeof item?.content).toBe('string');
|
|
518
|
+
expect(item?.content.length).toBeGreaterThan(0);
|
|
519
|
+
},
|
|
520
|
+
{ retry: 2 },
|
|
521
|
+
);
|
|
522
|
+
|
|
523
|
+
test(
|
|
524
|
+
'extracts content from multiple URLs',
|
|
525
|
+
async () => {
|
|
526
|
+
const result = await client.callTool({
|
|
527
|
+
name: 'you-contents',
|
|
528
|
+
arguments: {
|
|
529
|
+
urls: [
|
|
530
|
+
'https://documentation.you.com/developer-resources/mcp-server',
|
|
531
|
+
'https://documentation.you.com/developer-resources/python-sdk',
|
|
532
|
+
],
|
|
533
|
+
format: 'markdown',
|
|
534
|
+
},
|
|
535
|
+
});
|
|
467
536
|
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
537
|
+
const structuredContent = result.structuredContent as ContentsStructuredContent;
|
|
538
|
+
expect(structuredContent.count).toBe(2);
|
|
539
|
+
expect(structuredContent.items).toHaveLength(2);
|
|
540
|
+
|
|
541
|
+
const content = result.content as { type: string; text: string }[];
|
|
542
|
+
const text = content[0]?.text;
|
|
543
|
+
expect(text).toContain('Successfully extracted content from 2 URL(s)');
|
|
544
|
+
},
|
|
545
|
+
{ retry: 2 },
|
|
546
|
+
);
|
|
547
|
+
|
|
548
|
+
test(
|
|
549
|
+
'handles html format',
|
|
550
|
+
async () => {
|
|
551
|
+
const result = await client.callTool({
|
|
552
|
+
name: 'you-contents',
|
|
553
|
+
arguments: {
|
|
554
|
+
urls: ['https://documentation.you.com/developer-resources/mcp-server'],
|
|
555
|
+
format: 'html',
|
|
556
|
+
},
|
|
557
|
+
});
|
|
484
558
|
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
name: 'you-contents',
|
|
488
|
-
arguments: {
|
|
489
|
-
urls: ['https://documentation.you.com/developer-resources/mcp-server'],
|
|
490
|
-
},
|
|
491
|
-
});
|
|
559
|
+
const structuredContent = result.structuredContent as ContentsStructuredContent;
|
|
560
|
+
expect(structuredContent.format).toBe('html');
|
|
492
561
|
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
562
|
+
const content = result.content as { type: string; text: string }[];
|
|
563
|
+
const text = content[0]?.text;
|
|
564
|
+
expect(text).toContain('Format: html');
|
|
565
|
+
},
|
|
566
|
+
{ retry: 2 },
|
|
567
|
+
);
|
|
568
|
+
|
|
569
|
+
test(
|
|
570
|
+
'defaults to markdown format when not specified',
|
|
571
|
+
async () => {
|
|
572
|
+
const result = await client.callTool({
|
|
573
|
+
name: 'you-contents',
|
|
574
|
+
arguments: {
|
|
575
|
+
urls: ['https://documentation.you.com/developer-resources/mcp-server'],
|
|
576
|
+
},
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
const structuredContent = result.structuredContent as ContentsStructuredContent;
|
|
580
|
+
expect(structuredContent.format).toBe('markdown');
|
|
581
|
+
},
|
|
582
|
+
{ retry: 2 },
|
|
583
|
+
);
|
|
496
584
|
});
|