Enable MCP tool hooks in Codex sessions (#39296)

## What changed

- Execute `mcp_tool` hook handlers through the session's shared MCP runtime, including managed hook configurations.
- Restrict hook calls to already-connected, cataloged, and policy-allowed tools. Unavailable servers fail immediately without starting or reconnecting them.
- Pass session metadata to hook tools, cap hook timeouts by the server timeout, and invoke hooks without model-tool approval or recursive hook dispatch.
- Record handler type and execution mode in hook telemetry, and mark memory as externally influenced after successful or blocking MCP hooks when configured.

## Testing

- Added end-to-end coverage for prompt interpolation, hidden tools, blocking decisions, unavailable servers, thread metadata, approval bypass, and memory-mode handling.
- Added MCP runtime coverage for startup readiness, tool filters, catalog revisions, and managed connector policy.

GitOrigin-RevId: 1647cb801ebc5fa385c7bda9d9c266a389c611b8
This commit is contained in:
Abhinav authored and copyberry committed 2026-08-18 22:10:43 +00:00
1 parent 701db965e5
commit 87070a7792
25 files changed
+929 -140

No files matched your search

+13 -5
View File
@@ -5,6 +5,7 @@ use crate::agents_md_manager::AgentsMdManager;
use crate::config::ConstraintError;
use crate::environment_selection::ThreadEnvironments;
use crate::environment_selection::TurnEnvironmentSnapshot;
use crate::hook_mcp_executor::CoreHookMcpExecutor;
use crate::shell_snapshot::ShellSnapshot;
use crate::state::ActiveTurn;
use codex_extension_api::ExtensionDataInit;
@@ -1196,13 +1197,24 @@ impl Session {
(None, None)
};
// Hooks and extensions share one stable thread-owned MCP runtime handle.
let mcp_runtime = Arc::new(McpRuntime::empty(
mcp_projection.config.prefix_mcp_tool_names,
));
let hooks_config = build_hooks_config(
&config,
plugins_manager.as_ref(),
resolved_environments.single_local_environment(),
)
.await;
let (hooks, async_hook_results) = Hooks::new(hooks_config, thread_id)?;
let (hooks, async_hook_results) = Hooks::new(
hooks_config,
thread_id,
Arc::new(CoreHookMcpExecutor {
runtime: Arc::clone(&mcp_runtime),
thread_id,
}),
)?;
for warning in hooks.startup_warnings() {
post_session_configured_events.push(Event {
id: INITIAL_SUBMIT_ID.to_owned(),
@@ -1219,10 +1231,6 @@ impl Session {
config.analytics_enabled,
)
});
// Extensions need a stable thread-owned resource client before the Session exists.
let mcp_runtime = Arc::new(McpRuntime::empty(
mcp_projection.config.prefix_mcp_tool_names,
));
for item in initial_history.get_rollout_items() {
match item {
RolloutItem::Compacted(compacted) => {