mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
Add MCP server context to otel tool_result logs (#12267)
Summary - capture the origin for each configured MCP server and expose it via the connection manager - plumb MCP server name/origin into tool logging and emit codex.tool_result events with those fields - add unit coverage for origin parsing and extend OTEL tests to assert empty MCP fields for non-MCP tools - currently not logging full urls or url paths to prevent logging potentially sensitive data Testing - Not run (not requested)
This commit is contained in:
@@ -73,6 +73,7 @@ use tokio::task::JoinSet;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use tracing::instrument;
|
||||
use tracing::warn;
|
||||
use url::Url;
|
||||
|
||||
use crate::codex::INITIAL_SUBMIT_ID;
|
||||
use crate::config::types::McpServerConfig;
|
||||
@@ -507,6 +508,7 @@ pub struct SandboxState {
|
||||
/// A thin wrapper around a set of running [`RmcpClient`] instances.
|
||||
pub(crate) struct McpConnectionManager {
|
||||
clients: HashMap<String, AsyncManagedClient>,
|
||||
server_origins: HashMap<String, String>,
|
||||
elicitation_requests: ElicitationRequestManager,
|
||||
}
|
||||
|
||||
@@ -514,6 +516,7 @@ impl McpConnectionManager {
|
||||
pub(crate) fn new_uninitialized(approval_policy: &Constrained<AskForApproval>) -> Self {
|
||||
Self {
|
||||
clients: HashMap::new(),
|
||||
server_origins: HashMap::new(),
|
||||
elicitation_requests: ElicitationRequestManager::new(approval_policy.value()),
|
||||
}
|
||||
}
|
||||
@@ -529,6 +532,10 @@ impl McpConnectionManager {
|
||||
!self.clients.is_empty()
|
||||
}
|
||||
|
||||
pub(crate) fn server_origin(&self, server_name: &str) -> Option<&str> {
|
||||
self.server_origins.get(server_name).map(String::as_str)
|
||||
}
|
||||
|
||||
pub fn set_approval_policy(&self, approval_policy: &Constrained<AskForApproval>) {
|
||||
if let Ok(mut policy) = self.elicitation_requests.approval_policy.lock() {
|
||||
*policy = approval_policy.value();
|
||||
@@ -548,10 +555,14 @@ impl McpConnectionManager {
|
||||
) -> (Self, CancellationToken) {
|
||||
let cancel_token = CancellationToken::new();
|
||||
let mut clients = HashMap::new();
|
||||
let mut server_origins = HashMap::new();
|
||||
let mut join_set = JoinSet::new();
|
||||
let elicitation_requests = ElicitationRequestManager::new(approval_policy.value());
|
||||
let mcp_servers = mcp_servers.clone();
|
||||
for (server_name, cfg) in mcp_servers.into_iter().filter(|(_, cfg)| cfg.enabled) {
|
||||
if let Some(origin) = transport_origin(&cfg.transport) {
|
||||
server_origins.insert(server_name.clone(), origin);
|
||||
}
|
||||
let cancel_token = cancel_token.child_token();
|
||||
let _ = emit_update(
|
||||
&tx_event,
|
||||
@@ -624,6 +635,7 @@ impl McpConnectionManager {
|
||||
}
|
||||
let manager = Self {
|
||||
clients,
|
||||
server_origins,
|
||||
elicitation_requests: elicitation_requests.clone(),
|
||||
};
|
||||
tokio::spawn(async move {
|
||||
@@ -1448,6 +1460,16 @@ fn emit_duration(metric: &str, duration: Duration, tags: &[(&str, &str)]) {
|
||||
}
|
||||
}
|
||||
|
||||
fn transport_origin(transport: &McpServerTransportConfig) -> Option<String> {
|
||||
match transport {
|
||||
McpServerTransportConfig::StreamableHttp { url, .. } => {
|
||||
let parsed = Url::parse(url).ok()?;
|
||||
Some(parsed.origin().ascii_serialization())
|
||||
}
|
||||
McpServerTransportConfig::Stdio { .. } => Some("stdio".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
async fn list_tools_for_client_uncached(
|
||||
server_name: &str,
|
||||
client: &Arc<RmcpClient>,
|
||||
@@ -2164,4 +2186,32 @@ mod tests {
|
||||
display
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn transport_origin_extracts_http_origin() {
|
||||
let transport = McpServerTransportConfig::StreamableHttp {
|
||||
url: "https://example.com:8443/path?query=1".to_string(),
|
||||
bearer_token_env_var: None,
|
||||
http_headers: None,
|
||||
env_http_headers: None,
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
transport_origin(&transport),
|
||||
Some("https://example.com:8443".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn transport_origin_is_stdio_for_stdio_transport() {
|
||||
let transport = McpServerTransportConfig::Stdio {
|
||||
command: "server".to_string(),
|
||||
args: Vec::new(),
|
||||
env: None,
|
||||
env_vars: Vec::new(),
|
||||
cwd: None,
|
||||
};
|
||||
|
||||
assert_eq!(transport_origin(&transport), Some("stdio".to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,6 +102,21 @@ impl ToolRegistry {
|
||||
sandbox_policy_tag(&invocation.turn.sandbox_policy),
|
||||
),
|
||||
];
|
||||
let (mcp_server, mcp_server_origin) = match &invocation.payload {
|
||||
ToolPayload::Mcp { server, .. } => {
|
||||
let manager = invocation
|
||||
.session
|
||||
.services
|
||||
.mcp_connection_manager
|
||||
.read()
|
||||
.await;
|
||||
let origin = manager.server_origin(server).map(str::to_owned);
|
||||
(Some(server.clone()), origin)
|
||||
}
|
||||
_ => (None, None),
|
||||
};
|
||||
let mcp_server_ref = mcp_server.as_deref();
|
||||
let mcp_server_origin_ref = mcp_server_origin.as_deref();
|
||||
|
||||
let handler = match self.handler(tool_name.as_ref()) {
|
||||
Some(handler) => handler,
|
||||
@@ -116,6 +131,8 @@ impl ToolRegistry {
|
||||
false,
|
||||
&message,
|
||||
&metric_tags,
|
||||
mcp_server_ref,
|
||||
mcp_server_origin_ref,
|
||||
);
|
||||
return Err(FunctionCallError::RespondToModel(message));
|
||||
}
|
||||
@@ -131,6 +148,8 @@ impl ToolRegistry {
|
||||
false,
|
||||
&message,
|
||||
&metric_tags,
|
||||
mcp_server_ref,
|
||||
mcp_server_origin_ref,
|
||||
);
|
||||
return Err(FunctionCallError::Fatal(message));
|
||||
}
|
||||
@@ -146,6 +165,8 @@ impl ToolRegistry {
|
||||
&call_id_owned,
|
||||
log_payload.as_ref(),
|
||||
&metric_tags,
|
||||
mcp_server_ref,
|
||||
mcp_server_origin_ref,
|
||||
|| {
|
||||
let handler = handler.clone();
|
||||
let output_cell = &output_cell;
|
||||
|
||||
Reference in New Issue
Block a user