cc-inspect-mcp
Version:
MCP server for inspecting and searching Claude Code conversation history with boolean search support
259 lines (258 loc) • 9.61 kB
JavaScript
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
import { SessionLoader } from './sessionLoader.js';
import { SearchEngine } from './search.js';
import { ConfigLoader } from './config.js';
import { SecurityValidator } from './security.js';
// Load configuration
const configLoader = new ConfigLoader();
const config = configLoader.get();
// Validate configuration
const configErrors = configLoader.validate();
if (configErrors.length > 0) {
console.error('Configuration errors:', configErrors);
process.exit(1);
}
// Initialize components with config
const sessionLoader = new SessionLoader(config.sessionsDir, config.excludedProjects, config.allowedProjects, config.enableLogging);
const searchEngine = new SearchEngine();
const security = new SecurityValidator(config.sessionsDir);
// Load sessions on startup
if (config.enableLogging) {
console.error('Initializing CC Inspect MCP Server...');
}
await sessionLoader.loadSessions();
// Create MCP server
const server = new Server({
name: 'cc-inspect',
version: '1.0.0',
}, {
capabilities: {
tools: {},
},
});
// Define tools
const tools = [
{
name: 'searchProjects',
description: 'Search for Claude Code projects by name, path, or time range using CC Inspect',
inputSchema: {
type: 'object',
properties: {
query: {
type: 'string',
description: 'Search in project names/paths'
},
startTime: {
type: 'string',
description: 'ISO 8601 timestamp - projects active after this time'
},
endTime: {
type: 'string',
description: 'ISO 8601 timestamp - projects active before this time'
},
sortBy: {
type: 'string',
enum: ['recent', 'messageCount', 'name'],
description: 'How to sort results'
},
limit: {
type: 'number',
description: 'Maximum number of results (default: 50)'
},
offset: {
type: 'number',
description: 'Offset for pagination'
}
}
}
},
{
name: 'searchMessages',
description: 'Search messages in Claude Code conversation history',
inputSchema: {
type: 'object',
properties: {
query: {
type: 'string',
description: 'Text to search for in messages'
},
sessionId: {
type: 'string',
description: 'Get messages from a specific session'
},
projectPath: {
type: 'string',
description: 'Filter by project path'
},
messageType: {
type: 'string',
enum: ['user', 'assistant', 'all'],
description: 'Filter by message type'
},
startTime: {
type: 'string',
description: 'ISO 8601 timestamp - messages after this time'
},
endTime: {
type: 'string',
description: 'ISO 8601 timestamp - messages before this time'
},
sessionStartIndex: {
type: 'number',
description: 'When sessionId provided, start from this index'
},
sessionEndIndex: {
type: 'number',
description: 'When sessionId provided, end at this index'
},
limit: {
type: 'number',
description: 'Maximum number of results (default: 50)'
},
offset: {
type: 'number',
description: 'Offset for pagination'
}
}
}
},
{
name: 'getActivitySummary',
description: 'Get a summary of activity within a time range',
inputSchema: {
type: 'object',
required: ['startTime', 'endTime', 'groupBy'],
properties: {
startTime: {
type: 'string',
description: 'ISO 8601 timestamp - start of time range'
},
endTime: {
type: 'string',
description: 'ISO 8601 timestamp - end of time range'
},
groupBy: {
type: 'string',
enum: ['project', 'session', 'hour'],
description: 'How to group the summary'
}
}
}
}
];
// Handle tool listing
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools
}));
// Handle tool calls
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
switch (name) {
case 'searchProjects': {
const projects = sessionLoader.getProjects();
const params = args || {};
// Apply config limits
if (params.limit === undefined) {
params.limit = config.defaultLimit;
}
else if (params.limit > config.maxLimit) {
params.limit = config.maxLimit;
}
// Sanitize query if provided
if (params.query && typeof params.query === 'string') {
params.query = security.sanitizeQuery(params.query);
}
const result = searchEngine.searchProjects(projects, params);
return {
content: [
{
type: 'text',
text: JSON.stringify(result, null, 2)
}
]
};
}
case 'searchMessages': {
const messages = sessionLoader.getMessages();
const sessions = new Map();
const params = args || {};
// Apply config limits
if (params.limit === undefined) {
params.limit = config.defaultLimit;
}
else if (params.limit > config.maxLimit) {
params.limit = config.maxLimit;
}
// Validate and sanitize inputs
if (params.query && typeof params.query === 'string') {
params.query = security.sanitizeQuery(params.query);
}
if (params.sessionId && typeof params.sessionId === 'string' && !security.validateSessionId(params.sessionId)) {
throw new Error('Invalid session ID format');
}
// Build sessions map
for (const msg of messages) {
const existing = sessions.get(msg.sessionId) || [];
existing.push(msg);
sessions.set(msg.sessionId, existing);
}
const result = searchEngine.searchMessages(messages, sessions, params);
return {
content: [
{
type: 'text',
text: JSON.stringify(result, null, 2)
}
]
};
}
case 'getActivitySummary': {
const messages = sessionLoader.getMessages();
// Validate required parameters
if (!args || !args.startTime || !args.endTime || !args.groupBy) {
throw new Error('Missing required parameters: startTime, endTime, and groupBy are required');
}
// Type-safe parameter passing
const params = {
startTime: String(args.startTime),
endTime: String(args.endTime),
groupBy: args.groupBy
};
const result = searchEngine.getActivitySummary(messages, params);
return {
content: [
{
type: 'text',
text: JSON.stringify(result, null, 2)
}
]
};
}
default:
throw new Error(`Unknown tool: ${name}`);
}
}
catch (error) {
if (config.enableLogging) {
console.error(`Error in tool ${name}:`, error);
}
return {
content: [
{
type: 'text',
text: `Error: ${error instanceof Error ? error.message : String(error)}`
}
]
};
}
});
// Start server
const transport = new StdioServerTransport();
await server.connect(transport);
if (config.enableLogging) {
console.error('CC Inspect MCP Server running');
}