Contents: What is MCP? · Quick Start · Configuration · Usage · Troubleshooting
This guide explains how to create, configure, and use custom MCP (Model Context Protocol) servers with Maestro.
MCP (Model Context Protocol) is an open protocol that allows AI assistants to interact with external tools and data sources. Maestro supports MCP servers, enabling you to extend its capabilities with custom tools.
# Example: GitHub MCP server
npm install -g @modelcontextprotocol/server-githubCreate ~/.maestro/mcp.json:
{
"mcpServers": {
"github": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": {
"GITHUB_TOKEN": "ghp_your_token_here"
}
}
}
}maestro
/mcpYou should see:
Model Context Protocol
● github
Tools: list_issues, create_issue, get_repository, ...
- Global config:
~/.maestro/mcp.json(applies to all projects) - Project config:
.maestro/mcp.json(project-specific, overrides global)
Maestro supports two configuration formats:
{
"mcpServers": {
"server-name": {
"command": "node",
"args": ["path/to/server.js"],
"env": {
"API_KEY": "your-key"
},
"cwd": "/path/to/working/directory"
}
}
}{
"servers": [
{
"name": "server-name",
"transport": "stdio",
"command": "node",
"args": ["path/to/server.js"],
"env": {
"API_KEY": "your-key"
}
}
]
}| Option | Type | Description | Required |
|---|---|---|---|
command |
string | Executable to run | Yes (for stdio) |
args |
string[] | Command arguments | No |
env |
object | Environment variables | No |
cwd |
string | Working directory | No |
url |
string | Server URL (for HTTP/SSE) | Yes (for HTTP/SSE) |
headers |
object | HTTP headers | No |
disabled |
boolean | Disable this server | No |
timeout |
number | Connection timeout (ms) | No (default: 30000) |
Maestro auto-detects the transport type:
- stdio: Default when
commandis provided - sse: When URL contains
/sseorssesubdomain - http: For other URLs
Managed EvalOps launches can attach the Cerebro world-model MCP server without adding a project config file. Configure one of:
MAESTRO_PLATFORM_MCP_URLMAESTRO_AGENT_MCP_URLMAESTRO_EVALOPS_AGENT_MCP_URLMAESTRO_PLATFORM_MCP_MANIFEST_URL
The URL may be the public app base URL, the /mcp endpoint, or the EvalOps
agent MCP manifest at /.well-known/evalops/agent-mcp.json; Maestro normalizes
those forms to the HTTP MCP endpoint. Maestro forwards the bearer token from MAESTRO_PLATFORM_MCP_TOKEN,
MAESTRO_AGENT_MCP_TOKEN, MAESTRO_EVALOPS_ACCESS_TOKEN, or EVALOPS_TOKEN.
It also forwards X-EvalOps-Workspace-Id, X-EvalOps-Session-Id,
X-EvalOps-Agent-Id, X-EvalOps-Agent-Run-Id, trace/request IDs, and
X-EvalOps-Scopes.
For Cerebro, set scopes deliberately:
cerebro:readexposescerebro_search,cerebro_gather_facts,cerebro_debug_beliefs, and the other read tools.cerebro:assertadditionally exposescerebro_assert_factfor explicit, evidence-backed session learnings.
Agents should search or gather facts before asserting. Use
cerebro_assert_fact only when the session learned durable context that future
agents should recall, and always include a stable dimension, confidence reason,
and evidence.
// my-tools-server.ts
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
const server = new Server(
{ name: "my-tools", version: "1.0.0" },
{ capabilities: { tools: {} } }
);
// Define available tools
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: "greet",
description: "Generate a greeting message",
inputSchema: {
type: "object",
properties: {
name: {
type: "string",
description: "Name to greet",
},
},
required: ["name"],
},
},
{
name: "calculate",
description: "Perform basic math operations",
inputSchema: {
type: "object",
properties: {
operation: {
type: "string",
enum: ["add", "subtract", "multiply", "divide"],
},
a: { type: "number" },
b: { type: "number" },
},
required: ["operation", "a", "b"],
},
},
],
}));
// Handle tool execution
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
switch (name) {
case "greet":
return {
content: [
{ type: "text", text: `Hello, ${args.name}! Welcome to Maestro.` },
],
};
case "calculate": {
const { operation, a, b } = args as {
operation: string;
a: number;
b: number;
};
let result: number;
switch (operation) {
case "add":
result = a + b;
break;
case "subtract":
result = a - b;
break;
case "multiply":
result = a * b;
break;
case "divide":
result = b !== 0 ? a / b : NaN;
break;
default:
throw new Error(`Unknown operation: ${operation}`);
}
return {
content: [{ type: "text", text: `Result: ${result}` }],
};
}
default:
throw new Error(`Unknown tool: ${name}`);
}
});
// Start the server
const transport = new StdioServerTransport();
await server.connect(transport);# Install dependencies
npm install @modelcontextprotocol/sdk
# Build
npx tsc my-tools-server.ts --module nodenext --moduleResolution nodenext
# Test locally
echo '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | node my-tools-server.js{
"mcpServers": {
"my-tools": {
"command": "node",
"args": ["/path/to/my-tools-server.js"]
}
}
}MCP tools can include behavior hints that Maestro respects:
{
name: "delete_file",
description: "Delete a file from the filesystem",
inputSchema: { /* ... */ },
annotations: {
destructiveHint: true, // May perform destructive actions
readOnlyHint: false, // Modifies environment
idempotentHint: false, // Multiple calls have different effects
openWorldHint: true, // Interacts with external systems
}
}| Annotation | Meaning |
|---|---|
readOnlyHint |
Tool doesn't modify its environment |
destructiveHint |
Tool may perform destructive updates |
idempotentHint |
Safe to call repeatedly with same args |
openWorldHint |
Tool interacts with external systems |
MCP servers can also provide resources (data) and prompts (templates):
/mcp resources/mcp resources my-server resource://path/to/resource/mcp prompts/mcp prompts my-server prompt-nameimport { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { Pool } from "pg";
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
server.setRequestHandler(CallToolRequestSchema, async (request) => {
if (request.params.name === "query") {
const { sql } = request.params.arguments as { sql: string };
// Safety: Only allow SELECT queries
if (!sql.trim().toLowerCase().startsWith("select")) {
return {
content: [{ type: "text", text: "Error: Only SELECT queries allowed" }],
isError: true,
};
}
const result = await pool.query(sql);
return {
content: [
{ type: "text", text: JSON.stringify(result.rows, null, 2) },
],
};
}
});server.setRequestHandler(CallToolRequestSchema, async (request) => {
if (request.params.name === "fetch_weather") {
const { city } = request.params.arguments as { city: string };
const response = await fetch(
`https://api.weatherapi.com/v1/current.json?key=${process.env.WEATHER_API_KEY}&q=${city}`
);
const data = await response.json();
return {
content: [
{
type: "text",
text: `Weather in ${city}: ${data.current.condition.text}, ${data.current.temp_c}°C`,
},
],
};
}
});import { watch } from "fs";
// Notify Maestro when files change
watch("./src", { recursive: true }, (event, filename) => {
server.notification({
method: "notifications/resources/list_changed",
});
});-
Check server is executable:
node /path/to/server.js
-
Verify config syntax:
cat ~/.maestro/mcp.json | jq .
-
Check Maestro logs:
MAESTRO_LOG_LEVEL=debug maestro
-
Verify server implements tools/list:
echo '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | node server.js
-
Check for connection errors in
/mcpoutput
- Only explicitly configured env vars are passed to stdio servers
- System env vars are NOT inherited (security measure)
- Add required vars to the
envconfig block
- Check stderr output from the server
- Ensure all dependencies are installed
- Verify the working directory (
cwd) is correct
- Environment Isolation: MCP servers only receive explicitly configured env vars
- Input Validation: Always validate tool inputs before execution
- Principle of Least Privilege: Only expose necessary tools
- Secrets Management: Use env vars for API keys, never hardcode
npm install @modelcontextprotocol/sdkKey imports:
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
ListResourcesRequestSchema,
ReadResourceRequestSchema,
ListPromptsRequestSchema,
GetPromptRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";| Command | Description |
|---|---|
/mcp |
Show server status and tools |
/mcp resources |
List all resources |
/mcp resources <server> <uri> |
Read a specific resource |
/mcp prompts |
List all prompts |
/mcp prompts <server> <name> |
Get a specific prompt |