Home / Class/ Server Class — mcp Architecture

Server Class — mcp Architecture

Architecture documentation for the Server class in server.ts from the mcp codebase.

Entity Profile

Relationship Graph

Source Code

src/server.ts lines 55–351

export class Server {
  private server: McpServer;
  private client: ClientContext;
  private defaultWorkdir?: string;
  private options?: ServerOptions;
  constructor(defaultWorkdir?: string, options?: ServerOptions) {
    this.defaultWorkdir = defaultWorkdir;
    this.options = options;

    const experiment = process.env.SUPERMODEL_EXPERIMENT;

    // Note: noApiFallback is deferred to start() so startup precaching can use the API
    const experimentInstructions: Record<string, string> = {
      'minimal-instructions': 'Codebase analysis tool. Call symbol_context to look up functions/classes.',
      'search-symbol': 'Codebase search tool. Use `search_symbol` alongside Grep and Read for parallel exploration.',
      'split-tools': 'Codebase tools: `find_definition` locates symbols, `trace_calls` shows caller/callee graphs. Call them alongside Read, Grep, and Glob.',
      'annotate': 'Codebase annotation tool. Fire `annotate` alongside your Read and Grep calls to enrich results with structural metadata.',
      'graphrag': `# Supermodel: Codebase Intelligence

One read-only tool for instant call-graph exploration.

## Tool
- \`explore_function\`: BFS traversal of a function/class call graph. Returns source code, callers, callees, and cross-subsystem boundaries with ← DIFFERENT SUBSYSTEM markers. Supports partial matching and "ClassName.method" syntax.

## Workflow
1. Identify key symbols from the issue, call \`explore_function\` to understand their call-graph context. Issue multiple calls in parallel (read-only, safe).
2. Use file paths and source code from the response to start editing. Max 2 MCP calls total.

## Rules
- Do NOT use TodoWrite. Act directly.
- NEVER create standalone test scripts. Run the repo's existing test suite to verify.
- >2 MCP turns = diminishing returns. Get everything you need in one turn.`,
    };

    const instructions = experiment && experimentInstructions[experiment]
      ? experimentInstructions[experiment]
      : `# Supermodel: Codebase Intelligence

One read-only tool for instant codebase understanding. Pre-computed graphs enable sub-second responses.

The codebase overview is included below in these instructions — you already have the architecture map.

## Recommended workflow
1. Identify symbols from the issue/overview and call \`symbol_context\` to explore them.
   Batch via \`symbols\` array or issue multiple calls in parallel (read-only, safe).
2. Stop calling MCP tools. Start editing by turn 3. Max 3 MCP calls total.

## Rules
- Do NOT use TodoWrite. Act directly.
- Use the Task tool to delegate subtasks (e.g. running tests, exploring tangential code).
- >2 MCP turns = diminishing returns. Explore everything you need in one turn.

## After fixing
Run the full related test suite to catch regressions. Do NOT write standalone test scripts.

## Tool reference
- \`symbol_context\`: Source, callers, callees, domain for any function/class/method.
  Supports "Class.method", partial matching, and batch lookups via \`symbols\` array.
  Use \`brief: true\` for compact output when looking up 3+ symbols.
  Read-only — safe to call in parallel.`;

    this.server = new McpServer(
      {
        name: 'supermodel_api',
        version: '0.0.1',
      },
      {
        capabilities: { tools: {}, logging: {}, prompts: {}, resources: {} },
        instructions,
      },
    );

    const config = new Configuration({
      basePath: process.env.SUPERMODEL_BASE_URL || 'https://api.supermodeltools.com',
      apiKey: process.env.SUPERMODEL_API_KEY,
      fetchApi: fetchWithTimeout,
    });

    logger.debug('Server configuration:');
    logger.debug('Base URL:', config.basePath);
    logger.debug('API Key set:', !!process.env.SUPERMODEL_API_KEY);
    if (this.defaultWorkdir) {
      logger.debug('Default workdir:', this.defaultWorkdir);
    }

    const api = new DefaultApi(config);
    this.client = {
      graphs: new SupermodelClient(api),
    };

    this.setupHandlers();
    this.setupPrompts();
    this.setupResources();
  }

