@upstash/context7-mcp 2.2.3 → 2.2.5

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/dist/index.js CHANGED
@@ -89,35 +89,26 @@ function getClientIp(req) {
89
89
  }
90
90
  return undefined;
91
91
  }
92
- const server = new McpServer({
93
- name: "Context7",
94
- version: SERVER_VERSION,
95
- websiteUrl: "https://context7.com",
96
- description: "Context7 provides up-to-date documentation and code examples for libraries and frameworks.",
97
- icons: [
98
- {
99
- src: "https://context7.com/context7-icon-green.png",
100
- mimeType: "image/png",
101
- },
102
- ],
103
- }, {
104
- instructions: `Use this server to fetch current documentation whenever the user asks about a library, framework, SDK, API, CLI tool, or cloud service -- even well-known ones like React, Next.js, Prisma, Express, Tailwind, Django, or Spring Boot. This includes API syntax, configuration, version migration, library-specific debugging, setup instructions, and CLI tool usage. Use even when you think you know the answer -- your training data may not reflect recent changes. Prefer this over web search for library docs.
92
+ function createMcpServer() {
93
+ const server = new McpServer({
94
+ name: "Context7",
95
+ version: SERVER_VERSION,
96
+ websiteUrl: "https://context7.com",
97
+ description: "Context7 provides up-to-date documentation and code examples for libraries and frameworks.",
98
+ icons: [
99
+ {
100
+ src: "https://context7.com/context7-icon-green.png",
101
+ mimeType: "image/png",
102
+ },
103
+ ],
104
+ }, {
105
+ instructions: `Use this server to fetch current documentation whenever the user asks about a library, framework, SDK, API, CLI tool, or cloud service -- even well-known ones like React, Next.js, Prisma, Express, Tailwind, Django, or Spring Boot. This includes API syntax, configuration, version migration, library-specific debugging, setup instructions, and CLI tool usage. Use even when you think you know the answer -- your training data may not reflect recent changes. Prefer this over web search for library docs.
105
106
 
106
107
  Do not use for: refactoring, writing scripts from scratch, debugging business logic, code review, or general programming concepts.`,
107
- });
108
- // Capture client info from MCP initialize handshake
109
- server.server.oninitialized = () => {
110
- const clientVersion = server.server.getClientVersion();
111
- if (clientVersion) {
112
- stdioClientInfo = {
113
- ide: clientVersion.name,
114
- version: clientVersion.version,
115
- };
116
- }
117
- };
118
- server.registerTool("resolve-library-id", {
119
- title: "Resolve Context7 Library ID",
120
- description: `Resolves a package/product name to a Context7-compatible library ID and returns matching libraries.
108
+ });
109
+ server.registerTool("resolve-library-id", {
110
+ title: "Resolve Context7 Library ID",
111
+ description: `Resolves a package/product name to a Context7-compatible library ID and returns matching libraries.
121
112
 
122
113
  You MUST call this function before 'Query Documentation' tool 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.
123
114
 
@@ -150,99 +141,70 @@ Response Format:
150
141
  For ambiguous queries, request clarification before proceeding with a best-guess match.
151
142
 
152
143
  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.`,
153
- inputSchema: {
154
- query: z
155
- .string()
156
- .describe("The question or task you need help with. This is used to rank library results by relevance to what the user is trying to accomplish. The query is sent to the Context7 API for processing. Do not include any sensitive or confidential information such as API keys, passwords, credentials, personal data, or proprietary code in your query."),
157
- libraryName: z
158
- .string()
159
- .describe("Library name to search for and retrieve a Context7-compatible library ID. Use the official library name with proper punctuation — e.g., 'Next.js' instead of 'nextjs', 'Customer.io' instead of 'customerio', 'Three.js' instead of 'threejs'."),
160
- },
161
- annotations: {
162
- readOnlyHint: true,
163
- destructiveHint: false,
164
- openWorldHint: true,
165
- idempotentHint: true,
166
- },
167
- }, async ({ query, libraryName }) => {
168
- const searchResponse = await searchLibraries(query, libraryName, getClientContext());
169
- if (!searchResponse.results || searchResponse.results.length === 0) {
144
+ inputSchema: {
145
+ query: z
146
+ .string()
147
+ .describe("The question or task you need help with. This is used to rank library results by relevance to what the user is trying to accomplish. The query is sent to the Context7 API for processing. Do not include any sensitive or confidential information such as API keys, passwords, credentials, personal data, or proprietary code in your query."),
148
+ libraryName: z
149
+ .string()
150
+ .describe("Library name to search for and retrieve a Context7-compatible library ID. Use the official library name with proper punctuation — e.g., 'Next.js' instead of 'nextjs', 'Customer.io' instead of 'customerio', 'Three.js' instead of 'threejs'."),
151
+ },
152
+ annotations: {
153
+ readOnlyHint: true,
154
+ destructiveHint: false,
155
+ openWorldHint: true,
156
+ idempotentHint: true,
157
+ },
158
+ }, async ({ query, libraryName }) => {
159
+ const searchResponse = await searchLibraries(query, libraryName, getClientContext());
160
+ if (!searchResponse.results || searchResponse.results.length === 0) {
161
+ return {
162
+ content: [
163
+ {
164
+ type: "text",
165
+ text: searchResponse.error
166
+ ? searchResponse.error
167
+ : "No libraries found matching the provided name.",
168
+ },
169
+ ],
170
+ };
171
+ }
172
+ const resultsText = formatSearchResults(searchResponse);
173
+ const responseText = `Available Libraries:
174
+
175
+ ${resultsText}`;
170
176
  return {
171
177
  content: [
172
178
  {
173
179
  type: "text",
174
- text: searchResponse.error
175
- ? searchResponse.error
176
- : "No libraries found matching the provided name.",
180
+ text: responseText,
177
181
  },
178
182
  ],
179
183
  };
180
- }
181
- const resultsText = formatSearchResults(searchResponse);
182
- const responseText = `Available Libraries:
183
-
184
- ${resultsText}`;
185
- return {
186
- content: [
187
- {
188
- type: "text",
189
- text: responseText,
190
- },
191
- ],
192
- };
193
- });
194
- server.registerTool("query-docs", {
195
- title: "Query Documentation",
196
- description: `Retrieves and queries up-to-date documentation and code examples from Context7 for any programming library or framework.
184
+ });
185
+ server.registerTool("query-docs", {
186
+ title: "Query Documentation",
187
+ description: `Retrieves and queries up-to-date documentation and code examples from Context7 for any programming library or framework.
197
188
 
198
189
  You must call 'Resolve Context7 Library ID' tool 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.
199
190
 
200
- Workflow: call first without researchMode. If that doesn't answer the question, retry with researchMode: true. Do not call each tool more than 3 times per question.`,
201
- inputSchema: {
202
- libraryId: z
203
- .string()
204
- .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'."),
205
- query: z
206
- .string()
207
- .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'. The query is sent to the Context7 API for processing. Do not include any sensitive or confidential information such as API keys, passwords, credentials, personal data, or proprietary code in your query."),
208
- researchMode: z
209
- .boolean()
210
- .optional()
211
- .describe(`Retry the query with deep research: spins up sandboxed agents that read the actual source repos and runs a live web search, then synthesizes a fresh answer. Set true on retry if you weren't satisfied with the first answer and want a more thorough one. Requires an API key. You can get one free at https://context7.com.`),
212
- },
213
- annotations: {
214
- readOnlyHint: true,
215
- destructiveHint: false,
216
- openWorldHint: true,
217
- idempotentHint: true,
218
- },
219
- }, async ({ query, libraryId, researchMode }, { sendNotification, _meta }) => {
220
- // Emit periodic progress notifications while the upstream call is in flight.
221
- // MCP clients that opt into resetTimeoutOnProgress (e.g. opencode) reset their
222
- // request timer on each notification, which keeps long-running tools (notably
223
- // researchMode) alive past the SDK's default 60s wall-clock timeout. Clients
224
- // that don't pass a progressToken simply never see these, so behavior is unchanged.
225
- const progressToken = _meta?.progressToken;
226
- let progressInterval;
227
- if (researchMode && progressToken !== undefined) {
228
- let progress = 0;
229
- progressInterval = setInterval(() => {
230
- progress += 1;
231
- sendNotification({
232
- method: "notifications/progress",
233
- params: {
234
- progressToken,
235
- progress,
236
- message: "Researching documentation...",
237
- },
238
- }).catch(() => {
239
- // Notifications are best-effort; swallow transport errors so the tool
240
- // call itself isn't aborted by a notification write failure.
241
- });
242
- }, 20_000);
243
- }
244
- try {
245
- const response = await fetchLibraryContext({ query, libraryId, researchMode }, getClientContext());
191
+ Do not call this tool more than 3 times per question.`,
192
+ inputSchema: {
193
+ libraryId: z
194
+ .string()
195
+ .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'."),
196
+ query: z
197
+ .string()
198
+ .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'. The query is sent to the Context7 API for processing. Do not include any sensitive or confidential information such as API keys, passwords, credentials, personal data, or proprietary code in your query."),
199
+ },
200
+ annotations: {
201
+ readOnlyHint: true,
202
+ destructiveHint: false,
203
+ openWorldHint: true,
204
+ idempotentHint: true,
205
+ },
206
+ }, async ({ query, libraryId }) => {
207
+ const response = await fetchLibraryContext({ query, libraryId }, getClientContext());
246
208
  return {
247
209
  content: [
248
210
  {
@@ -251,12 +213,52 @@ Workflow: call first without researchMode. If that doesn't answer the question,
251
213
  },
252
214
  ],
253
215
  };
216
+ });
217
+ return server;
218
+ }
219
+ const GLOBAL_ALIASES = {
220
+ query: ["userQuery", "question"],
221
+ };
222
+ // Tool-scoped aliases, for keys that are canonical on one tool but a
223
+ // hallucination on another (e.g. `libraryName` is canonical for
224
+ // `resolve-library-id`, so we only rewrite it on `query-docs` calls).
225
+ const TOOL_ALIASES = {
226
+ "query-docs": {
227
+ libraryId: ["context7CompatibleLibraryID", "libraryID", "libraryName"],
228
+ },
229
+ };
230
+ function applyAliases(args, aliases) {
231
+ for (const [canonical, alternatives] of Object.entries(aliases)) {
232
+ if (canonical in args)
233
+ continue;
234
+ for (const alt of alternatives) {
235
+ if (alt in args) {
236
+ args[canonical] = args[alt];
237
+ delete args[alt];
238
+ break;
239
+ }
240
+ }
254
241
  }
255
- finally {
256
- if (progressInterval)
257
- clearInterval(progressInterval);
258
- }
259
- });
242
+ }
243
+ // Install BEFORE `server.connect(transport)`: the SDK's `Protocol.connect()`
244
+ // captures the existing `onmessage` and chains its dispatch handler over it,
245
+ // so our hook runs first on every incoming JSON-RPC message.
246
+ function installTransportArgAliasing(transport) {
247
+ transport.onmessage = (message) => {
248
+ const msg = message;
249
+ if (msg.method !== "tools/call")
250
+ return;
251
+ const args = msg.params?.arguments;
252
+ if (!args || typeof args !== "object")
253
+ return;
254
+ const argsRecord = args;
255
+ applyAliases(argsRecord, GLOBAL_ALIASES);
256
+ const toolName = msg.params?.name;
257
+ if (toolName && toolName in TOOL_ALIASES) {
258
+ applyAliases(argsRecord, TOOL_ALIASES[toolName]);
259
+ }
260
+ };
261
+ }
260
262
  async function main() {
261
263
  const transportType = TRANSPORT_TYPE;
262
264
  if (transportType === "http") {
@@ -346,16 +348,19 @@ async function main() {
346
348
  // Use SSE responses for tool calls (enableJsonResponse: false). The SDK then
347
349
  // flushes response headers immediately after parsing the request rather than
348
350
  // buffering until the tool returns. This is required for long-running tools
349
- // (e.g. researchMode) because some MCP HTTP clients cap the underlying fetch
350
- // at 60s waiting for headers, even though the per-tool timeout is much higher.
351
+ // because some MCP HTTP clients cap the underlying fetch at 60s waiting for
352
+ // headers, even though the per-tool timeout is much higher.
351
353
  const transport = new StreamableHTTPServerTransport({
352
354
  sessionIdGenerator: undefined,
353
355
  enableJsonResponse: false,
354
356
  });
357
+ const server = createMcpServer();
355
358
  res.on("close", () => {
356
359
  transport.close();
360
+ server.close();
357
361
  });
358
362
  await requestContext.run(context, async () => {
363
+ installTransportArgAliasing(transport);
359
364
  await server.connect(transport);
360
365
  await transport.handleRequest(req, res, req.body);
361
366
  });
@@ -451,7 +456,23 @@ async function main() {
451
456
  }
452
457
  else {
453
458
  stdioApiKey = cliOptions.apiKey || process.env.CONTEXT7_API_KEY;
459
+ process.stdin.on("end", () => process.exit(0));
460
+ process.stdin.on("close", () => process.exit(0));
461
+ process.on("SIGHUP", () => process.exit(0));
454
462
  const transport = new StdioServerTransport();
463
+ const server = createMcpServer();
464
+ // Capture client info from MCP initialize handshake (stdio only — HTTP
465
+ // mode plumbs client info through requestContext per request).
466
+ server.server.oninitialized = () => {
467
+ const clientVersion = server.server.getClientVersion();
468
+ if (clientVersion) {
469
+ stdioClientInfo = {
470
+ ide: clientVersion.name,
471
+ version: clientVersion.version,
472
+ };
473
+ }
474
+ };
475
+ installTransportArgAliasing(transport);
455
476
  await server.connect(transport);
456
477
  console.error(`Context7 Documentation MCP Server v${SERVER_VERSION} running on stdio`);
457
478
  }
package/dist/lib/api.js CHANGED
@@ -120,8 +120,6 @@ export async function fetchLibraryContext(request, context = {}) {
120
120
  const url = new URL(`${CONTEXT7_API_BASE_URL}/v2/context`);
121
121
  url.searchParams.set("query", request.query);
122
122
  url.searchParams.set("libraryId", request.libraryId);
123
- if (request.researchMode)
124
- url.searchParams.set("researchMode", "true");
125
123
  const headers = generateHeaders(context);
126
124
  const response = await fetch(url, { headers });
127
125
  if (!response.ok) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@upstash/context7-mcp",
3
- "version": "2.2.3",
3
+ "version": "2.2.5",
4
4
  "mcpName": "io.github.upstash/context7",
5
5
  "description": "MCP server for Context7",
6
6
  "repository": {