├── .gitignore ├── public └── cover.png ├── src ├── transport │ ├── index.ts │ ├── stdio.ts │ └── http.ts ├── api │ ├── index.ts │ ├── read-docs.ts │ └── search-docs.ts ├── tools │ ├── index.ts │ ├── utils.ts │ ├── types.ts │ ├── read-docs.ts │ └── search-docs.ts ├── index.ts └── server │ ├── middleware.ts │ └── server.ts ├── glama.json ├── .dockerignore ├── smithery.yaml ├── tsconfig.json ├── server.json ├── Dockerfile ├── LICENSE ├── package.json ├── eslint.config.mjs ├── .github └── workflows │ └── npm-publish.yml └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | node_modules/ 3 | .DS_Store 4 | .mcpregistry_* 5 | key.pem -------------------------------------------------------------------------------- /public/cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docfork/mcp/HEAD/public/cover.png -------------------------------------------------------------------------------- /src/transport/index.ts: -------------------------------------------------------------------------------- 1 | // Export all transport handlers 2 | export * from "./stdio.js"; 3 | export * from "./http.js"; -------------------------------------------------------------------------------- /glama.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://glama.ai/mcp/schemas/server.json", 3 | "maintainers": ["antonrisch", "docfork-maintainer"] 4 | } 5 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .git 4 | .github 5 | *.md 6 | .gitignore 7 | .dockerignore 8 | *.log 9 | npm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* -------------------------------------------------------------------------------- /src/api/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * The base URL for the Docfork API 3 | */ 4 | export const BASE_URL = "https://api.docfork.com/v1"; 5 | 6 | export { searchDocs } from "./search-docs.js"; 7 | export { readDocs } from "./read-docs.js"; 8 | -------------------------------------------------------------------------------- /smithery.yaml: -------------------------------------------------------------------------------- 1 | # Smithery configuration file: https://smithery.ai/docs/config#smitheryyaml 2 | 3 | startCommand: 4 | type: http 5 | configSchema: 6 | # JSON Schema defining the configuration options for the MCP. 7 | type: object 8 | description: No configuration required 9 | exampleConfig: {} -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "module": "Node16", 5 | "moduleResolution": "Node16", 6 | "outDir": "./dist", 7 | "rootDir": "./src", 8 | "strict": true, 9 | "esModuleInterop": true, 10 | "skipLibCheck": true, 11 | "forceConsistentCasingInFileNames": true 12 | }, 13 | "include": ["src/**/*"], 14 | "exclude": ["node_modules"] 15 | } 16 | -------------------------------------------------------------------------------- /src/transport/stdio.ts: -------------------------------------------------------------------------------- 1 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; 2 | import { createServerInstance } from "../server/server.js"; 3 | import { ServerConfig } from "../server/middleware.js"; 4 | 5 | /** 6 | * Start the server with stdio transport 7 | */ 8 | export async function startStdioServer(config: ServerConfig): Promise { 9 | const server = createServerInstance(config); 10 | const transport = new StdioServerTransport(); 11 | await server.connect(transport); 12 | console.error("Docfork MCP Server running on stdio"); 13 | } 14 | -------------------------------------------------------------------------------- /src/tools/index.ts: -------------------------------------------------------------------------------- 1 | // Export individual tool configs and handlers 2 | export { createSearchToolConfig, searchDocsHandler } from "./search-docs.js"; 3 | export { createReadToolConfig, readDocsHandler } from "./read-docs.js"; 4 | import { ToolConfigNames } from "./types.js"; 5 | 6 | // OpenAI MCP client uses shorter, simpler names 7 | export const OPENAI_TOOL_CONFIG: ToolConfigNames = { 8 | searchToolName: "search", 9 | readToolName: "fetch", 10 | }; 11 | 12 | // Default MCP client uses descriptive, namespaced names 13 | export const DEFAULT_TOOL_CONFIG: ToolConfigNames = { 14 | searchToolName: "docfork_search_docs", 15 | readToolName: "docfork_read_url", 16 | }; 17 | -------------------------------------------------------------------------------- /src/api/read-docs.ts: -------------------------------------------------------------------------------- 1 | import { BASE_URL } from "./index.js"; 2 | import { ReadDocsResponse } from "../tools/types.js"; 3 | 4 | export async function readDocs(urlToRead: string): Promise { 5 | const url = new URL(`${BASE_URL}/read`); 6 | url.searchParams.set("url", urlToRead); 7 | 8 | const response = await fetch(url.toString(), { 9 | method: "GET", 10 | headers: { 11 | "User-Agent": "docfork-mcp", 12 | accept: "application/json", 13 | }, 14 | }); 15 | 16 | if (!response.ok) { 17 | const text = await response.text(); 18 | throw new Error( 19 | `${response.status} ${response.statusText}: ${text.slice(0, 500)}` 20 | ); 21 | } 22 | 23 | // Parse JSON response and return text field 24 | const data = await response.json(); 25 | return data; 26 | } 27 | -------------------------------------------------------------------------------- /server.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://static.modelcontextprotocol.io/schemas/2025-10-17/server.schema.json", 3 | "name": "com.docfork/docfork-mcp", 4 | "description": "Up-to-date documentation to 9,000+ libraries for devs and AI agents.", 5 | "icons": [ 6 | { 7 | "src": "https://mcp.docfork.com/icon.svg", 8 | "mimeType": "image/svg+xml", 9 | "sizes": ["48x48", "96x96", "any"] 10 | } 11 | ], 12 | "repository": { 13 | "url": "https://github.com/docfork/docfork-mcp", 14 | "source": "github" 15 | }, 16 | "version": "1.0.5", 17 | "remotes": [ 18 | { 19 | "type": "streamable-http", 20 | "url": "https://mcp.docfork.com/mcp" 21 | } 22 | ], 23 | "packages": [ 24 | { 25 | "registryType": "npm", 26 | "identifier": "docfork", 27 | "version": "1.0.0", 28 | "transport": { 29 | "type": "stdio" 30 | } 31 | } 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Generated by https://smithery.ai. See: https://smithery.ai/docs/config#dockerfile 2 | # ----- Build Stage ----- 3 | FROM node:lts-alpine AS builder 4 | WORKDIR /app 5 | 6 | # Copy package files and TypeScript config 7 | COPY package.json package-lock.json tsconfig.json ./ 8 | 9 | # Copy source code 10 | COPY src ./src 11 | 12 | # Install dependencies and build 13 | RUN npm ci && npm run build 14 | 15 | # ----- Production Stage ----- 16 | FROM node:lts-alpine 17 | WORKDIR /app 18 | 19 | # Copy built artifacts 20 | COPY --from=builder /app/dist ./dist 21 | 22 | # Copy package.json for production install 23 | COPY package.json package-lock.json ./ 24 | 25 | # Install only production dependencies 26 | RUN npm ci --only=production --ignore-scripts 27 | 28 | # Set default environment variables for HTTP mode 29 | ENV MCP_TRANSPORT=streamable-http 30 | ENV PORT=3000 31 | 32 | # Expose HTTP port 33 | EXPOSE 3000 34 | 35 | # Default command 36 | CMD ["node", "dist/index.js"] -------------------------------------------------------------------------------- /src/api/search-docs.ts: -------------------------------------------------------------------------------- 1 | import { BASE_URL } from "./index.js"; 2 | import { SearchDocsResponse } from "../tools/types.js"; 3 | 4 | export async function searchDocs( 5 | query: string, 6 | tokens?: string, 7 | libraryId?: string 8 | ): Promise { 9 | const url = new URL(`${BASE_URL}/search`); 10 | url.searchParams.set("query", query); 11 | if (tokens) { 12 | url.searchParams.set("tokens", tokens); 13 | } 14 | if (libraryId) { 15 | url.searchParams.set("libraryId", libraryId); 16 | } 17 | 18 | const response = await fetch(url.toString(), { 19 | method: "GET", 20 | headers: { 21 | "Content-Type": "application/json", 22 | "User-Agent": "docfork-mcp", 23 | }, 24 | }); 25 | 26 | if (!response.ok) { 27 | const text = await response.text(); 28 | throw new Error( 29 | `${response.status} ${response.statusText}: ${text.slice(0, 500)}` 30 | ); 31 | } 32 | 33 | const result = await response.json(); 34 | return result as SearchDocsResponse; 35 | } 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Docfork Corp. Pty Ltd 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Docfork MCP Server 5 | * 6 | * A Model Context Protocol server that provides documentation search capabilities. 7 | * Supports both stdio and HTTP transports with proper session management. 8 | * 9 | * Features: 10 | * - Search for documentation libraries by name or ID 11 | * - Search within specific library documentation for detailed content 12 | * - Modern Streamable HTTP transport with session management 13 | * - Flexible configuration through environment variables 14 | */ 15 | 16 | import { getServerConfig } from "./server/middleware.js"; 17 | import { startStdioServer } from "./transport/stdio.js"; 18 | import { startHttpServer } from "./transport/http.js"; 19 | 20 | async function main() { 21 | const config = getServerConfig(); 22 | 23 | // Determine transport type from config 24 | if (config.transport === "streamable-http") { 25 | await startHttpServer(config); 26 | } else { 27 | // Default to stdio transport for MCP client compatibility 28 | await startStdioServer(config); 29 | } 30 | } 31 | 32 | // Handle graceful shutdown 33 | process.on("SIGINT", async () => { 34 | console.error("Received SIGINT, shutting down gracefully..."); 35 | process.exit(0); 36 | }); 37 | 38 | process.on("SIGTERM", async () => { 39 | console.error("Received SIGTERM, shutting down gracefully..."); 40 | process.exit(0); 41 | }); 42 | 43 | main().catch((error) => { 44 | console.error("Fatal error running server:", error); 45 | process.exit(1); 46 | }); 47 | -------------------------------------------------------------------------------- /src/tools/utils.ts: -------------------------------------------------------------------------------- 1 | import { ToolErrorResponse, DeepResearchResult } from "./types.js"; 2 | 3 | /** 4 | * Check if OpenAI mode is enabled 5 | */ 6 | export function isOpenAIMode(): boolean { 7 | return process.env.DOCFORK_OPENAI_MODE === "1"; 8 | } 9 | 10 | /** 11 | * Validate OpenAI mode configuration 12 | */ 13 | export function validateOpenAIMode(): void { 14 | const mode = process.env.DOCFORK_OPENAI_MODE; 15 | if (mode && mode !== "1" && mode !== "0") { 16 | console.warn( 17 | `Invalid DOCFORK_OPENAI_MODE value: ${mode}. Expected "1" or "0".` 18 | ); 19 | } 20 | } 21 | 22 | /** 23 | * Create a standardized error response 24 | */ 25 | export function createErrorResponse( 26 | toolName: string, 27 | message: string 28 | ): ToolErrorResponse { 29 | return { 30 | content: [ 31 | { 32 | type: "text", 33 | text: `[${toolName} tool] ${message}`, 34 | }, 35 | ], 36 | isError: true, 37 | }; 38 | } 39 | 40 | /** 41 | * Create a parameter validation error 42 | */ 43 | export function createParameterError( 44 | toolName: string, 45 | paramName: string 46 | ): ToolErrorResponse { 47 | return createErrorResponse(toolName, `Error: '${paramName}' is required.`); 48 | } 49 | 50 | /** 51 | * Create Deep Research compatible response 52 | */ 53 | export function createDeepResearchResponse( 54 | data: DeepResearchResult | DeepResearchResult[] 55 | ): { 56 | content: Array<{ type: "text"; text: string }>; 57 | } { 58 | const payload = Array.isArray(data) ? { results: data } : data; 59 | return { 60 | content: [ 61 | { 62 | type: "text", 63 | text: JSON.stringify(payload), 64 | }, 65 | ], 66 | }; 67 | } 68 | -------------------------------------------------------------------------------- /src/server/middleware.ts: -------------------------------------------------------------------------------- 1 | import dotenv from "dotenv"; 2 | 3 | // Load environment variables from .env file if present 4 | // Completely suppress dotenv output to prevent stdout pollution in MCP stdio transport 5 | const originalWrite = process.stdout.write; 6 | process.stdout.write = () => true; // Temporarily suppress stdout 7 | try { 8 | dotenv.config({ quiet: true }); 9 | } finally { 10 | process.stdout.write = originalWrite; // Restore stdout 11 | } 12 | 13 | export interface ServerConfig { 14 | name: string; 15 | description: string; 16 | version: string; 17 | defaultMinimumTokens: number; 18 | port: number; 19 | transport: "stdio" | "streamable-http"; 20 | } 21 | 22 | export function getServerConfig(): ServerConfig { 23 | // Get DEFAULT_MINIMUM_TOKENS from environment variable or use default 24 | let defaultMinimumTokens = 10000; 25 | if (process.env.DEFAULT_MINIMUM_TOKENS) { 26 | const parsedValue = parseInt(process.env.DEFAULT_MINIMUM_TOKENS, 10); 27 | if (!isNaN(parsedValue) && parsedValue > 0) { 28 | defaultMinimumTokens = parsedValue; 29 | } else { 30 | console.warn( 31 | `Warning: Invalid DEFAULT_MINIMUM_TOKENS value provided in environment variable. Using default value of 10000` 32 | ); 33 | } 34 | } 35 | 36 | // Get initial port from environment or use default 37 | const port = process.env.PORT ? parseInt(process.env.PORT, 10) : 3000; 38 | 39 | const transport = (process.env.MCP_TRANSPORT || 40 | "stdio") as ServerConfig["transport"]; 41 | 42 | return { 43 | name: "Docfork", 44 | description: 45 | "Gets the latest documentation and code examples for any library.", 46 | version: "0.7.0", 47 | defaultMinimumTokens, 48 | port, 49 | transport, 50 | }; 51 | } 52 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "docfork", 3 | "version": "1.0.5", 4 | "description": "Up-to-date documentation to 9,000+ libraries for devs and AI agents.", 5 | "mcpName": "com.docfork/docfork-mcp", 6 | "type": "module", 7 | "main": "dist/index.js", 8 | "scripts": { 9 | "watch": "tsc --watch", 10 | "inspect": "npx @modelcontextprotocol/inspector node dist/index.js", 11 | "dev": "concurrently \"npm run watch\" \"npm run inspect\"", 12 | "build": "tsc && chmod 755 dist/index.js", 13 | "start": "MCP_TRANSPORT=streamable-http node dist/index.js", 14 | "lint": "eslint \"**/*.{js,ts,tsx}\" --fix", 15 | "format": "prettier --write \"**/*.{js,ts,tsx,json,md}\"", 16 | "test": "echo \"No tests specified\" && exit 0" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "git+https://github.com/docfork/docfork-mcp.git" 21 | }, 22 | "keywords": [ 23 | "modelcontextprotocol", 24 | "mcp", 25 | "docfork" 26 | ], 27 | "author": "Docfork", 28 | "license": "MIT", 29 | "bin": { 30 | "docfork": "dist/index.js" 31 | }, 32 | "files": [ 33 | "dist" 34 | ], 35 | "bugs": { 36 | "url": "https://github.com/docfork/docfork-mcp/issues" 37 | }, 38 | "homepage": "https://github.com/docfork/docfork-mcp#readme", 39 | "dependencies": { 40 | "@modelcontextprotocol/inspector": "^0.17.5", 41 | "@modelcontextprotocol/sdk": "^1.24.3", 42 | "dotenv": "^17.2.3", 43 | "escape-html": "^1.0.3", 44 | "zod": "^4.1.13" 45 | }, 46 | "devDependencies": { 47 | "@eslint/js": "^9.39.1", 48 | "@types/escape-html": "^1.0.4", 49 | "@types/node": "^25.0.1", 50 | "@typescript-eslint/eslint-plugin": "^8.49.0", 51 | "@typescript-eslint/parser": "^8.49.0", 52 | "concurrently": "^9.2.1", 53 | "eslint": "^9.39.1", 54 | "eslint-plugin-prettier": "^5.5.4", 55 | "globals": "^16.5.0", 56 | "prettier": "^3.7.4", 57 | "ts-node": "^10.9.2", 58 | "typescript": "^5.9.3", 59 | "typescript-eslint": "^8.49.0" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/tools/types.ts: -------------------------------------------------------------------------------- 1 | // Unified tool configuration for consistent naming 2 | export interface ToolConfigNames { 3 | searchToolName: string; 4 | readToolName: string; 5 | } 6 | /** 7 | * Section item in search results 8 | */ 9 | export interface SearchSection { 10 | url: string; 11 | title: string; 12 | content: string; 13 | } 14 | 15 | /** 16 | * Response from the searchDocs API 17 | */ 18 | export interface SearchDocsResponse { 19 | sections: SearchSection[]; 20 | truncated?: boolean; 21 | } 22 | 23 | /** 24 | * Response from the readDocs API 25 | */ 26 | export interface ReadDocsResponse { 27 | text: string; 28 | library_identifier: string; 29 | version_info: string; 30 | } 31 | 32 | /** 33 | * Tool configuration interface 34 | */ 35 | export interface ToolConfig { 36 | /** Tool name for registration */ 37 | name: string; 38 | /** Human-readable title for the tool */ 39 | title: string; 40 | /** Human-readable description of the tool */ 41 | description: string; 42 | /** Zod schema object defining the expected parameters for the tool */ 43 | inputSchema: Record; 44 | } 45 | 46 | /** 47 | * Tool handler function type - matches MCP server expectations 48 | */ 49 | export type ToolHandler = (args: { [x: string]: any }) => Promise<{ 50 | content: Array<{ 51 | type: "text"; 52 | text: string; 53 | }>; 54 | isError?: boolean; 55 | _meta?: Record; 56 | }>; 57 | 58 | /** 59 | * OpenAI Deep Research compatible result format 60 | * Used for both search results and document content 61 | */ 62 | export interface DeepResearchResult { 63 | id: string; 64 | title: string; 65 | text: string; 66 | url?: string; 67 | metadata?: { 68 | source?: string; 69 | fetched_at?: string; 70 | library_identifier?: string; 71 | version_info?: string; 72 | [key: string]: any; 73 | }; 74 | } 75 | 76 | /** 77 | * Common error response format 78 | */ 79 | export interface ToolErrorResponse { 80 | content: Array<{ 81 | type: "text"; 82 | text: string; 83 | }>; 84 | isError?: boolean; 85 | } 86 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import js from "@eslint/js"; 2 | import typescriptEslint from "@typescript-eslint/eslint-plugin"; 3 | import tsParser from "@typescript-eslint/parser"; 4 | import globals from "globals"; 5 | 6 | export default [ 7 | // Global ignores 8 | { 9 | ignores: [ 10 | "**/node_modules/", 11 | "**/dist/", 12 | "**/build/", 13 | "**/*.d.ts", 14 | "**/coverage/", 15 | "**/.nyc_output/", 16 | ], 17 | }, 18 | 19 | // Base ESLint recommended rules for all files 20 | js.configs.recommended, 21 | 22 | // TypeScript files configuration 23 | { 24 | files: ["**/*.ts", "**/*.tsx", "**/*.mts", "**/*.cts"], 25 | plugins: { 26 | "@typescript-eslint": typescriptEslint, 27 | }, 28 | languageOptions: { 29 | parser: tsParser, 30 | ecmaVersion: "latest", 31 | sourceType: "module", 32 | globals: { 33 | ...globals.node, 34 | ...globals.es2021, 35 | }, 36 | }, 37 | rules: { 38 | // Include TypeScript recommended rules 39 | ...typescriptEslint.configs.recommended.rules, 40 | 41 | // Custom TypeScript rules 42 | "@typescript-eslint/no-unused-vars": "error", 43 | "@typescript-eslint/no-explicit-any": "warn", 44 | "@typescript-eslint/explicit-function-return-type": "off", 45 | "@typescript-eslint/explicit-module-boundary-types": "off", 46 | "@typescript-eslint/no-inferrable-types": "off", 47 | 48 | // General JavaScript/TypeScript rules 49 | "prefer-const": "error", 50 | "no-var": "error", 51 | "no-console": "warn", 52 | "no-debugger": "error", 53 | }, 54 | }, 55 | 56 | // JavaScript files configuration (if any) 57 | { 58 | files: ["**/*.js", "**/*.mjs", "**/*.cjs"], 59 | languageOptions: { 60 | ecmaVersion: "latest", 61 | sourceType: "module", 62 | globals: { 63 | ...globals.node, 64 | ...globals.es2021, 65 | }, 66 | }, 67 | rules: { 68 | "prefer-const": "error", 69 | "no-var": "error", 70 | "no-console": "warn", 71 | "no-debugger": "error", 72 | }, 73 | }, 74 | ]; 75 | -------------------------------------------------------------------------------- /.github/workflows/npm-publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow will run tests using node and then publish a package to npm when a release is published 2 | # For more information see: https://docs.github.com/en/actions/publishing-packages/publishing-nodejs-packages 3 | 4 | name: Node.js Package 5 | 6 | on: 7 | release: 8 | types: [published] 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: actions/setup-node@v4 16 | with: 17 | node-version: 20 18 | - run: npm ci 19 | - run: npm run build 20 | - run: npm test 21 | 22 | publish-npm: 23 | needs: build 24 | runs-on: ubuntu-latest 25 | permissions: 26 | contents: read 27 | steps: 28 | - name: Checkout Repo 29 | uses: actions/checkout@v4 30 | 31 | - name: Set version from tag 32 | run: echo "VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV 33 | 34 | - name: Setup Node 35 | uses: actions/setup-node@v4 36 | with: 37 | node-version: 20 38 | registry-url: https://registry.npmjs.org/ 39 | 40 | - name: Install dependencies 41 | run: npm ci 42 | 43 | - name: Set package version 44 | run: | 45 | echo $(jq --arg v "${{ env.VERSION }}" '(.version) = $v' package.json) > package.json 46 | echo $(jq --arg v "${{ env.VERSION }}" '.version = $v | .packages[0].version = $v' server.json) > server.json 47 | 48 | - name: Update version in source file 49 | run: | 50 | sed -i "s/version: \"[0-9]*\.[0-9]*\.[0-9]*\"/version: \"${{ env.VERSION }}\"/" src/index.ts 51 | 52 | - name: Build 53 | run: npm run build 54 | - name: Publish to npm (stable) 55 | if: ${{ !github.event.release.prerelease }} 56 | run: npm publish --access public 57 | env: 58 | NODE_AUTH_TOKEN: ${{secrets.npm_token}} 59 | 60 | - name: Publish to npm (pre-release) 61 | if: ${{ github.event.release.prerelease }} 62 | run: npm publish --access public --tag=beta 63 | env: 64 | NODE_AUTH_TOKEN: ${{secrets.npm_token}} 65 | -------------------------------------------------------------------------------- /src/tools/read-docs.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | import { readDocs } from "../api/read-docs.js"; 3 | import { 4 | ToolConfig, 5 | ToolHandler, 6 | ToolConfigNames, 7 | DeepResearchResult, 8 | } from "./types.js"; 9 | import { createParameterError, createErrorResponse } from "./utils.js"; 10 | import { OPENAI_TOOL_CONFIG, DEFAULT_TOOL_CONFIG } from "./index.js"; 11 | 12 | // Function to get tool config based on client 13 | function getToolConfig(mcpClient: string = "unknown"): ToolConfigNames { 14 | return mcpClient === "openai-mcp" ? OPENAI_TOOL_CONFIG : DEFAULT_TOOL_CONFIG; 15 | } 16 | 17 | // Function to create read tool configuration 18 | export function createReadToolConfig( 19 | mcpClient: string = "unknown" 20 | ): ToolConfig { 21 | const config = getToolConfig(mcpClient); 22 | const isOpenAI = mcpClient === "openai-mcp"; 23 | 24 | return { 25 | name: config.readToolName, 26 | title: isOpenAI ? "Fetch Document" : "Read Documentation URL", 27 | description: isOpenAI 28 | ? "Retrieve complete document content by ID for detailed analysis and citation." 29 | : `Read the content of a documentation URL as markdown/text. Pass the entire exact URL from '${config.searchToolName}' results.`, 30 | inputSchema: { 31 | [isOpenAI ? "id" : "url"]: z 32 | .string() 33 | .describe( 34 | isOpenAI 35 | ? "URL or unique identifier for the document to fetch." 36 | : "The complete URL of the webpage to read. Use the exact URL from search results." 37 | ), 38 | }, 39 | }; 40 | } 41 | 42 | // read function 43 | export async function doRead(url: string, mcpClient: string = "unknown") { 44 | const config = getToolConfig(mcpClient); 45 | 46 | if (!url || typeof url !== "string") { 47 | const paramName = mcpClient === "openai-mcp" ? "id" : "url"; 48 | return createParameterError(config.readToolName, paramName); 49 | } 50 | 51 | try { 52 | const { text, library_identifier, version_info } = await readDocs(url); 53 | 54 | // Return different formats based on client type 55 | if (mcpClient === "openai-mcp") { 56 | const result: DeepResearchResult = { 57 | id: url, 58 | title: `Documentation from ${library_identifier} ${version_info}`, 59 | text, 60 | url, 61 | metadata: { 62 | source: "docfork", 63 | fetched_at: new Date().toISOString(), 64 | library_identifier, 65 | version_info, 66 | }, 67 | }; 68 | 69 | return { 70 | content: [ 71 | { 72 | type: "text" as const, 73 | text: JSON.stringify(result), 74 | }, 75 | ], 76 | }; 77 | } else { 78 | return { 79 | content: [{ type: "text" as const, text }], 80 | }; 81 | } 82 | } catch (error) { 83 | const msg = error instanceof Error ? error.message : String(error); 84 | return createErrorResponse(config.readToolName, msg); 85 | } 86 | } 87 | 88 | // Handler for backward compatibility 89 | export const readDocsHandler: ToolHandler = async (args: { 90 | url?: string; 91 | id?: string; 92 | }) => { 93 | const inputValue = args.id || args.url || ""; 94 | return doRead(inputValue, "unknown"); 95 | }; 96 | -------------------------------------------------------------------------------- /src/tools/search-docs.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | import { searchDocs } from "../api/search-docs.js"; 3 | import { 4 | ToolConfig, 5 | ToolHandler, 6 | SearchSection, 7 | ToolConfigNames, 8 | DeepResearchResult, 9 | } from "./types.js"; 10 | import { createParameterError, createErrorResponse } from "./utils.js"; 11 | import { DEFAULT_TOOL_CONFIG, OPENAI_TOOL_CONFIG } from "./index.js"; 12 | 13 | // Function to get tool config based on client 14 | function getToolConfig(mcpClient: string = "unknown"): ToolConfigNames { 15 | return mcpClient === "openai-mcp" ? OPENAI_TOOL_CONFIG : DEFAULT_TOOL_CONFIG; 16 | } 17 | 18 | // Function to create search tool configuration 19 | export function createSearchToolConfig( 20 | mcpClient: string = "unknown" 21 | ): ToolConfig { 22 | const config = getToolConfig(mcpClient); 23 | const isOpenAI = mcpClient === "openai-mcp"; 24 | 25 | return { 26 | name: config.searchToolName, 27 | title: "Search Documentation", 28 | description: isOpenAI 29 | ? "Search for documents using semantic search across web documentation and GitHub. Returns a list of relevant search results." 30 | : `Search for documentation from GitHub or the web. Include programming language and framework names for best results. If a library ID appears in chat inside a URL, use that library ID for future searches to filter results to that specific library. Use '${config.readToolName}' to read a URL.`, 31 | inputSchema: isOpenAI 32 | ? { 33 | query: z 34 | .string() 35 | .describe( 36 | "Search query string. Natural language queries work best. Include programming language and framework names for better results." 37 | ), 38 | } 39 | : { 40 | query: z 41 | .string() 42 | .describe( 43 | "Query for documentation. Include programming language and framework names for best results." 44 | ), 45 | tokens: z 46 | .string() 47 | .optional() 48 | .describe( 49 | "Token budget control: 'dynamic' for system-determined optimal size, or number (100-10000) for hard limit" 50 | ), 51 | libraryId: z 52 | .string() 53 | .optional() 54 | .describe( 55 | "Optional library ID to filter search results to a specific library" 56 | ), 57 | }, 58 | }; 59 | } 60 | 61 | // Transform section to Deep Research format 62 | function toDeepResearchResult(section: SearchSection): DeepResearchResult { 63 | return { 64 | id: section.url, 65 | title: section.title, 66 | text: section.content.slice(0, 100), 67 | url: section.url, 68 | }; 69 | } 70 | 71 | // search function 72 | export async function doSearch( 73 | query: string, 74 | tokens?: string, 75 | libraryId?: string, 76 | mcpClient: string = "unknown" 77 | ) { 78 | const config = getToolConfig(mcpClient); 79 | 80 | if (!query || typeof query !== "string" || query.trim() === "") { 81 | return createParameterError(config.searchToolName, "query"); 82 | } 83 | 84 | try { 85 | const data = await searchDocs(query, tokens, libraryId); 86 | 87 | if (typeof data === "string") { 88 | return { content: [{ type: "text" as const, text: data }] }; 89 | } 90 | 91 | if (!data || !data.sections || data.sections.length === 0) { 92 | return { 93 | content: [{ type: "text" as const, text: "No results found" }], 94 | }; 95 | } 96 | 97 | // Return different formats based on client type 98 | if (mcpClient === "openai-mcp") { 99 | return { 100 | content: [ 101 | { 102 | type: "text" as const, 103 | text: JSON.stringify({ 104 | results: data.sections.map(toDeepResearchResult), 105 | }), 106 | }, 107 | ], 108 | }; 109 | } else { 110 | return { 111 | content: data.sections.map((section: SearchSection) => ({ 112 | type: "text" as const, 113 | text: `title: ${section.title}\nurl: ${section.url}`, 114 | })), 115 | }; 116 | } 117 | } catch (error) { 118 | const msg = error instanceof Error ? error.message : String(error); 119 | return createErrorResponse(config.searchToolName, msg); 120 | } 121 | } 122 | 123 | // Handler for backward compatibility 124 | export const searchDocsHandler: ToolHandler = async (args: { 125 | query?: string; 126 | tokens?: string; 127 | libraryId?: string; 128 | }) => { 129 | if ( 130 | !args.query || 131 | typeof args.query !== "string" || 132 | args.query.trim() === "" 133 | ) { 134 | return createParameterError("search-docs", "query"); 135 | } 136 | return doSearch(args.query, args.tokens, args.libraryId, "unknown"); 137 | }; 138 | -------------------------------------------------------------------------------- /src/server/server.ts: -------------------------------------------------------------------------------- 1 | import { Server } from "@modelcontextprotocol/sdk/server/index.js"; 2 | import { 3 | ListToolsRequestSchema, 4 | CallToolRequestSchema, 5 | ListPromptsRequestSchema, 6 | GetPromptRequestSchema, 7 | ErrorCode, 8 | McpError, 9 | } from "@modelcontextprotocol/sdk/types.js"; 10 | import { ServerConfig } from "./middleware.js"; 11 | import { 12 | createSearchToolConfig, 13 | searchDocsHandler, 14 | createReadToolConfig, 15 | readDocsHandler, 16 | } from "../tools/index.js"; 17 | 18 | /** 19 | * Create a new MCP server instance 20 | */ 21 | export function createServerInstance(config: ServerConfig): Server { 22 | // Create tool configurations 23 | const searchDocsToolConfig = createSearchToolConfig(); 24 | const readDocsToolConfig = createReadToolConfig(); 25 | 26 | const server = new Server( 27 | { 28 | name: config.name, 29 | version: config.version, 30 | }, 31 | { 32 | capabilities: { 33 | tools: {}, 34 | prompts: { 35 | listChanged: true, 36 | }, 37 | }, 38 | } 39 | ); 40 | 41 | // Register request handlers 42 | server.setRequestHandler(ListToolsRequestSchema, async () => ({ 43 | tools: [ 44 | { 45 | name: searchDocsToolConfig.name, 46 | description: searchDocsToolConfig.description, 47 | inputSchema: { 48 | type: "object", 49 | properties: { 50 | query: { 51 | type: "string", 52 | description: searchDocsToolConfig.inputSchema.query.description, 53 | }, 54 | tokens: { 55 | type: "string", 56 | description: 57 | searchDocsToolConfig.inputSchema.tokens?.description || 58 | "Token budget control", 59 | }, 60 | libraryId: { 61 | type: "string", 62 | description: 63 | searchDocsToolConfig.inputSchema.libraryId?.description || 64 | "Optional library ID to filter search results to a specific library", 65 | }, 66 | }, 67 | required: ["query"], 68 | }, 69 | }, 70 | { 71 | name: readDocsToolConfig.name, 72 | description: readDocsToolConfig.description, 73 | inputSchema: { 74 | type: "object", 75 | properties: Object.keys(readDocsToolConfig.inputSchema).reduce( 76 | (acc, key) => { 77 | const schema = readDocsToolConfig.inputSchema[key]; 78 | acc[key] = { 79 | type: "string", 80 | description: schema.description, 81 | }; 82 | return acc; 83 | }, 84 | {} as any 85 | ), 86 | required: Object.keys(readDocsToolConfig.inputSchema), 87 | }, 88 | }, 89 | ], 90 | })); 91 | 92 | server.setRequestHandler(CallToolRequestSchema, async (request) => { 93 | const { name, arguments: args } = request.params; 94 | 95 | if (name === searchDocsToolConfig.name) { 96 | return await searchDocsHandler(args || {}); 97 | } 98 | 99 | if (name === readDocsToolConfig.name) { 100 | return await readDocsHandler(args || {}); 101 | } 102 | 103 | throw new Error(`Unknown tool: ${name}`); 104 | }); 105 | 106 | // Error handler 107 | server.onerror = (error: any) => { 108 | console.error("MCP Server error:", error); 109 | }; 110 | 111 | // Prompts 112 | server.setRequestHandler(ListPromptsRequestSchema, async () => ({ 113 | prompts: [ 114 | { 115 | name: "search_docs", 116 | description: 117 | "Search for up-to-date documentation and code examples for any library or framework. Gets the latest docs straight from the source.", 118 | arguments: [ 119 | { 120 | name: "query", 121 | description: 122 | "Your question or topic you want documentation for (e.g., 'Next.js routing', 'React hooks', 'Tailwind CSS setup')", 123 | required: true, 124 | }, 125 | ], 126 | }, 127 | { 128 | name: "read_url", 129 | description: 130 | "Read the content of a documentation URL as markdown/text. Pass URLs from 'search_docs'.", 131 | arguments: [ 132 | { 133 | name: "url", 134 | description: "The URL of the webpage to read.", 135 | required: true, 136 | }, 137 | ], 138 | }, 139 | ], 140 | })); 141 | 142 | server.setRequestHandler(GetPromptRequestSchema, async (request) => { 143 | const { name, arguments: args } = request.params as any; 144 | const query = args?.query as string | undefined; 145 | if (!query) { 146 | throw new McpError( 147 | ErrorCode.InvalidParams, 148 | "Missing required argument: query" 149 | ); 150 | } 151 | 152 | if (name === "search_docs") { 153 | return { 154 | messages: [ 155 | { 156 | role: "user" as const, 157 | content: { 158 | type: "text" as const, 159 | text: `${query}\n\nUse docfork to get the latest documentation and code examples.`, 160 | }, 161 | }, 162 | ], 163 | }; 164 | } 165 | 166 | throw new McpError(ErrorCode.InvalidParams, `Unknown prompt: ${name}`); 167 | }); 168 | 169 | return server; 170 | } 171 | -------------------------------------------------------------------------------- /src/transport/http.ts: -------------------------------------------------------------------------------- 1 | import { createServer } from "http"; 2 | import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; 3 | import { parse } from "url"; 4 | import { randomUUID } from "node:crypto"; 5 | import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js"; 6 | import { createServerInstance } from "../server/server.js"; 7 | import { ServerConfig } from "../server/middleware.js"; 8 | import { Server } from "@modelcontextprotocol/sdk/server/index.js"; 9 | import { IncomingMessage } from "node:http"; 10 | 11 | // Session management - persistent storage by session ID 12 | const transports: Record = {}; 13 | const servers: Record = {}; 14 | const sessionClientInfo: Record = 15 | {}; 16 | 17 | /** 18 | * Get information about active sessions (for debugging/monitoring) 19 | */ 20 | function getSessionInfo() { 21 | return { 22 | activeSessions: Object.keys(transports).length, 23 | sessionIds: Object.keys(transports), 24 | clientInfo: { ...sessionClientInfo }, 25 | }; 26 | } 27 | 28 | /** 29 | * Parse request body as JSON 30 | */ 31 | function parseRequestBody(req: IncomingMessage): Promise { 32 | return new Promise((resolve, reject) => { 33 | let body = ""; 34 | req.on("data", (chunk) => { 35 | body += chunk.toString(); 36 | }); 37 | req.on("end", () => { 38 | try { 39 | if (body) { 40 | resolve(JSON.parse(body)); 41 | } else { 42 | resolve({}); 43 | } 44 | } catch (error) { 45 | console.error("Error parsing request body:", error); 46 | reject(new Error("Invalid JSON in request body")); 47 | } 48 | }); 49 | req.on("error", (error) => { 50 | reject(error); 51 | }); 52 | }); 53 | } 54 | 55 | /** 56 | * Clean up a specific session by ID 57 | */ 58 | export function cleanupSession(sessionId: string): boolean { 59 | if (!transports[sessionId]) { 60 | return false; 61 | } 62 | 63 | const transport = transports[sessionId]; 64 | delete transports[sessionId]; 65 | delete servers[sessionId]; 66 | delete sessionClientInfo[sessionId]; 67 | 68 | // Close transport if it has a close method 69 | if (transport && typeof transport.close === "function") { 70 | transport.close(); 71 | } 72 | 73 | return true; 74 | } 75 | 76 | /** 77 | * Start the server with HTTP-based transports (streamable-http) 78 | */ 79 | export async function startHttpServer(config: ServerConfig): Promise { 80 | try { 81 | // Get initial port from config 82 | const initialPort = config.port; 83 | // Keep track of which port we end up using 84 | let actualPort = initialPort; 85 | 86 | const httpServer = createServer(async (req, res) => { 87 | const url = parse(req.url || "").pathname; 88 | 89 | // Set CORS headers for all responses 90 | res.setHeader("Access-Control-Allow-Origin", "*"); 91 | res.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,DELETE"); 92 | res.setHeader( 93 | "Access-Control-Allow-Headers", 94 | "Content-Type, MCP-Session-Id, mcp-session-id, MCP-Protocol-Version" 95 | ); 96 | res.setHeader("Access-Control-Expose-Headers", "MCP-Session-Id"); 97 | 98 | // Handle preflight OPTIONS requests 99 | if (req.method === "OPTIONS") { 100 | res.writeHead(200); 101 | res.end(); 102 | return; 103 | } 104 | 105 | try { 106 | if (url === "/mcp") { 107 | if (req.method === "POST") { 108 | // Parse request body for POST requests 109 | let requestBody = {}; 110 | try { 111 | requestBody = await parseRequestBody(req); 112 | } catch { 113 | res.writeHead(400, { "Content-Type": "application/json" }); 114 | res.end( 115 | JSON.stringify({ 116 | jsonrpc: "2.0", 117 | error: { 118 | code: -32700, 119 | message: "Parse error", 120 | }, 121 | id: null, 122 | }) 123 | ); 124 | return; 125 | } 126 | 127 | // Check for existing session ID 128 | const sessionId = req.headers["mcp-session-id"] as 129 | | string 130 | | undefined; 131 | let transport: StreamableHTTPServerTransport; 132 | 133 | if (sessionId && transports[sessionId]) { 134 | // Reuse existing transport and server 135 | transport = transports[sessionId]; 136 | } else if (!sessionId && isInitializeRequest(requestBody)) { 137 | // New initialization request - create new session 138 | try { 139 | const newSessionId = randomUUID(); 140 | 141 | transport = new StreamableHTTPServerTransport({ 142 | sessionIdGenerator: () => newSessionId, 143 | }); 144 | 145 | // Create new server instance 146 | const server = createServerInstance(config); 147 | 148 | // Store session data 149 | transports[newSessionId] = transport; 150 | servers[newSessionId] = server; 151 | sessionClientInfo[newSessionId] = { 152 | name: (requestBody as any)?.params?.clientInfo?.name, 153 | userAgent: req.headers["user-agent"], 154 | }; 155 | 156 | // Clean up session when transport closes 157 | transport.onclose = () => { 158 | delete transports[newSessionId]; 159 | delete servers[newSessionId]; 160 | delete sessionClientInfo[newSessionId]; 161 | }; 162 | 163 | // Connect server to transport 164 | await server.connect(transport); 165 | } catch (error) { 166 | console.error("Error initializing session:", error); 167 | res.writeHead(500, { "Content-Type": "application/json" }); 168 | res.end( 169 | JSON.stringify({ 170 | jsonrpc: "2.0", 171 | error: { 172 | code: -32000, 173 | message: `Failed to initialize server session: ${ 174 | error instanceof Error ? error.message : String(error) 175 | }`, 176 | }, 177 | id: null, 178 | }) 179 | ); 180 | return; 181 | } 182 | } else { 183 | // Invalid request - missing session ID or not an initialize request 184 | res.writeHead(400, { "Content-Type": "application/json" }); 185 | res.end( 186 | JSON.stringify({ 187 | jsonrpc: "2.0", 188 | error: { 189 | code: -32000, 190 | message: sessionId 191 | ? "Session not found" 192 | : "Bad Request: No valid session ID provided", 193 | }, 194 | id: null, 195 | }) 196 | ); 197 | return; 198 | } 199 | 200 | // Handle the request 201 | await transport.handleRequest(req, res, requestBody); 202 | } else if (req.method === "DELETE") { 203 | // Handle session termination 204 | const sessionId = req.headers["mcp-session-id"] as 205 | | string 206 | | undefined; 207 | if (!sessionId) { 208 | res.writeHead(400, { "Content-Type": "text/plain" }); 209 | res.end("Missing session ID"); 210 | return; 211 | } 212 | 213 | const success = cleanupSession(sessionId); 214 | if (!success) { 215 | res.writeHead(404, { "Content-Type": "text/plain" }); 216 | res.end("Session not found"); 217 | return; 218 | } 219 | 220 | res.writeHead(200, { "Content-Type": "text/plain" }); 221 | res.end("Session terminated"); 222 | } else { 223 | res.writeHead(405, { "Content-Type": "text/plain" }); 224 | res.end("Method not allowed"); 225 | } 226 | } else if (url === "/ping") { 227 | res.writeHead(200, { "Content-Type": "text/plain" }); 228 | res.end("pong"); 229 | } else if (url === "/sessions" && req.method === "GET") { 230 | // Return session information for monitoring 231 | res.writeHead(200, { "Content-Type": "application/json" }); 232 | res.end(JSON.stringify(getSessionInfo(), null, 2)); 233 | } else { 234 | res.writeHead(404); 235 | res.end("Not found"); 236 | } 237 | } catch (error) { 238 | console.error("Error handling request:", error); 239 | if (!res.headersSent) { 240 | res.writeHead(500); 241 | res.end("Internal Server Error"); 242 | } 243 | } 244 | }); 245 | 246 | // Function to find available port and start server 247 | const findAvailablePort = async ( 248 | startPort: number, 249 | maxAttempts = 10 250 | ): Promise => { 251 | for (let port = startPort; port < startPort + maxAttempts; port++) { 252 | try { 253 | await new Promise((resolve, reject) => { 254 | const testServer = createServer(); 255 | testServer.once("error", (err: Error & { code?: string }) => { 256 | if (err.code === "EADDRINUSE") { 257 | reject(err); 258 | } else { 259 | reject(err); 260 | } 261 | }); 262 | testServer.listen(port, () => { 263 | testServer.close(() => resolve()); 264 | }); 265 | }); 266 | return port; // Port is available 267 | } catch (err: any) { 268 | if (err.code === "EADDRINUSE") { 269 | console.warn(`Port ${port} is in use, trying port ${port + 1}...`); 270 | continue; 271 | } else { 272 | throw err; 273 | } 274 | } 275 | } 276 | throw new Error( 277 | `No available ports found in range ${startPort}-${startPort + maxAttempts - 1}` 278 | ); 279 | }; 280 | 281 | // Find available port first, then start the server once 282 | const availablePort = await findAvailablePort(initialPort); 283 | actualPort = availablePort; 284 | 285 | await new Promise((resolve, reject) => { 286 | httpServer.once("error", reject); 287 | httpServer.listen(availablePort, () => { 288 | console.error( 289 | `Docfork MCP Server running on ${config.transport.toUpperCase()}:` 290 | ); 291 | console.error(` • HTTP endpoint: http://localhost:${actualPort}/mcp`); 292 | console.error(` • Health check: http://localhost:${actualPort}/ping`); 293 | console.error( 294 | ` • Session info: http://localhost:${actualPort}/sessions` 295 | ); 296 | resolve(); 297 | }); 298 | }); 299 | } catch (error) { 300 | console.error("Failed to start HTTP server:", error); 301 | throw new Error( 302 | `Failed to start HTTP server: ${ 303 | error instanceof Error ? error.message : String(error) 304 | }` 305 | ); 306 | } 307 | } 308 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Cover](public/cover.png) 2 | 3 | # Docfork MCP - Up-to-date Docs for Devs and AI Agents. 4 | 5 | [![smithery badge](https://smithery.ai/badge/@docfork/mcp)](https://smithery.ai/server/@docfork/mcp) [![Website](https://img.shields.io/badge/Website-docfork.com-%23088DCC)](https://docfork.com) [![NPM Version](https://img.shields.io/npm/v/docfork?color=red)](https://www.npmjs.com/package/docfork) [![MIT licensed](https://img.shields.io/npm/l/docfork)](./LICENSE) 6 | 7 | [![Install MCP Server](https://cursor.com/deeplink/mcp-install-light.svg)](https://cursor.com/en/install-mcp?name=docfork&config=eyJ1cmwiOiJodHRwczovL21jcC5kb2Nmb3JrLmNvbS9tY3AifQ%3D%3D) [Install in VS Code (npx)](https://insiders.vscode.dev/redirect?url=vscode%3Amcp%2Finstall%3F%7B%22name%22%3A%22docfork%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22docfork%40latest%22%5D%7D) 8 | 9 | ## ❌ The Problem: Expired Knowledge 10 | 11 | - Out of date code examples & stale data from year-old model training 12 | - Hallucinated syntax & APIs 13 | - Old or mismatched versions 14 | 15 | ## ✅ The Solution: Up-to-date docs at warp speed 16 | 17 | - Always in sync with the latest version of docs 18 | - Accurate descriptions and code examples 19 | - Sub-second retrieval results (500ms @ p95) in your AI code editor 20 | 21 | Docfork MCP pulls @latest documentation and code examples straight from the source - and adds them right into your context. 22 | 23 | Just tell Cursor to **`use docfork`**: 24 | 25 | ```txt 26 | Create a basic Next.js app with the App Router. use docfork 27 | ``` 28 | 29 | ## 🛠️ Installation 30 | 31 | ### 📋 Requirements 32 | 33 | - Node.js ≥ v18 34 | - Cursor/Windsurf/Claude Desktop (any MCP client) 35 | 36 |
37 | Installing via Smithery 38 | 39 | ### Installing via Smithery 40 | 41 | To install Docfork MCP Server for any client automatically via [Smithery](https://smithery.ai/server/@docfork/mcp): 42 | 43 | ```bash 44 | npx -y @smithery/cli@latest install @docfork/mcp --client --key 45 | ``` 46 | 47 | You can find your Smithery key in the [Smithery.ai webpage](https://smithery.ai/server/@docfork/mcp). 48 | 49 |
50 | 51 |
52 | Install in Cursor 53 | 54 | Go to: `Settings` -> `Cursor Settings` -> `Tools & Integrations` -> `Add a custom MCP server` 55 | 56 | Pasting the following config into your Cursor `~/.cursor/mcp.json` file is the recommended approach. You can also install in a specific project by creating `.cursor/mcp.json` in your project folder. See [Cursor MCP docs](https://docs.cursor.com/context/model-context-protocol) for more info. 57 | 58 | #### Cursor Remote Server Connection 59 | 60 | [![Install MCP Server](https://cursor.com/deeplink/mcp-install-light.svg)](https://cursor.com/en/install-mcp?name=docfork&config=eyJ1cmwiOiJodHRwczovL21jcC5kb2Nmb3JrLmNvbS9tY3AifQ%3D%3D) 61 | 62 | ```json 63 | { 64 | "mcpServers": { 65 | "docfork": { 66 | "url": "https://mcp.docfork.com/mcp" 67 | } 68 | } 69 | } 70 | ``` 71 | 72 | #### Cursor Local Server Connection 73 | 74 | [![Install MCP Server](https://cursor.com/deeplink/mcp-install-light.svg)](https://cursor.com/en/install-mcp?name=docfork&config=eyJjb21tYW5kIjoibnB4IC15IGRvY2ZvcmsiLCJhcmdzIjpbIi15IiwiZG9jZm9yayJdfQ%3D%3D) 75 | 76 | ```json 77 | { 78 | "mcpServers": { 79 | "docfork": { 80 | "command": "npx", 81 | "args": ["-y", "docfork"] 82 | } 83 | } 84 | } 85 | ``` 86 | 87 |
88 | 89 |
90 | Alternative: Use Bun 91 | 92 | ```json 93 | { 94 | "mcpServers": { 95 | "docfork": { 96 | "command": "bunx", 97 | "args": ["-y", "docfork"] 98 | } 99 | } 100 | } 101 | ``` 102 | 103 |
104 | 105 |
106 | Alternative: Use Deno 107 | 108 | ```json 109 | { 110 | "mcpServers": { 111 | "docfork": { 112 | "command": "deno", 113 | "args": ["run", "--allow-env", "--allow-net", "npm:docfork"] 114 | } 115 | } 116 | } 117 | ``` 118 | 119 |
120 | 121 |
122 | Install in Claude Code 123 | 124 | ### Install in Claude Code 125 | 126 | Run this command. See [Claude Code MCP docs](https://docs.anthropic.com/en/docs/agents-and-tools/claude-code/tutorials#set-up-model-context-protocol-mcp) for more info. 127 | 128 | #### Claude Code Remote Server Connection 129 | 130 | ```sh 131 | claude mcp add --transport sse docfork https://mcp.docfork.com/sse 132 | ``` 133 | 134 | #### Claude Code Local Server Connection 135 | 136 | ```sh 137 | claude mcp add docfork -- npx -y docfork 138 | ``` 139 | 140 |
141 | 142 |
143 | Install in Claude Desktop 144 | 145 | ### Install in Claude Desktop 146 | 147 | Add this to your Claude Desktop `claude_desktop_config.json` file. See [Claude Desktop MCP docs](https://modelcontextprotocol.io/quickstart/user) for more info. 148 | 149 | ```json 150 | { 151 | "mcpServers": { 152 | "docfork": { 153 | "command": "npx", 154 | "args": ["-y", "docfork"] 155 | } 156 | } 157 | } 158 | ``` 159 | 160 |
161 | 162 |
163 | Install in Windsurf 164 | 165 | ### Install in Windsurf 166 | 167 | Add this to your Windsurf MCP config. See [Windsurf MCP docs](https://docs.windsurf.com/windsurf/mcp) for more info. 168 | 169 | #### Windsurf Remote Server Connection 170 | 171 | ```json 172 | { 173 | "mcpServers": { 174 | "docfork": { 175 | "serverUrl": "https://mcp.docfork.com/sse" 176 | } 177 | } 178 | } 179 | ``` 180 | 181 | #### Windsurf Local Server Connection 182 | 183 | ```json 184 | { 185 | "mcpServers": { 186 | "docfork": { 187 | "command": "npx", 188 | "args": ["-y", "docfork"] 189 | } 190 | } 191 | } 192 | ``` 193 | 194 |
195 | 196 |
197 | Install in VS Code 198 | 199 | ### Install in VS Code 200 | 201 | Add this to your VS Code MCP config. See [VS Code MCP docs](https://code.visualstudio.com/docs/copilot/chat/mcp-servers) for more info. 202 | 203 | #### VS Code Remote Server Connection 204 | 205 | ```json 206 | { 207 | "mcpServers": { 208 | "docfork": { 209 | "type": "http", 210 | "url": "https://mcp.docfork.com/mcp" 211 | } 212 | } 213 | } 214 | ``` 215 | 216 | #### VS Code Local Server Connection 217 | 218 | ```json 219 | { 220 | "servers": { 221 | "docfork": { 222 | "type": "stdio", 223 | "command": "npx", 224 | "args": ["-y", "docfork"] 225 | } 226 | } 227 | } 228 | ``` 229 | 230 |
231 | 232 |
233 | Install in Zed 234 | 235 | ### Install in Zed 236 | 237 | One-click install: 238 | → Get the [Docfork Extension](https://zed.dev/extensions?query=Docfork&filter=context-servers) 239 | 240 | Or Manual config (for power users): 241 | 242 | ```json 243 | { 244 | "context_servers": { 245 | "docfork": { 246 | "command": { 247 | "path": "npx", 248 | "args": ["-y", "docfork"] 249 | }, 250 | "settings": {} 251 | } 252 | } 253 | } 254 | ``` 255 | 256 |
257 | 258 |
259 | Install in BoltAI 260 | 261 | ### Install in BoltAI 262 | 263 | Open the "Settings" page of the app, navigate to "Plugins," and enter the following JSON: 264 | 265 | ```json 266 | { 267 | "mcpServers": { 268 | "docfork": { 269 | "command": "npx", 270 | "args": ["-y", "docfork"] 271 | } 272 | } 273 | } 274 | ``` 275 | 276 | More info is available on [BoltAI's Documentation site](https://docs.boltai.com/docs/plugins/mcp-servers). For BoltAI on iOS, [see this guide](https://docs.boltai.com/docs/boltai-mobile/mcp-servers). 277 | 278 |
279 | 280 |
281 | Using Docker 282 | 283 | ### Using Docker 284 | 285 | If you prefer to run the MCP server in a Docker container: 286 | 287 | 1. **Build the Docker Image:** 288 | 289 | First, create a `Dockerfile` in the project root (or anywhere you prefer): 290 | 291 |
292 | Click to see Dockerfile content 293 | 294 | ```Dockerfile 295 | FROM node:18-alpine 296 | 297 | WORKDIR /app 298 | 299 | # Install the latest version globally 300 | RUN npm install -g docfork 301 | 302 | # Expose default port if needed (optional, depends on MCP client interaction) 303 | # EXPOSE 3000 304 | 305 | # Default command to run the server 306 | CMD ["docfork"] 307 | ``` 308 | 309 |
310 | 311 | Then, build the image using a tag (e.g., `docfork-mcp`). **Make sure Docker Desktop (or the Docker daemon) is running.** Run the following command in the same directory where you saved the `Dockerfile`: 312 | 313 | ```bash 314 | docker build -t docfork . 315 | ``` 316 | 317 | 2. **Configure Your MCP Client:** 318 | 319 | Update your MCP client's configuration to use the Docker command. 320 | 321 | _Example for a cline_mcp_settings.json:_ 322 | 323 | ```json 324 | { 325 | "mcpServers": { 326 | "docfork": { 327 | "autoApprove": [], 328 | "disabled": false, 329 | "timeout": 60, 330 | "command": "docker", 331 | "args": ["run", "-i", "--rm", "docfork-mcp"], 332 | "transportType": "stdio" 333 | } 334 | } 335 | } 336 | ``` 337 | 338 | _Note: This is an example configuration. Please refer to the specific examples for your MCP client (like Cursor, VS Code, etc.) earlier in this README to adapt the structure (e.g., `mcpServers` vs `servers`). Also, ensure the image name in `args` matches the tag used during the `docker build` command._ 339 | 340 |
341 | 342 |
343 | Install in Windows 344 | 345 | ### Install in Windows 346 | 347 | The configuration on Windows is slightly different compared to Linux or macOS (_`Cline` is used in the example_). The same principle applies to other editors; refer to the configuration of `command` and `args`. 348 | 349 | ```json 350 | { 351 | "mcpServers": { 352 | "github.com/docfork/mcp": { 353 | "command": "cmd", 354 | "args": ["/c", "npx", "-y", "docfork@latest"], 355 | "disabled": false, 356 | "autoApprove": [] 357 | } 358 | } 359 | } 360 | ``` 361 | 362 |
363 | 364 |
365 | Install in Augment Code 366 | 367 | ### Install in Augment Code 368 | 369 | To configure Docfork MCP in Augment Code, follow these steps: 370 | 371 | 1. Press Cmd/Ctrl Shift P or go to the hamburger menu in the Augment panel 372 | 2. Select Edit Settings 373 | 3. Under Advanced, click Edit in settings.json 374 | 4. Add the server configuration to the `mcpServers` array in the `augment.advanced` object 375 | 376 | ```json 377 | "augment.advanced": { 378 | "mcpServers": [ 379 | { 380 | "name": "docfork", 381 | "command": "npx", 382 | "args": ["-y", "docfork"] 383 | } 384 | ] 385 | } 386 | ``` 387 | 388 | Once the MCP server is added, restart your editor. If you receive any errors, check the syntax to make sure closing brackets or commas are not missing. 389 | 390 |
391 | 392 |
393 | Install in Roo Code 394 | 395 | ### Install in Roo Code 396 | 397 | Add this to your Roo Code MCP configuration file. See [Roo Code MCP docs](https://docs.roocode.com/features/mcp/using-mcp-in-roo) for more info. 398 | 399 | #### Roo Code Remote Server Connection 400 | 401 | ```json 402 | { 403 | "mcpServers": { 404 | "docfork": { 405 | "type": "streamable-http", 406 | "url": "https://mcp.docfork.com/mcp" 407 | } 408 | } 409 | } 410 | ``` 411 | 412 | #### Roo Code Local Server Connection 413 | 414 | ```json 415 | { 416 | "mcpServers": { 417 | "docfork": { 418 | "command": "npx", 419 | "args": ["-y", "docfork"] 420 | } 421 | } 422 | } 423 | ``` 424 | 425 |
426 | 427 |
428 | Install in Trae 429 | 430 | Use the Add manually feature and fill in the JSON configuration information for that MCP server. 431 | For more details, visit the [Trae documentation](https://docs.trae.ai/ide/model-context-protocol?_lang=en). 432 | 433 | #### Trae Remote Server Connection 434 | 435 | ```json 436 | { 437 | "mcpServers": { 438 | "docfork": { 439 | "url": "https://mcp.docfork.com/mcp" 440 | } 441 | } 442 | } 443 | ``` 444 | 445 | #### Trae Local Server Connection 446 | 447 | ```json 448 | { 449 | "mcpServers": { 450 | "docfork": { 451 | "command": "npx", 452 | "args": ["-y", "docfork"] 453 | } 454 | } 455 | } 456 | ``` 457 | 458 |
459 | 460 |
461 | Install in Visual Studio 2022 462 | 463 | You can configure Docfork MCP in Visual Studio 2022 by following the [Visual Studio MCP Servers documentation](https://learn.microsoft.com/visualstudio/ide/mcp-servers?view=vs-2022). 464 | 465 | Add this to your Visual Studio MCP config file (see the [Visual Studio docs](https://learn.microsoft.com/visualstudio/ide/mcp-servers?view=vs-2022) for details): 466 | 467 | ```json 468 | { 469 | "mcp": { 470 | "servers": { 471 | "docfork": { 472 | "type": "http", 473 | "url": "https://mcp.docfork.com/mcp" 474 | } 475 | } 476 | } 477 | } 478 | ``` 479 | 480 | Or, for a local server: 481 | 482 | ```json 483 | { 484 | "mcp": { 485 | "servers": { 486 | "docfork": { 487 | "type": "stdio", 488 | "command": "npx", 489 | "args": ["-y", "docfork"] 490 | } 491 | } 492 | } 493 | } 494 | ``` 495 | 496 | For more information and troubleshooting, refer to the [Visual Studio MCP Servers documentation](https://learn.microsoft.com/visualstudio/ide/mcp-servers?view=vs-2022). 497 | 498 |
499 | 500 |
501 | Install in Gemini CLI 502 | 503 | See [Gemini CLI Configuration](https://github.com/google-gemini/gemini-cli/blob/main/docs/cli/configuration.md) for details. 504 | 505 | 1. Open the Gemini CLI settings file. The location is `~/.gemini/settings.json` (where `~` is your home directory). 506 | 2. Add the following to the `mcpServers` object in your `settings.json` file: 507 | 508 | ```json 509 | { 510 | "mcpServers": { 511 | "docfork": { 512 | "httpUrl": "https://mcp.docfork.com/mcp" 513 | } 514 | } 515 | } 516 | ``` 517 | 518 | Or, for a local server: 519 | 520 | ```json 521 | { 522 | "mcpServers": { 523 | "docfork": { 524 | "command": "npx", 525 | "args": ["-y", "docfork"] 526 | } 527 | } 528 | } 529 | ``` 530 | 531 | If the `mcpServers` object does not exist, create it. 532 | 533 |
534 | 535 |
536 | Install in Crush 537 | 538 | Add this to your Crush configuration file. See [Crush MCP docs](https://github.com/charmbracelet/crush#mcps) for more info. 539 | 540 | #### Crush Remote Server Connection (HTTP) 541 | 542 | ```json 543 | { 544 | "$schema": "https://charm.land/crush.json", 545 | "mcp": { 546 | "docfork": { 547 | "type": "http", 548 | "url": "https://mcp.docfork.com/mcp" 549 | } 550 | } 551 | } 552 | ``` 553 | 554 | #### Crush Remote Server Connection (SSE) 555 | 556 | ```json 557 | { 558 | "$schema": "https://charm.land/crush.json", 559 | "mcp": { 560 | "docfork": { 561 | "type": "sse", 562 | "url": "https://mcp.docfork.com/sse" 563 | } 564 | } 565 | } 566 | ``` 567 | 568 | #### Crush Local Server Connection 569 | 570 | ```json 571 | { 572 | "$schema": "https://charm.land/crush.json", 573 | "mcp": { 574 | "docfork": { 575 | "type": "stdio", 576 | "command": "npx", 577 | "args": ["-y", "docfork"] 578 | } 579 | } 580 | } 581 | ``` 582 | 583 |
584 | 585 |
586 | 587 | Install in Cline 588 | 589 | 590 | You can easily install Docfork through the [Cline MCP Server Marketplace](https://cline.bot/mcp-marketplace) by following these instructions: 591 | 592 | 1. Open **Cline**. 593 | 2. Click the hamburger menu icon (☰) to enter the **MCP Servers** section. 594 | 3. Use the search bar within the **Marketplace** tab to find _Docfork_. 595 | 4. Click the **Install** button. 596 | 597 |
598 | 599 |
600 | Install in Zencoder 601 | 602 | To configure Docfork MCP in Zencoder, follow these steps: 603 | 604 | 1. Go to the Zencoder menu (...) 605 | 2. From the dropdown menu, select Agent tools 606 | 3. Click on the Add custom MCP 607 | 4. Add the name and server configuration from below, and make sure to hit the Install button 608 | 609 | ```json 610 | { 611 | "command": "npx", 612 | "args": ["-y", "docfork@latest"] 613 | } 614 | ``` 615 | 616 | Once the MCP server is added, you can easily continue using it. 617 | 618 |
619 | 620 |
621 | Install in Amazon Q Developer CLI 622 | 623 | Add this to your Amazon Q Developer CLI configuration file. See [Amazon Q Developer CLI docs](https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/command-line-mcp-configuration.html) for more details. 624 | 625 | ```json 626 | { 627 | "mcpServers": { 628 | "docfork": { 629 | "command": "npx", 630 | "args": ["-y", "docfork@latest"] 631 | } 632 | } 633 | } 634 | ``` 635 | 636 |
637 | 638 |
639 | Install in Qodo Gen 640 | 641 | See [Qodo Gen docs](https://docs.qodo.ai/qodo-documentation/qodo-gen/qodo-gen-chat/agentic-mode/agentic-tools-mcps) for more details. 642 | 643 | 1. Open Qodo Gen chat panel in VSCode or IntelliJ. 644 | 2. Click Connect more tools. 645 | 3. Click + Add new MCP. 646 | 4. Add the following configuration: 647 | 648 | ```json 649 | { 650 | "mcpServers": { 651 | "docfork": { 652 | "url": "https://mcp.docfork.com/mcp" 653 | } 654 | } 655 | } 656 | ``` 657 | 658 |
659 | 660 |
661 | Install in JetBrains AI Assistant 662 | 663 | See [JetBrains AI Assistant Documentation](https://www.jetbrains.com/help/ai-assistant/configure-an-mcp-server.html) for more details. 664 | 665 | 1. In JetBrains IDEs go to `Settings` -> `Tools` -> `AI Assistant` -> `Model Context Protocol (MCP)` 666 | 2. Click `+ Add`. 667 | 3. Click on `Command` in the top-left corner of the dialog and select the As JSON option from the list 668 | 4. Add this configuration and click `OK` 669 | 670 | ```json 671 | { 672 | "mcpServers": { 673 | "docfork": { 674 | "command": "npx", 675 | "args": ["-y", "docfork"] 676 | } 677 | } 678 | } 679 | ``` 680 | 681 | 5. Click `Apply` to save changes. 682 | 6. The same way docfork could be added for JetBrains Junie in `Settings` -> `Tools` -> `Junie` -> `MCP Settings` 683 | 684 |
685 | 686 |
687 | Install in Warp 688 | 689 | See [Warp Model Context Protocol Documentation](https://docs.warp.dev/knowledge-and-collaboration/mcp#adding-an-mcp-server) for details. 690 | 691 | 1. Navigate `Settings` > `AI` > `Manage MCP servers`. 692 | 2. Add a new MCP server by clicking the `+ Add` button. 693 | 3. Paste the configuration given below: 694 | 695 | ```json 696 | { 697 | "Docfork": { 698 | "command": "npx", 699 | "args": ["-y", "docfork"], 700 | "env": {}, 701 | "working_directory": null, 702 | "start_on_launch": true 703 | } 704 | } 705 | ``` 706 | 707 | 4. Click `Save` to apply the changes. 708 | 709 |
710 | 711 |
712 | Install in Opencode 713 | 714 | Add this to your Opencode configuration file. See [Opencode MCP docs](https://opencode.ai/docs/mcp-servers) docs for more info. 715 | 716 | #### Opencode Remote Server Connection 717 | 718 | ```json 719 | "mcp": { 720 | "docfork": { 721 | "type": "remote", 722 | "url": "https://mcp.docfork.com/mcp", 723 | "enabled": true 724 | } 725 | } 726 | ``` 727 | 728 | #### Opencode Local Server Connection 729 | 730 | ```json 731 | { 732 | "mcp": { 733 | "docfork": { 734 | "type": "local", 735 | "command": ["npx", "-y", "docfork"], 736 | "enabled": true 737 | } 738 | } 739 | } 740 | ``` 741 | 742 |
743 | 744 |
745 | 746 | Install in Copilot Coding Agent 747 | 748 | ## Using Docfork with Copilot Coding Agent 749 | 750 | Add the following configuration to the `mcp` section of your Copilot Coding Agent configuration file Repository->Settings->Copilot->Coding agent->MCP configuration: 751 | 752 | ```json 753 | { 754 | "mcpServers": { 755 | "docfork": { 756 | "type": "http", 757 | "url": "https://mcp.docfork.com/mcp", 758 | "tools": ["get-library-docs"] 759 | } 760 | } 761 | } 762 | ``` 763 | 764 | For more information, see the [official GitHub documentation](https://docs.github.com/en/enterprise-cloud@latest/copilot/how-tos/agents/copilot-coding-agent/extending-copilot-coding-agent-with-mcp). 765 | 766 |
767 | 768 |
769 | 770 | Install in Kiro 771 | 772 | See [Kiro Model Context Protocol Documentation](https://kiro.dev/docs/mcp/configuration/) for details. 773 | 774 | 1. Navigate `Kiro` > `MCP Servers` 775 | 2. Add a new MCP server by clicking the `+ Add` button. 776 | 3. Paste the configuration given below: 777 | 778 | ```json 779 | { 780 | "mcpServers": { 781 | "Docfork": { 782 | "command": "npx", 783 | "args": ["-y", "docfork"], 784 | "env": {}, 785 | "disabled": false, 786 | "autoApprove": [] 787 | } 788 | } 789 | } 790 | ``` 791 | 792 | 4. Click `Save` to apply the changes. 793 | 794 |
795 | 796 |
797 | Install in OpenAI Codex 798 | 799 | See [OpenAI Codex](https://github.com/openai/codex) for more information. 800 | 801 | Add the following configuration to your OpenAI Codex MCP server settings: 802 | 803 | ```toml 804 | [mcp_servers.docfork] 805 | args = ["-y", "docfork"] 806 | command = "npx" 807 | ``` 808 | 809 |
810 | 811 |
812 | Install in LM Studio 813 | 814 | See [LM Studio MCP Support](https://lmstudio.ai/blog/lmstudio-v0.3.17) for more information. 815 | 816 | #### One-click install: 817 | 818 | [![Add MCP Server docfork to LM Studio](https://files.lmstudio.ai/deeplink/mcp-install-light.svg)](https://lmstudio.ai/install-mcp?name=docfork&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsImRvY2ZvcmsiXX0%3D) 819 | 820 | #### Manual set-up: 821 | 822 | 1. Navigate to `Program` (right side) > `Install` > `Edit mcp.json`. 823 | 2. Paste the configuration given below: 824 | 825 | ```json 826 | { 827 | "mcpServers": { 828 | "Docfork": { 829 | "command": "npx", 830 | "args": ["-y", "docfork"] 831 | } 832 | } 833 | } 834 | ``` 835 | 836 | 3. Click `Save` to apply the changes. 837 | 4. Toggle the MCP server on/off from the right hand side, under `Program`, or by clicking the plug icon at the bottom of the chat box. 838 | 839 |
840 | 841 |
842 | Install in Perplexity Desktop 843 | 844 | See [Local and Remote MCPs for Perplexity](https://www.perplexity.ai/help-center/en/articles/11502712-local-and-remote-mcps-for-perplexity) for more information. 845 | 846 | 1. Navigate `Perplexity` > `Settings` 847 | 2. Select `Connectors`. 848 | 3. Click `Add Connector`. 849 | 4. Select `Advanced`. 850 | 5. Enter Server Name: `Docfork` 851 | 6. Paste the following JSON in the text area: 852 | 853 | ```json 854 | { 855 | "args": ["-y", "docfork"], 856 | "command": "npx", 857 | "env": {} 858 | } 859 | ``` 860 | 861 | 7. Click `Save`. 862 |
863 | 864 | ## 🔨 Available Tools 865 | 866 | Docfork MCP provides different tools depending on the client type: 867 | 868 | ### MCP Clients (Cursor, Claude Code, Claude Desktop, VS Code, etc.) 869 | 870 | - `docfork_search_docs`: Search for documentation across the web, GitHub, and other sources. 871 | - `query` (required): Query for documentation. Include language/framework/library names. 872 | - `tokens` (optional): Token budget control for response size. 873 | 874 | - `docfork_read_docs`: Read the content of a documentation URL as markdown/text. 875 | - `url` (required): The URL of the webpage to read (typically from `docfork_search_docs` results). 876 | 877 | ### OpenAI ChatGPT Connectors 878 | 879 | For OpenAI ChatGPT integration, Docfork provides OpenAI MCP specification-compliant tools: 880 | 881 | - `search`: Search for documents using semantic search. Returns a list of relevant search results. 882 | - `query` (required): Search query string. Natural language queries work best for semantic search. 883 | 884 | - `fetch`: Retrieve complete document content by ID for detailed analysis and citation. 885 | - `id` (required): URL or unique identifier for the document to fetch. 886 | 887 | > **Note:** 888 | > The OpenAI tools (`search` and `fetch`) automatically format their responses for ChatGPT connectors and are compatible with deep research workflows. 889 | 890 | ## 💡 Tips 891 | 892 | ### Add a Rule 893 | 894 | If you don't want to add `use docfork` to every prompt, you can define a simple rule from your `Cursor Settings > Rules` section in Cursor (or the equivalent in your MCP client) to auto-invoke Docfork on any code question: 895 | 896 | ```markdown 897 | --- 898 | alwaysApply: true 899 | --- 900 | 901 | when the user requests code examples, setup or configuration steps, or library/API documentation 902 | use docfork. 903 | ``` 904 | 905 | From then on you'll get Docfork's docs in any related conversation without typing anything extra. You can add your use cases to the match part. 906 | 907 | ### Use Specific Library Names 908 | 909 | When you know exactly which library you want to use, be specific in your prompts. This helps Docfork find the right documentation faster and more accurately: 910 | 911 | ```txt 912 | implement basic authentication with supabase. use docfork 913 | ``` 914 | 915 | ```txt 916 | create a Next.js middleware for rate limiting. use docfork 917 | ``` 918 | 919 | ```txt 920 | configure Tailwind CSS with custom typography. use docfork 921 | ``` 922 | 923 | The more specific you are about the library and what you want to accomplish, the better documentation you'll receive. 924 | 925 | ## Development 926 | 927 | Clone the project and install dependencies: 928 | 929 | ```bash 930 | npm i 931 | ``` 932 | 933 | Build: 934 | 935 | ```bash 936 | npm run build 937 | ``` 938 | 939 |
940 | Environment Variables 941 | 942 | The Docfork MCP server supports the following environment variables: 943 | 944 | - `DEFAULT_MINIMUM_TOKENS`: Set the minimum token count for documentation retrieval (default: 10000) 945 | 946 | ### For HTTP/SSE Transport Only 947 | 948 | The following environment variables are only relevant when running the server as an HTTP/SSE service (not for standard `npx` usage): 949 | 950 | - `MCP_TRANSPORT`: Set the transport type for MCP communication (default: `stdio`, options: `streamable-http`, `stdio`, `sse`) 951 | - `PORT`: Set the port number for HTTP/SSE transport (default: `3000`, only used when MCP_TRANSPORT is `streamable-http` or `sse`) 952 | 953 |
954 | 955 |
956 | Example Configurations 957 | 958 | **Standard node server configuration (most common):** 959 | 960 | ```json 961 | { 962 | "mcpServers": { 963 | "docfork": { 964 | "command": "npx", 965 | "args": ["-y", "docfork@latest"], 966 | "env": { 967 | "DEFAULT_MINIMUM_TOKENS": "10000" 968 | } 969 | } 970 | } 971 | } 972 | ``` 973 | 974 | **HTTP/SSE server configuration (for custom deployments):** 975 | 976 | These environment variables are used when you're running your own instance of the Docfork server, not when connecting to remote servers. For remote server connections, use the URL-based configurations shown earlier in this README (e.g., `"url": "https://mcp.docfork.com/mcp"`). 977 | 978 | If you're self-hosting and want to run the server with HTTP/SSE transport: 979 | 980 | ```bash 981 | # Set environment variables and run 982 | MCP_TRANSPORT=streamable-http PORT=3000 npx -y docfork@latest 983 | ``` 984 | 985 |
986 | 987 |
988 | Local Configuration Example 989 | 990 | ```json 991 | { 992 | "mcpServers": { 993 | "docfork": { 994 | "command": "node", 995 | "args": ["/path/to/folder/docfork/dist/index.js"] 996 | } 997 | } 998 | } 999 | ``` 1000 | 1001 |
1002 | 1003 |
1004 | Testing with MCP Inspector 1005 | 1006 | ```bash 1007 | npm run inspect 1008 | ``` 1009 | 1010 |
1011 | 1012 | ## 🚨 Troubleshooting 1013 | 1014 |
1015 | Module Not Found Errors 1016 | 1017 | If you encounter `ERR_MODULE_NOT_FOUND`, try using `bunx` instead of `npx`: 1018 | 1019 | ```json 1020 | { 1021 | "mcpServers": { 1022 | "docfork": { 1023 | "command": "bunx", 1024 | "args": ["-y", "docfork"] 1025 | } 1026 | } 1027 | } 1028 | ``` 1029 | 1030 | This often resolves module resolution issues in environments where `npx` doesn't properly install or resolve packages. 1031 | 1032 |
1033 | 1034 |
1035 | ESM Resolution Issues 1036 | 1037 | For errors like `Error: Cannot find module 'uriTemplate.js'`, try the `--experimental-vm-modules` flag: 1038 | 1039 | ```json 1040 | { 1041 | "mcpServers": { 1042 | "docfork": { 1043 | "command": "npx", 1044 | "args": ["-y", "--node-options=--experimental-vm-modules", "docfork"] 1045 | } 1046 | } 1047 | } 1048 | ``` 1049 | 1050 |
1051 | 1052 |
1053 | TLS/Certificate Issues 1054 | 1055 | Use the `--experimental-fetch` flag to bypass TLS-related problems: 1056 | 1057 | ```json 1058 | { 1059 | "mcpServers": { 1060 | "docfork": { 1061 | "command": "npx", 1062 | "args": ["-y", "--node-options=--experimental-fetch", "docfork"] 1063 | } 1064 | } 1065 | } 1066 | ``` 1067 | 1068 |
1069 | 1070 |
1071 | General MCP Client Errors 1072 | 1073 | 1. Try adding `@latest` to the package name 1074 | 2. Use `bunx` as an alternative to `npx` 1075 | 3. Consider using `deno` as another alternative 1076 | 4. Ensure you're using Node.js v18 or higher for native fetch support 1077 | 1078 |
1079 | 1080 | ## ⚠️ Disclaimer 1081 | 1082 | Docfork is an open, community-driven catalogue. Although we review submissions, we make no warranties—express or implied—about the accuracy, completeness, or security of any linked documentation or code. Projects listed here are created and maintained by their respective authors, not by Docfork. 1083 | 1084 | If you spot content that is suspicious, inappropriate, or potentially harmful, please contact us. 1085 | 1086 | By using Docfork, you agree to do so at your own discretion and risk. 1087 | 1088 | ## 🌟 Let's Connect! 1089 | 1090 | Stay in the loop and meet the community: 1091 | 1092 | - 🐦 Follow us on [X](https://x.com/docfork_ai) for product news and updates 1093 | - 🌐 Visit our [Website](https://docfork.com) 1094 | 1095 | ## Star History 1096 | 1097 | [![Star History Chart](https://api.star-history.com/svg?repos=docfork/mcp&type=Date)](https://www.star-history.com/#docfork/mcp&Date) 1098 | 1099 | ## License 1100 | 1101 | MIT --------------------------------------------------------------------------------