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

> Discover how Macro's Model Context Protocol (MCP) enables seamless interaction with external AI agents. Explore its standardized authentication, tool mapping, and dynamic prompt generation capabilities.

- Repository: [Macro/macro](https://github.com/macro-inc/macro)
- Tags: deep-dive
- Published: 2026-08-20

---

**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](https://github.com/macro-inc/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`](https://github.com/macro-inc/macro/blob/main/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`](https://github.com/macro-inc/macro/blob/main/services/mcp_service/src/tool_service.rs) lines 25-38, the handler establishes the authenticated context:

```rust
// 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`](https://github.com/macro-inc/macro/blob/main/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`](https://github.com/macro-inc/macro/blob/main/services/mcp_service/src/tool_service.rs) lines 70-79:

```rust
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`](https://github.com/macro-inc/macro/blob/main/crates/prompt/src/lib.rs) lines 62-69, the `mcp_instructions` function assembles the complete prompt:

```rust
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`](https://github.com/macro-inc/macro/blob/main/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`](https://github.com/macro-inc/macro/blob/main/services/mcp_auth_proxy/src/lib.rs) lines 1-7, the module registers dynamic public clients:

```rust
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`](https://github.com/macro-inc/macro/blob/main/services/mcp_auth_proxy/src/inbound/middleware.rs) lines 85-92) validates bearer tokens before delegation:

```rust
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`](https://github.com/macro-inc/macro/blob/main/services/mcp_service/src/main.rs), the server binary initializes the complete stack:

```rust
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:

```rust
// 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`](https://github.com/macro-inc/macro/blob/main/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`](https://github.com/macro-inc/macro/blob/main/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.