Skip to content

Use Governed Tools in a Model Request

Purpose

Let a model discover the caller's governed MCP tools and let the gateway execute an explicitly selected tool inside the model request. The Model gateway defines the supported fields and continuation contract.

Prerequisites

  1. Complete Your first governed request and retain its environment and selected model. The model must support function calls.
  2. Complete Your first MCP tool with the same key and Team. These recipes use its explicit synthetic crm__lookup-customer Data fixture and customer-42 record; substitute your discovered tool and prompt if different.
  3. Ask an administrator to declare that safe tool autoExecute: true, have the operator restart with the reviewed files, and enable the tool in the Team. Existing keys pick up current permissions; no replacement key or client restart is needed.

You need model.generate for the model and tool.invoke for the tool. The automatic set is the intersection of requested tool names, administrator autoExecute declarations, and the caller's eligible tools. Classification alone does not make a tool automatic. Action tools still go through their configured approval requirement, including during a model loop.

1. Opt in with an explicit automatic set

Run this in the mission-control-client scratch directory with the shell variables from the first request tutorial. JSON is generated safely from the discovered model ID:

python3 - <<'PYCODE' > governed-request.json
import json, os
print(json.dumps({
    "model": os.environ["MISSION_CONTROL_MODEL"],
    "messages": [{"role": "user", "content": "Use crm__lookup-customer to find the plan for customer-42."}],
}))
PYCODE
curl --fail-with-body --include \
  --header "Authorization: Bearer $MISSION_CONTROL_API_KEY" \
  --header "Content-Type: application/json" \
  --header "x-mc-tools: governed" \
  --header "x-mc-auto-execute: crm__lookup-customer" \
  --header "x-mc-max-tool-rounds: 3" \
  --data-binary @governed-request.json \
  "$MISSION_CONTROL_URL/v1/chat/completions"

For SDK recipes, prepend the corresponding OpenAI SDK setup to one block below. Save as governed.py or governed.mts, then run python governed.py or node governed.mts in that scratch directory.

raw = client.chat.completions.with_raw_response.create(
    model=model,
    messages=[{"role": "user", "content": "Use crm__lookup-customer to find the plan for customer-42."}],
    extra_headers={
        "x-mc-tools": "governed",
        "x-mc-auto-execute": "crm__lookup-customer",
        "x-mc-max-tool-rounds": "3",
    },
)
completion = raw.parse()
print("Trace ID:", raw.headers.get("x-mission-control-trace-id"))
print(completion.choices[0].message.content)
print("Executed:", (completion.model_extra or {}).get("mission_control"))
for pending in completion.choices[0].message.tool_calls or []:
    if pending.type == "function":
        print("Pending:", pending.function.name, pending.function.arguments)
const { data: completion, response } = await client.chat.completions.create({
  model,
  messages: [{ role: "user", content: "Use crm__lookup-customer to find the plan for customer-42." }],
}, { headers: {
  "x-mc-tools": "governed",
  "x-mc-auto-execute": "crm__lookup-customer",
  "x-mc-max-tool-rounds": "3",
} }).withResponse();
console.log("Trace ID:", response.headers.get("x-mission-control-trace-id"));
console.log(completion.choices[0].message.content);
console.log("Executed:", (completion as { mission_control?: unknown }).mission_control);
for (const pending of completion.choices[0].message.tool_calls ?? []) {
  if (pending.type === "function") console.log("Pending:", pending.function.name, pending.function.arguments);
}

2. Inspect executed and pending calls

When the model calls the selected fixture and the tool succeeds, Chat Completions returns mission_control.executed_tool_calls with each call's id, name, serialized arguments, content, and is_error, plus tool_rounds. The final assistant text may summarize the tool result. Usage is summed across model rounds. A model can choose not to call any tool; the prompt does not guarantee execution.

Calls outside the automatic set return as pending message.tool_calls. Inspect them, then use the MCP client to invoke governed calls with the same identity and exact arguments, handling in-band approval and connection requirements. Do not assume a pending call or an HTTP 200 response means an action completed.

3. Use Responses when needed

Responses accepts one Mission Control mcp tool object. Use this curl recipe to send the gateway-specific max_tool_rounds extension without relying on an SDK type that may not expose it:

python3 - <<'PYCODE' > governed-response.json
import json, os
print(json.dumps({
    "model": os.environ["MISSION_CONTROL_MODEL"],
    "input": "Use crm__lookup-customer to find the plan for customer-42.",
    "store": False,
    "tools": [{
        "type": "mcp", "server_label": "mission-control",
        "require_approval": {"never": {"tool_names": ["crm__lookup-customer"]}},
        "max_tool_rounds": 3,
    }],
}))
PYCODE
curl --fail-with-body --include \
  --header "Authorization: Bearer $MISSION_CONTROL_API_KEY" \
  --header "Content-Type: application/json" \
  --data-binary @governed-response.json \
  "$MISSION_CONTROL_URL/v1/responses"

Executed hops appear as mcp_call output items before the answer. require_approval: "always" (or absent) automatically runs nothing; "never" selects every eligible autoExecute tool. These request settings select the automatic set; they do not bypass Mission Control's Action approval policy. Responses remains stateless and accepts only store: false.

4. Verify and continue

Keep the returned Trace ID. With observability.read, find it under Observe → Traces. Each executed tool hop has its own decision on the model request's Trace; find them under Observe → Requests with the same observability.read.

To continue Chat Completions, reconstruct executed calls as an assistant message with tool_calls followed by one tool message per result, then include the returned assistant message and your next user message. For Responses, replay the returned mcp_call items as input. Follow the exact continuation rules; sending only final assistant text loses the tool history.

Bounded tool charges

For tools under finite spend caps, configure the actual tool's costPerCallMicros in reviewed YAML. An explicit 0 declares known-zero cost; absent pricing remains unknown and can be refused before dispatch. Every physical business call is counted once, with no model-token charge; model rounds are metered separately. Restart gateway/Console after price changes. See tool pricing.

Failure recovery

  • A requested tool remains pending: confirm discovery, autoExecute: true, and the exact requested public name. Asking for an ineligible automatic name is not itself an error.
  • unsupported_feature: governed tools cannot stream; remove stream: true or use a separate ordinary streaming request.
  • governed_tool_conflict: rename a caller-defined function that collides with a governed name.
  • Approval needed: the tool hop records approval_required and gives the model fixed explanatory text. A Team administrator must approve in the Console; retry the same tool arguments after approval. Inspect the recorded outcome before retrying any side effect.
  • Connection needed or a policy failure: inspect the tool result and decision; connect your account or correct the request using MCP client recovery.