Using MCP Servers
Using MCP Servers
Mentu scripts can call tools on external MCP servers through the servers.* proxy. This lets you compose mentu's SDK with any MCP-compatible tool — web crawlers, databases, code analysis, and more.
1. Configure servers
MCP servers are configured in .mcp.json at your workspace root. This is the standard MCP configuration format:
{
"mcpServers": {
"crawlio": {
"command": "/path/to/CrawlioMCP",
"args": [],
"env": {}
},
"sqlite": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-sqlite", "/path/to/db.sqlite"]
}
}
}Each key (e.g., crawlio, sqlite) becomes the server ID you use in scripts.
2. Call tools from scripts
Access any configured server through the servers proxy:
// Call a tool on the 'crawlio' server
const result = await servers.crawlio.call('search_api', {
query: 'authentication endpoints',
limit: 10,
});
console.log(JSON.stringify(result, null, 2));The servers.* proxy returns a Promise — use await to get the result. This is one of only two async APIs in the SDK (the other is sleep()).
3. Compose with CIR
The real power is composing MCP tool results with mentu's SDK. Fetch data from external sources, process it, and capture observations into CIR:
const query = mentu.vars.QUERY ?? 'security vulnerabilities';
// Fetch from an external MCP server
const searchResults = await servers.crawlio.call('search_api', { query });
// Process and capture into CIR
const items = Array.isArray(searchResults) ? searchResults : [];
console.log(`Found ${items.length} results for "${query}"`);
for (const item of items.slice(0, 5)) {
const body = typeof item === 'object' ? JSON.stringify(item) : String(item);
mentu.cir.capture(`Search result for "${query}": ${body.slice(0, 200)}`, {
type: 'observation',
domain: 'research',
source: 'mcp-search',
tags: ['search', query],
});
}
return { query, count: items.length };4. Lazy spawning
Servers are lazy-spawned — the child process is not started when the script begins. The server starts only on the first servers.<id>.call() invocation.
This means:
- Scripts that don't use MCP servers pay no startup cost
- Different code paths can conditionally call different servers
- Servers that aren't needed are never spawned
// The crawlio server process does NOT start here
console.log('Starting analysis...');
const stats = mentu.cir.stats();
// The crawlio server process starts HERE, on first call
if (stats.contradictions > 0) {
const result = await servers.crawlio.call('analyze_page', { url: 'https://example.com' });
console.log('Analysis complete');
}
// If no contradictions, crawlio was never spawnedAfter the script completes (or fails), all spawned server processes are automatically shut down.
5. Error handling
When a server call fails, the error is thrown as a standard JavaScript error:
try {
const result = await servers.myserver.call('some_tool', { arg: 'value' });
console.log('Success:', JSON.stringify(result));
} catch (err) {
console.error(`MCP call failed: ${err}`);
// Continue with fallback logic
mentu.cir.capture(`MCP call to myserver.some_tool failed: ${err}`, {
type: 'observation',
domain: 'errors',
source: 'mcp-script',
});
}Common failure cases:
| Scenario | What happens |
|---|---|
| Server not configured | Error: server ID not found in .mcp.json |
| Server binary not found | Error: spawn fails |
| Server crashes during call | Error thrown with crash details |
| Tool name doesn't exist | Error from the MCP server |
No .mcp.json file |
All servers.* calls fail with empty catalog |
6. Multiple servers in one script
You can call multiple servers in a single script:
// Fetch data from one server
const webData = await servers.crawlio.call('search_api', { query: 'mentu SDK' });
// Query another server
const dbResults = await servers.sqlite.call('read_query', {
sql: 'SELECT count(*) as total FROM events',
});
// Combine results and capture to CIR
mentu.cir.capture(`Cross-source analysis: ${webData.length} web results, ${JSON.stringify(dbResults)} db records`, {
type: 'observation',
domain: 'analysis',
source: 'multi-server-script',
});
return { web: webData.length, db: dbResults };Each server is independently lazy-spawned. If your script calls two servers, two child processes are started (and cleaned up on exit).
Configuration tips
- Place
.mcp.jsonin your workspace root — the script runner reads it from the--workspacedirectory - Server commands must be absolute paths or available in
$PATH - Environment variables in the
envfield are passed to the child process
Next steps
- SDK Reference —
servers.*proxy documentation - Examples — the MCP Tool Composition example
- Script Runner — MCP bootstrapping internals