@upstash/context7-mcp 1.1.0-canary-20251128121456 → 2.0.0

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,4 +1,4 @@
1
- ![Cover](public/cover.png)
1
+ ![Cover](https://github.com/upstash/context7/blob/master/public/cover.png?raw=true)
2
2
 
3
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
4
 
package/dist/index.js CHANGED
@@ -2,15 +2,12 @@
2
2
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
3
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
4
  import { z } from "zod";
5
- import { searchLibraries, fetchLibraryDocumentation } from "./lib/api.js";
5
+ import { searchLibraries, fetchLibraryContext } from "./lib/api.js";
6
6
  import { formatSearchResults } from "./lib/utils.js";
7
- import { DOCUMENTATION_MODES } from "./lib/types.js";
8
7
  import express from "express";
9
8
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
10
9
  import { Command } from "commander";
11
10
  import { AsyncLocalStorage } from "async_hooks";
12
- /** Default number of results to return per page */
13
- const DEFAULT_RESULTS_LIMIT = 10;
14
11
  /** Default HTTP server port */
15
12
  const DEFAULT_PORT = 3000;
16
13
  // Parse CLI arguments using commander
@@ -70,15 +67,15 @@ function getClientIp(req) {
70
67
  }
71
68
  const server = new McpServer({
72
69
  name: "Context7",
73
- version: "1.0.13",
70
+ version: "2.0.0",
74
71
  }, {
75
72
  instructions: "Use this server to retrieve up-to-date documentation and code examples for any library.",
76
73
  });
77
74
  server.registerTool("resolve-library-id", {
78
75
  title: "Resolve Context7 Library ID",
79
- description: `Resolves a package/product name to a Context7-compatible library ID and returns a list of matching libraries.
76
+ description: `Resolves a package/product name to a Context7-compatible library ID and returns matching libraries.
80
77
 
81
- 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.
78
+ You MUST call this function before 'query-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.
82
79
 
83
80
  Selection Process:
84
81
  1. Analyze the query to understand what library/package the user is looking for
@@ -95,15 +92,21 @@ Response Format:
95
92
  - If multiple good matches exist, acknowledge this but proceed with the most relevant one
96
93
  - If no good matches exist, clearly state this and suggest query refinements
97
94
 
98
- For ambiguous queries, request clarification before proceeding with a best-guess match.`,
95
+ For ambiguous queries, request clarification before proceeding with a best-guess match.
96
+
97
+ IMPORTANT: Do not call this tool more than 3 times per question. If you cannot find what you need after 3 calls, use the best result you have.`,
99
98
  inputSchema: {
99
+ query: z
100
+ .string()
101
+ .describe("The user's original question or task. This is used to rank library results by relevance to what the user is trying to accomplish. IMPORTANT: Do not include any sensitive or confidential information such as API keys, passwords, credentials, or personal data in your query."),
100
102
  libraryName: z
101
103
  .string()
102
104
  .describe("Library name to search for and retrieve a Context7-compatible library ID."),
103
105
  },
104
- }, async ({ libraryName }) => {
106
+ }, async ({ query, libraryName }) => {
105
107
  const ctx = requestContext.getStore();
106
- const searchResponse = await searchLibraries(libraryName, ctx?.clientIp, ctx?.apiKey);
108
+ const apiKey = ctx?.apiKey || globalApiKey;
109
+ const searchResponse = await searchLibraries(query, libraryName, ctx?.clientIp, apiKey);
107
110
  if (!searchResponse.results || searchResponse.results.length === 0) {
108
111
  return {
109
112
  content: [
@@ -111,7 +114,7 @@ For ambiguous queries, request clarification before proceeding with a best-guess
111
114
  type: "text",
112
115
  text: searchResponse.error
113
116
  ? searchResponse.error
114
- : "Failed to retrieve library documentation data from Context7",
117
+ : "No libraries found matching the provided name.",
115
118
  },
116
119
  ],
117
120
  };
@@ -142,53 +145,30 @@ ${resultsText}`;
142
145
  ],
143
146
  };
144
147
  });
145
- server.registerTool("get-library-docs", {
146
- title: "Get Library Docs",
147
- 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. Use mode='code' (default) for API references and code examples, or mode='info' for conceptual guides, narrative information, and architectural questions.",
148
+ server.registerTool("query-docs", {
149
+ title: "Query Documentation",
150
+ description: `Retrieves and queries up-to-date documentation and code examples from Context7 for any programming library or framework.
151
+
152
+ 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.
153
+
154
+ IMPORTANT: Do not call this tool more than 3 times per question. If you cannot find what you need after 3 calls, use the best information you have.`,
148
155
  inputSchema: {
149
- context7CompatibleLibraryID: z
156
+ libraryId: z
150
157
  .string()
151
158
  .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'."),
152
- mode: z
153
- .enum(["code", "info"])
154
- .optional()
155
- .default("code")
156
- .describe("Documentation mode: 'code' for API references and code examples (default), 'info' for conceptual guides, narrative information, and architectural questions."),
157
- topic: z
159
+ query: z
158
160
  .string()
159
- .optional()
160
- .describe("Topic to focus documentation on (e.g., 'hooks', 'routing')."),
161
- page: z
162
- .number()
163
- .int()
164
- .min(1)
165
- .max(10)
166
- .optional()
167
- .describe("Page number for pagination (start: 1, default: 1). If the context is not sufficient, try page=2, page=3, page=4, etc. with the same topic."),
161
+ .describe("The question or task you need help with. Be specific and include relevant details. Good: 'How to set up authentication with JWT in Express.js' or 'React useEffect cleanup function examples'. Bad: 'auth' or 'hooks'. IMPORTANT: Do not include any sensitive or confidential information such as API keys, passwords, credentials, or personal data in your query."),
168
162
  },
169
- }, async ({ context7CompatibleLibraryID, mode = DOCUMENTATION_MODES.CODE, page = 1, topic }) => {
163
+ }, async ({ query, libraryId }) => {
170
164
  const ctx = requestContext.getStore();
171
165
  const apiKey = ctx?.apiKey || globalApiKey;
172
- const fetchDocsResponse = await fetchLibraryDocumentation(context7CompatibleLibraryID, mode, {
173
- page,
174
- limit: DEFAULT_RESULTS_LIMIT,
175
- topic,
176
- }, ctx?.clientIp, apiKey);
177
- if (!fetchDocsResponse) {
178
- return {
179
- content: [
180
- {
181
- type: "text",
182
- 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.",
183
- },
184
- ],
185
- };
186
- }
166
+ const response = await fetchLibraryContext({ query, libraryId }, ctx?.clientIp, apiKey);
187
167
  return {
188
168
  content: [
189
169
  {
190
170
  type: "text",
191
- text: fetchDocsResponse,
171
+ text: response.data,
192
172
  },
193
173
  ],
194
174
  };
@@ -197,7 +177,6 @@ async function main() {
197
177
  const transportType = TRANSPORT_TYPE;
198
178
  if (transportType === "http") {
199
179
  const initialPort = CLI_PORT ?? DEFAULT_PORT;
200
- let actualPort = initialPort;
201
180
  const app = express();
202
181
  app.use(express.json());
203
182
  app.use((req, res, next) => {
@@ -227,12 +206,8 @@ async function main() {
227
206
  };
228
207
  const extractApiKey = (req) => {
229
208
  return (extractBearerToken(req.headers.authorization) ||
230
- extractHeaderValue(req.headers["Context7-API-Key"]) ||
231
- extractHeaderValue(req.headers["X-API-Key"]) ||
232
209
  extractHeaderValue(req.headers["context7-api-key"]) ||
233
210
  extractHeaderValue(req.headers["x-api-key"]) ||
234
- extractHeaderValue(req.headers["Context7_API_Key"]) ||
235
- extractHeaderValue(req.headers["X_API_Key"]) ||
236
211
  extractHeaderValue(req.headers["context7_api_key"]) ||
237
212
  extractHeaderValue(req.headers["x_api_key"]));
238
213
  };
@@ -286,8 +261,7 @@ async function main() {
286
261
  }
287
262
  });
288
263
  httpServer.once("listening", () => {
289
- actualPort = port;
290
- console.error(`Context7 Documentation MCP Server running on HTTP at http://localhost:${actualPort}/mcp`);
264
+ console.error(`Context7 Documentation MCP Server running on HTTP at http://localhost:${port}/mcp`);
291
265
  });
292
266
  };
