Skip to main content
Glama
index.ts8.18 kB
#!/usr/bin/env node /** * Docfork MCP Server * * Main entry point supporting both stdio and HTTP transports. * Automatically detects OpenAI clients and routes to appropriate server. */ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { CallToolResult, GetPromptResult, ReadResourceResult, } from "@modelcontextprotocol/sdk/types.js"; import { searchDocs, readUrl } from "./api/index.js"; import { z } from "zod"; import { getServerConfig } from "./config.js"; import { startHttpServer, startStdioServer } from "./server.js"; import { getServer as getOpenAIServer } from "./openai.js"; /** * Create and configure the standard MCP server */ export const getServer = () => { const server = new McpServer({ name: "Docfork", version: "1.2.0", websiteUrl: "https://docfork.com", icons: [ { src: "https://docfork.com/icon.svg", mimeType: "image/svg+xml", }, ], }); // register docfork search docs tool server.registerTool( "docfork_search_docs", { title: "Search Documentation", description: "Search documentation across GitHub repositories or the web. For targeted searches INSIDE a specific library's documentation, use the docforkIdentifier parameter (author/repo format). Extract from GitHub URLs (e.g., github.com/facebook/react → 'facebook/react') and include in ALL subsequent searches about that library for focused, accurate results.", inputSchema: { query: z .string() .describe("Search query. Include language/framework names."), docforkIdentifier: z .string() .optional() .describe( "CRITICAL for targeted library searches: Library identifier in author/repo format (e.g., 'facebook/react', 'vercel/next.js'). Use this to search INSIDE a specific library's documentation for focused, accurate results. Extract from URLs in docfork_search_docs results and ALWAYS include in all subsequent searches about that library." ), tokens: z .string() .optional() .describe("Token budget: 'dynamic' or number (100-10000)"), }, }, async ({ query, tokens, docforkIdentifier }): Promise<CallToolResult> => { const response = await searchDocs( query as string, docforkIdentifier as string | undefined, tokens as string | undefined ); return { content: response.sections.map((section) => ({ type: "text" as const, text: `title: ${section.title}\nurl: ${section.url}`, })), }; } ); // register docfork read url tool server.registerTool( "docfork_read_url", { title: "Read Documentation URL", description: "Fetches and returns the full content of a documentation page as markdown. This is ESSENTIAL for getting complete, detailed information after searching. Always use this to read URLs from docfork_search_docs results to get the actual documentation content.", inputSchema: { url: z .string() .describe( "Full URL to read. Use exact URLs from docfork_search_docs results." ), }, }, async (args): Promise<CallToolResult> => { const inputValue = args.url as string; const response = await readUrl(inputValue); return { content: [ { type: "text" as const, text: response.text, }, ], }; } ); // search docs resource server.registerResource( "search_docs", "docfork://tools/search_docs", { title: "Search Documentation Tool", description: "Guide for using the docfork_search_docs tool", mimeType: "text/markdown", }, async (uri): Promise<ReadResourceResult> => { const toolExplanation = `# docfork_search_docs Search documentation from GitHub or the web. ## Parameters - **query** (required): Search query with language/framework names - **docforkIdentifier** (critical for targeted searches): Library in author/repo format (e.g., 'facebook/react'). Use this to search INSIDE a specific library's documentation. Extract from URLs in search results. - **tokens** (optional): 'dynamic' or number (100-10000) ## Example \`\`\` query: "React hooks useState" docforkIdentifier: "facebook/react" // For targeted search inside React docs \`\`\` Use \`docfork_read_url\` to read URLs from results.`; return { contents: [ { uri: uri.href, mimeType: "text/markdown", text: toolExplanation }, ], }; } ); // read url resource server.registerResource( "read_url", "docfork://tools/read_url", { title: "Read URL Tool", description: "Essential tool for reading full documentation content", mimeType: "text/markdown", }, async (uri): Promise<ReadResourceResult> => { const toolExplanation = `# docfork_read_url **Essential for getting complete documentation content.** Fetches the full markdown content of documentation pages. Use this after docfork_search_docs to get detailed information. ## Parameters - **url** (required): Full URL from docfork_search_docs results ## Example \`\`\` url: "https://react.dev/reference/react/useState" \`\`\``; return { contents: [ { uri: uri.href, mimeType: "text/markdown", text: toolExplanation }, ], }; } ); // search docs prompt server.registerPrompt( "search_docs", { title: "Search Documentation", description: "Search documentation from GitHub or the web", argsSchema: { query: z.string().describe("Search query"), docforkIdentifier: z .string() .optional() .describe( "Library in author/repo format for targeted searches INSIDE that library's documentation (e.g., 'facebook/react')" ), tokens: z .string() .optional() .describe("Token budget: 'dynamic' or number"), }, }, async ({ query, docforkIdentifier, tokens }): Promise<GetPromptResult> => { let promptText = `${query}\n\nUse the 'docfork_search_docs' tool to search for documentation.`; if (tokens || docforkIdentifier) { promptText += `\n\nSearch parameters:`; if (tokens) promptText += `\n- Token budget: ${tokens}`; if (docforkIdentifier) promptText += `\n- Library filter: ${docforkIdentifier}`; } return { messages: [ { role: "user" as const, content: { type: "text" as const, text: promptText, }, }, ], }; } ); // read url prompt server.registerPrompt( "read_url", { title: "Read Documentation URL", description: "Fetch full documentation content from a URL", argsSchema: { url: z.string().describe("Documentation URL to read"), }, }, async ({ url }): Promise<GetPromptResult> => { return { messages: [ { role: "user" as const, content: { type: "text" as const, text: `Use the 'docfork_read_url' tool to read the documentation at: ${url as string}`, }, }, ], }; } ); // Error handler server.server.onerror = (error: any) => { console.error("MCP Server error:", error); }; return server; }; /** * Main function */ async function main() { const config = getServerConfig(); if (config.transport === "stdio") { await startStdioServer(getServer); } else { // HTTP transport with client detection await startHttpServer(config.port, getServer, getOpenAIServer); } } // Handle graceful shutdown const shutdown = (signal: string) => { console.error(`Received ${signal}, shutting down gracefully...`); process.exit(0); }; process.on("SIGINT", () => shutdown("SIGINT")); process.on("SIGTERM", () => shutdown("SIGTERM")); main().catch((error) => { console.error("Fatal error running server:", error); process.exit(1); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/docfork/docfork-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server