Skip to content

Your first governed MCP call

This walkthrough picks up where your first governed LLM call left off: the trial is running, a provider key is set on gpt-5.2, and you have a Default-group API key in $BRUTOR_API_KEY. In about ten minutes you will:

  1. Run a small MCP server on your machine, register it in the Admin Console and give the Default resource group access to it.
  2. Connect to it in the User Portal as the trial user and let the assistant call its tool.
  3. Call the same tool from the command line through the Core Proxy, using your Default-group API key.

Brutor is the AI control plane, and an MCP tool call is where an AI System acts on the world. So every MCP call that goes through Brutor is checked before it reaches the server. Brutor checks who the caller is, whether their group may use this server and this tool, and the guardrails, limits and approvals that apply. Then the call is recorded. That record is what Brutor later uses to assure the AI System against its contract.

What you need

Thing Value
Admin Console http://localhost:3002. Log in as a Tenant Admin, tenant ID default, username admin, password Admin123!
User Portal http://localhost:3001. Username trial, password Trial123!
Core Proxy (all runtime traffic) http://localhost:8100. MCP endpoint /v1/proxy/mcp/{server_id}
Resource group Default. The trial user’s only group, and the group your API key belongs to
API key $BRUTOR_API_KEY, a Default-group key (sk_brutor_api_...) from the first LLM call
MCP server brutor-weather-mcp-server. One tool, get_current_weather(city), no authentication, streamable HTTP on port 3012 at /mcp
Tools on your machine git, and uv or Python 3.13+

1. Run an MCP server and give the Default group access

Section titled “1. Run an MCP server and give the Default group access”

Watch Working with MCP Servers: registering an MCP server through the gateway, discovering its tools, prompts and resources, binding it to resource groups, controlling which capabilities each team can call, and using the tools from the User Portal chat.

Watch Working with MCP Servers shows this section and more: registering an MCP server through the gateway, discovering its tools, prompts and resources, binding it to resource groups, controlling which capabilities each team can call, and using the tools from the User Portal chat.

The weather server is a minimal FastMCP server. It looks up a city with OpenStreetMap Nominatim, then fetches current conditions from Open-Meteo. Neither service needs an API key.

Terminal window
git clone https://github.com/brutor-ai/brutor-weather-mcp-server.git
cd brutor-weather-mcp-server
uv run weather-mcp-server-remote.py

If you don’t use uv, run python -m venv .venv && source .venv/bin/activate && pip install -e . && python weather-mcp-server-remote.py instead. Either way, the server listens on 0.0.0.0:3012 and serves MCP at http://localhost:3012/mcp. Set MCP_PORT to use another port.

  1. Open the Admin Console at http://localhost:3002 and log in as the tenant admin.

  2. In the sidebar, go to Resources → MCP Servers → Configuration. Stay on the MCP Servers tab and click Add Server.

  3. In Add New Server, fill in:

    Field Value
    Display Name Weather Assistant
    Description Current weather for a city (Open-Meteo)
    Server Base URL http://host.docker.internal:3012 (on Linux, the address from the caution above)
    MCP endpoint is at root (/) unchecked
    MCP Endpoint Path /mcp
    Authentication Type No Authentication

    The form shows the Complete MCP URL, http://host.docker.internal:3012/mcp. Leave Enable health check (/health endpoint) unchecked, because this server has no /health route. Leave Automatically discover server capabilities checked and click Create Server.

  4. The new row shows the server ID under its name, for example mcp-01M4Z.... The Capabilities column shows 1 tools, which means discovery reached the server and found get_current_weather. It also records the protocol version the server negotiated. Click the capabilities cell to see the tool’s description and input schema.

    MCP Servers list in the Admin Console

  5. Open the row’s ⋮ menu and choose Endpoint Info. Copy the Proxy URL: http://localhost:8100/v1/proxy/mcp/<server_id>. This is the governed address you’ll use from the CLI in step 3.

Registering a server doesn’t give anyone access to it. Access comes from a server configuration, which binds the server to one or more resource groups. It is also how the portal and agents discover the server.

  1. On the same page, open the MCP Server Access tab and click Create Server Config.

  2. In Create Server Configuration, fill in:

    Field Value
    Backend type MCP Server (real upstream)
    MCP Server Weather Assistant
    Configuration Name weather-default
    Display Name (for Users) Weather Assistant. This is the name portal users see
    Access Control (Resource Groups) Default
    Enabled checked

    Click Create Configuration.

  3. Check the result from the group’s side. Go to AI Estate → Resource Groups → Default, open Resources → MCP Servers, and find Weather Assistant with a Direct badge and its tool count.

    A resource group’s MCP Servers tab, with the capability-filter shield and Tool limits in the Actions column

