An MCP server provides the agent with tools and context, extending its actions beyond the built-in ones. It is important to separate the roles of the three mechanisms at once: an MCP server gives tools, a skill explains the workflow around them, and a plugin can pack both. They complement each other rather than replace. And the key security caveat is the same as everywhere: connecting a server does not make it trusted automatically - it is an external source treated with the usual wariness.
Connecting a server is arranged uniformly and checkably. It helps to see the commands once. Below are adding a remote server by URL, the list of servers and /mcp verbose in the TUI for diagnostics. Codex supports both remote servers over Streamable HTTP and local stdio servers launched as a process. Different transports are for different sources: a remote service over HTTP, a local tool via stdio - but the connection and verification model is common to them.
It helps to see the declarative server setup in config too once. Below is the mcp_servers section with a URL, the required flag and the startup and tool-call timeouts. The timeouts here are not a formality: startup_timeout_sec limits waiting for the server to be ready, tool_timeout_sec a single call. required sets whether the server is mandatory: if a server critical to the task is marked required, its unavailability becomes an explicit error rather than a silently skipped capability.
Local stdio servers are connected just as declaratively but with a launch command. Below is adding a server that starts via npx as a child process. The difference from remote is that a local server is executable code launched on your machine with its rights, so its source is checked like any third-party script. The convenience of "one command" does not cancel the fact that you are launching someone else's process next to your code and access.
Tool policy is what turns connecting a server into a controlled boundary. In the server setup you list enabled_tools and disabled_tools, set default_tools_approval_mode and refine the mode for individual tools. It helps to see such a fragment once. Below is a server where search and read_record are allowed, delete_record is forbidden, and the approval mode for reading is set separately. This is the principle of least privilege at the tool level: enable exactly what is needed and disable the dangerous explicitly.
A separate per-tool approval mode solves a subtle task. One server may own both harmless reading and a dangerous mutation, and they cannot be approved the same way. So default_tools_approval_mode sets the general rule - for example, ask on writes - while for a specific tool the mode is refined separately. So read_record passes automatically, while operations with consequences stay under confirmation. Granularity here is the difference between "I trust the server" and "I trust these of its tools".
MCP diagnostics remove the frequent confusion of "why the tool is missing". /mcp verbose shows the state of servers and tools: whether the server connected, whether it met the timeout, which tools are available. Instead of assuming that "MCP does not work", you see a concrete cause - an unavailable server, an expired startup timeout, a disabled tool. Checking the state with a command is cheaper than guessing, and it is the first step in any MCP problem.
The typical failures around MCP are predictable. Treating a connected server as trusted by the fact of connection. Launching a local stdio server without checking its source as third-party code. Leaving all of a server's tools enabled instead of a narrow enabled_tools and an explicit disabled on the dangerous. And approving all tools with one mode instead of granular confirmation on mutations. Check the source, enable only the needed tools, separate confirmation by danger and diagnose via /mcp verbose.
[mcp_servers.openaiDeveloperDocs]
url = "https://developers.openai.com/mcp"
required = true
startup_timeout_sec = 20
tool_timeout_sec = 60
[mcp_servers.internal]
url = "https://mcp.example.com/mcp"
enabled_tools = ["search", "read_record"]
disabled_tools = ["delete_record"]
default_tools_approval_mode = "writes"
[mcp_servers.internal.tools.read_record]
approval_mode = "auto"codex mcp add openaiDeveloperDocs --url https://developers.openai.com/mcp
codex mcp list
codex mcp add context7 -- npx -y @upstash/context7-mcp # a local stdio server
# In the TUI: /mcp verbose (state of servers and tools)