mirror of
https://github.com/openai/codex.git
synced 2026-10-18 17:18:52 +00:00
fix: forward originator to Codex Apps MCP (#31481)
## Summary - Forward Codexs canonical `originator` header on ChatGPT-hosted Apps and plugin-runtime MCP requests. - Preserve the configured `X-OpenAI-Product-Sku` header. - Cover originator-only and originator-plus-SKU configurations. ## Why Sites project creation is logged downstream of Apps MCP. Production validation found `CODEX_UNKNOWN_DEFAULT` project-created threads that matched `codex_surface=desktop_app` and `originator=Codex Desktop` in `fact_codex_cli` ([query](https://kepler.gateway.data-1.internal.api.openai.org/permalink/H_mVoVPqLQ0)). The hosted Apps MCP configuration forwarded the product SKU but not Codexs canonical originator, so codex-backend could not derive `CODEX_DESKTOP_APP` for those tool calls. ## Validation - `just fmt` - `CARGO_HOME=/private/tmp/codex-cargo-home CARGO_TARGET_DIR=/private/tmp/codex-target just test -p codex-mcp` (106 passed) - `CARGO_HOME=/private/tmp/codex-cargo-home CARGO_TARGET_DIR=/private/tmp/codex-target just fix -p codex-mcp`
This commit is contained in:
@@ -1418,6 +1418,7 @@ fn server_metadata_preserves_tool_approval_policy() {
|
||||
let mut config = crate::codex_apps_mcp_server_config(
|
||||
"https://docs.example",
|
||||
/*apps_mcp_product_sku*/ None,
|
||||
/*originator*/ None,
|
||||
);
|
||||
config.environment_id = "remote".to_string();
|
||||
config.default_tools_approval_mode = Some(AppToolApproval::Prompt);
|
||||
@@ -1462,6 +1463,7 @@ fn host_owned_codex_apps_matches_reserved_name_with_server_metadata() {
|
||||
let server = EffectiveMcpServer::configured(crate::codex_apps_mcp_server_config(
|
||||
"https://chatgpt.com",
|
||||
/*apps_mcp_product_sku*/ None,
|
||||
/*originator*/ None,
|
||||
));
|
||||
manager.server_metadata.insert(
|
||||
CODEX_APPS_MCP_SERVER_NAME.to_string(),
|
||||
|
||||
@@ -493,10 +493,12 @@ fn codex_apps_mcp_url_for_base_url(base_url: &str) -> String {
|
||||
pub fn codex_apps_mcp_server_config(
|
||||
chatgpt_base_url: &str,
|
||||
apps_mcp_product_sku: Option<&str>,
|
||||
originator: Option<&str>,
|
||||
) -> McpServerConfig {
|
||||
mcp_server_config_for_url(
|
||||
codex_apps_mcp_url_for_base_url(chatgpt_base_url),
|
||||
apps_mcp_product_sku,
|
||||
originator,
|
||||
McpServerAuth::ChatGpt,
|
||||
)
|
||||
}
|
||||
@@ -505,6 +507,7 @@ pub fn codex_apps_mcp_server_config(
|
||||
pub fn hosted_plugin_runtime_mcp_server_config(
|
||||
chatgpt_base_url: &str,
|
||||
apps_mcp_product_sku: Option<&str>,
|
||||
originator: Option<&str>,
|
||||
) -> McpServerConfig {
|
||||
let base_url = normalize_codex_apps_base_url(chatgpt_base_url);
|
||||
let base_url = if base_url.contains("/backend-api") || base_url.contains("/api/codex") {
|
||||
@@ -515,6 +518,7 @@ pub fn hosted_plugin_runtime_mcp_server_config(
|
||||
mcp_server_config_for_url(
|
||||
format!("{base_url}/ps/mcp"),
|
||||
apps_mcp_product_sku,
|
||||
originator,
|
||||
McpServerAuth::ChatGpt,
|
||||
)
|
||||
}
|
||||
@@ -522,19 +526,21 @@ pub fn hosted_plugin_runtime_mcp_server_config(
|
||||
fn mcp_server_config_for_url(
|
||||
url: String,
|
||||
apps_mcp_product_sku: Option<&str>,
|
||||
originator: Option<&str>,
|
||||
auth_mode: McpServerAuth,
|
||||
) -> McpServerConfig {
|
||||
let product_sku = apps_mcp_product_sku.unwrap_or(DEFAULT_CODEX_APPS_MCP_PRODUCT_SKU);
|
||||
let http_headers = Some(HashMap::from([(
|
||||
"X-OpenAI-Product-Sku".to_string(),
|
||||
product_sku.to_string(),
|
||||
)]));
|
||||
let mut http_headers =
|
||||
HashMap::from([("X-OpenAI-Product-Sku".to_string(), product_sku.to_string())]);
|
||||
if let Some(originator) = originator {
|
||||
http_headers.insert("originator".to_string(), originator.to_string());
|
||||
}
|
||||
|
||||
McpServerConfig {
|
||||
transport: McpServerTransportConfig::StreamableHttp {
|
||||
url,
|
||||
bearer_token_env_var: codex_apps_mcp_bearer_token_env_var(),
|
||||
http_headers,
|
||||
http_headers: Some(http_headers),
|
||||
env_http_headers: None,
|
||||
},
|
||||
auth: auth_mode,
|
||||
|
||||
@@ -130,7 +130,11 @@ fn tool_plugin_provenance_collects_app_and_mcp_sources() {
|
||||
"alpha".to_string(),
|
||||
McpPluginAttribution::new("alpha@test".to_string(), "alpha-plugin".to_string()),
|
||||
/*plugin_order*/ 0,
|
||||
codex_apps_mcp_server_config("https://alpha.example", /*apps_mcp_product_sku*/ None),
|
||||
codex_apps_mcp_server_config(
|
||||
"https://alpha.example",
|
||||
/*apps_mcp_product_sku*/ None,
|
||||
/*originator*/ None,
|
||||
),
|
||||
));
|
||||
config.mcp_server_catalog = catalog.build();
|
||||
config.connector_snapshot =
|
||||
@@ -197,7 +201,11 @@ fn selected_mcp_attribution_does_not_join_an_unrelated_local_summary() {
|
||||
"Executor GitHub".to_string(),
|
||||
),
|
||||
/*selection_order*/ 0,
|
||||
codex_apps_mcp_server_config("https://github.example", /*apps_mcp_product_sku*/ None),
|
||||
codex_apps_mcp_server_config(
|
||||
"https://github.example",
|
||||
/*apps_mcp_product_sku*/ None,
|
||||
/*originator*/ None,
|
||||
),
|
||||
));
|
||||
config.mcp_server_catalog = catalog.build();
|
||||
config.connector_snapshot =
|
||||
@@ -252,8 +260,11 @@ fn codex_apps_mcp_url_for_base_url_keeps_existing_paths() {
|
||||
|
||||
#[test]
|
||||
fn codex_apps_server_config_uses_legacy_codex_apps_path() {
|
||||
let config =
|
||||
codex_apps_mcp_server_config("https://chatgpt.com", /*apps_mcp_product_sku*/ None);
|
||||
let config = codex_apps_mcp_server_config(
|
||||
"https://chatgpt.com",
|
||||
/*apps_mcp_product_sku*/ None,
|
||||
/*originator*/ None,
|
||||
);
|
||||
let url = match &config.transport {
|
||||
McpServerTransportConfig::StreamableHttp { url, .. } => url,
|
||||
_ => panic!("expected streamable http transport for codex apps"),
|
||||
@@ -262,10 +273,41 @@ fn codex_apps_server_config_uses_legacy_codex_apps_path() {
|
||||
assert_eq!(url, "https://chatgpt.com/backend-api/wham/apps");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn codex_apps_server_config_forwards_thread_originator_header() {
|
||||
let config = codex_apps_mcp_server_config(
|
||||
"https://chatgpt.com",
|
||||
/*apps_mcp_product_sku*/ None,
|
||||
Some("thread_originator"),
|
||||
);
|
||||
|
||||
match &config.transport {
|
||||
McpServerTransportConfig::StreamableHttp {
|
||||
http_headers,
|
||||
env_http_headers,
|
||||
..
|
||||
} => {
|
||||
assert_eq!(
|
||||
http_headers,
|
||||
&Some(HashMap::from([
|
||||
("originator".to_string(), "thread_originator".to_string()),
|
||||
("X-OpenAI-Product-Sku".to_string(), "codex".to_string()),
|
||||
]))
|
||||
);
|
||||
assert!(env_http_headers.is_none());
|
||||
}
|
||||
other => panic!("expected streamable http transport, got {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn codex_apps_server_config_sets_product_sku_header() {
|
||||
for (configured_product_sku, expected_product_sku) in [(None, "codex"), (Some("tpp"), "tpp")] {
|
||||
let config = codex_apps_mcp_server_config("https://chatgpt.com", configured_product_sku);
|
||||
let config = codex_apps_mcp_server_config(
|
||||
"https://chatgpt.com",
|
||||
configured_product_sku,
|
||||
/*originator*/ None,
|
||||
);
|
||||
|
||||
match &config.transport {
|
||||
McpServerTransportConfig::StreamableHttp {
|
||||
@@ -287,6 +329,33 @@ fn codex_apps_server_config_sets_product_sku_header() {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn codex_apps_server_config_forwards_originator_and_configured_product_sku_headers() {
|
||||
let config = codex_apps_mcp_server_config(
|
||||
"https://chatgpt.com",
|
||||
Some("tpp"),
|
||||
Some("thread_originator"),
|
||||
);
|
||||
|
||||
match &config.transport {
|
||||
McpServerTransportConfig::StreamableHttp {
|
||||
http_headers,
|
||||
env_http_headers,
|
||||
..
|
||||
} => {
|
||||
assert_eq!(
|
||||
http_headers,
|
||||
&Some(HashMap::from([
|
||||
("originator".to_string(), "thread_originator".to_string()),
|
||||
("X-OpenAI-Product-Sku".to_string(), "tpp".to_string()),
|
||||
]))
|
||||
);
|
||||
assert!(env_http_headers.is_none());
|
||||
}
|
||||
other => panic!("expected streamable http transport, got {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn effective_mcp_servers_preserve_runtime_servers() {
|
||||
let codex_home = tempfile::tempdir().expect("tempdir");
|
||||
@@ -352,6 +421,7 @@ async fn effective_mcp_servers_preserve_runtime_servers() {
|
||||
codex_apps_mcp_server_config(
|
||||
&config.chatgpt_base_url,
|
||||
config.apps_mcp_product_sku.as_deref(),
|
||||
/*originator*/ None,
|
||||
),
|
||||
));
|
||||
config.mcp_server_catalog = catalog.build();
|
||||
|
||||
@@ -79,9 +79,15 @@ impl McpManager {
|
||||
/// Returns the MCP config after applying compatibility built-ins and
|
||||
/// runtime-only extension overlays.
|
||||
pub async fn runtime_config(&self, config: &Config) -> McpConfig {
|
||||
self.runtime_config_with_context(McpServerContributionContext::global(config))
|
||||
.await
|
||||
.config
|
||||
self.runtime_config_with_context(
|
||||
McpServerContributionContext::global(config),
|
||||
// Threadless discovery and control-plane paths have no effective thread
|
||||
// originator; active-thread tool calls use runtime_config_for_step below.
|
||||
/*originator*/
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.config
|
||||
}
|
||||
|
||||
pub(crate) async fn runtime_config_for_step(
|
||||
@@ -89,20 +95,26 @@ impl McpManager {
|
||||
config: &Config,
|
||||
thread_init: &ExtensionDataInit,
|
||||
thread_store: &ExtensionData,
|
||||
originator: &str,
|
||||
available_environment_ids: &[String],
|
||||
) -> McpRuntimeProjection {
|
||||
self.runtime_config_with_context(McpServerContributionContext::for_step(
|
||||
config,
|
||||
thread_init,
|
||||
thread_store,
|
||||
available_environment_ids,
|
||||
))
|
||||
self.runtime_config_with_context(
|
||||
McpServerContributionContext::for_step(
|
||||
config,
|
||||
thread_init,
|
||||
thread_store,
|
||||
originator,
|
||||
available_environment_ids,
|
||||
),
|
||||
Some(originator),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn runtime_config_with_context(
|
||||
&self,
|
||||
context: McpServerContributionContext<'_, Config>,
|
||||
originator: Option<&str>,
|
||||
) -> McpRuntimeProjection {
|
||||
let config = context.config();
|
||||
let mut selected_plugin_available = false;
|
||||
@@ -181,6 +193,7 @@ impl McpManager {
|
||||
codex_apps_mcp_server_config(
|
||||
&mcp_config.chatgpt_base_url,
|
||||
mcp_config.apps_mcp_product_sku.as_deref(),
|
||||
originator,
|
||||
),
|
||||
));
|
||||
} else {
|
||||
|
||||
@@ -1435,6 +1435,7 @@ async fn host_owned_codex_apps_manager(
|
||||
codex_mcp::EffectiveMcpServer::configured(codex_mcp::codex_apps_mcp_server_config(
|
||||
"https://chatgpt.com",
|
||||
/*apps_mcp_product_sku*/ None,
|
||||
Some(&turn_context.originator),
|
||||
)),
|
||||
)]);
|
||||
let manager = codex_mcp::McpConnectionManager::new(
|
||||
|
||||
@@ -78,6 +78,7 @@ impl ElicitationReviewer for GuardianMcpElicitationReviewer {
|
||||
|
||||
impl Session {
|
||||
pub(crate) async fn runtime_mcp_config(&self, config: &Config) -> McpConfig {
|
||||
let originator = self.originator().await;
|
||||
let environments = self.services.turn_environments.snapshot().await;
|
||||
let selected_capability_roots = self
|
||||
.resolve_selected_capability_roots_for_step(&environments)
|
||||
@@ -90,6 +91,7 @@ impl Session {
|
||||
config,
|
||||
&self.services.mcp_thread_init,
|
||||
&self.services.thread_extension_data,
|
||||
&originator,
|
||||
&available_environment_ids,
|
||||
)
|
||||
.await
|
||||
@@ -132,6 +134,7 @@ impl Session {
|
||||
&turn_context.config,
|
||||
&self.services.mcp_thread_init,
|
||||
&self.services.thread_extension_data,
|
||||
&turn_context.originator,
|
||||
&available_environment_ids,
|
||||
)
|
||||
.await;
|
||||
@@ -475,6 +478,7 @@ impl Session {
|
||||
&refresh_config,
|
||||
&self.services.mcp_thread_init,
|
||||
&self.services.thread_extension_data,
|
||||
&turn_context.originator,
|
||||
&available_environment_ids,
|
||||
)
|
||||
.await;
|
||||
@@ -543,6 +547,7 @@ impl Session {
|
||||
refresh_config,
|
||||
&self.services.mcp_thread_init,
|
||||
&self.services.thread_extension_data,
|
||||
&turn_context.originator,
|
||||
&available_environment_ids,
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -672,6 +672,7 @@ impl Session {
|
||||
let mcp_manager_for_mcp = Arc::clone(&mcp_manager);
|
||||
let mcp_thread_init_for_startup = &mcp_thread_init;
|
||||
let thread_extension_data_for_mcp = &thread_extension_data;
|
||||
let mcp_originator = session_configuration.originator.clone();
|
||||
let mcp_runtime_cwd = session_configuration
|
||||
.environment_selections()
|
||||
.first()
|
||||
@@ -688,6 +689,7 @@ impl Session {
|
||||
&config_for_mcp,
|
||||
mcp_thread_init_for_startup,
|
||||
thread_extension_data_for_mcp,
|
||||
&mcp_originator,
|
||||
/*available_environment_ids*/ &[],
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -553,6 +553,7 @@ async fn start_thread_seeds_extension_data_for_mcp_and_lifecycle_contributors()
|
||||
let mut server = codex_mcp::codex_apps_mcp_server_config(
|
||||
"https://selected.invalid",
|
||||
/*apps_mcp_product_sku*/ None,
|
||||
/*originator*/ None,
|
||||
);
|
||||
let CapabilityRootLocation::Environment { environment_id, .. } =
|
||||
&selected_root.location;
|
||||
@@ -574,6 +575,10 @@ async fn start_thread_seeds_extension_data_for_mcp_and_lifecycle_contributors()
|
||||
let mut config = test_config().await;
|
||||
config.codex_home = temp_dir.path().join("codex-home").abs();
|
||||
config.cwd = config.codex_home.abs();
|
||||
config
|
||||
.features
|
||||
.enable(Feature::Apps)
|
||||
.expect("test config should allow apps");
|
||||
std::fs::create_dir_all(&config.codex_home).expect("create codex home");
|
||||
|
||||
let lifecycle_observed = Arc::new(std::sync::Mutex::new(Vec::new()));
|
||||
@@ -620,7 +625,7 @@ async fn start_thread_seeds_extension_data_for_mcp_and_lifecycle_contributors()
|
||||
session_source: None,
|
||||
thread_source: None,
|
||||
dynamic_tools: Vec::new(),
|
||||
metrics_service_name: None,
|
||||
metrics_service_name: Some("codex_work_desktop".to_string()),
|
||||
parent_trace: None,
|
||||
environments: Vec::new(),
|
||||
thread_extension_init: selected_root_init("selected-a", "env-a"),
|
||||
@@ -646,6 +651,7 @@ async fn start_thread_seeds_extension_data_for_mcp_and_lifecycle_contributors()
|
||||
.await
|
||||
.expect("start second thread");
|
||||
let first_session = &first_thread.thread.codex.session;
|
||||
let first_originator = first_session.originator().await;
|
||||
let first_resolved = first_session
|
||||
.services
|
||||
.mcp_manager
|
||||
@@ -653,10 +659,12 @@ async fn start_thread_seeds_extension_data_for_mcp_and_lifecycle_contributors()
|
||||
&config,
|
||||
&first_session.services.mcp_thread_init,
|
||||
&first_session.services.thread_extension_data,
|
||||
&first_originator,
|
||||
/*available_environment_ids*/ &[],
|
||||
)
|
||||
.await;
|
||||
let second_session = &second_thread.thread.codex.session;
|
||||
let second_originator = second_session.originator().await;
|
||||
let second_resolved = second_session
|
||||
.services
|
||||
.mcp_manager
|
||||
@@ -664,6 +672,7 @@ async fn start_thread_seeds_extension_data_for_mcp_and_lifecycle_contributors()
|
||||
&config,
|
||||
&second_session.services.mcp_thread_init,
|
||||
&second_session.services.thread_extension_data,
|
||||
&second_originator,
|
||||
/*available_environment_ids*/ &[],
|
||||
)
|
||||
.await;
|
||||
@@ -706,6 +715,21 @@ async fn start_thread_seeds_extension_data_for_mcp_and_lifecycle_contributors()
|
||||
selected_servers(&second_resolved.config),
|
||||
std::collections::BTreeMap::from([("selected-b".to_string(), "env-b".to_string())])
|
||||
);
|
||||
let codex_apps_server = codex_mcp::configured_mcp_servers(&first_resolved.config)
|
||||
.remove(codex_mcp::CODEX_APPS_MCP_SERVER_NAME)
|
||||
.expect("Codex Apps server should be configured");
|
||||
let codex_apps_headers = match codex_apps_server.transport {
|
||||
codex_config::McpServerTransportConfig::StreamableHttp { http_headers, .. } => http_headers,
|
||||
codex_config::McpServerTransportConfig::Stdio { .. } => {
|
||||
panic!("Codex Apps server should use streamable HTTP")
|
||||
}
|
||||
};
|
||||
assert_eq!(
|
||||
codex_apps_headers
|
||||
.expect("Codex Apps headers should be configured")
|
||||
.get("originator"),
|
||||
Some(&"codex_work_desktop".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
@@ -66,6 +66,7 @@ async fn hosted_plugin_runtime_ps_mcp_tool_calls_use_current_auth_manager_token(
|
||||
let mut hosted_plugin_runtime_config = codex_mcp::hosted_plugin_runtime_mcp_server_config(
|
||||
&apps_server.chatgpt_base_url,
|
||||
/*apps_mcp_product_sku*/ None,
|
||||
/*originator*/ None,
|
||||
);
|
||||
let McpServerTransportConfig::StreamableHttp {
|
||||
bearer_token_env_var,
|
||||
|
||||
@@ -15,6 +15,8 @@ pub struct McpServerContributionContext<'a, C> {
|
||||
thread_store: Option<&'a ExtensionData>,
|
||||
/// Stable host inputs for the active thread, when resolution is thread-scoped.
|
||||
thread_init: Option<&'a ExtensionDataInit>,
|
||||
/// Effective request originator for the active thread, when resolution is thread-scoped.
|
||||
originator: Option<&'a str>,
|
||||
/// Environment IDs whose selected roots may contribute to this exact step.
|
||||
available_environment_ids: Option<&'a [String]>,
|
||||
}
|
||||
@@ -34,6 +36,7 @@ impl<'a, C> McpServerContributionContext<'a, C> {
|
||||
config,
|
||||
thread_store: None,
|
||||
thread_init: None,
|
||||
originator: None,
|
||||
available_environment_ids: None,
|
||||
}
|
||||
}
|
||||
@@ -43,12 +46,14 @@ impl<'a, C> McpServerContributionContext<'a, C> {
|
||||
config: &'a C,
|
||||
thread_init: &'a ExtensionDataInit,
|
||||
thread_store: &'a ExtensionData,
|
||||
originator: &'a str,
|
||||
available_environment_ids: &'a [String],
|
||||
) -> Self {
|
||||
Self {
|
||||
config,
|
||||
thread_store: Some(thread_store),
|
||||
thread_init: Some(thread_init),
|
||||
originator: Some(originator),
|
||||
available_environment_ids: Some(available_environment_ids),
|
||||
}
|
||||
}
|
||||
@@ -68,6 +73,11 @@ impl<'a, C> McpServerContributionContext<'a, C> {
|
||||
self.thread_init
|
||||
}
|
||||
|
||||
/// Returns the effective request originator when resolving for a running thread.
|
||||
pub fn originator(&self) -> Option<&'a str> {
|
||||
self.originator
|
||||
}
|
||||
|
||||
/// Returns the exact environment availability projection for a model step.
|
||||
///
|
||||
/// `Some` means contributors must omit selected roots whose environment ID is absent from the
|
||||
|
||||
@@ -32,6 +32,7 @@ impl McpServerContributor<Config> for HostedPluginRuntimeExtension {
|
||||
config: Box::new(hosted_plugin_runtime_mcp_server_config(
|
||||
&config.chatgpt_base_url,
|
||||
config.apps_mcp_product_sku.as_deref(),
|
||||
context.originator(),
|
||||
)),
|
||||
}]
|
||||
})
|
||||
@@ -51,3 +52,7 @@ pub fn install_executor_plugins(
|
||||
executor_plugin::SelectedExecutorPluginMcpContributor::new(environment_manager),
|
||||
));
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "lib_tests.rs"]
|
||||
mod tests;
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
use super::*;
|
||||
use codex_config::McpServerTransportConfig;
|
||||
use codex_core::config::ConfigBuilder;
|
||||
use codex_extension_api::ExtensionData;
|
||||
use codex_extension_api::ExtensionDataInit;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[tokio::test]
|
||||
async fn hosted_plugin_runtime_forwards_thread_originator() -> Result<(), Box<dyn std::error::Error>>
|
||||
{
|
||||
let codex_home = tempfile::tempdir()?;
|
||||
let config = ConfigBuilder::default()
|
||||
.codex_home(codex_home.path().to_path_buf())
|
||||
.fallback_cwd(Some(codex_home.path().to_path_buf()))
|
||||
.cli_overrides(vec![
|
||||
("features.apps".to_string(), true.into()),
|
||||
("chatgpt_base_url".to_string(), "https://chatgpt.com".into()),
|
||||
])
|
||||
.build()
|
||||
.await?;
|
||||
let thread_init = ExtensionDataInit::new();
|
||||
let thread_store = ExtensionData::new("thread");
|
||||
|
||||
let contributions = HostedPluginRuntimeExtension
|
||||
.contribute(McpServerContributionContext::for_step(
|
||||
&config,
|
||||
&thread_init,
|
||||
&thread_store,
|
||||
"codex_work_desktop",
|
||||
/*available_environment_ids*/ &[],
|
||||
))
|
||||
.await;
|
||||
let [McpServerContribution::Set { config: server, .. }] = contributions.as_slice() else {
|
||||
panic!("hosted plugin runtime should contribute one server");
|
||||
};
|
||||
let McpServerTransportConfig::StreamableHttp { http_headers, .. } = &server.transport else {
|
||||
panic!("hosted plugin runtime should use streamable HTTP");
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
http_headers
|
||||
.as_ref()
|
||||
.and_then(|headers| headers.get("originator")),
|
||||
Some(&"codex_work_desktop".to_string())
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -202,6 +202,7 @@ async fn raw_selected_plugin_contributions(
|
||||
config,
|
||||
&thread_init,
|
||||
&thread_store,
|
||||
"test_originator",
|
||||
&available_environment_ids,
|
||||
))
|
||||
.await)
|
||||
|
||||
Reference in New Issue
Block a user