  private setupHandlers() {
    const experiment = process.env.SUPERMODEL_EXPERIMENT;

    // Experiment variants: swap tool definitions to test parallel calling behavior
    let allTools: typeof symbolContextTool[];
    switch (experiment) {
      case 'minimal-schema':
        allTools = [{ tool: symbolContextMinimalTool, handler: symbolContextTool.handler }];
        break;
      case 'search-symbol':
        allTools = [searchSymbolEndpoint];
        break;
      case 'split-tools':
        allTools = [findDefinitionEndpoint, traceCallsEndpoint];
        break;
      case 'annotate':
        allTools = [annotateEndpoint];
        break;
      case 'graphrag':
        allTools = [exploreFunctionEndpoint];
        break;
      default:
        allTools = [symbolContextTool];
        break;
    }

    // Create a map for quick handler lookup
    const toolMap = new Map<string, typeof allTools[0]>();
    for (const t of allTools) {
      if (toolMap.has(t.tool.name)) {
        throw new Error(`Duplicate tool name: ${t.tool.name}`);
      }
      toolMap.set(t.tool.name, t);
    }

    this.server.server.setRequestHandler(ListToolsRequestSchema, async () => {
      return {
        tools: allTools.map(t => t.tool),
      };
    });

    this.server.server.setRequestHandler(CallToolRequestSchema, async (request) => {
      const { name, arguments: args } = request.params;

      const tool = toolMap.get(name);
      if (tool) {
        return tool.handler(this.client, args, this.defaultWorkdir);
      }

      throw new Error(`Unknown tool: ${name}`);
    });
  }

  private setupPrompts() {
    this.server.prompt(
      'lookup-symbol',
      'Look up a symbol (function, class, or method) in a codebase — returns source code, callers, callees, and architectural domain',
      {
        symbol: z.string().describe('Name of the symbol to look up (e.g. "filter_queryset", "UserModel.save")'),
        directory: z.string().optional().describe('Path to the repository directory'),
      },
      ({ symbol, directory }) => ({
        messages: [{
          role: 'user' as const,
          content: {
            type: 'text' as const,
            text: `Use the symbol_context tool to look up "${symbol}"${directory ? ` in the repository at ${directory}` : ''}. Return the definition location, source code, callers, callees, and architectural domain.`,
          },
        }],
      }),
    );

    this.server.prompt(
      'explore-architecture',
      'Explore the architectural structure of a codebase — surfaces subsystems, entry points, and key components via the codebase overview',
      {
        directory: z.string().describe('Path to the repository directory'),
        focus: z.string().optional().describe('Optional area or subsystem to focus on'),
      },
      ({ directory, focus }) => ({
        messages: [{
          role: 'user' as const,
          content: {
            type: 'text' as const,
            text: `Explore the architecture of the repository at ${directory}${focus ? `, focusing on ${focus}` : ''}. Use the symbol_context tool to look up key classes and entry points. Summarize the main subsystems, their responsibilities, and how they interact.`,
          },
        }],
      }),
    );
  }

  private setupResources() {
    this.server.resource(
      'configuration',
      'supermodel://docs/configuration',
      {
        description: 'Supermodel MCP Server configuration reference — environment variables and CLI options',
        mimeType: 'text/markdown',
      },
      async (uri) => ({
        contents: [{
          uri: uri.toString(),
          mimeType: 'text/markdown',
          text: `# Configuration Reference\n\n## Environment Variables\n\n| Variable | Required | Default | Description |\n|----------|----------|---------|-------------|\n| \`SUPERMODEL_API_KEY\` | Yes | — | API key from [dashboard.supermodeltools.com](https://dashboard.supermodeltools.com) |\n| \`SUPERMODEL_BASE_URL\` | No | \`https://api.supermodeltools.com\` | Override API endpoint |\n| \`SUPERMODEL_CACHE_DIR\` | No | — | Directory for pre-computed graph cache files |\n| \`SUPERMODEL_TIMEOUT_MS\` | No | \`900000\` | API request timeout in milliseconds |\n| \`SUPERMODEL_NO_API_FALLBACK\` | No | — | Set to disable on-demand API calls (cache-only mode) |\n| \`SUPERMODEL_EXPERIMENT\` | No | — | Experiment mode (e.g. \`graphrag\`) |\n\n## CLI Usage\n\n\`\`\`bash\nnpx @supermodeltools/mcp-server [directory] [--precache]\n\`\`\`\n\n| Argument | Description |\n|----------|-------------|\n| \`directory\` | Default working directory for tool calls |\n| \`--precache\` | Generate and cache the graph for the directory on startup |\n\n## Pre-computing Graphs\n\n\`\`\`bash\nnpx @supermodeltools/mcp-server precache /path/to/repo --output-dir ./cache\n\`\`\`\n`,
        }],
      }),
    );

    this.server.resource(
      'quickstart',
      'supermodel://docs/quickstart',
      {
        description: 'Supermodel MCP Server quick start guide — install, configure, and start using in under 5 minutes',
        mimeType: 'text/markdown',
      },
      async (uri) => ({
        contents: [{
          uri: uri.toString(),
          mimeType: 'text/markdown',
          text: `# Quick Start Guide\n\n## 1. Get an API Key\n\nSign up at [dashboard.supermodeltools.com](https://dashboard.supermodeltools.com) to get your free API key.\n\n## 2. Install\n\n\`\`\`bash\nnpm install -g @supermodeltools/mcp-server\n\`\`\`\n\nOr run directly with npx (no install needed):\n\n\`\`\`bash\nnpx @supermodeltools/mcp-server\n\`\`\`\n\n## 3. Add to Your MCP Client\n\n### Claude Code\n\n\`\`\`bash\nclaude mcp add supermodel --env SUPERMODEL_API_KEY=your-key -- npx -y @supermodeltools/mcp-server\n\`\`\`\n\n### Cursor\n\nAdd to \`~/.cursor/mcp.json\`:\n\n\`\`\`json\n{\n  "mcpServers": {\n    "supermodel": {\n      "command": "npx",\n      "args": ["-y", "@supermodeltools/mcp-server"],\n      "env": { "SUPERMODEL_API_KEY": "your-key" }\n    }\n  }\n}\n\`\`\`\n\n## 4. Use the Tools\n\n- **\`symbol_context\`**: Look up any function, class, or method with full caller/callee graph and source code\n- Supports batch lookups via the \`symbols\` array\n- Use \`brief: true\` for compact output when looking up 3+ symbols\n\n## Next Steps\n\n- Read the [configuration reference](supermodel://docs/configuration) for advanced options\n- Use \`precache\` to pre-compute graphs for faster responses\n- See the [GitHub repo](https://github.com/supermodeltools/mcp) for more\n`,
        }],
      }),
    );
  }

