/**
* NocoBase MCP Server
*
* Uses the flowModels API for modern NocoBase pages.
*
* Workflow:
* 1. menu_create_group(title) → groupId
* 2. page_create(title, parentId) → pageSchemaUid, tabSchemaUid
* 3. table_add(pageSchemaUid, tabSchemaUid, collection) → tableBlockUid, actionsColumnUid
* 4. column_add(tableBlockUid, collection, fieldPath) - repeat for each column
* 5. action_add(tableBlockUid, actionType, collection) - for header actions (Add New, Filter)
* 6. action_add(actionsColumnUid, actionType, collection, isRowAction: true) - for row actions (View, Edit, Delete)
*/
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 { NocoBaseClient } from './client.js';
import * as PageTools from './tools/page-tools.js';
const SERVER_VERSION = '7.0.0';
class NocoBaseMCPServer {
private server: Server;
private client: NocoBaseClient;
constructor() {
const baseURL = process.env.NOCOBASE_URL || 'http://localhost:13000';
this.client = new NocoBaseClient(baseURL, {
apiToken: process.env.NOCOBASE_API_TOKEN,
email: process.env.NOCOBASE_EMAIL,
password: process.env.NOCOBASE_PASSWORD
});
this.server = new Server(
{ name: 'nocobase-mcp', version: SERVER_VERSION },
{ capabilities: { tools: {} } }
);
this.setupHandlers();
}
private setupHandlers() {
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: rawArgs } = request.params;
const args: any = rawArgs || {};
try {
let result: any;
// MENU
if (name === 'menu_create_group') {
result = await PageTools.createMenuGroup(this.client, args);
}
// PAGE
else if (name === 'page_create') {
result = await PageTools.createFlowPage(this.client, args);
}
else if (name === 'page_list') {
result = await PageTools.listRoutes(this.client, args);
}
else if (name === 'page_delete') {
result = await PageTools.deletePage(this.client, args);
}
else if (name === 'page_inspect') {
result = await PageTools.inspectPage(this.client, args);
}
// TABLE
else if (name === 'table_add') {
result = await PageTools.addTableBlock(this.client, args);
}
else if (name === 'column_add') {
result = await PageTools.addColumn(this.client, args);
}
else if (name === 'action_add') {
result = await PageTools.addAction(this.client, args);
}
// COLLECTION
else if (name === 'collection_list') {
const res = await this.client.resourceList('collections', {
paginate: false,
sort: ['name']
});
result = {
success: true,
collections: (res.data?.data || []).map((c: any) => ({
name: c.name,
title: c.title,
fieldsCount: c.fields?.length || 0
}))
};
}
else if (name === 'collection_get') {
const res = await this.client.resourceGet('collections', args.name, {
appends: ['fields']
});
const collection = res.data?.data;
result = {
success: true,
name: collection?.name,
title: collection?.title,
fields: (collection?.fields || []).map((f: any) => ({
name: f.name,
type: f.type,
interface: f.interface
}))
};
}
// DATA
else if (name === 'data_list') {
const res = await this.client.resourceList(args.collection, {
pageSize: args.pageSize || 20,
page: args.page || 1,
filter: args.filter
});
result = {
success: true,
data: res.data?.data || [],
meta: res.data?.meta
};
}
else if (name === 'data_create') {
const res = await this.client.resourceCreate(args.collection, args.values);
result = { success: true, data: res.data?.data };
}
else if (name === 'data_update') {
const res = await this.client.resourceUpdate(args.collection, args.id, args.values);
result = { success: true, data: res.data?.data };
}
else if (name === 'data_delete') {
await this.client.resourceDelete(args.collection, args.id);
result = { success: true, message: `Deleted ${args.id}` };
}
// HEALTH
else if (name === 'health_check') {
await this.client.authenticate();
const info = await this.client.appGetInfo();
result = {
status: 'connected',
nocobaseVersion: info.data?.data?.version,
mcpVersion: SERVER_VERSION
};
}
else {
throw new Error(`Unknown tool: ${name}`);
}
return {
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }]
};
} catch (error: any) {
console.error(`[${name}] Error:`, error.message);
return {
content: [{ type: 'text', text: `Error: ${error.message}` }],
isError: true
};
}
});
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
// MENU
{
name: 'menu_create_group',
description: 'Create a menu group. Returns groupId for use as parentId.',
inputSchema: {
type: 'object',
properties: {
title: { type: 'string', description: 'Group title' },
icon: { type: 'string', description: 'Icon (default: FolderOutlined)' }
},
required: ['title']
}
},
// PAGE
{
name: 'page_create',
description: 'Create a FlowPage (modern page). Returns pageSchemaUid and tabSchemaUid for adding blocks.',
inputSchema: {
type: 'object',
properties: {
title: { type: 'string', description: 'Page title' },
parentId: { type: 'string', description: 'Parent group ID' }
},
required: ['title']
}
},
{
name: 'page_list',
description: 'List all pages/routes',
inputSchema: { type: 'object', properties: {} }
},
{
name: 'page_delete',
description: 'Delete a page by route ID',
inputSchema: {
type: 'object',
properties: {
routeId: { type: 'string', description: 'Route ID' }
},
required: ['routeId']
}
},
{
name: 'page_inspect',
description: 'Inspect page to get gridUid, tableBlockUid, actionsColumnUid. Use pageTitle OR schemaUid/tabSchemaUid.',
inputSchema: {
type: 'object',
properties: {
pageTitle: { type: 'string', description: 'Page title to search for (easiest method)' },
schemaUid: { type: 'string', description: 'Page schema UID (if known)' },
tabSchemaUid: { type: 'string', description: 'Tab schema UID (if known)' }
}
}
},
// TABLE
{
name: 'table_add',
description: 'Add a table block to page. Returns tableBlockUid and actionsColumnUid.',
inputSchema: {
type: 'object',
properties: {
gridUid: { type: 'string', description: 'Grid UID from page_create' },
collection: { type: 'string', description: 'Collection name' }
},
required: ['gridUid', 'collection']
}
},
{
name: 'column_add',
description: 'Add a column to table',
inputSchema: {
type: 'object',
properties: {
tableBlockUid: { type: 'string', description: 'Table block UID from table_add' },
collection: { type: 'string', description: 'Collection name' },
fieldPath: { type: 'string', description: 'Field name to show' },
sortIndex: { type: 'number', description: 'Column order (default: 1)' }
},
required: ['tableBlockUid', 'collection', 'fieldPath']
}
},
{
name: 'action_add',
description: 'Add action button. Types: addNew, view, edit, delete, filter, refresh. For row actions set isRowAction=true.',
inputSchema: {
type: 'object',
properties: {
parentUid: { type: 'string', description: 'tableBlockUid for header actions, actionsColumnUid for row actions' },
actionType: { type: 'string', enum: ['addNew', 'view', 'edit', 'delete', 'filter', 'refresh'] },
collection: { type: 'string', description: 'Collection name' },
sortIndex: { type: 'number', description: 'Action order' },
isRowAction: { type: 'boolean', description: 'True for View/Edit/Delete buttons in each row' }
},
required: ['parentUid', 'actionType', 'collection']
}
},
// COLLECTION
{
name: 'collection_list',
description: 'List all collections',
inputSchema: { type: 'object', properties: {} }
},
{
name: 'collection_get',
description: 'Get collection fields',
inputSchema: {
type: 'object',
properties: {
name: { type: 'string', description: 'Collection name' }
},
required: ['name']
}
},
// DATA
{
name: 'data_list',
description: 'List records from collection',
inputSchema: {
type: 'object',
properties: {
collection: { type: 'string' },
pageSize: { type: 'number' },
page: { type: 'number' },
filter: { type: 'object' }
},
required: ['collection']
}
},
{
name: 'data_create',
description: 'Create record',
inputSchema: {
type: 'object',
properties: {
collection: { type: 'string' },
values: { type: 'object' }
},
required: ['collection', 'values']
}
},
{
name: 'data_update',
description: 'Update record',
inputSchema: {
type: 'object',
properties: {
collection: { type: 'string' },
id: { type: 'string' },
values: { type: 'object' }
},
required: ['collection', 'id', 'values']
}
},
{
name: 'data_delete',
description: 'Delete record',
inputSchema: {
type: 'object',
properties: {
collection: { type: 'string' },
id: { type: 'string' }
},
required: ['collection', 'id']
}
},
// HEALTH
{
name: 'health_check',
description: 'Check NocoBase connection',
inputSchema: { type: 'object', properties: {} }
}
]
};
});
}
async run() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error(`[NocoBase MCP] Server v${SERVER_VERSION} running`);
}
}
const server = new NocoBaseMCPServer();
server.run().catch(console.error);