2025-04-03 12:32:03 +03:00
# ! / u s r / b i n / e n v n o d e
2025-03-31 15:19:29 +03:00
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js" ;
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js" ;
import { z } from "zod" ;
2025-04-17 01:03:45 +03:00
import { searchLibraries , fetchLibraryDocumentation } from "./lib/api.js" ;
import { formatSearchResults } from "./lib/utils.js" ;
2025-05-01 20:27:03 +07:00
import dotenv from "dotenv" ;
2025-05-28 00:34:49 +03:00
import { createServer } from "http" ;
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js" ;
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js" ;
import { parse } from "url" ;
2025-04-17 01:03:45 +03:00
2025-05-01 20:27:03 +07:00
// Load environment variables from .env file if present
dotenv . config ( ) ;
// Get DEFAULT_MINIMUM_TOKENS from environment variable or use default
2025-05-01 22:50:40 +03:00
let DEFAULT_MINIMUM_TOKENS = 10000 ;
2025-05-01 20:27:03 +07:00
if ( process . env . DEFAULT_MINIMUM_TOKENS ) {
const parsedValue = parseInt ( process . env . DEFAULT_MINIMUM_TOKENS , 10 ) ;
if ( ! isNaN ( parsedValue ) && parsedValue > 0 ) {
DEFAULT_MINIMUM_TOKENS = parsedValue ;
} else {
2025-05-01 22:50:40 +03:00
console . warn (
` Warning: Invalid DEFAULT_MINIMUM_TOKENS value provided in environment variable. Using default value of 10000 `
2025-05-01 20:27:03 +07:00
) ;
}
}
2025-03-31 15:19:29 +03:00
2025-05-28 00:34:49 +03:00
// Store SSE transports by session ID
const sseTransports : Record < string , SSEServerTransport > = { } ;
// Function to create a new server instance with all tools registered
function createServerInstance() {
const server = new McpServer ( {
name : "Context7" ,
description : "Retrieves up-to-date documentation and code examples for any library." ,
2025-05-28 12:31:03 +03:00
version : "1.0.13" ,
2025-05-28 00:34:49 +03:00
capabilities : {
resources : { } ,
tools : { } ,
} ,
} ) ;
2025-03-31 15:19:29 +03:00
2025-05-28 00:34:49 +03:00
// Register Context7 tools
server . tool (
"resolve-library-id" ,
` Resolves a package/product name to a Context7-compatible library ID and returns a list of matching libraries.
2025-05-02 00:09:19 +03:00
2025-05-24 17:57:46 +03:00
You MUST call this function before 'get-library-docs' to obtain a valid Context7 - compatible library ID UNLESS the user explicitly provides a library ID in the format '/org/project' or '/org/project/version' in their query .
2025-05-02 00:09:19 +03:00
2025-05-17 02:49:46 +03:00
Selection Process :
1 . Analyze the query to understand what library / package the user is looking for
2 . Return the most relevant match based on :
- Name similarity to the query ( exact matches prioritized )
- Description relevance to the query ' s intent
- Documentation coverage ( prioritize libraries with higher Code Snippet counts )
- Trust score ( consider libraries with scores of 7 - 10 more authoritative )
Response Format :
- Return the selected library ID in a clearly marked section
- Provide a brief explanation for why this library was chosen
- If multiple good matches exist , acknowledge this but proceed with the most relevant one
- If no good matches exist , clearly state this and suggest query refinements
For ambiguous queries , request clarification before proceeding with a best - guess match . ` ,
2025-05-28 00:34:49 +03:00
{
libraryName : z
. string ( )
. describe ( "Library name to search for and retrieve a Context7-compatible library ID." ) ,
} ,
async ( { libraryName } ) = > {
const searchResponse = await searchLibraries ( libraryName ) ;
if ( ! searchResponse || ! searchResponse . results ) {
return {
content : [
{
type : "text" ,
text : "Failed to retrieve library documentation data from Context7" ,
} ,
] ,
} ;
}
if ( searchResponse . results . length === 0 ) {
return {
content : [
{
type : "text" ,
text : "No documentation libraries available" ,
} ,
] ,
} ;
}
const resultsText = formatSearchResults ( searchResponse ) ;
2025-03-31 15:19:29 +03:00
return {
content : [
{
type : "text" ,
2025-05-28 00:34:49 +03:00
text : ` Available Libraries (top matches):
2025-05-02 00:09:19 +03:00
Each result includes :
2025-05-24 17:57:46 +03:00
- Library ID : Context7 - compatible identifier ( format : / o r g / p r o j e c t )
2025-05-02 00:09:19 +03:00
- Name : Library or package name
- Description : Short summary
- Code Snippets : Number of available code examples
2025-05-17 02:49:46 +03:00
- Trust Score : Authority indicator
2025-05-22 11:41:03 -07:00
- Versions : List of versions if available . Use one of those versions if and only if the user explicitly provides a version in their query .
2025-05-02 00:09:19 +03:00
2025-05-17 02:49:46 +03:00
For best results , select libraries based on name match , trust score , snippet coverage , and relevance to your use case .
2025-05-02 00:09:19 +03:00
2025-05-17 02:49:46 +03:00
-- -- -- -- --
2025-05-02 00:09:19 +03:00
$ { resultsText } ` ,
2025-05-28 00:34:49 +03:00
} ,
] ,
} ;
}
) ;
server . tool (
"get-library-docs" ,
"Fetches up-to-date documentation for a library. You must call 'resolve-library-id' first to obtain the exact Context7-compatible library ID required to use this tool, UNLESS the user explicitly provides a library ID in the format '/org/project' or '/org/project/version' in their query." ,
{
context7CompatibleLibraryID : z
. string ( )
. describe (
"Exact Context7-compatible library ID (e.g., '/mongodb/docs', '/vercel/next.js', '/supabase/supabase', '/vercel/next.js/v14.3.0-canary.87') retrieved from 'resolve-library-id' or directly from user query in the format '/org/project' or '/org/project/version'."
) ,
topic : z
. string ( )
. optional ( )
. describe ( "Topic to focus documentation on (e.g., 'hooks', 'routing')." ) ,
tokens : z
. preprocess ( ( val ) = > ( typeof val === "string" ? Number ( val ) : val ) , z . number ( ) )
. transform ( ( val ) = > ( val < DEFAULT_MINIMUM_TOKENS ? DEFAULT_MINIMUM_TOKENS : val ) )
. optional ( )
. describe (
` Maximum number of tokens of documentation to retrieve (default: ${ DEFAULT_MINIMUM_TOKENS } ). Higher values provide more context but consume more tokens. `
) ,
} ,
async ( { context7CompatibleLibraryID , tokens = DEFAULT_MINIMUM_TOKENS , topic = "" } ) = > {
const documentationText = await fetchLibraryDocumentation ( context7CompatibleLibraryID , {
tokens ,
topic ,
} ) ;
if ( ! documentationText ) {
return {
content : [
{
type : "text" ,
text : "Documentation not found or not finalized for this library. This might have happened because you used an invalid Context7-compatible library ID. To get a valid Context7-compatible library ID, use the 'resolve-library-id' with the package name you wish to retrieve documentation for." ,
} ,
] ,
} ;
}
2025-03-31 15:19:29 +03:00
return {
content : [
{
type : "text" ,
2025-05-28 00:34:49 +03:00
text : documentationText ,
2025-03-31 15:19:29 +03:00
} ,
] ,
} ;
}
2025-05-28 00:34:49 +03:00
) ;
2025-04-03 11:51:17 +03:00
2025-05-28 00:34:49 +03:00
return server ;
}
2025-03-31 15:19:29 +03:00
async function main() {
2025-05-28 00:34:49 +03:00
const transportType = process . env . MCP_TRANSPORT || "stdio" ;
if ( transportType === "http" || transportType === "sse" ) {
const port = process . env . PORT ? parseInt ( process . env . PORT , 10 ) : 3000 ;
const httpServer = createServer ( async ( req , res ) = > {
const url = parse ( req . url || "" ) . pathname ;
// Set CORS headers for all responses
2025-05-28 12:31:03 +03:00
res . setHeader ( "Access-Control-Allow-Origin" , "*" ) ;
res . setHeader ( "Access-Control-Allow-Methods" , "GET,POST,OPTIONS,DELETE" ) ;
res . setHeader ( "Access-Control-Allow-Headers" , "Content-Type, MCP-Session-Id, mcp-session-id" ) ;
2025-05-28 00:34:49 +03:00
// Handle preflight OPTIONS requests
2025-05-28 12:31:03 +03:00
if ( req . method === "OPTIONS" ) {
2025-05-28 00:34:49 +03:00
res . writeHead ( 200 ) ;
res . end ( ) ;
return ;
}
try {
// Create new server instance for each request
const requestServer = createServerInstance ( ) ;
if ( url === "/mcp" ) {
const transport = new StreamableHTTPServerTransport ( {
2025-05-28 12:31:03 +03:00
sessionIdGenerator : undefined ,
2025-05-28 00:34:49 +03:00
} ) ;
await requestServer . connect ( transport ) ;
await transport . handleRequest ( req , res ) ;
} else if ( url === "/sse" && req . method === "GET" ) {
// Create new SSE transport for GET request
const sseTransport = new SSEServerTransport ( "/messages" , res ) ;
// Store the transport by session ID
sseTransports [ sseTransport . sessionId ] = sseTransport ;
// Clean up transport when connection closes
res . on ( "close" , ( ) = > {
delete sseTransports [ sseTransport . sessionId ] ;
} ) ;
await requestServer . connect ( sseTransport ) ;
} else if ( url === "/messages" && req . method === "POST" ) {
// Get session ID from query parameters
const parsedUrl = parse ( req . url || "" , true ) ;
const sessionId = parsedUrl . query . sessionId as string ;
2025-05-28 12:31:03 +03:00
2025-05-28 00:34:49 +03:00
if ( ! sessionId ) {
res . writeHead ( 400 ) ;
res . end ( "Missing sessionId parameter" ) ;
return ;
}
// Get existing transport for this session
const sseTransport = sseTransports [ sessionId ] ;
if ( ! sseTransport ) {
res . writeHead ( 400 ) ;
res . end ( ` No transport found for sessionId: ${ sessionId } ` ) ;
return ;
}
// Handle the POST message with the existing transport
await sseTransport . handlePostMessage ( req , res ) ;
} else if ( url === "/ping" ) {
2025-05-28 12:31:03 +03:00
res . writeHead ( 200 , { "Content-Type" : "text/plain" } ) ;
res . end ( "pong" ) ;
2025-05-28 00:34:49 +03:00
} else {
res . writeHead ( 404 ) ;
res . end ( "Not found" ) ;
}
} catch ( error ) {
console . error ( "Error handling request:" , error ) ;
if ( ! res . headersSent ) {
res . writeHead ( 500 ) ;
res . end ( "Internal Server Error" ) ;
}
}
} ) ;
httpServer . listen ( port , ( ) = > {
2025-05-28 12:31:03 +03:00
console . error (
` Context7 Documentation MCP Server running on ${ transportType . toUpperCase ( ) } at http://localhost: ${ port } /mcp and legacy SSE at /sse `
) ;
2025-05-28 00:34:49 +03:00
} ) ;
} else {
// Stdio transport - this is already stateless by nature
const server = createServerInstance ( ) ;
const transport = new StdioServerTransport ( ) ;
await server . connect ( transport ) ;
console . error ( "Context7 Documentation MCP Server running on stdio" ) ;
}
2025-03-31 15:19:29 +03:00
}
main ( ) . catch ( ( error ) = > {
console . error ( "Fatal error in main():" , error ) ;
process . exit ( 1 ) ;
} ) ;