  private getTestHint(primaryLanguage: string): string {
    switch (primaryLanguage.toLowerCase()) {
      case 'python': return '\n\n**Test with:** `python -m pytest <test_file> -x`';
      case 'javascript':
      case 'typescript': return '\n\n**Test with:** `npm test`';
      case 'go': return '\n\n**Test with:** `go test ./...`';
      case 'rust': return '\n\n**Test with:** `cargo test`';
      case 'java': return '\n\n**Test with:** `mvn test` or `gradle test`';
      case 'ruby': return '\n\n**Test with:** `bundle exec rake test`';
      default: return '';
    }
  }

  private injectOverviewInstructions(repoMap: Map<string, import('./cache/graph-cache').IndexedGraph>) {
    if (repoMap.size === 0) return;

    // Skip overview injection during experiments to isolate variables (except graphrag)
    if (process.env.SUPERMODEL_EXPERIMENT && process.env.SUPERMODEL_EXPERIMENT !== 'graphrag') return;

    // Only inject if there's exactly 1 unique graph (SWE-bench always has exactly 1 repo)
    const uniqueGraphs = new Set([...repoMap.values()]);
    if (uniqueGraphs.size !== 1) return;

    const graph = [...uniqueGraphs][0];
    try {
      const overview = renderOverview(graph);
      const testHint = this.getTestHint(graph.summary.primaryLanguage);
      const current = (this.server.server as any)._instructions as string | undefined;
      (this.server.server as any)._instructions = (current || '') + '\n\n' + overview + testHint;
      logger.debug('Injected overview into server instructions');
    } catch (err: any) {
      logger.warn('Failed to render overview for instructions:', err.message || err);
    }
  }

  async start() {
    // Clean up any stale ZIP files from previous sessions
    await cleanupOldZips(ZIP_CLEANUP_AGE_MS);

    // Load pre-computed graphs from cache directory
    const cacheDir = process.env.SUPERMODEL_CACHE_DIR;
    if (cacheDir) {
      try {
        logger.debug('Loading pre-computed graphs from:', cacheDir);
        const repoMap = await loadCacheFromDisk(cacheDir, graphCache);
        setRepoMap(repoMap);
        logger.debug(`Loaded ${repoMap.size} repo mappings`);
        this.injectOverviewInstructions(repoMap);
      } catch (err: any) {
        logger.warn('Failed to load cache directory:', err.message || err);
      }
    }

    // Connect transport FIRST so the MCP handshake completes immediately.
    // This prevents Claude Code from timing out the server (MCP_TIMEOUT=60s)
    // when precaching requires a slow API call.
    const transport = new StdioServerTransport();
    await this.server.connect(transport);
    logger.info('Supermodel MCP Server running on stdio');

    // Precache the workdir's repo if --precache flag is set.
    // Runs AFTER connect but BEFORE noApiFallback so the API is available.
    // This is fire-and-forget from the MCP client's perspective — tools
    // that arrive before precaching finishes will use on-demand API calls.
    if (this.options?.precache && this.defaultWorkdir) {
      try {
        await precacheForDirectory(this.client, this.defaultWorkdir, cacheDir);
      } catch (err: any) {
        // Non-fatal: if precaching fails, tools fall back to on-demand API
        logger.warn('Startup precache failed:', err.message || err);
      }
    }

    // NOW enable no-api-fallback (after precaching had its chance)
    setNoApiFallback(!!this.options?.noApiFallback);
  }
}

Domain

Analyze Your Own Codebase

Get architecture documentation, dependency graphs, and domain analysis for your codebase in minutes.

Try Supermodel Free