MCP Runtime Server Plugin for ObjectStack — exposes AI tools, data resources, and agent prompts via the Model Context Protocol.
- Model Context Protocol (MCP): Expose ObjectStack resources to AI models via MCP
- AI Tools: Auto-generate MCP tools from ObjectStack actions and flows
- Data Resources: Expose objects, records, and metadata as MCP resources
- Agent Prompts: Register prompt templates for AI agents
- Type-Safe: Full Zod schema validation for tool inputs/outputs
- Auto-Discovery: MCP clients automatically discover available tools and resources
- Streaming Support: Stream large datasets and real-time updates
- Security: Built-in permission checks for tool execution
Model Context Protocol (MCP) is an open protocol that standardizes how AI applications provide context to Large Language Models (LLMs). It allows AI models to:
- Access Tools: Execute functions and operations
- Read Resources: Access data and content
- Use Prompts: Leverage pre-defined prompt templates
Read more: MCP Specification
pnpm add @objectstack/plugin-mcp-serverimport { defineStack } from '@objectstack/spec';
import { PluginMCPServer } from '@objectstack/plugin-mcp-server';
const stack = defineStack({
plugins: [
PluginMCPServer.configure({
serverName: 'objectstack-server',
version: '1.0.0',
autoRegisterTools: true,
}),
],
});interface MCPServerConfig {
/** Server name (shown to AI clients) */
serverName?: string;
/** Server version */
version?: string;
/** Auto-register tools from actions and flows */
autoRegisterTools?: boolean;
/** Auto-expose objects as resources */
autoExposeObjects?: boolean;
/** Enable streaming for large responses */
enableStreaming?: boolean;
/** Transport mechanism ('stdio' | 'http') */
transport?: 'stdio' | 'http';
/** HTTP port (if transport is 'http') */
port?: number;
}ObjectStack automatically exposes these operations as MCP tools:
// CRUD operations (auto-registered)
'objectstack_find' // Query records
'objectstack_findOne' // Get single record
'objectstack_create' // Create record
'objectstack_update' // Update record
'objectstack_delete' // Delete record
// Metadata operations
'objectstack_describeObject' // Get object schema
'objectstack_listObjects' // List all objects
'objectstack_listFields' // List object fieldsRegister custom tools that AI models can call:
import { defineTool } from '@objectstack/spec';
const calculateRevenueTool = defineTool({
name: 'calculate_revenue',
description: 'Calculate total revenue for an account',
inputSchema: {
type: 'object',
properties: {
accountId: { type: 'string', description: 'Account ID' },
startDate: { type: 'string', description: 'Start date (ISO 8601)' },
endDate: { type: 'string', description: 'End date (ISO 8601)' },
},
required: ['accountId'],
},
async execute({ accountId, startDate, endDate }) {
const opportunities = await kernel.getDriver().find({
object: 'opportunity',
filters: [
{ field: 'account_id', operator: 'eq', value: accountId },
{ field: 'stage', operator: 'eq', value: 'closed_won' },
{ field: 'close_date', operator: 'gte', value: startDate },
{ field: 'close_date', operator: 'lte', value: endDate },
],
});
const total = opportunities.reduce((sum, opp) => sum + opp.amount, 0);
return {
accountId,
totalRevenue: total,
opportunityCount: opportunities.length,
};
},
});
// Register with MCP server
kernel.getService('mcp').registerTool(calculateRevenueTool);All ObjectStack objects are automatically exposed as MCP resources:
objectstack://objects/opportunity # Opportunity object schema
objectstack://objects/opportunity/records # All opportunity records
objectstack://objects/opportunity/123 # Specific opportunity record
Expose custom resources to AI models:
kernel.getService('mcp').registerResource({
uri: 'objectstack://reports/sales-pipeline',
name: 'Sales Pipeline Report',
description: 'Current sales pipeline with stages and amounts',
mimeType: 'application/json',
async read() {
const opportunities = await kernel.getDriver().find({
object: 'opportunity',
filters: [
{ field: 'stage', operator: 'neq', value: 'closed_won' },
{ field: 'stage', operator: 'neq', value: 'closed_lost' },
],
});
const pipeline = opportunities.reduce((acc, opp) => {
acc[opp.stage] = (acc[opp.stage] || 0) + opp.amount;
return acc;
}, {});
return {
content: [
{
type: 'text',
text: JSON.stringify(pipeline, null, 2),
},
],
};
},
});Register prompt templates that AI models can use:
kernel.getService('mcp').registerPrompt({
name: 'analyze_account',
description: 'Analyze an account and its opportunities',
arguments: [
{
name: 'accountId',
description: 'Account ID to analyze',
required: true,
},
],
async render({ accountId }) {
const account = await kernel.getDriver().findOne({
object: 'account',
filters: [{ field: 'id', operator: 'eq', value: accountId }],
});
const opportunities = await kernel.getDriver().find({
object: 'opportunity',
filters: [{ field: 'account_id', operator: 'eq', value: accountId }],
});
return {
messages: [
{
role: 'user',
content: {
type: 'text',
text: `Analyze this account and provide insights:
Account: ${account.name}
Industry: ${account.industry}
Total Opportunities: ${opportunities.length}
Total Value: $${opportunities.reduce((sum, o) => sum + o.amount, 0)}
Opportunities:
${opportunities.map(o => `- ${o.name} (${o.stage}): $${o.amount}`).join('\n')}
Please provide:
1. Key insights about this account
2. Risk assessment
3. Recommendations for next steps`,
},
},
],
};
},
});Add to ~/Library/Application Support/Claude/claude_desktop_config.json:
{
"mcpServers": {
"objectstack": {
"command": "node",
"args": ["/path/to/your/objectstack/server.js"],
"env": {
"DATABASE_URL": "your-database-url"
}
}
}
}Add to .cursor/mcp.json:
{
"mcpServers": {
"objectstack": {
"command": "node",
"args": ["./server.js"]
}
}
}Configure in Cline settings:
{
"cline.mcpServers": {
"objectstack": {
"command": "node",
"args": ["./server.js"]
}
}
}// server.ts
import { defineStack } from '@objectstack/spec';
import { PluginMCPServer } from '@objectstack/plugin-mcp-server';
import { DriverTurso } from '@objectstack/driver-turso';
const stack = defineStack({
driver: DriverTurso.configure({
url: process.env.DATABASE_URL!,
authToken: process.env.TURSO_AUTH_TOKEN!,
}),
plugins: [
PluginMCPServer.configure({
serverName: 'my-crm',
transport: 'stdio', // Claude Desktop, Cursor, Cline
}),
],
});
await stack.boot();const stack = defineStack({
driver: DriverTurso.configure({ /* ... */ }),
plugins: [
PluginMCPServer.configure({
serverName: 'my-crm',
transport: 'http',
port: 3100,
}),
],
});
await stack.boot();
// MCP server running on http://localhost:3100kernel.getService('mcp').registerResource({
uri: 'objectstack://exports/opportunities-csv',
name: 'Opportunities Export (CSV)',
mimeType: 'text/csv',
async *stream() {
// Stream header
yield 'Name,Stage,Amount,Close Date\n';
// Stream records in batches
let offset = 0;
const batchSize = 100;
while (true) {
const batch = await kernel.getDriver().find({
object: 'opportunity',
limit: batchSize,
offset,
});
if (batch.length === 0) break;
for (const opp of batch) {
yield `${opp.name},${opp.stage},${opp.amount},${opp.close_date}\n`;
}
offset += batchSize;
}
},
});kernel.getService('mcp').registerTool({
name: 'delete_opportunity',
description: 'Delete an opportunity',
permissions: ['opportunity:delete'], // Require permission
inputSchema: {
type: 'object',
properties: {
id: { type: 'string' },
},
required: ['id'],
},
async execute({ id }, context) {
// context includes userId, permissions, etc.
if (!context.hasPermission('opportunity:delete')) {
throw new Error('Permission denied');
}
await kernel.getDriver().delete({
object: 'opportunity',
filters: [{ field: 'id', operator: 'eq', value: id }],
});
return { success: true, deleted: id };
},
});// Register tools from flow definitions
const flows = await kernel.getMetadata('flow');
for (const flow of flows) {
kernel.getService('mcp').registerTool({
name: `flow_${flow.name}`,
description: flow.description,
inputSchema: generateSchemaFromFlow(flow),
async execute(inputs) {
return await kernel.executeFlow(flow.name, inputs);
},
});
}The MCP server exposes these capabilities:
{
"capabilities": {
"tools": {
"listChanged": true
},
"resources": {
"subscribe": true,
"listChanged": true
},
"prompts": {
"listChanged": true
},
"logging": {},
"experimental": {
"streaming": true
}
}
}- Tool Design: Keep tools focused and well-documented
- Resource Naming: Use clear, hierarchical URI schemes
- Prompt Templates: Make prompts flexible with arguments
- Error Handling: Always return helpful error messages
- Permissions: Check permissions before tool execution
- Performance: Use streaming for large datasets
- Versioning: Version your server and tools
Enable debug logging:
PluginMCPServer.configure({
serverName: 'my-crm',
debug: true, // Log all MCP messages
});View MCP messages in client:
- Claude Desktop: Check logs in
~/Library/Logs/Claude/mcp*.log - Cursor: Check Output panel → MCP Server
- Cline: Check extension logs
import { defineStack, defineTool } from '@objectstack/spec';
import { PluginMCPServer } from '@objectstack/plugin-mcp-server';
const stack = defineStack({
driver: /* ... */,
plugins: [
PluginMCPServer.configure({
serverName: 'crm-assistant',
autoRegisterTools: true,
}),
],
});
await stack.boot();
const mcp = stack.kernel.getService('mcp');
// Register custom tools
mcp.registerTool(defineTool({
name: 'forecast_revenue',
description: 'Forecast revenue based on pipeline',
async execute() {
// Implementation
},
}));
// Register custom resources
mcp.registerResource({
uri: 'objectstack://dashboards/sales',
name: 'Sales Dashboard',
async read() {
// Implementation
},
});
// Register prompts
mcp.registerPrompt({
name: 'weekly_report',
description: 'Generate weekly sales report',
async render() {
// Implementation
},
});Apache-2.0