#!/usr/bin/env node
/**
* Claude Code Connector MCP Server
*
* Entry point for the MCP server that bridges Claude Desktop,
* Claude Code CLI, and Claude Code for VS Code.
*/
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema,
ListResourcesRequestSchema,
ReadResourceRequestSchema,
ListPromptsRequestSchema,
GetPromptRequestSchema
} from '@modelcontextprotocol/sdk/types.js';
import {
RegisterProjectArgs,
ListProjectsArgs,
WriteToProjectArgs,
ReadFromProjectArgs,
MCPError
} from './models/types.js';
import { registerProject } from './tools/register_project.js';
import { listProjects } from './tools/list_projects.js';
import { writeToProject } from './tools/write_to_project.js';
import { readFromProject } from './tools/read_from_project.js';
import { getProjectFilesResource } from './resources/project_files.js';
/**
* Initialize the MCP server
*/
const server = new Server(
{
name: 'claude-code-connector',
version: '0.1.0',
},
{
capabilities: {
tools: {},
resources: {},
prompts: {},
},
}
);
/**
* Handler for listing available tools
*/
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: 'register_project',
description: 'Register a project directory for Claude Code Connector access',
inputSchema: {
type: 'object',
properties: {
name: { type: 'string', description: 'Human-readable project name' },
rootPath: { type: 'string', description: 'Absolute path to project root' },
id: { type: 'string', description: 'Optional unique ID (auto-generated if not provided)' },
specPaths: { type: 'array', items: { type: 'string' }, description: 'Relative paths for spec/doc storage' },
},
required: ['name', 'rootPath'],
},
},
{
name: 'list_projects',
description: 'List all registered projects with status',
inputSchema: {
type: 'object',
properties: {
includeInactive: { type: 'boolean', description: 'Include inactive projects' }
}
}
},
{
name: 'write_to_project',
description: 'Write content to a file in registered project directory',
inputSchema: {
type: 'object',
properties: {
projectId: { type: 'string', description: 'ID of registered project' },
filePath: { type: 'string', description: 'Relative path within project' },
content: { type: 'string', description: 'File content to write' },
createDirs: { type: 'boolean', description: 'Create parent directories if they don\'t exist' },
overwrite: { type: 'boolean', description: 'Overwrite if file exists' }
},
required: ['projectId', 'filePath', 'content']
}
},
{
name: 'read_from_project',
description: 'Read file content from registered project',
inputSchema: {
type: 'object',
properties: {
projectId: { type: 'string', description: 'ID of registered project' },
filePath: { type: 'string', description: 'Relative path within project' },
startLine: { type: 'number', description: 'Start reading from line N (1-indexed)' },
endLine: { type: 'number', description: 'Stop reading at line N (1-indexed)' }
},
required: ['projectId', 'filePath']
}
}
],
};
});
/**
* Handler for tool execution
*/
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
switch (name) {
case 'register_project':
return {
content: [{ type: 'text', text: JSON.stringify(await registerProject(args as unknown as RegisterProjectArgs), null, 2) }]
};
case 'list_projects':
return {
content: [{ type: 'text', text: JSON.stringify(await listProjects(args as unknown as ListProjectsArgs), null, 2) }]
};
case 'write_to_project':
return {
content: [{ type: 'text', text: JSON.stringify(await writeToProject(args as unknown as WriteToProjectArgs), null, 2) }]
};
case 'read_from_project':
return {
content: [{ type: 'text', text: JSON.stringify(await readFromProject(args as unknown as ReadFromProjectArgs), null, 2) }]
};
default:
throw new Error(`Unknown tool: ${name}`);
}
} catch (error) {
if (error instanceof MCPError) {
return {
content: [
{
type: 'text',
text: JSON.stringify({
error: error.code,
message: error.message,
suggestion: error.suggestion,
}, null, 2),
},
],
isError: true,
};
}
throw error;
}
});
/**
* Handler for listing available resources
*/
server.setRequestHandler(ListResourcesRequestSchema, async () => {
// In a real implementation this might be dynamic, but for now we rely on templates
// or just listing what we know if possible?
// Actually, resources are usually listed via templates or static list.
// Spec says URI template: claude-code://{projectId}/files
// We can list specific resources for *known* projects.
// Import ProjectManager here to avoid top-level await issues?
// Or just fetch inside.
const { ProjectManager } = await import('./services/project_manager.js');
const pm = new ProjectManager();
const projects = await pm.getAllProjects();
return {
resources: projects.map(p => ({
uri: `claude-code://${p.id}/files`,
name: `${p.name} Files`,
description: `Browserable file tree for ${p.name}`,
mimeType: "application/json"
}))
};
});
/**
* Handler for reading resources
*/
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
const { uri } = request.params;
// Parse URI: claude-code://{projectId}/files
const match = uri.match(/^claude-code:\/\/([^\/]+)\/files$/);
if (match) {
const projectId = match[1];
try {
const result = await getProjectFilesResource(projectId);
return {
contents: [{
uri,
mimeType: "application/json",
text: JSON.stringify(result, null, 2)
}]
};
} catch (error: any) {
throw new Error(`Failed to read resource: ${error.message}`);
}
}
throw new Error(`Resource not found: ${uri}`);
});
/**
* Handler for listing available prompts
*/
server.setRequestHandler(ListPromptsRequestSchema, async () => {
return {
prompts: [],
};
});
/**
* Handler for getting prompt content
*/
server.setRequestHandler(GetPromptRequestSchema, async (request) => {
const { name } = request.params;
throw new Error(`Prompt not found: ${name}`);
});
/**
* Start the server
*/
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error('Claude Code Connector MCP server running on stdio');
}
main().catch((error) => {
console.error('Fatal error in main():', error);
process.exit(1);
});