Governed substrate for autonomous agents: scoped identity (passports), audited actions, MCP workspace. Infra IPs and secrets redacted for public release.
127 lines
4.4 KiB
JavaScript
127 lines
4.4 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* GOD CRM — MCP Server
|
|
*
|
|
* Exposes all CRM backend tools as MCP tools for Claude Code CLI.
|
|
* Reads tool definitions from AGENT_TOOLS, executes via executeTool().
|
|
*
|
|
* Usage:
|
|
* node backend/mcp-server.js (stdio transport)
|
|
*
|
|
* Connect via Claude Code CLI:
|
|
* claude --mcp-config .mcp.json
|
|
*/
|
|
|
|
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 dotenv from 'dotenv';
|
|
import path from 'path';
|
|
import { fileURLToPath } from 'url';
|
|
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
dotenv.config({ path: path.resolve(__dirname, '../.env') });
|
|
|
|
// Import CRM tool system
|
|
import { executeTool } from './services/agent-tools/executor.js';
|
|
import { buildToolList } from './services/mcp/buildToolList.js';
|
|
|
|
// ADR-0040: this stdio MCP server is a SEPARATE process from the Express boot,
|
|
// so it must initialize the SecretsVault itself. Without this, the vault
|
|
// singleton stays uninitialized in this process and vault-backed tools with no
|
|
// env fallback (e.g. bluesky_search → bluesky_handle/app_password) return
|
|
// "not configured" even though the creds are present and decrypt fine.
|
|
import secretsVault from './services/secrets/SecretsVault.js';
|
|
import { getAdapter as getDbAdapter } from './database/connection.js';
|
|
|
|
// ── Build tool list ────────────────────────────────────────────
|
|
// SKIP_TOOLS + the OpenAI→MCP conversion now live in the shared builder so the
|
|
// stdio surface here and the public HTTP mount can never drift (NO-SIGNUP-MCP §B1).
|
|
|
|
const mcpTools = buildToolList();
|
|
|
|
// ── MCP Server ─────────────────────────────────────────────────
|
|
|
|
const server = new Server(
|
|
{
|
|
name: 'godcrm',
|
|
version: '1.0.0',
|
|
},
|
|
{
|
|
capabilities: {
|
|
tools: {},
|
|
},
|
|
}
|
|
);
|
|
|
|
// List tools handler
|
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
return { tools: mcpTools };
|
|
});
|
|
|
|
// Call tool handler
|
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
const { name, arguments: args } = request.params;
|
|
|
|
// Validate tool exists
|
|
const toolDef = mcpTools.find(t => t.name === name);
|
|
if (!toolDef) {
|
|
return {
|
|
content: [{ type: 'text', text: JSON.stringify({ error: `Unknown tool: ${name}` }) }],
|
|
isError: true,
|
|
};
|
|
}
|
|
|
|
try {
|
|
// userId=1 (system/admin), context with space_id
|
|
const context = {
|
|
spaceId: parseInt(process.env.MCP_SPACE_ID || '11', 10),
|
|
source: 'mcp',
|
|
};
|
|
|
|
const result = await executeTool(name, args || {}, 1, context);
|
|
|
|
const isError = result?.error ? true : false;
|
|
const text = typeof result === 'string' ? result : JSON.stringify(result, null, 2);
|
|
|
|
return {
|
|
content: [{ type: 'text', text }],
|
|
isError,
|
|
};
|
|
} catch (err) {
|
|
return {
|
|
content: [{ type: 'text', text: JSON.stringify({ error: err.message }) }],
|
|
isError: true,
|
|
};
|
|
}
|
|
});
|
|
|
|
// ── Start ──────────────────────────────────────────────────────
|
|
|
|
async function main() {
|
|
// Initialize the SecretsVault before serving tools. Best-effort: a failure
|
|
// here must not stop the MCP server (secret-less tools still work), but in
|
|
// NODE_ENV=production with a missing master key init() self-exits (ADR-0040
|
|
// AC4 fail-fast) — that is intentional. Logs to stderr to keep stdio clean.
|
|
try {
|
|
const adapter = await getDbAdapter();
|
|
const health = await secretsVault.init({ adapter });
|
|
console.error(`[godcrm-mcp] SecretsVault initialized (hasKey=${health.hasKey}, listening=${health.listening}).`);
|
|
} catch (err) {
|
|
console.error('[godcrm-mcp] SecretsVault init failed — vault-backed tools degraded:', err?.message || err);
|
|
}
|
|
|
|
const transport = new StdioServerTransport();
|
|
await server.connect(transport);
|
|
// Log to stderr so it doesn't interfere with stdio protocol
|
|
console.error(`[godcrm-mcp] Server started. ${mcpTools.length} tools registered.`);
|
|
}
|
|
|
|
main().catch((err) => {
|
|
console.error('[godcrm-mcp] Fatal error:', err);
|
|
process.exit(1);
|
|
});
|