How Macro's Model Context Protocol (MCP) Facilitates Interaction with External AI Agents

Macro's Model Context Protocol (MCP) provides a lightweight, streamable HTTP layer that exposes the platform's AI toolset to external models through standardized authentication, tool mapping, and dynamic prompt generation.

The Macro workspace platform implements MCP as a bridge between its internal AI capabilities and third-party LLMs. This protocol transforms Macro's proprietary tools into a protocol-agnostic service that any external AI agent can discover, list, and invoke via standard HTTP calls. The implementation combines Rust-based server infrastructure with OAuth security and runtime-generated instructions tailored for model consumption.

MCP Server Architecture: The mcp_service HTTP Layer

At the core of Macro's MCP implementation is a dedicated HTTP server defined in services/mcp_service/src/tool_service.rs. This service implements the rmcp::handler::server::ServerHandler trait, which standardizes how external clients interact with Macro's tool collection.

The server handles three critical responsibilities:

  • HTTP request routing through an Axum-based web framework
  • User authentication extraction via request extensions
  • Tool call forwarding to Macro's internal AsyncToolCollection

In services/mcp_service/src/tool_service.rs lines 25-38, the handler establishes the authenticated context:

// From services/mcp_service/src/tool_service.rs
impl ServerHandler for AuthenticatedToolService {
    async fn call_tool(
        &self,
        request: CallToolRequest,
    ) -> Result<CallToolResult, rmcp::Error> {
        let user_id = self.authenticated_user_id().await?;
        // Forward to AsyncToolCollection with user-scoped permissions
        self.toolset.execute(request, user_id).await
    }
}

Lines 85-91 handle the initialization of this authenticated context, ensuring every tool execution respects Macro's user-based permission model.

Tool Definition Mapping: From Internal to MCP Wire Format

External AI agents need standardized tool metadata to understand what operations are available. Macro solves this through a bidirectional annotation mapping between its internal ai_toolset::ToolAnnotations and the MCP wire format.

In services/mcp_service/src/tool_service.rs lines 12-22, the conversion preserves critical behavioral flags:

MCP Flag Purpose in Macro
read_only Prevents accidental modifications to workspace content
destructive Warns models about irreversible operations
idempotent Allows safe retry of failed operations
open_world Indicates tools that access external resources

This mapping ensures external LLMs receive accurate capability information without exposure of Macro's internal type system.

Authenticated Context: User-Scoped MCP Requests

Every MCP request in Macro carries an authenticated user identity. The AuthenticatedToolService::authenticated_user_id method extracts a MacroUserIdStr from Axum request extensions, as shown in services/mcp_service/src/tool_service.rs lines 70-79:

async fn authenticated_user_id(&self) -> Result<MacroUserIdStr, rmcp::Error> {
    self.extensions
        .get::<MacroUserIdStr>()
        .cloned()
        .ok_or_else(|| {
            rmcp::Error::invalid_request("Missing authenticated user", None)
        })
}

This design guarantees that MCP calls execute with the exact permissions of the authenticated user, preventing cross-user data leakage or privilege escalation.

Dynamic Instructions for External Models

When advertising capabilities through get_info, Macro's MCP server generates runtime-tailored instructions that teach external models how to format responses correctly. This combines static guidance with dynamic URL generation.

In crates/prompt/src/lib.rs lines 62-69, the mcp_instructions function assembles the complete prompt:

pub fn mcp_instructions(base_url: &str) -> String {
    format!(
        "{}\n\n{}",
        MCP_STATIC_INSTRUCTIONS,           // Citation rules, formatting constraints
        dynamic_linking_instructions(base_url), // URL patterns for Macro items
    )
}

The dynamic section, rendered in crates/prompt/src/mcp_item_links.rs lines 22-34, instructs models to use plain Markdown URLs rather than internal XML tags:


