Skip to main content
Glama

MyContext MCP Server

by doko89
index.ts•8.48 kB
import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js"; import * as fs from "fs/promises"; import * as path from "path"; import * as os from "os"; // Default context directory const CONTEXT_DIR = path.join(os.homedir(), ".mycontext"); interface ProjectStructure { name: string; type: "file" | "directory"; path: string; children?: ProjectStructure[]; } // Ensure context directory exists async function ensureContextDir() { try { await fs.access(CONTEXT_DIR); } catch { await fs.mkdir(CONTEXT_DIR, { recursive: true }); } } // List all projects (top-level directories) async function listProjects(): Promise<string[]> { await ensureContextDir(); const entries = await fs.readdir(CONTEXT_DIR, { withFileTypes: true }); return entries.filter((e) => e.isDirectory()).map((e) => e.name); } // Get project structure recursively async function getProjectStructure( projectName: string, subPath: string = "" ): Promise<ProjectStructure> { const fullPath = path.join(CONTEXT_DIR, projectName, subPath); const stats = await fs.stat(fullPath); const name = subPath ? path.basename(subPath) : projectName; if (stats.isFile()) { return { name, type: "file", path: subPath || projectName, }; } const entries = await fs.readdir(fullPath, { withFileTypes: true }); const children = await Promise.all( entries.map((entry) => getProjectStructure( projectName, subPath ? path.join(subPath, entry.name) : entry.name ) ) ); return { name, type: "directory", path: subPath || projectName, children, }; } // Read context file async function readContext( projectName: string, filePath: string ): Promise<string> { const fullPath = path.join(CONTEXT_DIR, projectName, filePath); // Security check: ensure path is within context directory const resolvedPath = path.resolve(fullPath); const resolvedContextDir = path.resolve(CONTEXT_DIR); if (!resolvedPath.startsWith(resolvedContextDir)) { throw new Error("Invalid path: outside context directory"); } return await fs.readFile(fullPath, "utf-8"); } // Search across all markdown files async function searchContext(query: string): Promise<any[]> { const projects = await listProjects(); const results: any[] = []; for (const project of projects) { const matches = await searchInProject(project, "", query); if (matches.length > 0) { results.push({ project, matches, }); } } return results; } async function searchInProject( projectName: string, subPath: string, query: string ): Promise<any[]> { const fullPath = path.join(CONTEXT_DIR, projectName, subPath); const entries = await fs.readdir(fullPath, { withFileTypes: true }); const matches: any[] = []; for (const entry of entries) { const entryPath = path.join(subPath, entry.name); if (entry.isDirectory()) { const subMatches = await searchInProject(projectName, entryPath, query); matches.push(...subMatches); } else if (entry.name.endsWith(".md")) { const content = await readContext(projectName, entryPath); const lines = content.split("\n"); const queryLower = query.toLowerCase(); lines.forEach((line, index) => { if (line.toLowerCase().includes(queryLower)) { matches.push({ file: entryPath, line: index + 1, content: line.trim(), }); } }); } } return matches; } // Create MCP server const server = new Server( { name: "mycontext", version: "1.0.0", }, { capabilities: { tools: {}, }, } ); // List available tools server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: "list_projects", description: "List all available projects in the context directory (~/.mycontext/)", inputSchema: { type: "object", properties: {}, }, }, { name: "get_project_structure", description: "Get the complete directory structure of a specific project", inputSchema: { type: "object", properties: { project_name: { type: "string", description: "Name of the project", }, }, required: ["project_name"], }, }, { name: "read_context", description: "Read the content of a specific context file within a project", inputSchema: { type: "object", properties: { project_name: { type: "string", description: "Name of the project", }, file_path: { type: "string", description: "Relative path to the file within the project (e.g., 'backend/gin.md')", }, }, required: ["project_name", "file_path"], }, }, { name: "search_context", description: "Search for a keyword across all context files in all projects", inputSchema: { type: "object", properties: { query: { type: "string", description: "Search query", }, }, required: ["query"], }, }, ], }; }); // Handle tool calls server.setRequestHandler(CallToolRequestSchema, async (request) => { try { const { name, arguments: args } = request.params; if (!args) { throw new Error("Arguments are required"); } switch (name) { case "list_projects": { const projects = await listProjects(); return { content: [ { type: "text", text: JSON.stringify( { projects, context_directory: CONTEXT_DIR, }, null, 2 ), }, ], }; } case "get_project_structure": { const projectName = args.project_name as string; if (!projectName) { throw new Error("project_name is required"); } const structure = await getProjectStructure(projectName); return { content: [ { type: "text", text: JSON.stringify(structure, null, 2), }, ], }; } case "read_context": { const projectName = args.project_name as string; const filePath = args.file_path as string; if (!projectName || !filePath) { throw new Error("project_name and file_path are required"); } const content = await readContext(projectName, filePath); return { content: [ { type: "text", text: content, }, ], }; } case "search_context": { const query = args.query as string; if (!query) { throw new Error("query is required"); } const results = await searchContext(query); return { content: [ { type: "text", text: JSON.stringify( { query, total_results: results.reduce( (sum, r) => sum + r.matches.length, 0 ), results, }, null, 2 ), }, ], }; } default: throw new Error(`Unknown tool: ${name}`); } } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); return { content: [ { type: "text", text: `Error: ${errorMessage}`, }, ], isError: true, }; } }); // Start server async function main() { const transport = new StdioServerTransport(); await server.connect(transport); console.error("MyContext MCP Server running on stdio"); } main().catch((error) => { console.error("Fatal error:", error); process.exit(1); });

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/doko89/mcp-mycontext'

If you have feedback or need assistance with the MCP directory API, please join our Discord server