@upstash/context7-mcp 1.0.3 → 1.0.4
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/README.md +19 -3
- package/dist/index.js +26 -19
- package/dist/lib/api.js +25 -30
- package/dist/lib/utils.js +13 -120
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -52,7 +52,7 @@ Paste this into your Cursor `~/.cursor/mcp.json` file. See [Cursor MCP docs](htt
|
|
|
52
52
|
"mcpServers": {
|
|
53
53
|
"context7": {
|
|
54
54
|
"command": "npx",
|
|
55
|
-
"args": ["-y", "@upstash/context7-mcp"]
|
|
55
|
+
"args": ["-y", "@upstash/context7-mcp@latest"]
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
58
|
}
|
|
@@ -67,7 +67,23 @@ Add this to your Windsurf MCP config file. See [Windsurf MCP docs](https://docs.
|
|
|
67
67
|
"mcpServers": {
|
|
68
68
|
"context7": {
|
|
69
69
|
"command": "npx",
|
|
70
|
-
"args": ["-y", "@upstash/context7-mcp"]
|
|
70
|
+
"args": ["-y", "@upstash/context7-mcp@latest"]
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Install in VSCode
|
|
77
|
+
|
|
78
|
+
Add this to your VSCode MCP config file. See [VSCode MCP docs](https://code.visualstudio.com/docs/copilot/chat/mcp-servers) for more info.
|
|
79
|
+
|
|
80
|
+
```json
|
|
81
|
+
{
|
|
82
|
+
"servers": {
|
|
83
|
+
"Context7": {
|
|
84
|
+
"type": "stdio",
|
|
85
|
+
"command": "npx",
|
|
86
|
+
"args": ["-y", "@upstash/context7-mcp@latest"]
|
|
71
87
|
}
|
|
72
88
|
}
|
|
73
89
|
}
|
|
@@ -112,7 +128,7 @@ bun run build
|
|
|
112
128
|
### Testing with MCP Inspector
|
|
113
129
|
|
|
114
130
|
```bash
|
|
115
|
-
npx -y @modelcontextprotocol/inspector npx @upstash/context7-mcp
|
|
131
|
+
npx -y @modelcontextprotocol/inspector npx @upstash/context7-mcp@latest
|
|
116
132
|
```
|
|
117
133
|
|
|
118
134
|
## License
|
package/dist/index.js
CHANGED
|
@@ -2,13 +2,14 @@
|
|
|
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 {
|
|
6
|
-
import {
|
|
5
|
+
import { searchLibraries, fetchLibraryDocumentation } from "./lib/api.js";
|
|
6
|
+
import { formatSearchResults } from "./lib/utils.js";
|
|
7
|
+
const DEFAULT_MINIMUM_TOKENS = 5000;
|
|
7
8
|
// Create server instance
|
|
8
9
|
const server = new McpServer({
|
|
9
10
|
name: "Context7",
|
|
10
11
|
description: "Retrieves up-to-date documentation and code examples for any library.",
|
|
11
|
-
version: "1.0.
|
|
12
|
+
version: "1.0.4",
|
|
12
13
|
capabilities: {
|
|
13
14
|
resources: {},
|
|
14
15
|
tools: {},
|
|
@@ -21,8 +22,8 @@ server.tool("resolve-library-id", "Required first step: Resolves a general packa
|
|
|
21
22
|
.optional()
|
|
22
23
|
.describe("Optional library name to search for and rerank results based on."),
|
|
23
24
|
}, async ({ libraryName }) => {
|
|
24
|
-
const
|
|
25
|
-
if (!
|
|
25
|
+
const searchResponse = await searchLibraries(libraryName || "");
|
|
26
|
+
if (!searchResponse || !searchResponse.results) {
|
|
26
27
|
return {
|
|
27
28
|
content: [
|
|
28
29
|
{
|
|
@@ -32,28 +33,22 @@ server.tool("resolve-library-id", "Required first step: Resolves a general packa
|
|
|
32
33
|
],
|
|
33
34
|
};
|
|
34
35
|
}
|
|
35
|
-
|
|
36
|
-
const finalizedProjects = projects.filter((project) => project.version.state === "finalized");
|
|
37
|
-
if (finalizedProjects.length === 0) {
|
|
36
|
+
if (searchResponse.results.length === 0) {
|
|
38
37
|
return {
|
|
39
38
|
content: [
|
|
40
39
|
{
|
|
41
40
|
type: "text",
|
|
42
|
-
text: "No
|
|
41
|
+
text: "No documentation libraries available",
|
|
43
42
|
},
|
|
44
43
|
],
|
|
45
44
|
};
|
|
46
45
|
}
|
|
47
|
-
|
|
48
|
-
const rankedProjects = libraryName
|
|
49
|
-
? rerankProjects(finalizedProjects, libraryName)
|
|
50
|
-
: finalizedProjects;
|
|
51
|
-
const projectsText = formatProjectsList(rankedProjects);
|
|
46
|
+
const resultsText = formatSearchResults(searchResponse);
|
|
52
47
|
return {
|
|
53
48
|
content: [
|
|
54
49
|
{
|
|
55
50
|
type: "text",
|
|
56
|
-
text: "Available libraries and their Context7-compatible library
|
|
51
|
+
text: "Available libraries and their Context7-compatible library IDs:\n\n" + resultsText,
|
|
57
52
|
},
|
|
58
53
|
],
|
|
59
54
|
};
|
|
@@ -68,11 +63,23 @@ server.tool("get-library-docs", "Fetches up-to-date documentation for a library.
|
|
|
68
63
|
.describe("Topic to focus documentation on (e.g., 'hooks', 'routing')."),
|
|
69
64
|
tokens: z
|
|
70
65
|
.number()
|
|
71
|
-
.min(
|
|
66
|
+
.min(DEFAULT_MINIMUM_TOKENS)
|
|
72
67
|
.optional()
|
|
73
|
-
.describe(
|
|
74
|
-
}, async ({ context7CompatibleLibraryID, tokens =
|
|
75
|
-
|
|
68
|
+
.describe(`Maximum number of tokens of documentation to retrieve (default: ${DEFAULT_MINIMUM_TOKENS}). Higher values provide more context but consume more tokens.`),
|
|
69
|
+
}, async ({ context7CompatibleLibraryID, tokens = DEFAULT_MINIMUM_TOKENS, topic = "" }) => {
|
|
70
|
+
// Extract folders parameter if present in the ID
|
|
71
|
+
let folders = "";
|
|
72
|
+
let libraryId = context7CompatibleLibraryID;
|
|
73
|
+
if (context7CompatibleLibraryID.includes("?folders=")) {
|
|
74
|
+
const [id, foldersParam] = context7CompatibleLibraryID.split("?folders=");
|
|
75
|
+
libraryId = id;
|
|
76
|
+
folders = foldersParam;
|
|
77
|
+
}
|
|
78
|
+
const documentationText = await fetchLibraryDocumentation(libraryId, {
|
|
79
|
+
tokens,
|
|
80
|
+
topic,
|
|
81
|
+
folders,
|
|
82
|
+
});
|
|
76
83
|
if (!documentationText) {
|
|
77
84
|
return {
|
|
78
85
|
content: [
|
package/dist/lib/api.js
CHANGED
|
@@ -1,51 +1,46 @@
|
|
|
1
|
-
const
|
|
1
|
+
const CONTEXT7_API_BASE_URL = "https://context7.com/api";
|
|
2
|
+
const DEFAULT_TYPE = "txt";
|
|
2
3
|
/**
|
|
3
|
-
*
|
|
4
|
-
* @
|
|
4
|
+
* Searches for libraries matching the given query
|
|
5
|
+
* @param query The search query
|
|
6
|
+
* @returns Search results or null if the request fails
|
|
5
7
|
*/
|
|
6
|
-
export async function
|
|
8
|
+
export async function searchLibraries(query) {
|
|
7
9
|
try {
|
|
8
|
-
const
|
|
10
|
+
const url = new URL(`${CONTEXT7_API_BASE_URL}/v1/search`);
|
|
11
|
+
url.searchParams.set("query", query);
|
|
12
|
+
const response = await fetch(url);
|
|
9
13
|
if (!response.ok) {
|
|
10
|
-
console.error(`Failed to
|
|
14
|
+
console.error(`Failed to search libraries: ${response.status}`);
|
|
11
15
|
return null;
|
|
12
16
|
}
|
|
13
17
|
return await response.json();
|
|
14
18
|
}
|
|
15
19
|
catch (error) {
|
|
16
|
-
console.error("Error
|
|
20
|
+
console.error("Error searching libraries:", error);
|
|
17
21
|
return null;
|
|
18
22
|
}
|
|
19
23
|
}
|
|
20
24
|
/**
|
|
21
25
|
* Fetches documentation context for a specific library
|
|
22
|
-
* @param
|
|
23
|
-
* @param
|
|
24
|
-
* @param topic Optional topic to rerank context for
|
|
26
|
+
* @param libraryId The library ID to fetch documentation for
|
|
27
|
+
* @param options Options for the request
|
|
25
28
|
* @returns The documentation text or null if the request fails
|
|
26
29
|
*/
|
|
27
|
-
export async function fetchLibraryDocumentation(
|
|
30
|
+
export async function fetchLibraryDocumentation(libraryId, options = {}) {
|
|
28
31
|
try {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
libraryName = libraryName.slice(1);
|
|
32
|
+
if (libraryId.startsWith("/")) {
|
|
33
|
+
libraryId = libraryId.slice(1);
|
|
32
34
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
if (
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
folders
|
|
40
|
-
|
|
41
|
-
const
|
|
42
|
-
if (folders)
|
|
43
|
-
contextURL.searchParams.set("folders", folders);
|
|
44
|
-
if (tokens)
|
|
45
|
-
contextURL.searchParams.set("tokens", tokens.toString());
|
|
46
|
-
if (topic)
|
|
47
|
-
contextURL.searchParams.set("topic", topic);
|
|
48
|
-
const response = await fetch(contextURL, {
|
|
35
|
+
const url = new URL(`${CONTEXT7_API_BASE_URL}/v1/${libraryId}`);
|
|
36
|
+
if (options.tokens)
|
|
37
|
+
url.searchParams.set("tokens", options.tokens.toString());
|
|
38
|
+
if (options.topic)
|
|
39
|
+
url.searchParams.set("topic", options.topic);
|
|
40
|
+
if (options.folders)
|
|
41
|
+
url.searchParams.set("folders", options.folders);
|
|
42
|
+
url.searchParams.set("type", DEFAULT_TYPE);
|
|
43
|
+
const response = await fetch(url, {
|
|
49
44
|
headers: {
|
|
50
45
|
"X-Context7-Source": "mcp-server",
|
|
51
46
|
},
|
package/dist/lib/utils.js
CHANGED
|
@@ -1,127 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Format a
|
|
3
|
-
* @param
|
|
4
|
-
* @returns Formatted
|
|
2
|
+
* Format a search result into a string representation
|
|
3
|
+
* @param result SearchResult to format
|
|
4
|
+
* @returns Formatted search result string
|
|
5
5
|
*/
|
|
6
|
-
export function
|
|
7
|
-
return `Title: ${
|
|
6
|
+
export function formatSearchResult(result) {
|
|
7
|
+
return `Title: ${result.title}\n\nContext7-compatible library ID: ${result.id}`;
|
|
8
8
|
}
|
|
9
9
|
/**
|
|
10
|
-
* Format
|
|
11
|
-
* @param
|
|
12
|
-
* @returns Formatted
|
|
10
|
+
* Format search results into a string representation
|
|
11
|
+
* @param searchResponse The search response to format
|
|
12
|
+
* @returns Formatted search results string
|
|
13
13
|
*/
|
|
14
|
-
export function
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
" available documentation libraries:\n\n" +
|
|
18
|
-
formattedProjects.join("\n"));
|
|
19
|
-
}
|
|
20
|
-
/**
|
|
21
|
-
* Rerank projects based on a search term
|
|
22
|
-
* @param projects Projects to rerank
|
|
23
|
-
* @param searchTerm Search term to rerank by
|
|
24
|
-
* @returns Reranked projects
|
|
25
|
-
*/
|
|
26
|
-
export function rerankProjects(projects, searchTerm) {
|
|
27
|
-
if (!searchTerm)
|
|
28
|
-
return projects;
|
|
29
|
-
// Normalize the search term - remove special characters and convert to lowercase
|
|
30
|
-
const normalizedSearchTerm = searchTerm.toLowerCase().replace(/[^\w\s]/g, "");
|
|
31
|
-
return [...projects].sort((a, b) => {
|
|
32
|
-
const aTitle = a.settings.title.toLowerCase();
|
|
33
|
-
const aProject = a.settings.project.toLowerCase();
|
|
34
|
-
const aProjectName = aProject.split("/").pop() || "";
|
|
35
|
-
const aProjectPath = aProject.split("/").slice(0, -1).join("/");
|
|
36
|
-
const bTitle = b.settings.title.toLowerCase();
|
|
37
|
-
const bProject = b.settings.project.toLowerCase();
|
|
38
|
-
const bProjectName = bProject.split("/").pop() || "";
|
|
39
|
-
const bProjectPath = bProject.split("/").slice(0, -1).join("/");
|
|
40
|
-
// Normalize project names for better matching - remove special characters
|
|
41
|
-
const normalizedATitle = aTitle.replace(/[^\w\s]/g, "");
|
|
42
|
-
const normalizedAProject = aProject.replace(/[^\w\s]/g, "");
|
|
43
|
-
const normalizedAProjectName = aProjectName.replace(/[^\w\s]/g, "");
|
|
44
|
-
const normalizedBTitle = bTitle.replace(/[^\w\s]/g, "");
|
|
45
|
-
const normalizedBProject = bProject.replace(/[^\w\s]/g, "");
|
|
46
|
-
const normalizedBProjectName = bProjectName.replace(/[^\w\s]/g, "");
|
|
47
|
-
// Calculate match scores for better ranking
|
|
48
|
-
const aScore = calculateMatchScore(normalizedSearchTerm, {
|
|
49
|
-
original: {
|
|
50
|
-
title: aTitle,
|
|
51
|
-
project: aProject,
|
|
52
|
-
projectName: aProjectName,
|
|
53
|
-
projectPath: aProjectPath,
|
|
54
|
-
},
|
|
55
|
-
normalized: {
|
|
56
|
-
title: normalizedATitle,
|
|
57
|
-
project: normalizedAProject,
|
|
58
|
-
projectName: normalizedAProjectName,
|
|
59
|
-
},
|
|
60
|
-
});
|
|
61
|
-
const bScore = calculateMatchScore(normalizedSearchTerm, {
|
|
62
|
-
original: {
|
|
63
|
-
title: bTitle,
|
|
64
|
-
project: bProject,
|
|
65
|
-
projectName: bProjectName,
|
|
66
|
-
projectPath: bProjectPath,
|
|
67
|
-
},
|
|
68
|
-
normalized: {
|
|
69
|
-
title: normalizedBTitle,
|
|
70
|
-
project: normalizedBProject,
|
|
71
|
-
projectName: normalizedBProjectName,
|
|
72
|
-
},
|
|
73
|
-
});
|
|
74
|
-
// Higher score first
|
|
75
|
-
if (aScore !== bScore) {
|
|
76
|
-
return bScore - aScore;
|
|
77
|
-
}
|
|
78
|
-
// Default to alphabetical by project name
|
|
79
|
-
return aProject.localeCompare(bProject);
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
/**
|
|
83
|
-
* Calculate a match score for ranking
|
|
84
|
-
* Higher score means better match
|
|
85
|
-
*/
|
|
86
|
-
function calculateMatchScore(searchTerm, projectData) {
|
|
87
|
-
const { original, normalized } = projectData;
|
|
88
|
-
let score = 0;
|
|
89
|
-
// Exact matches (highest priority)
|
|
90
|
-
if (original.project === searchTerm ||
|
|
91
|
-
original.title === searchTerm ||
|
|
92
|
-
original.projectName === searchTerm) {
|
|
93
|
-
score += 100;
|
|
94
|
-
}
|
|
95
|
-
// Normalized exact matches
|
|
96
|
-
if (normalized.project === searchTerm ||
|
|
97
|
-
normalized.title === searchTerm ||
|
|
98
|
-
normalized.projectName === searchTerm) {
|
|
99
|
-
score += 90;
|
|
100
|
-
}
|
|
101
|
-
// Starts with matches
|
|
102
|
-
if (original.project.startsWith(searchTerm) ||
|
|
103
|
-
original.title.startsWith(searchTerm) ||
|
|
104
|
-
original.projectName.startsWith(searchTerm)) {
|
|
105
|
-
score += 80;
|
|
106
|
-
}
|
|
107
|
-
// Normalized starts with matches
|
|
108
|
-
if (normalized.project.startsWith(searchTerm) ||
|
|
109
|
-
normalized.title.startsWith(searchTerm) ||
|
|
110
|
-
normalized.projectName.startsWith(searchTerm)) {
|
|
111
|
-
score += 70;
|
|
112
|
-
}
|
|
113
|
-
// Contains matches
|
|
114
|
-
if (original.project.includes(searchTerm) ||
|
|
115
|
-
original.title.includes(searchTerm) ||
|
|
116
|
-
original.projectName.includes(searchTerm) ||
|
|
117
|
-
original.projectPath.includes(searchTerm)) {
|
|
118
|
-
score += 60;
|
|
119
|
-
}
|
|
120
|
-
// Normalized contains matches
|
|
121
|
-
if (normalized.project.includes(searchTerm) ||
|
|
122
|
-
normalized.title.includes(searchTerm) ||
|
|
123
|
-
normalized.projectName.includes(searchTerm)) {
|
|
124
|
-
score += 50;
|
|
14
|
+
export function formatSearchResults(searchResponse) {
|
|
15
|
+
if (!searchResponse.results || searchResponse.results.length === 0) {
|
|
16
|
+
return "No documentation libraries found matching your query.";
|
|
125
17
|
}
|
|
126
|
-
|
|
18
|
+
const formattedResults = searchResponse.results.map(formatSearchResult);
|
|
19
|
+
return formattedResults.join("\n\n");
|
|
127
20
|
}
|