desearch-mcp-server 0.1.2 → 0.1.3
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/CHANGELOG.md +10 -0
- package/README.md +87 -11
- package/build/http.d.ts.map +1 -1
- package/build/http.js +77 -7
- package/build/server.d.ts +1 -1
- package/build/server.d.ts.map +1 -1
- package/build/server.js +43 -19
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.3
|
|
4
|
+
|
|
5
|
+
Tool copy no longer states unsourced speed or latency.
|
|
6
|
+
|
|
7
|
+
- `ai-search` description is `AI search and analysis on web using Desearch AI`.
|
|
8
|
+
- `x-search` description is `Search X (Twitter) using Desearch AI. Optional filters narrow by user, date, language, verification, media, and engagement. Sort stays Top.`
|
|
9
|
+
- `ai-search` `model` is `Model to use for the search: NOVA (default) or ORBIT.`
|
|
10
|
+
|
|
11
|
+
Removed from those strings: `Real-time`, `real-time`, and `Nova is 10s model, Orbit is 30s model`. The Vercel section of the README no longer says Orbit searches run about 30 seconds. The 60-second figure there is the `vercel.json` function duration.
|
|
12
|
+
|
|
3
13
|
## 0.1.2
|
|
4
14
|
|
|
5
15
|
Migration from the published npm package `desearch-mcp-server@0.0.1`.
|
package/README.md
CHANGED
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/desearch-mcp-server)
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
AI search, X search and web search for AI agents, plus page extraction and X data tools. Bring your own Desearch API key.
|
|
6
6
|
|
|
7
7
|
## Tools
|
|
8
8
|
|
|
9
9
|
The Desearch MCP server includes the following tools:
|
|
10
10
|
|
|
11
|
-
- **AI Search** (`ai-search`): Performs
|
|
12
|
-
- **X Search** (`x-search`):
|
|
11
|
+
- **AI Search** (`ai-search`): Performs AI Twitter and web searches with relevant links and summary. `tools` uses short source ids (`web`, `twitter`, `arxiv`, `wikipedia`, `youtube`, `hackernews`, `reddit`). Older labels such as `Web Search` are still accepted and sent to the API as the short id. Default is `["web", "twitter"]`.
|
|
12
|
+
- **X Search** (`x-search`): Tweet search on X. Arguments: `query` (required), `count` (optional, default 20). Sort stays Top. Optional filters: `user`, `start_date`, `end_date` (YYYY-MM-DD), `lang`, `verified`, `blue_verified`, `is_quote`, `is_video`, `is_image`, `min_retweets`, `min_replies`, `min_likes`.
|
|
13
13
|
- **Web Search** (`web-search`): SERP-style web search. Arguments: `query` (required), `start` (optional pagination offset).
|
|
14
14
|
- **Web Links Search** (`web-links-search`): Web link search. Arguments: `prompt` (required), `tools` (optional, only `web`, default `["web"]`; `Web Search` is accepted and rewritten to `web`), `count` (optional, 10–200). The links/web API rejects other sources, so they are not in the enum.
|
|
15
15
|
- **Extract** (`extract`): Read a public URL as text or HTML. Preferred over crawl. Arguments: `url` (required), `format` (optional, `html` or `text`), `js` (optional), `wait` (optional milliseconds).
|
|
@@ -69,18 +69,94 @@ Cursor or Claude can start that bin directly:
|
|
|
69
69
|
|
|
70
70
|
### Using Smithery
|
|
71
71
|
|
|
72
|
-
To install the Desearch MCP server for Claude Desktop automatically via [Smithery](https://smithery.ai/
|
|
72
|
+
To install the Desearch MCP server for Claude Desktop automatically via [Smithery](https://smithery.ai/servers/desearch/desearch):
|
|
73
73
|
|
|
74
74
|
```bash
|
|
75
|
-
npx -y @smithery/cli install
|
|
75
|
+
npx -y @smithery/cli install desearch/desearch --client claude
|
|
76
76
|
```
|
|
77
77
|
|
|
78
78
|
Or for Cursor IDE:
|
|
79
79
|
|
|
80
80
|
```bash
|
|
81
|
-
npx -y @smithery/cli install
|
|
81
|
+
npx -y @smithery/cli install desearch/desearch --client cursor
|
|
82
82
|
```
|
|
83
83
|
|
|
84
|
+
### Windsurf
|
|
85
|
+
|
|
86
|
+
Windsurf's Cascade agent reads MCP servers from `mcp_config.json` under the `mcpServers` key. Open it from the Cascade panel: click the `...` (Actions) menu, then `Open MCP config file`. Windsurf builds use `~/.codeium/windsurf/mcp_config.json` (on Windows, `%USERPROFILE%\.codeium\windsurf\mcp_config.json`). Newer builds may open `~/.config/devin/mcp_config.json` instead (Windows: `%APPDATA%\devin\mcp_config.json`); edit whichever file that action opens.
|
|
87
|
+
|
|
88
|
+
Hosted server (no local install). Remote servers use `serverUrl` with `headers`:
|
|
89
|
+
|
|
90
|
+
```json
|
|
91
|
+
{
|
|
92
|
+
"mcpServers": {
|
|
93
|
+
"desearch": {
|
|
94
|
+
"serverUrl": "https://mcp.desearch.ai/mcp",
|
|
95
|
+
"headers": {
|
|
96
|
+
"x-api-key": "your-api-key"
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
To keep the key out of the file, Windsurf can interpolate an environment variable: `"x-api-key": "${env:DESEARCH_API_KEY}"`.
|
|
104
|
+
|
|
105
|
+
Local stdio alternative:
|
|
106
|
+
|
|
107
|
+
```json
|
|
108
|
+
{
|
|
109
|
+
"mcpServers": {
|
|
110
|
+
"desearch": {
|
|
111
|
+
"command": "npx",
|
|
112
|
+
"args": ["-y", "desearch-mcp-server"],
|
|
113
|
+
"env": {
|
|
114
|
+
"DESEARCH_API_KEY": "your-api-key"
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Save the file, then refresh the MCP servers list in Cascade.
|
|
122
|
+
|
|
123
|
+
### Zed
|
|
124
|
+
|
|
125
|
+
Zed calls MCP servers context servers. Open your settings file with the `zed: open settings file` action (or use Settings → AI → MCP Servers → `Add Server`) and add a `context_servers` entry.
|
|
126
|
+
|
|
127
|
+
Hosted server:
|
|
128
|
+
|
|
129
|
+
```json
|
|
130
|
+
{
|
|
131
|
+
"context_servers": {
|
|
132
|
+
"desearch": {
|
|
133
|
+
"url": "https://mcp.desearch.ai/mcp",
|
|
134
|
+
"headers": {
|
|
135
|
+
"x-api-key": "your-api-key"
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Local stdio alternative:
|
|
143
|
+
|
|
144
|
+
```json
|
|
145
|
+
{
|
|
146
|
+
"context_servers": {
|
|
147
|
+
"desearch": {
|
|
148
|
+
"command": "npx",
|
|
149
|
+
"args": ["-y", "desearch-mcp-server"],
|
|
150
|
+
"env": {
|
|
151
|
+
"DESEARCH_API_KEY": "your-api-key"
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
The server is ready when the dot next to `desearch` in Settings → AI → MCP Servers turns green ("Server is active").
|
|
159
|
+
|
|
84
160
|
## Configuration ⚙️
|
|
85
161
|
|
|
86
162
|
### 1. Configure Cursor IDE to run the Desearch MCP server
|
|
@@ -164,18 +240,18 @@ For the changes to take effect:
|
|
|
164
240
|
|
|
165
241
|
The same server can run over MCP Streamable HTTP for a remote client. Local stdio (`desearch-mcp-server`, Smithery) is unchanged and still reads `DESEARCH_API_KEY` from the environment.
|
|
166
242
|
|
|
167
|
-
Remote requests do not use that environment variable.
|
|
243
|
+
Remote requests do not use that environment variable. Discovery does not need a key: `initialize`, `notifications/initialized`, `ping`, `tools/list`, `prompts/list`, `resources/list`, and `resources/templates/list` return 200 so a marketplace scanner can read the tool list. `tools/call` and every other method still require the caller's own Desearch API key, the same key from [console.desearch.ai/api-keys](https://console.desearch.ai/api-keys):
|
|
168
244
|
|
|
169
245
|
- `Authorization: Bearer <DESEARCH_API_KEY>` (preferred)
|
|
170
246
|
- `x-api-key: <DESEARCH_API_KEY>`
|
|
171
247
|
|
|
172
|
-
A bare `Authorization: <DESEARCH_API_KEY>` value is also accepted. The key is not read from the query string. There is no shared server secret: the hosted process forwards the per-request key to the Desearch API.
|
|
248
|
+
A bare `Authorization: <DESEARCH_API_KEY>` value is also accepted. The key is not read from the query string. There is no shared server secret and no `WWW-Authenticate` challenge: the hosted process forwards the per-request key to the Desearch API only when a call needs it.
|
|
173
249
|
|
|
174
|
-
The MCP endpoint is `POST /mcp`. Responses are JSON (stateless Streamable HTTP). `GET` and `DELETE` on `/mcp` return `405` because the server does not keep a session or push server-to-client messages. `GET
|
|
250
|
+
The MCP endpoint is `POST /mcp`. Responses are JSON (stateless Streamable HTTP). `GET` and `DELETE` on `/mcp` return `405` because the server does not keep a session or push server-to-client messages. `GET /`, `GET /health`, and `GET /api/health` are unauthenticated health checks.
|
|
175
251
|
|
|
176
252
|
## Hosted endpoint
|
|
177
253
|
|
|
178
|
-
The public Streamable HTTP endpoint is `https://mcp.desearch.ai/mcp`. Send your Desearch API key on each
|
|
254
|
+
The public Streamable HTTP endpoint is `https://mcp.desearch.ai/mcp`. Listing the tools does not need a key. Send your Desearch API key on each `tools/call` in the `x-api-key` header. `Authorization: Bearer <key>` is also accepted. Use the key from [console.desearch.ai/api-keys](https://console.desearch.ai/api-keys). The server does not read a key from the query string. Remote requests do not use a process-level `DESEARCH_API_KEY`.
|
|
179
255
|
|
|
180
256
|
Cursor, or any remote MCP client:
|
|
181
257
|
|
|
@@ -265,7 +341,7 @@ docker run --rm -e MCP_TRANSPORT=http -p 3000:3000 desearch-mcp
|
|
|
265
341
|
|
|
266
342
|
### Deploy on Vercel
|
|
267
343
|
|
|
268
|
-
Vercel fits this server because the handler is stateless and answers each JSON-RPC call in one response. `vercel.json` builds the project, serves `POST /mcp`, and
|
|
344
|
+
Vercel fits this server because the handler is stateless and answers each JSON-RPC call in one response. `vercel.json` builds the project, serves `POST /mcp`, and sets the function duration to 60 seconds. Hobby plans cap function duration lower than that, so AI Search tool calls need a plan that allows at least 60 seconds. `initialize` and `tools/list` are short either way.
|
|
269
345
|
|
|
270
346
|
No server-side Desearch API key is required in the Vercel project. After deploy, the endpoint is:
|
|
271
347
|
|
package/build/http.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../http.ts"],"names":[],"mappings":"AAsBA,MAAM,WAAW,iBAAiB;IAC9B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,iBAAiB;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B;AAED;;;;;;GAMG;AACH;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAa/E;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAkB1E;
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../http.ts"],"names":[],"mappings":"AAsBA,MAAM,WAAW,iBAAiB;IAC9B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,iBAAiB;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B;AAED;;;;;;GAMG;AACH;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAa/E;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAkB1E;AAkID;;;;;;GAMG;AACH,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAiE9E;AAuDD,wBAAgB,eAAe,CAAC,OAAO,GAAE,iBAAsB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAiD3F"}
|
package/build/http.js
CHANGED
|
@@ -68,7 +68,10 @@ function isMcpPath(pathname) {
|
|
|
68
68
|
return pathname === "/mcp" || pathname === "/api/mcp";
|
|
69
69
|
}
|
|
70
70
|
function isHealthPath(pathname) {
|
|
71
|
-
|
|
71
|
+
// `/api/health` is the Vercel function path. The public rewrite of `/health`
|
|
72
|
+
// delivers that pathname when the function wrapper does not remap it, and
|
|
73
|
+
// `isMcpPath` already accepts both `/mcp` and `/api/mcp` for the same reason.
|
|
74
|
+
return pathname === "/" || pathname === "/health" || pathname === "/api/health";
|
|
72
75
|
}
|
|
73
76
|
function withCors(response) {
|
|
74
77
|
const headers = new Headers(response.headers);
|
|
@@ -96,7 +99,7 @@ function healthResponse() {
|
|
|
96
99
|
version: SERVER_VERSION,
|
|
97
100
|
transport: "streamable-http",
|
|
98
101
|
endpoint: "/mcp",
|
|
99
|
-
auth: "Authorization: Bearer <DESEARCH_API_KEY> or x-api-key
|
|
102
|
+
auth: "initialize and tools/list are public. tools/call requires Authorization: Bearer <DESEARCH_API_KEY> or x-api-key.",
|
|
100
103
|
}), {
|
|
101
104
|
status: 200,
|
|
102
105
|
headers: { "Content-Type": "application/json" },
|
|
@@ -107,6 +110,62 @@ function unauthorized() {
|
|
|
107
110
|
// WWW-Authenticate discovery hint makes some MCP clients start an OAuth flow.
|
|
108
111
|
return jsonRpcError(401, -32001, "Unauthorized. Send your Desearch API key in the Authorization: Bearer <key> header or the x-api-key header.");
|
|
109
112
|
}
|
|
113
|
+
/**
|
|
114
|
+
* Methods a marketplace scanner can call with no Desearch API key.
|
|
115
|
+
* Anything else, including tools/call, stays on the 401 gate so a missing
|
|
116
|
+
* key never reaches the Desearch API.
|
|
117
|
+
*/
|
|
118
|
+
const KEYLESS_METHODS = new Set([
|
|
119
|
+
"initialize",
|
|
120
|
+
"notifications/initialized",
|
|
121
|
+
"ping",
|
|
122
|
+
"tools/list",
|
|
123
|
+
"prompts/list",
|
|
124
|
+
"resources/list",
|
|
125
|
+
"resources/templates/list",
|
|
126
|
+
]);
|
|
127
|
+
function isKeylessDiscovery(raw) {
|
|
128
|
+
let parsed;
|
|
129
|
+
try {
|
|
130
|
+
parsed = JSON.parse(raw);
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
const messages = Array.isArray(parsed) ? parsed : [parsed];
|
|
136
|
+
return (messages.length > 0 &&
|
|
137
|
+
messages.every((message) => {
|
|
138
|
+
if (typeof message !== "object" || message === null) {
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
const method = message.method;
|
|
142
|
+
return typeof method === "string" && KEYLESS_METHODS.has(method);
|
|
143
|
+
}));
|
|
144
|
+
}
|
|
145
|
+
function copyRequestHeaders(headers) {
|
|
146
|
+
const copied = new Headers();
|
|
147
|
+
headers.forEach((value, key) => {
|
|
148
|
+
if (key.toLowerCase() === "content-length" || key.toLowerCase() === "host") {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
try {
|
|
152
|
+
copied.append(key, value);
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
// The Fetch constructor rejects forbidden request headers such as host.
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
return copied;
|
|
159
|
+
}
|
|
160
|
+
function requestWithJsonBody(request, body) {
|
|
161
|
+
const init = { method: "POST", headers: request.headers, body };
|
|
162
|
+
try {
|
|
163
|
+
return new Request(request.url, init);
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
return new Request(request.url, { ...init, headers: copyRequestHeaders(request.headers) });
|
|
167
|
+
}
|
|
168
|
+
}
|
|
110
169
|
/**
|
|
111
170
|
* Stateless Streamable HTTP handler.
|
|
112
171
|
* Each POST builds a fresh MCP server tied to that request's API key.
|
|
@@ -131,16 +190,24 @@ export async function handleMcpHttpRequest(request) {
|
|
|
131
190
|
headers: { "Content-Type": "application/json" },
|
|
132
191
|
}));
|
|
133
192
|
}
|
|
134
|
-
const apiKey = extractDesearchApiKey(request);
|
|
135
|
-
if (!apiKey) {
|
|
136
|
-
return withCors(unauthorized());
|
|
137
|
-
}
|
|
138
193
|
if (request.method === "GET" || request.method === "DELETE") {
|
|
194
|
+
if (!extractDesearchApiKey(request)) {
|
|
195
|
+
return withCors(unauthorized());
|
|
196
|
+
}
|
|
139
197
|
return withCors(jsonRpcError(405, -32000, "Method not allowed. This server is stateless; use POST.", { Allow: "POST" }));
|
|
140
198
|
}
|
|
141
199
|
if (request.method !== "POST") {
|
|
142
200
|
return withCors(jsonRpcError(405, -32000, "Method not allowed.", { Allow: "POST" }));
|
|
143
201
|
}
|
|
202
|
+
let apiKey = extractDesearchApiKey(request);
|
|
203
|
+
if (!apiKey) {
|
|
204
|
+
const raw = await request.text();
|
|
205
|
+
if (!isKeylessDiscovery(raw)) {
|
|
206
|
+
return withCors(unauthorized());
|
|
207
|
+
}
|
|
208
|
+
request = requestWithJsonBody(request, raw);
|
|
209
|
+
apiKey = "";
|
|
210
|
+
}
|
|
144
211
|
const server = createDesearchMcpServer(apiKey);
|
|
145
212
|
const transport = new WebStandardStreamableHTTPServerTransport({
|
|
146
213
|
enableJsonResponse: true,
|
|
@@ -168,7 +235,10 @@ function readRawBody(req) {
|
|
|
168
235
|
req.on("data", (chunk) => {
|
|
169
236
|
chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
|
|
170
237
|
});
|
|
171
|
-
|
|
238
|
+
// TS 5.7+ DOM BodyInit accepts Uint8Array<ArrayBuffer> and rejects
|
|
239
|
+
// Uint8Array<ArrayBufferLike> (Node Buffer, whose buffer may be a
|
|
240
|
+
// SharedArrayBuffer). Copy into a plain ArrayBuffer.
|
|
241
|
+
req.on("end", () => resolve(new Uint8Array(Buffer.concat(chunks))));
|
|
172
242
|
req.on("error", reject);
|
|
173
243
|
});
|
|
174
244
|
}
|
package/build/server.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
2
|
export declare const SERVER_NAME = "Desearch";
|
|
3
|
-
export declare const SERVER_VERSION = "0.1.
|
|
3
|
+
export declare const SERVER_VERSION = "0.1.3";
|
|
4
4
|
interface XSearchPayload {
|
|
5
5
|
query: string;
|
|
6
6
|
sort?: "Top" | "Latest";
|
package/build/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAUpE,eAAO,MAAM,WAAW,aAAa,CAAC;AACtC,eAAO,MAAM,cAAc,UAAU,CAAC;AAEtC,UAAU,cAAc;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,KAAK,GAAG,QAAQ,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC/B,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC9B,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CAC/B;AAED,UAAU,kBAAkB;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACzB,EAAE,CAAC,EAAE,OAAO,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,UAAU,cAAc;IACpB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC7D,OAAO,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACnD,SAAS,CAAC,OAAO,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACxE,gBAAgB,CAAC,OAAO,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,EAAE,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACjG,cAAc,CAAC,OAAO,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC9E,YAAY,CAAC,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,EAAE,CAAA;KAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC5D,SAAS,CAAC,OAAO,EAAE;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACrD,YAAY,CAAC,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC1F,eAAe,CAAC,OAAO,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC5E,UAAU,CAAC,OAAO,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC7E,YAAY,CAAC,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC1F,YAAY,CAAC,OAAO,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC7F,OAAO,CAAC,OAAO,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACtE,OAAO,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACvD,QAAQ,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAC3D;AAsED;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,yBAAyB,CAAC;AAuCvD;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAYzD;AAgED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,cAAc,GAAG,SAAS,CA+c1F"}
|
package/build/server.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { ListPromptsRequestSchema, ListResourcesRequestSchema, ListResourceTemplatesRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
2
3
|
import DesearchImport from "desearch-js";
|
|
3
4
|
import { z } from "zod";
|
|
4
5
|
import { AI_SEARCH_TOOLS, WEB_LINK_TOOLS, toolIdSchema } from "./tool-sources.js";
|
|
5
6
|
export const SERVER_NAME = "Desearch";
|
|
6
|
-
export const SERVER_VERSION = "0.1.
|
|
7
|
+
export const SERVER_VERSION = "0.1.3";
|
|
7
8
|
// desearch-js 1.5 publishes a default class. Node16 resolution types that package
|
|
8
9
|
// as a module namespace, so the constructor is applied through a cast.
|
|
9
10
|
const Desearch = DesearchImport;
|
|
@@ -135,11 +136,22 @@ function fail(label, error) {
|
|
|
135
136
|
isError: true,
|
|
136
137
|
};
|
|
137
138
|
}
|
|
138
|
-
function registerTool(server, name, description, inputSchema, handler) {
|
|
139
|
+
function registerTool(server, apiKey, name, title, description, inputSchema, handler) {
|
|
139
140
|
// SDK 1.30's tool generics blow the TypeScript instantiation limit on these
|
|
140
141
|
// schemas. Runtime registration is the same registerTool call.
|
|
141
142
|
const register = server.registerTool.bind(server);
|
|
142
|
-
|
|
143
|
+
const guarded = async (args) => {
|
|
144
|
+
if (!apiKey) {
|
|
145
|
+
return fail("Desearch", new Error("Desearch API key required. Send it in the Authorization: Bearer <key> header or the x-api-key header."));
|
|
146
|
+
}
|
|
147
|
+
return handler(args);
|
|
148
|
+
};
|
|
149
|
+
register(name, {
|
|
150
|
+
title,
|
|
151
|
+
description,
|
|
152
|
+
inputSchema,
|
|
153
|
+
annotations: { ...READ_ONLY_OPEN_WORLD, title },
|
|
154
|
+
}, guarded);
|
|
143
155
|
}
|
|
144
156
|
/**
|
|
145
157
|
* One MCP server bound to a single Desearch API key.
|
|
@@ -154,7 +166,7 @@ export function createDesearchMcpServer(apiKey, client) {
|
|
|
154
166
|
name: SERVER_NAME,
|
|
155
167
|
version: SERVER_VERSION,
|
|
156
168
|
});
|
|
157
|
-
registerTool(server, "ai-search", "
|
|
169
|
+
registerTool(server, apiKey, "ai-search", "AI Search", "AI search and analysis on web using Desearch AI", {
|
|
158
170
|
prompt: z.string().describe("Question, example: 'What is the latest news on AI?'"),
|
|
159
171
|
tools: z
|
|
160
172
|
.array(toolIdSchema(AI_SEARCH_TOOLS))
|
|
@@ -197,7 +209,7 @@ export function createDesearchMcpServer(apiKey, client) {
|
|
|
197
209
|
model: z
|
|
198
210
|
.enum(["NOVA", "ORBIT"])
|
|
199
211
|
.default("NOVA")
|
|
200
|
-
.describe("Model to use for the search
|
|
212
|
+
.describe("Model to use for the search: NOVA (default) or ORBIT."),
|
|
201
213
|
}, async ({ prompt, tools, date_filter, start_date, end_date, result_type, include_domains, exclude_domains, model }) => {
|
|
202
214
|
try {
|
|
203
215
|
const payload = {
|
|
@@ -218,7 +230,7 @@ export function createDesearchMcpServer(apiKey, client) {
|
|
|
218
230
|
return fail("AI Search error", error);
|
|
219
231
|
}
|
|
220
232
|
});
|
|
221
|
-
registerTool(server, "x-search", "Search
|
|
233
|
+
registerTool(server, apiKey, "x-search", "X Search", "Search X (Twitter) using Desearch AI. Optional filters narrow by user, date, language, verification, media, and engagement. Sort stays Top.", {
|
|
222
234
|
query: z
|
|
223
235
|
.string()
|
|
224
236
|
.describe("Twitter advanced search query, example: 'from:elonmusk since:2023-01-01 min_replies:10'"),
|
|
@@ -271,7 +283,7 @@ export function createDesearchMcpServer(apiKey, client) {
|
|
|
271
283
|
return fail("X Search error", error);
|
|
272
284
|
}
|
|
273
285
|
});
|
|
274
|
-
registerTool(server, "web-search", "SERP-style web search using Desearch. Returns ranked titles, links, and snippets.", {
|
|
286
|
+
registerTool(server, apiKey, "web-search", "Web Search", "SERP-style web search using Desearch. Returns ranked titles, links, and snippets.", {
|
|
275
287
|
query: z.string().describe("Search query, example: 'latest news on AI'"),
|
|
276
288
|
start: z
|
|
277
289
|
.number()
|
|
@@ -287,7 +299,7 @@ export function createDesearchMcpServer(apiKey, client) {
|
|
|
287
299
|
return fail("Web Search error", error);
|
|
288
300
|
}
|
|
289
301
|
});
|
|
290
|
-
registerTool(server, "web-links-search", "Search the web for links using Desearch. Only the web source is accepted.", {
|
|
302
|
+
registerTool(server, apiKey, "web-links-search", "Web Links Search", "Search the web for links using Desearch. Only the web source is accepted.", {
|
|
291
303
|
prompt: z
|
|
292
304
|
.string()
|
|
293
305
|
.describe("Search query prompt, example: 'open source browser automation tools'"),
|
|
@@ -315,7 +327,7 @@ export function createDesearchMcpServer(apiKey, client) {
|
|
|
315
327
|
return fail("Web Links Search error", error);
|
|
316
328
|
}
|
|
317
329
|
});
|
|
318
|
-
registerTool(server, "x-links-search", "AI search for X (Twitter) post links using Desearch. Returns links from posts that match the prompt.", {
|
|
330
|
+
registerTool(server, apiKey, "x-links-search", "X Links Search", "AI search for X (Twitter) post links using Desearch. Returns links from posts that match the prompt.", {
|
|
319
331
|
prompt: z.string().describe("Search query prompt, example: 'Bittensor subnet updates'"),
|
|
320
332
|
count: optionalLinksCount,
|
|
321
333
|
}, async ({ prompt, count }) => {
|
|
@@ -326,7 +338,7 @@ export function createDesearchMcpServer(apiKey, client) {
|
|
|
326
338
|
return fail("X Links Search error", error);
|
|
327
339
|
}
|
|
328
340
|
});
|
|
329
|
-
registerTool(server, "x-posts-by-urls", "Fetch full X (Twitter) posts for a list of post URLs.", {
|
|
341
|
+
registerTool(server, apiKey, "x-posts-by-urls", "Get X Posts by URLs", "Fetch full X (Twitter) posts for a list of post URLs.", {
|
|
330
342
|
urls: z
|
|
331
343
|
.array(z.string())
|
|
332
344
|
.min(1)
|
|
@@ -339,7 +351,7 @@ export function createDesearchMcpServer(apiKey, client) {
|
|
|
339
351
|
return fail("X Posts By URLs error", error);
|
|
340
352
|
}
|
|
341
353
|
});
|
|
342
|
-
registerTool(server, "x-post-by-id", "Fetch a single X (Twitter) post by its ID.", {
|
|
354
|
+
registerTool(server, apiKey, "x-post-by-id", "Get X Post by ID", "Fetch a single X (Twitter) post by its ID.", {
|
|
343
355
|
id: z.string().describe("The unique ID of the post, example: '1234567890'"),
|
|
344
356
|
}, async ({ id }) => {
|
|
345
357
|
try {
|
|
@@ -349,7 +361,7 @@ export function createDesearchMcpServer(apiKey, client) {
|
|
|
349
361
|
return fail("X Post By ID error", error);
|
|
350
362
|
}
|
|
351
363
|
});
|
|
352
|
-
registerTool(server, "x-posts-by-user", "Search X (Twitter) posts by a specific user, with an optional keyword query.", {
|
|
364
|
+
registerTool(server, apiKey, "x-posts-by-user", "Search X Posts by User", "Search X (Twitter) posts by a specific user, with an optional keyword query.", {
|
|
353
365
|
user: z.string().describe("User to search for, example: 'elonmusk'"),
|
|
354
366
|
query: z.string().optional().describe("Advanced search query to filter this user's posts."),
|
|
355
367
|
count: optionalPostCount,
|
|
@@ -361,7 +373,7 @@ export function createDesearchMcpServer(apiKey, client) {
|
|
|
361
373
|
return fail("X Posts By User error", error);
|
|
362
374
|
}
|
|
363
375
|
});
|
|
364
|
-
registerTool(server, "x-post-retweeters", "List users who retweeted an X (Twitter) post. Pass cursor to page through more users.", {
|
|
376
|
+
registerTool(server, apiKey, "x-post-retweeters", "List X Post Retweeters", "List users who retweeted an X (Twitter) post. Pass cursor to page through more users.", {
|
|
365
377
|
id: z.string().describe("The ID of the post to get retweeters for."),
|
|
366
378
|
cursor: z.string().optional().describe("Cursor for pagination from a previous response."),
|
|
367
379
|
}, async ({ id, cursor }) => {
|
|
@@ -372,7 +384,7 @@ export function createDesearchMcpServer(apiKey, client) {
|
|
|
372
384
|
return fail("X Post Retweeters error", error);
|
|
373
385
|
}
|
|
374
386
|
});
|
|
375
|
-
registerTool(server, "x-user-posts", "Retrieve a user's X (Twitter) timeline posts by username. Pass cursor to page through more posts.", {
|
|
387
|
+
registerTool(server, apiKey, "x-user-posts", "Get X User Timeline", "Retrieve a user's X (Twitter) timeline posts by username. Pass cursor to page through more posts.", {
|
|
376
388
|
username: z.string().describe("Username to fetch posts for, example: 'elonmusk'"),
|
|
377
389
|
cursor: z.string().optional().describe("Cursor for pagination from a previous response."),
|
|
378
390
|
}, async ({ username, cursor }) => {
|
|
@@ -383,7 +395,7 @@ export function createDesearchMcpServer(apiKey, client) {
|
|
|
383
395
|
return fail("X User Posts error", error);
|
|
384
396
|
}
|
|
385
397
|
});
|
|
386
|
-
registerTool(server, "x-user-replies", "Fetch posts and replies by an X (Twitter) user, with an optional keyword query.", {
|
|
398
|
+
registerTool(server, apiKey, "x-user-replies", "Get X User Replies", "Fetch posts and replies by an X (Twitter) user, with an optional keyword query.", {
|
|
387
399
|
user: z.string().describe("Username of the user to search for, example: 'elonmusk'"),
|
|
388
400
|
count: optionalPostCount,
|
|
389
401
|
query: z.string().optional().describe("Advanced search query to filter this user's posts and replies."),
|
|
@@ -395,7 +407,7 @@ export function createDesearchMcpServer(apiKey, client) {
|
|
|
395
407
|
return fail("X User Replies error", error);
|
|
396
408
|
}
|
|
397
409
|
});
|
|
398
|
-
registerTool(server, "x-post-replies", "Fetch replies to an X (Twitter) post, with an optional keyword query.", {
|
|
410
|
+
registerTool(server, apiKey, "x-post-replies", "Get X Post Replies", "Fetch replies to an X (Twitter) post, with an optional keyword query.", {
|
|
399
411
|
post_id: z.string().describe("The ID of the post to fetch replies for."),
|
|
400
412
|
count: optionalPostCount,
|
|
401
413
|
query: z.string().optional().describe("Advanced search query to filter replies."),
|
|
@@ -407,7 +419,7 @@ export function createDesearchMcpServer(apiKey, client) {
|
|
|
407
419
|
return fail("X Post Replies error", error);
|
|
408
420
|
}
|
|
409
421
|
});
|
|
410
|
-
registerTool(server, "extract", "Extract a public URL and return its content as plain text or HTML using Desearch. Preferred over web-crawl for new integrations.", pageContentFields(), async ({ url, format, js, wait }) => {
|
|
422
|
+
registerTool(server, apiKey, "extract", "Extract Page Content", "Extract a public URL and return its content as plain text or HTML using Desearch. Preferred over web-crawl for new integrations.", pageContentFields(), async ({ url, format, js, wait }) => {
|
|
411
423
|
try {
|
|
412
424
|
return ok(await desearch.extract({ url, format, js, wait }));
|
|
413
425
|
}
|
|
@@ -415,7 +427,7 @@ export function createDesearchMcpServer(apiKey, client) {
|
|
|
415
427
|
return fail("Extract error", error);
|
|
416
428
|
}
|
|
417
429
|
});
|
|
418
|
-
registerTool(server, "web-crawl", "Crawl a public URL and return its content as plain text or HTML on the legacy Desearch /web/crawl route. The SDK marks webCrawl deprecated in favor of extract; this tool stays for parity with that route. Prefer extract for new integrations.", pageContentFields(), async ({ url, format, js, wait }) => {
|
|
430
|
+
registerTool(server, apiKey, "web-crawl", "Crawl Web Page (Legacy)", "Crawl a public URL and return its content as plain text or HTML on the legacy Desearch /web/crawl route. The SDK marks webCrawl deprecated in favor of extract; this tool stays for parity with that route. Prefer extract for new integrations.", pageContentFields(), async ({ url, format, js, wait }) => {
|
|
419
431
|
try {
|
|
420
432
|
return ok(await desearch.webCrawl({ url, format, js, wait }));
|
|
421
433
|
}
|
|
@@ -423,7 +435,7 @@ export function createDesearchMcpServer(apiKey, client) {
|
|
|
423
435
|
return fail("Web Crawl error", error);
|
|
424
436
|
}
|
|
425
437
|
});
|
|
426
|
-
registerTool(server, "x-trends", "Retrieve trending topics on X (Twitter) for a location by its WOEID using Desearch.", {
|
|
438
|
+
registerTool(server, apiKey, "x-trends", "Get X Trends", "Retrieve trending topics on X (Twitter) for a location by its WOEID using Desearch.", {
|
|
427
439
|
woeid: z
|
|
428
440
|
.number()
|
|
429
441
|
.int()
|
|
@@ -443,5 +455,17 @@ export function createDesearchMcpServer(apiKey, client) {
|
|
|
443
455
|
return fail("X Trends error", error);
|
|
444
456
|
}
|
|
445
457
|
});
|
|
458
|
+
// Scanners ask for these lists during discovery. There is nothing to return,
|
|
459
|
+
// but an empty list is a successful response. Method-not-found looks like a
|
|
460
|
+
// broken server to some directory crawlers.
|
|
461
|
+
server.server.registerCapabilities({
|
|
462
|
+
prompts: {},
|
|
463
|
+
resources: {},
|
|
464
|
+
});
|
|
465
|
+
server.server.setRequestHandler(ListPromptsRequestSchema, () => ({ prompts: [] }));
|
|
466
|
+
server.server.setRequestHandler(ListResourcesRequestSchema, () => ({ resources: [] }));
|
|
467
|
+
server.server.setRequestHandler(ListResourceTemplatesRequestSchema, () => ({
|
|
468
|
+
resourceTemplates: [],
|
|
469
|
+
}));
|
|
446
470
|
return server;
|
|
447
471
|
}
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "desearch-mcp-server",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"mcpName": "io.github.Desearch-ai/mcp-desearch",
|
|
5
|
-
"description": "
|
|
5
|
+
"description": "AI search, X search and web search for AI agents, plus page extraction and X data tools. Bring your own Desearch API key.",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"homepage": "https://www.desearch.ai/docs/guide/sdk/mcp",
|
|
8
8
|
"type": "module",
|