@upstash/context7-mcp 1.0.32 → 1.0.34-canary.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.
@@ -0,0 +1,319 @@
1
+ import "dotenv/config";
2
+ import { experimental_createMCPClient as createMCPClient } from "@ai-sdk/mcp";
3
+ import { generateText, stepCountIs } from "ai";
4
+ import { anthropic } from "@ai-sdk/anthropic";
5
+ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
6
+ import { writeFileSync, mkdirSync } from "fs";
7
+ import { join, dirname } from "path";
8
+ import { fileURLToPath } from "url";
9
+ // Check for required environment variables
10
+ if (!process.env.CONTEXT7_API_KEY) {
11
+ console.error("Error: CONTEXT7_API_KEY environment variable is required");
12
+ console.error("Set it in your .env file or export it in your shell");
13
+ process.exit(1);
14
+ }
15
+ const __filename = fileURLToPath(import.meta.url);
16
+ const __dirname = dirname(__filename);
17
+ // Package root is two levels up from dist/benchmark/
18
+ const packageRoot = join(__dirname, "..", "..");
19
+ /**
20
+ * Creates a logging function bound to a specific simulation state
21
+ */
22
+ function createLogger(state) {
23
+ return (message) => {
24
+ console.log(message);
25
+ state.consoleLog += message + "\n";
26
+ };
27
+ }
28
+ /**
29
+ * Simulates an LLM-powered conversation using Context7 MCP server
30
+ *
31
+ * Usage:
32
+ * - CLI: pnpm run simulate "your question here"
33
+ * - Programmatic: import { simulate } from './simulate.js'; await simulate("your question");
34
+ */
35
+ export async function simulate(question, uniqueId) {
36
+ // Create local state for this simulation (prevents race conditions)
37
+ const state = {
38
+ consoleLog: "",
39
+ toolCallsMap: new Map(),
40
+ totalTokens: 0,
41
+ };
42
+ // Create logger bound to this simulation's state
43
+ const log = createLogger(state);
44
+ // If no question provided, try to get from command line args
45
+ if (!question) {
46
+ question = process.argv[2];
47
+ }
48
+ if (!question) {
49
+ console.error("Error: Please provide a question as an argument");
50
+ console.error('Usage: pnpm run simulate "your question here"');
51
+ process.exit(1);
52
+ }
53
+ // Store original question for display
54
+ const originalQuestion = question;
55
+ // Append Context7 instruction to the question
56
+ question = `${question}. Use Context7 to fetch docs.`;
57
+ const startTime = new Date();
58
+ log("=".repeat(80));
59
+ log("Context7 MCP Simulation");
60
+ log("=".repeat(80));
61
+ log(`Question: ${originalQuestion}`);
62
+ log(`Timestamp: ${startTime.toISOString()}`);
63
+ log("");
64
+ let mcpClient = null;
65
+ try {
66
+ // Get API key from environment
67
+ const apiKey = process.env.CONTEXT7_API_KEY;
68
+ if (!apiKey) {
69
+ throw new Error("CONTEXT7_API_KEY environment variable is required");
70
+ }
71
+ // Initialize MCP client using AI SDK's experimental_createMCPClient
72
+ log("─".repeat(80));
73
+ log("Initializing MCP Connection");
74
+ log("─".repeat(80));
75
+ const mcpServerPath = join(packageRoot, "dist", "index.js");
76
+ mcpClient = await createMCPClient({
77
+ transport: new StdioClientTransport({
78
+ command: "node",
79
+ args: [mcpServerPath, "--api-key", apiKey],
80
+ env: {
81
+ ...process.env,
82
+ CONTEXT7_API_KEY: apiKey,
83
+ },
84
+ }),
85
+ });
86
+ log("✅ Connected to Context7 MCP server");
87
+ log("");
88
+ // Get AI SDK-compatible tools
89
+ const tools = await mcpClient.tools();
90
+ const toolNames = Object.keys(tools);
91
+ log(`📦 Available tools: ${toolNames.join(", ")}`);
92
+ log("");
93
+ // Use AI model to interact with tools
94
+ log("─".repeat(80));
95
+ log("🤖 AI Processing");
96
+ log("─".repeat(80));
97
+ log("Model: Claude Haiku 4.5");
98
+ log("");
99
+ const model = anthropic("claude-haiku-4-5");
100
+ const result = await generateText({
101
+ model,
102
+ tools,
103
+ stopWhen: stepCountIs(15),
104
+ messages: [
105
+ {
106
+ role: "user",
107
+ content: question,
108
+ },
109
+ ],
110
+ onStepFinish: (step) => {
111
+ if (step.toolCalls) {
112
+ step.toolCalls.forEach((tc) => {
113
+ const input = tc.input || tc.args || {};
114
+ log("─".repeat(80));
115
+ log(`🔧 Tool Call: ${tc.toolName}`);
116
+ log("─".repeat(80));
117
+ log(`Arguments:`);
118
+ log("```json");
119
+ log(JSON.stringify(input, null, 2));
120
+ log("```");
121
+ log("");
122
+ // Store for structured report using toolCallId as key
123
+ state.toolCallsMap.set(tc.toolCallId, {
124
+ tool: tc.toolName,
125
+ args: input,
126
+ response: null,
127
+ });
128
+ });
129
+ }
130
+ if (step.toolResults) {
131
+ step.toolResults.forEach((tr) => {
132
+ const resultText = JSON.stringify(tr, null, 2);
133
+ log(`Result:`);
134
+ log("```json");
135
+ if (resultText.length > 2000) {
136
+ log(resultText.substring(0, 2000));
137
+ log(`... (truncated ${resultText.length - 2000} characters)`);
138
+ }
139
+ else {
140
+ log(resultText);
141
+ }
142
+ log("```");
143
+ log("");
144
+ // Store response for structured report by matching toolCallId
145
+ const toolCall = state.toolCallsMap.get(tr.toolCallId);
146
+ if (toolCall) {
147
+ const output = tr.output || tr;
148
+ toolCall.response = output;
149
+ // Extract usage data from MCP response structure
150
+ try {
151
+ // Check if output has MCP format: {content: [{type: "text", text: "..."}]}
152
+ if (output && output.content && Array.isArray(output.content)) {
153
+ output.content.forEach((item) => {
154
+ if (item.type === "text" && item.text) {
155
+ try {
156
+ const parsed = JSON.parse(item.text);
157
+ // Check for new API format: {data: "...", usage: {...}}
158
+ if (parsed.usage && parsed.usage.totalTokens) {
159
+ toolCall.usage = parsed.usage;
160
+ state.totalTokens += parsed.usage.totalTokens;
161
+ log(`Token usage: ${parsed.usage.totalTokens} total (input: ${parsed.usage.inputTokens}, output: ${parsed.usage.outputTokens})`);
162
+ log("");
163
+ }
164
+ }
165
+ catch {
166
+ // Not JSON or doesn't have usage data
167
+ }
168
+ }
169
+ });
170
+ }
171
+ }
172
+ catch {
173
+ // If parsing fails, it's not JSON or doesn't have usage data, which is fine
174
+ }
175
+ }
176
+ });
177
+ }
178
+ },
179
+ });
180
+ log("─".repeat(80));
181
+ log("✅ AI Final Response");
182
+ log("─".repeat(80));
183
+ log(result.text);
184
+ log("");
185
+ const endTime = new Date();
186
+ const duration = endTime.getTime() - startTime.getTime();
187
+ // Summary
188
+ log("=".repeat(80));
189
+ log("Summary");
190
+ log("=".repeat(80));
191
+ log(`✅ Simulation completed in ${(duration / 1000).toFixed(2)}s`);
192
+ log(`⏱️ Start: ${startTime.toISOString()}`);
193
+ log(`⏱️ End: ${endTime.toISOString()}`);
194
+ if (state.totalTokens > 0) {
195
+ log(`🪙 Total Tokens: ${state.totalTokens}`);
196
+ }
197
+ log("=".repeat(80));
198
+ // Create reports directory under benchmark folder
199
+ const reportsDir = join(packageRoot, "src", "benchmark", "reports");
200
+ mkdirSync(reportsDir, { recursive: true });
201
+ // Generate filenames with timestamp and optional unique identifier
202
+ const baseFilename = uniqueId
203
+ ? `${startTime.toISOString().replace(/[:.]/g, "-").split("Z")[0]}_${uniqueId}`
204
+ : startTime.toISOString().replace(/[:.]/g, "-").split("Z")[0];
205
+ const reportPath = join(reportsDir, `${baseFilename}.md`);
206
+ const rawReportPath = join(reportsDir, `${baseFilename}_raw.md`);
207
+ // Convert Map to array for report generation (preserves insertion order)
208
+ const structuredToolCalls = Array.from(state.toolCallsMap.values());
209
+ // Generate structured markdown report
210
+ let structuredReport = `# Context7 MCP Simulation Report\n\n`;
211
+ structuredReport += `**Date**: ${startTime.toISOString()}\n`;
212
+ structuredReport += `**Duration**: ${(duration / 1000).toFixed(2)}s\n`;
213
+ if (state.totalTokens > 0) {
214
+ structuredReport += `**Total Tokens**: ${state.totalTokens}\n`;
215
+ }
216
+ structuredReport += `\n## Question\n\n${originalQuestion}\n\n`;
217
+ // Add tool calls and responses
218
+ if (structuredToolCalls.length > 0) {
219
+ structuredReport += `## Tool Calls\n\n`;
220
+ structuredToolCalls.forEach((call, idx) => {
221
+ structuredReport += `### Tool Call ${idx + 1}: ${call.tool}\n\n`;
222
+ structuredReport += `**Parameters:**\n\`\`\`json\n${JSON.stringify(call.args, null, 2)}\n\`\`\`\n\n`;
223
+ if (call.usage) {
224
+ structuredReport += `**Token Usage:**\n`;
225
+ structuredReport += `- Input: ${call.usage.inputTokens}\n`;
226
+ structuredReport += `- Output: ${call.usage.outputTokens}\n`;
227
+ structuredReport += `- Total: ${call.usage.totalTokens}\n\n`;
228
+ }
229
+ structuredReport += `**Response:**\n\`\`\`json\n`;
230
+ const responseText = JSON.stringify(call.response, null, 2);
231
+ if (responseText.length > 3000) {
232
+ structuredReport +=
233
+ responseText.substring(0, 3000) +
234
+ `...\n(truncated ${responseText.length - 3000} characters)\n`;
235
+ }
236
+ else {
237
+ structuredReport += responseText + "\n";
238
+ }
239
+ structuredReport += `\`\`\`\n\n`;
240
+ });
241
+ }
242
+ // Add final AI response
243
+ structuredReport += `## AI Final Response\n\n${result.text}\n\n`;
244
+ // Add console log section
245
+ structuredReport += `---\n\n## Console Log\n\n\`\`\`\n${state.consoleLog}\n\`\`\`\n`;
246
+ // Save structured markdown report
247
+ writeFileSync(reportPath, structuredReport);
248
+ // Generate raw markdown report (just question and raw text responses)
249
+ let rawReport = `QUESTION: ${originalQuestion}\n\n`;
250
+ rawReport += `${"─".repeat(80)}\n\n`;
251
+ rawReport += `CONTEXT:\n\n`;
252
+ // Add raw tool responses (extract text content only, skip resolve-library-id)
253
+ structuredToolCalls.forEach((call) => {
254
+ // Skip resolve-library-id tool responses
255
+ if (call.tool === "resolve-library-id") {
256
+ return;
257
+ }
258
+ if (call.response && call.response.content) {
259
+ const content = call.response.content;
260
+ // Extract text from MCP response format: {content: [{type: "text", text: "..."}]}
261
+ if (Array.isArray(content)) {
262
+ content.forEach((item) => {
263
+ if (item.type === "text" && item.text) {
264
+ // Try to parse the text as JSON (new API format with {data, usage})
265
+ try {
266
+ const parsed = JSON.parse(item.text);
267
+ // New API format: {data: "text", usage: {...}}
268
+ if (parsed.data && typeof parsed.data === "string") {
269
+ rawReport += parsed.data + "\n\n";
270
+ return;
271
+ }
272
+ }
273
+ catch {
274
+ // Not JSON, treat as plain text (legacy format)
275
+ rawReport += item.text + "\n\n";
276
+ }
277
+ }
278
+ });
279
+ }
280
+ else if (typeof content === "string") {
281
+ // Try to parse string content as JSON
282
+ try {
283
+ const parsed = JSON.parse(content);
284
+ if (parsed.data && typeof parsed.data === "string") {
285
+ rawReport += parsed.data + "\n\n";
286
+ return;
287
+ }
288
+ }
289
+ catch {
290
+ // Not JSON, treat as plain text
291
+ rawReport += content + "\n\n";
292
+ }
293
+ }
294
+ }
295
+ });
296
+ // Save raw markdown report
297
+ writeFileSync(rawReportPath, rawReport);
298
+ log("");
299
+ log(`📝 Structured report saved to: ${reportPath}`);
300
+ log(`📝 Raw report saved to: ${rawReportPath}`);
301
+ }
302
+ catch (error) {
303
+ const errorMsg = `❌ Simulation failed: ${error}`;
304
+ log(errorMsg);
305
+ process.exit(1);
306
+ }
307
+ finally {
308
+ if (mcpClient) {
309
+ await mcpClient.close();
310
+ }
311
+ }
312
+ }
313
+ // Run simulation if this file is executed directly
314
+ if (import.meta.url === `file://${process.argv[1]}`) {
315
+ simulate().catch((error) => {
316
+ console.error("Fatal error:", error);
317
+ process.exit(1);
318
+ });
319
+ }
package/dist/index.js CHANGED
@@ -2,15 +2,11 @@
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";
6
- import { formatSearchResults } from "./lib/utils.js";
7
- import { DOCUMENTATION_MODES } from "./lib/types.js";
5
+ import { fetchLibraryContext } from "./lib/api.js";
8
6
  import express from "express";
