mirror of
https://github.com/openai/codex.git
synced 2026-10-20 00:18:49 +00:00
Attribute OpenAI docs MCP requests to Codex (#33424)
## What changed - Add `source=codex` to requests sent to the OpenAI developer docs MCP endpoint. - Preserve OAuth discovery URLs and leave clients for other MCP servers unchanged. ## Testing - Add unit coverage for regular and streaming HTTP requests, OAuth discovery, and non-docs MCP clients. GitOrigin-RevId: d04e19fe6fc2db0b5cd7bfb429f0cb821c49b28a
This commit is contained in:
committed by
copyberry
parent
9ab3dd8b9f
commit
2b486b4676
@@ -86,6 +86,7 @@ pub(crate) mod codex_apps;
|
||||
pub(crate) mod connection_manager;
|
||||
pub(crate) mod elicitation;
|
||||
pub(crate) mod mcp;
|
||||
mod openai_docs_source_attribution;
|
||||
mod plugin_config;
|
||||
mod resource_client;
|
||||
pub(crate) mod rmcp_client;
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use codex_exec_server::ExecServerError;
|
||||
use codex_exec_server::HttpClient;
|
||||
use codex_exec_server::HttpRequestParams;
|
||||
use codex_exec_server::HttpRequestResponse;
|
||||
use codex_exec_server::HttpResponseBodyStream;
|
||||
use futures::future::BoxFuture;
|
||||
|
||||
const OPENAI_DEVELOPER_DOCS_MCP_URL: &str = "https://developers.openai.com/mcp";
|
||||
const OPENAI_DEVELOPER_DOCS_MCP_CODEX_URL: &str = "https://developers.openai.com/mcp?source=codex";
|
||||
|
||||
pub(crate) fn maybe_with_openai_docs_source_attribution(
|
||||
mcp_server_url: &str,
|
||||
http_client: Arc<dyn HttpClient>,
|
||||
) -> Arc<dyn HttpClient> {
|
||||
if mcp_server_url == OPENAI_DEVELOPER_DOCS_MCP_URL {
|
||||
Arc::new(OpenAiDocsHttpClient { http_client })
|
||||
} else {
|
||||
http_client
|
||||
}
|
||||
}
|
||||
|
||||
struct OpenAiDocsHttpClient {
|
||||
http_client: Arc<dyn HttpClient>,
|
||||
}
|
||||
|
||||
impl OpenAiDocsHttpClient {
|
||||
fn attribute_mcp_request(&self, params: &mut HttpRequestParams) {
|
||||
if params.url == OPENAI_DEVELOPER_DOCS_MCP_URL {
|
||||
params.url = OPENAI_DEVELOPER_DOCS_MCP_CODEX_URL.to_string();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl HttpClient for OpenAiDocsHttpClient {
|
||||
fn http_request(
|
||||
&self,
|
||||
mut params: HttpRequestParams,
|
||||
) -> BoxFuture<'_, Result<HttpRequestResponse, ExecServerError>> {
|
||||
self.attribute_mcp_request(&mut params);
|
||||
self.http_client.http_request(params)
|
||||
}
|
||||
|
||||
fn http_request_stream(
|
||||
&self,
|
||||
mut params: HttpRequestParams,
|
||||
) -> BoxFuture<'_, Result<(HttpRequestResponse, HttpResponseBodyStream), ExecServerError>> {
|
||||
self.attribute_mcp_request(&mut params);
|
||||
self.http_client.http_request_stream(params)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "openai_docs_source_attribution_tests.rs"]
|
||||
mod tests;
|
||||
@@ -0,0 +1,92 @@
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
|
||||
use codex_exec_server::ExecServerError;
|
||||
use codex_exec_server::HttpClient;
|
||||
use codex_exec_server::HttpRedirectPolicy;
|
||||
use codex_exec_server::HttpRequestParams;
|
||||
use codex_exec_server::HttpRequestResponse;
|
||||
use codex_exec_server::HttpResponseBodyStream;
|
||||
use futures::FutureExt;
|
||||
use futures::future::BoxFuture;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
use super::OPENAI_DEVELOPER_DOCS_MCP_CODEX_URL;
|
||||
use super::OPENAI_DEVELOPER_DOCS_MCP_URL;
|
||||
use super::maybe_with_openai_docs_source_attribution;
|
||||
|
||||
#[derive(Default)]
|
||||
struct RecordingHttpClient {
|
||||
urls: Mutex<Vec<String>>,
|
||||
}
|
||||
|
||||
impl HttpClient for RecordingHttpClient {
|
||||
fn http_request(
|
||||
&self,
|
||||
params: HttpRequestParams,
|
||||
) -> BoxFuture<'_, Result<HttpRequestResponse, ExecServerError>> {
|
||||
self.urls.lock().unwrap().push(params.url);
|
||||
async { Err(ExecServerError::HttpRequest("test response".to_string())) }.boxed()
|
||||
}
|
||||
|
||||
fn http_request_stream(
|
||||
&self,
|
||||
params: HttpRequestParams,
|
||||
) -> BoxFuture<'_, Result<(HttpRequestResponse, HttpResponseBodyStream), ExecServerError>> {
|
||||
self.urls.lock().unwrap().push(params.url);
|
||||
async { Err(ExecServerError::HttpRequest("test response".to_string())) }.boxed()
|
||||
}
|
||||
}
|
||||
|
||||
fn request(url: &str) -> HttpRequestParams {
|
||||
HttpRequestParams {
|
||||
method: "POST".to_string(),
|
||||
url: url.to_string(),
|
||||
headers: Vec::new(),
|
||||
body: None,
|
||||
timeout_ms: None,
|
||||
redirect_policy: HttpRedirectPolicy::Follow,
|
||||
request_id: "test-request".to_string(),
|
||||
stream_response: true,
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn attributes_only_docs_mcp_requests() {
|
||||
let recording_client = Arc::new(RecordingHttpClient::default());
|
||||
let http_client = maybe_with_openai_docs_source_attribution(
|
||||
OPENAI_DEVELOPER_DOCS_MCP_URL,
|
||||
recording_client.clone(),
|
||||
);
|
||||
|
||||
let _ = http_client
|
||||
.http_request_stream(request(OPENAI_DEVELOPER_DOCS_MCP_URL))
|
||||
.await;
|
||||
let _ = http_client
|
||||
.http_request(request(
|
||||
"https://developers.openai.com/.well-known/oauth-protected-resource/mcp",
|
||||
))
|
||||
.await;
|
||||
|
||||
assert_eq!(
|
||||
recording_client.urls.lock().unwrap().as_slice(),
|
||||
[
|
||||
OPENAI_DEVELOPER_DOCS_MCP_CODEX_URL,
|
||||
"https://developers.openai.com/.well-known/oauth-protected-resource/mcp",
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn leaves_other_mcp_clients_unwrapped() {
|
||||
let recording_client = Arc::new(RecordingHttpClient::default());
|
||||
let http_client = maybe_with_openai_docs_source_attribution(
|
||||
"https://example.com/mcp",
|
||||
recording_client.clone(),
|
||||
);
|
||||
|
||||
assert!(Arc::ptr_eq(
|
||||
&http_client,
|
||||
&(recording_client as Arc<dyn HttpClient>)
|
||||
));
|
||||
}
|
||||
@@ -25,6 +25,7 @@ use crate::codex_apps::prepare_openai_file_params_for_model;
|
||||
use crate::elicitation::ElicitationRequestManager;
|
||||
use crate::mcp::CODEX_APPS_MCP_SERVER_NAME;
|
||||
use crate::mcp::ToolPluginProvenance;
|
||||
use crate::openai_docs_source_attribution::maybe_with_openai_docs_source_attribution;
|
||||
use crate::runtime::McpRuntimeContext;
|
||||
use crate::runtime::emit_duration;
|
||||
use crate::server::EffectiveMcpServer;
|
||||
@@ -1077,6 +1078,7 @@ async fn make_rmcp_client(
|
||||
|| Arc::new(ReqwestHttpClient) as Arc<dyn HttpClient>,
|
||||
|environment| environment.get_http_client(),
|
||||
);
|
||||
let http_client = maybe_with_openai_docs_source_attribution(&url, http_client);
|
||||
let resolved_bearer_token =
|
||||
match resolve_bearer_token(server_name, bearer_token_env_var.as_deref()) {
|
||||
Ok(token) => token,
|
||||
|
||||
Reference in New Issue
Block a user