mirror of
https://github.com/upstash/context7.git
synced 2025-06-26 23:50:04 +00:00
Merge pull request #208 from upstash/sse-http-support
This commit is contained in:
commit
70b7b49d5f
82
README.md
82
README.md
@ -2,8 +2,7 @@
|
||||
|
||||
[](https://context7.com) [](https://smithery.ai/server/@upstash/context7-mcp) [<img alt="Install in VS Code (npx)" src="https://img.shields.io/badge/VS_Code-VS_Code?style=flat-square&label=Install%20Context7%20MCP&color=0098FF">](https://insiders.vscode.dev/redirect?url=vscode%3Amcp%2Finstall%3F%7B%22name%22%3A%22context7%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40upstash%2Fcontext7-mcp%40latest%22%5D%7D)
|
||||
|
||||
[](./docs/README.zh-CN.md) [](./docs/README.ko.md) [](./docs/README.es.md) [](./docs/README.fr.md) [-purple)](./docs/README.pt-BR.md) [](./docs/README.it.md) [](./docs/README.id-ID.md) [](./docs/README.de.md) [](./docs/README.ru.md) [](./docs/README.tr.md) [](./docs/README.ar.md)
|
||||
|
||||
[](./docs/README.zh-CN.md) [](./docs/README.ko.md) [](./docs/README.es.md) [](./docs/README.fr.md) [-purple)](./docs/README.pt-BR.md) [](./docs/README.it.md) [](./docs/README.id-ID.md) [](./docs/README.de.md) [](./docs/README.ru.md) [](./docs/README.tr.md) [](./docs/README.ar.md)
|
||||
|
||||
## ❌ Without Context7
|
||||
|
||||
@ -44,18 +43,34 @@ No tab-switching, no hallucinated APIs that don't exist, no outdated code genera
|
||||
|
||||
### Installing via Smithery
|
||||
|
||||
To install Context7 MCP Server for Claude Desktop automatically via [Smithery](https://smithery.ai/server/@upstash/context7-mcp):
|
||||
To install Context7 MCP Server for any client automatically via [Smithery](https://smithery.ai/server/@upstash/context7-mcp):
|
||||
|
||||
```bash
|
||||
npx -y @smithery/cli install @upstash/context7-mcp --client claude
|
||||
npx -y @smithery/cli@latest install @upstash/context7-mcp --client <CLIENT_NAME> --key <YOUR_SMITHERY_KEY>
|
||||
```
|
||||
|
||||
You can find your Smithery key in the [Smithery.ai webpage](https://smithery.ai/server/@upstash/context7-mcp).
|
||||
|
||||
### Install in Cursor
|
||||
|
||||
Go to: `Settings` -> `Cursor Settings` -> `MCP` -> `Add new global MCP server`
|
||||
|
||||
Pasting the following configuration into your Cursor `~/.cursor/mcp.json` file is the recommended approach. You may 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.
|
||||
|
||||
#### Cursor Remote Server Connection
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"context7": {
|
||||
"url": "https://mcp.context7.com/mcp"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Cursor Local Server Connection
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
@ -103,6 +118,20 @@ Pasting the following configuration into your Cursor `~/.cursor/mcp.json` file i
|
||||
|
||||
Add this to your Windsurf MCP config file. See [Windsurf MCP docs](https://docs.windsurf.com/windsurf/mcp) for more info.
|
||||
|
||||
#### Windsurf Remote Server Connection
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"context7": {
|
||||
"serverUrl": "https://mcp.context7.com/sse"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Windsurf Local Server Connection
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
@ -195,7 +224,7 @@ Once saved, enter in the chat `get-library-docs` followed by your Context7 docum
|
||||
|
||||
If you prefer to run the MCP server in a Docker container:
|
||||
|
||||
1. **Build the Docker Image:**
|
||||
1. **Build the Docker Image:**
|
||||
|
||||
First, create a `Dockerfile` in the project root (or anywhere you prefer):
|
||||
|
||||
@ -249,7 +278,9 @@ If you prefer to run the MCP server in a Docker container:
|
||||
*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.*
|
||||
|
||||
### Install in Windows
|
||||
|
||||
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`.
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
@ -272,7 +303,7 @@ The configuration on Windows is slightly different compared to Linux or macOS (*
|
||||
|
||||
- `DEFAULT_MINIMUM_TOKENS`: Set the minimum token count for documentation retrieval (default: 10000).
|
||||
|
||||
Examples:
|
||||
Example configuration with environment variables:
|
||||
|
||||
```json
|
||||
{
|
||||
@ -281,7 +312,7 @@ Examples:
|
||||
"command": "npx",
|
||||
"args": ["-y", "@upstash/context7-mcp"],
|
||||
"env": {
|
||||
"DEFAULT_MINIMUM_TOKENS": "10000"
|
||||
"DEFAULT_MINIMUM_TOKENS": "6000"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -290,10 +321,13 @@ Examples:
|
||||
|
||||
### Available Tools
|
||||
|
||||
Context7 MCP provides the following tools that LLMs can use:
|
||||
|
||||
- `resolve-library-id`: Resolves a general library name into a Context7-compatible library ID.
|
||||
- `libraryName` (required)
|
||||
- `libraryName` (required): The name of the library to search for
|
||||
|
||||
- `get-library-docs`: Fetches documentation for a library using a Context7-compatible library ID.
|
||||
- `context7CompatibleLibraryID` (required)
|
||||
- `context7CompatibleLibraryID` (required): Exact Context7-compatible library ID (e.g., `/mongodb/docs`, `/vercel/next.js`)
|
||||
- `topic` (optional): Focus the docs on a specific topic (e.g., "routing", "hooks")
|
||||
- `tokens` (optional, default 10000): Max number of tokens to return. Values less than the configured `DEFAULT_MINIMUM_TOKENS` value or the default value of 10000 are automatically increased to that value.
|
||||
|
||||
@ -332,9 +366,9 @@ npx -y @modelcontextprotocol/inspector npx @upstash/context7-mcp
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### ERR_MODULE_NOT_FOUND
|
||||
### Module Not Found Errors
|
||||
|
||||
If you see this error, try using `bunx` instead of `npx`.
|
||||
If you encounter `ERR_MODULE_NOT_FOUND`, try using `bunx` instead of `npx`:
|
||||
|
||||
```json
|
||||
{
|
||||
@ -347,11 +381,11 @@ If you see this error, try using `bunx` instead of `npx`.
|
||||
}
|
||||
```
|
||||
|
||||
This often resolves module resolution issues, especially in environments where `npx` does not properly install or resolve packages.
|
||||
This often resolves module resolution issues in environments where `npx` doesn't properly install or resolve packages.
|
||||
|
||||
### ESM Resolution Issues
|
||||
|
||||
If you encounter an error like: `Error: Cannot find module 'uriTemplate.js'` try running with the `--experimental-vm-modules` flag:
|
||||
For errors like `Error: Cannot find module 'uriTemplate.js'`, try the `--experimental-vm-modules` flag:
|
||||
|
||||
```json
|
||||
{
|
||||
@ -370,7 +404,7 @@ If you encounter an error like: `Error: Cannot find module 'uriTemplate.js'` try
|
||||
|
||||
### TLS/Certificate Issues
|
||||
|
||||
Use the `--experimental-fetch` flag with `npx` to bypass TLS-related issues:
|
||||
Use the `--experimental-fetch` flag to bypass TLS-related problems:
|
||||
|
||||
```json
|
||||
{
|
||||
@ -387,15 +421,12 @@ Use the `--experimental-fetch` flag with `npx` to bypass TLS-related issues:
|
||||
}
|
||||
```
|
||||
|
||||
### MCP Client Errors
|
||||
### General MCP Client Errors
|
||||
|
||||
1. Try adding `@latest` to the package name.
|
||||
|
||||
2. Try using `bunx` as an alternative.
|
||||
|
||||
3. Try using `deno` as an alternative.
|
||||
|
||||
4. Make sure you are using Node v18 or higher to have native fetch support with `npx`.
|
||||
1. Try adding `@latest` to the package name
|
||||
2. Use `bunx` as an alternative to `npx`
|
||||
3. Consider using `deno` as another alternative
|
||||
4. Ensure you're using Node.js v18 or higher for native fetch support
|
||||
|
||||
## Disclaimer
|
||||
|
||||
@ -404,18 +435,19 @@ Context7 projects are community-contributed and while we strive to maintain high
|
||||
## Connect with Us
|
||||
|
||||
Stay updated and join our community:
|
||||
|
||||
- 📢 Follow us on [X](https://x.com/contextai) for the latest news and updates
|
||||
- 🌐 Visit our [Website](https://context7.com)
|
||||
- 💬 Join our [Discord Community](https://upstash.com/discord) (if applicable)
|
||||
- 💬 Join our [Discord Community](https://upstash.com/discord)
|
||||
|
||||
## Context7 In Media
|
||||
|
||||
- [Better Stack: "Free Tool Makes Cursor 10x Smarter"](https://youtu.be/52FC3qObp9E)
|
||||
- [Cole Medin: "This is Hands Down the BEST MCP Server for AI Coding Assistants"](https://www.youtube.com/watch?v=G7gK8H6u7Rs)
|
||||
- [Income stream surfers: "Context7 + SequentialThinking MCPs: Is This AGI?"](https://www.youtube.com/watch?v=-ggvzyLpK6o)
|
||||
- [Income Stream Surfers: "Context7 + SequentialThinking MCPs: Is This AGI?"](https://www.youtube.com/watch?v=-ggvzyLpK6o)
|
||||
- [Julian Goldie SEO: "Context7: New MCP AI Agent Update"](https://www.youtube.com/watch?v=CTZm6fBYisc)
|
||||
- [JeredBlu: "Context 7 MCP: Get Documentation Instantly + VS Code Setup"](https://www.youtube.com/watch?v=-ls0D-rtET4)
|
||||
- [Income stream surfers: "Context7: The New MCP Server That Will CHANGE AI Coding"](https://www.youtube.com/watch?v=PS-2Azb-C3M)
|
||||
- [Income Stream Surfers: "Context7: The New MCP Server That Will CHANGE AI Coding"](https://www.youtube.com/watch?v=PS-2Azb-C3M)
|
||||
- [AICodeKing: "Context7 + Cline & RooCode: This MCP Server Makes CLINE 100X MORE EFFECTIVE!"](https://www.youtube.com/watch?v=qZfENAPMnyo)
|
||||
- [Sean Kochel: "5 MCP Servers For Vibe Coding Glory (Just Plug-In & Go)"](https://www.youtube.com/watch?v=LqTQi8qexJM)
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@upstash/context7-mcp",
|
||||
"version": "1.0.6",
|
||||
"version": "1.0.0",
|
||||
"description": "MCP server for Context7",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
@ -31,7 +31,7 @@
|
||||
},
|
||||
"homepage": "https://github.com/upstash/context7#readme",
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.8.0",
|
||||
"@modelcontextprotocol/sdk": "^1.12.0",
|
||||
"dotenv": "^16.5.0",
|
||||
"zod": "^3.24.2"
|
||||
},
|
||||
|
312
src/index.ts
312
src/index.ts
@ -6,6 +6,10 @@ import { z } from "zod";
|
||||
import { searchLibraries, fetchLibraryDocumentation } from "./lib/api.js";
|
||||
import { formatSearchResults } from "./lib/utils.js";
|
||||
import dotenv from "dotenv";
|
||||
import { createServer } from "http";
|
||||
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
||||
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
|
||||
import { parse } from "url";
|
||||
|
||||
// Load environment variables from .env file if present
|
||||
dotenv.config();
|
||||
@ -23,21 +27,25 @@ if (process.env.DEFAULT_MINIMUM_TOKENS) {
|
||||
}
|
||||
}
|
||||
|
||||
// Create server instance
|
||||
const server = new McpServer({
|
||||
name: "Context7",
|
||||
description: "Retrieves up-to-date documentation and code examples for any library.",
|
||||
version: "1.0.6",
|
||||
capabilities: {
|
||||
resources: {},
|
||||
tools: {},
|
||||
},
|
||||
});
|
||||
// Store SSE transports by session ID
|
||||
const sseTransports: Record<string, SSEServerTransport> = {};
|
||||
|
||||
// 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.
|
||||
// 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.",
|
||||
version: "1.0.13",
|
||||
capabilities: {
|
||||
resources: {},
|
||||
tools: {},
|
||||
},
|
||||
});
|
||||
|
||||
// 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.
|
||||
|
||||
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.
|
||||
|
||||
@ -56,43 +64,43 @@ Response Format:
|
||||
- If no good matches exist, clearly state this and suggest query refinements
|
||||
|
||||
For ambiguous queries, request clarification before proceeding with a best-guess match.`,
|
||||
{
|
||||
libraryName: z
|
||||
.string()
|
||||
.describe("Library name to search for and retrieve a Context7-compatible library ID."),
|
||||
},
|
||||
async ({ libraryName }) => {
|
||||
const searchResponse = await searchLibraries(libraryName);
|
||||
{
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Available Libraries (top matches):
|
||||
text: `Available Libraries (top matches):
|
||||
|
||||
Each result includes:
|
||||
- Library ID: Context7-compatible identifier (format: /org/project)
|
||||
@ -107,65 +115,173 @@ For best results, select libraries based on name match, trust score, snippet cov
|
||||
----------
|
||||
|
||||
${resultsText}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
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.",
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: documentationText,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
);
|
||||
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.",
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: documentationText,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
return server;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const transport = new StdioServerTransport();
|
||||
await server.connect(transport);
|
||||
console.error("Context7 Documentation MCP Server running on stdio");
|
||||
const transportType = process.env.MCP_TRANSPORT || "stdio";
|
||||
|
||||
if (transportType === "http" || transportType === "sse") {
|
||||
// Get initial port from environment or use default
|
||||
const initialPort = process.env.PORT ? parseInt(process.env.PORT, 10) : 3000;
|
||||
// Keep track of which port we end up using
|
||||
let actualPort = initialPort;
|
||||
const httpServer = createServer(async (req, res) => {
|
||||
const url = parse(req.url || "").pathname;
|
||||
|
||||
// Set CORS headers for all responses
|
||||
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");
|
||||
|
||||
// Handle preflight OPTIONS requests
|
||||
if (req.method === "OPTIONS") {
|
||||
res.writeHead(200);
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Create new server instance for each request
|
||||
const requestServer = createServerInstance();
|
||||
|
||||
if (url === "/mcp") {
|
||||
const transport = new StreamableHTTPServerTransport({
|
||||
sessionIdGenerator: undefined,
|
||||
});
|
||||
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;
|
||||
|
||||
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") {
|
||||
res.writeHead(200, { "Content-Type": "text/plain" });
|
||||
res.end("pong");
|
||||
} 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");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Function to attempt server listen with port fallback
|
||||
const startServer = (port: number, maxAttempts = 10) => {
|
||||
httpServer.once("error", (err: NodeJS.ErrnoException) => {
|
||||
if (err.code === "EADDRINUSE" && port < initialPort + maxAttempts) {
|
||||
console.warn(`Port ${port} is in use, trying port ${port + 1}...`);
|
||||
startServer(port + 1, maxAttempts);
|
||||
} else {
|
||||
console.error(`Failed to start server: ${err.message}`);
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
httpServer.listen(port, () => {
|
||||
actualPort = port;
|
||||
console.error(
|
||||
`Context7 Documentation MCP Server running on ${transportType.toUpperCase()} at http://localhost:${actualPort}/mcp and legacy SSE at /sse`
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
// Start the server with initial port
|
||||
startServer(initialPort);
|
||||
} 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");
|
||||
}
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
|
Loading…
x
Reference in New Issue
Block a user