Supabase Memory MCP Server
by gtrusler
- src
#!/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 { exec, spawn } from 'child_process';
import { promisify } from 'util';
import { join } from 'path';
// Convert exec to promise-based
const execAsync = promisify(exec);
/**
* MCP Inception Server
* This server provides a wrapper around the ToGODer CLI tool, allowing it to be used through the MCP protocol.
* It executes shell commands and returns their output, specifically designed for research and data fetching tasks.
*/
interface McpInceptionConfig {
executable?: string;
workingDirectory?: string;
maxConcurrent?: number;
}
class McpInceptionServer {
private server: Server;
private executable: string;
private workingDirectory: string;
private readonly maxConcurrent: number;
constructor(config: McpInceptionConfig = {}) {
this.executable = process.env.MCP_INCEPTION_EXECUTABLE || config.executable || 'llm';
this.workingDirectory = process.env.MCP_INCEPTION_WORKING_DIR || config.workingDirectory || process.cwd();
this.maxConcurrent = parseInt(process.env.MCP_INCEPTION_MAX_CONCURRENT || '') || config.maxConcurrent || 10;
this.server = new Server(
{
name: 'mcp-inception',
version: '0.1.0',
},
{
capabilities: {
tools: {},
},
}
);
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);
});
}
// Helper function to safely pipe input to a command
private async safeCommandPipe(input: string, command: string, forceJson: boolean = false): Promise<{stdout: string, stderr: string}> {
return new Promise((resolve, reject) => {
// Get the full path to the executable
const executablePath = join(this.workingDirectory, this.executable);
console.error(`[Debug] Executing: ${executablePath} in ${this.workingDirectory}`);
const proc = spawn('/bin/bash', [executablePath], {
shell: false,
env: process.env, // Pass through environment variables
cwd: this.workingDirectory // Use configured working directory
});
let stdout = '';
let stderr = '';
proc.stdout.on('data', (data) => {
const str = data.toString();
console.error(`[Debug] stdout: ${str}`);
stdout += str;
});
proc.stderr.on('data', (data) => {
const str = data.toString();
console.error(`[Debug] stderr: ${str}`);
stderr += str;
});
proc.on('error', (err) => {
console.error(`[Debug] Process error: ${err.message}`);
reject(new Error(`Failed to start process: ${err.message}`));
});
proc.on('close', (code) => {
console.error(`[Debug] Process exited with code ${code}`);
if (code === 0) {
resolve({ stdout, stderr });
} else {
reject(new Error(`Command failed with code ${code}. stderr: ${stderr}`));
}
});
// Safely write input with newline and close stdin
// If forceJson is true, append a directive to return JSON
const inputWithDirective = forceJson ? input + ' [RESPOND IN JSON KEY-VALUE PAIRS]' : input;
proc.stdin.write(Buffer.from(inputWithDirective + '\n'));
proc.stdin.end();
});
}
/**
* Executes multiple commands in parallel with a maximum concurrency limit
*/
private async executeParallel(prompt: string, items: string[]): Promise<{results: any[], errors: string[]}> {
const results: any[] = [];
const errors: string[] = [];
// Process items in chunks based on maxConcurrent
for (let i = 0; i < items.length; i += this.maxConcurrent) {
const chunk = items.slice(i, i + this.maxConcurrent);
const promises = chunk.map(async (item) => {
try {
const { stdout, stderr } = await this.safeCommandPipe(`${prompt} ${item}`, this.executable, true);
if (stdout) {
results.push(stdout);
} else if (stderr) {
errors.push(`Error processing item "${item}": ${stderr}`);
}
} catch (error: any) {
errors.push(`Failed to process item "${item}": ${error.message}`);
}
});
// Wait for current chunk to complete before processing next chunk
await Promise.all(promises);
}
return { results, errors };
}
private setupToolHandlers() {
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: 'execute_mcp_client',
description: 'Offload certain tasks to AI. Used for research purposes, do not use for code editing or anything code related. Only used to fetch data.',
inputSchema: {
type: 'object',
properties: {
command: {
type: 'string',
description: 'The MCP client command to execute',
},
},
required: ['command'],
},
},
{
name: 'execute_parallel_mcp_client',
description: 'Execute multiple AI tasks in parallel, with responses in JSON key-value pairs.',
inputSchema: {
type: 'object',
properties: {
prompt: {
type: 'string',
description: 'The base prompt to use for all executions',
},
items: {
type: 'array',
items: {
type: 'string'
},
description: 'Array of parameters to process in parallel',
},
},
required: ['prompt', 'items'],
},
},
],
}));
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
switch (request.params.name) {
case 'execute_mcp_client': {
const args = request.params.arguments as { command: string };
try {
const { stdout, stderr } = await this.safeCommandPipe(args.command, this.executable);
return {
content: [
{
type: 'text',
text: stdout || stderr,
},
],
};
} catch (error: any) {
return {
content: [
{
type: 'text',
text: `Error executing MCP client command: ${error?.message || 'Unknown error'}`,
},
],
isError: true,
};
}
}
case 'execute_parallel_mcp_client': {
const args = request.params.arguments as { prompt: string; items: string[] };
try {
const { results, errors } = await this.executeParallel(args.prompt, args.items);
return {
content: [
{
type: 'text',
text: JSON.stringify({ results, errors }, null, 2),
},
],
isError: errors.length > 0,
};
} catch (error: any) {
return {
content: [
{
type: 'text',
text: `Error executing parallel MCP client commands: ${error?.message || 'Unknown error'}`,
},
],
isError: true,
};
}
}
default:
throw new McpError(
ErrorCode.MethodNotFound,
`Unknown tool: ${request.params.name}`
);
}
});
}
async run() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error('MCP Inception server running on stdio');
}
}
const server = new McpInceptionServer();
server.run().catch(console.error);