From 2ad03d2f7d2526bec6ced705547dc5aa63cd81dc Mon Sep 17 00:00:00 2001 From: Colby McHenry Date: Tue, 10 Feb 2026 02:26:47 -0600 Subject: [PATCH] updates --- README.md | 2 ++ package-lock.json | 4 +-- package.json | 2 +- src/mcp/index.ts | 89 ++++++++++++++++++++++++++++++++--------------- src/mcp/tools.ts | 19 +++++++++- 5 files changed, 83 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index 6ffb007..b04c027 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,8 @@ **30% fewer tokens • 25% fewer tool calls • 100% local** +**Works on Windows, macOS, and Linux** + [![npm version](https://img.shields.io/npm/v/@colbymchenry/codegraph.svg)](https://www.npmjs.com/package/@colbymchenry/codegraph) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![Node.js](https://img.shields.io/badge/Node.js-18+-green.svg)](https://nodejs.org/) diff --git a/package-lock.json b/package-lock.json index ef53f19..7e895fc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@colbymchenry/codegraph", - "version": "0.4.1", + "version": "0.4.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@colbymchenry/codegraph", - "version": "0.4.1", + "version": "0.4.3", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 29f697b..0823497 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@colbymchenry/codegraph", - "version": "0.4.1", + "version": "0.4.3", "description": "Supercharge Claude Code with semantic code intelligence. 30% fewer tokens, 25% fewer tool calls, 100% local.", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/src/mcp/index.ts b/src/mcp/index.ts index e35b61a..6ae6f69 100644 --- a/src/mcp/index.ts +++ b/src/mcp/index.ts @@ -15,6 +15,7 @@ * ``` */ +import * as path from 'path'; import CodeGraph, { findNearestCodeGraphRoot } from '../index'; import { StdioTransport, JsonRpcRequest, JsonRpcNotification, ErrorCodes } from './transport'; import { tools, ToolHandler } from './tools'; @@ -22,6 +23,25 @@ import { initSentry, captureException } from '../sentry'; initSentry({ processName: 'codegraph-mcp' }); +/** + * Convert a file:// URI to a filesystem path. + * Handles URL encoding and Windows drive letter paths. + */ +function fileUriToPath(uri: string): string { + try { + const url = new URL(uri); + let filePath = decodeURIComponent(url.pathname); + // On Windows, file:///C:/path produces pathname /C:/path — strip leading / + if (process.platform === 'win32' && /^\/[a-zA-Z]:/.test(filePath)) { + filePath = filePath.slice(1); + } + return path.resolve(filePath); + } catch { + // Fallback for non-standard URIs + return uri.replace(/^file:\/\/\/?/, ''); + } +} + /** * MCP Server Info */ @@ -44,13 +64,14 @@ const PROTOCOL_VERSION = '2024-11-05'; export class MCPServer { private transport: StdioTransport; private cg: CodeGraph | null = null; - private toolHandler: ToolHandler | null = null; + private toolHandler: ToolHandler; private projectPath: string | null; - private initError: string | null = null; constructor(projectPath?: string) { this.projectPath = projectPath || null; this.transport = new StdioTransport(); + // Create ToolHandler eagerly — cross-project queries work even without a default project + this.toolHandler = new ToolHandler(null); } /** @@ -70,18 +91,21 @@ export class MCPServer { } /** - * Initialize CodeGraph for the project + * Try to initialize CodeGraph for the default project. * * Walks up parent directories to find the nearest .codegraph/ folder, * similar to how git finds .git/ directories. + * + * If initialization fails, the error is recorded but the server continues + * to work — cross-project queries and retries on subsequent tool calls + * are still possible. */ - private async initializeCodeGraph(projectPath: string): Promise { + private async tryInitializeDefault(projectPath: string): Promise { // Walk up parent directories to find nearest .codegraph/ const resolvedRoot = findNearestCodeGraphRoot(projectPath); if (!resolvedRoot) { this.projectPath = projectPath; - this.initError = `CodeGraph not initialized in ${projectPath}. Run 'codegraph init' first.`; return; } @@ -89,11 +113,31 @@ export class MCPServer { try { this.cg = await CodeGraph.open(resolvedRoot); - this.toolHandler = new ToolHandler(this.cg); - this.initError = null; + this.toolHandler.setDefaultCodeGraph(this.cg); } catch (err) { captureException(err); - this.initError = `Failed to open CodeGraph: ${err instanceof Error ? err.message : String(err)}`; + } + } + + /** + * Retry initialization of the default project if it previously failed. + * Called lazily on tool calls that need the default project. + */ + private retryInitIfNeeded(): void { + // Already initialized successfully + if (this.toolHandler.hasDefaultCodeGraph()) return; + // No project path to retry with + if (!this.projectPath) return; + + const resolvedRoot = findNearestCodeGraphRoot(this.projectPath); + if (!resolvedRoot) return; + + try { + this.cg = CodeGraph.openSync(resolvedRoot); + this.projectPath = resolvedRoot; + this.toolHandler.setDefaultCodeGraph(this.cg); + } catch { + // Still failing — will retry on next tool call } } @@ -102,9 +146,7 @@ export class MCPServer { */ stop(): void { // Close all cached cross-project connections first - if (this.toolHandler) { - this.toolHandler.closeAll(); - } + this.toolHandler.closeAll(); // Close the main CodeGraph instance if (this.cg) { this.cg.close(); @@ -175,10 +217,9 @@ export class MCPServer { let projectPath = this.projectPath; if (params?.rootUri) { - // Convert file:// URI to path - projectPath = params.rootUri.replace(/^file:\/\//, ''); + projectPath = fileUriToPath(params.rootUri); } else if (params?.workspaceFolders?.[0]?.uri) { - projectPath = params.workspaceFolders[0].uri.replace(/^file:\/\//, ''); + projectPath = fileUriToPath(params.workspaceFolders[0].uri); } // Fall back to current working directory if no path provided @@ -186,8 +227,8 @@ export class MCPServer { projectPath = process.cwd(); } - // Initialize CodeGraph if we have a project path - await this.initializeCodeGraph(projectPath); + // Try to initialize the default project (non-fatal if it fails) + await this.tryInitializeDefault(projectPath); // We accept the client's protocol version but respond with our supported version this.transport.sendResult(request.id, { @@ -240,19 +281,9 @@ export class MCPServer { return; } - // Execute the tool - if (!this.toolHandler) { - const errorMsg = this.initError || - (this.projectPath - ? `CodeGraph not initialized in ${this.projectPath}. Run 'codegraph init' first.` - : 'No project path provided. Ensure Claude Code is running in a project directory.'); - this.transport.sendError( - request.id, - ErrorCodes.InternalError, - errorMsg - ); - return; - } + // If the default project isn't initialized yet, retry in case it was + // initialized after the MCP server started (e.g. user ran codegraph init) + this.retryInitIfNeeded(); const result = await this.toolHandler.execute(toolName, toolArgs); diff --git a/src/mcp/tools.ts b/src/mcp/tools.ts index 7d3280c..de9c3d7 100644 --- a/src/mcp/tools.ts +++ b/src/mcp/tools.ts @@ -259,7 +259,21 @@ export class ToolHandler { // Cache of opened CodeGraph instances for cross-project queries private projectCache: Map = new Map(); - constructor(private cg: CodeGraph) {} + constructor(private cg: CodeGraph | null) {} + + /** + * Update the default CodeGraph instance (e.g. after lazy initialization) + */ + setDefaultCodeGraph(cg: CodeGraph): void { + this.cg = cg; + } + + /** + * Whether a default CodeGraph instance is available + */ + hasDefaultCodeGraph(): boolean { + return this.cg !== null; + } /** * Get CodeGraph instance for a project @@ -272,6 +286,9 @@ export class ToolHandler { */ private getCodeGraph(projectPath?: string): CodeGraph { if (!projectPath) { + if (!this.cg) { + throw new Error('CodeGraph not initialized for this project. Run \'codegraph init\' first.'); + } return this.cg; }