import type { Tool } from '@modelcontextprotocol/sdk/types.js';
import { z } from 'zod';
import { pocketBaseService, PocketBaseError } from '../pocketbase-service.js';
import { logger } from '../utils/logger.js';
import type {
ToolResult,
} from '../types/mcp.js';
import {
ListCollectionsParamsSchema,
GetCollectionParamsSchema,
CreateCollectionParamsSchema,
UpdateCollectionParamsSchema,
DeleteCollectionParamsSchema,
} from '../types/mcp.js';
// Helper function to create tool results
function createResult<T = unknown>(
text: string,
isError = false,
meta?: T,
): ToolResult<T> {
const result: ToolResult<T> = {
content: [{ type: 'text', text }],
isError,
};
if (meta !== undefined) {
(result as any)._meta = meta;
}
return result;
}
// Helper function to validate parameters
function validateParams<T>(schema: z.ZodSchema<T>, params: unknown): T {
try {
return schema.parse(params);
} catch (error) {
if (error instanceof z.ZodError) {
const errors = error.errors.map((err) => `${err.path.join('.')}: ${err.message}`);
throw new Error(`Invalid parameters:\n${errors.join('\n')}`);
}
throw error;
}
}
// List collections tool
export const listCollectionsTool: Tool = {
name: 'pb_collections_list',
description: 'List all collections (admin only)',
inputSchema: {
type: 'object',
properties: {
page: {
type: 'number',
minimum: 1,
default: 1,
description: 'Page number',
},
perPage: {
type: 'number',
minimum: 1,
maximum: 500,
default: 30,
description: 'Items per page',
},
sort: {
type: 'string',
description: 'Sort criteria (e.g., "name", "-created")',
},
filter: {
type: 'string',
description: 'Filter criteria',
},
},
},
};
export async function handleListCollections(params: unknown): Promise<ToolResult> {
try {
const { page, perPage, sort, filter } = validateParams(
ListCollectionsParamsSchema,
params,
);
logger.info('Processing list collections request', { page, perPage, sort, filter });
const result = await pocketBaseService.listCollections({
page,
perPage,
sort,
filter,
});
const collections = result.items.map((collection) => ({
id: collection.id,
name: collection.name,
type: collection.type,
system: collection.system,
schema: collection.schema,
listRule: collection.listRule,
viewRule: collection.viewRule,
createRule: collection.createRule,
updateRule: collection.updateRule,
deleteRule: collection.deleteRule,
created: collection.created,
updated: collection.updated,
}));
const response = {
success: true,
pagination: {
page: result.page,
perPage: result.perPage,
totalItems: result.totalItems,
totalPages: result.totalPages,
},
collections,
};
return createResult(
`Found ${collections.length} collections (page ${page} of ${result.totalPages})`,
false,
response,
);
} catch (error) {
logger.error('List collections failed', error);
if (error instanceof PocketBaseError) {
return createResult(
`List collections failed: ${error.message}`,
true,
{ error: error.message, status: error.status },
);
}
const message = error instanceof Error ? error.message : 'Unknown error occurred';
return createResult(`List collections failed: ${message}`, true);
}
}
// Get collection tool
export const getCollectionTool: Tool = {
name: 'pb_collections_get',
description: 'Get a specific collection by ID or name (admin only)',
inputSchema: {
type: 'object',
properties: {
idOrName: {
type: 'string',
description: 'Collection ID or name',
},
},
required: ['idOrName'],
},
};
export async function handleGetCollection(params: unknown): Promise<ToolResult> {
try {
const { idOrName } = validateParams(GetCollectionParamsSchema, params);
logger.info('Processing get collection request', { idOrName });
const collection = await pocketBaseService.getCollection(idOrName);
const result = {
success: true,
collection: {
id: collection.id,
name: collection.name,
type: collection.type,
system: collection.system,
schema: collection.schema,
indexes: collection.indexes,
listRule: collection.listRule,
viewRule: collection.viewRule,
createRule: collection.createRule,
updateRule: collection.updateRule,
deleteRule: collection.deleteRule,
options: collection.options,
created: collection.created,
updated: collection.updated,
},
};
return createResult(
`Collection retrieved: ${collection.name}`,
false,
result,
);
} catch (error) {
logger.error('Get collection failed', error);
if (error instanceof PocketBaseError) {
return createResult(
`Get collection failed: ${error.message}`,
true,
{ error: error.message, status: error.status },
);
}
const message = error instanceof Error ? error.message : 'Unknown error occurred';
return createResult(`Get collection failed: ${message}`, true);
}
}
// Create collection tool
export const createCollectionTool: Tool = {
name: 'pb_collections_create',
description: 'Create a new collection (admin only)',
inputSchema: {
type: 'object',
properties: {
name: {
type: 'string',
pattern: '^[a-zA-Z0-9_]+$',
description: 'Collection name (alphanumeric and underscores only)',
},
type: {
type: 'string',
enum: ['base', 'auth', 'view'],
description: 'Collection type',
},
schema: {
type: 'array',
items: {
type: 'object',
},
description: 'Collection schema fields',
},
listRule: {
type: ['string', 'null'],
description: 'List access rule',
},
viewRule: {
type: ['string', 'null'],
description: 'View access rule',
},
createRule: {
type: ['string', 'null'],
description: 'Create access rule',
},
updateRule: {
type: ['string', 'null'],
description: 'Update access rule',
},
deleteRule: {
type: ['string', 'null'],
description: 'Delete access rule',
},
options: {
type: 'object',
description: 'Additional collection options',
},
},
required: ['name', 'type'],
},
};
export async function handleCreateCollection(params: unknown): Promise<ToolResult> {
try {
const data = validateParams(CreateCollectionParamsSchema, params);
logger.info('Processing create collection request', { name: data.name, type: data.type });
const collection = await pocketBaseService.createCollection(data);
const result = {
success: true,
collection: {
id: collection.id,
name: collection.name,
type: collection.type,
system: collection.system,
schema: collection.schema,
created: collection.created,
updated: collection.updated,
},
};
return createResult(
`Collection created successfully: ${collection.name}`,
false,
result,
);
} catch (error) {
logger.error('Create collection failed', error);
if (error instanceof PocketBaseError) {
return createResult(
`Create collection failed: ${error.message}`,
true,
{ error: error.message, status: error.status },
);
}
const message = error instanceof Error ? error.message : 'Unknown error occurred';
return createResult(`Create collection failed: ${message}`, true);
}
}
// Update collection tool
export const updateCollectionTool: Tool = {
name: 'pb_collections_update',
description: 'Update an existing collection (admin only)',
inputSchema: {
type: 'object',
properties: {
idOrName: {
type: 'string',
description: 'Collection ID or name to update',
},
name: {
type: 'string',
pattern: '^[a-zA-Z0-9_]+$',
description: 'New collection name',
},
schema: {
type: 'array',
items: {
type: 'object',
},
description: 'Updated collection schema fields',
},
listRule: {
type: ['string', 'null'],
description: 'List access rule',
},
viewRule: {
type: ['string', 'null'],
description: 'View access rule',
},
createRule: {
type: ['string', 'null'],
description: 'Create access rule',
},
updateRule: {
type: ['string', 'null'],
description: 'Update access rule',
},
deleteRule: {
type: ['string', 'null'],
description: 'Delete access rule',
},
options: {
type: 'object',
description: 'Additional collection options',
},
},
required: ['idOrName'],
},
};
export async function handleUpdateCollection(params: unknown): Promise<ToolResult> {
try {
const { idOrName, ...updateData } = validateParams(UpdateCollectionParamsSchema, params);
logger.info('Processing update collection request', { idOrName });
const collection = await pocketBaseService.updateCollection(idOrName, updateData);
const result = {
success: true,
collection: {
id: collection.id,
name: collection.name,
type: collection.type,
system: collection.system,
schema: collection.schema,
listRule: collection.listRule,
viewRule: collection.viewRule,
createRule: collection.createRule,
updateRule: collection.updateRule,
deleteRule: collection.deleteRule,
created: collection.created,
updated: collection.updated,
},
};
return createResult(
`Collection updated successfully: ${collection.name}`,
false,
result,
);
} catch (error) {
logger.error('Update collection failed', error);
if (error instanceof PocketBaseError) {
return createResult(
`Update collection failed: ${error.message}`,
true,
{ error: error.message, status: error.status },
);
}
const message = error instanceof Error ? error.message : 'Unknown error occurred';
return createResult(`Update collection failed: ${message}`, true);
}
}
// Delete collection tool
export const deleteCollectionTool: Tool = {
name: 'pb_collections_delete',
description: 'Delete a collection (admin only)',
inputSchema: {
type: 'object',
properties: {
idOrName: {
type: 'string',
description: 'Collection ID or name to delete',
},
},
required: ['idOrName'],
},
};
export async function handleDeleteCollection(params: unknown): Promise<ToolResult> {
try {
const { idOrName } = validateParams(DeleteCollectionParamsSchema, params);
logger.info('Processing delete collection request', { idOrName });
const success = await pocketBaseService.deleteCollection(idOrName);
if (success) {
return createResult(
`Collection deleted successfully: ${idOrName}`,
false,
{ success: true, deletedCollection: idOrName },
);
}
return createResult(
'Failed to delete collection',
true,
);
} catch (error) {
logger.error('Delete collection failed', error);
if (error instanceof PocketBaseError) {
return createResult(
`Delete collection failed: ${error.message}`,
true,
{ error: error.message, status: error.status },
);
}
const message = error instanceof Error ? error.message : 'Unknown error occurred';
return createResult(`Delete collection failed: ${message}`, true);
}
}
// Export all collection tools and handlers
export const collectionTools = [
listCollectionsTool,
getCollectionTool,
createCollectionTool,
updateCollectionTool,
deleteCollectionTool,
];
export const collectionHandlers = {
pb_collections_list: handleListCollections,
pb_collections_get: handleGetCollection,
pb_collections_create: handleCreateCollection,
pb_collections_update: handleUpdateCollection,
pb_collections_delete: handleDeleteCollection,
};