@upstash/context7-mcp 1.0.34-canary.1 → 1.0.34-canary.3
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 +78 -25
- package/dist/lib/api.js +34 -5
- package/package.json +2 -10
- package/dist/benchmark/benchmark.js +0 -347
- package/dist/benchmark/compare-benchmark.js +0 -289
- package/dist/benchmark/run-benchmark.js +0 -459
- package/dist/benchmark/simulate.js +0 -319
|
@@ -1,319 +0,0 @@
|
|
|
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
|
-
}
|