Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
145 changes: 145 additions & 0 deletions packages/mcp-server/CURSOR_SETUP.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
# Cursor MCP Setup Guide

This guide shows how to integrate the dev-agent MCP server with Cursor IDE.

## Prerequisites

1. Build the MCP server:
```bash
cd /path/to/dev-agent
pnpm install
pnpm build
```

2. Ensure the repository is indexed:
```bash
pnpm dev scan
```

## Configuration

### 1. Locate Cursor's MCP Config

The MCP configuration file is located at:
- **macOS**: `~/Library/Application Support/Cursor/User/globalStorage/mcp.json`
- **Linux**: `~/.config/Cursor/User/globalStorage/mcp.json`
- **Windows**: `%APPDATA%\Cursor\User\globalStorage\mcp.json`

### 2. Add dev-agent MCP Server

Create or update the `mcp.json` file with the following configuration:

```json
{
"mcpServers": {
"dev-agent": {
"command": "node",
"args": [
"/absolute/path/to/dev-agent/packages/mcp-server/dist/bin/dev-agent-mcp.js"
],
"env": {
"REPOSITORY_PATH": "/absolute/path/to/your/repository",
"LOG_LEVEL": "info"
}
}
}
}
```

**Important:** Replace the paths with your actual absolute paths.

### 3. Restart Cursor

After updating the configuration, restart Cursor to load the MCP server.

## Verification

1. Open Cursor
2. Try using the `dev_search` tool in a chat:
```
Search for "authentication middleware" in the codebase
```

3. The MCP server should respond with semantic search results

## Available Tools

### `dev_search`

Semantic code search across your repository.

**Parameters:**
- `query` (required): Natural language search query
- `format` (optional): `compact` (default) or `verbose`
- `limit` (optional): Number of results (1-50, default: 10)
- `scoreThreshold` (optional): Minimum relevance score (0-1, default: 0)

**Example:**
```
dev_search:
query: "user authentication logic"
format: compact
limit: 5
```

## Troubleshooting

### Server Not Starting

1. Check Cursor's logs (Help > Show Logs)
2. Verify the paths in `mcp.json` are absolute and correct
3. Ensure the server builds successfully: `pnpm build`
4. Test the server manually:
```bash
echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"1.0","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}' | \
node /path/to/dev-agent/packages/mcp-server/dist/bin/dev-agent-mcp.js
```

### Repository Not Indexed

If searches return no results:
```bash
cd /path/to/your/repository
/path/to/dev-agent/packages/cli/dist/index.js scan
```

### Logs

Set `LOG_LEVEL` to `debug` in the env section of `mcp.json` for more verbose logging:
```json
"env": {
"REPOSITORY_PATH": "/path/to/repo",
"LOG_LEVEL": "debug"
}
```

## Multiple Repositories

To use dev-agent with multiple repositories, add multiple server configurations:

```json
{
"mcpServers": {
"dev-agent-project-a": {
"command": "node",
"args": ["/path/to/dev-agent/packages/mcp-server/dist/bin/dev-agent-mcp.js"],
"env": {
"REPOSITORY_PATH": "/path/to/project-a"
}
},
"dev-agent-project-b": {
"command": "node",
"args": ["/path/to/dev-agent/packages/mcp-server/dist/bin/dev-agent-mcp.js"],
"env": {
"REPOSITORY_PATH": "/path/to/project-b"
}
}
}
}
```

## Next Steps

- See [README.md](./README.md) for general MCP server documentation
- See [../../docs/WORKFLOW.md](../../docs/WORKFLOW.md) for the dev-agent workflow

68 changes: 68 additions & 0 deletions packages/mcp-server/bin/dev-agent-mcp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#!/usr/bin/env node
/**
* dev-agent MCP Server Entry Point
* Starts the MCP server with stdio transport for AI tools (Claude, Cursor, etc.)
*/

