MCP Server Development
Guide for adding new built-in MCP servers to Octomind.
When to Add a New Server
Add a built-in server when you need:
- Deep integration with Octomind internals (session state, config)
- Functionality that doesn't make sense as an external process
- Tools that require access to the MCP coordinator
For external tools, prefer configuring a stdio or http server in config.
Built-in Servers
| Server | Location | Purpose |
|---|---|---|
core |
src/mcp/core/ |
Plan, MCP management, agents, scheduling, skills |
agent |
src/mcp/agent/ |
Agent delegation via ACP |
External:
| Server | Type | Purpose |
|---|---|---|
filesystem (octofs) |
stdio | File ops, shell, AST grep |
Step-by-Step Guide
1. Create Server Module
src/mcp/
your_server/
mod.rs # Server implementation
2. Implement get_all_functions()
Return a list of MCP tool definitions:
use rmcp::model::{Tool, ToolInputSchema};
use serde_json::json;
pub fn get_all_functions() -> Vec<Tool> {
vec![
Tool {
name: "your_tool".into(),
description: Some("Description shown to AI".into()),
input_schema: ToolInputSchema {
r#type: "object".into(),
properties: Some(json!({
"param1": {
"type": "string",
"description": "Parameter description"
}
})),
required: Some(vec!["param1".into()]),
..Default::default()
},
},
]
}
3. Register in src/mcp/mod.rs
Add your server to the MCP coordinator's server registry.
4. Implement Tool Execution
Handle tool calls and return results:
use crate::mcp::McpToolResult;
pub async fn execute_tool(
tool_name: &str,
params: &serde_json::Value,
tool_id: &str,
) -> McpToolResult {
match tool_name {
"your_tool" => {
let param1 = match params.get("param1").and_then(|v| v.as_str()) {
Some(v) => v,
None => return McpToolResult::error(
tool_id,
"Missing required parameter: param1"
),
};
// Do work...
McpToolResult::success(tool_id, "Result text")
}
_ => McpToolResult::error(tool_id, &format!("Unknown tool: {tool_name}")),
}
}
5. Add Config Registration
Register your server as a builtin in the config:
[[mcp.servers]]
name = "your_server"
type = "builtin"
timeout_seconds = 30
tools = []
6. Add Misuse Hints (Optional)
Provide hints when the AI misuses a tool:
pub fn get_misuse_hints() -> Vec<(&'static str, &'static str)> {
vec![
("your_tool", "Use param1 for X, not Y"),
]
}
Protocol Compliance
All tools must follow MCP protocol:
- Return
McpToolResult::error()instead of panicking - Validate all parameters with clear error messages
- Handle missing/empty/wrong-type parameters gracefully
- Support cancellation via
CancellationToken - Use standard response format:
{content: [{type: "text", text: "..."}], isError: bool}
Testing
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_your_tool() {
let params = serde_json::json!({"param1": "test"});
let result = execute_tool("your_tool", ¶ms, "test-id").await;
assert!(!result.is_error);
}
#[tokio::test]
async fn test_missing_params() {
let params = serde_json::json!({});
let result = execute_tool("your_tool", ¶ms, "test-id").await;
assert!(result.is_error);
}
}
Reference Patterns
Config-Dependent Functions
pub fn get_config_functions(config: &Config) -> Vec<Tool> {
let mut tools = get_all_functions();
if config.some_feature_enabled {
tools.push(additional_tool());
}
tools
}
Async Operations with Timeout
use tokio::time::timeout;
use std::time::Duration;
let result = timeout(
Duration::from_secs(config.timeout),
async_operation()
).await
.map_err(|_| anyhow!("Operation timed out"))?;
Server Stderr Capture
For stdio servers, stderr is captured in SERVER_STDERR buffer for debugging. Access via get_server_stderr(server_name).