Tool-level access is a separate, optional step. With no capability filter, every tool on a bound server is allowed for the group. The shield icon in the row’s Actions column (Configure governance) opens the capability filters. There, each tool is set to Enabled, Approval or Disabled for this group, and you click Save Filters to apply. Tool limits next to it sets per-tool rate limits. You’ll use the filter in step 4.

The trial user is a member of Default, so they can now see the server.

  1. Open the User Portal at http://localhost:3001 and sign in as trial / Trial123!.

  2. In the navigation, open Data Access. Under Available Data Access Servers you’ll see a Weather Assistant card. Click Connect. The card changes to Connected and shows how many tools, resources and prompts the server has.

    Available Data Access Servers in the User Portal, each with a Connect button

  3. Open Chat, pick OpenAI GPT-5.2 Auto-Routing, and ask: “What’s the weather in Berlin right now?”

  4. The model decides to call get_current_weather, and the portal asks you first. The Tool Call Approval Required dialog shows the tool, its server and the arguments the model chose ({"city": "Berlin"}). Click Approve Once. Approve All also works: it pre-approves this tool so the portal stops asking. You can revoke that later under Profile → MCP Settings → Tool Call Approval. Cancel refuses the call.

  5. The tool’s result, with the current temperature, conditions and wind for Berlin, goes back to the model, and the model answers in plain language.

That confirmation dialog is the portal asking you. It is separate from governance. The governed part happens in the Core Proxy. The portal has no direct connection to the weather server: it sends each MCP request to /v1/proxy/mcp/<server_id> with the trial user’s portal session, as user trial in group Default. Before the call reaches the server, the proxy checks group access, the tool’s capability filter, MCP limits and the Default Guardrails on the tool arguments (mcp_input). The result is scanned again (mcp_output) on the way back. Each step is recorded.

3. Call it from the CLI with a Default-group API key

Section titled “3. Call it from the CLI with a Default-group API key”

Agents and scripts reach the same server with a Brutor API key. The key identifies the tenant and the group, so you don’t need an X-Tenant-ID header.

Terminal window
export BRUTOR_API_KEY="sk_brutor_api_..." # the Default-group key from the first LLM call
export MCP_URL="http://localhost:8100/v1/proxy/mcp/mcp-01M4Z..." # the Proxy URL from Endpoint Info

How the endpoint works:

URL POST /v1/proxy/mcp/{server_id}. Don’t add /mcp to it. The proxy appends the server’s MCP Endpoint Path itself
Body JSON-RPC 2.0: initialize, tools/list, tools/call and so on
Headers Authorization: Bearer sk_brutor_api_... (or X-API-Key), Content-Type: application/json, Accept: application/json, text/event-stream
Sessions The proxy passes MCP sessions through. This server is stateful, so initialize returns an mcp-session-id header, and you send it back as Mcp-Session-Id on every later call
Response Whatever the server sends, after Brutor’s checks. This server answers as Streamable HTTP (event: message / data: {...}). Brutor adds x-correlation-id, which identifies the call’s proxy-log row

The curl tab shows the raw protocol. The Python and Node.js tabs use the official MCP SDKs, which handle initialize, the session ID and SSE parsing for you. That is how a real agent connects.

Terminal window
# 1. initialize: note the mcp-session-id response header
curl -s -D - $MCP_URL \
-H "Authorization: Bearer $BRUTOR_API_KEY" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{
"protocolVersion":"2025-06-18",
"clientInfo":{"name":"curl","version":"1.0"},
"capabilities":{}}}'
export MCP_SESSION="0b0cfcef9f2d4aa3920482a49441da22" # the value you got
# 2. tell the server the client is ready (a notification: no id, empty 202 reply)
curl -s $MCP_URL \
-H "Authorization: Bearer $BRUTOR_API_KEY" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "Mcp-Session-Id: $MCP_SESSION" \
-d '{"jsonrpc":"2.0","method":"notifications/initialized"}'
# 3. tools/list
curl -s $MCP_URL \
-H "Authorization: Bearer $BRUTOR_API_KEY" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "Mcp-Session-Id: $MCP_SESSION" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}'
# 4. tools/call
curl -s $MCP_URL \
-H "Authorization: Bearer $BRUTOR_API_KEY" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "Mcp-Session-Id: $MCP_SESSION" \
-d '{"jsonrpc":"2.0","id":3,"method":"tools/call",
"params":{"name":"get_current_weather","arguments":{"city":"Berlin"}}}'

initialize response. These are the headers that matter. The proxy forwards the server’s session ID and adds its own correlation ID:

HTTP/1.1 200 OK
content-type: text/event-stream
mcp-session-id: 0b0cfcef9f2d4aa3920482a49441da22
x-correlation-id: 4c68426b-1803-44de-b625-ce4d6b34f1bb
x-request-type: mcp-sse
event: message
data: {"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2025-06-18","capabilities":{"experimental":{},"prompts":{"listChanged":false},"resources":{"subscribe":false,"listChanged":false},"tools":{"listChanged":false}},"serverInfo":{"name":"weather-assistant","version":"1.27.1"}}}

tools/list response. This lists only the tools the Default group is allowed to use. Right now, that is all of them:

event: message
data: {"jsonrpc":"2.0","id":2,"result":{"tools":[{"name":"get_current_weather","description":"\nGets the current weather in a given location by using external service. \nFinds longitude and latitude for given city and fetches current temperature using Open-Meteo API.\n\nArgs:\n city: Location of interest (city name)\n \nReturns:\n Weather information including temperature, conditions, and wind speed\n","inputSchema":{"properties":{"city":{"title":"City","type":"string"}},"required":["city"],"title":"get_current_weatherArguments","type":"object"},"outputSchema":{"properties":{"result":{"title":"Result","type":"string"}},"required":["result"],"title":"get_current_weatherOutput","type":"object"}}]}}

tools/call response. This is the server’s result, passed through after the mcp_output guardrail checks:

event: message
data: {"jsonrpc":"2.0","id":3,"result":{"content":[{"type":"text","text":"🌤️ Weather information for Berlin, Deutschland:\n\n🌡️ Current Temperature: 12.2°C\n☁️ Weather Conditions: Overcast\n💨 Wind Speed: 7.9 km/h\n📍 Coordinates: 52.5173885, 13.3951309"}],"structuredContent":{"result":"🌤️ Weather information for Berlin, Deutschland:\n\n🌡️ Current Temperature: 12.2°C\n☁️ Weather Conditions: Overcast\n💨 Wind Speed: 7.9 km/h\n📍 Coordinates: 52.5173885, 13.3951309"},"isError":false}}
  • Operations → Logs & Audit → Proxy Logs has one row for each MCP request, including initialize and tools/list, not just tool calls. MCP rows fill in Resource (the server), Method (tools/call), Capability (get_current_weather), Principal (the trial user, or your group key), Target (the upstream URL), Duration and Status. Expand a row to see the SHA-256 digests of the request, the response and the tool’s definition, which seal what was called and with what. Open the row’s details to see the request and response body previews. To find one call, search for its x-correlation-id.

    Proxy Logs in the Admin Console

  • Resource Groups → Default → Usage counts MCP Calls next to LLM tokens, with daily MCP Requests and an MCP usage panel. Your portal chat and your CLI calls both count here.

A tool filter shows MCP governance most directly. It is a rule on the group, enforced by the gateway, and it holds for every client.

  1. Go to Resource Groups → Default → Resources → MCP Servers. Click the shield (Configure governance) on Weather Assistant, set get_current_weather to Disabled, and click Save Filters.

  2. Run the tools/call from step 3 again, using a new session if the old one has expired. The gateway refuses the call before it reaches the server and returns 403 Forbidden:

    {
    "error": {
    "message": "Access denied: tool 'get_current_weather' is disabled for your group"
    }
    }

    tools/list now leaves the tool out, so an agent never sees it. The refusal is recorded too. Proxy Logs gets a 403 row with the attempted arguments and the reason, and the same applies in the portal chat.

  3. Set the tool back to Enabled and click Save Filters.

The same gateway also enforces Approval on a tool, so the call is held until a group member approves it. It also runs guardrails on the tool’s arguments and results, argument policies that parse a specific argument (for example, deny any SQL that writes), and per-tool rate limits. The pages linked below cover each one.

The portal call and the curl call went through the same MCP pipeline in the Core Proxy:

  1. Identify the caller. The portal session or API key identifies the tenant, the user or key, and the resource group.
  2. Check access to the server. The server has to be bound to the caller’s group through an enabled server configuration.
  3. Apply limits. The group’s MCP quotas and per-tool rate limits are checked.
  4. Check the tool. The group’s capability filter decides Enabled, Approval or Disabled. Argument policies and the mcp_input guardrails check the arguments.
  5. Forward and check the result. Brutor forwards the call without your key, adds the server’s own credentials if it has any, and scans the result with the mcp_output guardrails.
  6. Record. A proxy-log row is written under the correlation ID and chained into the audit trail. Blocked calls are recorded the same way.

An MCP server is where an AI System takes actions. Brutor can only assure the actions it sees, and it sees every one that is pointed at it.