desearch-mcp-server 0.0.1 → 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/build/http.js ADDED
@@ -0,0 +1,324 @@
1
+ import { createServer } from "node:http";
2
+ import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
3
+ import { createDesearchMcpServer, SERVER_NAME, SERVER_VERSION } from "./server.js";
4
+ const CORS_HEADERS = {
5
+ "Access-Control-Allow-Origin": "*",
6
+ "Access-Control-Allow-Methods": "GET, POST, DELETE, OPTIONS",
7
+ "Access-Control-Allow-Headers": "Content-Type, Accept, Authorization, x-api-key, Mcp-Session-Id, Mcp-Protocol-Version, Last-Event-ID",
8
+ "Access-Control-Expose-Headers": "Mcp-Session-Id",
9
+ "Access-Control-Max-Age": "86400",
10
+ };
11
+ const HOP_BY_HOP = new Set([
12
+ "connection",
13
+ "keep-alive",
14
+ "transfer-encoding",
15
+ "upgrade",
16
+ "host",
17
+ "content-length",
18
+ ]);
19
+ /**
20
+ * Desearch API key from the remote client.
21
+ * Prefer `Authorization: Bearer <key>` (what MCP clients send). `x-api-key` is
22
+ * accepted as well. A bare `Authorization: <key>` value is accepted so the same
23
+ * string used as `DESEARCH_API_KEY` for stdio can be sent without a scheme.
24
+ * Query strings are ignored so keys do not land in access logs.
25
+ */
26
+ /**
27
+ * Copy a request onto another path. Used by the Vercel functions, which see
28
+ * the rewritten URL (`/api/mcp`) rather than the public path (`/mcp`).
29
+ */
30
+ export function requestWithPathname(request, pathname) {
31
+ const url = new URL(request.url);
32
+ url.pathname = pathname;
33
+ const method = request.method;
34
+ const init = {
35
+ method,
36
+ headers: request.headers,
37
+ };
38
+ if (method !== "GET" && method !== "HEAD") {
39
+ init.body = request.body;
40
+ init.duplex = "half";
41
+ }
42
+ return new Request(url, init);
43
+ }
44
+ export function extractDesearchApiKey(request) {
45
+ const authorization = request.headers.get("authorization")?.trim();
46
+ if (authorization) {
47
+ const bearer = /^Bearer\s+(\S+)$/i.exec(authorization);
48
+ if (bearer?.[1]) {
49
+ return bearer[1];
50
+ }
51
+ if (!/^Bearer\b/i.test(authorization) && !/\s/.test(authorization)) {
52
+ return authorization;
53
+ }
54
+ }
55
+ const headerKey = request.headers.get("x-api-key")?.trim();
56
+ if (headerKey) {
57
+ return headerKey;
58
+ }
59
+ return undefined;
60
+ }
61
+ function normalizePath(pathname) {
62
+ if (pathname.length > 1 && pathname.endsWith("/")) {
63
+ return pathname.slice(0, -1);
64
+ }
65
+ return pathname;
66
+ }
67
+ function isMcpPath(pathname) {
68
+ return pathname === "/mcp" || pathname === "/api/mcp";
69
+ }
70
+ function isHealthPath(pathname) {
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";
75
+ }
76
+ function withCors(response) {
77
+ const headers = new Headers(response.headers);
78
+ for (const [key, value] of Object.entries(CORS_HEADERS)) {
79
+ headers.set(key, value);
80
+ }
81
+ return new Response(response.body, {
82
+ status: response.status,
83
+ statusText: response.statusText,
84
+ headers,
85
+ });
86
+ }
87
+ function jsonRpcError(status, code, message, extra) {
88
+ const headers = new Headers({ "Content-Type": "application/json", ...extra });
89
+ return new Response(JSON.stringify({
90
+ jsonrpc: "2.0",
91
+ error: { code, message },
92
+ id: null,
93
+ }), { status, headers });
94
+ }
95
+ function healthResponse() {
96
+ return new Response(JSON.stringify({
97
+ ok: true,
98
+ name: SERVER_NAME,
99
+ version: SERVER_VERSION,
100
+ transport: "streamable-http",
101
+ endpoint: "/mcp",
102
+ auth: "initialize and tools/list are public. tools/call requires Authorization: Bearer <DESEARCH_API_KEY> or x-api-key.",
103
+ }), {
104
+ status: 200,
105
+ headers: { "Content-Type": "application/json" },
106
+ });
107
+ }
108
+ function unauthorized() {
109
+ // No OAuth challenge. Desearch auth is a per-user API key, and a
110
+ // WWW-Authenticate discovery hint makes some MCP clients start an OAuth flow.
111
+ return jsonRpcError(401, -32001, "Unauthorized. Send your Desearch API key in the Authorization: Bearer <key> header or the x-api-key header.");
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
+ }
169
+ /**
170
+ * Stateless Streamable HTTP handler.
171
+ * Each POST builds a fresh MCP server tied to that request's API key.
172
+ * JSON responses (not a long-lived SSE session) so the same handler runs
173
+ * in a local Node process and in a Vercel function.
174
+ * GET and DELETE return 405: this server does not push messages or store sessions.
175
+ */
176
+ export async function handleMcpHttpRequest(request) {
177
+ if (request.method === "OPTIONS") {
178
+ return withCors(new Response(null, { status: 204 }));
179
+ }
180
+ const pathname = normalizePath(new URL(request.url).pathname);
181
+ if (isHealthPath(pathname)) {
182
+ if (request.method === "GET") {
183
+ return withCors(healthResponse());
184
+ }
185
+ return withCors(jsonRpcError(405, -32000, "Method not allowed.", { Allow: "GET" }));
186
+ }
187
+ if (!isMcpPath(pathname)) {
188
+ return withCors(new Response(JSON.stringify({ error: "Not found" }), {
189
+ status: 404,
190
+ headers: { "Content-Type": "application/json" },
191
+ }));
192
+ }
193
+ if (request.method === "GET" || request.method === "DELETE") {
194
+ if (!extractDesearchApiKey(request)) {
195
+ return withCors(unauthorized());
196
+ }
197
+ return withCors(jsonRpcError(405, -32000, "Method not allowed. This server is stateless; use POST.", { Allow: "POST" }));
198
+ }
199
+ if (request.method !== "POST") {
200
+ return withCors(jsonRpcError(405, -32000, "Method not allowed.", { Allow: "POST" }));
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
+ }
211
+ const server = createDesearchMcpServer(apiKey);
212
+ const transport = new WebStandardStreamableHTTPServerTransport({
213
+ enableJsonResponse: true,
214
+ });
215
+ transport.onerror = (error) => {
216
+ console.error(`MCP HTTP error: ${error.message}`);
217
+ };
218
+ try {
219
+ await server.connect(transport);
220
+ const response = await transport.handleRequest(request);
221
+ return withCors(response);
222
+ }
223
+ catch (error) {
224
+ console.error(`MCP HTTP handler error: ${error instanceof Error ? error.message : String(error)}`);
225
+ return withCors(jsonRpcError(500, -32603, "Internal server error"));
226
+ }
227
+ finally {
228
+ await transport.close().catch(() => undefined);
229
+ await server.close().catch(() => undefined);
230
+ }
231
+ }
232
+ function readRawBody(req) {
233
+ return new Promise((resolve, reject) => {
234
+ const chunks = [];
235
+ req.on("data", (chunk) => {
236
+ chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
237
+ });
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))));
242
+ req.on("error", reject);
243
+ });
244
+ }
245
+ async function toWebRequest(req) {
246
+ const host = req.headers.host ?? "localhost";
247
+ const url = `http://${host}${req.url ?? "/"}`;
248
+ const headers = new Headers();
249
+ for (const [key, value] of Object.entries(req.headers)) {
250
+ if (value === undefined || HOP_BY_HOP.has(key.toLowerCase())) {
251
+ continue;
252
+ }
253
+ if (Array.isArray(value)) {
254
+ for (const entry of value) {
255
+ headers.append(key, entry);
256
+ }
257
+ }
258
+ else {
259
+ headers.set(key, value);
260
+ }
261
+ }
262
+ const method = req.method ?? "GET";
263
+ const hasBody = method !== "GET" && method !== "HEAD";
264
+ const body = hasBody ? await readRawBody(req) : undefined;
265
+ return new Request(url, {
266
+ method,
267
+ headers,
268
+ body: body && body.byteLength > 0 ? body : undefined,
269
+ });
270
+ }
271
+ async function writeWebResponse(res, response) {
272
+ const body = Buffer.from(await response.arrayBuffer());
273
+ const headers = new Headers(response.headers);
274
+ headers.set("content-length", String(body.byteLength));
275
+ const outgoing = {};
276
+ headers.forEach((value, key) => {
277
+ outgoing[key] = value;
278
+ });
279
+ res.writeHead(response.status, outgoing);
280
+ res.end(body);
281
+ }
282
+ export function startHttpServer(options = {}) {
283
+ const host = options.host ?? "0.0.0.0";
284
+ const port = options.port ?? 3000;
285
+ const nodeServer = createServer(async (req, res) => {
286
+ try {
287
+ const request = await toWebRequest(req);
288
+ const response = await handleMcpHttpRequest(request);
289
+ await writeWebResponse(res, response);
290
+ }
291
+ catch (error) {
292
+ console.error(`HTTP server error: ${error instanceof Error ? error.message : String(error)}`);
293
+ if (!res.headersSent) {
294
+ res.writeHead(500, { "Content-Type": "application/json" });
295
+ res.end(JSON.stringify({
296
+ jsonrpc: "2.0",
297
+ error: { code: -32603, message: "Internal server error" },
298
+ id: null,
299
+ }));
300
+ }
301
+ else {
302
+ res.end();
303
+ }
304
+ }
305
+ });
306
+ return new Promise((resolve, reject) => {
307
+ nodeServer.once("error", reject);
308
+ nodeServer.listen(port, host, () => {
309
+ const address = nodeServer.address();
310
+ const boundPort = typeof address === "object" && address ? address.port : port;
311
+ console.error(`Desearch MCP listening on ${host}:${boundPort} (endpoint /mcp)`);
312
+ resolve({
313
+ host,
314
+ port: boundPort,
315
+ close: () => new Promise((closeResolve, closeReject) => {
316
+ // Client transports abort the optional GET stream and can leave
317
+ // a socket that close() would otherwise hold until timeout.
318
+ nodeServer.closeAllConnections();
319
+ nodeServer.close((error) => (error ? closeReject(error) : closeResolve()));
320
+ }),
321
+ });
322
+ });
323
+ });
324
+ }
package/build/index.js CHANGED
@@ -1,124 +1,30 @@
1
1
  #!/usr/bin/env node
