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 {
AdminLoginParamsSchema,
ListUsersParamsSchema,
ImpersonateParamsSchema,
} 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;
}
}
// Admin login tool
export const adminLoginTool: Tool = {
name: 'pb_admin_login',
description: 'Authenticate as superuser/admin',
inputSchema: {
type: 'object',
properties: {
email: {
type: 'string',
format: 'email',
description: 'Admin email address',
},
password: {
type: 'string',
description: 'Admin password',
},
},
required: ['email', 'password'],
},
};
export async function handleAdminLogin(params: unknown): Promise<ToolResult> {
try {
// For this tool, we'll use the pre-configured credentials from env
// but we can validate the provided credentials match
const { email, password: _password } = validateParams(AdminLoginParamsSchema, params);
logger.info('Processing admin login request', { email });
// Authenticate using the service (which uses env credentials)
const authData = await pocketBaseService.authenticateAdmin();
const result = {
success: true,
admin: {
id: authData.record.id,
email: authData.record.email,
created: authData.record.created,
updated: authData.record.updated,
},
token: authData.token,
};
return createResult(
`Admin authentication successful for: ${email}`,
false,
result,
);
} catch (error) {
logger.error('Admin login failed', error);
if (error instanceof PocketBaseError) {
return createResult(
`Admin login failed: ${error.message}`,
true,
{ error: error.message, status: error.status },
);
}
const message = error instanceof Error ? error.message : 'Unknown error occurred';
return createResult(`Admin login failed: ${message}`, true);
}
}
// List users tool (admin only)
export const listUsersTool: Tool = {
name: 'pb_admin_list_users',
description: 'List all users (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., "created", "-updated")',
},
filter: {
type: 'string',
description: 'Filter criteria',
},
},
},
};
export async function handleListUsers(params: unknown): Promise<ToolResult> {
try {
const { page, perPage, sort, filter } = validateParams(ListUsersParamsSchema, params);
logger.info('Processing list users request', { page, perPage, sort, filter });
const result = await pocketBaseService.listRecords('users', {
page,
perPage,
sort,
filter,
});
const users = result.items.map((user) => ({
id: user.id,
email: (user as any).email,
username: (user as any).username,
verified: (user as any).verified,
emailVisibility: (user as any).emailVisibility,
created: user.created,
updated: user.updated,
}));
const response = {
success: true,
pagination: {
page: result.page,
perPage: result.perPage,
totalItems: result.totalItems,
totalPages: result.totalPages,
},
users,
};
return createResult(
`Found ${users.length} users (page ${page} of ${result.totalPages})`,
false,
response,
);
} catch (error) {
logger.error('List users failed', error);
if (error instanceof PocketBaseError) {
return createResult(
`List users failed: ${error.message}`,
true,
{ error: error.message, status: error.status },
);
}
const message = error instanceof Error ? error.message : 'Unknown error occurred';
return createResult(`List users failed: ${message}`, true);
}
}
// Create user tool (admin only)
export const createUserTool: Tool = {
name: 'pb_admin_create_user',
description: 'Create a new user (admin only)',
inputSchema: {
type: 'object',
properties: {
email: {
type: 'string',
format: 'email',
description: 'User email address',
},
password: {
type: 'string',
minLength: 8,
description: 'User password',
},
passwordConfirm: {
type: 'string',
minLength: 8,
description: 'Password confirmation',
},
username: {
type: 'string',
description: 'Optional username',
},
name: {
type: 'string',
description: 'Optional display name',
},
verified: {
type: 'boolean',
default: false,
description: 'Whether the user is verified',
},
emailVisibility: {
type: 'boolean',
default: false,
description: 'Whether email is visible to other users',
},
},
required: ['email', 'password', 'passwordConfirm'],
},
};
export async function handleCreateUser(params: unknown): Promise<ToolResult> {
try {
const data = params as Record<string, unknown>;
if (data.password !== data.passwordConfirm) {
return createResult(
'User creation failed: Password confirmation does not match',
true,
);
}
logger.info('Processing create user request', { email: data.email });
const user = await pocketBaseService.createRecord('users', data);
const result = {
success: true,
user: {
id: user.id,
email: (user as any).email,
username: (user as any).username,
verified: (user as any).verified,
emailVisibility: (user as any).emailVisibility,
created: user.created,
updated: user.updated,
},
};
return createResult(
`User created successfully: ${data.email}`,
false,
result,
);
} catch (error) {
logger.error('Create user failed', error);
if (error instanceof PocketBaseError) {
return createResult(
`Create user failed: ${error.message}`,
true,
{ error: error.message, status: error.status },
);
}
const message = error instanceof Error ? error.message : 'Unknown error occurred';
return createResult(`Create user failed: ${message}`, true);
}
}
// Update user tool (admin only)
export const updateUserTool: Tool = {
name: 'pb_admin_update_user',
description: 'Update an existing user (admin only)',
inputSchema: {
type: 'object',
properties: {
id: {
type: 'string',
description: 'User ID to update',
},
email: {
type: 'string',
format: 'email',
description: 'User email address',
},
username: {
type: 'string',
description: 'Username',
},
name: {
type: 'string',
description: 'Display name',
},
verified: {
type: 'boolean',
description: 'Whether the user is verified',
},
emailVisibility: {
type: 'boolean',
description: 'Whether email is visible to other users',
},
password: {
type: 'string',
minLength: 8,
description: 'New password (optional)',
},
passwordConfirm: {
type: 'string',
minLength: 8,
description: 'Password confirmation (required if password is set)',
},
},
required: ['id'],
},
};
export async function handleUpdateUser(params: unknown): Promise<ToolResult> {
try {
const data = params as Record<string, unknown>;
const { id, ...updateData } = data;
if (updateData.password && updateData.password !== updateData.passwordConfirm) {
return createResult(
'User update failed: Password confirmation does not match',
true,
);
}
logger.info('Processing update user request', { id });
const user = await pocketBaseService.updateRecord('users', id as string, updateData);
const result = {
success: true,
user: {
id: user.id,
email: (user as any).email,
username: (user as any).username,
verified: (user as any).verified,
emailVisibility: (user as any).emailVisibility,
created: user.created,
updated: user.updated,
},
};
return createResult(
`User updated successfully: ${id}`,
false,
result,
);
} catch (error) {
logger.error('Update user failed', error);
if (error instanceof PocketBaseError) {
return createResult(
`Update user failed: ${error.message}`,
true,
{ error: error.message, status: error.status },
);
}
const message = error instanceof Error ? error.message : 'Unknown error occurred';
return createResult(`Update user failed: ${message}`, true);
}
}
// Delete user tool (admin only)
export const deleteUserTool: Tool = {
name: 'pb_admin_delete_user',
description: 'Delete a user (admin only)',
inputSchema: {
type: 'object',
properties: {
id: {
type: 'string',
description: 'User ID to delete',
},
},
required: ['id'],
},
};
export async function handleDeleteUser(params: unknown): Promise<ToolResult> {
try {
const { id } = params as { id: string };
logger.info('Processing delete user request', { id });
const success = await pocketBaseService.deleteRecord('users', id);
if (success) {
return createResult(
`User deleted successfully: ${id}`,
false,
{ success: true, deletedId: id },
);
}
return createResult(
'Failed to delete user',
true,
);
} catch (error) {
logger.error('Delete user failed', error);
if (error instanceof PocketBaseError) {
return createResult(
`Delete user failed: ${error.message}`,
true,
{ error: error.message, status: error.status },
);
}
const message = error instanceof Error ? error.message : 'Unknown error occurred';
return createResult(`Delete user failed: ${message}`, true);
}
}
// Impersonate user tool (admin only)
export const impersonateUserTool: Tool = {
name: 'pb_admin_impersonate_user',
description: 'Impersonate a user (admin only)',
inputSchema: {
type: 'object',
properties: {
recordId: {
type: 'string',
description: 'User ID to impersonate',
},
duration: {
type: 'number',
minimum: 1,
description: 'Token duration in seconds (optional)',
},
},
required: ['recordId'],
},
};
export async function handleImpersonateUser(params: unknown): Promise<ToolResult> {
try {
const { recordId, duration } = validateParams(ImpersonateParamsSchema, params);
logger.info('Processing user impersonation request', { recordId, duration });
const impersonateClient = await pocketBaseService.impersonateUser(recordId, duration);
const userRecord = impersonateClient.authStore.model;
const result = {
success: true,
impersonatedUser: {
id: userRecord?.id,
email: (userRecord as any)?.email,
username: (userRecord as any)?.username,
verified: (userRecord as any)?.verified,
},
token: impersonateClient.authStore.token,
duration: duration || 'default',
};
return createResult(
`User impersonation started for: ${recordId}`,
false,
result,
);
} catch (error) {
logger.error('User impersonation failed', error);
if (error instanceof PocketBaseError) {
return createResult(
`User impersonation failed: ${error.message}`,
true,
{ error: error.message, status: error.status },
);
}
const message = error instanceof Error ? error.message : 'Unknown error occurred';
return createResult(`User impersonation failed: ${message}`, true);
}
}
// Get application settings tool (admin only)
export const getSettingsTool: Tool = {
name: 'pb_admin_get_settings',
description: 'Get application settings (admin only)',
inputSchema: {
type: 'object',
properties: {},
},
};
export async function handleGetSettings(): Promise<ToolResult> {
try {
logger.info('Processing get settings request');
// Ensure admin authentication
await pocketBaseService.ensureAdminAuth();
const adminClient = pocketBaseService.getAdminClient();
const settings = await adminClient.settings.getAll();
// Filter sensitive information
const filteredSettings = {
...settings,
// Remove sensitive fields
smtp: settings.smtp ? {
...settings.smtp,
password: '[REDACTED]',
} : undefined,
s3: settings.s3 ? {
...settings.s3,
secret: '[REDACTED]',
} : undefined,
};
return createResult(
'Application settings retrieved successfully',
false,
{ success: true, settings: filteredSettings },
);
} catch (error) {
logger.error('Get settings failed', error);
if (error instanceof PocketBaseError) {
return createResult(
`Get settings failed: ${error.message}`,
true,
{ error: error.message, status: error.status },
);
}
const message = error instanceof Error ? error.message : 'Unknown error occurred';
return createResult(`Get settings failed: ${message}`, true);
}
}
// Update application settings tool (admin only)
export const updateSettingsTool: Tool = {
name: 'pb_admin_update_settings',
description: 'Update application settings (admin only)',
inputSchema: {
type: 'object',
properties: {
settings: {
type: 'object',
description: 'Settings object to update',
},
},
required: ['settings'],
},
};
export async function handleUpdateSettings(params: unknown): Promise<ToolResult> {
try {
const { settings } = params as { settings: Record<string, unknown> };
logger.info('Processing update settings request');
// Ensure admin authentication
await pocketBaseService.ensureAdminAuth();
const adminClient = pocketBaseService.getAdminClient();
const updatedSettings = await adminClient.settings.update(settings);
return createResult(
'Application settings updated successfully',
false,
{ success: true, settings: updatedSettings },
);
} catch (error) {
logger.error('Update settings failed', error);
if (error instanceof PocketBaseError) {
return createResult(
`Update settings failed: ${error.message}`,
true,
{ error: error.message, status: error.status },
);
}
const message = error instanceof Error ? error.message : 'Unknown error occurred';
return createResult(`Update settings failed: ${message}`, true);
}
}
// Export all admin tools and handlers
export const adminTools = [
adminLoginTool,
listUsersTool,
createUserTool,
updateUserTool,
deleteUserTool,
impersonateUserTool,
getSettingsTool,
updateSettingsTool,
];
export const adminHandlers = {
pb_admin_login: handleAdminLogin,
pb_admin_list_users: handleListUsers,
pb_admin_create_user: handleCreateUser,
pb_admin_update_user: handleUpdateUser,
pb_admin_delete_user: handleDeleteUser,
pb_admin_impersonate_user: handleImpersonateUser,
pb_admin_get_settings: handleGetSettings,
pb_admin_update_settings: handleUpdateSettings,
};