@upstash/context7-mcp 1.0.25 → 1.0.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,12 +1,12 @@
1
1
  ![Cover](public/cover.png)
2
2
 
3
+ [![Install MCP Server](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en/install-mcp?name=context7&config=eyJ1cmwiOiJodHRwczovL21jcC5jb250ZXh0Ny5jb20vbWNwIn0%3D) [<img alt="Install in VS Code (npx)" src="https://img.shields.io/badge/Install%20in%20VS%20Code-0098FF?style=for-the-badge&logo=visualstudiocode&logoColor=white">](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)
4
+
3
5
  # Context7 MCP - Up-to-date Code Docs For Any Prompt
4
6
 
5
7
  [![Website](https://img.shields.io/badge/Website-context7.com-blue)](https://context7.com) [![smithery badge](https://smithery.ai/badge/@upstash/context7-mcp)](https://smithery.ai/server/@upstash/context7-mcp) [![NPM Version](https://img.shields.io/npm/v/%40upstash%2Fcontext7-mcp?color=red)](https://www.npmjs.com/package/@upstash/context7-mcp) [![MIT licensed](https://img.shields.io/npm/l/%40upstash%2Fcontext7-mcp)](./LICENSE)
6
8
 
7
- [![Install MCP Server](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en/install-mcp?name=context7&config=eyJ1cmwiOiJodHRwczovL21jcC5jb250ZXh0Ny5jb20vbWNwIn0%3D) [<img alt="Install in VS Code (npx)" src="https://img.shields.io/badge/Install%20in%20VS%20Code-0098FF?style=for-the-badge&logo=visualstudiocode&logoColor=white">](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)
8
-
9
- [![繁體中文](https://img.shields.io/badge/docs-繁體中文-yellow)](./docs/README.zh-TW.md) [![简体中文](https://img.shields.io/badge/docs-简体中文-yellow)](./docs/README.zh-CN.md) [![日本語](https://img.shields.io/badge/docs-日本語-b7003a)](./docs/README.ja.md) [![한국어 문서](https://img.shields.io/badge/docs-한국어-green)](./docs/README.ko.md) [![Documentación en Español](https://img.shields.io/badge/docs-Español-orange)](./docs/README.es.md) [![Documentation en Français](https://img.shields.io/badge/docs-Français-blue)](./docs/README.fr.md) [![Documentação em Português (Brasil)](<https://img.shields.io/badge/docs-Português%20(Brasil)-purple>)](./docs/README.pt-BR.md) [![Documentazione in italiano](https://img.shields.io/badge/docs-Italian-red)](./docs/README.it.md) [![Dokumentasi Bahasa Indonesia](https://img.shields.io/badge/docs-Bahasa%20Indonesia-pink)](./docs/README.id-ID.md) [![Dokumentation auf Deutsch](https://img.shields.io/badge/docs-Deutsch-darkgreen)](./docs/README.de.md) [![Документация на русском языке](https://img.shields.io/badge/docs-Русский-darkblue)](./docs/README.ru.md) [![Українська документація](https://img.shields.io/badge/docs-Українська-lightblue)](./docs/README.uk.md) [![Türkçe Doküman](https://img.shields.io/badge/docs-Türkçe-blue)](./docs/README.tr.md) [![Arabic Documentation](https://img.shields.io/badge/docs-Arabic-white)](./docs/README.ar.md) [![Tiếng Việt](https://img.shields.io/badge/docs-Tiếng%20Việt-red)](./docs/README.vi.md)
9
+ [![繁體中文](https://img.shields.io/badge/docs-繁體中文-yellow)](./i18n/README.zh-TW.md) [![简体中文](https://img.shields.io/badge/docs-简体中文-yellow)](./i18n/README.zh-CN.md) [![日本語](https://img.shields.io/badge/docs-日本語-b7003a)](./i18n/README.ja.md) [![한국어 문서](https://img.shields.io/badge/docs-한국어-green)](./i18n/README.ko.md) [![Documentación en Español](https://img.shields.io/badge/docs-Español-orange)](./i18n/README.es.md) [![Documentation en Français](https://img.shields.io/badge/docs-Français-blue)](./i18n/README.fr.md) [![Documentação em Português (Brasil)](<https://img.shields.io/badge/docs-Português%20(Brasil)-purple>)](./i18n/README.pt-BR.md) [![Documentazione in italiano](https://img.shields.io/badge/docs-Italian-red)](./i18n/README.it.md) [![Dokumentasi Bahasa Indonesia](https://img.shields.io/badge/docs-Bahasa%20Indonesia-pink)](./i18n/README.id-ID.md) [![Dokumentation auf Deutsch](https://img.shields.io/badge/docs-Deutsch-darkgreen)](./i18n/README.de.md) [![Документация на русском языке](https://img.shields.io/badge/docs-Русский-darkblue)](./i18n/README.ru.md) [![Українська документація](https://img.shields.io/badge/docs-Українська-lightblue)](./i18n/README.uk.md) [![Türkçe Doküman](https://img.shields.io/badge/docs-Türkçe-blue)](./i18n/README.tr.md) [![Arabic Documentation](https://img.shields.io/badge/docs-Arabic-white)](./i18n/README.ar.md) [![Tiếng Việt](https://img.shields.io/badge/docs-Tiếng%20Việt-red)](./i18n/README.vi.md)
10
10
 
11
11
  ## ❌ Without Context7
12
12
 
@@ -23,11 +23,13 @@ Context7 MCP pulls up-to-date, version-specific documentation and code examples
23
23
  Add `use context7` to your prompt in Cursor:
24
24
 
25
25
  ```txt
26
- Create a Next.js middleware that checks for a valid JWT in cookies and redirects unauthenticated users to `/login`. use context7
26
+ Create a Next.js middleware that checks for a valid JWT in cookies
27
+ and redirects unauthenticated users to `/login`. use context7
27
28
  ```
28
29
 
29
30
  ```txt
30
- Configure a Cloudflare Worker script to cache JSON API responses for five minutes. use context7
31
+ Configure a Cloudflare Worker script to cache
32
+ JSON API responses for five minutes. use context7
31
33
  ```
32
34
 
33
35
  Context7 fetches up-to-date code examples and documentation right into your LLM's context.
@@ -38,9 +40,12 @@ Context7 fetches up-to-date code examples and documentation right into your LLM'
38
40
 
39
41
  No tab-switching, no hallucinated APIs that don't exist, no outdated code generation.
40
42
 
43
+ > [!NOTE]
44
+ > This repository hosts the source code of Context7 MCP server. The supporting components — API backend, parsing engine, and crawling engine — are private and not part of this release.
45
+
41
46
  ## 📚 Adding Projects
42
47
 
43
- Check out our [project addition guide](./docs/adding-projects.md) to learn how to add (or update) your favorite libraries to Context7.
48
+ Check out our [project addition guide](https://context7.com/docs/adding-libraries) to learn how to add (or update) your favorite libraries to Context7.
44
49
 
45
50
  ## 🛠️ Installation
46
51
 
@@ -50,11 +55,6 @@ Check out our [project addition guide](./docs/adding-projects.md) to learn how t
50
55
  - Cursor, Claude Code, VSCode, Windsurf or another MCP Client
51
56
  - Context7 API Key (Optional) for higher rate limits and private repositories (Get yours by creating an account at [context7.com/dashboard](https://context7.com/dashboard))
52
57
 
53
- > [!WARNING]
54
- > **SSE Protocol Deprecation Notice**
55
- >
56
- > The Server-Sent Events (SSE) transport protocol is deprecated and its endpoint will be removed in upcoming releases. Please use HTTP or stdio transport methods instead.
57
-
58
58
  <details>
59
59
  <summary><b>Installing via Smithery</b></summary>
60
60
 
@@ -507,6 +507,8 @@ See [OpenAI Codex](https://github.com/openai/codex) for more information.
507
507
 
508
508
  Add the following configuration to your OpenAI Codex MCP server settings:
509
509
 
510
+ #### Local Server Connection
511
+
510
512
  ```toml
511
513
  [mcp_servers.context7]
512
514
  args = ["-y", "@upstash/context7-mcp", "--api-key", "YOUR_API_KEY"]
@@ -514,6 +516,14 @@ command = "npx"
514
516
  startup_timeout_ms = 20_000
515
517
  ```
516
518
 
519
+ #### Remote Server Connection
520
+
521
+ ```toml
522
+ [mcp_servers.context7]
523
+ url = "https://mcp.context7.com/mcp"
524
+ http_headers = { "CONTEXT7_API_KEY" = "YOUR_API_KEY" }
525
+ ```
526
+
517
527
  > Optional troubleshooting — only if you see startup "request timed out" or "not found program". Most users can ignore this.
518
528
  >
519
529
  > - First try: increase `startup_timeout_ms` to `40_000` and retry.
@@ -832,6 +842,57 @@ For more information, see the [official GitHub documentation](https://docs.githu
832
842
 
833
843
  </details>
834
844
 
845
+ <details>
846
+ <summary><b>Install in Copilot CLI</b></summary>
847
+
848
+ 1. Open the Copilot CLI MCP config file. The location is `~/.copilot/mcp-config.json` (where `~` is your home directory).
849
+ 2. Add the following to the `mcpServers` object in your `mcp-config.json` file:
850
+
851
+ ```json
852
+ {
853
+ "mcpServers": {
854
+ "context7": {
855
+ "type": "http",
856
+ "url": "https://mcp.context7.com/mcp",
857
+ "headers": {
858
+ "CONTEXT7_API_KEY": "YOUR_API_KEY"
859
+ },
860
+ "tools": [
861
+ "get-library-docs",
862
+ "resolve-library-id"
863
+ ]
864
+ }
865
+ }
866
+ }
867
+ ```
868
+
869
+ Or, for a local server:
870
+
871
+ ```json
872
+ {
873
+ "mcpServers": {
874
+ "context7": {
875
+ "type": "local",
876
+ "command": "npx",
877
+ "tools": [
878
+ "get-library-docs",
879
+ "resolve-library-id"
880
+ ],
881
+ "args": [
882
+ "-y",
883
+ "@upstash/context7-mcp",
884
+ "--api-key",
885
+ "YOUR_API_KEY"
886
+ ]
887
+ }
888
+ }
889
+ }
890
+ ```
891
+
892
+ If the `mcp-config.json` file does not exist, create it.
893
+
894
+ </details>
895
+
835
896
  <details>
836
897
  <summary><b>Install in LM Studio</b></summary>
837
898
 
@@ -1082,6 +1143,37 @@ See [Local and Remote MCPs for Perplexity](https://www.perplexity.ai/help-center
1082
1143
  7. Click `Save`.
1083
1144
  </details>
1084
1145
 
1146
+ <details>
1147
+ <summary><b>Install in Factory</b></summary>
1148
+
1149
+ Factory's droid supports MCP servers through its CLI. See [Factory MCP docs](https://docs.factory.ai/cli/configuration/mcp) for more info.
1150
+
1151
+ #### Factory Remote Server Connection (HTTP)
1152
+
1153
+ Run this command in your terminal:
1154
+
1155
+ ```sh
1156
+ droid mcp add context7 https://mcp.context7.com/mcp --type http --header "CONTEXT7_API_KEY: YOUR_API_KEY"
1157
+ ```
1158
+
1159
+ Or without an API key (basic usage with rate limits):
1160
+
1161
+ ```sh
1162
+ droid mcp add context7 https://mcp.context7.com/mcp --type http
1163
+ ```
1164
+
1165
+ #### Factory Local Server Connection (Stdio)
1166
+
1167
+ Run this command in your terminal:
1168
+
1169
+ ```sh
1170
+ droid mcp add context7 "npx -y @upstash/context7-mcp" --env CONTEXT7_API_KEY=YOUR_API_KEY
1171
+ ```
1172
+
1173
+ Once configured, Context7 tools will be available in your droid sessions. Type `/mcp` within droid to manage servers, authenticate, and view available tools.
1174
+
1175
+ </details>
1176
+
1085
1177
  ## 🔨 Available Tools
1086
1178
 
1087
1179
  Context7 MCP provides the following tools that LLMs can use:
@@ -1154,7 +1246,7 @@ bun run dist/index.js
1154
1246
 
1155
1247
  `context7-mcp` accepts the following CLI flags:
1156
1248
 
1157
- - `--transport <stdio|http>` – Transport to use (`stdio` by default). Note that HTTP transport automatically provides both HTTP and SSE endpoints.
1249
+ - `--transport <stdio|http>` – Transport to use (`stdio` by default). Use `http` for remote HTTP server or `stdio` for local integration.
1158
1250
  - `--port <number>` – Port to listen on when using `http` transport (default `3000`).
1159
1251
  - `--api-key <key>` – API key for authentication (or set `CONTEXT7_API_KEY` env var). You can get your API key by creating an account at [context7.com/dashboard](https://context7.com/dashboard).
1160
1252
 
@@ -1298,7 +1390,9 @@ Use the `--experimental-fetch` flag to bypass TLS-related problems:
1298
1390
 
1299
1391
  ## ⚠️ Disclaimer
1300
1392
 
1301
- Context7 projects are community-contributed and while we strive to maintain high quality, we cannot guarantee the accuracy, completeness, or security of all library documentation. Projects listed in Context7 are developed and maintained by their respective owners, not by Context7. If you encounter any suspicious, inappropriate, or potentially harmful content, please use the "Report" button on the project page to notify us immediately. We take all reports seriously and will review flagged content promptly to maintain the integrity and safety of our platform. By using Context7, you acknowledge that you do so at your own discretion and risk.
1393
+ 1- Context7 projects are community-contributed and while we strive to maintain high quality, we cannot guarantee the accuracy, completeness, or security of all library documentation. Projects listed in Context7 are developed and maintained by their respective owners, not by Context7. If you encounter any suspicious, inappropriate, or potentially harmful content, please use the "Report" button on the project page to notify us immediately. We take all reports seriously and will review flagged content promptly to maintain the integrity and safety of our platform. By using Context7, you acknowledge that you do so at your own discretion and risk.
1394
+
1395
+ 2- This repository hosts the MCP server’s source code. The supporting components — API backend, parsing engine, and crawling engine — are private and not part of this release.
1302
1396
 
1303
1397
  ## 🤝 Connect with Us
1304
1398
 
package/dist/index.js CHANGED
@@ -4,10 +4,10 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
4
4
  import { z } from "zod";
5
5
  import { searchLibraries, fetchLibraryDocumentation } from "./lib/api.js";
6
6
  import { formatSearchResults } from "./lib/utils.js";
7
- import { createServer } from "http";
7
+ import express from "express";
8
8
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
9
- import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
10
9
  import { Command } from "commander";
10
+ import { AsyncLocalStorage } from "async_hooks";
11
11
  /** Minimum allowed tokens for documentation retrieval */
12
12
  const MINIMUM_TOKENS = 1000;
13
13
  /** Default tokens when none specified */
@@ -46,16 +46,12 @@ const CLI_PORT = (() => {
46
46
  const parsed = parseInt(cliOptions.port, 10);
47
47
  return isNaN(parsed) ? undefined : parsed;
48
48
  })();
49
- // Store SSE transports by session ID
50
- const sseTransports = {};
49
+ const requestContext = new AsyncLocalStorage();
51
50
  function getClientIp(req) {
52
- // Check both possible header casings
53
51
  const forwardedFor = req.headers["x-forwarded-for"] || req.headers["X-Forwarded-For"];
54
52
  if (forwardedFor) {
55
- // X-Forwarded-For can contain multiple IPs
56
53
  const ips = Array.isArray(forwardedFor) ? forwardedFor[0] : forwardedFor;
57
54
  const ipList = ips.split(",").map((ip) => ip.trim());
58
- // Find the first public IP address
59
55
  for (const ip of ipList) {
60
56
  const plainIp = ip.replace(/^::ffff:/, "");
61
57
  if (!plainIp.startsWith("10.") &&
@@ -64,27 +60,22 @@ function getClientIp(req) {
64
60
  return plainIp;
65
61
  }
66
62
  }
67
- // If all are private, use the first one
68
63
  return ipList[0].replace(/^::ffff:/, "");
69
64
  }
70
- // Fallback: use remote address, strip IPv6-mapped IPv4
71
65
  if (req.socket?.remoteAddress) {
72
66
  return req.socket.remoteAddress.replace(/^::ffff:/, "");
73
67
  }
74
68
  return undefined;
75
69
  }
76
- // Function to create a new server instance with all tools registered
77
- function createServerInstance(clientIp, apiKey) {
78
- const server = new McpServer({
79
- name: "Context7",
80
- version: "1.0.25",
81
- }, {
82
- instructions: "Use this server to retrieve up-to-date documentation and code examples for any library.",
83
- });
84
- // Register Context7 tools
85
- server.registerTool("resolve-library-id", {
86
- title: "Resolve Context7 Library ID",
87
- description: `Resolves a package/product name to a Context7-compatible library ID and returns a list of matching libraries.
70
+ const server = new McpServer({
71
+ name: "Context7",
72
+ version: "1.0.27",
73
+ }, {
74
+ instructions: "Use this server to retrieve up-to-date documentation and code examples for any library.",
75
+ });
76
+ server.registerTool("resolve-library-id", {
77
+ title: "Resolve Context7 Library ID",
78
+ description: `Resolves a package/product name to a Context7-compatible library ID and returns a list of matching libraries.
88
79
 
89
80
  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.
90
81
 
@@ -94,7 +85,8 @@ Selection Process:
94
85
  - Name similarity to the query (exact matches prioritized)
95
86
  - Description relevance to the query's intent
96
87
  - Documentation coverage (prioritize libraries with higher Code Snippet counts)
97
- - Trust score (consider libraries with scores of 7-10 more authoritative)
88
+ - Source reputation (consider libraries with High or Medium reputation more authoritative)
89
+ - Benchmark Score: Quality indicator (100 is the highest score)
98
90
 
99
91
  Response Format:
100
92
  - Return the selected library ID in a clearly marked section
@@ -103,132 +95,128 @@ Response Format:
103
95
  - If no good matches exist, clearly state this and suggest query refinements
104
96
 
105
97
  For ambiguous queries, request clarification before proceeding with a best-guess match.`,
106
- inputSchema: {
107
- libraryName: z
108
- .string()
109
- .describe("Library name to search for and retrieve a Context7-compatible library ID."),
110
- },
111
- }, async ({ libraryName }) => {
112
- const searchResponse = await searchLibraries(libraryName, clientIp, apiKey);
113
- if (!searchResponse.results || searchResponse.results.length === 0) {
114
- return {
115
- content: [
116
- {
117
- type: "text",
118
- text: searchResponse.error
119
- ? searchResponse.error
120
- : "Failed to retrieve library documentation data from Context7",
121
- },
122
- ],
123
- };
124
- }
125
- const resultsText = formatSearchResults(searchResponse);
98
+ inputSchema: {
99
+ libraryName: z
100
+ .string()
101
+ .describe("Library name to search for and retrieve a Context7-compatible library ID."),
102
+ },
103
+ }, async ({ libraryName }) => {
104
+ const ctx = requestContext.getStore();
105
+ const searchResponse = await searchLibraries(libraryName, ctx?.clientIp, ctx?.apiKey);
106
+ if (!searchResponse.results || searchResponse.results.length === 0) {
126
107
  return {
127
108
  content: [
128
109
  {
129
110
  type: "text",
130
- text: `Available Libraries (top matches):
111
+ text: searchResponse.error
112
+ ? searchResponse.error
113
+ : "Failed to retrieve library documentation data from Context7",
114
+ },
115
+ ],
116
+ };
117
+ }
118
+ const resultsText = formatSearchResults(searchResponse);
119
+ const responseText = `Available Libraries (top matches):
131
120
 
132
121
  Each result includes:
133
122
  - Library ID: Context7-compatible identifier (format: /org/project)
134
123
  - Name: Library or package name
135
124
  - Description: Short summary
136
125
  - Code Snippets: Number of available code examples
137
- - Trust Score: Authority indicator
126
+ - Source Reputation: Authority indicator (High, Medium, Low, or Unknown)
127
+ - Benchmark Score: Quality indicator (100 is the highest score)
138
128
  - Versions: List of versions if available. Use one of those versions if the user provides a version in their query. The format of the version is /org/project/version.
139
129
 
140
- For best results, select libraries based on name match, trust score, snippet coverage, and relevance to your use case.
130
+ For best results, select libraries based on name match, source reputation, snippet coverage, and relevance to your use case.
141
131
 
142
132
  ----------
143
133
 
144
- ${resultsText}`,
145
- },
146
- ],
147
- };
148
- });
149
- server.registerTool("get-library-docs", {
150
- title: "Get Library Docs",
151
- description: "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.",
152
- inputSchema: {
153
- context7CompatibleLibraryID: z
154
- .string()
155
- .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'."),
156
- topic: z
157
- .string()
158
- .optional()
159
- .describe("Topic to focus documentation on (e.g., 'hooks', 'routing')."),
160
- tokens: z
161
- .preprocess((val) => (typeof val === "string" ? Number(val) : val), z.number())
162
- .transform((val) => (val < MINIMUM_TOKENS ? MINIMUM_TOKENS : val))
163
- .optional()
164
- .describe(`Maximum number of tokens of documentation to retrieve (default: ${DEFAULT_TOKENS}). Higher values provide more context but consume more tokens.`),
165
- },
166
- }, async ({ context7CompatibleLibraryID, tokens = DEFAULT_TOKENS, topic = "" }) => {
167
- const fetchDocsResponse = await fetchLibraryDocumentation(context7CompatibleLibraryID, {
168
- tokens,
169
- topic,
170
- }, clientIp, apiKey);
171
- if (!fetchDocsResponse) {
172
- return {
173
- content: [
174
- {
175
- type: "text",
176
- 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.",
177
- },
178
- ],
179
- };
180
- }
134
+ ${resultsText}`;
135
+ return {
136
+ content: [
137
+ {
138
+ type: "text",
139
+ text: responseText,
140
+ },
141
+ ],
142
+ };
143
+ });
144
+ server.registerTool("get-library-docs", {
145
+ title: "Get Library Docs",
146
+ description: "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.",
147
+ inputSchema: {
148
+ context7CompatibleLibraryID: z
149
+ .string()
150
+ .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'."),
151
+ topic: z
152
+ .string()
153
+ .optional()
154
+ .describe("Topic to focus documentation on (e.g., 'hooks', 'routing')."),
155
+ tokens: z
156
+ .preprocess((val) => (typeof val === "string" ? Number(val) : val), z.number())
157
+ .transform((val) => (val < MINIMUM_TOKENS ? MINIMUM_TOKENS : val))
158
+ .optional()
159
+ .describe(`Maximum number of tokens of documentation to retrieve (default: ${DEFAULT_TOKENS}). Higher values provide more context but consume more tokens.`),
160
+ },
161
+ }, async ({ context7CompatibleLibraryID, tokens = DEFAULT_TOKENS, topic = "" }) => {
162
+ const ctx = requestContext.getStore();
163
+ const fetchDocsResponse = await fetchLibraryDocumentation(context7CompatibleLibraryID, {
164
+ tokens,
165
+ topic,
166
+ }, ctx?.clientIp, ctx?.apiKey);
167
+ if (!fetchDocsResponse) {
181
168
  return {
182
169
  content: [
183
170
  {
184
171
  type: "text",
185
- text: fetchDocsResponse,
172
+ 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.",
186
173
  },
187
174
  ],
188
175
  };
189
- });
190
- return server;
191
- }
176
+ }
177
+ return {
178
+ content: [
179
+ {
180
+ type: "text",
181
+ text: fetchDocsResponse,
182
+ },
183
+ ],
184
+ };
185
+ });
192
186
  async function main() {
193
187
  const transportType = TRANSPORT_TYPE;
194
188
  if (transportType === "http") {
195
- // Get initial port from environment or use default
196
189
  const initialPort = CLI_PORT ?? DEFAULT_PORT;
197
- // Keep track of which port we end up using
198
190
  let actualPort = initialPort;
199
- const httpServer = createServer(async (req, res) => {
200
- const pathname = new URL(req.url || "/", "http://localhost").pathname;
201
- // Set CORS headers for all responses
191
+ const app = express();
192
+ app.use(express.json());
193
+ app.use((req, res, next) => {
202
194
  res.setHeader("Access-Control-Allow-Origin", "*");
203
195
  res.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,DELETE");
204
196
  res.setHeader("Access-Control-Allow-Headers", "Content-Type, MCP-Session-Id, MCP-Protocol-Version, X-Context7-API-Key, Context7-API-Key, X-API-Key, Authorization");
205
197
  res.setHeader("Access-Control-Expose-Headers", "MCP-Session-Id");
206
- // Handle preflight OPTIONS requests
207
198
  if (req.method === "OPTIONS") {
208
- res.writeHead(200);
209
- res.end();
199
+ res.sendStatus(200);
210
200
  return;
211
201
  }
212
- // Function to extract header value safely, handling both string and string[] cases
213
- const extractHeaderValue = (value) => {
214
- if (!value)
215
- return undefined;
216
- return typeof value === "string" ? value : value[0];
217
- };
218
- // Extract Authorization header and remove Bearer prefix if present
219
- const extractBearerToken = (authHeader) => {
220
- const header = extractHeaderValue(authHeader);
221
- if (!header)
222
- return undefined;
223
- // If it starts with 'Bearer ', remove that prefix
224
- if (header.startsWith("Bearer ")) {
225
- return header.substring(7).trim();
226
- }
227
- // Otherwise return the raw value
228
- return header;
229
- };
230
- // Check headers in order of preference
231
- const apiKey = extractBearerToken(req.headers.authorization) ||
202
+ next();
203
+ });
204
+ const extractHeaderValue = (value) => {
205
+ if (!value)
206
+ return undefined;
207
+ return typeof value === "string" ? value : value[0];
208
+ };
209
+ const extractBearerToken = (authHeader) => {
210
+ const header = extractHeaderValue(authHeader);
211
+ if (!header)
212
+ return undefined;
213
+ if (header.startsWith("Bearer ")) {
214
+ return header.substring(7).trim();
215
+ }
216
+ return header;
217
+ };
218
+ const extractApiKey = (req) => {
219
+ return (extractBearerToken(req.headers.authorization) ||
232
220
  extractHeaderValue(req.headers["Context7-API-Key"]) ||
233
221
  extractHeaderValue(req.headers["X-API-Key"]) ||
234
222
  extractHeaderValue(req.headers["context7-api-key"]) ||
@@ -236,85 +224,51 @@ async function main() {
236
224
  extractHeaderValue(req.headers["Context7_API_Key"]) ||
237
225
  extractHeaderValue(req.headers["X_API_Key"]) ||
238
226
  extractHeaderValue(req.headers["context7_api_key"]) ||
239
- extractHeaderValue(req.headers["x_api_key"]);
227
+ extractHeaderValue(req.headers["x_api_key"]));
228
+ };
229
+ app.all("/mcp", async (req, res) => {
240
230
  try {
241
- // Extract client IP address using socket remote address (most reliable)
242
231
  const clientIp = getClientIp(req);
243
- // Create new server instance for each request
244
- const requestServer = createServerInstance(clientIp, apiKey);
245
- if (pathname === "/mcp") {
246
- const transport = new StreamableHTTPServerTransport({
247
- sessionIdGenerator: undefined,
248
- });
249
- res.on("close", () => {
250
- transport.close();
251
- requestServer.close();
252
- });
253
- await requestServer.connect(transport);
254
- await transport.handleRequest(req, res);
255
- }
256
- else if (pathname === "/sse" && req.method === "GET") {
257
- // Create new SSE transport for GET request
258
- const sseTransport = new SSEServerTransport("/messages", res);
259
- // Store the transport by session ID
260
- sseTransports[sseTransport.sessionId] = sseTransport;
261
- // Clean up transport when connection closes
262
- res.on("close", () => {
263
- delete sseTransports[sseTransport.sessionId];
264
- sseTransport.close();
265
- requestServer.close();
266
- });
267
- await requestServer.connect(sseTransport);
268
- // Send initial message to establish communication
269
- res.write("data: " +
270
- JSON.stringify({
271
- type: "connection_established",
272
- sessionId: sseTransport.sessionId,
273
- timestamp: new Date().toISOString(),
274
- }) +
275
- "\n\n");
276
- }
277
- else if (pathname === "/messages" && req.method === "POST") {
278
- // Get session ID from query parameters
279
- const sessionId = new URL(req.url || "/", "http://localhost").searchParams.get("sessionId") ?? "";
280
- if (!sessionId) {
281
- res.writeHead(400, { "Content-Type": "application/json" });
282
- res.end(JSON.stringify({ error: "Missing sessionId parameter", status: 400 }));
283
- return;
284
- }
285
- // Get existing transport for this session
286
- const sseTransport = sseTransports[sessionId];
287
- if (!sseTransport) {
288
- res.writeHead(400, { "Content-Type": "application/json" });
289
- res.end(JSON.stringify({
290
- error: `No transport found for sessionId: ${sessionId}`,
291
- status: 400,
292
- }));
293
- return;
294
- }
295
- // Handle the POST message with the existing transport
296
- await sseTransport.handlePostMessage(req, res);
297
- }
298
- else if (pathname === "/ping") {
299
- res.writeHead(200, { "Content-Type": "application/json" });
300
- res.end(JSON.stringify({ status: "ok", message: "pong" }));
301
- }
302
- else {
303
- res.writeHead(404, { "Content-Type": "application/json" });
304
- res.end(JSON.stringify({ error: "Not found", status: 404 }));
305
- }
232
+ const apiKey = extractApiKey(req);
233
+ const transport = new StreamableHTTPServerTransport({
234
+ sessionIdGenerator: undefined,
235
+ enableJsonResponse: true,
236
+ });
237
+ res.on("close", () => {
238
+ transport.close();
239
+ });
240
+ await requestContext.run({ clientIp, apiKey }, async () => {
241
+ await server.connect(transport);
242
+ await transport.handleRequest(req, res, req.body);
243
+ });
306
244
  }
307
245
  catch (error) {
308
- console.error("Error handling request:", error);
246
+ console.error("Error handling MCP request:", error);
309
247
  if (!res.headersSent) {
310
- res.writeHead(500, { "Content-Type": "application/json" });
311
- res.end(JSON.stringify({ error: "Internal Server Error", status: 500 }));
248
+ res.status(500).json({
249
+ jsonrpc: "2.0",
250
+ error: { code: -32603, message: "Internal server error" },
251
+ id: null,
252
+ });
312
253
  }
313
254
  }
314
255
  });
315
- // Function to attempt server listen with port fallback
256
+ app.get("/ping", (_req, res) => {
257
+ res.json({ status: "ok", message: "pong" });
258
+ });
259
+ // Catch-all 404 handler - must be after all other routes
260
+ app.use((_req, res) => {
261
+ res.status(404).json({
262
+ error: "not_found",
263
+ message: "Endpoint not found. Use /mcp for MCP protocol communication.",
264
+ });
265
+ });
316
266
  const startServer = (port, maxAttempts = 10) => {
317
- httpServer.once("error", (err) => {
267
+ const httpServer = app.listen(port, () => {
268
+ actualPort = port;
269
+ console.error(`Context7 Documentation MCP Server running on HTTP at http://localhost:${actualPort}/mcp`);
270
+ });
271
+ httpServer.on("error", (err) => {
318
272
  if (err.code === "EADDRINUSE" && port < initialPort + maxAttempts) {
319
273
  console.warn(`Port ${port} is in use, trying port ${port + 1}...`);
320
274
  startServer(port + 1, maxAttempts);
@@ -324,20 +278,15 @@ async function main() {
324
278
  process.exit(1);
325
279
  }
326
280
  });
327
- httpServer.listen(port, () => {
328
- actualPort = port;
329
- console.error(`Context7 Documentation MCP Server running on ${transportType.toUpperCase()} at http://localhost:${actualPort}/mcp with SSE endpoint at /sse`);
330
- });
331
281
  };
332
- // Start the server with initial port
333
282
  startServer(initialPort);
334
283
  }
335
284
  else {
336
- // Stdio transport - this is already stateless by nature
337
285
  const apiKey = cliOptions.apiKey || process.env.CONTEXT7_API_KEY;
338
- const server = createServerInstance(undefined, apiKey);
339
286
  const transport = new StdioServerTransport();
340
- await server.connect(transport);
287
+ await requestContext.run({ apiKey }, async () => {
288
+ await server.connect(transport);
289
+ });
341
290
  console.error("Context7 Documentation MCP Server running on stdio");
342
291
  }
343
292
  }
package/dist/lib/api.js CHANGED
@@ -37,7 +37,9 @@ export async function searchLibraries(query, clientIp, apiKey) {
37
37
  if (!response.ok) {
38
38
  const errorCode = response.status;
39
39
  if (errorCode === 429) {
40
- const errorMessage = "Rate limited due to too many requests. Please try again later.";
40
+ const errorMessage = apiKey
41
+ ? "Rate limited due to too many requests. Please try again later."
42
+ : "Rate limited due to too many requests. You can create a free API key at https://context7.com/dashboard for higher rate limits.";
41
43
  console.error(errorMessage);
42
44
  return {
43
45
  results: [],
@@ -93,7 +95,9 @@ export async function fetchLibraryDocumentation(libraryId, options = {}, clientI
93
95
  if (!response.ok) {
94
96
  const errorCode = response.status;
95
97
  if (errorCode === 429) {
96
- const errorMessage = "Rate limited due to too many requests. Please try again later.";
98
+ const errorMessage = apiKey
99
+ ? "Rate limited due to too many requests. Please try again later."
100
+ : "Rate limited due to too many requests. You can create a free API key at https://context7.com/dashboard for higher rate limits.";
97
101
  console.error(errorMessage);
98
102
  return errorMessage;
99
103
  }
package/dist/lib/utils.js CHANGED
@@ -1,3 +1,17 @@
1
+ /**
2
+ * Maps numeric source reputation score to an interpretable label for LLM consumption.
3
+ *
4
+ * @returns One of: "High", "Medium", "Low", or "Unknown"
5
+ */
6
+ function getSourceReputationLabel(sourceReputation) {
7
+ if (sourceReputation === undefined || sourceReputation < 0)
8
+ return "Unknown";
9
+ if (sourceReputation >= 7)
10
+ return "High";
11
+ if (sourceReputation >= 4)
12
+ return "Medium";
13
+ return "Low";
14
+ }
1
15
  /**
2
16
  * Formats a search result into a human-readable string representation.
3
17
  * Only shows code snippet count and GitHub stars when available (not equal to -1).
@@ -16,9 +30,12 @@ export function formatSearchResult(result) {
16
30
  if (result.totalSnippets !== -1 && result.totalSnippets !== undefined) {
17
31
  formattedResult.push(`- Code Snippets: ${result.totalSnippets}`);
18
32
  }
19
- // Only add trust score if it's a valid value
20
- if (result.trustScore !== -1 && result.trustScore !== undefined) {
21
- formattedResult.push(`- Trust Score: ${result.trustScore}`);
33
+ // Always add categorized source reputation
34
+ const reputationLabel = getSourceReputationLabel(result.trustScore);
35
+ formattedResult.push(`- Source Reputation: ${reputationLabel}`);
36
+ // Only add benchmark score if it's a valid value
37
+ if (result.benchmarkScore !== undefined && result.benchmarkScore > 0) {
38
+ formattedResult.push(`- Benchmark Score: ${result.benchmarkScore}`);
22
39
  }
23
40
  // Only add versions if it's a valid value
24
41
  if (result.versions !== undefined && result.versions.length > 0) {
package/package.json CHANGED
@@ -1 +1 @@
1
- {"name":"@upstash/context7-mcp","version":"1.0.25","mcpName":"io.github.upstash/context7","description":"MCP server for Context7","scripts":{"test":"echo \"Error: no test specified\" && exit 1","build":"tsc && chmod 755 dist/index.js","format":"prettier --write .","lint":"eslint \"**/*.{js,ts,tsx}\" --fix","lint:check":"eslint \"**/*.{js,ts,tsx}\"","start":"node dist/index.js --transport http","pack-mcpb":"bun install && bun run build && rm -rf node_modules && bun install --production && mv mcpb/.mcpbignore .mcpbignore && mv mcpb/manifest.json manifest.json && mv public/icon.png icon.png && mcpb validate manifest.json && mcpb pack . mcpb/context7.mcpb && mv manifest.json mcpb/manifest.json && mv .mcpbignore mcpb/.mcpbignore && mv icon.png public/icon.png && bun install"},"repository":{"type":"git","url":"git+https://github.com/upstash/context7.git"},"keywords":["modelcontextprotocol","mcp","context7","vibe-coding","developer tools","documentation","context"],"author":"abdush","license":"MIT","type":"module","bin":{"context7-mcp":"dist/index.js"},"files":["dist"],"bugs":{"url":"https://github.com/upstash/context7/issues"},"homepage":"https://github.com/upstash/context7#readme","dependencies":{"@modelcontextprotocol/sdk":"^1.17.5","commander":"^14.0.0","undici":"^6.6.3","zod":"^3.24.2"},"devDependencies":{"@types/node":"^22.13.14","@typescript-eslint/eslint-plugin":"^8.28.0","@typescript-eslint/parser":"^8.28.0","eslint":"^9.34.0","eslint-config-prettier":"^10.1.1","eslint-plugin-prettier":"^5.2.5","prettier":"^3.6.2","typescript":"^5.8.2","typescript-eslint":"^8.28.0"}}
1
+ {"name":"@upstash/context7-mcp","version":"1.0.27","mcpName":"io.github.upstash/context7","description":"MCP server for Context7","scripts":{"test":"echo \"Error: no test specified\" && exit 1","build":"tsc && chmod 755 dist/index.js","format":"prettier --write .","lint":"eslint \"**/*.{js,ts,tsx}\" --fix","lint:check":"eslint \"**/*.{js,ts,tsx}\"","start":"node dist/index.js --transport http","pack-mcpb":"bun install && bun run build && rm -rf node_modules && bun install --production && mv mcpb/.mcpbignore .mcpbignore && mv mcpb/manifest.json manifest.json && mv public/icon.png icon.png && mcpb validate manifest.json && mcpb pack . mcpb/context7.mcpb && mv manifest.json mcpb/manifest.json && mv .mcpbignore mcpb/.mcpbignore && mv icon.png public/icon.png && bun install"},"repository":{"type":"git","url":"git+https://github.com/upstash/context7.git"},"keywords":["modelcontextprotocol","mcp","context7","vibe-coding","developer tools","documentation","context"],"author":"abdush","license":"MIT","type":"module","bin":{"context7-mcp":"dist/index.js"},"files":["dist"],"bugs":{"url":"https://github.com/upstash/context7/issues"},"homepage":"https://github.com/upstash/context7#readme","dependencies":{"@modelcontextprotocol/sdk":"^1.17.5","@types/express":"^5.0.4","commander":"^14.0.0","express":"^5.1.0","undici":"^6.6.3","zod":"^3.24.2"},"devDependencies":{"@types/node":"^22.13.14","@typescript-eslint/eslint-plugin":"^8.28.0","@typescript-eslint/parser":"^8.28.0","eslint":"^9.34.0","eslint-config-prettier":"^10.1.1","eslint-plugin-prettier":"^5.2.5","prettier":"^3.6.2","typescript":"^5.8.2","typescript-eslint":"^8.28.0"}}