Documentation Index
Fetch the complete documentation index at: https://mintlify.com/block/goose/llms.txt
Use this file to discover all available pages before exploring further.
Goose extensions communicate using MCP (Model Context Protocol), a standardized protocol for AI agent-extension communication.
What is MCP?
MCP (Model Context Protocol) is an open protocol that enables AI applications to integrate with external data sources and tools. It provides:
- Standardized communication - Consistent interface across extensions
- Tool discovery - Extensions expose available capabilities
- Bidirectional streaming - Efficient data transfer
- Type safety - JSON Schema validation
Protocol Overview
MCP uses JSON-RPC 2.0 over stdio (standard input/output) for communication.
Requests:
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/list",
"params": {}
}
Responses:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"tools": [
{
"name": "read_file",
"description": "Read file contents",
"inputSchema": { ... }
}
]
}
}
Errors:
{
"jsonrpc": "2.0",
"id": 1,
"error": {
"code": -32601,
"message": "Method not found"
}
}
Core MCP Methods
List Tools
Request:
{
"method": "tools/list",
"params": {}
}
Response:
{
"result": {
"tools": [
{
"name": "tool_name",
"description": "What the tool does",
"inputSchema": {
"type": "object",
"properties": { ... },
"required": [ ... ]
}
}
]
}
}
Call Tool
Request:
{
"method": "tools/call",
"params": {
"name": "tool_name",
"arguments": { ... }
}
}
Response:
{
"result": {
"content": [
{
"type": "text",
"text": "Tool result"
}
],
"isError": false
}
}
Resources
List Resources
Request:
{
"method": "resources/list",
"params": {}
}
Response:
{
"result": {
"resources": [
{
"uri": "ext://path/to/resource",
"name": "Resource Name",
"description": "What it contains",
"mimeType": "application/json"
}
]
}
}
Read Resource
Request:
{
"method": "resources/read",
"params": {
"uri": "ext://path/to/resource"
}
}
Response:
{
"result": {
"contents": [
{
"uri": "ext://path/to/resource",
"mimeType": "application/json",
"text": "{ ... }"
}
]
}
}
Prompts
List Prompts
Request:
{
"method": "prompts/list",
"params": {}
}
Response:
{
"result": {
"prompts": [
{
"name": "prompt_name",
"description": "What the prompt does",
"arguments": [
{
"name": "arg_name",
"description": "Argument purpose",
"required": true
}
]
}
]
}
}
Get Prompt
Request:
{
"method": "prompts/get",
"params": {
"name": "prompt_name",
"arguments": { ... }
}
}
Response:
{
"result": {
"messages": [
{
"role": "user",
"content": {
"type": "text",
"text": "Generated prompt"
}
}
]
}
}
MCP in Goose
Goose uses the rmcp crate for MCP implementation.
Server Implementation
Implement the ServerHandler trait:
use rmcp::{ServerHandler, model::*};
use async_trait::async_trait;
struct MyMCPServer;
#[async_trait]
impl ServerHandler for MyMCPServer {
async fn list_tools(
&self,
_params: ListToolsRequestParams,
) -> rmcp::Result<ListToolsResult> {
Ok(ListToolsResult {
tools: vec![
Tool {
name: "example_tool".into(),
description: Some("Example tool".to_string()),
input_schema: serde_json::json!({
"type": "object",
"properties": {
"input": {"type": "string"}
},
"required": ["input"]
}),
}
],
})
}
async fn call_tool(
&self,
params: CallToolRequestParams,
) -> rmcp::Result<CallToolResult> {
match params.name.as_str() {
"example_tool" => {
// Extract arguments
let input = params.arguments
.get("input")
.and_then(|v| v.as_str())
.ok_or_else(|| rmcp::Error::invalid_params(
"Missing input parameter"
))?;
// Perform operation
let result = process(input);
// Return result
Ok(CallToolResult {
content: vec![ToolResponseContent::text(result)],
is_error: Some(false),
})
}
_ => Err(rmcp::Error::method_not_found(
format!("Unknown tool: {}", params.name)
)),
}
}
}
Serving the Extension
Stdio transport:
use rmcp::ServiceExt;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let server = MyMCPServer;
let stdin = tokio::io::stdin();
let stdout = tokio::io::stdout();
server.serve((stdin, stdout)).await?.waiting().await;
Ok(())
}
In-process (for builtin extensions):
fn spawn_and_serve<S>(
server: S,
transport: (tokio::io::DuplexStream, tokio::io::DuplexStream),
) where
S: ServerHandler + Send + 'static,
{
tokio::spawn(async move {
match server.serve(transport).await {
Ok(running) => {
let _ = running.waiting().await;
}
Err(e) => tracing::error!("Server error: {}", e),
}
});
}
Extension Manager
Goose’s ExtensionManager handles MCP communication:
use goose::agents::extension_manager::ExtensionManager;
// Create manager with extensions
let manager = ExtensionManager::new(
vec![extension_config],
cancellation_token,
).await?;
// List all tools from all extensions
let tools = manager.list_tools().await?;
// Call a tool
let result = manager.call_tool(
"tool_name",
tool_params,
).await?;
Testing MCP Extensions
MCP Inspector
Use the official MCP Inspector to test extensions:
npx @modelcontextprotocol/inspector cargo run -p goose-mcp --example mcp
For external extensions:
npx @modelcontextprotocol/inspector python my_extension.py
Integration Tests
Record and replay MCP interactions (crates/goose/tests/mcp_integration_test.rs):
#[tokio::test]
async fn test_mcp_extension() {
let config = ExtensionConfig::builtin("memory");
let manager = ExtensionManager::new(
vec![config],
CancellationToken::new(),
).await.unwrap();
let tools = manager.list_tools().await.unwrap();
assert!(!tools.is_empty());
}
Record interactions:
GOOSE_RECORD_MCP=1 cargo test --package goose --test mcp_integration_test
Error Handling
Standard Error Codes
-32700 - Parse error
-32600 - Invalid request
-32601 - Method not found
-32602 - Invalid params
-32603 - Internal error
Custom Errors
use rmcp::Error;
// Invalid parameters
Err(Error::invalid_params("Missing required field"))
// Method not found
Err(Error::method_not_found("Unknown tool"))
// Internal error
Err(Error::internal_error("Database connection failed"))
Transport Layer
MCP supports multiple transports:
Stdio (Standard)
Communication over stdin/stdout:
let stdin = tokio::io::stdin();
let stdout = tokio::io::stdout();
server.serve((stdin, stdout)).await?
Duplex Stream (Builtin Extensions)
In-process communication:
let (client_read, server_write) = tokio::io::duplex(8192);
let (server_read, client_write) = tokio::io::duplex(8192);
server.serve((server_read, server_write)).await?
Best Practices
Validate all inputs using JSON Schema:
input_schema: serde_json::json!({
"type": "object",
"properties": {
"path": {
"type": "string",
"pattern": "^[a-zA-Z0-9/_-]+$"
}
},
"required": ["path"]
})
2. Clear Descriptions
Provide detailed descriptions:
Tool {
name: "read_file".into(),
description: Some(
"Read contents of a file. Returns the file contents as text. \
Fails if file doesn't exist or is not readable.".to_string()
),
// ...
}
3. Error Reporting
Return actionable error messages:
if !path.exists() {
return Ok(CallToolResult {
content: vec![ToolResponseContent::text(
format!("File not found: {}", path.display())
)],
is_error: Some(true),
});
}
4. Timeouts
Implement timeouts for long operations:
tokio::time::timeout(
Duration::from_secs(30),
perform_operation(),
).await??
MCP Resources
Next Steps