9
7
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
10
8
  import { Command } from "commander";
11
9
  import { AsyncLocalStorage } from "async_hooks";
12
- /** Default number of results to return per page */
13
- const DEFAULT_RESULTS_LIMIT = 10;
14
10
  /** Default HTTP server port */
15
11
  const DEFAULT_PORT = 3000;
16
12
  // Parse CLI arguments using commander
@@ -48,147 +44,62 @@ const CLI_PORT = (() => {
48
44
  const requestContext = new AsyncLocalStorage();
49
45
  // Store API key globally for stdio mode (where requestContext may not be available in tool handlers)
50
46
  let globalApiKey;
47
+ const stripIpv6Prefix = (ip) => ip.replace(/^::ffff:/, "");
48
+ const isPrivateIp = (ip) => ip.startsWith("10.") || ip.startsWith("192.168.") || /^172\.(1[6-9]|2[0-9]|3[0-1])\./.test(ip);
51
49
  function getClientIp(req) {
52
- const forwardedFor = req.headers["x-forwarded-for"] || req.headers["X-Forwarded-For"];
50
+ const forwardedFor = req.headers["x-forwarded-for"];
53
51
  if (forwardedFor) {
54
52
  const ips = Array.isArray(forwardedFor) ? forwardedFor[0] : forwardedFor;
55
- const ipList = ips.split(",").map((ip) => ip.trim());
56
- for (const ip of ipList) {
57
- const plainIp = ip.replace(/^::ffff:/, "");
58
- if (!plainIp.startsWith("10.") &&
59
- !plainIp.startsWith("192.168.") &&
60
- !/^172\.(1[6-9]|2[0-9]|3[0-1])\./.test(plainIp)) {
61
- return plainIp;
62
- }
63
- }
64
- return ipList[0].replace(/^::ffff:/, "");
65
- }
66
- if (req.socket?.remoteAddress) {
67
- return req.socket.remoteAddress.replace(/^::ffff:/, "");
53
+ const ipList = ips.split(",").map((ip) => stripIpv6Prefix(ip.trim()));
54
+ return ipList.find((ip) => !isPrivateIp(ip)) ?? ipList[0];
68
55
  }
69
- return undefined;
56
+ return req.socket?.remoteAddress ? stripIpv6Prefix(req.socket.remoteAddress) : undefined;
70
57
  }
71
58
  const server = new McpServer({
72
59
  name: "Context7",
73
- version: "1.0.13",
60
+ version: "2.0.0",
74
61
  }, {
62
+ capabilities: {
63
+ tools: { listChanged: true },
64
+ },
75
65
  instructions: "Use this server to retrieve up-to-date documentation and code examples for any library.",
76
66
  });
77
- server.registerTool("resolve-library-id", {
78
- 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.
80
-
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.
67
+ server.registerTool("get-docs", {
68
+ title: "Get Library Documentation",
69
+ description: `Retrieves up-to-date documentation and code examples from Context7 for any programming library or framework.
82
70
 
83
- Selection Process:
84
- 1. Analyze the query to understand what library/package the user is looking for
85
- 2. Return the most relevant match based on:
86
- - Name similarity to the query (exact matches prioritized)
87
- - Description relevance to the query's intent
88
- - Documentation coverage (prioritize libraries with higher Code Snippet counts)
89
- - Source reputation (consider libraries with High or Medium reputation more authoritative)
90
- - Benchmark Score: Quality indicator (100 is the highest score)
91
-
92
- Response Format:
93
- - Return the selected library ID in a clearly marked section
94
- - Provide a brief explanation for why this library was chosen
95
- - If multiple good matches exist, acknowledge this but proceed with the most relevant one
96
- - If no good matches exist, clearly state this and suggest query refinements
97
-
98
- For ambiguous queries, request clarification before proceeding with a best-guess match.`,
71
+ USE THIS TOOL TO:
72
+ - Get current, accurate documentation for libraries (e.g., React, Next.js, Express, LangChain)
73
+ - Find working code examples and implementation patterns
74
+ - Answer "how do I..." questions about specific libraries
75
+ - Look up API references, configuration options, and best practices`,
99
76
  inputSchema: {
100
- libraryName: z
77
+ query: z
101
78
  .string()
102
- .describe("Library name to search for and retrieve a Context7-compatible library ID."),
103
- },
104
- }, async ({ libraryName }) => {
105
- const ctx = requestContext.getStore();
106
- const searchResponse = await searchLibraries(libraryName, ctx?.clientIp, ctx?.apiKey);
107
- if (!searchResponse.results || searchResponse.results.length === 0) {
108
- return {
109
- content: [
110
- {
111
- type: "text",
112
- text: searchResponse.error
113
- ? searchResponse.error
114
- : "Failed to retrieve library documentation data from Context7",
115
- },
116
- ],
117
- };
118
- }
119
- const resultsText = formatSearchResults(searchResponse);
120
- const responseText = `Available Libraries:
121
-
122
- Each result includes:
123
- - Library ID: Context7-compatible identifier (format: /org/project)
124
- - Name: Library or package name
125
- - Description: Short summary
126
- - Code Snippets: Number of available code examples
127
- - Source Reputation: Authority indicator (High, Medium, Low, or Unknown)
128
- - Benchmark Score: Quality indicator (100 is the highest score)
129
- - 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.
130
-
131
- For best results, select libraries based on name match, source reputation, snippet coverage, benchmark score, and relevance to your use case.
132
-
133
- ----------
134
-
135
- ${resultsText}`;
136
- return {
137
- content: [
138
- {
139
- type: "text",
140
- text: responseText,
141
- },
142
- ],
143
- };
144
- });
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
- inputSchema: {
149
- context7CompatibleLibraryID: z
79
+ .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."),
80
+ library: z
150
81
  .string()
151
- .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
82
  .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."),
83
+ .describe("Library or framework name (e.g., 'react', 'express') OR exact library ID if provided by the user with or without version (e.g., '/vercel/next.js', '/vercel/next.js@v14.3.0-canary.87'). If omitted, auto-selects based on query."),
157
84
  topic: z
158
85
  .string()
159
86
  .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)
87
+ .describe("Narrow down results to a specific topic within the library. Examples: 'hooks', 'routing', 'middleware', 'authentication', 'state management'."),
88
+ mode: z
89
+ .enum(["code", "info"])
166
90
  .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."),
91
+ .default("code")
92
+ .describe("Type of content to prioritize. Use 'code' (default) when you need working code examples, API usage patterns, and implementation snippets. Use 'info' when you need conceptual narrative explanations, architectural overviews, or understanding how something works."),
168
93
  },
169
- }, async ({ context7CompatibleLibraryID, mode = DOCUMENTATION_MODES.CODE, page = 1, topic }) => {
94
+ }, async ({ query, library, topic, mode = "code" }) => {
170
95
  const ctx = requestContext.getStore();
171
96
  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
- }
97
+ const response = await fetchLibraryContext({ query, library, topic, mode }, ctx?.clientIp, apiKey);
187
98
  return {
188
99
  content: [
189
100
  {
190
101
  type: "text",
191
- text: fetchDocsResponse,
102
+ text: response.data,
192
103
  },
193
104
  ],
194
105
  };
@@ -197,7 +108,6 @@ async function main() {
197
108
  const transportType = TRANSPORT_TYPE;
198
109
  if (transportType === "http") {
199
110
  const initialPort = CLI_PORT ?? DEFAULT_PORT;
200
- let actualPort = initialPort;
201
111
  const app = express();
202
112
  app.use(express.json());
203
113
  app.use((req, res, next) => {
@@ -227,14 +137,8 @@ async function main() {
227
137
  };
228
138
  const extractApiKey = (req) => {
229
139
  return (extractBearerToken(req.headers.authorization) ||
230
- extractHeaderValue(req.headers["Context7-API-Key"]) ||
231
- extractHeaderValue(req.headers["X-API-Key"]) ||
232
140
  extractHeaderValue(req.headers["context7-api-key"]) ||
233
- extractHeaderValue(req.headers["x-api-key"]) ||
234
- extractHeaderValue(req.headers["Context7_API_Key"]) ||
235
- extractHeaderValue(req.headers["X_API_Key"]) ||
236
- extractHeaderValue(req.headers["context7_api_key"]) ||
237
- extractHeaderValue(req.headers["x_api_key"]));
141
+ extractHeaderValue(req.headers["x-api-key"]));
238
142
  };
239
143
  app.all("/mcp", async (req, res) => {
240
144
  try {
@@ -286,8 +190,7 @@ async function main() {
286
190
  }
287
191
  });
288
192
  httpServer.once("listening", () => {
289
- actualPort = port;
290
- console.error(`Context7 Documentation MCP Server running on HTTP at http://localhost:${actualPort}/mcp`);
193
+ console.error(`Context7 Documentation MCP Server running on HTTP at http://localhost:${port}/mcp`);
291
194
  });
292
195
  };
293
196
  startServer(initialPort);