293
267
  startServer(initialPort);
package/dist/lib/api.js CHANGED
@@ -1,49 +1,37 @@
1
1
  import { generateHeaders } from "./encryption.js";
2
2
  import { ProxyAgent, setGlobalDispatcher } from "undici";
3
- import { DOCUMENTATION_MODES } from "./types.js";
4
- import { maskApiKey } from "./utils.js";
5
3
  const CONTEXT7_API_BASE_URL = "https://context7.com/api";
6
- const DEFAULT_TYPE = "txt";
7
4
  /**
8
- * Parses a Context7-compatible library ID into its components
9
- * @param libraryId The library ID (e.g., "/vercel/next.js" or "/vercel/next.js/v14.3.0")
10
- * @returns Object with username, library, and optional tag
11
- */
12
- function parseLibraryId(libraryId) {
13
- // Remove leading slash if present
14
- const cleaned = libraryId.startsWith("/") ? libraryId.slice(1) : libraryId;
15
- const parts = cleaned.split("/");
16
- if (parts.length < 2) {
17
- throw new Error(`Invalid library ID format: ${libraryId}. Expected format: /username/library or /username/library/tag`);
18
- }
19
- return {
20
- username: parts[0],
21
- library: parts[1],
22
- tag: parts[2], // undefined if not present
23
- };
24
- }
25
- /**
26
- * Generates appropriate error messages based on HTTP status codes
27
- * @param errorCode The HTTP error status code
28
- * @param apiKey Optional API key (used for rate limit message)
5
+ * Parses error response from the Context7 API
6
+ * Extracts the server's error message, falling back to status-based messages if parsing fails
7
+ * @param response The fetch Response object
8
+ * @param apiKey Optional API key (used for fallback messages)
29
9
  * @returns Error message string
30
10
  */
31
- function createErrorMessage(errorCode, apiKey) {
32
- switch (errorCode) {
33
- case 429:
34
- return apiKey
35
- ? "Rate limited due to too many requests. Please try again later."
36
- : "Rate limited due to too many requests. You can create a free API key at https://context7.com/dashboard for higher rate limits.";
37
- case 404:
38
- return "The library you are trying to access does not exist. Please try with a different library ID.";
39
- case 401:
40
- if (!apiKey) {
41
- return "Unauthorized. Please provide an API key.";
42
- }
43
- return `Unauthorized. Please check your API key. The API key you provided (possibly incorrect) is: ${maskApiKey(apiKey)}. API keys should start with 'ctx7sk'`;
44
- default:
45
- return `Failed to fetch documentation. Please try again later. Error code: ${errorCode}`;
11
+ async function parseErrorResponse(response, apiKey) {
12
+ try {
13
+ const json = (await response.json());
14
+ if (json.message) {
15
+ return json.message;
16
+ }
17
+ }
18
+ catch {
19
+ // JSON parsing failed, fall through to default
46
20
  }
21
+ // Fallback for non-JSON responses
22
+ const status = response.status;
23
+ if (status === 429) {
24
+ return apiKey
25
+ ? "Rate limited or quota exceeded. Upgrade your plan at https://context7.com/plans for higher limits."
26
+ : "Rate limited or quota exceeded. Create a free API key at https://context7.com/dashboard for higher limits.";
27
+ }
28
+ if (status === 404) {
29
+ return "The library you are trying to access does not exist. Please try with a different library ID.";
30
+ }
31
+ if (status === 401) {
32
+ return "Invalid API key. Please check your API key. API keys should start with 'ctx7sk' prefix.";
33
+ }
34
+ return `Request failed with status ${status}. Please try again later.`;
47
35
  }
48
36
  // Pick up proxy configuration in a variety of common env var names.
49
37
  const PROXY_URL = process.env.HTTPS_PROXY ??
@@ -66,20 +54,21 @@ if (PROXY_URL && !PROXY_URL.startsWith("$") && /^(http|https):\/\//i.test(PROXY_
66
54
  }
67
55
  /**
68
56
  * Searches for libraries matching the given query
69
- * @param query The search query
57
+ * @param query The user's question or task (used for LLM relevance ranking)
58
+ * @param libraryName The library name to search for in the database
70
59
  * @param clientIp Optional client IP address to include in headers
71
60
  * @param apiKey Optional API key for authentication
72
61
  * @returns Search results or null if the request fails
73
62
  */
74
- export async function searchLibraries(query, clientIp, apiKey) {
63
+ export async function searchLibraries(query, libraryName, clientIp, apiKey) {
75
64
  try {
76
- const url = new URL(`${CONTEXT7_API_BASE_URL}/v2/search`);
65
+ const url = new URL(`${CONTEXT7_API_BASE_URL}/v2/libs/search`);
77
66
  url.searchParams.set("query", query);
67
+ url.searchParams.set("libraryName", libraryName);
78
68
  const headers = generateHeaders(clientIp, apiKey);
79
69
  const response = await fetch(url, { headers });
80
70
  if (!response.ok) {
81
- const errorCode = response.status;
82
- const errorMessage = createErrorMessage(errorCode, apiKey);
71
+ const errorMessage = await parseErrorResponse(response, apiKey);
83
72
  console.error(errorMessage);
84
73
  return {
85
74
  results: [],
@@ -96,50 +85,35 @@ export async function searchLibraries(query, clientIp, apiKey) {
96
85
  }
97
86
  }
98
87
  /**
99
- * Fetches documentation context for a specific library
100
- * @param libraryId The library ID to fetch documentation for
101
- * @param docMode Documentation mode (CODE for API references and code examples, INFO for conceptual guides)
102
- * @param options Optional request parameters (page, limit, topic)
88
+ * Fetches intelligent, reranked context for a natural language query
89
+ * @param request The context request parameters (query, topic, library, mode)
103
90
  * @param clientIp Optional client IP address to include in headers
104
91
  * @param apiKey Optional API key for authentication
105
- * @returns The documentation text or null if the request fails
92
+ * @returns Context response with data
106
93
  */
107
- export async function fetchLibraryDocumentation(libraryId, docMode, options = {}, clientIp, apiKey) {
94
+ export async function fetchLibraryContext(request, clientIp, apiKey) {
108
95
  try {
109
- const { username, library, tag } = parseLibraryId(libraryId);
110
- // Build URL path
111
- let urlPath = `${CONTEXT7_API_BASE_URL}/v2/docs/${docMode}/${username}/${library}`;
112
- if (tag) {
113
- urlPath += `/${tag}`;
114
- }
115
- const url = new URL(urlPath);
116
- url.searchParams.set("type", DEFAULT_TYPE);
117
- if (options.topic)
118
- url.searchParams.set("topic", options.topic);
119
- if (options.page)
120
- url.searchParams.set("page", options.page.toString());
121
- if (options.limit)
122
- url.searchParams.set("limit", options.limit.toString());
96
+ const url = new URL(`${CONTEXT7_API_BASE_URL}/v2/context`);
97
+ url.searchParams.set("query", request.query);
98
+ url.searchParams.set("libraryId", request.libraryId);
123
99
  const headers = generateHeaders(clientIp, apiKey, { "X-Context7-Source": "mcp-server" });
124
100
  const response = await fetch(url, { headers });
125
101
  if (!response.ok) {
126
- const errorCode = response.status;
127
- const errorMessage = createErrorMessage(errorCode, apiKey);
102
+ const errorMessage = await parseErrorResponse(response, apiKey);
128
103
  console.error(errorMessage);
129
- return errorMessage;
104
+ return { data: errorMessage };
130
105
  }
131
106
  const text = await response.text();
132
- if (!text || text === "No content available" || text === "No context data available") {
133
- const suggestion = docMode === DOCUMENTATION_MODES.CODE
134
- ? " Try mode='info' for guides and tutorials."
135
- : " Try mode='code' for API references and code examples.";
136
- return `No ${docMode} documentation available for this library.${suggestion}`;
107
+ if (!text) {
108
+ return {
109
+ data: "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.",
110
+ };
137
111
  }
138
- return text;
112
+ return { data: text };
139
113
  }
140
114
  catch (error) {
141
- const errorMessage = `Error fetching library documentation. Please try again later. ${error}`;
115
+ const errorMessage = `Error fetching library context. Please try again later. ${error}`;
142
116
  console.error(errorMessage);
143
- return errorMessage;
117
+ return { data: errorMessage };
144
118
  }
145
119
  }
package/dist/lib/types.js CHANGED
@@ -1,7 +1 @@
1
- /**
2
- * Documentation modes for fetching library documentation
3
- */
4
- export const DOCUMENTATION_MODES = {
5
- CODE: "code",
6
- INFO: "info",
7
- };
1
+ export {};
package/dist/lib/utils.js CHANGED
@@ -58,20 +58,3 @@ export function formatSearchResults(searchResponse) {
58
58
  const formattedResults = searchResponse.results.map(formatSearchResult);
59
59
  return formattedResults.join("\n----------\n");
60
60
  }
61
- /**
62
- * Masks an API key by showing only the first 10 characters and last 4 characters.
63
- * This prevents full API keys from being exposed in logs while maintaining some
64
- * identifiability for debugging.
65
- *
66
- * @param apiKey The API key to mask
67
- * @returns Masked API key string (e.g., "ctx7sk-abc...xyz1") or "[NO-API-KEY]" if no key provided
68
- */
69
- export function maskApiKey(apiKey) {
70
- if (apiKey.length <= 14) {
71
- // If the key is too short to mask meaningfully, just show first part
72
- return apiKey.substring(0, 7) + "...";
73
- }
74
- const firstPart = apiKey.substring(0, 10);
75
- const lastPart = apiKey.substring(apiKey.length - 4);
76
- return `${firstPart}...${lastPart}`;
77
- }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@upstash/context7-mcp",
3
- "version": "1.1.0-canary-20251128121456",
3
+ "version": "2.0.0",
4
4
  "mcpName": "io.github.upstash/context7",
5
5
  "description": "MCP server for Context7",
6
6
  "repository": {
@@ -46,13 +46,14 @@
46
46
  },
47
47
  "scripts": {
48
48
  "build": "tsc && chmod 755 dist/index.js",
49
- "test": "echo \"Error: no test specified\" && exit 1",
49
+ "test": "echo \"No tests yet\"",
50
+ "typecheck": "tsc --noEmit",
50
51
  "lint": "eslint .",
51
52
  "lint:check": "eslint .",
52
53
  "format": "prettier --write .",
53
54
  "format:check": "prettier --check .",
54
55
  "dev": "tsc --watch",
55
56
  "start": "node dist/index.js --transport http",
56
- "pack-mcpb": "pnpm install && pnpm run build && rm -rf node_modules && pnpm install --prod && 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"
57
+ "pack-mcpb": "pnpm install && pnpm run build && rm -rf node_modules && pnpm install --prod && cp mcpb/manifest.json manifest.json && cp ../../public/icon.png icon.png && mcpb validate manifest.json && mcpb pack . mcpb/context7.mcpb && rm manifest.json icon.png && pnpm install"
57
58
  }
58
59
  }