import { RepositoryIndexer } from '@lytics/dev-agent-core';
import { SearchAdapter } from '../src/adapters/built-in/search-adapter';
import { MCPServer } from '../src/server/mcp-server';

// Get config from environment
const repositoryPath = process.env.REPOSITORY_PATH || process.cwd();
const vectorStorePath =
process.env.VECTOR_STORE_PATH || `${repositoryPath}/.dev-agent/vectors.lance`;
const logLevel = (process.env.LOG_LEVEL as 'debug' | 'info' | 'warn' | 'error') || 'info';

async function main() {
try {
// Initialize repository indexer
const indexer = new RepositoryIndexer({
repositoryPath,
vectorStorePath,
});

await indexer.initialize();

// Create and register adapters
const searchAdapter = new SearchAdapter({
repositoryIndexer: indexer,
defaultFormat: 'compact',
defaultLimit: 10,
});

// Create MCP server
const server = new MCPServer({
serverInfo: {
name: 'dev-agent',
version: '0.1.0',
},
config: {
repositoryPath,
logLevel,
},
transport: 'stdio',
adapters: [searchAdapter],
});

// Handle graceful shutdown
const shutdown = async () => {
await server.stop();
await indexer.close();
process.exit(0);
};

process.on('SIGINT', shutdown);
process.on('SIGTERM', shutdown);

// Start server
await server.start();

// Keep process alive (server runs until stdin closes or signal received)
} catch (error) {
console.error('Failed to start MCP server:', error);
process.exit(1);
}
}