When referencing Macro documents, use this format:
[Document Name](https://macro.com/app/md/<document_id>)

For lists, use this table schema:
| Number | Name | Link |
|--------|------|------|
| 1      | ...  | ...  |

This ensures external AI agents produce outputs that render correctly in Macro's UI while maintaining protocol standardization.

OAuth Security: The mcp_auth_proxy Protection Layer

To secure MCP endpoints against unauthorized access, Macro deploys a dedicated OAuth broker in services/mcp_auth_proxy/. This proxy handles the complete OAuth 2.0 flow for MCP clients.

In services/mcp_auth_proxy/src/lib.rs lines 1-7, the module registers dynamic public clients:

pub async fn register_client(
    &self,
    client_metadata: ClientMetadata,
) -> Result<RegisteredClient, AuthError> {
    // Store client configuration with temporary state in Redis
}

The inbound middleware (services/mcp_auth_proxy/src/inbound/middleware.rs lines 85-92) validates bearer tokens before delegation:

async fn validate_token(
    &self,
    token: &str,
) -> Result<MacroUserIdStr, AuthError> {
    // Redis lookup + JWT validation
    // Returns authenticated user ID on success
}

This architecture separates authentication concerns from tool execution logic, allowing the mcp_service to focus purely on protocol implementation.

Practical Implementation: Client and Server Code

Launching an Authenticated MCP Server

From services/mcp_service/src/main.rs, the server binary initializes the complete stack:

use std::sync::Arc;
use ai_toolset::AsyncToolCollection;
use macro_user_id::user_id::MacroUserIdStr;
use macro_service::tool_service::AuthenticatedToolService;

#[tokio::main]
async fn main() {
    let toolset = Arc::new(AsyncToolCollection::load().await);
    let ctx = AppContext::from_env().await;
    let base_url = std::env::var("APP_BASE_URL")
        .expect("APP_BASE_URL not set");
    
    let service = AuthenticatedToolService::new(
        toolset,
        ctx,
        base_url,
    );
    
    // Axum server binding...
}

External LLM Integration: Listing and Calling Tools

An external AI agent connects to Macro's MCP endpoint using standard client libraries:

// List available tools
let client = rmcp::client::Client::new("https://macro.com/mcp");
let tools = client.list_tools().await?;
println!("Available: {:?}", tools.tools.iter().map(|t| &t.name).collect::<Vec<_>>());

// Execute a workspace operation
let result = client.call_tool(CallToolRequestParams {
    name: "ReadContent".into(),
    arguments: Some(serde_json::json!({
        "documentId": workspace_doc_id
    }).into()),
    ..Default::default()
}).await?;

The rmcp crate handles protocol serialization, while Macro's server enforces authentication and translates calls into internal tool executions.

Summary

  • MCP Server (mcp_service) implements rmcp::handler::server::ServerHandler to expose Macro's tools via HTTP
  • Tool Annotation Mapping converts internal ai_toolset metadata to MCP-compatible flags (read_only, destructive, etc.)
  • Authenticated Context extracts MacroUserIdStr from every request for user-scoped execution
  • Dynamic Prompt Generation (mcp_instructions, mcp_item_links) teaches external models Macro-specific output formats
  • OAuth Protection (mcp_auth_proxy) validates bearer tokens and manages client registration via Redis-backed state

Frequently Asked Questions

What protocol does Macro use for external AI agent communication?

Macro implements the Model Context Protocol (MCP), a lightweight HTTP-based standard that exposes tool definitions and execution capabilities to external LLMs. The server uses the rmcp Rust crate for protocol handling and Axum for HTTP transport.

How does Macro ensure MCP requests are authenticated?

Every MCP request passes through the mcp_auth_proxy OAuth broker, which validates bearer tokens against Redis-stored state and extracts a MacroUserIdStr. This user ID propagates to AuthenticatedToolService in services/mcp_service/src/tool_service.rs, ensuring all tool executions respect the authenticated user's permissions.

Can external models customize how they reference Macro workspace items?

No—Macro enforces output formatting through server-generated instructions. The mcp_instructions function in crates/prompt/src/lib.rs combines static rules with dynamic URL patterns derived from APP_BASE_URL. This ensures all external models produce plain Markdown links that render correctly in Macro's UI, preventing formatting inconsistencies.

What happens if an MCP tool call fails or needs retry?

Tool annotations include idempotent flags that inform external models whether operations are safe to retry. Destructive or non-idempotent tools carry appropriate warnings in their metadata, allowing sophisticated LLMs to implement appropriate error handling strategies without Macro-specific client logic.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →