@endday/search-mcp 1.0.0 → 1.0.2
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/dist/index.js +4724 -0
- package/{mcp → dist}/search-mcp.js +1 -2
- package/package.json +14 -14
- package/data/blocklist.generated.js +0 -2
- package/envs.js +0 -129
- package/index.js +0 -6
- package/src/content/extract.impl.js +0 -228
- package/src/content/extract.js +0 -1
- package/src/content/fetch.impl.js +0 -400
- package/src/content/fetch.js +0 -1
- package/src/core/crypto.js +0 -7
- package/src/core/errors.impl.js +0 -52
- package/src/core/errors.js +0 -1
- package/src/core/html.impl.js +0 -69
- package/src/core/html.js +0 -1
- package/src/mcp/config.js +0 -75
- package/src/mcp/format.js +0 -44
- package/src/mcp/index.js +0 -10
- package/src/mcp/local/content.js +0 -26
- package/src/mcp/local/search.js +0 -233
- package/src/mcp/schemas.js +0 -132
- package/src/mcp/server.js +0 -97
- package/src/mcp/tools/content.js +0 -31
- package/src/mcp/tools/jinaContent.js +0 -38
- package/src/mcp/tools/newsSearch.js +0 -22
- package/src/mcp/tools/webSearch.js +0 -57
- package/src/platform/auth.impl.js +0 -166
- package/src/platform/auth.js +0 -1
- package/src/platform/cache.impl.js +0 -166
- package/src/platform/cache.js +0 -1
- package/src/platform/health.impl.js +0 -133
- package/src/platform/health.js +0 -1
- package/src/platform/http.impl.js +0 -108
- package/src/platform/http.js +0 -1
- package/src/platform/logger.impl.js +0 -51
- package/src/platform/logger.js +0 -1
- package/src/platform/metrics.impl.js +0 -43
- package/src/platform/metrics.js +0 -1
- package/src/platform/nodeHttpClient.js +0 -104
- package/src/platform/rateLimit.impl.js +0 -141
- package/src/platform/rateLimit.js +0 -1
- package/src/platform/requestContext.impl.js +0 -10
- package/src/platform/requestContext.js +0 -1
- package/src/platform/session.impl.js +0 -198
- package/src/platform/session.js +0 -1
- package/src/platform/stateKv.impl.js +0 -18
- package/src/platform/stateKv.js +0 -1
- package/src/platform/tasks.impl.js +0 -17
- package/src/platform/tasks.js +0 -1
- package/src/routes/requestParams.impl.js +0 -12
- package/src/routes/requestParams.js +0 -1
- package/src/search/engineRegistry.impl.js +0 -117
- package/src/search/engineRegistry.js +0 -1
- package/src/search/engineRequest.impl.js +0 -377
- package/src/search/engineRequest.js +0 -1
- package/src/search/engineUtils.impl.js +0 -227
- package/src/search/engineUtils.js +0 -1
- package/src/search/engines/baidu.impl.js +0 -145
- package/src/search/engines/baidu.js +0 -2
- package/src/search/engines/bing.impl.js +0 -509
- package/src/search/engines/bing.js +0 -2
- package/src/search/engines/brave.impl.js +0 -223
- package/src/search/engines/brave.js +0 -2
- package/src/search/engines/duckduckgo.impl.js +0 -164
- package/src/search/engines/duckduckgo.js +0 -2
- package/src/search/engines/mojeek.impl.js +0 -115
- package/src/search/engines/mojeek.js +0 -2
- package/src/search/engines/qwant.impl.js +0 -188
- package/src/search/engines/qwant.js +0 -2
- package/src/search/engines/startpage.impl.js +0 -237
- package/src/search/engines/startpage.js +0 -2
- package/src/search/engines/toutiao.impl.js +0 -265
- package/src/search/engines/toutiao.js +0 -2
- package/src/search/engines/yahoo.impl.js +0 -379
- package/src/search/engines/yahoo.js +0 -2
- package/src/search/gateway.impl.js +0 -423
- package/src/search/gateway.js +0 -1
- package/src/search/ranking.impl.js +0 -381
- package/src/search/ranking.js +0 -1
- package/src/search/requestPolicy.impl.js +0 -137
- package/src/search/requestPolicy.js +0 -1
- package/src/search/upstreamSession.impl.js +0 -148
- package/src/search/upstreamSession.js +0 -1
- /package/{index.d.ts → dist/index.d.ts} +0 -0
package/src/mcp/local/search.js
DELETED
|
@@ -1,233 +0,0 @@
|
|
|
1
|
-
import { env } from "../../../envs.js";
|
|
2
|
-
import { ApiError } from "../../core/errors.js";
|
|
3
|
-
import { searchAllWithMeta } from "../../search/gateway.js";
|
|
4
|
-
|
|
5
|
-
const LATIN_QUERY_RE = /^[\p{Script=Latin}\p{Number}\s'".,!?():/_+-]+$/u;
|
|
6
|
-
const DEFAULT_NEWS_ENGINES = ["bing", "brave", "yahoo"];
|
|
7
|
-
|
|
8
|
-
function normalizeSourceTypeList(value) {
|
|
9
|
-
const items = Array.isArray(value)
|
|
10
|
-
? value
|
|
11
|
-
: String(value || "")
|
|
12
|
-
.split(",")
|
|
13
|
-
.map((item) => item.trim());
|
|
14
|
-
|
|
15
|
-
return [
|
|
16
|
-
...new Set(
|
|
17
|
-
items
|
|
18
|
-
.map((item) => String(item || "").trim().toLowerCase())
|
|
19
|
-
.filter(Boolean)
|
|
20
|
-
),
|
|
21
|
-
];
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function normalizeMinAuthorityScore(value) {
|
|
25
|
-
if (value === undefined || value === null || value === "") {
|
|
26
|
-
return null;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const parsed = Number.parseFloat(value);
|
|
30
|
-
return Number.isFinite(parsed) ? parsed : null;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function normalizeSourceFilters(options) {
|
|
34
|
-
const includeSourceTypes = normalizeSourceTypeList(options.include_source_types);
|
|
35
|
-
const excludeSourceTypes = normalizeSourceTypeList(options.exclude_source_types);
|
|
36
|
-
const minAuthorityScore = normalizeMinAuthorityScore(options.min_authority_score);
|
|
37
|
-
|
|
38
|
-
return {
|
|
39
|
-
include_source_types: includeSourceTypes,
|
|
40
|
-
exclude_source_types: excludeSourceTypes,
|
|
41
|
-
min_authority_score: minAuthorityScore,
|
|
42
|
-
active:
|
|
43
|
-
includeSourceTypes.length > 0 ||
|
|
44
|
-
excludeSourceTypes.length > 0 ||
|
|
45
|
-
minAuthorityScore !== null,
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function resultMatchesSourceFilters(result, filters) {
|
|
50
|
-
if (!filters.active) {
|
|
51
|
-
return true;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const sourceType = String(result.source_type || "unknown").toLowerCase();
|
|
55
|
-
const authorityScore = Number.isFinite(result.authority_score)
|
|
56
|
-
? result.authority_score
|
|
57
|
-
: 0;
|
|
58
|
-
|
|
59
|
-
if (
|
|
60
|
-
filters.include_source_types.length > 0 &&
|
|
61
|
-
!filters.include_source_types.includes(sourceType)
|
|
62
|
-
) {
|
|
63
|
-
return false;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
if (filters.exclude_source_types.includes(sourceType)) {
|
|
67
|
-
return false;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
if (
|
|
71
|
-
filters.min_authority_score !== null &&
|
|
72
|
-
authorityScore < filters.min_authority_score
|
|
73
|
-
) {
|
|
74
|
-
return false;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
return true;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function applySourceFilters(response, filters) {
|
|
81
|
-
if (!filters.active) {
|
|
82
|
-
return response;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const results = response.results.filter((result) =>
|
|
86
|
-
resultMatchesSourceFilters(result, filters)
|
|
87
|
-
);
|
|
88
|
-
|
|
89
|
-
return {
|
|
90
|
-
...response,
|
|
91
|
-
number_of_results: results.length,
|
|
92
|
-
source_filters: filters,
|
|
93
|
-
results,
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
function inferLanguageFromQuery(query, fallbackLanguage) {
|
|
98
|
-
const normalizedQuery = String(query || "");
|
|
99
|
-
|
|
100
|
-
if (/[\u3040-\u30ff]/u.test(normalizedQuery)) {
|
|
101
|
-
return "ja-JP";
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
if (/[\uac00-\ud7af]/u.test(normalizedQuery)) {
|
|
105
|
-
return "ko-KR";
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
if (/[\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff]/u.test(normalizedQuery)) {
|
|
109
|
-
return "zh-CN";
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
if (LATIN_QUERY_RE.test(normalizedQuery) && /[a-z]/i.test(normalizedQuery)) {
|
|
113
|
-
return "en-US";
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
return fallbackLanguage;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function resolveLanguage(query, language) {
|
|
120
|
-
return language || inferLanguageFromQuery(query, env.DEFAULT_LANGUAGE);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
function isChineseLanguage(language) {
|
|
124
|
-
return String(language || "").trim().toLowerCase().startsWith("zh");
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
function resolveRequestedEngines(engines, language, vertical = "web") {
|
|
128
|
-
if (Array.isArray(engines) && engines.length > 0) {
|
|
129
|
-
return engines;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
if (vertical === "news") {
|
|
133
|
-
return DEFAULT_NEWS_ENGINES;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
return isChineseLanguage(language)
|
|
137
|
-
? env.DEFAULT_ENGINES_ZH
|
|
138
|
-
: env.DEFAULT_ENGINES_NON_ZH;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
function appendLocationToQuery(query, location) {
|
|
142
|
-
const normalizedLocation = String(location || "").trim();
|
|
143
|
-
if (!normalizedLocation) {
|
|
144
|
-
return query;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
if (String(query).toLowerCase().includes(normalizedLocation.toLowerCase())) {
|
|
148
|
-
return query;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
return `${query} ${normalizedLocation}`;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
function createRuntimeContext(query, clientId) {
|
|
155
|
-
return {
|
|
156
|
-
request: new Request(
|
|
157
|
-
`https://mcp.local/search?q=${encodeURIComponent(query)}`,
|
|
158
|
-
{
|
|
159
|
-
method: "GET",
|
|
160
|
-
headers: {
|
|
161
|
-
"x-mcp-client-id": clientId,
|
|
162
|
-
},
|
|
163
|
-
}
|
|
164
|
-
),
|
|
165
|
-
};
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
function paginateResponse(response, options) {
|
|
169
|
-
const offset = Math.max(0, Number.parseInt(options.offset ?? "0", 10) || 0);
|
|
170
|
-
const count =
|
|
171
|
-
options.count === undefined || options.count === null || options.count === ""
|
|
172
|
-
? null
|
|
173
|
-
: Math.max(1, Number.parseInt(options.count, 10) || 0);
|
|
174
|
-
|
|
175
|
-
if (offset === 0 && count === null) {
|
|
176
|
-
return response;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
const results =
|
|
180
|
-
count === null
|
|
181
|
-
? response.results.slice(offset)
|
|
182
|
-
: response.results.slice(offset, offset + count);
|
|
183
|
-
|
|
184
|
-
return {
|
|
185
|
-
...response,
|
|
186
|
-
number_of_results: results.length,
|
|
187
|
-
offset,
|
|
188
|
-
count,
|
|
189
|
-
results,
|
|
190
|
-
};
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
export async function searchLocal(query, engines = null, options = {}) {
|
|
194
|
-
const normalizedQuery = String(query || "").trim();
|
|
195
|
-
if (!normalizedQuery) {
|
|
196
|
-
throw new ApiError({
|
|
197
|
-
status: 400,
|
|
198
|
-
code: "MISSING_QUERY",
|
|
199
|
-
category: "validation",
|
|
200
|
-
message: "query required",
|
|
201
|
-
});
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
const vertical = String(options.vertical || "web").trim().toLowerCase() || "web";
|
|
205
|
-
const language = resolveLanguage(
|
|
206
|
-
normalizedQuery,
|
|
207
|
-
options.search_lang || options.ui_lang || options.language
|
|
208
|
-
);
|
|
209
|
-
const requestedEngines = resolveRequestedEngines(engines, language, vertical);
|
|
210
|
-
const effectiveQuery = appendLocationToQuery(normalizedQuery, options.location);
|
|
211
|
-
const filters = normalizeSourceFilters(options);
|
|
212
|
-
const clientId = String(options.clientId || "mcp-local").trim() || "mcp-local";
|
|
213
|
-
const runtimeContext = createRuntimeContext(effectiveQuery, clientId);
|
|
214
|
-
|
|
215
|
-
const { response } = await searchAllWithMeta({
|
|
216
|
-
vertical,
|
|
217
|
-
query: effectiveQuery,
|
|
218
|
-
engines: requestedEngines,
|
|
219
|
-
language,
|
|
220
|
-
time_range: options.time_range,
|
|
221
|
-
pageno: options.pageno,
|
|
222
|
-
clientId,
|
|
223
|
-
runtimeContext,
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
return {
|
|
227
|
-
...paginateResponse(applySourceFilters(response, filters), options),
|
|
228
|
-
query: normalizedQuery,
|
|
229
|
-
effective_query: effectiveQuery,
|
|
230
|
-
location: options.location || null,
|
|
231
|
-
location_source: options.location ? "explicit" : "disabled",
|
|
232
|
-
};
|
|
233
|
-
}
|
package/src/mcp/schemas.js
DELETED
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
import { MAX_CHARS_DEFAULT, MAX_CHARS_MAX, MAX_CHARS_MIN } from "./format.js";
|
|
2
|
-
|
|
3
|
-
function createSearchProperties(allEngines, options = {}) {
|
|
4
|
-
const properties = {
|
|
5
|
-
query: {
|
|
6
|
-
type: "string",
|
|
7
|
-
description: "Search query",
|
|
8
|
-
},
|
|
9
|
-
engines: {
|
|
10
|
-
type: "array",
|
|
11
|
-
items: {
|
|
12
|
-
type: "string",
|
|
13
|
-
enum: allEngines,
|
|
14
|
-
},
|
|
15
|
-
description: `Engines to query. Default: ${allEngines.join(", ")}.`,
|
|
16
|
-
},
|
|
17
|
-
language: {
|
|
18
|
-
type: "string",
|
|
19
|
-
description: "Language hint (e.g. en, zh-CN)",
|
|
20
|
-
},
|
|
21
|
-
search_lang: {
|
|
22
|
-
type: "string",
|
|
23
|
-
description: "Search language hint (e.g. en, zh-CN)",
|
|
24
|
-
},
|
|
25
|
-
ui_lang: {
|
|
26
|
-
type: "string",
|
|
27
|
-
description: "UI locale hint (e.g. en-US, zh-CN)",
|
|
28
|
-
},
|
|
29
|
-
time_range: {
|
|
30
|
-
type: "string",
|
|
31
|
-
enum: ["day", "week", "month", "year"],
|
|
32
|
-
description: "Time range filter",
|
|
33
|
-
},
|
|
34
|
-
count: {
|
|
35
|
-
type: "integer",
|
|
36
|
-
minimum: 1,
|
|
37
|
-
description: "Max number of results to return after ranking",
|
|
38
|
-
},
|
|
39
|
-
offset: {
|
|
40
|
-
type: "integer",
|
|
41
|
-
minimum: 0,
|
|
42
|
-
description: "Result offset to apply after ranking",
|
|
43
|
-
},
|
|
44
|
-
min_authority_score: {
|
|
45
|
-
type: "number",
|
|
46
|
-
description: "Min source authority score",
|
|
47
|
-
},
|
|
48
|
-
include_source_types: {
|
|
49
|
-
type: "array",
|
|
50
|
-
items: { type: "string" },
|
|
51
|
-
description: "Source types to include",
|
|
52
|
-
},
|
|
53
|
-
exclude_source_types: {
|
|
54
|
-
type: "array",
|
|
55
|
-
items: { type: "string" },
|
|
56
|
-
description: "Source types to exclude",
|
|
57
|
-
},
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
if (options.includeLocation !== false) {
|
|
61
|
-
properties.location = {
|
|
62
|
-
type: "string",
|
|
63
|
-
description: "Location hint for query enrichment",
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
if (options.includePage !== false) {
|
|
68
|
-
properties.pageno = {
|
|
69
|
-
type: "integer",
|
|
70
|
-
minimum: 0,
|
|
71
|
-
description: "Page number (0-based)",
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
return properties;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
export function createSearchInputSchema(allEngines) {
|
|
79
|
-
return {
|
|
80
|
-
type: "object",
|
|
81
|
-
properties: createSearchProperties(allEngines, {
|
|
82
|
-
includeLocation: true,
|
|
83
|
-
includePage: true,
|
|
84
|
-
}),
|
|
85
|
-
required: ["query"],
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
export function createNewsSearchInputSchema(allEngines) {
|
|
90
|
-
return {
|
|
91
|
-
type: "object",
|
|
92
|
-
properties: createSearchProperties(allEngines, {
|
|
93
|
-
includeLocation: false,
|
|
94
|
-
includePage: false,
|
|
95
|
-
}),
|
|
96
|
-
required: ["query"],
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
export const CONTENT_INPUT_SCHEMA = {
|
|
101
|
-
type: "object",
|
|
102
|
-
properties: {
|
|
103
|
-
url: {
|
|
104
|
-
type: "string",
|
|
105
|
-
description: "URL to extract content from",
|
|
106
|
-
},
|
|
107
|
-
max_chars: {
|
|
108
|
-
type: "integer",
|
|
109
|
-
minimum: MAX_CHARS_MIN,
|
|
110
|
-
maximum: MAX_CHARS_MAX,
|
|
111
|
-
description: `Max text chars to return. Default: ${MAX_CHARS_DEFAULT}.`,
|
|
112
|
-
},
|
|
113
|
-
},
|
|
114
|
-
required: ["url"],
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
export const JINA_CONTENT_INPUT_SCHEMA = {
|
|
118
|
-
type: "object",
|
|
119
|
-
properties: {
|
|
120
|
-
url: {
|
|
121
|
-
type: "string",
|
|
122
|
-
description: "URL to extract content from via Jina AI reader",
|
|
123
|
-
},
|
|
124
|
-
max_chars: {
|
|
125
|
-
type: "integer",
|
|
126
|
-
minimum: MAX_CHARS_MIN,
|
|
127
|
-
maximum: MAX_CHARS_MAX,
|
|
128
|
-
description: `Max text chars to return. Default: ${MAX_CHARS_DEFAULT}.`,
|
|
129
|
-
},
|
|
130
|
-
},
|
|
131
|
-
required: ["url"],
|
|
132
|
-
};
|
package/src/mcp/server.js
DELETED
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
2
|
-
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
-
import {
|
|
4
|
-
CallToolRequestSchema,
|
|
5
|
-
ListToolsRequestSchema,
|
|
6
|
-
} from "@modelcontextprotocol/sdk/types.js";
|
|
7
|
-
|
|
8
|
-
import {
|
|
9
|
-
CONTENT_INPUT_SCHEMA,
|
|
10
|
-
createNewsSearchInputSchema,
|
|
11
|
-
createSearchInputSchema,
|
|
12
|
-
JINA_CONTENT_INPUT_SCHEMA,
|
|
13
|
-
} from "./schemas.js";
|
|
14
|
-
import { getSupportedEnginesForVertical } from "../search/engineRegistry.js";
|
|
15
|
-
import { handleWebSearch } from "./tools/webSearch.js";
|
|
16
|
-
import { handleNewsSearch } from "./tools/newsSearch.js";
|
|
17
|
-
import { handleContent } from "./tools/content.js";
|
|
18
|
-
import { handleJinaContent } from "./tools/jinaContent.js";
|
|
19
|
-
|
|
20
|
-
export function createServer(config) {
|
|
21
|
-
const newsEngineSet = new Set(getSupportedEnginesForVertical("news"));
|
|
22
|
-
const newsEngines =
|
|
23
|
-
config.newsEngines ||
|
|
24
|
-
config.allEngines.filter((engineName) => newsEngineSet.has(engineName));
|
|
25
|
-
const server = new Server(
|
|
26
|
-
{ name: "search-mcp", version: "2.0.0" },
|
|
27
|
-
{ capabilities: { tools: {} } }
|
|
28
|
-
);
|
|
29
|
-
|
|
30
|
-
const TOOL_LIST = {
|
|
31
|
-
tools: [
|
|
32
|
-
{
|
|
33
|
-
name: "web_search",
|
|
34
|
-
description:
|
|
35
|
-
"Search the web with local-first MCP execution. Returns deduplicated results with snippets and source authority scores.",
|
|
36
|
-
inputSchema: createSearchInputSchema(config.allEngines),
|
|
37
|
-
},
|
|
38
|
-
{
|
|
39
|
-
name: "news_search",
|
|
40
|
-
description:
|
|
41
|
-
"Search recent news with an explicit news-search capability. Use this instead of web_search when the caller needs news results rather than general web pages.",
|
|
42
|
-
inputSchema: createNewsSearchInputSchema(newsEngines),
|
|
43
|
-
},
|
|
44
|
-
{
|
|
45
|
-
name: "content",
|
|
46
|
-
description:
|
|
47
|
-
"Extract readable text from a URL with local-first MCP execution. Returns title, URL, and extracted content.",
|
|
48
|
-
inputSchema: CONTENT_INPUT_SCHEMA,
|
|
49
|
-
},
|
|
50
|
-
{
|
|
51
|
-
name: "jina_content",
|
|
52
|
-
description:
|
|
53
|
-
"Extract readable text from a URL using Jina AI reader. Per-user rate limit unless JINA_API_KEY is configured.",
|
|
54
|
-
inputSchema: JINA_CONTENT_INPUT_SCHEMA,
|
|
55
|
-
},
|
|
56
|
-
],
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
const VALID_TOOL_NAMES = new Set([
|
|
60
|
-
"web_search",
|
|
61
|
-
"news_search",
|
|
62
|
-
"content",
|
|
63
|
-
"jina_content",
|
|
64
|
-
]);
|
|
65
|
-
|
|
66
|
-
server.setRequestHandler(ListToolsRequestSchema, async () => TOOL_LIST);
|
|
67
|
-
|
|
68
|
-
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
69
|
-
const { name } = request.params;
|
|
70
|
-
if (!VALID_TOOL_NAMES.has(name)) {
|
|
71
|
-
throw new Error(`Unknown tool: ${name}`);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const args = request.params.arguments || {};
|
|
75
|
-
|
|
76
|
-
if (name === "web_search") {
|
|
77
|
-
return handleWebSearch(config, args);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
if (name === "news_search") {
|
|
81
|
-
return handleNewsSearch(config, args);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
if (name === "content") {
|
|
85
|
-
return handleContent(config, args);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
return handleJinaContent(config, args);
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
return server;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
export async function startServer(server) {
|
|
95
|
-
const transport = new StdioServerTransport();
|
|
96
|
-
await server.connect(transport);
|
|
97
|
-
}
|
package/src/mcp/tools/content.js
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
formatContentResponse,
|
|
3
|
-
normalizeMaxChars,
|
|
4
|
-
} from "../format.js";
|
|
5
|
-
import { contentLocal, requireUrl } from "../local/content.js";
|
|
6
|
-
|
|
7
|
-
function estimateContentMaxBytes(maxChars) {
|
|
8
|
-
return Math.max(1000000, maxChars * 16 + 200000);
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
async function executeContent(config, url, estimatedMaxBytes) {
|
|
12
|
-
return contentLocal(url, { max_bytes: estimatedMaxBytes });
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export async function handleContent(config, args) {
|
|
16
|
-
const url = requireUrl(args);
|
|
17
|
-
const maxChars = normalizeMaxChars(args.max_chars);
|
|
18
|
-
const estimatedMaxBytes = estimateContentMaxBytes(maxChars);
|
|
19
|
-
|
|
20
|
-
try {
|
|
21
|
-
const result = await executeContent(config, url, estimatedMaxBytes);
|
|
22
|
-
return {
|
|
23
|
-
content: [{ type: "text", text: formatContentResponse(result, maxChars) }],
|
|
24
|
-
};
|
|
25
|
-
} catch (error) {
|
|
26
|
-
return {
|
|
27
|
-
content: [{ type: "text", text: `Content failed: ${error.message}` }],
|
|
28
|
-
isError: true,
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
}
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import { normalizeMaxChars, truncateText } from "../format.js";
|
|
2
|
-
import { requireUrl } from "../local/content.js";
|
|
3
|
-
|
|
4
|
-
async function jinaContentAPI(config, targetUrl) {
|
|
5
|
-
const encodedUrl = encodeURIComponent(targetUrl);
|
|
6
|
-
const response = await fetch(`${config.jinaBaseUrl}${encodedUrl}`, {
|
|
7
|
-
headers: config.jinaApiKey
|
|
8
|
-
? { Authorization: `Bearer ${config.jinaApiKey}` }
|
|
9
|
-
: {},
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
if (!response.ok) {
|
|
13
|
-
const body = await response.text().catch(() => "");
|
|
14
|
-
throw new Error(`Jina failed: ${response.status} ${body}`.trim());
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
return {
|
|
18
|
-
url: targetUrl,
|
|
19
|
-
text: await response.text(),
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export async function handleJinaContent(config, args) {
|
|
24
|
-
const url = requireUrl(args);
|
|
25
|
-
const maxChars = normalizeMaxChars(args.max_chars);
|
|
26
|
-
|
|
27
|
-
try {
|
|
28
|
-
const result = await jinaContentAPI(config, url);
|
|
29
|
-
return {
|
|
30
|
-
content: [{ type: "text", text: truncateText(String(result.text || ""), maxChars) }],
|
|
31
|
-
};
|
|
32
|
-
} catch (error) {
|
|
33
|
-
return {
|
|
34
|
-
content: [{ type: "text", text: error.message }],
|
|
35
|
-
isError: true,
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { formatSearchResponse } from "../format.js";
|
|
2
|
-
import { executeSearch } from "./webSearch.js";
|
|
3
|
-
|
|
4
|
-
export async function handleNewsSearch(config, args) {
|
|
5
|
-
if (!args.query || typeof args.query !== "string") {
|
|
6
|
-
throw new Error("query required");
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
try {
|
|
10
|
-
const result = await executeSearch(config, args, {
|
|
11
|
-
vertical: "news",
|
|
12
|
-
});
|
|
13
|
-
return {
|
|
14
|
-
content: [{ type: "text", text: formatSearchResponse(result) }],
|
|
15
|
-
};
|
|
16
|
-
} catch (error) {
|
|
17
|
-
return {
|
|
18
|
-
content: [{ type: "text", text: `News search failed: ${error.message}` }],
|
|
19
|
-
isError: true,
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
}
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import { formatSearchResponse } from "../format.js";
|
|
2
|
-
import { searchLocal } from "../local/search.js";
|
|
3
|
-
|
|
4
|
-
export function buildSearchOptions(args, overrides = {}) {
|
|
5
|
-
const searchOptions = {
|
|
6
|
-
vertical: overrides.vertical || "web",
|
|
7
|
-
language: args.language,
|
|
8
|
-
search_lang: args.search_lang,
|
|
9
|
-
ui_lang: args.ui_lang,
|
|
10
|
-
location: args.location,
|
|
11
|
-
time_range: args.time_range,
|
|
12
|
-
pageno: args.pageno,
|
|
13
|
-
count: args.count,
|
|
14
|
-
offset: args.offset,
|
|
15
|
-
clientId: args.client_id,
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
if (Number.isFinite(args.min_authority_score)) {
|
|
19
|
-
searchOptions.min_authority_score = args.min_authority_score;
|
|
20
|
-
}
|
|
21
|
-
if (Array.isArray(args.include_source_types)) {
|
|
22
|
-
searchOptions.include_source_types = args.include_source_types;
|
|
23
|
-
}
|
|
24
|
-
if (Array.isArray(args.exclude_source_types)) {
|
|
25
|
-
searchOptions.exclude_source_types = args.exclude_source_types;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
return searchOptions;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export async function executeSearch(config, args, overrides = {}) {
|
|
32
|
-
const engines = Array.isArray(args.engines) && args.engines.length > 0 ? args.engines : null;
|
|
33
|
-
const searchOptions = {
|
|
34
|
-
...buildSearchOptions(args, overrides),
|
|
35
|
-
clientId: args.client_id || config.localClientId,
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
return searchLocal(args.query, engines, searchOptions);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export async function handleWebSearch(config, args) {
|
|
42
|
-
if (!args.query || typeof args.query !== "string") {
|
|
43
|
-
throw new Error("query required");
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
try {
|
|
47
|
-
const result = await executeSearch(config, args);
|
|
48
|
-
return {
|
|
49
|
-
content: [{ type: "text", text: formatSearchResponse(result) }],
|
|
50
|
-
};
|
|
51
|
-
} catch (error) {
|
|
52
|
-
return {
|
|
53
|
-
content: [{ type: "text", text: `Search failed: ${error.message}` }],
|
|
54
|
-
isError: true,
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
}
|