First commit to public

This commit is contained in:
Breno Yano
2025-04-07 19:12:46 -03:00
parent b75f686b56
commit ad9496a9fa
9 changed files with 2778 additions and 1 deletions

130
.gitignore vendored Normal file
View File

@@ -0,0 +1,130 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
.cache
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*

21
LICENSE copy Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) [year] [fullname]
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

285
README.md
View File

@@ -1 +1,284 @@
# pocketbase-mcp
# PocketBase MCP Server
This is an MCP server that interacts with a PocketBase instance. It allows you to fetch, list, create, update, and manage records and files in your PocketBase collections.
## Installation
1. **Clone the repository (if you haven't already):**
```bash
git clone <repository_url>
cd pocketbase-mcp
```
2. **Install dependencies:**
```bash
npm install
```
3. **Build the server:**
```bash
npm run build
```
This compiles the TypeScript code to JavaScript in the `build/` directory and makes the entry point executable.
## Configuration
This server requires the following environment variables to be set:
- `POCKETBASE_API_URL`: The URL of your PocketBase instance (e.g., `http://127.0.0.1:8090`). Defaults to `http://127.0.0.1:8090` if not set.
- `POCKETBASE_ADMIN_TOKEN`: An admin authentication token for your PocketBase instance. **This is required.** You can generate this from your PocketBase admin UI.
These variables need to be configured when adding the server to Cline (see Cline Installation section).
## Available Tools
The server provides the following tools:
- **fetch_record**: Fetch a single record from a PocketBase collection by ID.
- *Input Schema*:
```json
{
"type": "object",
"properties": {
"collection": {
"type": "string",
"description": "The name of the PocketBase collection."
},
"id": {
"type": "string",
"description": "The ID of the record to fetch."
}
},
"required": [
"collection",
"id"
]
}
```
- **list_records**: List records from a PocketBase collection. Supports pagination, filtering, sorting, and expanding relations.
- *Input Schema*:
```json
{
"type": "object",
"properties": {
"collection": {
"type": "string",
"description": "The name of the PocketBase collection."
},
"page": {
"type": "number",
"description": "Page number (defaults to 1).",
"minimum": 1
},
"perPage": {
"type": "number",
"description": "Items per page (defaults to 25).",
"minimum": 1,
"maximum": 100
},
"filter": {
"type": "string",
"description": "Filter string for the PocketBase query."
},
"sort": {
"type": "string",
"description": "Sort string for the PocketBase query (e.g., \\"fieldName,-otherFieldName\\")."
},
"expand": {
"type": "string",
"description": "Expand string for the PocketBase query (e.g., \\"relation1,relation2.subRelation\\")."
}
},
"required": [
"collection"
]
}
```
- **create_record**: Create a new record in a PocketBase collection.
- *Input Schema*:
```json
{
"type": "object",
"properties": {
"collection": {
"type": "string",
"description": "The name of the PocketBase collection."
},
"data": {
"type": "object",
"description": "The data for the new record.",
"additionalProperties": true
}
},
"required": [
"collection",
"data"
]
}
```
- **update_record**: Update an existing record in a PocketBase collection.
- *Input Schema*:
```json
{
"type": "object",
"properties": {
"collection": {
"type": "string",
"description": "The name of the PocketBase collection."
},
"id": {
"type": "string",
"description": "The ID of the record to update."
},
"data": {
"type": "object",
"description": "The data to update.",
"additionalProperties": true
}
},
"required": [
"collection",
"id",
"data"
]
}
```
- **get_collection_schema**: Get the schema of a PocketBase collection.
- *Input Schema*:
```json
{
"type": "object",
"properties": {
"collection": {
"type": "string",
"description": "The name of the PocketBase collection."
}
},
"required": [
"collection"
]
}
```
- **upload_file**: Upload a file to a specific field in a PocketBase collection record.
- *Input Schema*:
```json
{
"type": "object",
"properties": {
"collection": {
"type": "string",
"description": "The name of the PocketBase collection."
},
"recordId": {
"type": "string",
"description": "The ID of the record to upload the file to."
},
"fileField": {
"type": "string",
"description": "The name of the file field in the PocketBase collection."
},
"fileContent": {
"type": "string",
"description": "The content of the file to upload."
},
"fileName": {
"type": "string",
"description": "The name of the file."
}
},
"required": [
"collection",
"recordId",
"fileField",
"fileContent",
"fileName"
]
}
```
- **list_collections**: List all collections in the PocketBase instance.
- *Input Schema*:
```json
{
"type": "object",
"properties": {},
"additionalProperties": false
}
```
- **download_file**: Get the download URL for a file stored in a PocketBase collection record.
- *Input Schema*:
```json
{
"type": "object",
"properties": {
"collection": {
"type": "string",
"description": "The name of the PocketBase collection."
},
"recordId": {
"type": "string",
"description": "The ID of the record to download the file from."
},
"fileField": {
"type": "string",
"description": "The name of the file field in the PocketBase collection."
},
"downloadPath": {
"type": "string",
"description": "The path where the downloaded file should be saved (Note: This tool currently returns the URL, download must be handled separately)."
}
},
"required": [
"collection",
"recordId",
"fileField",
"downloadPath"
]
}
```
*Note: This tool returns the file URL. The actual download needs to be performed by the client using this URL.*
## Cline Installation
To use this server with Cline, you need to add it to your MCP settings file (`cline_mcp_settings.json`).
1. **Locate your Cline MCP settings file:**
* Typically found at `~/.config/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json` on Linux/macOS.
* Or `~/Library/Application Support/Claude/claude_desktop_config.json` if using the Claude desktop app on macOS.
2. **Edit the file and add the following configuration under the `mcpServers` key.** Replace `/path/to/pocketbase-mcp` with the actual absolute path to this project directory on your system. Also, replace `<YOUR_POCKETBASE_API_URL>` and `<YOUR_POCKETBASE_ADMIN_TOKEN>` with your actual PocketBase URL and admin token.
```json
{
"mcpServers": {
// ... other servers might be listed here ...
"pocketbase-mcp": {
"command": "node",
"args": ["/path/to/pocketbase-mcp/build/index.js"],
"env": {
"POCKETBASE_API_URL": "<YOUR_POCKETBASE_API_URL>", // e.g., "http://127.0.0.1:8090"
"POCKETBASE_ADMIN_TOKEN": "<YOUR_POCKETBASE_ADMIN_TOKEN>"
},
"disabled": false, // Ensure it's enabled
"autoApprove": [] // Default auto-approve settings
}
// ... other servers might be listed here ...
}
}
```
3. **Save the settings file.** Cline should automatically detect the changes and connect to the server. You can then use the tools listed above.
## Dependencies
- `@modelcontextprotocol/sdk`
- `pocketbase`
- `typescript`
- `ts-node` (dev dependency)
- `@types/node` (dev dependency)

398
build/index.js Executable file
View File

@@ -0,0 +1,398 @@
#!/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 PocketBase from 'pocketbase';
const API_URL = process.env.POCKETBASE_API_URL || 'http://127.0.0.1:8090';
const ADMIN_TOKEN = process.env.POCKETBASE_ADMIN_TOKEN;
if (!ADMIN_TOKEN) {
throw new Error('POCKETBASE_ADMIN_TOKEN environment variable is required');
}
class PocketBaseServer {
constructor() {
this.server = new Server({
name: 'pocketbase-mcp',
version: '0.1.0',
}, {
capabilities: {
tools: {},
},
});
this.pb = new PocketBase(API_URL);
if (ADMIN_TOKEN) {
this.pb.authStore.save(ADMIN_TOKEN, null);
}
this.setupToolHandlers();
// Error handling
this.server.onerror = (error) => { console.error('[MCP Error]', error); };
process.on('SIGINT', async () => {
await this.server.close();
process.exit(0);
});
}
setupToolHandlers() {
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: 'fetch_record',
description: 'Fetch a single record from a PocketBase collection by ID.',
inputSchema: {
type: 'object',
properties: {
collection: {
type: 'string',
description: 'The name of the PocketBase collection.',
},
id: {
type: 'string',
description: 'The ID of the record to fetch.',
},
},
required: ['collection', 'id'],
},
},
{
name: 'list_records',
description: 'List records from a PocketBase collection. Supports pagination using `page` and `perPage` parameters.',
inputSchema: {
type: 'object',
properties: {
collection: {
type: 'string',
description: 'The name of the PocketBase collection.',
},
page: {
type: 'number',
description: 'Page number (defaults to 1).',
minimum: 1
},
perPage: {
type: 'number',
description: 'Items per page (defaults to 25).',
minimum: 1,
maximum: 100
},
filter: {
type: 'string',
description: 'Filter string for the PocketBase query.'
},
sort: {
type: 'string',
description: 'Sort string for the PocketBase query (e.g., "fieldName,-otherFieldName").'
},
expand: {
type: 'string',
description: 'Expand string for the PocketBase query (e.g., "relation1,relation2.subRelation").'
}
},
required: ['collection'],
},
},
{
name: 'create_record',
description: 'Create a new record in a PocketBase collection.',
inputSchema: {
type: 'object',
properties: {
collection: {
type: 'string',
description: 'The name of the PocketBase collection.',
},
data: {
type: 'object',
description: 'The data for the new record.',
additionalProperties: true
},
},
required: ['collection', 'data'],
},
},
{
name: 'update_record',
description: 'Update an existing record in a PocketBase collection.',
inputSchema: {
type: 'object',
properties: {
collection: {
type: 'string',
description: 'The name of the PocketBase collection.',
},
id: {
type: 'string',
description: 'The ID of the record to update.',
},
data: {
type: 'object',
description: 'The data to update.',
additionalProperties: true
},
},
required: ['collection', 'id', 'data'],
},
},
{
name: 'get_collection_schema',
description: 'Get the schema of a PocketBase collection.',
inputSchema: {
type: 'object',
properties: {
collection: {
type: 'string',
description: 'The name of the PocketBase collection.',
},
},
required: ['collection'],
},
},
{
name: 'upload_file',
description: 'Upload a file to a PocketBase collection record.',
inputSchema: {
type: 'object',
properties: {
collection: {
type: 'string',
description: 'The name of the PocketBase collection.',
},
recordId: {
type: 'string',
description: 'The ID of the record to upload the file to.',
},
fileField: {
type: 'string',
description: 'The name of the file field in the PocketBase collection.',
},
fileContent: {
type: 'string',
description: 'The content of the file to upload.',
},
fileName: {
type: 'string',
description: 'The name of the file.',
}
},
required: [
"collection",
"recordId",
"fileField",
"fileContent",
"fileName"
]
},
},
{
name: 'list_collections',
description: 'List all collections in the PocketBase instance.',
inputSchema: {
type: 'object',
properties: {},
additionalProperties: false,
},
},
{
name: 'download_file',
description: 'Download a file from a PocketBase collection record.',
inputSchema: {
type: 'object',
properties: {
collection: {
type: 'string',
description: 'The name of the PocketBase collection.',
},
recordId: {
type: 'string',
description: 'The ID of the record to download the file from.',
},
fileField: {
type: 'string',
description: 'The name of the file field in the PocketBase collection.',
},
downloadPath: {
type: 'string',
description: 'The path where the downloaded file should be saved.',
},
},
required: [
"collection",
"recordId",
"fileField",
"downloadPath"
]
}
}
],
}));
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
try {
const { name, arguments: args } = request.params;
switch (name) {
case 'fetch_record': {
if (!args || typeof args !== 'object' || !('collection' in args) || !('id' in args)) {
throw new McpError(ErrorCode.InvalidParams, "Missing collection or id");
}
const { collection, id } = args;
const record = await this.pb.collection(collection).getOne(id);
return {
content: [
{
type: 'text',
text: JSON.stringify(record, null, 2),
},
],
};
}
case 'list_records': {
if (!args || typeof args !== 'object' || !('collection' in args)) {
throw new McpError(ErrorCode.InvalidParams, "Missing collection");
}
const { collection, page, perPage, filter, sort, expand } = args;
const result = await this.pb.collection(collection).getList(page || 1, perPage || 25, {
filter,
sort,
expand
});
return {
content: [
{
type: 'text',
content: result,
text: JSON.stringify(result, null, 2),
},
],
};
}
case 'create_record': {
if (!args || typeof args !== 'object' || !('collection' in args) || !('data' in args)) {
throw new McpError(ErrorCode.InvalidParams, "Missing collection or data");
}
const { collection, data } = args;
const record = await this.pb.collection(collection).create(data);
return {
content: [
{
type: 'text',
text: JSON.stringify(record, null, 2),
},
],
};
}
case 'update_record': {
if (!args || typeof args !== 'object' || !('collection' in args) || !('id' in args) || !('data' in args)) {
throw new McpError(ErrorCode.InvalidParams, "Missing collection, id, or data");
}
const { collection, id, data } = args;
const record = await this.pb.collection(collection).update(id, data);
return {
content: [
{
type: 'text',
text: JSON.stringify(record, null, 2),
},
],
};
}
case 'get_collection_schema': {
if (!args || typeof args !== 'object' || !('collection' in args)) {
throw new McpError(ErrorCode.InvalidParams, "Missing collection");
}
const { collection } = args;
const schema = await this.pb.collections.getOne(collection);
return {
content: [
{
type: 'text',
text: JSON.stringify(schema, null, 2),
},
],
};
}
case 'upload_file': {
if (!args || typeof args !== 'object' || !('collection' in args) || !('recordId' in args) || !('fileField' in args) || !('fileContent' in args) || !('fileName' in args)) {
throw new McpError(ErrorCode.InvalidParams, "Missing collection, recordId, fileField, fileContent, or fileName");
}
const { collection, recordId, fileField, fileContent, fileName } = args;
// Create a Blob from the file content
const blob = new Blob([fileContent]);
// Create a FormData object and append the file
const formData = new FormData();
formData.append(fileField, blob, fileName);
// Update the record with the file
const record = await this.pb.collection(collection).update(recordId, formData);
return {
content: [
{
type: 'text',
text: JSON.stringify(record, null, 2),
},
],
};
}
case 'download_file': {
if (!args || typeof args !== 'object' || !('collection' in args) || !('recordId' in args) || !('fileField' in args) || !('downloadPath' in args)) {
throw new McpError(ErrorCode.InvalidParams, "Missing collection, recordId, fileField, or downloadPath");
}
const { collection, recordId, fileField, downloadPath } = args;
// Fetch the record
const record = await this.pb.collection(collection).getOne(recordId);
// Get the file URL
const fileUrl = this.pb.getFileUrl(record, record[fileField]);
// Return the file URL to the user. They can use this URL to download the file.
return {
content: [
{
type: 'text',
text: fileUrl
}
]
};
// The following code is not possible because we cannot download files within the MCP server
// // Download the file content
// const response = await fetch(fileUrl);
// const fileContent = await response.text();
// // Save the file using the write_to_file tool
// await write_to_file({ path: downloadPath, content: fileContent });
// return {
// content: [
// {
// type: 'text',
// text: `File downloaded to ${downloadPath}`,
// },
// ],
// };
}
case 'list_collections': {
const result = await this.pb.collections.getFullList({ sort: '-created' });
return {
content: [
{
type: 'text',
text: JSON.stringify(result, null, 2),
},
],
};
}
default:
throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
}
}
catch (error) {
console.error(error);
const errorMessage = error instanceof Error ? error.message : "An unknown error occurred";
return {
content: [{
type: 'text',
text: errorMessage,
}],
isError: true
};
}
});
}
async run() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error('PocketBase MCP server running on stdio');
}
}
const server = new PocketBaseServer();
server.run().catch(console.error);

1397
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

22
package.json Normal file
View File

@@ -0,0 +1,22 @@
{
"name": "pocketbase-mcp",
"version": "1.0.0",
"main": "index.js",
"type": "module",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\""
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.7.0",
"@types/node": "^22.13.10",
"axios": "^1.8.3",
"pocketbase": "^0.25.2",
"ts-node": "^10.9.2",
"typescript": "^5.8.2"
}
}

440
src/index.ts Normal file
View File

@@ -0,0 +1,440 @@
#!/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 PocketBase from 'pocketbase';
const API_URL = process.env.POCKETBASE_API_URL || 'http://127.0.0.1:8090';
const ADMIN_TOKEN = process.env.POCKETBASE_ADMIN_TOKEN;
if (!ADMIN_TOKEN) {
throw new Error('POCKETBASE_ADMIN_TOKEN environment variable is required');
}
interface ToolError {
content: [{
type: 'text',
text: string
}],
isError: true
}
class PocketBaseServer {
private server: Server;
private pb: PocketBase;
constructor() {
this.server = new Server(
{
name: 'pocketbase-mcp',
version: '0.1.0',
},
{
capabilities: {
tools: {},
},
}
);
this.pb = new PocketBase(API_URL);
if (ADMIN_TOKEN) {
this.pb.authStore.save(ADMIN_TOKEN, null);
}
this.setupToolHandlers();
// Error handling
this.server.onerror = (error: Error) => { console.error('[MCP Error]', error) };
process.on('SIGINT', async () => {
await this.server.close();
process.exit(0);
});
}
private setupToolHandlers() {
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: 'fetch_record',
description: 'Fetch a single record from a PocketBase collection by ID.',
inputSchema: {
type: 'object',
properties: {
collection: {
type: 'string',
description: 'The name of the PocketBase collection.',
},
id: {
type: 'string',
description: 'The ID of the record to fetch.',
},
},
required: ['collection', 'id'],
},
},
{
name: 'list_records',
description: 'List records from a PocketBase collection. Supports pagination using `page` and `perPage` parameters.',
inputSchema: {
type: 'object',
properties: {
collection: {
type: 'string',
description: 'The name of the PocketBase collection.',
},
page: {
type: 'number',
description: 'Page number (defaults to 1).',
minimum: 1
},
perPage: {
type: 'number',
description: 'Items per page (defaults to 25).',
minimum: 1,
maximum: 100
},
filter: {
type: 'string',
description: 'Filter string for the PocketBase query.'
},
sort: {
type: 'string',
description: 'Sort string for the PocketBase query (e.g., "fieldName,-otherFieldName").'
},
expand: {
type: 'string',
description: 'Expand string for the PocketBase query (e.g., "relation1,relation2.subRelation").'
}
},
required: ['collection'],
},
},
{
name: 'create_record',
description: 'Create a new record in a PocketBase collection.',
inputSchema: {
type: 'object',
properties: {
collection: {
type: 'string',
description: 'The name of the PocketBase collection.',
},
data: {
type: 'object',
description: 'The data for the new record.',
additionalProperties: true
},
},
required: ['collection', 'data'],
},
},
{
name: 'update_record',
description: 'Update an existing record in a PocketBase collection.',
inputSchema: {
type: 'object',
properties: {
collection: {
type: 'string',
description: 'The name of the PocketBase collection.',
},
id: {
type: 'string',
description: 'The ID of the record to update.',
},
data: {
type: 'object',
description: 'The data to update.',
additionalProperties: true
},
},
required: ['collection', 'id', 'data'],
},
},
{
name: 'get_collection_schema',
description: 'Get the schema of a PocketBase collection.',
inputSchema: {
type: 'object',
properties: {
collection: {
type: 'string',
description: 'The name of the PocketBase collection.',
},
},
required: ['collection'],
},
},
{
name: 'upload_file',
description: 'Upload a file to a PocketBase collection record.',
inputSchema: {
type: 'object',
properties: {
collection: {
type: 'string',
description: 'The name of the PocketBase collection.',
},
recordId: {
type: 'string',
description: 'The ID of the record to upload the file to.',
},
fileField: {
type: 'string',
description: 'The name of the file field in the PocketBase collection.',
},
fileContent: {
type: 'string',
description: 'The content of the file to upload.',
},
fileName: {
type: 'string',
description: 'The name of the file.',
}
},
required: [
"collection",
"recordId",
"fileField",
"fileContent",
"fileName"
]
},
},
{
name: 'list_collections',
description: 'List all collections in the PocketBase instance.',
inputSchema: {
type: 'object',
properties: {},
additionalProperties: false,
},
},
{
name: 'download_file',
description: 'Download a file from a PocketBase collection record.',
inputSchema: {
type: 'object',
properties: {
collection: {
type: 'string',
description: 'The name of the PocketBase collection.',
},
recordId: {
type: 'string',
description: 'The ID of the record to download the file from.',
},
fileField: {
type: 'string',
description: 'The name of the file field in the PocketBase collection.',
},
downloadPath: {
type: 'string',
description: 'The path where the downloaded file should be saved.',
},
},
required: [
"collection",
"recordId",
"fileField",
"downloadPath"
]
}
}
],
}));
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
try {
const { name, arguments: args } = request.params;
switch (name) {
case 'fetch_record': {
if (!args || typeof args !== 'object' || !('collection' in args) || !('id' in args)) {
throw new McpError(ErrorCode.InvalidParams, "Missing collection or id");
}
const { collection, id } = args as { collection: string; id: string; };
const record = await this.pb.collection(collection).getOne(id);
return {
content: [
{
type: 'text',
text: JSON.stringify(record, null, 2),
},
],
};
}
case 'list_records': {
if (!args || typeof args !== 'object' || !('collection' in args)) {
throw new McpError(ErrorCode.InvalidParams, "Missing collection");
}
const { collection, page, perPage, filter, sort, expand } = args as { collection: string; page?: number; perPage?: number; filter?: string; sort?: string; expand?: string; };
const result = await this.pb.collection(collection).getList(page || 1, perPage || 25, {
filter,
sort,
expand
});
return {
content: [
{
type: 'text',
content: result,
text: JSON.stringify(result, null, 2),
},
],
};
}
case 'create_record': {
if (!args || typeof args !== 'object' || !('collection' in args) || !('data' in args)) {
throw new McpError(ErrorCode.InvalidParams, "Missing collection or data");
}
const { collection, data } = args as {collection: string, data: any};
const record = await this.pb.collection(collection).create(data);
return {
content: [
{
type: 'text',
text: JSON.stringify(record, null, 2),
},
],
};
}
case 'update_record': {
if (!args || typeof args !== 'object' || !('collection' in args) || !('id' in args) || !('data' in args)) {
throw new McpError(ErrorCode.InvalidParams, "Missing collection, id, or data");
}
const { collection, id, data } = args as {collection: string, id: string, data: any};
const record = await this.pb.collection(collection).update(id, data);
return {
content: [
{
type: 'text',
text: JSON.stringify(record, null, 2),
},
],
};
}
case 'get_collection_schema': {
if (!args || typeof args !== 'object' || !('collection' in args)) {
throw new McpError(ErrorCode.InvalidParams, "Missing collection");
}
const { collection } = args as { collection: string };
const schema = await this.pb.collections.getOne(collection);
return {
content: [
{
type: 'text',
text: JSON.stringify(schema, null, 2),
},
],
};
}
case 'upload_file': {
if (!args || typeof args !== 'object' || !('collection' in args) || !('recordId' in args) || !('fileField' in args) || !('fileContent' in args) || !('fileName' in args)) {
throw new McpError(ErrorCode.InvalidParams, "Missing collection, recordId, fileField, fileContent, or fileName");
}
const { collection, recordId, fileField, fileContent, fileName } = args as { collection: string; recordId: string; fileField: string; fileContent: string; fileName: string };
// Create a Blob from the file content
const blob = new Blob([fileContent]);
// Create a FormData object and append the file
const formData = new FormData();
formData.append(fileField, blob, fileName);
// Update the record with the file
const record = await this.pb.collection(collection).update(recordId, formData);
return {
content: [
{
type: 'text',
text: JSON.stringify(record, null, 2),
},
],
};
}
case 'download_file': {
if (!args || typeof args !== 'object' || !('collection' in args) || !('recordId' in args) || !('fileField' in args) || !('downloadPath' in args)) {
throw new McpError(ErrorCode.InvalidParams, "Missing collection, recordId, fileField, or downloadPath");
}
const { collection, recordId, fileField, downloadPath } = args as { collection: string; recordId: string; fileField: string; downloadPath: string; };
// Fetch the record
const record = await this.pb.collection(collection).getOne(recordId);
// Get the file URL
const fileUrl = this.pb.getFileUrl(record, record[fileField]);
// Return the file URL to the user. They can use this URL to download the file.
return {
content: [
{
type: 'text',
text: fileUrl
}
]
};
// The following code is not possible because we cannot download files within the MCP server
// // Download the file content
// const response = await fetch(fileUrl);
// const fileContent = await response.text();
// // Save the file using the write_to_file tool
// await write_to_file({ path: downloadPath, content: fileContent });
// return {
// content: [
// {
// type: 'text',
// text: `File downloaded to ${downloadPath}`,
// },
// ],
// };
}
case 'list_collections': {
const result = await this.pb.collections.getFullList({ sort: '-created' });
return {
content: [
{
type: 'text',
text: JSON.stringify(result, null, 2),
},
],
};
}
default:
throw new McpError(
ErrorCode.MethodNotFound,
`Unknown tool: ${name}`
);
}
} catch (error: unknown) {
console.error(error);
const errorMessage = error instanceof Error ? error.message : "An unknown error occurred";
return {
content: [{
type: 'text',
text: errorMessage,
}],
isError: true
};
}
});
}
async run() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error('PocketBase MCP server running on stdio');
}
}
const server = new PocketBaseServer();
server.run().catch(console.error);

