| import { z } from "zod"; | |
| import { McpServer } from "@/libs/mcp-server/mcp"; | |
| import DeepResearch from "@/utils/deep-research"; | |
| import { multiApiKeyPolling } from "@/utils/model"; | |
| import { | |
| getAIProviderBaseURL, | |
| getAIProviderApiKey, | |
| getSearchProviderBaseURL, | |
| getSearchProviderApiKey, | |
| } from "../utils"; | |
| const AI_PROVIDER = process.env.MCP_AI_PROVIDER || ""; | |
| const SEARCH_PROVIDER = process.env.MCP_SEARCH_PROVIDER || "model"; | |
| const THINKING_MODEL = process.env.MCP_THINKING_MODEL || ""; | |
| const TASK_MODEL = process.env.MCP_TASK_MODEL || ""; | |
| function initDeepResearchServer({ | |
| language, | |
| maxResult, | |
| }: { | |
| language?: string; | |
| maxResult?: number; | |
| }) { | |
| const deepResearch = new DeepResearch({ | |
| language, | |
| AIProvider: { | |
| baseURL: getAIProviderBaseURL(AI_PROVIDER), | |
| apiKey: multiApiKeyPolling(getAIProviderApiKey(AI_PROVIDER)), | |
| provider: AI_PROVIDER, | |
| thinkingModel: THINKING_MODEL, | |
| taskModel: TASK_MODEL, | |
| }, | |
| searchProvider: { | |
| baseURL: getSearchProviderBaseURL(SEARCH_PROVIDER), | |
| apiKey: multiApiKeyPolling(getSearchProviderApiKey(SEARCH_PROVIDER)), | |
| provider: SEARCH_PROVIDER, | |
| maxResult, | |
| }, | |
| onMessage: (event, data) => { | |
| if (event === "progress") { | |
| console.log( | |
| `[${data.step}]: ${data.name ? `"${data.name}" ` : ""}${data.status}` | |
| ); | |
| if (data.status === "end" && data.data) { | |
| console.log(data.data); | |
| } | |
| } else if (event === "error") { | |
| console.error(data.message); | |
| throw new Error(data.message); | |
| } | |
| }, | |
| }); | |
| return deepResearch; | |
| } | |
| export function initMcpServer() { | |
| const deepResearchToolDescription = | |
| "Start deep research on any question, obtain and organize information through search engines, and generate research report."; | |
| const writeResearchPlanDescription = | |
| "Generate research plan based on user query."; | |
| const generateSERPQueryDescription = | |
| "Generate a list of data collection tasks based on the research plan."; | |
| const searchTaskDescription = | |
| "Generate SERP queries based on the research plan."; | |
| const writeFinalReportDescription = | |
| "Write a final research report based on the research plan and the results of the information collection tasks."; | |
| const server = new McpServer( | |
| { | |
| name: "deep-research", | |
| version: "0.1.0", | |
| }, | |
| { | |
| capabilities: { | |
| tools: { | |
| "deep-research": { | |
| description: deepResearchToolDescription, | |
| }, | |
| "write-research-plan": { | |
| description: writeResearchPlanDescription, | |
| }, | |
| "generate-SERP-query": { | |
| description: generateSERPQueryDescription, | |
| }, | |
| "search-task": { | |
| description: searchTaskDescription, | |
| }, | |
| "write-final-report": { | |
| description: writeFinalReportDescription, | |
| }, | |
| }, | |
| }, | |
| } | |
| ); | |
| server.tool( | |
| "deep-research", | |
| deepResearchToolDescription, | |
| { | |
| query: z.string().describe("The topic for deep research."), | |
| language: z | |
| .string() | |
| .optional() | |
| .describe("The final report text language."), | |
| maxResult: z | |
| .number() | |
| .optional() | |
| .default(5) | |
| .describe("Maximum number of search results."), | |
| enableCitationImage: z | |
| .boolean() | |
| .default(true) | |
| .optional() | |
| .describe( | |
| "Whether to include content-related images in the final report." | |
| ), | |
| enableReferences: z | |
| .boolean() | |
| .default(true) | |
| .optional() | |
| .describe( | |
| "Whether to include citation links in search results and final reports." | |
| ), | |
| }, | |
| async ( | |
| { query, language, maxResult, enableCitationImage, enableReferences }, | |
| { signal } | |
| ) => { | |
| signal.addEventListener("abort", () => { | |
| throw new Error("The client closed unexpectedly!"); | |
| }); | |
| try { | |
| const deepResearch = initDeepResearchServer({ | |
| language, | |
| maxResult, | |
| }); | |
| const result = await deepResearch.start( | |
| query, | |
| enableCitationImage, | |
| enableReferences | |
| ); | |
| return { | |
| content: [{ type: "text", text: JSON.stringify(result) }], | |
| }; | |
| } catch (error) { | |
| return { | |
| isError: true, | |
| content: [ | |
| { | |
| type: "text", | |
| text: `Error: ${ | |
| error instanceof Error ? error.message : "Unknown error" | |
| }`, | |
| }, | |
| ], | |
| }; | |
| } | |
| } | |
| ); | |
| server.tool( | |
| "write-research-plan", | |
| writeResearchPlanDescription, | |
| { | |
| query: z.string().describe("The topic for deep research."), | |
| language: z.string().optional().describe("The response Language."), | |
| }, | |
| async ({ query, language }, { signal }) => { | |
| signal.addEventListener("abort", () => { | |
| throw new Error("The client closed unexpectedly!"); | |
| }); | |
| try { | |
| const deepResearch = initDeepResearchServer({ language }); | |
| const result = await deepResearch.writeReportPlan(query); | |
| return { | |
| content: [ | |
| { type: "text", text: JSON.stringify({ reportPlan: result }) }, | |
| ], | |
| }; | |
| } catch (error) { | |
| return { | |
| isError: true, | |
| content: [ | |
| { | |
| type: "text", | |
| text: `Error: ${ | |
| error instanceof Error ? error.message : "Unknown error" | |
| }`, | |
| }, | |
| ], | |
| }; | |
| } | |
| } | |
| ); | |
| server.tool( | |
| "generate-SERP-query", | |
| generateSERPQueryDescription, | |
| { | |
| plan: z.string().describe("Research plan for deep research."), | |
| language: z.string().optional().describe("The response Language."), | |
| }, | |
| async ({ plan, language }, { signal }) => { | |
| signal.addEventListener("abort", () => { | |
| throw new Error("The client closed unexpectedly!"); | |
| }); | |
| try { | |
| const deepResearch = initDeepResearchServer({ language }); | |
| const result = await deepResearch.generateSERPQuery(plan); | |
| return { | |
| content: [{ type: "text", text: JSON.stringify(result) }], | |
| }; | |
| } catch (error) { | |
| return { | |
| isError: true, | |
| content: [ | |
| { | |
| type: "text", | |
| text: `Error: ${ | |
| error instanceof Error ? error.message : "Unknown error" | |
| }`, | |
| }, | |
| ], | |
| }; | |
| } | |
| } | |
| ); | |
| server.tool( | |
| "search-task", | |
| searchTaskDescription, | |
| { | |
| tasks: z | |
| .array( | |
| z.object({ | |
| query: z.string().describe("Information to be queried."), | |
| researchGoal: z.string().describe("The goal of this query task."), | |
| }) | |
| ) | |
| .describe("Information Collection Task List."), | |
| language: z.string().optional().describe("The response Language."), | |
| maxResult: z | |
| .number() | |
| .optional() | |
| .default(5) | |
| .describe("Maximum number of search results."), | |
| enableReferences: z | |
| .boolean() | |
| .default(true) | |
| .optional() | |
| .describe( | |
| "Whether to include citation links in search results and final reports." | |
| ), | |
| }, | |
| async ( | |
| { tasks, language, maxResult, enableReferences = true }, | |
| { signal } | |
| ) => { | |
| signal.addEventListener("abort", () => { | |
| throw new Error("The client closed unexpectedly!"); | |
| }); | |
| try { | |
| const deepResearch = initDeepResearchServer({ language, maxResult }); | |
| const result = await deepResearch.runSearchTask( | |
| tasks, | |
| enableReferences | |
| ); | |
| return { | |
| content: [{ type: "text", text: JSON.stringify(result) }], | |
| }; | |
| } catch (error) { | |
| return { | |
| isError: true, | |
| content: [ | |
| { | |
| type: "text", | |
| text: `Error: ${ | |
| error instanceof Error ? error.message : "Unknown error" | |
| }`, | |
| }, | |
| ], | |
| }; | |
| } | |
| } | |
| ); | |
| server.tool( | |
| "write-final-report", | |
| writeFinalReportDescription, | |
| { | |
| plan: z.string().describe("Research plan for deep research."), | |
| tasks: z | |
| .array( | |
| z.object({ | |
| query: z.string().describe("Information to be queried."), | |
| researchGoal: z.string().describe("The goal of this query task."), | |
| learning: z | |
| .string() | |
| .describe( | |
| "Knowledge learned while performing information gathering tasks." | |
| ), | |
| sources: z | |
| .array( | |
| z.object({ | |
| url: z.string().describe("Web link."), | |
| title: z.string().optional().describe("Page title."), | |
| }) | |
| ) | |
| .optional() | |
| .describe( | |
| "Web page information that was queried when performing information collection tasks." | |
| ), | |
| images: z | |
| .array( | |
| z.object({ | |
| url: z.string().describe("Image link."), | |
| description: z | |
| .string() | |
| .optional() | |
| .describe("Image Description."), | |
| }) | |
| ) | |
| .optional() | |
| .describe( | |
| "Image resources obtained when performing information collection tasks." | |
| ), | |
| }) | |
| ) | |
| .describe( | |
| "The data information collected during the execution of the query task." | |
| ), | |
| language: z | |
| .string() | |
| .optional() | |
| .describe("The final report text language."), | |
| maxResult: z | |
| .number() | |
| .optional() | |
| .default(5) | |
| .describe("Maximum number of search results."), | |
| enableCitationImage: z | |
| .boolean() | |
| .default(true) | |
| .optional() | |
| .describe( | |
| "Whether to include content-related images in the final report." | |
| ), | |
| enableReferences: z | |
| .boolean() | |
| .default(true) | |
| .optional() | |
| .describe( | |
| "Whether to include citation links in search results and final reports." | |
| ), | |
| }, | |
| async ( | |
| { | |
| plan, | |
| tasks, | |
| language, | |
| maxResult, | |
| enableCitationImage = true, | |
| enableReferences = true, | |
| }, | |
| { signal } | |
| ) => { | |
| signal.addEventListener("abort", () => { | |
| throw new Error("The client closed unexpectedly!"); | |
| }); | |
| try { | |
| const deepResearch = initDeepResearchServer({ language, maxResult }); | |
| const result = await deepResearch.writeFinalReport( | |
| plan, | |
| tasks, | |
| enableCitationImage, | |
| enableReferences | |
| ); | |
| return { | |
| content: [{ type: "text", text: JSON.stringify(result) }], | |
| }; | |
| } catch (error) { | |
| return { | |
| isError: true, | |
| content: [ | |
| { | |
| type: "text", | |
| text: `Error: ${ | |
| error instanceof Error ? error.message : "Unknown error" | |
| }`, | |
| }, | |
| ], | |
| }; | |
| } | |
| } | |
| ); | |
| return server; | |
| } | |