#!/usr/bin/env node
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 all tools and handlers
import { authTools, authHandlers } from './tools/auth.js';
import { adminTools, adminHandlers } from './tools/admin.js';
import { collectionTools, collectionHandlers } from './tools/collections.js';
import { recordTools, recordHandlers } from './tools/records.js';
import { fileTools, fileHandlers } from './tools/files.js';
import { systemTools, systemHandlers } from './tools/system.js';
// Import utilities
import { logger } from './utils/logger.js';
import { appConfig } from './utils/config.js';
import { pocketBaseService } from './pocketbase-service.js';
// Server information
const SERVER_INFO = {
name: 'pocketbase-mcp-server',
version: '1.0.0',
description: 'A comprehensive MCP server for PocketBase with user and admin functionality',
author: 'Claude Code',
};
// Combine all tools and handlers
const allTools = [
...authTools,
...adminTools,
...collectionTools,
...recordTools,
...fileTools,
...systemTools,
];
const allHandlers = {
...authHandlers,
...adminHandlers,
...collectionHandlers,
...recordHandlers,
...fileHandlers,
...systemHandlers,
};
class PocketBaseMCPServer {
private server: Server;
constructor() {
this.server = new Server(
{
name: SERVER_INFO.name,
version: SERVER_INFO.version,
},
{
capabilities: {
tools: {},
},
},
);
this.setupHandlers();
this.setupErrorHandling();
}
private setupHandlers(): void {
// Handle tool listing
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
logger.info('Listing available tools', { count: allTools.length });
return {
tools: allTools.map((tool) => ({
name: tool.name,
description: tool.description,
inputSchema: tool.inputSchema,
})),
};
});
// Handle tool calls
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
logger.info('Tool call received', {
toolName: name,
hasArgs: Boolean(args),
argsKeys: args ? Object.keys(args) : [],
});
try {
// Find the handler for this tool
const handler = allHandlers[name as keyof typeof allHandlers];
if (!handler) {
const availableTools = Object.keys(allHandlers).join(', ');
const errorMessage = `Unknown tool: ${name}. Available tools: ${availableTools}`;
logger.error('Unknown tool called', { toolName: name, availableTools });
return {
content: [
{
type: 'text',
text: errorMessage,
},
],
isError: true,
};
}
// Call the handler with the provided arguments
const result = await handler(args || {});
logger.info('Tool call completed', {
toolName: name,
success: !result.isError,
});
return {
content: result.content,
isError: result.isError,
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
logger.error('Tool call failed', {
toolName: name,
error: errorMessage,
stack: error instanceof Error ? error.stack : undefined,
});
return {
content: [
{
type: 'text',
text: `Tool execution failed: ${errorMessage}`,
},
],
isError: true,
};
}
});
}
private setupErrorHandling(): void {
// Handle uncaught errors
process.on('uncaughtException', (error) => {
logger.error('Uncaught exception', error);
process.exit(1);
});
process.on('unhandledRejection', (reason, promise) => {
logger.error('Unhandled rejection', { reason, promise });
process.exit(1);
});
// Graceful shutdown
process.on('SIGINT', () => {
logger.info('Received SIGINT, shutting down gracefully');
this.shutdown();
});
process.on('SIGTERM', () => {
logger.info('Received SIGTERM, shutting down gracefully');
this.shutdown();
});
}
private shutdown(): void {
logger.info('Shutting down PocketBase MCP Server');
process.exit(0);
}
async start(): Promise<void> {
try {
logger.info('Starting PocketBase MCP Server', {
name: SERVER_INFO.name,
version: SERVER_INFO.version,
environment: appConfig.server.environment,
pocketbaseUrl: appConfig.pocketbase.url,
totalTools: allTools.length,
});
// Test PocketBase connection during startup
try {
await pocketBaseService.healthCheck();
logger.info('PocketBase connection verified');
} catch (error) {
logger.warn('PocketBase health check failed during startup', error);
// Continue anyway - connection issues will be handled per-request
}
// Create transport and connect
const transport = new StdioServerTransport();
await this.server.connect(transport);
logger.info('PocketBase MCP Server started successfully', {
toolsRegistered: allTools.map((tool) => tool.name),
});
} catch (error) {
logger.error('Failed to start PocketBase MCP Server', error);
process.exit(1);
}
}
}
// Start the server if this file is run directly
if (import.meta.url === `file://${process.argv[1]}`) {
const server = new PocketBaseMCPServer();
server.start().catch((error) => {
logger.error('Failed to start server', error);
process.exit(1);
});
}
export { PocketBaseMCPServer };