70
task.md Normal file
View File

@@ -0,0 +1,70 @@
# Task List
This file is used to track tasks. Tasks are marked with `[ ]` for incomplete and `[X]` for completed. The next task to be taken is the first one marked with `[ ]`. If that task has subtasks do the one with [ ], only marking the parent task as done when all subtasks for that parent task are with [X] marked.
**IMPORTANT: After completing each task or subtask, update this file immediately to reflect the changes (mark tasks as complete and add new tasks as needed).**
## General Tasks
- [X] **Enhance `list_records` tool:**
- [X] Add support for filtering records based on field values.
- [X] Implement pagination options for listing large datasets.
- [X] Allow sorting of records by specific fields.
- [X] Implement support for expanding relations in list results.
- [ ] **Collection Management Tools:**
- [X] Add a tool to list all collections in the PocketBase instance (`list_collections`).
- [X] Implement a tool to get schema of a specific collection (`get_collection_schema`).
- [ ] Implement tools for Collection Management:
- [ ] Create and manage collections with custom schemas (`create_collection`, `update_collection`)
- [ ] Migrate collection schemas with data preservation (`migrate_collection_schema`)
- [ ] Advanced index management tools:
- [ ] Create indexes (`create_index`)
- [ ] Delete indexes (`delete_index`)
- [ ] List indexes (`list_indexes`)
- [ ] Implement schema validation and type safety (This might be more of an implementation detail within other tools, but could be a task to ensure it's properly handled)
- [ ] **File Handling Tools:**
- [ ] Implement a tool to upload files to a PocketBase collection with file fields (`upload_file`).
- [ ] Add a tool to download files from a PocketBase collection (`download_file`).
- [ ] **Record Operations:**
- [ ] Implement a tool to delete a record from a PocketBase collection (`delete_record`).
- [ ] Enhance Record Operations:
- [ ] Implement advanced querying with aggregation (`list_records` with aggregation support)
- [ ] Implement batch import/export capabilities:
- [ ] Batch record import (`batch_import_records`)
- [ ] Batch record export (`batch_export_records`)
- [ ] **User Management Tools:**
- [ ] Implement User Management Tools:
- [ ] User authentication and token management tools: (`create_user_token`, `verify_user_token`)
- [ ] User account creation and management tools: (`create_user`, `update_user`, `delete_user`, `list_users`)
- [ ] Password management tools: (`update_user_password`, `reset_user_password`)
- [ ] Role-based access control tools (This might be complex and could be broken down further if needed)
- [ ] Session handling (This might be more of an implementation detail, but could be a task if specific tools are needed)
- [ ] **Database Operation Tools:**
- [ ] Implement Database Operation Tools:
- [ ] Database backup and restore tools: (`backup_database`, `restore_database`)
- [ ] Multiple export formats (`export_database_json`, `export_database_csv`)
- [ ] Data migration tools (This is a broad task and might need further definition)
- [ ] Index optimization tools (`optimize_indexes`)
- [ ] Batch operations (This might overlap with Batch Record Operations, needs clarification)
- [ ] **Realtime Subscription Tools (Advanced):**
- [ ] Explore adding tools to subscribe to realtime events for specific collections (`subscribe_collection`).
- [ ] Implement a tool to unsubscribe from realtime events (`unsubscribe_collection`).
- [ ] **Admin Authentication Tool (Optional):**
- [ ] If not using impersonate token, add a tool to authenticate as admin and obtain an admin token (`admin_auth`).
- [ ] **Documentation:**
- [ ] Create a README.md file for the `pocketbase-mcp` server.
- [ ] Document each tool with clear descriptions, input schemas, and example usage, including code examples.
- [ ] **Improve Error Handling:**
- [ ] Implement more specific error handling for PocketBase API interactions.
- [ ] Provide more informative error messages to the user through the MCP tool responses, including PocketBase error details when available.
- [ ] Handle rate limiting and retry mechanisms for API requests.
- [ ] **Add Input Validation:**
- [ ] Validate input parameters for all tools to ensure they conform to the input schemas.
- [ ] Return specific error messages for invalid input, clearly indicating the invalid fields and expected format.
- [ ] Validate data types and formats for create and update operations based on collection schema.
- [ ] Add documentation on setting up authentication and environment variables.

16
tsconfig.json Normal file
View File

@@ -0,0 +1,16 @@
{
"compilerOptions": {
"target": "es2020",
"module": "es2020",
"lib": ["es2020"],
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true,
"moduleResolution": "node",
"outDir": "build",
"rootDir": "src",
"resolveJsonModule": true
},
"include": ["src/**/*"]
}