@youdotcom-oss/mcp 2.0.7 → 2.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 +16 -4
- package/bin/stdio.js +272 -49
- package/package.json +2 -2
- package/src/http.ts +2 -0
- package/src/research/register-research-tool.ts +69 -0
- package/src/research/research.schemas.ts +19 -0
- package/src/research/research.utils.ts +30 -0
- package/src/stdio.ts +2 -0
- package/src/tests/tool.spec.ts +128 -2
package/README.md
CHANGED
|
@@ -5,6 +5,7 @@ The You.com MCP Server gives your AI agents **real-time access to the latest web
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
7
|
- **Web and news search**: Comprehensive search using You.com's unified Search API with advanced search operators
|
|
8
|
+
- **Research**: Comprehensive answers with cited sources, configurable effort (lite to exhaustive)
|
|
8
9
|
- **Content extraction**: Extract and retrieve full content from web pages in markdown or HTML format
|
|
9
10
|
- **Multiple transport protocols**: STDIO and Streamable HTTP support
|
|
10
11
|
- **Bearer Token Authentication**: Secure API access in HTTP mode
|
|
@@ -180,13 +181,18 @@ For setup, follow the MCP installation [guide](https://zed.dev/docs/ai/mcp#as-cu
|
|
|
180
181
|
|
|
181
182
|
## Available tools
|
|
182
183
|
|
|
183
|
-
This MCP server provides
|
|
184
|
+
This MCP server provides three tools that work seamlessly with your AI agent through natural language:
|
|
184
185
|
|
|
185
186
|
### you-search
|
|
186
187
|
Comprehensive web and news search with advanced filtering capabilities. Perfect for finding current information, research articles, documentation, and news stories.
|
|
187
188
|
|
|
188
189
|
**When to use**: When you need to search the web for information, filter by specific sites/file types, or get the latest news on a topic.
|
|
189
190
|
|
|
191
|
+
### you-research
|
|
192
|
+
Research with comprehensive answers and cited sources. Configurable effort levels (lite, standard, deep, exhaustive) let you trade speed for thoroughness.
|
|
193
|
+
|
|
194
|
+
**When to use**: When you need in-depth answers to complex questions, research reports with citations, or thorough analysis that goes beyond simple search results.
|
|
195
|
+
|
|
190
196
|
### you-contents
|
|
191
197
|
Extract full page content from URLs in markdown, HTML, or structured metadata formats. Useful for documentation analysis, content processing, SEO data extraction, and batch URL processing.
|
|
192
198
|
|
|
@@ -208,6 +214,12 @@ Here are common scenarios showing when and how to use each tool with natural lan
|
|
|
208
214
|
- "Get the latest news about renewable energy from the past week"
|
|
209
215
|
- "Find PDF files about machine learning algorithms"
|
|
210
216
|
|
|
217
|
+
**Use you-research when:**
|
|
218
|
+
- "Give me a comprehensive analysis of the current state of AI regulation"
|
|
219
|
+
- "Research the pros and cons of WebAssembly vs JavaScript for performance-critical applications"
|
|
220
|
+
- "What are the latest breakthroughs in quantum computing? Include sources."
|
|
221
|
+
- "Provide a detailed comparison of React, Vue, and Svelte with citations"
|
|
222
|
+
|
|
211
223
|
### Content extraction & analysis
|
|
212
224
|
|
|
213
225
|
**Use you-contents when:**
|
|
@@ -221,9 +233,9 @@ Here are common scenarios showing when and how to use each tool with natural lan
|
|
|
221
233
|
### Combined workflows
|
|
222
234
|
|
|
223
235
|
Your AI agent can combine multiple tools in a single conversation:
|
|
224
|
-
1. **
|
|
225
|
-
2. **
|
|
226
|
-
3. **News +
|
|
236
|
+
1. **Search + Extract**: "Search for the best TypeScript tutorials, then extract the content from the top 3 results"
|
|
237
|
+
2. **Research + Deep Dive**: "Research WebAssembly performance benefits, then extract code samples from the cited sources"
|
|
238
|
+
3. **News + Research**: "Find recent articles about AI regulation, then research the policy implications"
|
|
227
239
|
|
|
228
240
|
### Pro tips
|
|
229
241
|
|
package/bin/stdio.js
CHANGED
|
@@ -4,25 +4,43 @@ var __getProtoOf = Object.getPrototypeOf;
|
|
|
4
4
|
var __defProp = Object.defineProperty;
|
|
5
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
6
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
function __accessProp(key) {
|
|
8
|
+
return this[key];
|
|
9
|
+
}
|
|
10
|
+
var __toESMCache_node;
|
|
11
|
+
var __toESMCache_esm;
|
|
7
12
|
var __toESM = (mod, isNodeMode, target) => {
|
|
13
|
+
var canCache = mod != null && typeof mod === "object";
|
|
14
|
+
if (canCache) {
|
|
15
|
+
var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
|
|
16
|
+
var cached = cache.get(mod);
|
|
17
|
+
if (cached)
|
|
18
|
+
return cached;
|
|
19
|
+
}
|
|
8
20
|
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
9
21
|
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
10
22
|
for (let key of __getOwnPropNames(mod))
|
|
11
23
|
if (!__hasOwnProp.call(to, key))
|
|
12
24
|
__defProp(to, key, {
|
|
13
|
-
get: (
|
|
25
|
+
get: __accessProp.bind(mod, key),
|
|
14
26
|
enumerable: true
|
|
15
27
|
});
|
|
28
|
+
if (canCache)
|
|
29
|
+
cache.set(mod, to);
|
|
16
30
|
return to;
|
|
17
31
|
};
|
|
18
32
|
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
33
|
+
var __returnValue = (v) => v;
|
|
34
|
+
function __exportSetter(name, newValue) {
|
|
35
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
36
|
+
}
|
|
19
37
|
var __export = (target, all) => {
|
|
20
38
|
for (var name in all)
|
|
21
39
|
__defProp(target, name, {
|
|
22
40
|
get: all[name],
|
|
23
41
|
enumerable: true,
|
|
24
42
|
configurable: true,
|
|
25
|
-
set: (
|
|
43
|
+
set: __exportSetter.bind(all, name)
|
|
26
44
|
});
|
|
27
45
|
};
|
|
28
46
|
|
|
@@ -6498,7 +6516,7 @@ var require_dist = __commonJS((exports, module) => {
|
|
|
6498
6516
|
exports.default = formatsPlugin;
|
|
6499
6517
|
});
|
|
6500
6518
|
|
|
6501
|
-
// ../../node_modules/.bun/@modelcontextprotocol+sdk@1.
|
|
6519
|
+
// ../../node_modules/.bun/@modelcontextprotocol+sdk@1.27.1+702d06f8eea47248/node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js
|
|
6502
6520
|
import process3 from "node:process";
|
|
6503
6521
|
|
|
6504
6522
|
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/classic/external.js
|
|
@@ -20034,7 +20052,7 @@ function date4(params) {
|
|
|
20034
20052
|
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/classic/external.js
|
|
20035
20053
|
config(en_default());
|
|
20036
20054
|
|
|
20037
|
-
// ../../node_modules/.bun/@modelcontextprotocol+sdk@1.
|
|
20055
|
+
// ../../node_modules/.bun/@modelcontextprotocol+sdk@1.27.1+702d06f8eea47248/node_modules/@modelcontextprotocol/sdk/dist/esm/types.js
|
|
20038
20056
|
var LATEST_PROTOCOL_VERSION = "2025-11-25";
|
|
20039
20057
|
var SUPPORTED_PROTOCOL_VERSIONS = [LATEST_PROTOCOL_VERSION, "2025-06-18", "2025-03-26", "2024-11-05", "2024-10-07"];
|
|
20040
20058
|
var RELATED_TASK_META_KEY = "io.modelcontextprotocol/related-task";
|
|
@@ -20876,7 +20894,7 @@ class UrlElicitationRequiredError extends McpError {
|
|
|
20876
20894
|
}
|
|
20877
20895
|
}
|
|
20878
20896
|
|
|
20879
|
-
// ../../node_modules/.bun/@modelcontextprotocol+sdk@1.
|
|
20897
|
+
// ../../node_modules/.bun/@modelcontextprotocol+sdk@1.27.1+702d06f8eea47248/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/stdio.js
|
|
20880
20898
|
class ReadBuffer {
|
|
20881
20899
|
append(chunk) {
|
|
20882
20900
|
this._buffer = this._buffer ? Buffer.concat([this._buffer, chunk]) : chunk;
|
|
@@ -20906,7 +20924,7 @@ function serializeMessage(message) {
|
|
|
20906
20924
|
`;
|
|
20907
20925
|
}
|
|
20908
20926
|
|
|
20909
|
-
// ../../node_modules/.bun/@modelcontextprotocol+sdk@1.
|
|
20927
|
+
// ../../node_modules/.bun/@modelcontextprotocol+sdk@1.27.1+702d06f8eea47248/node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js
|
|
20910
20928
|
class StdioServerTransport {
|
|
20911
20929
|
constructor(_stdin = process3.stdin, _stdout = process3.stdout) {
|
|
20912
20930
|
this._stdin = _stdin;
|
|
@@ -20990,7 +21008,7 @@ var ContentsItemSchema = object({
|
|
|
20990
21008
|
var ContentsApiResponseSchema = array(ContentsItemSchema);
|
|
20991
21009
|
// ../api/src/shared/api.constants.ts
|
|
20992
21010
|
var SEARCH_API_URL = process.env.YDC_SEARCH_API_URL || "https://api.you.com/v1/agents/search";
|
|
20993
|
-
var
|
|
21011
|
+
var RESEARCH_API_URL = process.env.YDC_RESEARCH_API_URL || "https://api.you.com/v1/research";
|
|
20994
21012
|
var CONTENTS_API_URL = process.env.YDC_CONTENTS_API_URL || "https://ydc-index.io/v1/contents";
|
|
20995
21013
|
|
|
20996
21014
|
// ../api/src/shared/check-response-for-errors.ts
|
|
@@ -21057,21 +21075,89 @@ var fetchContents = async ({
|
|
|
21057
21075
|
const parsedResults = ContentsApiResponseSchema.parse(results);
|
|
21058
21076
|
return parsedResults;
|
|
21059
21077
|
};
|
|
21060
|
-
// ../api/src/
|
|
21061
|
-
var
|
|
21062
|
-
var
|
|
21063
|
-
|
|
21064
|
-
|
|
21078
|
+
// ../api/src/research/research.schemas.ts
|
|
21079
|
+
var ResearchEffortSchema = _enum2(["lite", "standard", "deep", "exhaustive"]).describe("Controls how much time and effort the Research API spends on your question. Higher effort levels run more searches and dig deeper into sources, at the cost of a longer response time.");
|
|
21080
|
+
var ResearchQuerySchema = object({
|
|
21081
|
+
input: string2().min(1, "Input is required").describe("The research question or complex query requiring in-depth investigation and multi-step reasoning. Maximum length: 40,000 characters."),
|
|
21082
|
+
research_effort: ResearchEffortSchema.optional().default("standard").describe("Controls how much time and effort the Research API spends on your question. lite: fast answers, standard: balanced (default), deep: thorough, exhaustive: most comprehensive.")
|
|
21065
21083
|
});
|
|
21066
|
-
var
|
|
21084
|
+
var ResearchSourceSchema = object({
|
|
21067
21085
|
url: string2().describe("Source webpage URL"),
|
|
21068
|
-
title: string2().describe("Source webpage title"),
|
|
21086
|
+
title: string2().optional().describe("Source webpage title"),
|
|
21069
21087
|
snippets: array(string2()).describe("Relevant excerpts from the source page used in generating the answer")
|
|
21070
21088
|
});
|
|
21071
|
-
var
|
|
21072
|
-
|
|
21073
|
-
|
|
21089
|
+
var ResearchOutputSchema = object({
|
|
21090
|
+
content: string2().describe("Comprehensive response with inline citations, formatted in Markdown"),
|
|
21091
|
+
content_type: _enum2(["text"]).describe("The format of the content field"),
|
|
21092
|
+
sources: array(ResearchSourceSchema).describe("List of web sources used to generate the answer")
|
|
21074
21093
|
});
|
|
21094
|
+
var ResearchResponseSchema = object({
|
|
21095
|
+
output: ResearchOutputSchema.describe("The research output containing the answer and sources")
|
|
21096
|
+
});
|
|
21097
|
+
// ../api/src/research/research.utils.ts
|
|
21098
|
+
var callResearch = async ({
|
|
21099
|
+
researchQuery,
|
|
21100
|
+
YDC_API_KEY = process.env.YDC_API_KEY,
|
|
21101
|
+
getUserAgent
|
|
21102
|
+
}) => {
|
|
21103
|
+
if (!YDC_API_KEY) {
|
|
21104
|
+
throw new Error("YDC_API_KEY is required for Research API");
|
|
21105
|
+
}
|
|
21106
|
+
const response = await fetch(RESEARCH_API_URL, {
|
|
21107
|
+
method: "POST",
|
|
21108
|
+
headers: new Headers({
|
|
21109
|
+
"X-API-Key": YDC_API_KEY,
|
|
21110
|
+
"Content-Type": "application/json",
|
|
21111
|
+
"User-Agent": getUserAgent()
|
|
21112
|
+
}),
|
|
21113
|
+
body: JSON.stringify(researchQuery)
|
|
21114
|
+
});
|
|
21115
|
+
if (!response.ok) {
|
|
21116
|
+
const errorCode = response.status;
|
|
21117
|
+
if (errorCode === 429) {
|
|
21118
|
+
throw new Error("Rate limited by You.com API. Please try again later.");
|
|
21119
|
+
} else if (errorCode === 403) {
|
|
21120
|
+
throw new Error("Forbidden. Please check your You.com API key.");
|
|
21121
|
+
} else if (errorCode === 402) {
|
|
21122
|
+
throw new Error("Free tier limit exceeded. Please upgrade at: https://you.com/platform");
|
|
21123
|
+
}
|
|
21124
|
+
throw new Error(`Research API request failed. Error code: ${errorCode}`);
|
|
21125
|
+
}
|
|
21126
|
+
const data = await response.json();
|
|
21127
|
+
checkResponseForErrors(data);
|
|
21128
|
+
return ResearchResponseSchema.parse(data);
|
|
21129
|
+
};
|
|
21130
|
+
var formatResearchResponse = (response) => {
|
|
21131
|
+
const parts = [];
|
|
21132
|
+
parts.push(`# Answer
|
|
21133
|
+
`);
|
|
21134
|
+
parts.push(response.output.content);
|
|
21135
|
+
parts.push(`
|
|
21136
|
+
`);
|
|
21137
|
+
if (response.output.sources.length > 0) {
|
|
21138
|
+
parts.push(`
|
|
21139
|
+
## Sources
|
|
21140
|
+
`);
|
|
21141
|
+
for (const [index, source] of response.output.sources.entries()) {
|
|
21142
|
+
parts.push(`
|
|
21143
|
+
### ${index + 1}. ${source.title ?? source.url}
|
|
21144
|
+
`);
|
|
21145
|
+
parts.push(`**URL:** ${source.url}
|
|
21146
|
+
`);
|
|
21147
|
+
if (source.snippets.length > 0) {
|
|
21148
|
+
parts.push(`
|
|
21149
|
+
**Key Excerpts:**
|
|
21150
|
+
`);
|
|
21151
|
+
for (const snippet of source.snippets) {
|
|
21152
|
+
parts.push(`> ${snippet}
|
|
21153
|
+
`);
|
|
21154
|
+
}
|
|
21155
|
+
}
|
|
21156
|
+
}
|
|
21157
|
+
}
|
|
21158
|
+
return parts.join(`
|
|
21159
|
+
`);
|
|
21160
|
+
};
|
|
21075
21161
|
// ../api/src/search/search.schemas.ts
|
|
21076
21162
|
var LanguageSchema = _enum2([
|
|
21077
21163
|
"AR",
|
|
@@ -21157,7 +21243,6 @@ var SearchQuerySchema = object({
|
|
|
21157
21243
|
"CN",
|
|
21158
21244
|
"PL",
|
|
21159
21245
|
"PT",
|
|
21160
|
-
"PT-BR",
|
|
21161
21246
|
"PH",
|
|
21162
21247
|
"RU",
|
|
21163
21248
|
"SA",
|
|
@@ -21256,23 +21341,18 @@ var fetchSearchResults = async ({
|
|
|
21256
21341
|
} else if (errorCode === 402) {
|
|
21257
21342
|
let errorMessage = "Free tier limit exceeded. Please upgrade to continue.";
|
|
21258
21343
|
let upgradeUrl = "https://you.com/platform";
|
|
21259
|
-
|
|
21260
|
-
|
|
21261
|
-
|
|
21262
|
-
|
|
21263
|
-
|
|
21264
|
-
|
|
21265
|
-
|
|
21266
|
-
|
|
21267
|
-
|
|
21268
|
-
|
|
21269
|
-
|
|
21270
|
-
|
|
21271
|
-
const resetDate = new Date(errorBody.reset_at).toLocaleDateString();
|
|
21272
|
-
errorMessage += ` Limit resets on ${resetDate}.`;
|
|
21273
|
-
}
|
|
21274
|
-
}
|
|
21275
|
-
} catch {}
|
|
21344
|
+
const json2 = await response.json();
|
|
21345
|
+
const errorBody = ApiErrorResponseSchema.parse(json2);
|
|
21346
|
+
if (errorBody.message) {
|
|
21347
|
+
errorMessage = errorBody.message;
|
|
21348
|
+
}
|
|
21349
|
+
if (errorBody.upgrade_url) {
|
|
21350
|
+
upgradeUrl = errorBody.upgrade_url;
|
|
21351
|
+
}
|
|
21352
|
+
if (errorBody.reset_at) {
|
|
21353
|
+
const resetDate = new Date(errorBody.reset_at).toLocaleDateString();
|
|
21354
|
+
errorMessage += ` Limit resets on ${resetDate}.`;
|
|
21355
|
+
}
|
|
21276
21356
|
throw new Error(`${errorMessage} Upgrade at: ${upgradeUrl}`);
|
|
21277
21357
|
}
|
|
21278
21358
|
throw new Error(`Failed to perform search. Error code: ${errorCode}`);
|
|
@@ -25320,7 +25400,7 @@ function object2(shape, params) {
|
|
|
25320
25400
|
};
|
|
25321
25401
|
return new ZodMiniObject(def);
|
|
25322
25402
|
}
|
|
25323
|
-
// ../../node_modules/.bun/@modelcontextprotocol+sdk@1.
|
|
25403
|
+
// ../../node_modules/.bun/@modelcontextprotocol+sdk@1.27.1+702d06f8eea47248/node_modules/@modelcontextprotocol/sdk/dist/esm/server/zod-compat.js
|
|
25324
25404
|
function isZ4Schema(s) {
|
|
25325
25405
|
const schema = s;
|
|
25326
25406
|
return !!schema._zod;
|
|
@@ -25464,7 +25544,7 @@ function getLiteralValue(schema) {
|
|
|
25464
25544
|
return;
|
|
25465
25545
|
}
|
|
25466
25546
|
|
|
25467
|
-
// ../../node_modules/.bun/@modelcontextprotocol+sdk@1.
|
|
25547
|
+
// ../../node_modules/.bun/@modelcontextprotocol+sdk@1.27.1+702d06f8eea47248/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/interfaces.js
|
|
25468
25548
|
function isTerminal(status) {
|
|
25469
25549
|
return status === "completed" || status === "failed" || status === "cancelled";
|
|
25470
25550
|
}
|
|
@@ -26705,7 +26785,7 @@ var zodToJsonSchema = (schema, options) => {
|
|
|
26705
26785
|
}
|
|
26706
26786
|
return combined;
|
|
26707
26787
|
};
|
|
26708
|
-
// ../../node_modules/.bun/@modelcontextprotocol+sdk@1.
|
|
26788
|
+
// ../../node_modules/.bun/@modelcontextprotocol+sdk@1.27.1+702d06f8eea47248/node_modules/@modelcontextprotocol/sdk/dist/esm/server/zod-json-schema-compat.js
|
|
26709
26789
|
function mapMiniTarget(t) {
|
|
26710
26790
|
if (!t)
|
|
26711
26791
|
return "draft-7";
|
|
@@ -26747,7 +26827,7 @@ function parseWithCompat(schema, data) {
|
|
|
26747
26827
|
return result.data;
|
|
26748
26828
|
}
|
|
26749
26829
|
|
|
26750
|
-
// ../../node_modules/.bun/@modelcontextprotocol+sdk@1.
|
|
26830
|
+
// ../../node_modules/.bun/@modelcontextprotocol+sdk@1.27.1+702d06f8eea47248/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/protocol.js
|
|
26751
26831
|
var DEFAULT_REQUEST_TIMEOUT_MSEC = 60000;
|
|
26752
26832
|
|
|
26753
26833
|
class Protocol {
|
|
@@ -27582,7 +27662,7 @@ function mergeCapabilities(base, additional) {
|
|
|
27582
27662
|
return result;
|
|
27583
27663
|
}
|
|
27584
27664
|
|
|
27585
|
-
// ../../node_modules/.bun/@modelcontextprotocol+sdk@1.
|
|
27665
|
+
// ../../node_modules/.bun/@modelcontextprotocol+sdk@1.27.1+702d06f8eea47248/node_modules/@modelcontextprotocol/sdk/dist/esm/validation/ajv-provider.js
|
|
27586
27666
|
var import_ajv = __toESM(require_ajv(), 1);
|
|
27587
27667
|
var import_ajv_formats = __toESM(require_dist(), 1);
|
|
27588
27668
|
function createDefaultAjvInstance() {
|
|
@@ -27622,7 +27702,7 @@ class AjvJsonSchemaValidator {
|
|
|
27622
27702
|
}
|
|
27623
27703
|
}
|
|
27624
27704
|
|
|
27625
|
-
// ../../node_modules/.bun/@modelcontextprotocol+sdk@1.
|
|
27705
|
+
// ../../node_modules/.bun/@modelcontextprotocol+sdk@1.27.1+702d06f8eea47248/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/server.js
|
|
27626
27706
|
class ExperimentalServerTasks {
|
|
27627
27707
|
constructor(_server) {
|
|
27628
27708
|
this._server = _server;
|
|
@@ -27630,6 +27710,62 @@ class ExperimentalServerTasks {
|
|
|
27630
27710
|
requestStream(request, resultSchema, options) {
|
|
27631
27711
|
return this._server.requestStream(request, resultSchema, options);
|
|
27632
27712
|
}
|
|
27713
|
+
createMessageStream(params, options) {
|
|
27714
|
+
const clientCapabilities = this._server.getClientCapabilities();
|
|
27715
|
+
if ((params.tools || params.toolChoice) && !clientCapabilities?.sampling?.tools) {
|
|
27716
|
+
throw new Error("Client does not support sampling tools capability.");
|
|
27717
|
+
}
|
|
27718
|
+
if (params.messages.length > 0) {
|
|
27719
|
+
const lastMessage = params.messages[params.messages.length - 1];
|
|
27720
|
+
const lastContent = Array.isArray(lastMessage.content) ? lastMessage.content : [lastMessage.content];
|
|
27721
|
+
const hasToolResults = lastContent.some((c) => c.type === "tool_result");
|
|
27722
|
+
const previousMessage = params.messages.length > 1 ? params.messages[params.messages.length - 2] : undefined;
|
|
27723
|
+
const previousContent = previousMessage ? Array.isArray(previousMessage.content) ? previousMessage.content : [previousMessage.content] : [];
|
|
27724
|
+
const hasPreviousToolUse = previousContent.some((c) => c.type === "tool_use");
|
|
27725
|
+
if (hasToolResults) {
|
|
27726
|
+
if (lastContent.some((c) => c.type !== "tool_result")) {
|
|
27727
|
+
throw new Error("The last message must contain only tool_result content if any is present");
|
|
27728
|
+
}
|
|
27729
|
+
if (!hasPreviousToolUse) {
|
|
27730
|
+
throw new Error("tool_result blocks are not matching any tool_use from the previous message");
|
|
27731
|
+
}
|
|
27732
|
+
}
|
|
27733
|
+
if (hasPreviousToolUse) {
|
|
27734
|
+
const toolUseIds = new Set(previousContent.filter((c) => c.type === "tool_use").map((c) => c.id));
|
|
27735
|
+
const toolResultIds = new Set(lastContent.filter((c) => c.type === "tool_result").map((c) => c.toolUseId));
|
|
27736
|
+
if (toolUseIds.size !== toolResultIds.size || ![...toolUseIds].every((id) => toolResultIds.has(id))) {
|
|
27737
|
+
throw new Error("ids of tool_result blocks and tool_use blocks from previous message do not match");
|
|
27738
|
+
}
|
|
27739
|
+
}
|
|
27740
|
+
}
|
|
27741
|
+
return this.requestStream({
|
|
27742
|
+
method: "sampling/createMessage",
|
|
27743
|
+
params
|
|
27744
|
+
}, CreateMessageResultSchema, options);
|
|
27745
|
+
}
|
|
27746
|
+
elicitInputStream(params, options) {
|
|
27747
|
+
const clientCapabilities = this._server.getClientCapabilities();
|
|
27748
|
+
const mode = params.mode ?? "form";
|
|
27749
|
+
switch (mode) {
|
|
27750
|
+
case "url": {
|
|
27751
|
+
if (!clientCapabilities?.elicitation?.url) {
|
|
27752
|
+
throw new Error("Client does not support url elicitation.");
|
|
27753
|
+
}
|
|
27754
|
+
break;
|
|
27755
|
+
}
|
|
27756
|
+
case "form": {
|
|
27757
|
+
if (!clientCapabilities?.elicitation?.form) {
|
|
27758
|
+
throw new Error("Client does not support form elicitation.");
|
|
27759
|
+
}
|
|
27760
|
+
break;
|
|
27761
|
+
}
|
|
27762
|
+
}
|
|
27763
|
+
const normalizedParams = mode === "form" && params.mode === undefined ? { ...params, mode: "form" } : params;
|
|
27764
|
+
return this.requestStream({
|
|
27765
|
+
method: "elicitation/create",
|
|
27766
|
+
params: normalizedParams
|
|
27767
|
+
}, ElicitResultSchema, options);
|
|
27768
|
+
}
|
|
27633
27769
|
async getTask(taskId, options) {
|
|
27634
27770
|
return this._server.getTask({ taskId }, options);
|
|
27635
27771
|
}
|
|
@@ -27644,7 +27780,7 @@ class ExperimentalServerTasks {
|
|
|
27644
27780
|
}
|
|
27645
27781
|
}
|
|
27646
27782
|
|
|
27647
|
-
// ../../node_modules/.bun/@modelcontextprotocol+sdk@1.
|
|
27783
|
+
// ../../node_modules/.bun/@modelcontextprotocol+sdk@1.27.1+702d06f8eea47248/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/helpers.js
|
|
27648
27784
|
function assertToolsCallTaskCapability(requests, method, entityName) {
|
|
27649
27785
|
if (!requests) {
|
|
27650
27786
|
throw new Error(`${entityName} does not support task creation (required for ${method})`);
|
|
@@ -27679,7 +27815,7 @@ function assertClientRequestTaskCapability(requests, method, entityName) {
|
|
|
27679
27815
|
}
|
|
27680
27816
|
}
|
|
27681
27817
|
|
|
27682
|
-
// ../../node_modules/.bun/@modelcontextprotocol+sdk@1.
|
|
27818
|
+
// ../../node_modules/.bun/@modelcontextprotocol+sdk@1.27.1+702d06f8eea47248/node_modules/@modelcontextprotocol/sdk/dist/esm/server/index.js
|
|
27683
27819
|
class Server extends Protocol {
|
|
27684
27820
|
constructor(_serverInfo, options) {
|
|
27685
27821
|
super(options);
|
|
@@ -28012,7 +28148,7 @@ class Server extends Protocol {
|
|
|
28012
28148
|
}
|
|
28013
28149
|
}
|
|
28014
28150
|
|
|
28015
|
-
// ../../node_modules/.bun/@modelcontextprotocol+sdk@1.
|
|
28151
|
+
// ../../node_modules/.bun/@modelcontextprotocol+sdk@1.27.1+702d06f8eea47248/node_modules/@modelcontextprotocol/sdk/dist/esm/server/completable.js
|
|
28016
28152
|
var COMPLETABLE_SYMBOL = Symbol.for("mcp.completable");
|
|
28017
28153
|
function isCompletable(schema) {
|
|
28018
28154
|
return !!schema && typeof schema === "object" && COMPLETABLE_SYMBOL in schema;
|
|
@@ -28026,7 +28162,7 @@ var McpZodTypeKind;
|
|
|
28026
28162
|
McpZodTypeKind2["Completable"] = "McpCompletable";
|
|
28027
28163
|
})(McpZodTypeKind || (McpZodTypeKind = {}));
|
|
28028
28164
|
|
|
28029
|
-
// ../../node_modules/.bun/@modelcontextprotocol+sdk@1.
|
|
28165
|
+
// ../../node_modules/.bun/@modelcontextprotocol+sdk@1.27.1+702d06f8eea47248/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/toolNameValidation.js
|
|
28030
28166
|
var TOOL_NAME_REGEX = /^[A-Za-z0-9._-]{1,128}$/;
|
|
28031
28167
|
function validateToolName(name) {
|
|
28032
28168
|
const warnings = [];
|
|
@@ -28084,7 +28220,7 @@ function validateAndWarnToolName(name) {
|
|
|
28084
28220
|
return result.isValid;
|
|
28085
28221
|
}
|
|
28086
28222
|
|
|
28087
|
-
// ../../node_modules/.bun/@modelcontextprotocol+sdk@1.
|
|
28223
|
+
// ../../node_modules/.bun/@modelcontextprotocol+sdk@1.27.1+702d06f8eea47248/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/mcp-server.js
|
|
28088
28224
|
class ExperimentalMcpServerTasks {
|
|
28089
28225
|
constructor(_mcpServer) {
|
|
28090
28226
|
this._mcpServer = _mcpServer;
|
|
@@ -28099,7 +28235,7 @@ class ExperimentalMcpServerTasks {
|
|
|
28099
28235
|
}
|
|
28100
28236
|
}
|
|
28101
28237
|
|
|
28102
|
-
// ../../node_modules/.bun/@modelcontextprotocol+sdk@1.
|
|
28238
|
+
// ../../node_modules/.bun/@modelcontextprotocol+sdk@1.27.1+702d06f8eea47248/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js
|
|
28103
28239
|
class McpServer {
|
|
28104
28240
|
constructor(serverInfo, options) {
|
|
28105
28241
|
this._registeredResources = {};
|
|
@@ -28821,7 +28957,7 @@ var EMPTY_COMPLETION_RESULT = {
|
|
|
28821
28957
|
// package.json
|
|
28822
28958
|
var package_default = {
|
|
28823
28959
|
name: "@youdotcom-oss/mcp",
|
|
28824
|
-
version: "2.0
|
|
28960
|
+
version: "2.1.0",
|
|
28825
28961
|
description: "You.com API Model Context Protocol Server - For programmatic API access, use @youdotcom-oss/api",
|
|
28826
28962
|
license: "MIT",
|
|
28827
28963
|
engines: {
|
|
@@ -28877,7 +29013,7 @@ var package_default = {
|
|
|
28877
29013
|
},
|
|
28878
29014
|
mcpName: "io.github.youdotcom-oss/mcp",
|
|
28879
29015
|
dependencies: {
|
|
28880
|
-
"@youdotcom-oss/api": "0.
|
|
29016
|
+
"@youdotcom-oss/api": "0.4.0",
|
|
28881
29017
|
zod: "^4.3.6",
|
|
28882
29018
|
"@hono/mcp": "^0.2.3",
|
|
28883
29019
|
"@modelcontextprotocol/sdk": "^1.25.3",
|
|
@@ -28900,6 +29036,92 @@ var getMCpServer = () => new McpServer({
|
|
|
28900
29036
|
instructions: `Use this server to search the web, get AI-powered answers with web context, and extract content from web pages using You.com. The you-contents tool extracts page content and returns it in markdown or HTML format. Use HTML format for layout preservation, interactive content, and visual fidelity; use markdown for text extraction and simpler consumption.`
|
|
28901
29037
|
});
|
|
28902
29038
|
|
|
29039
|
+
// src/research/research.schemas.ts
|
|
29040
|
+
var ResearchStructuredContentSchema = object({
|
|
29041
|
+
content_type: string2().describe("Format of the content field"),
|
|
29042
|
+
sourceCount: number2().describe("Number of sources used"),
|
|
29043
|
+
sources: array(object({
|
|
29044
|
+
url: string2().describe("Source URL"),
|
|
29045
|
+
title: string2().optional().describe("Source title"),
|
|
29046
|
+
snippetCount: number2().describe("Number of excerpts from this source")
|
|
29047
|
+
})).describe("Sources used in the research answer")
|
|
29048
|
+
});
|
|
29049
|
+
|
|
29050
|
+
// src/research/research.utils.ts
|
|
29051
|
+
var formatResearchResults = (response) => {
|
|
29052
|
+
const text = formatResearchResponse(response);
|
|
29053
|
+
return {
|
|
29054
|
+
content: [
|
|
29055
|
+
{
|
|
29056
|
+
type: "text",
|
|
29057
|
+
text
|
|
29058
|
+
}
|
|
29059
|
+
],
|
|
29060
|
+
structuredContent: {
|
|
29061
|
+
content_type: response.output.content_type,
|
|
29062
|
+
sourceCount: response.output.sources.length,
|
|
29063
|
+
sources: response.output.sources.map((source) => ({
|
|
29064
|
+
url: source.url,
|
|
29065
|
+
title: source.title,
|
|
29066
|
+
snippetCount: source.snippets.length
|
|
29067
|
+
}))
|
|
29068
|
+
}
|
|
29069
|
+
};
|
|
29070
|
+
};
|
|
29071
|
+
|
|
29072
|
+
// src/research/register-research-tool.ts
|
|
29073
|
+
var registerResearchTool = ({
|
|
29074
|
+
mcp,
|
|
29075
|
+
YDC_API_KEY,
|
|
29076
|
+
getUserAgent
|
|
29077
|
+
}) => {
|
|
29078
|
+
mcp.registerTool("you-research", {
|
|
29079
|
+
title: "Research",
|
|
29080
|
+
description: "Research a topic with comprehensive answers and cited sources. Configurable effort levels (lite, standard, deep, exhaustive).",
|
|
29081
|
+
inputSchema: ResearchQuerySchema.shape,
|
|
29082
|
+
outputSchema: ResearchStructuredContentSchema.shape
|
|
29083
|
+
}, async (researchQuery) => {
|
|
29084
|
+
const logger = getLogger(mcp);
|
|
29085
|
+
try {
|
|
29086
|
+
const response = await callResearch({
|
|
29087
|
+
researchQuery,
|
|
29088
|
+
YDC_API_KEY,
|
|
29089
|
+
getUserAgent
|
|
29090
|
+
});
|
|
29091
|
+
const sourceCount = response.output.sources.length;
|
|
29092
|
+
await logger({
|
|
29093
|
+
level: "info",
|
|
29094
|
+
data: `Research successful for input: "${researchQuery.input.substring(0, 100)}" - ${sourceCount} source(s)`
|
|
29095
|
+
});
|
|
29096
|
+
const { content, structuredContent } = formatResearchResults(response);
|
|
29097
|
+
return { content, structuredContent };
|
|
29098
|
+
} catch (err) {
|
|
29099
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
29100
|
+
const reportLink = generateErrorReportLink({
|
|
29101
|
+
errorMessage,
|
|
29102
|
+
tool: "you-research",
|
|
29103
|
+
clientInfo: getUserAgent()
|
|
29104
|
+
});
|
|
29105
|
+
await logger({
|
|
29106
|
+
level: "error",
|
|
29107
|
+
data: `Research API call failed: ${errorMessage}
|
|
29108
|
+
|
|
29109
|
+
Report this issue: ${reportLink}`
|
|
29110
|
+
});
|
|
29111
|
+
return {
|
|
29112
|
+
content: [
|
|
29113
|
+
{
|
|
29114
|
+
type: "text",
|
|
29115
|
+
text: `Error: ${errorMessage}`
|
|
29116
|
+
}
|
|
29117
|
+
],
|
|
29118
|
+
structuredContent: undefined,
|
|
29119
|
+
isError: true
|
|
29120
|
+
};
|
|
29121
|
+
}
|
|
29122
|
+
});
|
|
29123
|
+
};
|
|
29124
|
+
|
|
28903
29125
|
// src/search/search.schema.ts
|
|
28904
29126
|
var SearchStructuredContentSchema = object({
|
|
28905
29127
|
resultCounts: object({
|
|
@@ -29103,6 +29325,7 @@ try {
|
|
|
29103
29325
|
const getUserAgent = useGetClientVersion(mcp);
|
|
29104
29326
|
registerSearchTool({ mcp, YDC_API_KEY, getUserAgent });
|
|
29105
29327
|
registerContentsTool({ mcp, YDC_API_KEY, getUserAgent });
|
|
29328
|
+
registerResearchTool({ mcp, YDC_API_KEY, getUserAgent });
|
|
29106
29329
|
const transport = new StdioServerTransport;
|
|
29107
29330
|
await mcp.connect(transport);
|
|
29108
29331
|
} catch (error48) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@youdotcom-oss/mcp",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.1.0",
|
|
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.4.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
|
@@ -4,6 +4,7 @@ import { trimTrailingSlash } from 'hono/trailing-slash'
|
|
|
4
4
|
import packageJson from '../package.json' with { type: 'json' }
|
|
5
5
|
import { registerContentsTool } from './contents/register-contents-tool.ts'
|
|
6
6
|
import { getMCpServer } from './get-mcp-server.ts'
|
|
7
|
+
import { registerResearchTool } from './research/register-research-tool.ts'
|
|
7
8
|
import { registerSearchTool } from './search/register-search-tool.ts'
|
|
8
9
|
import { useGetClientVersion } from './shared/use-client-version.ts'
|
|
9
10
|
|
|
@@ -40,6 +41,7 @@ const handleMcpRequest = async (c: Context) => {
|
|
|
40
41
|
getUserAgent,
|
|
41
42
|
})
|
|
42
43
|
registerContentsTool({ mcp, YDC_API_KEY, getUserAgent })
|
|
44
|
+
registerResearchTool({ mcp, YDC_API_KEY, getUserAgent })
|
|
43
45
|
|
|
44
46
|
const transport = new StreamableHTTPTransport()
|
|
45
47
|
await mcp.connect(transport)
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
|
|
2
|
+
import { callResearch, generateErrorReportLink, ResearchQuerySchema } from '@youdotcom-oss/api'
|
|
3
|
+
import { getLogger } from '../shared/get-logger.ts'
|
|
4
|
+
import { ResearchStructuredContentSchema } from './research.schemas.ts'
|
|
5
|
+
import { formatResearchResults } from './research.utils.ts'
|
|
6
|
+
|
|
7
|
+
export const registerResearchTool = ({
|
|
8
|
+
mcp,
|
|
9
|
+
YDC_API_KEY,
|
|
10
|
+
getUserAgent,
|
|
11
|
+
}: {
|
|
12
|
+
mcp: McpServer
|
|
13
|
+
YDC_API_KEY?: string
|
|
14
|
+
getUserAgent: () => string
|
|
15
|
+
}) => {
|
|
16
|
+
mcp.registerTool(
|
|
17
|
+
'you-research',
|
|
18
|
+
{
|
|
19
|
+
title: 'Research',
|
|
20
|
+
description:
|
|
21
|
+
'Research a topic with comprehensive answers and cited sources. Configurable effort levels (lite, standard, deep, exhaustive).',
|
|
22
|
+
inputSchema: ResearchQuerySchema.shape,
|
|
23
|
+
outputSchema: ResearchStructuredContentSchema.shape,
|
|
24
|
+
},
|
|
25
|
+
async (researchQuery) => {
|
|
26
|
+
const logger = getLogger(mcp)
|
|
27
|
+
try {
|
|
28
|
+
const response = await callResearch({
|
|
29
|
+
researchQuery,
|
|
30
|
+
YDC_API_KEY,
|
|
31
|
+
getUserAgent,
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
const sourceCount = response.output.sources.length
|
|
35
|
+
|
|
36
|
+
await logger({
|
|
37
|
+
level: 'info',
|
|
38
|
+
data: `Research successful for input: "${researchQuery.input.substring(0, 100)}" - ${sourceCount} source(s)`,
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
const { content, structuredContent } = formatResearchResults(response)
|
|
42
|
+
return { content, structuredContent }
|
|
43
|
+
} catch (err: unknown) {
|
|
44
|
+
const errorMessage = err instanceof Error ? err.message : String(err)
|
|
45
|
+
const reportLink = generateErrorReportLink({
|
|
46
|
+
errorMessage,
|
|
47
|
+
tool: 'you-research',
|
|
48
|
+
clientInfo: getUserAgent(),
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
await logger({
|
|
52
|
+
level: 'error',
|
|
53
|
+
data: `Research API call failed: ${errorMessage}\n\nReport this issue: ${reportLink}`,
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
content: [
|
|
58
|
+
{
|
|
59
|
+
type: 'text' as const,
|
|
60
|
+
text: `Error: ${errorMessage}`,
|
|
61
|
+
},
|
|
62
|
+
],
|
|
63
|
+
structuredContent: undefined,
|
|
64
|
+
isError: true,
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
)
|
|
69
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import * as z from 'zod'
|
|
2
|
+
|
|
3
|
+
// Minimal schema for structuredContent (reduces payload duplication)
|
|
4
|
+
// Full research content is in the text content field
|
|
5
|
+
export const ResearchStructuredContentSchema = z.object({
|
|
6
|
+
content_type: z.string().describe('Format of the content field'),
|
|
7
|
+
sourceCount: z.number().describe('Number of sources used'),
|
|
8
|
+
sources: z
|
|
9
|
+
.array(
|
|
10
|
+
z.object({
|
|
11
|
+
url: z.string().describe('Source URL'),
|
|
12
|
+
title: z.string().optional().describe('Source title'),
|
|
13
|
+
snippetCount: z.number().describe('Number of excerpts from this source'),
|
|
14
|
+
}),
|
|
15
|
+
)
|
|
16
|
+
.describe('Sources used in the research answer'),
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
export type ResearchStructuredContent = z.infer<typeof ResearchStructuredContentSchema>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { ResearchResponse } from '@youdotcom-oss/api'
|
|
2
|
+
import { formatResearchResponse } from '@youdotcom-oss/api'
|
|
3
|
+
import type { ResearchStructuredContent } from './research.schemas.ts'
|
|
4
|
+
|
|
5
|
+
export const formatResearchResults = (
|
|
6
|
+
response: ResearchResponse,
|
|
7
|
+
): {
|
|
8
|
+
content: Array<{ type: 'text'; text: string }>
|
|
9
|
+
structuredContent: ResearchStructuredContent
|
|
10
|
+
} => {
|
|
11
|
+
const text = formatResearchResponse(response)
|
|
12
|
+
|
|
13
|
+
return {
|
|
14
|
+
content: [
|
|
15
|
+
{
|
|
16
|
+
type: 'text',
|
|
17
|
+
text,
|
|
18
|
+
},
|
|
19
|
+
],
|
|
20
|
+
structuredContent: {
|
|
21
|
+
content_type: response.output.content_type,
|
|
22
|
+
sourceCount: response.output.sources.length,
|
|
23
|
+
sources: response.output.sources.map((source) => ({
|
|
24
|
+
url: source.url,
|
|
25
|
+
title: source.title,
|
|
26
|
+
snippetCount: source.snippets.length,
|
|
27
|
+
})),
|
|
28
|
+
},
|
|
29
|
+
}
|
|
30
|
+
}
|
package/src/stdio.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
|
|
3
3
|
import { registerContentsTool } from './contents/register-contents-tool.ts'
|
|
4
4
|
import { getMCpServer } from './get-mcp-server.ts'
|
|
5
|
+
import { registerResearchTool } from './research/register-research-tool.ts'
|
|
5
6
|
import { registerSearchTool } from './search/register-search-tool.ts'
|
|
6
7
|
import { useGetClientVersion } from './shared/use-client-version.ts'
|
|
7
8
|
|
|
@@ -13,6 +14,7 @@ try {
|
|
|
13
14
|
|
|
14
15
|
registerSearchTool({ mcp, YDC_API_KEY, getUserAgent })
|
|
15
16
|
registerContentsTool({ mcp, YDC_API_KEY, getUserAgent })
|
|
17
|
+
registerResearchTool({ mcp, YDC_API_KEY, getUserAgent })
|
|
16
18
|
|
|
17
19
|
const transport = new StdioServerTransport()
|
|
18
20
|
await mcp.connect(transport)
|
package/src/tests/tool.spec.ts
CHANGED
|
@@ -3,12 +3,13 @@ import { Client } from '@modelcontextprotocol/sdk/client/index.js'
|
|
|
3
3
|
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'
|
|
4
4
|
import { $ } from 'bun'
|
|
5
5
|
import type { ContentsStructuredContent } from '../contents/contents.schemas.ts'
|
|
6
|
+
import type { ResearchStructuredContent } from '../research/research.schemas.ts'
|
|
6
7
|
import type { SearchStructuredContent } from '../search/search.schema.ts'
|
|
7
8
|
|
|
8
9
|
let client: Client
|
|
9
10
|
|
|
10
11
|
beforeAll(async () => {
|
|
11
|
-
await $`bun run build`
|
|
12
|
+
await $`bun run build`
|
|
12
13
|
const transport = new StdioClientTransport({
|
|
13
14
|
command: 'npx',
|
|
14
15
|
args: [Bun.resolveSync('../../bin/stdio', import.meta.dir)],
|
|
@@ -23,7 +24,7 @@ beforeAll(async () => {
|
|
|
23
24
|
})
|
|
24
25
|
|
|
25
26
|
await client.connect(transport)
|
|
26
|
-
})
|
|
27
|
+
}, 30_000)
|
|
27
28
|
|
|
28
29
|
afterAll(async () => {
|
|
29
30
|
await client.close()
|
|
@@ -521,3 +522,128 @@ describe('registerContentsTool', () => {
|
|
|
521
522
|
{ retry: 2 },
|
|
522
523
|
)
|
|
523
524
|
})
|
|
525
|
+
|
|
526
|
+
describe('registerResearchTool', () => {
|
|
527
|
+
test('tool is registered and available', async () => {
|
|
528
|
+
const tools = await client.listTools()
|
|
529
|
+
|
|
530
|
+
const researchTool = tools.tools.find((t) => t.name === 'you-research')
|
|
531
|
+
|
|
532
|
+
expect(researchTool).toBeDefined()
|
|
533
|
+
expect(researchTool?.title).toBe('Research')
|
|
534
|
+
expect(researchTool?.description).toContain('Research a topic')
|
|
535
|
+
})
|
|
536
|
+
|
|
537
|
+
test(
|
|
538
|
+
'performs basic research successfully',
|
|
539
|
+
async () => {
|
|
540
|
+
const result = await client.callTool({
|
|
541
|
+
name: 'you-research',
|
|
542
|
+
arguments: {
|
|
543
|
+
input: 'What is TypeScript?',
|
|
544
|
+
research_effort: 'lite',
|
|
545
|
+
},
|
|
546
|
+
})
|
|
547
|
+
|
|
548
|
+
expect(result).toHaveProperty('content')
|
|
549
|
+
expect(result).toHaveProperty('structuredContent')
|
|
550
|
+
|
|
551
|
+
const content = result.content as { type: string; text: string }[]
|
|
552
|
+
expect(Array.isArray(content)).toBe(true)
|
|
553
|
+
expect(content[0]).toHaveProperty('type', 'text')
|
|
554
|
+
expect(content[0]).toHaveProperty('text')
|
|
555
|
+
|
|
556
|
+
const text = content[0]?.text
|
|
557
|
+
expect(typeof text).toBe('string')
|
|
558
|
+
expect(text?.length).toBeGreaterThan(0)
|
|
559
|
+
},
|
|
560
|
+
{ retry: 2 },
|
|
561
|
+
)
|
|
562
|
+
|
|
563
|
+
test(
|
|
564
|
+
'returns structured content with source information',
|
|
565
|
+
async () => {
|
|
566
|
+
const result = await client.callTool({
|
|
567
|
+
name: 'you-research',
|
|
568
|
+
arguments: {
|
|
569
|
+
input: 'What are the benefits of REST APIs?',
|
|
570
|
+
research_effort: 'lite',
|
|
571
|
+
},
|
|
572
|
+
})
|
|
573
|
+
|
|
574
|
+
const structuredContent = result.structuredContent as ResearchStructuredContent
|
|
575
|
+
expect(structuredContent).toHaveProperty('content_type', 'text')
|
|
576
|
+
expect(structuredContent).toHaveProperty('sourceCount')
|
|
577
|
+
expect(typeof structuredContent.sourceCount).toBe('number')
|
|
578
|
+
expect(structuredContent.sourceCount).toBeGreaterThan(0)
|
|
579
|
+
|
|
580
|
+
expect(structuredContent).toHaveProperty('sources')
|
|
581
|
+
expect(Array.isArray(structuredContent.sources)).toBe(true)
|
|
582
|
+
expect(structuredContent.sources.length).toBeGreaterThan(0)
|
|
583
|
+
|
|
584
|
+
const source = structuredContent.sources[0]
|
|
585
|
+
expect(source).toBeDefined()
|
|
586
|
+
expect(source).toHaveProperty('url')
|
|
587
|
+
expect(typeof source?.url).toBe('string')
|
|
588
|
+
expect(source).toHaveProperty('snippetCount')
|
|
589
|
+
expect(typeof source?.snippetCount).toBe('number')
|
|
590
|
+
},
|
|
591
|
+
{ retry: 2 },
|
|
592
|
+
)
|
|
593
|
+
|
|
594
|
+
test(
|
|
595
|
+
'text content contains markdown with answer and sources',
|
|
596
|
+
async () => {
|
|
597
|
+
const result = await client.callTool({
|
|
598
|
+
name: 'you-research',
|
|
599
|
+
arguments: {
|
|
600
|
+
input: 'What is JWT authentication?',
|
|
601
|
+
research_effort: 'lite',
|
|
602
|
+
},
|
|
603
|
+
})
|
|
604
|
+
|
|
605
|
+
const content = result.content as { type: string; text: string }[]
|
|
606
|
+
const text = content[0]?.text
|
|
607
|
+
expect(text).toContain('# Answer')
|
|
608
|
+
expect(text).toContain('## Sources')
|
|
609
|
+
},
|
|
610
|
+
{ retry: 2 },
|
|
611
|
+
)
|
|
612
|
+
|
|
613
|
+
test(
|
|
614
|
+
'handles research_effort parameter',
|
|
615
|
+
async () => {
|
|
616
|
+
const transport = new StdioClientTransport({
|
|
617
|
+
command: 'npx',
|
|
618
|
+
args: [Bun.resolveSync('../../bin/stdio', import.meta.dir)],
|
|
619
|
+
env: {
|
|
620
|
+
YDC_API_KEY: process.env.YDC_API_KEY ?? '',
|
|
621
|
+
},
|
|
622
|
+
})
|
|
623
|
+
|
|
624
|
+
const dedicatedClient = new Client({
|
|
625
|
+
name: 'test-client-research',
|
|
626
|
+
version: '0.0.1',
|
|
627
|
+
})
|
|
628
|
+
|
|
629
|
+
await dedicatedClient.connect(transport)
|
|
630
|
+
|
|
631
|
+
try {
|
|
632
|
+
const result = await dedicatedClient.callTool({
|
|
633
|
+
name: 'you-research',
|
|
634
|
+
arguments: {
|
|
635
|
+
input: 'Explain microservices architecture',
|
|
636
|
+
research_effort: 'standard',
|
|
637
|
+
},
|
|
638
|
+
})
|
|
639
|
+
|
|
640
|
+
const content = result.content as { type: string; text: string }[]
|
|
641
|
+
expect(content[0]).toHaveProperty('text')
|
|
642
|
+
expect(content[0]?.text?.length).toBeGreaterThan(0)
|
|
643
|
+
} finally {
|
|
644
|
+
await dedicatedClient.close()
|
|
645
|
+
}
|
|
646
|
+
},
|
|
647
|
+
{ retry: 2, timeout: 120_000 },
|
|
648
|
+
)
|
|
649
|
+
})
|