@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.
@@ -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
- }