Docs/Adapters/External MCP

External MCP (SecureMCPProxy)

Inline gateway for 3rd-party MCP servers you don't control. Wrap Snowflake, Databricks, Salesforce, and any external MCP server with MACAW security—no server modifications required.

Inline GatewayHTTP + stdio TransportWhitepaper ↗

When to Use

Use CaseAdapter
Building your own MCP serverSecureMCP
Connecting to Snowflake MCPSecureMCPProxy
Connecting to Databricks MCPSecureMCPProxy
Connecting to Salesforce MCPSecureMCPProxy
Wrapping any 3rd-party MCP serverSecureMCPProxy

Quick Start

HTTP Transport (Remote MCP Servers)
from macaw_adapters.mcp import SecureMCPProxy

# Connect to Snowflake MCP server (running with HTTP transport)
proxy = SecureMCPProxy(
    app_name="snowflake-mcp",
    upstream_url="http://localhost:9000/mcp"
)

# List available tools
tools = proxy.list_tools()
for tool in tools:
    print(f"  {tool['name']}: {tool['description']}")

# Call a tool
result = proxy.call_tool("run_snowflake_query", {
    "statement": "SELECT CURRENT_USER(), CURRENT_ROLE()"
})
print(result)
stdio Transport (Local MCP Servers)
import os
from macaw_adapters.mcp import SecureMCPProxy

# Connect to Salesforce MCP server via subprocess
proxy = SecureMCPProxy(
    app_name="salesforce-dx",
    command=["npx", "@salesforce/mcp", "--orgs", "DEFAULT"],
    env={"HOME": os.environ["HOME"]}  # Pass required env vars
)

# Use same API as HTTP transport
tools = proxy.list_tools()
result = proxy.call_tool("query", {"soql": "SELECT Id FROM Account"})

Constructor

class SecureMCPProxy:
    def __init__(
        self,
        app_name: str,                    # Application name (required)

        # HTTP transport (pick one)
        upstream_url: str = None,         # URL of upstream MCP server
        upstream_auth: dict = None,       # Auth config (see below)

        # stdio transport (pick one)
        command: list[str] = None,        # Command to start MCP server
        env: dict = None,                 # Environment variables

        # Identity
        iam_token: str = None,            # IAM token for user identity
        user_name: str = None,            # User name
        intent_policy: dict = None        # MAPL intent policy
    )

Transport Selection

Provide either upstream_url (HTTP transport) or command (stdio transport), not both. HTTP transport is for remote MCP servers; stdio transport spawns a local subprocess.

Authentication Options

# Bearer token authentication
proxy = SecureMCPProxy(
    app_name="my-proxy",
    upstream_url="https://api.example.com/mcp",
    upstream_auth={"type": "bearer", "token": "sk-xxx"}
)

# API key authentication
proxy = SecureMCPProxy(
    app_name="my-proxy",
    upstream_url="https://api.example.com/mcp",
    upstream_auth={
        "type": "api_key",
        "api_key": "my-api-key",
        "header_name": "X-API-Key"
    }
)

Methods

MethodDescription
call_tool(name, params)Call a tool through MACAW security
list_tools()List available tools from upstream
get_tool_schema(name)Get schema for a specific tool
bind_to_user(client)Bind proxy to user identity for multi-tenant
refresh_tools()Re-discover tools from upstream

Multi-User Pattern

Use bind_to_user() to create per-user proxies with proper identity propagation:

Per-User Identity Binding
from macaw_adapters.mcp import SecureMCPProxy
from macaw_client import MACAWClient, RemoteIdentityProvider

# Service creates shared proxy
proxy = SecureMCPProxy(
    app_name="data-platform",
    upstream_url="http://localhost:9000/mcp"
)

# Authenticate users via your IdP (Keycloak, Okta, etc.)
alice_jwt, _ = RemoteIdentityProvider().login("alice", "Alice123!")
alice = MACAWClient(
    user_name="alice",
    iam_token=alice_jwt,
    app_name="data-platform"
)
alice.register()

bob_jwt, _ = RemoteIdentityProvider().login("bob", "Bob@123!")
bob = MACAWClient(
    user_name="bob",
    iam_token=bob_jwt,
    app_name="data-platform"
)
bob.register()

# Bind proxy to each user's identity
alice_proxy = proxy.bind_to_user(alice)
bob_proxy = proxy.bind_to_user(bob)

# Each user's calls use their identity and policy
alice_result = alice_proxy.call_tool("list_databases", {})
bob_result = bob_proxy.call_tool("run_query", {"sql": "SELECT 1"})

Per-Invocation Security

Even with persistent connections (stdio), MACAW creates a fresh AuthenticatedContext for each call. This solves the "shared context" problem in standard MCP—each invocation carries the correct user identity and is cryptographically signed.


Security Model

Identity Propagation

User identity embeds in invocation—not impersonation, cryptographic proof

Cryptographic Signing

Every call signed with user's session key, binding identity to exact parameters

Policy Enforcement

MAPL policies evaluated before forwarding to upstream

Audit Logging

All operations logged with cryptographic proof of authorization

The inline gateway pattern provides the same security guarantees as a centralized gateway—but distributed. See the Inline Gateway Whitepaper for architectural details.


Properties

PropertyTypeDescription
proxy.app_namestrApplication name
proxy.upstream_urlstrUpstream URL (HTTP transport)
proxy.agent_idstrMACAW agent ID
proxy.server_idstrAlias for agent_id
proxy.tool_schemasdictDiscovered tool schemas
proxy.is_connectedboolConnection status

Related Topics