mirror of
https://github.com/upstash/context7.git
synced 2025-06-26 23:50:04 +00:00
fix: commit updated build
This commit is contained in:
parent
7641e71e85
commit
846464146f
103
build/index.js
Executable file
103
build/index.js
Executable file
@ -0,0 +1,103 @@
|
||||
#!/usr/bin/env node
|
||||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
||||
import { z } from "zod";
|
||||
import { fetchProjects, fetchLibraryDocumentation } from "./lib/api.js";
|
||||
import { formatProjectsList, rerankProjects } from "./lib/utils.js";
|
||||
// Create server instance
|
||||
const server = new McpServer({
|
||||
name: "Context7",
|
||||
description: "Retrieves up-to-date documentation and code examples for npm packages.",
|
||||
version: "1.0.0",
|
||||
capabilities: {
|
||||
resources: {},
|
||||
tools: {},
|
||||
},
|
||||
});
|
||||
// Register Context7 tools
|
||||
server.tool("resolve-library-id", "Required first step: Resolves a general package name into a Context7-compatible library ID. Must be called before using 'get-library-docs' to retrieve a valid Context7-compatible library ID.", {
|
||||
libraryName: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe("Optional library name to search for and rerank results based on."),
|
||||
}, async ({ libraryName }) => {
|
||||
const projects = await fetchProjects();
|
||||
if (!projects) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: "Failed to retrieve library documentation data from Context7",
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
// Filter projects to only include those with state "finalized"
|
||||
const finalizedProjects = projects.filter((project) => project.version.state === "finalized");
|
||||
if (finalizedProjects.length === 0) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: "No finalized documentation libraries available",
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
// Rerank projects if a library name is provided
|
||||
const rankedProjects = libraryName
|
||||
? rerankProjects(finalizedProjects, libraryName)
|
||||
: finalizedProjects;
|
||||
const projectsText = formatProjectsList(rankedProjects);
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: "Available libraries and their Context7-compatible library ID:\n\n" + projectsText,
|
||||
},
|
||||
],
|
||||
};
|
||||
});
|
||||
server.tool("get-library-docs", "Fetches up-to-date documentation for a library. You must call 'resolve-library-id' first to obtain the exact Context7-compatible library ID required to use this tool.", {
|
||||
context7CompatibleLibraryID: z
|
||||
.string()
|
||||
.describe("Exact Context7-compatible library ID (e.g., 'mongodb/docs', 'vercel/nextjs') retrieved from 'resolve-library-id'."),
|
||||
topic: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe("Topic to focus documentation on (e.g., 'hooks', 'routing')."),
|
||||
tokens: z
|
||||
.number()
|
||||
.min(5000)
|
||||
.optional()
|
||||
.describe("Maximum number of tokens of documentation to retrieve (default: 5000). Higher values provide more context but consume more tokens."),
|
||||
}, async ({ context7CompatibleLibraryID, tokens = 5000, topic = "" }) => {
|
||||
const documentationText = await fetchLibraryDocumentation(context7CompatibleLibraryID, tokens, topic);
|
||||
if (!documentationText) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: "Documentation not found or not finalized for this library. This might have happened because you used an invalid Context7-compatible library ID. To get a valid Context7-compatible library ID, use the 'resolve-library-id' with the package name you wish to retrieve documentation for.",
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: documentationText,
|
||||
},
|
||||
],
|
||||
};
|
||||
});
|
||||
async function main() {
|
||||
const transport = new StdioServerTransport();
|
||||
await server.connect(transport);
|
||||
console.error("Context7 Documentation MCP Server running on stdio");
|
||||
}
|
||||
main().catch((error) => {
|
||||
console.error("Fatal error in main():", error);
|
||||
process.exit(1);
|
||||
});
|
70
build/lib/api.js
Normal file
70
build/lib/api.js
Normal file
@ -0,0 +1,70 @@
|
||||
const CONTEXT7_BASE_URL = "https://context7.com";
|
||||
/**
|
||||
* Fetches projects from the Context7 API
|
||||
* @returns Array of projects or null if the request fails
|
||||
*/
|
||||
export async function fetchProjects() {
|
||||
try {
|
||||
const response = await fetch(`${CONTEXT7_BASE_URL}/api/projects`);
|
||||
if (!response.ok) {
|
||||
console.error(`Failed to fetch projects: ${response.status}`);
|
||||
return null;
|
||||
}
|
||||
return await response.json();
|
||||
}
|
||||
catch (error) {
|
||||
console.error("Error fetching projects:", error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Fetches documentation context for a specific library
|
||||
* @param libraryName The library name to fetch documentation for
|
||||
* @param tokens Number of tokens to retrieve (default: 5000)
|
||||
* @param topic Optional topic to rerank context for
|
||||
* @returns The documentation text or null if the request fails
|
||||
*/
|
||||
export async function fetchLibraryDocumentation(libraryName, tokens = 5000, topic = "") {
|
||||
try {
|
||||
// if libraryName has a "/" as the first character, remove it
|
||||
if (libraryName.startsWith("/")) {
|
||||
libraryName = libraryName.slice(1);
|
||||
}
|
||||
// Handle folders parameter
|
||||
let basePath = libraryName;
|
||||
let folders = "";
|
||||
if (libraryName.includes("?folders=")) {
|
||||
const [path, foldersParam] = libraryName.split("?folders=");
|
||||
basePath = path;
|
||||
folders = foldersParam;
|
||||
}
|
||||
let contextURL = `${CONTEXT7_BASE_URL}/${basePath}/llms.txt`;
|
||||
const params = [];
|
||||
if (folders) {
|
||||
params.push(`folders=${encodeURIComponent(folders)}`);
|
||||
}
|
||||
if (tokens) {
|
||||
params.push(`tokens=${tokens}`);
|
||||
}
|
||||
if (topic) {
|
||||
params.push(`topic=${encodeURIComponent(topic)}`);
|
||||
}
|
||||
if (params.length > 0) {
|
||||
contextURL += `?${params.join("&")}`;
|
||||
}
|
||||
const response = await fetch(contextURL);
|
||||
if (!response.ok) {
|
||||
console.error(`Failed to fetch documentation: ${response.status}`);
|
||||
return null;
|
||||
}
|
||||
const text = await response.text();
|
||||
if (!text || text === "No content available" || text === "No context data available") {
|
||||
return null;
|
||||
}
|
||||
return text;
|
||||
}
|
||||
catch (error) {
|
||||
console.error("Error fetching library documentation:", error);
|
||||
return null;
|
||||
}
|
||||
}
|
1
build/lib/types.js
Normal file
1
build/lib/types.js
Normal file
@ -0,0 +1 @@
|
||||
export {};
|
127
build/lib/utils.js
Normal file
127
build/lib/utils.js
Normal file
@ -0,0 +1,127 @@
|
||||
/**
|
||||
* Format a project into a string representation
|
||||
* @param project Project to format
|
||||
* @returns Formatted project string
|
||||
*/
|
||||
export function formatProject(project) {
|
||||
return `Title: ${project.settings.title}\nContext7-compatible library ID: ${project.settings.project}\n`;
|
||||
}
|
||||
/**
|
||||
* Format a list of projects into a string representation
|
||||
* @param projects Projects to format
|
||||
* @returns Formatted projects string
|
||||
*/
|
||||
export function formatProjectsList(projects) {
|
||||
const formattedProjects = projects.map(formatProject);
|
||||
return (formattedProjects.length +
|
||||
" available documentation libraries:\n\n" +
|
||||
formattedProjects.join("\n"));
|
||||
}
|
||||
/**
|
||||
* Rerank projects based on a search term
|
||||
* @param projects Projects to rerank
|
||||
* @param searchTerm Search term to rerank by
|
||||
* @returns Reranked projects
|
||||
*/
|
||||
export function rerankProjects(projects, searchTerm) {
|
||||
if (!searchTerm)
|
||||
return projects;
|
||||
// Normalize the search term - remove special characters and convert to lowercase
|
||||
const normalizedSearchTerm = searchTerm.toLowerCase().replace(/[^\w\s]/g, "");
|
||||
return [...projects].sort((a, b) => {
|
||||
const aTitle = a.settings.title.toLowerCase();
|
||||
const aProject = a.settings.project.toLowerCase();
|
||||
const aProjectName = aProject.split("/").pop() || "";
|
||||
const aProjectPath = aProject.split("/").slice(0, -1).join("/");
|
||||
const bTitle = b.settings.title.toLowerCase();
|
||||
const bProject = b.settings.project.toLowerCase();
|
||||
const bProjectName = bProject.split("/").pop() || "";
|
||||
const bProjectPath = bProject.split("/").slice(0, -1).join("/");
|
||||
// Normalize project names for better matching - remove special characters
|
||||
const normalizedATitle = aTitle.replace(/[^\w\s]/g, "");
|
||||
const normalizedAProject = aProject.replace(/[^\w\s]/g, "");
|
||||
const normalizedAProjectName = aProjectName.replace(/[^\w\s]/g, "");
|
||||
const normalizedBTitle = bTitle.replace(/[^\w\s]/g, "");
|
||||
const normalizedBProject = bProject.replace(/[^\w\s]/g, "");
|
||||
const normalizedBProjectName = bProjectName.replace(/[^\w\s]/g, "");
|
||||
// Calculate match scores for better ranking
|
||||
const aScore = calculateMatchScore(normalizedSearchTerm, {
|
||||
original: {
|
||||
title: aTitle,
|
||||
project: aProject,
|
||||
projectName: aProjectName,
|
||||
projectPath: aProjectPath,
|
||||
},
|
||||
normalized: {
|
||||
title: normalizedATitle,
|
||||
project: normalizedAProject,
|
||||
projectName: normalizedAProjectName,
|
||||
},
|
||||
});
|
||||
const bScore = calculateMatchScore(normalizedSearchTerm, {
|
||||
original: {
|
||||
title: bTitle,
|
||||
project: bProject,
|
||||
projectName: bProjectName,
|
||||
projectPath: bProjectPath,
|
||||
},
|
||||
normalized: {
|
||||
title: normalizedBTitle,
|
||||
project: normalizedBProject,
|
||||
projectName: normalizedBProjectName,
|
||||
},
|
||||
});
|
||||
// Higher score first
|
||||
if (aScore !== bScore) {
|
||||
return bScore - aScore;
|
||||
}
|
||||
// Default to alphabetical by project name
|
||||
return aProject.localeCompare(bProject);
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Calculate a match score for ranking
|
||||
* Higher score means better match
|
||||
*/
|
||||
function calculateMatchScore(searchTerm, projectData) {
|
||||
const { original, normalized } = projectData;
|
||||
let score = 0;
|
||||
// Exact matches (highest priority)
|
||||
if (original.project === searchTerm ||
|
||||
original.title === searchTerm ||
|
||||
original.projectName === searchTerm) {
|
||||
score += 100;
|
||||
}
|
||||
// Normalized exact matches
|
||||
if (normalized.project === searchTerm ||
|
||||
normalized.title === searchTerm ||
|
||||
normalized.projectName === searchTerm) {
|
||||
score += 90;
|
||||
}
|
||||
// Starts with matches
|
||||
if (original.project.startsWith(searchTerm) ||
|
||||
original.title.startsWith(searchTerm) ||
|
||||
original.projectName.startsWith(searchTerm)) {
|
||||
score += 80;
|
||||
}
|
||||
// Normalized starts with matches
|
||||
if (normalized.project.startsWith(searchTerm) ||
|
||||
normalized.title.startsWith(searchTerm) ||
|
||||
normalized.projectName.startsWith(searchTerm)) {
|
||||
score += 70;
|
||||
}
|
||||
// Contains matches
|
||||
if (original.project.includes(searchTerm) ||
|
||||
original.title.includes(searchTerm) ||
|
||||
original.projectName.includes(searchTerm) ||
|
||||
original.projectPath.includes(searchTerm)) {
|
||||
score += 60;
|
||||
}
|
||||
// Normalized contains matches
|
||||
if (normalized.project.includes(searchTerm) ||
|
||||
normalized.title.includes(searchTerm) ||
|
||||
normalized.projectName.includes(searchTerm)) {
|
||||
score += 50;
|
||||
}
|
||||
return score;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user