Skip to main content
Glama

MTS MCP Server

by CalvinMagezi
index.ts30 kB
#!/usr/bin/env node import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { ListToolsRequestSchema, CallToolRequestSchema, ErrorCode, McpError, ReadResourceRequestSchema, ListResourcesRequestSchema, RequestSchema, ResultSchema, NotificationSchema, } from "@modelcontextprotocol/sdk/types.js"; import { NexusManager } from "./services/nexus/nexus-manager.js"; import { ReasoningManager } from "./services/reasoning/reasoning-manager.js"; import { PackageManagerServer } from "./services/package-manager/package-manager.js"; import { CreateReasoningStepArgs, AnalyzeArgs, ReasoningType, SynthesizeArgs, ValidateArgs, SequentialReasoningArgs, } from "./types/reasoning.js"; import { PerplexityManager } from "./services/perplexity/perplexity-manager.js"; import { PerplexitySearchArgs } from "./types/perplexity.js"; import { GetHeadlinesArgs, NewsManager } from "./services/news/news-manager.js"; import { NotionManager } from "./services/notion/notion-manager.js"; // Initialize the server with proper types const server = new Server< (typeof RequestSchema)["_output"], (typeof NotificationSchema)["_output"], (typeof ResultSchema)["_output"] >( { name: "mts-mcp-server", version: "1.0.0", }, { capabilities: { tools: {}, sampling: {}, resources: {}, notifications: { listChanged: true, }, }, } ); // Initialize managers const nexusManager = new NexusManager(server); const reasoningManager = new ReasoningManager(server); const perplexityManager = new PerplexityManager(server); const newsManager = new NewsManager(server); const notionManager = new NotionManager(server); const packageManager = new PackageManagerServer(); // Set up request handlers server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ // Nexus Tools { name: "clear_nexus", description: "Clear all stored knowledge from the Nexus system", inputSchema: { type: "object", properties: { confirmation: { type: "boolean", description: "Confirm that you want to clear all Nexus data", }, }, required: ["confirmation"], }, }, { name: "create_entities", description: "Create multiple new entities in the knowledge graph", inputSchema: { type: "object", properties: { entities: { type: "array", items: { type: "object", properties: { name: { type: "string", description: "The name of the entity", }, nodeType: { type: "string", description: "The type of the entity", }, insights: { type: "array", items: { type: "string" }, description: "An array of observation contents associated with the entity", }, }, required: ["name", "nodeType", "insights"], }, }, }, required: ["entities"], }, }, { name: "create_relations", description: "Create multiple new relations between entities", inputSchema: { type: "object", properties: { relations: { type: "array", items: { type: "object", properties: { from: { type: "string", description: "Name of the source entity", }, to: { type: "string", description: "Name of the target entity", }, linkType: { type: "string", description: "Type of the relation", }, }, required: ["from", "to", "linkType"], }, }, }, required: ["relations"], }, }, // Reasoning Tools { name: "create_reasoning_step", description: "Create a new reasoning step", inputSchema: { type: "object", properties: { type: { type: "string", enum: [ "hypothesis", "analysis", "inference", "conclusion", "counterargument", "synthesis", "decomposition", "validation", "revision", "branch", "question", "realization", ], description: "Type of reasoning step", }, content: { type: "string", description: "Content of the reasoning step", }, dependencies: { type: "array", items: { type: "string" }, description: "IDs of dependent reasoning steps", }, evidence: { type: "array", items: { type: "string" }, description: "Supporting evidence or references", }, confidence: { type: "number", minimum: 0, maximum: 1, description: "Confidence level (0-1)", }, }, required: ["type", "content"], }, }, { name: "analyze", description: "Perform deep analysis using structured reasoning", inputSchema: { type: "object", properties: { prompt: { type: "string", description: "The prompt to analyze", }, depth: { type: "number", description: "Depth of analysis (1-5)", minimum: 1, maximum: 5, }, focus_areas: { type: "array", items: { type: "string" }, description: "Specific areas to focus analysis on", }, }, required: ["prompt"], }, }, { name: "scrape_documentation", description: "Scrape documentation from a website", inputSchema: { type: "object", properties: { baseUrl: { type: "string", description: "The base URL of the documentation to scrape", }, }, required: ["baseUrl"], }, }, { name: "scrape_page", description: "Scrape content from a single webpage", inputSchema: { type: "object", properties: { url: { type: "string", description: "The URL of the page to scrape", }, }, required: ["url"], }, }, { name: "synthesize", description: "Synthesize multiple reasoning steps into a new insight", inputSchema: { type: "object", properties: { step_ids: { type: "array", items: { type: "string" }, description: "IDs of steps to synthesize", }, perspective: { type: "string", description: "Optional perspective for synthesis", }, }, required: ["step_ids"], }, }, { name: "validate", description: "Validate a reasoning step against given criteria", inputSchema: { type: "object", properties: { step_id: { type: "string", description: "ID of the step to validate", }, criteria: { type: "array", items: { type: "string" }, description: "Validation criteria", }, }, required: ["step_id"], }, }, { name: "sequential_reasoning", description: "Perform sequential reasoning steps", inputSchema: { type: "object", properties: { prompt: { type: "string", description: "The initial prompt", }, initialSteps: { type: "number", description: "Number of initial steps", }, focusAreas: { type: "array", items: { type: "string" }, description: "Areas to focus on", }, branchId: { type: "string", description: "Optional branch ID", }, branchFromStepId: { type: "string", description: "Optional step ID to branch from", }, }, required: ["prompt"], }, }, // Perplexity Tools { name: "search", description: "Search the internet using Perplexity AI with advanced research capabilities", inputSchema: { type: "object", properties: { query: { type: "string", description: "The search query to perform", }, focus: { type: "string", enum: ["internet", "academic", "writing", "math", "coding"], default: "internet", description: "The type of search to perform", }, return_citations: { type: "boolean", default: true, description: "Whether to return source citations", }, search_domain_filter: { type: "array", items: { type: "string" }, description: "Optional list of domains to restrict search to", }, search_recency_filter: { type: "string", enum: ["day", "week", "month", "year"], description: "Optional time filter for search results", }, }, required: ["query"], }, }, { name: "search_news", description: "Search for news articles using keywords and filters", inputSchema: { type: "object", properties: { q: { type: "string", description: "Keywords or phrases to search for", }, language: { type: "string", description: "2-letter ISO-639-1 language code (e.g., en, es, fr)", default: "en", }, sortBy: { type: "string", enum: ["relevancy", "popularity", "publishedAt"], default: "publishedAt", }, pageSize: { type: "number", description: "Number of results to return (max 100)", default: 10, }, }, required: ["q"], }, }, { name: "get_headlines", description: "Get top headlines by country, category, or sources", inputSchema: { type: "object", properties: { country: { type: "string", description: "2-letter ISO 3166-1 country code", }, category: { type: "string", enum: [ "business", "entertainment", "general", "health", "science", "sports", "technology", ], }, sources: { type: "string", description: "Comma-separated news sources", }, pageSize: { type: "number", default: 10, }, }, }, }, // Package Manager Tools { name: "manage_packages", description: "Install or uninstall packages using npm/yarn/pnpm/bun", inputSchema: { type: "object", properties: { operation: { type: "string", enum: ["install", "uninstall"], description: "Operation to perform", }, packages: { type: "array", items: { type: "string" }, description: "Package names", }, packageManager: { type: "string", enum: ["npm", "yarn", "pnpm", "bun"], default: "bun", description: "Package manager to use", }, dev: { type: "boolean", default: false, description: "Install as dev dependency", }, }, required: ["operation", "packages"], }, }, { name: "set_working_directory", description: "Set the working directory for package operations", inputSchema: { type: "object", properties: { path: { type: "string", description: "Path to working directory", }, }, required: ["path"], }, }, { name: "create_nextjs_app", description: "Create a new Next.js application with interactive setup", inputSchema: { type: "object", properties: { projectPath: { type: "string", description: "Path where the Next.js project should be created", }, answers: { type: "object", description: "Answers to the setup questions", properties: { "What is your project named?": { type: "string", description: "The name of your Next.js project", }, "Would you like to use TypeScript?": { type: "string", enum: ["No", "Yes"], default: "Yes", }, "Would you like to use ESLint?": { type: "string", enum: ["No", "Yes"], default: "Yes", }, "Would you like to use Tailwind CSS?": { type: "string", enum: ["No", "Yes"], default: "Yes", }, "Would you like your code inside a `src/` directory?": { type: "string", enum: ["No", "Yes"], default: "Yes", }, "Would you like to use App Router? (recommended)": { type: "string", enum: ["No", "Yes"], default: "Yes", }, "Would you like to use Turbopack for `next dev`?": { type: "string", enum: ["No", "Yes"], default: "No", }, "Would you like to customize the import alias ('*' (see below for file content) by default)?": { type: "string", enum: ["No", "Yes"], default: "No", }, }, required: ["What is your project named?"], }, }, required: ["projectPath", "answers"], }, }, // Notion Tools { name: "search_notion", description: "Search Notion pages and databases", inputSchema: { type: "object", properties: { query: { type: "string", description: "Search query", }, limit: { type: "number", description: "Maximum number of results (1-100)", minimum: 1, maximum: 100, }, }, required: ["query"], }, }, { name: "get_database_content", description: "Get contents of a specific Notion database", inputSchema: { type: "object", properties: { database_id: { type: "string", description: "Notion database ID", }, limit: { type: "number", description: "Maximum number of results (1-100)", minimum: 1, maximum: 100, }, }, required: ["database_id"], }, }, ], }; }); server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name: toolName, arguments: params } = request.params; if (!params || typeof params !== "object") { throw new McpError( ErrorCode.InvalidRequest, "Tool parameters are required and must be an object" ); } try { // Helper function to create standardized tool responses const createToolResponse = (content: { type: string; text: string }[]) => ({ _meta: {}, content, }); switch (toolName) { // Nexus Tools case "clear_nexus": { const confirmation = params.confirmation; if (typeof confirmation !== "boolean") { throw new McpError( ErrorCode.InvalidRequest, "Confirmation must be a boolean" ); } await nexusManager.clearNexus(confirmation); return createToolResponse([ { type: "text", text: "Nexus cleared successfully" }, ]); } case "create_entities": { const { entities } = params as { entities: { name: string; nodeType: string; insights: string[] }[]; }; if (!Array.isArray(entities)) { throw new McpError( ErrorCode.InvalidRequest, "Entities must be an array" ); } await nexusManager.createEntities({ entities }); return createToolResponse([ { type: "text", text: "Entities created successfully" }, ]); } case "create_relations": { const { relations } = params as { relations: { from: string; to: string; linkType: string }[]; }; if (!Array.isArray(relations)) { throw new McpError( ErrorCode.InvalidRequest, "Relations must be an array" ); } await nexusManager.createRelations({ relations }); return createToolResponse([ { type: "text", text: "Relations created successfully" }, ]); } // Reasoning Tools case "create_reasoning_step": { const rawParams = params as Record<string, unknown>; const args: CreateReasoningStepArgs = { type: rawParams.type as ReasoningType, content: rawParams.content as string, dependencies: rawParams.dependencies as string[] | undefined, evidence: rawParams.evidence as string[] | undefined, confidence: rawParams.confidence as number | undefined, }; if (!args.type || !args.content) { throw new McpError( ErrorCode.InvalidRequest, "Valid type and content are required" ); } const result = await reasoningManager.createReasoningStep(args); return createToolResponse([ { type: "text", text: JSON.stringify( { step_id: result.content[0].text, type: args.type, content: args.content, confidence: args.confidence, evidence: args.evidence, }, null, 2 ), }, ]); } case "analyze": { const rawParams = params as Record<string, unknown>; const args: AnalyzeArgs = { prompt: rawParams.prompt as string, depth: rawParams.depth as number | undefined, focus_areas: rawParams.focus_areas as string[] | undefined, }; if (!args.prompt) { throw new McpError( ErrorCode.InvalidRequest, "Valid prompt is required" ); } const result = await reasoningManager.analyze(args); return createToolResponse([ { type: "text", text: JSON.stringify(result, null, 2), }, ]); } case "scrape_documentation": { const { baseUrl } = params as { baseUrl: string }; if (typeof baseUrl !== "string") { throw new McpError( ErrorCode.InvalidRequest, "Base URL must be a string" ); } await nexusManager.scrapeDocumentation(baseUrl); return createToolResponse([ { type: "text", text: "Documentation scraped successfully" }, ]); } case "scrape_page": { const { url } = params as { url: string }; if (typeof url !== "string") { throw new McpError(ErrorCode.InvalidRequest, "URL must be a string"); } await nexusManager.scrapePage(url); return createToolResponse([ { type: "text", text: "Page scraped successfully" }, ]); } case "synthesize": { const rawParams = params as Record<string, unknown>; const args: SynthesizeArgs = { step_ids: rawParams.step_ids as string[], perspective: rawParams.perspective as string | undefined, }; if (!args.step_ids?.length) { throw new McpError( ErrorCode.InvalidRequest, "At least one step ID is required" ); } const result = await reasoningManager.synthesize(args); return createToolResponse([ { type: "text", text: JSON.stringify( { synthesis_id: result.content[0].text, synthesized_steps: args.step_ids, perspective: args.perspective, result: result.content[0].text, }, null, 2 ), }, ]); } case "validate": { const rawParams = params as Record<string, unknown>; const args: ValidateArgs = { step_id: rawParams.step_id as string, criteria: rawParams.criteria as string[] | undefined, }; if (!args.step_id) { throw new McpError(ErrorCode.InvalidRequest, "Step ID is required"); } const result = await reasoningManager.validate(args); return createToolResponse(result.content); } case "sequential_reasoning": { const rawParams = params as Record<string, unknown>; const args: SequentialReasoningArgs = { prompt: rawParams.prompt as string, initialSteps: rawParams.initialSteps as number | undefined, focusAreas: rawParams.focusAreas as string[] | undefined, branchId: rawParams.branchId as string | undefined, branchFromStepId: rawParams.branchFromStepId as string | undefined, }; if (!args.prompt) { throw new McpError(ErrorCode.InvalidRequest, "Prompt is required"); } const result = await reasoningManager.sequentialReasoning(args); return createToolResponse([ { type: "text", text: JSON.stringify( { steps: result.content[0].text, branch_id: args.branchId, total_steps: args.initialSteps || 3, prompt: args.prompt, }, null, 2 ), }, ]); } case "search": { const { query, focus, return_citations, search_domain_filter, search_recency_filter, } = params as Record<string, any>; if (!query) throw new Error("Search query is required"); const searchParams: PerplexitySearchArgs = { query, focus, return_citations, search_domain_filter, search_recency_filter, }; const result = await perplexityManager.search(searchParams); const formattedText = `${result.text}\n\nSources:\n${result.citations .map((citation, index) => `[${index + 1}] ${citation.url}`) .join("\n")}`; return createToolResponse([ { type: "text", text: formattedText, }, ]); } case "search_news": { const { q, language, sortBy, pageSize } = params as { q: string; language?: string; sortBy?: "relevancy" | "popularity" | "publishedAt"; pageSize?: number; }; const articles = await newsManager.searchNews({ q, language, sortBy, pageSize, }); return createToolResponse( articles.map((article, index) => ({ type: "text", text: `[${index + 1}] ${article.title}\n${ article.description || "" }\n`, })) ); } case "get_headlines": { const articles = await newsManager.getHeadlines( params as GetHeadlinesArgs ); return createToolResponse( articles.map((article, index) => ({ type: "text", text: `[${index + 1}] ${article.title}\n${ article.description || "" }\n`, })) ); } case "search_notion": { const { query, limit } = params as { query: string; limit?: number; }; const results = await notionManager.search({ query, limit }); return createToolResponse( results.map((result, index) => ({ type: "text", text: JSON.stringify(result, null, 2), })) ); } case "get_database_content": { const { database_id, limit } = params as { database_id: string; limit?: number; }; const results = await notionManager.getDatabaseContent({ database_id, limit, }); return createToolResponse( results.map((result, index) => ({ type: "text", text: JSON.stringify(result, null, 2), })) ); } // Package Manager Tools case "manage_packages": case "set_working_directory": case "create_nextjs_app": { return packageManager.handleToolRequest(toolName, params); } default: throw new McpError( ErrorCode.InvalidRequest, `Unknown tool: ${toolName}` ); } } catch (error) { if (error instanceof McpError) { throw error; } throw new McpError( ErrorCode.InternalError, `Error executing tool ${toolName}: ${(error as Error).message}` ); } }); // Add resource handling for search results server.setRequestHandler(ListResourcesRequestSchema, async () => { const searchResults = perplexityManager.getAllSearchResults(); const newsArticles = newsManager.getAllArticles(); return { resources: [ ...searchResults.map((result) => ({ uri: `perplexity://${result.id}`, mimeType: "text/plain", name: result.text.substring(0, 100) + "...", description: `Search performed at ${result.timestamp}`, })), ...newsArticles.map((article) => ({ uri: `news://${article.id}`, mimeType: "text/plain", name: article.title, description: article.description || "No description available", })), ], }; }); server.setRequestHandler(ReadResourceRequestSchema, async (request) => { const url = new URL(request.params.uri); if (url.protocol === "perplexity:") { const id = url.hostname; const result = perplexityManager.getSearchResult(id); if (!result) { throw new Error(`Search result ${id} not found`); } const fullContent = [ `Search Result ID: ${result.id}`, `Timestamp: ${result.timestamp}`, "", result.text, "", "Citations:", ...result.citations.map( (citation, index) => `[${index + 1}] ${citation.url}\n${citation.text}` ), ].join("\n"); return { contents: [ { uri: request.params.uri, mimeType: "text/plain", text: fullContent, }, ], }; } if (url.protocol === "news:") { const id = url.hostname; const article = newsManager.getArticle(id); if (!article) { throw new Error(`News article ${id} not found`); } const fullContent = [ `Title: ${article.title}`, `Author: ${article.author || "Unknown"}`, `Published: ${article.publishedAt}`, `Source: ${article.source.name}`, `URL: ${article.url}`, "", article.content || article.description || "No content available", ].join("\n"); return { contents: [ { uri: request.params.uri, mimeType: "text/plain", text: fullContent, }, ], }; } return { contents: [] }; }); // Set up error handling server.onerror = (error) => { console.error("[MCP Error]", error); }; // Handle graceful shutdown process.on("SIGINT", async () => { await server.close(); process.exit(0); }); // Start the server const transport = new StdioServerTransport(); await server.connect(transport);

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/CalvinMagezi/mts-mcp'

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