@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 +138 -117
- package/dist/lib/api.js +0 -2
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -89,35 +89,26 @@ function getClientIp(req) {
|
|
|
89
89
|
}
|
|
90
90
|
return undefined;
|
|
91
91
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
}, async ({ query, libraryName }) => {
|
|
168
|
-
|
|
169
|
-
|
|
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:
|
|
175
|
-
? searchResponse.error
|
|
176
|
-
: "No libraries found matching the provided name.",
|
|
180
|
+
text: responseText,
|
|
177
181
|
},
|
|
178
182
|
],
|
|
179
183
|
};
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
|
|
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
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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
|
-
|
|
256
|
-
|
|
257
|
-
|
|
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
|
-
//
|
|
350
|
-
//
|
|
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) {
|