2
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
2
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
- import { z } from "zod";
5
- import Desearch from "desearch-js";
6
- const server = new McpServer({
7
- name: "Desearch",
8
- version: "0.0.1",
9
- });
10
- const DESEARCH_API_KEY = process.env.DESEARCH_API_KEY;
11
- if (!DESEARCH_API_KEY) {
12
- throw new Error("DESEARCH_API_KEY environment variable is required");
3
+ import { startHttpServer } from "./http.js";
4
+ import { createDesearchMcpServer } from "./server.js";
5
+ function wantsHttp() {
6
+ return process.argv.includes("--http") || process.env.MCP_TRANSPORT === "http";
13
7
  }
14
- const desearch = new Desearch(DESEARCH_API_KEY);
15
- server.tool("ai-search", "Real-time AI search and analysis on web using Desearch AI", {
16
- prompt: z.string().describe("Question, example: 'What is the latest news on AI?'"),
17
- tools: z
18
- .array(z.enum([
19
- "Twitter Search",
20
- "Web Search",
21
- "ArXiv Search",
22
- "Wikipedia Search",
23
- "Youtube Search",
24
- "Hacker News Search",
25
- "Reddit Search",
26
- ]))
27
- .optional()
28
- .default(["Twitter Search", "Web Search"])
29
- .describe("Tools to use for the search, example: ['Web Search', 'Twitter Search']"),
30
- date_filter: z
31
- .enum([
32
- "PAST_24_HOURS",
33
- "PAST_2_DAYS",
34
- "PAST_WEEK",
35
- "PAST_2_WEEKS",
36
- "PAST_MONTH",
37
- "PAST_2_MONTHS",
38
- "PAST_YEAR",
39
- "PAST_2_YEARS",
40
- ])
41
- .optional()
42
- .describe("Time range for the search, example: 'PAST_WEEK'"),
43
- model: z
44
- .enum(["NOVA", "ORBIT"])
45
- .default("NOVA")
46
- .describe("Model to use for the search, example: 'NOVA', Nova is 10s model, Orbit is 30s model"),
47
- }, async ({ prompt, tools, date_filter, model }) => {
48
- try {
49
- const aiResult = await desearch.AISearch({
50
- prompt,
51
- tools,
52
- date_filter,
53
- model,
54
- streaming: false,
55
- });
56
- return {
57
- content: [
58
- {
59
- type: "text",
60
- text: JSON.stringify(aiResult, null, 2),
61
- },
62
- ],
63
- };
64
- }
65
- catch (error) {
66
- return {
67
- content: [
68
- {
69
- type: "text",
70
- text: `X Search error: ${error instanceof Error ? error.message : String(error)}`,
71
- },
72
- ],
73
- isError: true,
74
- };
75
- }
76
- });
77
- server.tool("x-search", "Search the X (Twitter) using Desearch AI - performs real-time tweet search on X.", {
78
- query: z
79
- .string()
80
- .describe("Twitter advanced search query, example: 'from:elonmusk since:2023-01-01 min_replies:10'"),
81
- count: z
82
- .number()
83
- .optional()
84
- .default(20)
85
- .describe("Number of search results to return (default: 20), max is 100"),
86
- }, async ({ query, count }) => {
87
- try {
88
- const twitterResult = await desearch.twitterSearch({
89
- query,
90
- sort: "Top",
91
- count,
92
- });
93
- return {
94
- content: [
95
- {
96
- type: "text",
97
- text: JSON.stringify(twitterResult, null, 2),
98
- },
99
- ],
100
- };
8
+ async function main() {
9
+ if (wantsHttp()) {
10
+ const port = Number(process.env.PORT ?? "3000");
11
+ if (!Number.isInteger(port) || port < 0 || port > 65535) {
12
+ throw new Error("PORT must be an integer between 0 and 65535");
13
+ }
14
+ const host = process.env.HOST ?? "0.0.0.0";
15
+ await startHttpServer({ port, host });
16
+ return;
101
17
  }
102
- catch (error) {
103
- return {
104
- content: [
105
- {
106
- type: "text",
107
- text: `X Search error: ${error instanceof Error ? error.message : String(error)}`,
108
- },
109
- ],
110
- isError: true,
111
- };
18
+ const apiKey = process.env.DESEARCH_API_KEY;
19
+ if (!apiKey) {
20
+ throw new Error("DESEARCH_API_KEY environment variable is required");
112
21
  }
22
+ const server = createDesearchMcpServer(apiKey);
23
+ const transport = new StdioServerTransport();
24
+ await server.connect(transport);
25
+ console.error("Desearch server is running...");
26
+ }
27
+ main().catch((error) => {
28
+ console.error(`Server error: ${error instanceof Error ? error.message : String(error)}`);
29
+ process.exit(1);
113
30
  });
114
- (async () => {
115
- try {
116
- // Start receiving messages on stdin and sending messages on stdout
117
- const transport = new StdioServerTransport();
118
- await server.connect(transport);
119
- }
120
- catch (error) {
121
- console.log(`Server error: ${error instanceof Error ? error.message : String(error)}`);
122
- process.exit(1);
123
- }
124
- })();
@@ -0,0 +1,103 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare const SERVER_NAME = "Desearch";
3
+ export declare const SERVER_VERSION = "0.1.3";
4
+ interface XSearchPayload {
5
+ query: string;
6
+ sort?: "Top" | "Latest";
7
+ count?: number;
8
+ user?: string;
9
+ start_date?: string;
10
+ end_date?: string;
11
+ lang?: string;
12
+ verified?: boolean;
13
+ blue_verified?: boolean;
14
+ is_quote?: boolean;
15
+ is_video?: boolean;
16
+ is_image?: boolean;
17
+ min_retweets?: number | string;
18
+ min_replies?: number | string;
19
+ min_likes?: number | string;
20
+ }
21
+ interface PageContentPayload {
22
+ url: string;
23
+ format?: "html" | "text";
24
+ js?: boolean;
25
+ wait?: number;
26
+ }
27
+ interface DesearchClient {
28
+ aiSearch(payload: Record<string, unknown>): Promise<unknown>;
29
+ xSearch(payload: XSearchPayload): Promise<unknown>;
30
+ webSearch(payload: {
31
+ query: string;
32
+ start?: number;
33
+ }): Promise<unknown>;
34
+ aiWebLinksSearch(payload: {
35
+ prompt: string;
36
+ tools: string[];
37
+ count?: number;
38
+ }): Promise<unknown>;
39
+ aiXLinksSearch(payload: {
40
+ prompt: string;
41
+ count?: number;
42
+ }): Promise<unknown>;
43
+ xPostsByUrls(payload: {
44
+ urls: string[];
45
+ }): Promise<unknown>;
46
+ xPostById(payload: {
47
+ id: string;
48
+ }): Promise<unknown>;
49
+ xPostsByUser(payload: {
50
+ user: string;
51
+ query?: string;
52
+ count?: number;
53
+ }): Promise<unknown>;
54
+ xPostRetweeters(payload: {
55
+ id: string;
56
+ cursor?: string;
57
+ }): Promise<unknown>;
58
+ xUserPosts(payload: {
59
+ username: string;
60
+ cursor?: string;
61
+ }): Promise<unknown>;
62
+ xUserReplies(payload: {
63
+ user: string;
64
+ count?: number;
65
+ query?: string;
66
+ }): Promise<unknown>;
67
+ xPostReplies(payload: {
68
+ post_id: string;
69
+ count?: number;
70
+ query?: string;
71
+ }): Promise<unknown>;
72
+ xTrends(payload: {
73
+ woeid: number;
74
+ count?: number;
75
+ }): Promise<unknown>;
76
+ extract(payload: PageContentPayload): Promise<unknown>;
77
+ webCrawl(payload: PageContentPayload): Promise<unknown>;
78
+ }
79
+ /**
80
+ * Payload note when a link search body has no link list.
81
+ * Does not name web vs AI search, and does not say the query had "results".
82
+ * A missing list (the cost-only API bug) and an empty list are the same note:
83
+ * this response body does not contain links.
84
+ */
85
+ export declare const NO_LINKS_MESSAGE = "no links in response";
86
+ /**
87
+ * Pass a link payload through when it contains at least one link.
88
+ * A cost-only body (billing fields, no link list) and an empty link list
89
+ * both get `message: "no links in response"` plus the original fields,
90
+ * including billing. The note does not claim the search returned results
91
+ * and does not name which search tool produced the body.
92
+ */
93
+ export declare function presentSearchBody(value: unknown): unknown;
94
+ /**
95
+ * One MCP server bound to a single Desearch API key.
96
+ * Stdio uses the process env key. Remote HTTP builds a new server per request
97
+ * so each caller spends their own key.
98
+ * `client` is a test seam. Production callers omit it and the server builds
99
+ * a desearch-js client from `apiKey`.
100
+ */
101
+ export declare function createDesearchMcpServer(apiKey: string, client?: DesearchClient): McpServer;
102
+ export {};
103
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../server.ts"],"names":[],"mappings":"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"}