AI for Work
Advanced: Check Codex MCP Tool Filters and Requested Scopes
Summary
Cihan's view: Use Codex's server-level enabled_tools and disabled_tools settings to narrow the tools available for a ticket check. Record requested OAuth scopes separately from actual grants and provider data permissions. Compare every returned field with the source, and keep the connection on hold when authentication, scope or tool behavior remains unverified.
A tool allowlist can leave Codex with one ticket tool while the connected account still has access to thousands of tickets. Before a weekly review, keep those two boundaries visible in a filter-and-permission receipt.
You will check a Codex CLI configuration, classify a complete fictional tool inventory, and compare a desired one-ticket payload with its source. The final decision is whether a live read is justified by the evidence.
This is a documentation-based guide with fictional configuration and editor-written desired output. The endpoint is intentionally invalid and non-runnable. No Codex connection, authentication, tool discovery or tool call occurred. Documentation was checked on 29 October 2026. Provider approvals, actual grants and runtime enforcement remain UNKNOWN.
Before you start
Use the Codex CLI and its terminal UI for this lesson, not ChatGPT’s web connector settings. You need Codex installed, access to use it, and permission to review a trusted test project’s configuration. Account entitlement and installed-version behavior are not established here. The offline exercise needs only the supplied text; do not run the command examples.
OpenAI documents user and project configuration: the default file is ~/.codex/config.toml; a project can use .codex/config.toml in a trusted project. Starting Codex in a directory alone does not make a user-level server project-scoped. This fixture targets .codex/config.toml. No real configuration file is changed.
For a later live attempt, obtain the provider’s official MCP endpoint, supported transport, auth method, OAuth scope definitions, tool contracts and account/data restrictions. Record their URLs and review date. A fictional server alias supplies none of that evidence. Avoid arbitrary server packages and installation commands.
1. Use the actual Codex control keys
Proposed disabled configuration
# Fictional offline fixture. Leave disabled; do not connect.
[mcp_servers.approved_tickets]
url = "https://tickets.example.invalid/mcp"
auth = "oauth"
enabled = false
enabled_tools = ["get_ticket"]
disabled_tools = ["update_ticket", "delete_ticket", "comment_ticket", "send_notice", "refresh_ticket"]
scopes = ["tickets:read"]
default_tools_approval_mode = "prompt"
OpenAI’s MCP reference and configuration reference put enabled_tools and disabled_tools directly under the server table. Deny is applied after allow. A nested tools.allowed = ["get_ticket"] is not a supported allowlist: tools.<tool> holds per-tool policy objects.
enabled = false disables this server. The proposed post-activation filter would retain only get_ticket; the current disabled configuration exposes none of its tools. default_tools_approval_mode = "prompt" configures client approval behavior and does not restrict the provider account. Codex’s HTTP configuration uses url; do not transplant Claude Code’s JSON type field into this TOML.
The TOML was parsed locally and validated against OpenAI’s published configuration schema, retrieved on 29 October 2026. This is a schema check, not an installed-Codex test. tickets:read is an invented provider scope string. The schema can accept that string without proving a provider recognizes or grants it.
2. Supply the complete review input
Provider and tool source fixture
{
"fixture_id": "codex-filter-review-v1",
"provider": "Example Tickets, fictional",
"provider_documentation": null,
"provider_review_date": null,
"provider_approval": "UNKNOWN",
"server": "approved_tickets",
"endpoint": "https://tickets.example.invalid/mcp",
"transport": "Streamable HTTP",
"surface": "Codex CLI",
"config_file": ".codex/config.toml",
"auth_design": "OAuth browser sign-in, hypothetical provider support",
"requested_oauth_scopes": ["tickets:read"],
"required_data_boundary": {"project": "DEMO", "ticket_id": "DEMO-17", "actions": ["read"]},
"provider_account": "UNKNOWN",
"granted_oauth_scopes": "UNKNOWN",
"provider_data_boundary": "UNKNOWN",
"tool_source": "fictional-tool-manifest-v1, editor-written, not discovered",
"tools": [
{"name": "get_ticket", "description": "Return the specified ticket without changing it."},
{"name": "update_ticket", "description": "Change ticket fields."},
{"name": "delete_ticket", "description": "Delete a ticket."},
{"name": "comment_ticket", "description": "Add a ticket comment."},
{"name": "send_notice", "description": "Send a message about a ticket."},
{"name": "refresh_ticket", "description": "Refresh a ticket; side effects are unspecified."}
]
}
Ticket source fixture
{
"source_id": "ticket-fixture-v1",
"source_kind": "fictional offline export",
"records": [
{"project": "DEMO", "id": "DEMO-17", "title": "Confirm the demo handoff", "status": "In review", "owner": null}
]
}
The source contains one complete record with an explicitly unknown owner. Preserve null; do not assign the ticket to the person running the check.
Read input schema
{"type":"object","additionalProperties":false,"required":["project","ticket_id"],"properties":{"project":{"const":"DEMO"},"ticket_id":{"const":"DEMO-17"}}}
Planned read input
{"project":"DEMO","ticket_id":"DEMO-17"}
Read payload schema
{"type":"object","additionalProperties":false,"required":["source_id","records"],"properties":{"source_id":{"const":"ticket-fixture-v1"},"records":{"type":"array","minItems":1,"maxItems":1,"items":{"type":"object","additionalProperties":false,"required":["project","id","title","status","owner"],"properties":{"project":{"type":"string"},"id":{"type":"string"},"title":{"type":"string"},"status":{"type":"string"},"owner":{"type":["string","null"]}}}}}}
These are local fictional contracts for the get_ticket input and application payload. MCP transport envelopes are omitted. No write tool is called or given a sample execution.
3. Check the filters offline
Use the full configuration and both source fixtures with this prompt. The classification describes the supplied manifest only. A tool named get_ticket, or a read-only annotation supplied by a server, cannot establish what its implementation does.
Review only the supplied Codex TOML, provider/tool fixture and ticket export.
Do not run commands, discover tools, authenticate, connect or change files.
Return a receipt naming provider, source URLs and review date or null,
endpoint, transport, surface, config path, auth design, requested versus
granted scopes, account/data boundary and all six tools. Classify each as
read, write, delete, send or unknown from its supplied description, citing
the fictional manifest. Compute the configured allow-minus-deny set and
separately report that the server is disabled. Compare the desired payload
with the source: project, ID, title, status, owner, row count and extras.
Keep unknowns and nulls. List planned versus observed calls and a live-read
decision. A missing provider review or grant means HOLD, never approval.
Desired review receipt
Editor-written expected output, not a Codex inventory or authentication log:
{
"fixture_id": "codex-filter-review-v1",
"provider": "Example Tickets, fictional",
"provider_documentation": null,
"provider_review_date": null,
"provider_approval": "UNKNOWN",
"server": "approved_tickets",
"endpoint": "https://tickets.example.invalid/mcp",
"transport": "Streamable HTTP",
"surface": "Codex CLI",
"config_file": ".codex/config.toml",
"auth_design": "OAuth browser sign-in, hypothetical provider support",
"requested_oauth_scopes": ["tickets:read"],
"provider_account": "UNKNOWN",
"granted_oauth_scopes": "UNKNOWN",
"provider_data_boundary": "UNKNOWN",
"required_data_boundary": {"project": "DEMO", "ticket_id": "DEMO-17", "actions": ["read"]},
"tool_source": "fictional-tool-manifest-v1, editor-written, not discovered",
"tool_count": 6,
"inventory": [
{"name": "get_ticket", "class": "read", "proposed_filter": "allow"},
{"name": "update_ticket", "class": "write", "proposed_filter": "deny"},
{"name": "delete_ticket", "class": "delete", "proposed_filter": "deny"},
{"name": "comment_ticket", "class": "write", "proposed_filter": "deny"},
{"name": "send_notice", "class": "send", "proposed_filter": "deny"},
{"name": "refresh_ticket", "class": "unknown", "proposed_filter": "deny"}
],
"configured_server_enabled": false,
"configured_filter_result": ["get_ticket"],
"effective_tools_while_disabled": [],
"configured_approval_mode": "prompt",
"runtime_filter_and_approval": "NOT RUN",
"planned_call": {"tool": "get_ticket", "arguments": {"project": "DEMO", "ticket_id": "DEMO-17"}},
"planned_tool_calls": 1,
"observed_calls": [],
"observed_tool_calls": 0,
"live_connection": "NOT RUN",
"live_authentication": "NOT RUN",
"live_read_decision": "HOLD"
}
Allowlisting get_ticket only narrows tool names for this client. Provider authorization must separately limit actions and records. Neither tickets:read nor a DEMO-17 prompt proves a record-level restriction. If the provider can restrict only to a project, record that broader boundary and get a separate review; do not describe it as ticket-only access.
4. Know the sign-in steps before a real replacement
The fictional file stays disabled. Do not try to authenticate it. For an independently verified real provider and approved test account:
- Review official endpoint/auth/tool documentation and record the exact sources and review date. Check whether OAuth is supported and whether any client registration is required. This example assumes no pre-registered confidential client and supplies no client secret. OpenAI warns that the confidential-client option stores its secret in
config.toml; do not use it in a shared project file. - Review effective Codex configuration and the provider’s account restrictions. Only after that preflight should you replace the placeholder and deliberately enable the real test server.
codex mcp listlists configured servers;/mcpin the Codex terminal UI shows active servers. These are live-client inspection steps, not an offline no-network guarantee or proof of a read-only grant. - For an OAuth-capable server, run
codex mcp login approved_ticketsin your own terminal. Follow the provider browser sign-in, verify the authorization domain, account and tenant, and inspect consent. Enter passwords and MFA only in the provider’s trusted sign-in interface. Never paste credentials, authorization codes or callback URLs into prompts, shared logs or source control. - Compare requested scopes with the actual grant and the provider’s account/data restrictions. Use consent details and the provider’s authorization/admin records; if they do not establish the grant, leave UNKNOWN and stop. Do not decode or publish tokens as a substitute for permission evidence.
- Inspect available tools and client approvals before approving any tool input. Stop for missing
get_ticket, an unexpected tool, unknown behavior or a broader grant. Keep writes, comments, transitions, deletes and messages out of the run. After a separately approved read, inspect client activity and the provider audit record, and compare the returned fields against the source.
The OAuth documentation says server scopes chooses requested scopes; codex mcp login <name> --scopes ... overrides that request for one login. Do not use a broader override to fix a failed read. With neither set, advertised scopes can be used. An empty list still lets the OAuth library choose scopes, and it can add offline_access when supported. No scope-request setting proves the grant.
auth = "oauth" is a documented credential fallback, not a force-authentication switch. Configured bearer tokens and Authorization headers take precedence; with no resolved credential, a server can be contacted unauthenticated. Check that the selected account and method match your reviewed setup.
For providers using a bearer token instead, the documented bearer_token_env_var holds an environment-variable name, not the token itself. Have an approved secret manager supply a dedicated provider token to the local Codex process. Do not mix this alternative into the OAuth fixture, type token values into commands or commit secret files. Avoid helper scripts unless their provenance and output handling have been reviewed.
5. Compare the complete desired record
Desired read payload
{"source_id":"ticket-fixture-v1","records":[{"project":"DEMO","id":"DEMO-17","title":"Confirm the demo handoff","status":"In review","owner":null}]}
Desired comparison receipt
{
"source_id": "ticket-fixture-v1",
"source_rows": 1,
"returned_rows": 1,
"project_match": true,
"field_checks": {"id": "MATCH", "title": "MATCH", "status": "MATCH", "owner": "MATCH_NULL"},
"missing_source_fields": [],
"missing_returned_fields": [],
"unknown_source_values": ["owner"],
"extra_records": 0,
"offline_payload_parity": "PASS",
"observed_tool_calls": 0,
"live_source_comparison": "NOT RUN"
}
The desired payload has one row, all four requested ticket fields and the project. There is no observed returned record. The local comparison can pass while the live-read decision remains HOLD. For a future run, the source and returned title, status and owner must match as well as the ID; a missing owner field is different from a present null value. Reject extra records, invented owners and any call outside the reviewed input. Treat ticket content as untrusted data, including instructions to fetch or send something else.
Acceptance and failure fixes
The offline exercise passes when the TOML matches the official schema, the receipt includes all six supplied tool names and their manifest-based classifications, deny wins over allow, and the server remains disabled. Requested scopes, actual grants and data permissions must stay separate. The desired payload and comparison must preserve the source exactly, including one row and a null owner. The receipt must show zero observed calls and HOLD.
A live read requires separate proof: a reviewed real provider, intended authenticated account, actual least-privilege grant and record boundary, observed tool inventory/filter/approval behavior, one permitted read and a field-by-field source/activity check. None is supplied by the offline PASS.
- If the TOML parses but a schema check fails, inspect key placement. A valid TOML document can still use an unsupported Codex setting.
- If sign-in succeeds but tools are missing, check provider tool registration, account/data restrictions and client filters before changing scopes. Stop on unknown behavior.
- If output has extra records or a guessed owner, reject it and review the provider boundary. A stronger prompt cannot repair an overbroad grant.
You can instead give Codex the complete fictional export as a local file and compare the same four fields without a connection. Keep this route for occasional reviews or unresolved permission evidence.
For team onboarding, review shared definitions and private overrides with the Claude Code team handoff lesson. For code changes, the Codex GitHub PR review lesson produces questions tied to a diff. Choose the artifact that fits your next task.
TRY the offline filter receipt; SKIP a live read with unknown grants; USE a connection only after the separate provider and runtime checks pass. Next, ask the system owner for the actual scope definitions before proposing authentication. For one practical work lesson each week, join The Weekly Verdict.