UNPKG

@basicsu/courses-mcp

Version:

Interactive programming courses from Basics - MCP server for Cursor

117 lines 4.15 kB
import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { zodToJsonSchema } from 'zod-to-json-schema'; import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js'; import { phaseZeroTools } from './tools/phase-zero-tools.js'; import { loadConfig, hasValidAuth } from './config.js'; // Global variables for auth state let config = loadConfig(); let userEmail; let deviceToken; async function initializeAuth() { // Load existing auth if available if (hasValidAuth()) { userEmail = config.userEmail; deviceToken = config.deviceToken; } // If not authenticated, that's OK - tools will handle registration } // Verify device token using Supabase MCP async function verifyAuth() { // This will be replaced with Supabase MCP calls // For now, return a mock user ID if we have a device token if (deviceToken) { return 'mock-user-id'; // This will be replaced with actual MCP query } throw new Error('No device token found'); } // Create MCP server const server = new Server({ name: 'basicsu-courses', version: '1.0.0', description: 'Interactive programming courses from Basics' }, { capabilities: { tools: {}, logging: { enabled: true } } }); // Register tools list handler server.setRequestHandler(ListToolsRequestSchema, async () => { // Log server startup with version to stderr (not stdout) console.error('[MCP Server] Starting Basics MCP v1.3.8 - Comprehensive security & monitoring enabled'); console.error('[MCP Server] Database: Connected to Supabase'); console.error('[MCP Server] Progress tracking: Using profile IDs with RLS policies fixed'); // Always return tools - authentication happens at tool execution time return { tools: phaseZeroTools.map(tool => { const schema = zodToJsonSchema(tool.inputSchema, { name: `${tool.name}Input` }); // Flatten schema for Cursor compatibility if (schema.$ref && schema.definitions) { const refName = schema.$ref.split('/').pop(); const flatSchema = { ...schema.definitions[refName], $schema: schema.$schema }; return { name: tool.name, description: tool.description, inputSchema: flatSchema }; } return { name: tool.name, description: tool.description, inputSchema: schema }; }) }; }); // Register tools call handler server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; const mcpTool = phaseZeroTools.find(t => t.name === name); if (!mcpTool) { throw new Error(`Unknown tool: ${name}`); } const parseResult = mcpTool.inputSchema.safeParse(args || {}); if (!parseResult.success) { throw new Error(`Invalid parameters: ${parseResult.error.message}`); } // Provide context (may be unauthenticated) let userId = null; if (hasValidAuth()) { try { userId = await verifyAuth(); } catch (error) { // Auth verification failed - tools will handle this gracefully } } const context = { userEmail: userEmail || null, userId, projectId: 'mqvlccclucfewgtmhyiv' // Supabase project ID for MCP calls }; const result = await mcpTool.handler(parseResult.data, context); return { content: [ { type: 'text', text: result } ] }; }); // Export runServer function for stdio.ts export async function runServer() { // Initialize authentication first await initializeAuth(); const transport = new StdioServerTransport(); await server.connect(transport); } //# sourceMappingURL=index.js.map