This commit is contained in:
Colby McHenry
2026-02-10 02:26:47 -06:00
parent 4c983ba082
commit 2ad03d2f7d
5 changed files with 83 additions and 33 deletions
+2
View File
@@ -6,6 +6,8 @@
**30% fewer tokens • 25% fewer tool calls • 100% local** **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) [![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) [![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/) [![Node.js](https://img.shields.io/badge/Node.js-18+-green.svg)](https://nodejs.org/)
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "@colbymchenry/codegraph", "name": "@colbymchenry/codegraph",
"version": "0.4.1", "version": "0.4.3",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@colbymchenry/codegraph", "name": "@colbymchenry/codegraph",
"version": "0.4.1", "version": "0.4.3",
"hasInstallScript": true, "hasInstallScript": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@colbymchenry/codegraph", "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.", "description": "Supercharge Claude Code with semantic code intelligence. 30% fewer tokens, 25% fewer tool calls, 100% local.",
"main": "dist/index.js", "main": "dist/index.js",
"types": "dist/index.d.ts", "types": "dist/index.d.ts",
+59 -28
View File
@@ -15,6 +15,7 @@
* ``` * ```
*/ */
import * as path from 'path';
import CodeGraph, { findNearestCodeGraphRoot } from '../index'; import CodeGraph, { findNearestCodeGraphRoot } from '../index';
import { StdioTransport, JsonRpcRequest, JsonRpcNotification, ErrorCodes } from './transport'; import { StdioTransport, JsonRpcRequest, JsonRpcNotification, ErrorCodes } from './transport';
import { tools, ToolHandler } from './tools'; import { tools, ToolHandler } from './tools';
@@ -22,6 +23,25 @@ import { initSentry, captureException } from '../sentry';
initSentry({ processName: 'codegraph-mcp' }); 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 * MCP Server Info
*/ */
@@ -44,13 +64,14 @@ const PROTOCOL_VERSION = '2024-11-05';
export class MCPServer { export class MCPServer {
private transport: StdioTransport; private transport: StdioTransport;
private cg: CodeGraph | null = null; private cg: CodeGraph | null = null;
private toolHandler: ToolHandler | null = null; private toolHandler: ToolHandler;
private projectPath: string | null; private projectPath: string | null;
private initError: string | null = null;
constructor(projectPath?: string) { constructor(projectPath?: string) {
this.projectPath = projectPath || null; this.projectPath = projectPath || null;
this.transport = new StdioTransport(); 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, * Walks up parent directories to find the nearest .codegraph/ folder,
* similar to how git finds .git/ directories. * 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<void> { private async tryInitializeDefault(projectPath: string): Promise<void> {
// Walk up parent directories to find nearest .codegraph/ // Walk up parent directories to find nearest .codegraph/
const resolvedRoot = findNearestCodeGraphRoot(projectPath); const resolvedRoot = findNearestCodeGraphRoot(projectPath);
if (!resolvedRoot) { if (!resolvedRoot) {
this.projectPath = projectPath; this.projectPath = projectPath;
this.initError = `CodeGraph not initialized in ${projectPath}. Run 'codegraph init' first.`;
return; return;
} }
@@ -89,11 +113,31 @@ export class MCPServer {
try { try {
this.cg = await CodeGraph.open(resolvedRoot); this.cg = await CodeGraph.open(resolvedRoot);
this.toolHandler = new ToolHandler(this.cg); this.toolHandler.setDefaultCodeGraph(this.cg);
this.initError = null;
} catch (err) { } catch (err) {
captureException(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 { stop(): void {
// Close all cached cross-project connections first // Close all cached cross-project connections first
if (this.toolHandler) {
this.toolHandler.closeAll(); this.toolHandler.closeAll();
}
// Close the main CodeGraph instance // Close the main CodeGraph instance
if (this.cg) { if (this.cg) {
this.cg.close(); this.cg.close();
@@ -175,10 +217,9 @@ export class MCPServer {
let projectPath = this.projectPath; let projectPath = this.projectPath;
if (params?.rootUri) { if (params?.rootUri) {
// Convert file:// URI to path projectPath = fileUriToPath(params.rootUri);
projectPath = params.rootUri.replace(/^file:\/\//, '');
} else if (params?.workspaceFolders?.[0]?.uri) { } 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 // Fall back to current working directory if no path provided
@@ -186,8 +227,8 @@ export class MCPServer {
projectPath = process.cwd(); projectPath = process.cwd();
} }
// Initialize CodeGraph if we have a project path // Try to initialize the default project (non-fatal if it fails)
await this.initializeCodeGraph(projectPath); await this.tryInitializeDefault(projectPath);
// We accept the client's protocol version but respond with our supported version // We accept the client's protocol version but respond with our supported version
this.transport.sendResult(request.id, { this.transport.sendResult(request.id, {
@@ -240,19 +281,9 @@ export class MCPServer {
return; return;
} }
// Execute the tool // If the default project isn't initialized yet, retry in case it was
if (!this.toolHandler) { // initialized after the MCP server started (e.g. user ran codegraph init)
const errorMsg = this.initError || this.retryInitIfNeeded();
(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;
}
const result = await this.toolHandler.execute(toolName, toolArgs); const result = await this.toolHandler.execute(toolName, toolArgs);
+18 -1
View File
@@ -259,7 +259,21 @@ export class ToolHandler {
// Cache of opened CodeGraph instances for cross-project queries // Cache of opened CodeGraph instances for cross-project queries
private projectCache: Map<string, CodeGraph> = new Map(); private projectCache: Map<string, CodeGraph> = 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 * Get CodeGraph instance for a project
@@ -272,6 +286,9 @@ export class ToolHandler {
*/ */
private getCodeGraph(projectPath?: string): CodeGraph { private getCodeGraph(projectPath?: string): CodeGraph {
if (!projectPath) { if (!projectPath) {
if (!this.cg) {
throw new Error('CodeGraph not initialized for this project. Run \'codegraph init\' first.');
}
return this.cg; return this.cg;
} }