@upstash/context7-mcp 0.0.1
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/LICENSE +21 -0
- package/README.md +83 -0
- package/build/index.js +103 -0
- package/build/lib/api.js +55 -0
- package/build/lib/types.js +1 -0
- package/build/lib/utils.js +127 -0
- package/package.json +44 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2021 Upstash, Inc.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Context7 MCP Server
|
|
2
|
+
|
|
3
|
+
In this repository, we provide an MCP Server for [Context7](https://context7.com), which offers access to high-quality documentation for popular libraries.
|
|
4
|
+
|
|
5
|
+
This lets you use Cursor, Windsurf, Claude Desktop, or any MCP Client, to use natural language to search and access documentation for libraries, e.g.:
|
|
6
|
+
|
|
7
|
+
- "What are the main features of React hooks?"
|
|
8
|
+
- "How do I implement authentication with Next.js?"
|
|
9
|
+
- "Rate limiting with Redis"
|
|
10
|
+
- "Get examples of using React Query"
|
|
11
|
+
|
|
12
|
+
## Usage
|
|
13
|
+
|
|
14
|
+
### Requirements
|
|
15
|
+
|
|
16
|
+
- Node.js >= v18.0.0
|
|
17
|
+
- Cursor, Windsurf, Claude Desktop or another MCP Client
|
|
18
|
+
|
|
19
|
+
### How to use locally
|
|
20
|
+
|
|
21
|
+
#### Installing for Cursor
|
|
22
|
+
|
|
23
|
+
Add this command to the MCP list in Cursor. For more info, check the [Cursor MCP](https://docs.cursor.com/context/model-context-protocol) docs.
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npx -y @upstash/context7-mcp
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
#### Installing for Windsurf
|
|
30
|
+
|
|
31
|
+
Add this to your Windsurf MCP config file. For more info, check the [Windsurf MCP](https://docs.windsurf.com/windsurf/mcp) docs.
|
|
32
|
+
|
|
33
|
+
```json
|
|
34
|
+
{
|
|
35
|
+
"mcpServers": {
|
|
36
|
+
"context7": {
|
|
37
|
+
"command": "npx",
|
|
38
|
+
"args": ["-y", "@upstash/context7-mcp"]
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Tools
|
|
45
|
+
|
|
46
|
+
- `list-available-docs`: Lists all available documentation libraries from Context7
|
|
47
|
+
- `get-library-documentation`: Retrieves documentation for a specific library with options for:
|
|
48
|
+
- `libraryName`: Name of the library to retrieve docs for
|
|
49
|
+
- `topic`: Specific topic within the library
|
|
50
|
+
- `tokens`: Maximum tokens to retrieve (default: 5000)
|
|
51
|
+
|
|
52
|
+
## Development
|
|
53
|
+
|
|
54
|
+
Clone the project and run:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
npm install
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
You can use the following commands to format and lint the code:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
npm run format
|
|
64
|
+
npm run lint
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Building
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
npm run build
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Testing with MCP Inspector
|
|
74
|
+
|
|
75
|
+
You can also use the MCP Inspector to test the tools by following the MCP documentation for setting up the inspector.
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
npx -y @modelcontextprotocol/inspector npx @upstash/context7-mcp
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## License
|
|
82
|
+
|
|
83
|
+
MIT
|
package/build/index.js
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { fetchProjects, fetchLibraryDocumentation } from "./lib/api.js";
|
|
6
|
+
import { formatProjectsList, rerankProjects } from "./lib/utils.js";
|
|
7
|
+
// Create server instance
|
|
8
|
+
const server = new McpServer({
|
|
9
|
+
name: "Context7",
|
|
10
|
+
description: "Retrieves documentation and code examples for software libraries.",
|
|
11
|
+
version: "1.0.0",
|
|
12
|
+
capabilities: {
|
|
13
|
+
resources: {},
|
|
14
|
+
tools: {},
|
|
15
|
+
},
|
|
16
|
+
});
|
|
17
|
+
// Register Context7 tools
|
|
18
|
+
server.tool("list-available-docs", "Lists all available library documentation from Context7. The library names can be used with 'get-library-documentation' to retrieve documentation.", {
|
|
19
|
+
libraryName: z
|
|
20
|
+
.string()
|
|
21
|
+
.optional()
|
|
22
|
+
.describe("Optional library name to search for and rerank results based on"),
|
|
23
|
+
}, async ({ libraryName }) => {
|
|
24
|
+
const projects = await fetchProjects();
|
|
25
|
+
if (!projects) {
|
|
26
|
+
return {
|
|
27
|
+
content: [
|
|
28
|
+
{
|
|
29
|
+
type: "text",
|
|
30
|
+
text: "Failed to retrieve library documentation data from Context7",
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
// Filter projects to only include those with state "finalized"
|
|
36
|
+
const finalizedProjects = projects.filter((project) => project.version.state === "finalized");
|
|
37
|
+
if (finalizedProjects.length === 0) {
|
|
38
|
+
return {
|
|
39
|
+
content: [
|
|
40
|
+
{
|
|
41
|
+
type: "text",
|
|
42
|
+
text: "No finalized documentation libraries available",
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
// Rerank projects if a library name is provided
|
|
48
|
+
const rankedProjects = libraryName
|
|
49
|
+
? rerankProjects(finalizedProjects, libraryName)
|
|
50
|
+
: finalizedProjects;
|
|
51
|
+
const projectsText = formatProjectsList(rankedProjects);
|
|
52
|
+
return {
|
|
53
|
+
content: [
|
|
54
|
+
{
|
|
55
|
+
type: "text",
|
|
56
|
+
text: projectsText,
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
};
|
|
60
|
+
});
|
|
61
|
+
server.tool("get-library-documentation", "Retrieves documentation for a specific library from Context7. Use 'list-available-docs' first to see what's available.", {
|
|
62
|
+
libraryName: z
|
|
63
|
+
.string()
|
|
64
|
+
.describe("Name of the library to retrieve documentation for (e.g., 'upstash-redis', 'nextjs'). Must match exactly a library name from 'list-available-docs'."),
|
|
65
|
+
topic: z
|
|
66
|
+
.string()
|
|
67
|
+
.optional()
|
|
68
|
+
.describe("Specific topic within the library to focus the documentation on (e.g., 'hooks', 'routing')."),
|
|
69
|
+
tokens: z
|
|
70
|
+
.number()
|
|
71
|
+
.min(5000)
|
|
72
|
+
.optional()
|
|
73
|
+
.describe("Maximum number of tokens of documentation to retrieve (default: 5000).Higher values provide more comprehensive documentation but use more context window."),
|
|
74
|
+
}, async ({ libraryName, tokens = 5000, topic = "" }) => {
|
|
75
|
+
const documentationText = await fetchLibraryDocumentation(libraryName, tokens, topic);
|
|
76
|
+
if (!documentationText) {
|
|
77
|
+
return {
|
|
78
|
+
content: [
|
|
79
|
+
{
|
|
80
|
+
type: "text",
|
|
81
|
+
text: "Documentation not found or not finalized for this library. Verify you've provided a valid library name exactly as listed by the 'list-available-docs' tool.",
|
|
82
|
+
},
|
|
83
|
+
],
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
content: [
|
|
88
|
+
{
|
|
89
|
+
type: "text",
|
|
90
|
+
text: documentationText,
|
|
91
|
+
},
|
|
92
|
+
],
|
|
93
|
+
};
|
|
94
|
+
});
|
|
95
|
+
async function main() {
|
|
96
|
+
const transport = new StdioServerTransport();
|
|
97
|
+
await server.connect(transport);
|
|
98
|
+
console.error("Context7 Documentation MCP Server running on stdio");
|
|
99
|
+
}
|
|
100
|
+
main().catch((error) => {
|
|
101
|
+
console.error("Fatal error in main():", error);
|
|
102
|
+
process.exit(1);
|
|
103
|
+
});
|
package/build/lib/api.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
const CONTEXT7_BASE_URL = "https://context7.com";
|
|
2
|
+
/**
|
|
3
|
+
* Fetches projects from the Context7 API
|
|
4
|
+
* @returns Array of projects or null if the request fails
|
|
5
|
+
*/
|
|
6
|
+
export async function fetchProjects() {
|
|
7
|
+
try {
|
|
8
|
+
const response = await fetch(`${CONTEXT7_BASE_URL}/api/projects`);
|
|
9
|
+
if (!response.ok) {
|
|
10
|
+
console.error(`Failed to fetch projects: ${response.status}`);
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
return await response.json();
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
console.error("Error fetching projects:", error);
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Fetches documentation context for a specific library
|
|
22
|
+
* @param libraryName The library name to fetch documentation for
|
|
23
|
+
* @param tokens Number of tokens to retrieve (default: 5000)
|
|
24
|
+
* @param topic Optional topic to rerank context for
|
|
25
|
+
* @returns The documentation text or null if the request fails
|
|
26
|
+
*/
|
|
27
|
+
export async function fetchLibraryDocumentation(libraryName, tokens = 5000, topic = "") {
|
|
28
|
+
try {
|
|
29
|
+
let contextURL = `${CONTEXT7_BASE_URL}/${libraryName}/llms.txt`;
|
|
30
|
+
const params = [];
|
|
31
|
+
if (tokens) {
|
|
32
|
+
params.push(`tokens=${tokens}`);
|
|
33
|
+
}
|
|
34
|
+
if (topic) {
|
|
35
|
+
params.push(`topic=${encodeURIComponent(topic)}`);
|
|
36
|
+
}
|
|
37
|
+
if (params.length > 0) {
|
|
38
|
+
contextURL += `?${params.join("&")}`;
|
|
39
|
+
}
|
|
40
|
+
const response = await fetch(contextURL);
|
|
41
|
+
if (!response.ok) {
|
|
42
|
+
console.error(`Failed to fetch documentation: ${response.status}`);
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
const text = await response.text();
|
|
46
|
+
if (!text || text === "No content available" || text === "No context data available") {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
return text;
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
console.error("Error fetching library documentation:", error);
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Format a project into a string representation
|
|
3
|
+
* @param project Project to format
|
|
4
|
+
* @returns Formatted project string
|
|
5
|
+
*/
|
|
6
|
+
export function formatProject(project) {
|
|
7
|
+
return `Title: ${project.settings.title}\nLibrary name: ${project.settings.project}\n`;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Format a list of projects into a string representation
|
|
11
|
+
* @param projects Projects to format
|
|
12
|
+
* @returns Formatted projects string
|
|
13
|
+
*/
|
|
14
|
+
export function formatProjectsList(projects) {
|
|
15
|
+
const formattedProjects = projects.map(formatProject);
|
|
16
|
+
return (formattedProjects.length +
|
|
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;
|
|
125
|
+
}
|
|
126
|
+
return score;
|
|
127
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@upstash/context7-mcp",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "MCP server for Context7",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"test": "echo \"Error: no test specified\" && exit 1",
|
|
7
|
+
"build": "tsc && chmod 755 build/index.js",
|
|
8
|
+
"format": "prettier --write .",
|
|
9
|
+
"lint": "eslint \"**/*.{js,ts,tsx}\" --fix"
|
|
10
|
+
},
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "git+https://github.com/upstash/context7-mcp.git"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [],
|
|
16
|
+
"author": "",
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"type": "module",
|
|
19
|
+
"bin": {
|
|
20
|
+
"context7-mcp": "./build/index.js"
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"build"
|
|
24
|
+
],
|
|
25
|
+
"bugs": {
|
|
26
|
+
"url": "https://github.com/upstash/context7-mcp/issues"
|
|
27
|
+
},
|
|
28
|
+
"homepage": "https://github.com/upstash/context7-mcp#readme",
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@modelcontextprotocol/sdk": "^1.8.0",
|
|
31
|
+
"zod": "^3.24.2"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@types/node": "^22.13.14",
|
|
35
|
+
"@typescript-eslint/eslint-plugin": "^8.28.0",
|
|
36
|
+
"@typescript-eslint/parser": "^8.28.0",
|
|
37
|
+
"eslint": "^9.23.0",
|
|
38
|
+
"eslint-config-prettier": "^10.1.1",
|
|
39
|
+
"eslint-plugin-prettier": "^5.2.5",
|
|
40
|
+
"prettier": "^3.5.3",
|
|
41
|
+
"typescript": "^5.8.2",
|
|
42
|
+
"typescript-eslint": "^8.28.0"
|
|
43
|
+
}
|
|
44
|
+
}
|