main();
2 changes: 1 addition & 1 deletion packages/mcp-server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"bin": {
"dev-agent-mcp": "./dist/index.js"
"dev-agent-mcp": "./dist/bin/dev-agent-mcp.js"
},
"scripts": {
"build": "tsc",
Expand Down
51 changes: 41 additions & 10 deletions packages/mcp-server/src/server/mcp-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,9 @@ import type {
ErrorCode,
InitializeResult,
JSONRPCRequest,
JSONRPCResponse,
MCPMethod,
ServerCapabilities,
ServerInfo,
ToolCall,
} from './protocol/types';
import { StdioTransport } from './transport/stdio-transport';
import type { Transport, TransportMessage } from './transport/transport';
Expand All @@ -32,9 +30,10 @@ export interface MCPServerConfig {
export class MCPServer {
private registry: AdapterRegistry;
private transport: Transport;
private logger = new ConsoleLogger('[MCP Server]');
private logger = new ConsoleLogger('[MCP Server]', 'debug'); // Enable debug logging
private config: Config;
private serverInfo: ServerInfo;
private clientProtocolVersion?: string;

constructor(config: MCPServerConfig) {
this.config = config.config;
Expand Down Expand Up @@ -105,17 +104,39 @@ export class MCPServer {
* Handle incoming MCP message
*/
private async handleMessage(message: TransportMessage): Promise<void> {
// Only handle requests (not notifications for now)
this.logger.debug('Raw message received', {
type: typeof message,
isRequest: JSONRPCHandler.isRequest(message),
preview: JSON.stringify(message).substring(0, 200),
});

// Handle notifications
if (!JSONRPCHandler.isRequest(message)) {
this.logger.debug('Ignoring notification', { method: message.method });
const method = (message as { method: string }).method;
this.logger.info('Received notification', { method });

// The 'initialized' notification is sent by the client after receiving
// the initialize response. We acknowledge it but don't need to respond.
if (method === 'initialized') {
this.logger.info('Client initialized successfully');
}

return;
}

const request = message as JSONRPCRequest;
this.logger.info('Received request', { method: request.method, id: request.id });

try {
const result = await this.routeRequest(request);
const response = JSONRPCHandler.createResponse(request.id!, result);
// request.id is guaranteed to be defined for requests (checked by isRequest)
const requestId = request.id ?? 0;
const response = JSONRPCHandler.createResponse(requestId, result);
this.logger.debug('Sending response', {
id: request.id,
method: request.method,
responsePreview: JSON.stringify(response).substring(0, 200),
});
await this.transport.send(response);
} catch (error) {
this.logger.error('Request handling failed', {
Expand All @@ -124,7 +145,8 @@ export class MCPServer {
});

const jsonrpcError = error as { code: ErrorCode; message: string; data?: unknown };
const errorResponse = JSONRPCHandler.createErrorResponse(request.id, jsonrpcError);
const requestId = request.id ?? 0;
const errorResponse = JSONRPCHandler.createErrorResponse(requestId, jsonrpcError);
await this.transport.send(errorResponse);
}
}
Expand All @@ -137,7 +159,7 @@ export class MCPServer {

switch (method) {
case 'initialize':
return this.handleInitialize();
return this.handleInitialize(request.params as { protocolVersion?: string });

case 'tools/list':
return this.handleToolsList();
Expand All @@ -161,15 +183,24 @@ export class MCPServer {
/**
* Handle initialize request
*/
private handleInitialize(): InitializeResult {
private handleInitialize(params?: { protocolVersion?: string }): InitializeResult {
// Store the client's protocol version and echo it back
// MCP uses date-based versioning (e.g., "2025-06-18")
this.clientProtocolVersion = params?.protocolVersion || '1.0';

this.logger.debug('Initialize request', {
clientProtocolVersion: this.clientProtocolVersion,
clientParams: params,
});

const capabilities: ServerCapabilities = {
tools: { supported: true },
resources: { supported: false }, // Not yet implemented
prompts: { supported: false }, // Not yet implemented
};

return {
protocolVersion: '1.0',
protocolVersion: this.clientProtocolVersion,
capabilities,
serverInfo: this.serverInfo,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ export class StdioTransport extends Transport {
}

// Create readline interface for line-by-line input
// Note: We don't specify 'output' to avoid readline echoing input to stdout
this.readline = readline.createInterface({
input: process.stdin,
output: process.stdout,
terminal: false,
});

Expand Down
20 changes: 15 additions & 5 deletions packages/mcp-server/src/utils/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,30 @@
import type { Logger } from '../adapters/types';

export class ConsoleLogger implements Logger {
constructor(private prefix = '[MCP]') {}
constructor(
private prefix = '[MCP]',
private minLevel: 'debug' | 'info' | 'warn' | 'error' = 'info'
) {}

debug(message: string, meta?: Record<string, unknown>): void {
if (process.env.DEBUG) {
console.debug(`${this.prefix} DEBUG:`, message, meta || '');
if (this.minLevel === 'debug') {
// MCP requires all logs on stderr (stdout is for JSON-RPC only)
console.error(`${this.prefix} DEBUG:`, message, meta || '');
}
}

info(message: string, meta?: Record<string, unknown>): void {
console.info(`${this.prefix} INFO:`, message, meta ? JSON.stringify(meta) : '');
if (this.minLevel === 'debug' || this.minLevel === 'info') {
// MCP requires all logs on stderr (stdout is for JSON-RPC only)
console.error(`${this.prefix} INFO:`, message, meta ? JSON.stringify(meta) : '');
}
}

warn(message: string, meta?: Record<string, unknown>): void {
console.warn(`${this.prefix} WARN:`, message, meta ? JSON.stringify(meta) : '');
if (this.minLevel !== 'error') {
// MCP requires all logs on stderr (stdout is for JSON-RPC only)
console.error(`${this.prefix} WARN:`, message, meta ? JSON.stringify(meta) : '');
}
}

error(message: string | Error, meta?: Record<string, unknown>): void {
Expand Down
4 changes: 2 additions & 2 deletions packages/mcp-server/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src",
"rootDir": ".",
"composite": true,
"declaration": true,
"declarationMap": true
},
"include": ["src/**/*"],
"include": ["src/**/*", "bin/**/*"],
"exclude": ["node_modules", "dist", "**/*.test.ts"],
"references": [
{ "path": "../core" },
Expand Down