Skip to main content

Overview

MCP (Model Context Protocol) resources let you intercept and control HTTP traffic originating from AI agents. Formal sits in the path to enforce policies and log all activity.

Connecting via the Desktop App (Transparent Proxy)

Transparent proxy mode is only supported on macOS. The Formal network extension must be enabled (formal transparent-proxy enable).
Use the Desktop App’s transparent proxy to intercept outbound traffic without reconfiguring the MCP client itself.
formal connect --transparent mcp-resource-name
Replace mcp-resource-name with the name of your MCP resource in Formal.

Creating an MCP Resource

resource "formal_resource" "github_mcp" {
  name       = "github-mcp"
  technology = "mcp"
  hostname   = "api.githubcopilot.com"
  port       = 443
}

Trusting the Formal CA

The Desktop App terminates TLS using a CA it manages at ~/.formal/ca/formal-org-ca.cer. MCP clients must trust this CA to allow interception. Trust it manually on macOS:
  1. Open ~/.formal/ca/formal-org-ca.cer in Keychain Access.
  2. Select the Formal certificate and open Trust.
  3. Set Secure Sockets Layer (SSL) to Always Trust.
Or set the environment variable for Node.js-based MCP clients:
export NODE_EXTRA_CA_CERTS="$HOME/.formal/ca/formal-org-ca.cer"

Connecting without the Desktop App

If your MCP client runs in a cloud environment, route its traffic through a Connector by updating the upstream hostname. Replace the target API hostname with the Connector hostname (and the resource subdomain if using smart routing):
  • Before: https://api.githubcopilot.com/mcp
  • After: https://<your connector hostname>/mcp
Like with the HTTP technology, set X-Formal-User-Username and X-Formal-User-Password headers to associate requests with a Formal identity.

Policy Evaluation

Formal supports the following policy evaluation stages for MCP:
  • Session: Evaluate and enforce policies at connection time
  • Request: Evaluate and enforce policies before the request is forwarded
  • Response: Evaluate and enforce policies after the response is received
At the request and response stages, input.mcp is populated when the request is an MCP tool call. See MCP policy inputs for the full list of available fields.

Example: Block a specific tool

package formal.v2

import future.keywords.if

default request := {"action": "allow"}

request := {"action": "block", "type": "block_with_formal_message"} if {
  input.mcp.tool_name == "list_pull_requests"
}

Authentication

The Connector forwards authentication headers from the MCP client transparently. You can also inject credentials using Native Users so the MCP client does not need to hold secrets directly.