/**
* @fileoverview MCP Server module for DeepSource integration
*
* This module provides a configurable MCP server that can be used
* to integrate DeepSource functionality with Model Context Protocol.
* It separates the server setup logic from the main entry point,
* allowing for better testability and reusability.
*
* @packageDocumentation
*/
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { createLogger } from '../utils/logging/logger.js';
import { ToolRegistry } from './tool-registry.js';
import { BaseHandlerDeps } from '../handlers/base/handler.interface.js';
import { createDefaultHandlerDeps } from '../handlers/base/handler.factory.js';
import { registerDeepSourceTools } from './tool-registration.js';
import { VERSION } from '../version.js';
const logger = createLogger('MCPServer');
/**
* Configuration options for the MCP server
*/
export interface MCPServerConfig {
/** Server name */
name?: string;
/** Server version */
version?: string;
/** Custom handler dependencies */
handlerDeps?: BaseHandlerDeps;
/** Whether to auto-register DeepSource tools */
autoRegisterTools?: boolean;
/** Custom transport (defaults to StdioServerTransport) */
transport?: StdioServerTransport;
/** Whether to start the server immediately */
autoStart?: boolean;
}
/**
* Default configuration values
*/
const DEFAULT_CONFIG: Required<Omit<MCPServerConfig, 'handlerDeps' | 'transport'>> = {
name: 'deepsource-mcp-server',
version: VERSION,
autoRegisterTools: true,
autoStart: false,
};
/**
* DeepSource MCP Server class
*
* This class encapsulates the MCP server setup and configuration,
* providing a clean API for creating and managing the server.
*/
export class DeepSourceMCPServer {
private mcpServer: McpServer;
private toolRegistry: ToolRegistry;
private transport?: StdioServerTransport;
private config: Required<Omit<MCPServerConfig, 'handlerDeps' | 'transport'>> &
Pick<MCPServerConfig, 'handlerDeps' | 'transport'>;
private isConnected = false;
constructor(config: MCPServerConfig = {}) {
this.config = { ...DEFAULT_CONFIG, ...config };
logger.info('Initializing DeepSource MCP Server', {
name: this.config.name,
version: this.config.version,
});
// Initialize MCP server
this.mcpServer = new McpServer({
name: this.config.name,
version: this.config.version,
});
// Set transport FIRST (default to stdio if not provided)
// This must be set before registering tools
this.transport = this.config.transport || new StdioServerTransport();
// Initialize tool registry
const handlerDeps = this.config.handlerDeps || createDefaultHandlerDeps();
this.toolRegistry = new ToolRegistry(this.mcpServer, handlerDeps);
// Auto-register tools if configured (AFTER transport is set)
if (this.config.autoRegisterTools) {
this.registerDefaultTools();
}
}
/**
* Registers the default DeepSource tools
*/
private registerDefaultTools(): void {
try {
logger.info('Starting registration of default DeepSource tools', {
mcpServerType: typeof this.mcpServer,
mcpServerExists: Boolean(this.mcpServer),
toolRegistryExists: Boolean(this.toolRegistry),
});
registerDeepSourceTools(this.toolRegistry);
const registeredTools = this.toolRegistry.getToolNames();
logger.info('Successfully registered DeepSource tools', {
registeredTools,
toolCount: registeredTools.length,
});
// Verify tools are actually registered with the MCP server
// The MCP server doesn't expose a direct way to query tools,
// but we can log what we've registered
logger.info('Tool registration verification', {
toolRegistryCount: registeredTools.length,
mcpServerConnected: this.isConnected,
transportSet: Boolean(this.transport),
});
} catch (error) {
logger.error('Failed to register DeepSource tools', {
error,
message: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
});
throw error;
}
}
/**
* Gets the MCP server instance
*/
getMcpServer(): McpServer {
return this.mcpServer;
}
/**
* Gets the tool registry
*/
getToolRegistry(): ToolRegistry {
return this.toolRegistry;
}
/**
* Connects the server to the transport
*
* @param transport - Optional transport to use (overrides constructor transport)
*/
async connect(transport?: StdioServerTransport): Promise<void> {
if (this.isConnected) {
logger.warn('Server is already connected');
return;
}
const transportToUse = transport || this.transport || new StdioServerTransport();
logger.info('Connecting MCP server to transport');
await this.mcpServer.connect(transportToUse);
this.isConnected = true;
logger.info('MCP server connected successfully');
}
/**
* Starts the server (convenience method that creates transport and connects)
*/
async start(): Promise<void> {
if (!this.transport) {
this.transport = new StdioServerTransport();
}
await this.connect();
}
/**
* Checks if the server is connected
*/
isServerConnected(): boolean {
return this.isConnected;
}
/**
* Updates handler dependencies
*
* @param deps - New handler dependencies
*/
updateHandlerDeps(deps: BaseHandlerDeps): void {
this.toolRegistry.updateDefaultDeps(deps);
logger.info('Handler dependencies updated');
}
/**
* Gets registered tool names
*/
getRegisteredTools(): string[] {
return this.toolRegistry.getToolNames();
}
/**
* Discovers and loads tools from filesystem (requires FEATURE_TOOL_DISCOVERY)
*
* @param options - Discovery options
* @returns Array of discovered tool names
*/
async discoverTools(options?: Record<string, unknown>): Promise<string[]> {
logger.info('Starting tool discovery from DeepSourceMCPServer');
return this.toolRegistry.discoverTools(options);
}
/**
* Gets enhanced tool information including metadata
*/
getToolsInfo(): Array<{
name: string;
description: string;
category?: string;
version?: string;
tags?: string[];
enabled?: boolean;
discovered?: boolean;
}> {
return this.toolRegistry.getToolsInfo();
}
/**
* Creates and optionally starts a DeepSource MCP server
*
* @param config - Server configuration
* @returns The created server instance
*/
static async create(config: MCPServerConfig = {}): Promise<DeepSourceMCPServer> {
const server = new DeepSourceMCPServer(config);
if (config.autoStart) {
await server.start();
}
return server;
}
}
/**
* Convenience function to create a server with default configuration
*
* @param autoStart - Whether to start the server immediately
* @returns The created server instance
*/
export async function createDeepSourceMCPServer(autoStart = false): Promise<DeepSourceMCPServer> {
return DeepSourceMCPServer.create({ autoStart });
}