CouchDB MCP Server
- src
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ErrorCode,
ListToolsRequestSchema,
McpError,
} from '@modelcontextprotocol/sdk/types.js';
import {
getDatabase,
listDatabases,
deleteDatabase,
isVersion3OrHigher,
createMangoIndex,
deleteMangoIndex,
listMangoIndexes,
findDocuments,
detectVersion
} from './connection.js';
class CouchDBServer {
private server: Server;
constructor() {
this.server = new Server(
{
name: 'couchdb-mcp-server',
version: '0.1.0',
},
{
capabilities: {
tools: {},
},
}
);
this.setupToolHandlers();
this.server.onerror = (error) => console.error('[MCP Error]', error);
process.on('SIGINT', async () => {
await this.server.close();
process.exit(0);
});
}
private async setupToolHandlers() {
const version = process.env.COUCHDB_VERSION || '1.7.2';
const isV3Plus = parseInt(version.split('.')[0]) >= 3;
// List available tools
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
const baseTools = [
{
name: 'createDatabase',
description: 'Create a new CouchDB database',
inputSchema: {
type: 'object',
properties: {
dbName: {
type: 'string',
description: 'Database name',
},
},
required: ['dbName'],
},
},
{
name: 'listDatabases',
description: 'List all CouchDB databases',
inputSchema: {
type: 'object',
properties: {},
},
},
{
name: 'deleteDatabase',
description: 'Delete a CouchDB database',
inputSchema: {
type: 'object',
properties: {
dbName: {
type: 'string',
description: 'Database name to delete',
},
},
required: ['dbName'],
},
},
{
name: 'createDocument',
description: 'Create a new document or update an existing document in a database',
inputSchema: {
type: 'object',
properties: {
dbName: {
type: 'string',
description: 'Database name',
},
docId: {
type: 'string',
description: 'Document ID',
},
data: {
type: 'object',
description: 'Document data',
},
},
required: ['dbName', 'docId', 'data'],
},
},
{
name: 'getDocument',
description: 'Get a document from a database',
inputSchema: {
type: 'object',
properties: {
dbName: {
type: 'string',
description: 'Database name',
},
docId: {
type: 'string',
description: 'Document ID',
},
},
required: ['dbName', 'docId'],
},
}
];
const mangoTools = isV3Plus ? [
{
name: 'createMangoIndex',
description: 'Create a new Mango index (CouchDB 3.x+)',
inputSchema: {
type: 'object',
properties: {
dbName: {
type: 'string',
description: 'Database name',
},
indexName: {
type: 'string',
description: 'Name of the index',
},
fields: {
type: 'array',
items: {
type: 'string'
},
description: 'Fields to index',
},
},
required: ['dbName', 'indexName', 'fields'],
},
},
{
name: 'deleteMangoIndex',
description: 'Delete a Mango index (CouchDB 3.x+)',
inputSchema: {
type: 'object',
properties: {
dbName: {
type: 'string',
description: 'Database name',
},
designDoc: {
type: 'string',
description: 'Design document name',
},
indexName: {
type: 'string',
description: 'Name of the index',
},
},
required: ['dbName', 'designDoc', 'indexName'],
},
},
{
name: 'listMangoIndexes',
description: 'List all Mango indexes in a database (CouchDB 3.x+)',
inputSchema: {
type: 'object',
properties: {
dbName: {
type: 'string',
description: 'Database name',
},
},
required: ['dbName'],
},
},
{
name: 'findDocuments',
description: 'Query documents using Mango query (CouchDB 3.x+)',
inputSchema: {
type: 'object',
properties: {
dbName: {
type: 'string',
description: 'Database name',
},
query: {
type: 'object',
description: 'Mango query object',
},
},
required: ['dbName', 'query'],
},
}
] : [];
return {
tools: [...baseTools, ...mangoTools]
};
});
// Handle tool calls
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
// Version check for Mango query tools
if (!isV3Plus && ['createMangoIndex', 'deleteMangoIndex', 'listMangoIndexes', 'findDocuments'].includes(request.params.name)) {
throw new McpError(
ErrorCode.MethodNotFound,
`Tool ${request.params.name} requires CouchDB 3.x or higher`
);
}
switch (request.params.name) {
case 'createDatabase':
return this.handleCreateDatabase(request.params.arguments);
case 'listDatabases':
return this.handleListDatabases();
case 'deleteDatabase':
return this.handleDeleteDatabase(request.params.arguments);
case 'createDocument':
return this.handleCreateDocument(request.params.arguments);
case 'getDocument':
return this.handleGetDocument(request.params.arguments);
case 'createMangoIndex':
return this.handleCreateMangoIndex(request.params.arguments);
case 'deleteMangoIndex':
return this.handleDeleteMangoIndex(request.params.arguments);
case 'listMangoIndexes':
return this.handleListMangoIndexes(request.params.arguments);
case 'findDocuments':
return this.handleFindDocuments(request.params.arguments);
default:
throw new McpError(
ErrorCode.MethodNotFound,
`Unknown tool: ${request.params.name}`
);
}
});
}
private async handleCreateDatabase(args: any) {
if (!args.dbName || typeof args.dbName !== 'string') {
throw new McpError(ErrorCode.InvalidParams, 'Invalid database name');
}
try {
await getDatabase(args.dbName);
return {
content: [
{
type: 'text',
text: `Database ${args.dbName} created successfully`,
},
],
};
} catch (error: any) {
return {
content: [
{
type: 'text',
text: `Error creating database: ${error.message}`,
},
],
isError: true,
};
}
}
private async handleListDatabases() {
try {
const databases = await listDatabases();
return {
content: [
{
type: 'text',
text: JSON.stringify(databases, null, 2),
},
],
};
} catch (error: any) {
return {
content: [
{
type: 'text',
text: `Error listing databases: ${error.message}`,
},
],
isError: true,
};
}
}
private async handleDeleteDatabase(args: any) {
if (!args.dbName || typeof args.dbName !== 'string') {
throw new McpError(ErrorCode.InvalidParams, 'Invalid database name');
}
try {
await deleteDatabase(args.dbName);
return {
content: [
{
type: 'text',
text: `Database ${args.dbName} deleted successfully`,
},
],
};
} catch (error: any) {
return {
content: [
{
type: 'text',
text: `Error deleting database: ${error.message}`,
},
],
isError: true,
};
}
}
private async handleCreateDocument(args: any) {
if (!args.dbName || !args.docId || !args.data) {
throw new McpError(
ErrorCode.InvalidParams,
'Missing required parameters: dbName, docId, data'
);
}
try {
const db = await getDatabase(args.dbName);
const response = await db.insert(args.data, args.docId);
const action = args.data._rev ? 'updated' : 'created';
return {
content: [
{
type: 'text',
text: `Document ${action} with ID: ${response.id}, rev: ${response.rev}`,
},
],
};
} catch (error: any) {
const action = args.data._rev ? 'updating' : 'creating';
return {
content: [
{
type: 'text',
text: `Error ${action} document: ${error.message}`,
},
],
isError: true,
};
}
}
private async handleGetDocument(args: any) {
if (!args.dbName || !args.docId) {
throw new McpError(
ErrorCode.InvalidParams,
'Missing required parameters: dbName, docId'
);
}
try {
const db = await getDatabase(args.dbName);
const doc = await db.get(args.docId);
return {
content: [
{
type: 'text',
text: JSON.stringify(doc, null, 2),
},
],
};
} catch (error: any) {
return {
content: [
{
type: 'text',
text: `Error retrieving document: ${error.message}`,
},
],
isError: true,
};
}
}
private async handleCreateMangoIndex(args: any) {
if (!args.dbName || !args.indexName || !args.fields) {
throw new McpError(
ErrorCode.InvalidParams,
'Missing required parameters: dbName, indexName, fields'
);
}
try {
const result = await createMangoIndex(args.dbName, args.indexName, args.fields);
return {
content: [
{
type: 'text',
text: JSON.stringify(result, null, 2),
},
],
};
} catch (error: any) {
return {
content: [
{
type: 'text',
text: `Error creating Mango index: ${error.message}`,
},
],
isError: true,
};
}
}
private async handleDeleteMangoIndex(args: any) {
if (!args.dbName || !args.designDoc || !args.indexName) {
throw new McpError(
ErrorCode.InvalidParams,
'Missing required parameters: dbName, designDoc, indexName'
);
}
try {
const result = await deleteMangoIndex(args.dbName, args.designDoc, args.indexName);
return {
content: [
{
type: 'text',
text: JSON.stringify(result, null, 2),
},
],
};
} catch (error: any) {
return {
content: [
{
type: 'text',
text: `Error deleting Mango index: ${error.message}`,
},
],
isError: true,
};
}
}
private async handleListMangoIndexes(args: any) {
if (!args.dbName) {
throw new McpError(
ErrorCode.InvalidParams,
'Missing required parameter: dbName'
);
}
try {
const result = await listMangoIndexes(args.dbName);
return {
content: [
{
type: 'text',
text: JSON.stringify(result, null, 2),
},
],
};
} catch (error: any) {
return {
content: [
{
type: 'text',
text: `Error listing Mango indexes: ${error.message}`,
},
],
isError: true,
};
}
}
private async handleFindDocuments(args: any) {
if (!args.dbName || !args.query) {
throw new McpError(
ErrorCode.InvalidParams,
'Missing required parameters: dbName, query'
);
}
try {
const result = await findDocuments(args.dbName, args.query);
return {
content: [
{
type: 'text',
text: JSON.stringify(result, null, 2),
},
],
};
} catch (error: any) {
return {
content: [
{
type: 'text',
text: `Error finding documents: ${error.message}`,
},
],
isError: true,
};
}
}
async run() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
const version = await detectVersion();
console.error(`CouchDB MCP server running on stdio (CouchDB ${version})`);
}
}
const server = new CouchDBServer();
server.run().catch(console.error);