mirror of
https://github.com/openai/codex.git
synced 2026-11-08 20:40:33 +00:00
Support Always Allow for MCP app messages
This commit is contained in:
+77
@@ -6089,6 +6089,16 @@
|
||||
"boolean",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"mcp_app_message_approval_mode": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/v2/McpAppMessageApprovalMode"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
@@ -10866,6 +10876,13 @@
|
||||
"title": "MarketplaceUpgradeResponse",
|
||||
"type": "object"
|
||||
},
|
||||
"McpAppMessageApprovalMode": {
|
||||
"enum": [
|
||||
"auto",
|
||||
"approve_in_thread"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"McpAuthStatus": {
|
||||
"enum": [
|
||||
"unsupported",
|
||||
@@ -10990,6 +11007,62 @@
|
||||
"title": "McpServerOauthLoginResponse",
|
||||
"type": "object"
|
||||
},
|
||||
"McpServerProvider": {
|
||||
"oneOf": [
|
||||
{
|
||||
"properties": {
|
||||
"type": {
|
||||
"enum": [
|
||||
"codex-apps"
|
||||
],
|
||||
"title": "CodexAppsMcpServerProviderType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type"
|
||||
],
|
||||
"title": "CodexAppsMcpServerProvider",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"type": {
|
||||
"enum": [
|
||||
"mcp"
|
||||
],
|
||||
"title": "McpMcpServerProviderType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type"
|
||||
],
|
||||
"title": "McpMcpServerProvider",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"pluginId": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"plugin-mcp"
|
||||
],
|
||||
"title": "PluginMcpMcpServerProviderType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"pluginId",
|
||||
"type"
|
||||
],
|
||||
"title": "PluginMcpMcpServerProvider",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
"McpServerRefreshResponse": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "McpServerRefreshResponse",
|
||||
@@ -11012,6 +11085,9 @@
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"provider": {
|
||||
"$ref": "#/definitions/v2/McpServerProvider"
|
||||
},
|
||||
"resourceTemplates": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/v2/ResourceTemplate"
|
||||
@@ -11034,6 +11110,7 @@
|
||||
"required": [
|
||||
"authStatus",
|
||||
"name",
|
||||
"provider",
|
||||
"resourceTemplates",
|
||||
"resources",
|
||||
"tools"
|
||||
|
||||
+77
@@ -668,6 +668,16 @@
|
||||
"boolean",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"mcp_app_message_approval_mode": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/McpAppMessageApprovalMode"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
@@ -7477,6 +7487,13 @@
|
||||
"title": "MarketplaceUpgradeResponse",
|
||||
"type": "object"
|
||||
},
|
||||
"McpAppMessageApprovalMode": {
|
||||
"enum": [
|
||||
"auto",
|
||||
"approve_in_thread"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"McpAuthStatus": {
|
||||
"enum": [
|
||||
"unsupported",
|
||||
@@ -7601,6 +7618,62 @@
|
||||
"title": "McpServerOauthLoginResponse",
|
||||
"type": "object"
|
||||
},
|
||||
"McpServerProvider": {
|
||||
"oneOf": [
|
||||
{
|
||||
"properties": {
|
||||
"type": {
|
||||
"enum": [
|
||||
"codex-apps"
|
||||
],
|
||||
"title": "CodexAppsMcpServerProviderType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type"
|
||||
],
|
||||
"title": "CodexAppsMcpServerProvider",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"type": {
|
||||
"enum": [
|
||||
"mcp"
|
||||
],
|
||||
"title": "McpMcpServerProviderType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type"
|
||||
],
|
||||
"title": "McpMcpServerProvider",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"pluginId": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"plugin-mcp"
|
||||
],
|
||||
"title": "PluginMcpMcpServerProviderType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"pluginId",
|
||||
"type"
|
||||
],
|
||||
"title": "PluginMcpMcpServerProvider",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
"McpServerRefreshResponse": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "McpServerRefreshResponse",
|
||||
@@ -7623,6 +7696,9 @@
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"provider": {
|
||||
"$ref": "#/definitions/McpServerProvider"
|
||||
},
|
||||
"resourceTemplates": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/ResourceTemplate"
|
||||
@@ -7645,6 +7721,7 @@
|
||||
"required": [
|
||||
"authStatus",
|
||||
"name",
|
||||
"provider",
|
||||
"resourceTemplates",
|
||||
"resources",
|
||||
"tools"
|
||||
|
||||
@@ -89,6 +89,16 @@
|
||||
"boolean",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"mcp_app_message_approval_mode": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/McpAppMessageApprovalMode"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
@@ -585,6 +595,13 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"McpAppMessageApprovalMode": {
|
||||
"enum": [
|
||||
"auto",
|
||||
"approve_in_thread"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"ProfileV2": {
|
||||
"additionalProperties": true,
|
||||
"properties": {
|
||||
|
||||
+60
@@ -10,6 +10,62 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"McpServerProvider": {
|
||||
"oneOf": [
|
||||
{
|
||||
"properties": {
|
||||
"type": {
|
||||
"enum": [
|
||||
"codex-apps"
|
||||
],
|
||||
"title": "CodexAppsMcpServerProviderType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type"
|
||||
],
|
||||
"title": "CodexAppsMcpServerProvider",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"type": {
|
||||
"enum": [
|
||||
"mcp"
|
||||
],
|
||||
"title": "McpMcpServerProviderType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type"
|
||||
],
|
||||
"title": "McpMcpServerProvider",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"pluginId": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"plugin-mcp"
|
||||
],
|
||||
"title": "PluginMcpMcpServerProviderType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"pluginId",
|
||||
"type"
|
||||
],
|
||||
"title": "PluginMcpMcpServerProvider",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
"McpServerStatus": {
|
||||
"properties": {
|
||||
"authStatus": {
|
||||
@@ -18,6 +74,9 @@
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"provider": {
|
||||
"$ref": "#/definitions/McpServerProvider"
|
||||
},
|
||||
"resourceTemplates": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/ResourceTemplate"
|
||||
@@ -40,6 +99,7 @@
|
||||
"required": [
|
||||
"authStatus",
|
||||
"name",
|
||||
"provider",
|
||||
"resourceTemplates",
|
||||
"resources",
|
||||
"tools"
|
||||
|
||||
@@ -2,5 +2,6 @@
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { AppToolApproval } from "./AppToolApproval";
|
||||
import type { McpAppMessageApprovalMode } from "./McpAppMessageApprovalMode";
|
||||
|
||||
export type AppToolsConfig = { [key in string]?: { enabled: boolean | null, approval_mode: AppToolApproval | null, } };
|
||||
export type AppToolsConfig = { [key in string]?: { enabled: boolean | null, approval_mode: AppToolApproval | null, mcp_app_message_approval_mode: McpAppMessageApprovalMode | null, } };
|
||||
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type McpAppMessageApprovalMode = "auto" | "approve_in_thread";
|
||||
@@ -0,0 +1,5 @@
|
||||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type McpServerProvider = { "type": "codex-apps" } | { "type": "mcp" } | { "type": "plugin-mcp", pluginId: string, };
|
||||
@@ -5,5 +5,6 @@ import type { Resource } from "../Resource";
|
||||
import type { ResourceTemplate } from "../ResourceTemplate";
|
||||
import type { Tool } from "../Tool";
|
||||
import type { McpAuthStatus } from "./McpAuthStatus";
|
||||
import type { McpServerProvider } from "./McpServerProvider";
|
||||
|
||||
export type McpServerStatus = { name: string, tools: { [key in string]?: Tool }, resources: Array<Resource>, resourceTemplates: Array<ResourceTemplate>, authStatus: McpAuthStatus, };
|
||||
export type McpServerStatus = { name: string, provider: McpServerProvider, tools: { [key in string]?: Tool }, resources: Array<Resource>, resourceTemplates: Array<ResourceTemplate>, authStatus: McpAuthStatus, };
|
||||
|
||||
@@ -191,6 +191,7 @@ export type { MarketplaceRemoveResponse } from "./MarketplaceRemoveResponse";
|
||||
export type { MarketplaceUpgradeErrorInfo } from "./MarketplaceUpgradeErrorInfo";
|
||||
export type { MarketplaceUpgradeParams } from "./MarketplaceUpgradeParams";
|
||||
export type { MarketplaceUpgradeResponse } from "./MarketplaceUpgradeResponse";
|
||||
export type { McpAppMessageApprovalMode } from "./McpAppMessageApprovalMode";
|
||||
export type { McpAuthStatus } from "./McpAuthStatus";
|
||||
export type { McpElicitationArrayType } from "./McpElicitationArrayType";
|
||||
export type { McpElicitationBooleanSchema } from "./McpElicitationBooleanSchema";
|
||||
@@ -223,6 +224,7 @@ export type { McpServerMigration } from "./McpServerMigration";
|
||||
export type { McpServerOauthLoginCompletedNotification } from "./McpServerOauthLoginCompletedNotification";
|
||||
export type { McpServerOauthLoginParams } from "./McpServerOauthLoginParams";
|
||||
export type { McpServerOauthLoginResponse } from "./McpServerOauthLoginResponse";
|
||||
export type { McpServerProvider } from "./McpServerProvider";
|
||||
export type { McpServerRefreshResponse } from "./McpServerRefreshResponse";
|
||||
export type { McpServerStartupState } from "./McpServerStartupState";
|
||||
export type { McpServerStatus } from "./McpServerStatus";
|
||||
|
||||
@@ -166,6 +166,14 @@ pub enum AppToolApproval {
|
||||
Approve,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum McpAppMessageApprovalMode {
|
||||
Auto,
|
||||
ApproveInThread,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[ts(export_to = "v2/")]
|
||||
@@ -184,6 +192,7 @@ pub struct AppsDefaultConfig {
|
||||
pub struct AppToolConfig {
|
||||
pub enabled: Option<bool>,
|
||||
pub approval_mode: Option<AppToolApproval>,
|
||||
pub mcp_app_message_approval_mode: Option<McpAppMessageApprovalMode>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
|
||||
@@ -46,11 +46,28 @@ pub enum McpServerStatusDetail {
|
||||
ToolsAndAuthOnly,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(tag = "type")]
|
||||
#[ts(tag = "type")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum McpServerProvider {
|
||||
#[serde(rename = "codex-apps")]
|
||||
#[ts(rename = "codex-apps")]
|
||||
CodexApps,
|
||||
#[serde(rename = "mcp")]
|
||||
#[ts(rename = "mcp")]
|
||||
Mcp,
|
||||
#[serde(rename = "plugin-mcp", rename_all = "camelCase")]
|
||||
#[ts(rename = "plugin-mcp", rename_all = "camelCase")]
|
||||
PluginMcp { plugin_id: String },
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct McpServerStatus {
|
||||
pub name: String,
|
||||
pub provider: McpServerProvider,
|
||||
pub tools: std::collections::HashMap<String, McpTool>,
|
||||
pub resources: Vec<McpResource>,
|
||||
pub resource_templates: Vec<McpResourceTemplate>,
|
||||
|
||||
@@ -234,6 +234,52 @@ async fn write_value_supports_custom_mcp_server_default_tool_approval_mode() ->
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn write_value_supports_custom_mcp_server_message_approval_mode() -> Result<()> {
|
||||
let tmp = tempdir().expect("tempdir");
|
||||
std::fs::write(
|
||||
tmp.path().join(CONFIG_TOML_FILE),
|
||||
"[mcp_servers.docs]\ncommand = \"docs-server\"\n",
|
||||
)?;
|
||||
|
||||
let service = ConfigManager::without_managed_config_for_tests(tmp.path().to_path_buf());
|
||||
service
|
||||
.write_value(ConfigValueWriteParams {
|
||||
file_path: Some(tmp.path().join(CONFIG_TOML_FILE).display().to_string()),
|
||||
key_path: "mcp_servers.docs.tools".to_string(),
|
||||
value: serde_json::json!({
|
||||
"search": {
|
||||
"mcp_app_message_approval_mode": "approve_in_thread",
|
||||
},
|
||||
}),
|
||||
merge_strategy: MergeStrategy::Upsert,
|
||||
expected_version: None,
|
||||
})
|
||||
.await
|
||||
.expect("write mcp server tool mcp_app_message_approval_mode succeeds");
|
||||
|
||||
let read = service
|
||||
.read(ConfigReadParams {
|
||||
include_layers: false,
|
||||
cwd: None,
|
||||
})
|
||||
.await
|
||||
.expect("config read succeeds");
|
||||
|
||||
assert_eq!(
|
||||
read.config
|
||||
.additional
|
||||
.get("mcp_servers")
|
||||
.and_then(|servers| servers.get("docs"))
|
||||
.and_then(|server| server.get("tools"))
|
||||
.and_then(|tools| tools.get("search"))
|
||||
.and_then(|tool| tool.get("mcp_app_message_approval_mode")),
|
||||
Some(&serde_json::json!("approve_in_thread"))
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn read_includes_origins_and_layers() {
|
||||
let tmp = tempdir().expect("tempdir");
|
||||
|
||||
@@ -92,6 +92,7 @@ use codex_app_server_protocol::McpResourceReadResponse;
|
||||
use codex_app_server_protocol::McpServerOauthLoginCompletedNotification;
|
||||
use codex_app_server_protocol::McpServerOauthLoginParams;
|
||||
use codex_app_server_protocol::McpServerOauthLoginResponse;
|
||||
use codex_app_server_protocol::McpServerProvider;
|
||||
use codex_app_server_protocol::McpServerRefreshResponse;
|
||||
use codex_app_server_protocol::McpServerStatus;
|
||||
use codex_app_server_protocol::McpServerStatusDetail;
|
||||
@@ -320,6 +321,7 @@ use codex_login::complete_device_code_login;
|
||||
use codex_login::login_with_api_key;
|
||||
use codex_login::request_device_code;
|
||||
use codex_login::run_login_server;
|
||||
use codex_mcp::CODEX_APPS_MCP_SERVER_NAME;
|
||||
use codex_mcp::McpRuntimeEnvironment;
|
||||
use codex_mcp::McpServerStatusSnapshot;
|
||||
use codex_mcp::McpSnapshotDetail;
|
||||
|
||||
@@ -318,6 +318,29 @@ impl McpRequestProcessor {
|
||||
resource_templates,
|
||||
auth_statuses,
|
||||
} = snapshot;
|
||||
let mut provider_by_server: HashMap<String, McpServerProvider> = config
|
||||
.mcp_servers
|
||||
.keys()
|
||||
.cloned()
|
||||
.map(|name| (name, McpServerProvider::Mcp))
|
||||
.collect();
|
||||
for plugin in &mcp_config.plugin_capability_summaries {
|
||||
for name in &plugin.mcp_server_names {
|
||||
if effective_servers.contains_key(name) {
|
||||
provider_by_server.entry(name.clone()).or_insert_with(|| {
|
||||
McpServerProvider::PluginMcp {
|
||||
plugin_id: plugin.config_name.clone(),
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
if effective_servers.contains_key(CODEX_APPS_MCP_SERVER_NAME) {
|
||||
provider_by_server.insert(
|
||||
CODEX_APPS_MCP_SERVER_NAME.to_string(),
|
||||
McpServerProvider::CodexApps,
|
||||
);
|
||||
}
|
||||
|
||||
let mut server_names: Vec<String> = config
|
||||
.mcp_servers
|
||||
@@ -357,6 +380,10 @@ impl McpRequestProcessor {
|
||||
.iter()
|
||||
.map(|name| McpServerStatus {
|
||||
name: name.clone(),
|
||||
provider: provider_by_server
|
||||
.get(name)
|
||||
.cloned()
|
||||
.unwrap_or(McpServerProvider::Mcp),
|
||||
tools: tools_by_server.get(name).cloned().unwrap_or_default(),
|
||||
resources: resources.get(name).cloned().unwrap_or_default(),
|
||||
resource_templates: resource_templates.get(name).cloned().unwrap_or_default(),
|
||||
|
||||
@@ -88,6 +88,7 @@ pub use marketplace_edit::remove_user_marketplace_config;
|
||||
pub use mcp_edit::ConfigEditsBuilder;
|
||||
pub use mcp_edit::load_global_mcp_servers;
|
||||
pub use mcp_types::AppToolApproval;
|
||||
pub use mcp_types::McpAppMessageApprovalMode;
|
||||
pub use mcp_types::McpServerConfig;
|
||||
pub use mcp_types::McpServerDisabledReason;
|
||||
pub use mcp_types::McpServerEnvVar;
|
||||
|
||||
@@ -13,6 +13,7 @@ use toml_edit::value;
|
||||
|
||||
use crate::AppToolApproval;
|
||||
use crate::CONFIG_TOML_FILE;
|
||||
use crate::McpAppMessageApprovalMode;
|
||||
use crate::McpServerConfig;
|
||||
use crate::McpServerEnvVar;
|
||||
use crate::McpServerTransportConfig;
|
||||
@@ -232,6 +233,12 @@ fn serialize_mcp_server(config: &McpServerConfig) -> TomlItem {
|
||||
AppToolApproval::Approve => "approve",
|
||||
});
|
||||
}
|
||||
if let Some(approval_mode) = tool_config.mcp_app_message_approval_mode {
|
||||
tool_entry["mcp_app_message_approval_mode"] = value(match approval_mode {
|
||||
McpAppMessageApprovalMode::Auto => "auto",
|
||||
McpAppMessageApprovalMode::ApproveInThread => "approve_in_thread",
|
||||
});
|
||||
}
|
||||
tools.insert(name, TomlItem::Table(tool_entry));
|
||||
}
|
||||
entry.insert("tools", TomlItem::Table(tools));
|
||||
|
||||
@@ -39,12 +39,16 @@ async fn replace_mcp_servers_serializes_per_tool_approval_overrides() -> anyhow:
|
||||
"search".to_string(),
|
||||
McpServerToolConfig {
|
||||
approval_mode: Some(AppToolApproval::Approve),
|
||||
mcp_app_message_approval_mode: Some(
|
||||
McpAppMessageApprovalMode::ApproveInThread,
|
||||
),
|
||||
},
|
||||
),
|
||||
(
|
||||
"read".to_string(),
|
||||
McpServerToolConfig {
|
||||
approval_mode: Some(AppToolApproval::Prompt),
|
||||
mcp_app_message_approval_mode: None,
|
||||
},
|
||||
),
|
||||
]),
|
||||
@@ -72,6 +76,7 @@ approval_mode = "prompt"
|
||||
|
||||
[mcp_servers.docs.tools.search]
|
||||
approval_mode = "approve"
|
||||
mcp_app_message_approval_mode = "approve_in_thread"
|
||||
"#
|
||||
);
|
||||
|
||||
|
||||
@@ -22,6 +22,13 @@ pub enum AppToolApproval {
|
||||
Approve,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum McpAppMessageApprovalMode {
|
||||
Auto,
|
||||
ApproveInThread,
|
||||
}
|
||||
|
||||
/// Human-readable reason a configured MCP server was disabled after requirements
|
||||
/// were applied.
|
||||
///
|
||||
@@ -54,6 +61,10 @@ pub struct McpServerToolConfig {
|
||||
/// Approval mode for this tool.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub approval_mode: Option<AppToolApproval>,
|
||||
|
||||
/// Approval mode for MCP app messages sent by this tool.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub mcp_app_message_approval_mode: Option<McpAppMessageApprovalMode>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema)]
|
||||
|
||||
@@ -320,6 +320,7 @@ fn deserialize_server_config_with_default_tool_approval_mode() {
|
||||
|
||||
[tools.search]
|
||||
approval_mode = "prompt"
|
||||
mcp_app_message_approval_mode = "approve_in_thread"
|
||||
"#,
|
||||
)
|
||||
.expect("should deserialize default tool approval mode");
|
||||
@@ -332,11 +333,13 @@ fn deserialize_server_config_with_default_tool_approval_mode() {
|
||||
cfg.tools.get("search"),
|
||||
Some(&McpServerToolConfig {
|
||||
approval_mode: Some(AppToolApproval::Prompt),
|
||||
mcp_app_message_approval_mode: Some(McpAppMessageApprovalMode::ApproveInThread),
|
||||
})
|
||||
);
|
||||
|
||||
let serialized = toml::to_string(&cfg).expect("should serialize MCP config");
|
||||
assert!(serialized.contains("default_tools_approval_mode = \"approve\""));
|
||||
assert!(serialized.contains("mcp_app_message_approval_mode = \"approve_in_thread\""));
|
||||
|
||||
let round_tripped: McpServerConfig =
|
||||
toml::from_str(&serialized).expect("should deserialize serialized MCP config");
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
// definitions that do not contain business logic.
|
||||
|
||||
pub use crate::mcp_types::AppToolApproval;
|
||||
pub use crate::mcp_types::McpAppMessageApprovalMode;
|
||||
pub use crate::mcp_types::McpServerConfig;
|
||||
pub use crate::mcp_types::McpServerDisabledReason;
|
||||
pub use crate::mcp_types::McpServerEnvVar;
|
||||
@@ -399,6 +400,10 @@ pub struct AppToolConfig {
|
||||
/// Approval mode for this tool.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub approval_mode: Option<AppToolApproval>,
|
||||
|
||||
/// Approval mode for MCP app messages sent by resources returned from this tool.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub mcp_app_message_approval_mode: Option<McpAppMessageApprovalMode>,
|
||||
}
|
||||
|
||||
/// Tool settings for a single app.
|
||||
|
||||
@@ -325,6 +325,7 @@ approval_mode = "approve"
|
||||
server.tools.get("search"),
|
||||
Some(&McpServerToolConfig {
|
||||
approval_mode: Some(AppToolApproval::Approve),
|
||||
mcp_app_message_approval_mode: None,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@@ -161,6 +161,14 @@
|
||||
"enabled": {
|
||||
"description": "Whether this tool is enabled. `Some(true)` explicitly allows this tool.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"mcp_app_message_approval_mode": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/McpAppMessageApprovalMode"
|
||||
}
|
||||
],
|
||||
"description": "Approval mode for MCP app messages sent by resources returned from this tool."
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
@@ -1148,6 +1156,13 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"McpAppMessageApprovalMode": {
|
||||
"enum": [
|
||||
"auto",
|
||||
"approve_in_thread"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"McpServerEnvVar": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -1181,6 +1196,14 @@
|
||||
}
|
||||
],
|
||||
"description": "Approval mode for this tool."
|
||||
},
|
||||
"mcp_app_message_approval_mode": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/McpAppMessageApprovalMode"
|
||||
}
|
||||
],
|
||||
"description": "Approval mode for MCP app messages sent by this tool."
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
|
||||
@@ -3787,6 +3787,7 @@ approval_mode = "approve"
|
||||
server.tools.get("search"),
|
||||
Some(&McpServerToolConfig {
|
||||
approval_mode: Some(AppToolApproval::Approve),
|
||||
mcp_app_message_approval_mode: None,
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -3830,6 +3831,7 @@ approval_mode = "approve"
|
||||
tool,
|
||||
&McpServerToolConfig {
|
||||
approval_mode: Some(AppToolApproval::Approve),
|
||||
mcp_app_message_approval_mode: None,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -197,6 +197,7 @@ pub fn model_availability_nux_count_edits(shown_count: &HashMap<String, u32>) ->
|
||||
// TODO(jif) move to a dedicated file
|
||||
mod document_helpers {
|
||||
use codex_config::types::AppToolApproval;
|
||||
use codex_config::types::McpAppMessageApprovalMode;
|
||||
use codex_config::types::McpServerConfig;
|
||||
use codex_config::types::McpServerEnvVar;
|
||||
use codex_config::types::McpServerToolConfig;
|
||||
@@ -361,6 +362,12 @@ mod document_helpers {
|
||||
AppToolApproval::Approve => "approve",
|
||||
});
|
||||
}
|
||||
if let Some(approval_mode) = config.mcp_app_message_approval_mode {
|
||||
entry["mcp_app_message_approval_mode"] = value(match approval_mode {
|
||||
McpAppMessageApprovalMode::Auto => "auto",
|
||||
McpAppMessageApprovalMode::ApproveInThread => "approve_in_thread",
|
||||
});
|
||||
}
|
||||
TomlItem::Table(entry)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use super::*;
|
||||
use codex_config::types::AppToolApproval;
|
||||
use codex_config::types::McpAppMessageApprovalMode;
|
||||
use codex_config::types::McpServerToolConfig;
|
||||
use codex_config::types::McpServerTransportConfig;
|
||||
use codex_config::types::SessionPickerViewMode;
|
||||
@@ -1011,6 +1012,7 @@ fn blocking_replace_mcp_servers_serializes_tool_approval_overrides() {
|
||||
"search".to_string(),
|
||||
McpServerToolConfig {
|
||||
approval_mode: Some(AppToolApproval::Approve),
|
||||
mcp_app_message_approval_mode: Some(McpAppMessageApprovalMode::ApproveInThread),
|
||||
},
|
||||
)]),
|
||||
},
|
||||
@@ -1031,6 +1033,7 @@ default_tools_approval_mode = \"prompt\"
|
||||
|
||||
[mcp_servers.docs.tools.search]
|
||||
approval_mode = \"approve\"
|
||||
mcp_app_message_approval_mode = \"approve_in_thread\"
|
||||
";
|
||||
assert_eq!(raw, expected);
|
||||
}
|
||||
|
||||
@@ -859,6 +859,7 @@ fn app_tool_policy_per_tool_enabled_true_overrides_app_level_disable_flags() {
|
||||
AppToolConfig {
|
||||
enabled: Some(true),
|
||||
approval_mode: None,
|
||||
mcp_app_message_approval_mode: None,
|
||||
},
|
||||
)]),
|
||||
}),
|
||||
@@ -1009,6 +1010,7 @@ fn app_tool_policy_matches_prefix_stripped_tool_name_for_tool_config() {
|
||||
AppToolConfig {
|
||||
enabled: Some(true),
|
||||
approval_mode: Some(AppToolApproval::Approve),
|
||||
mcp_app_message_approval_mode: None,
|
||||
},
|
||||
)]),
|
||||
}),
|
||||
|
||||
@@ -1527,6 +1527,7 @@ async fn persist_codex_app_tool_approval_writes_tool_override() {
|
||||
AppToolConfig {
|
||||
enabled: None,
|
||||
approval_mode: Some(AppToolApproval::Approve),
|
||||
mcp_app_message_approval_mode: None,
|
||||
},
|
||||
)]),
|
||||
}),
|
||||
@@ -1567,6 +1568,7 @@ async fn persist_custom_mcp_tool_approval_writes_tool_override() {
|
||||
tool,
|
||||
&McpServerToolConfig {
|
||||
approval_mode: Some(AppToolApproval::Approve),
|
||||
mcp_app_message_approval_mode: None,
|
||||
}
|
||||
);
|
||||
assert!(contents.contains("[mcp_servers.docs.tools.search]"));
|
||||
@@ -1736,6 +1738,7 @@ async fn maybe_persist_mcp_tool_approval_reloads_session_config() {
|
||||
&AppToolConfig {
|
||||
enabled: None,
|
||||
approval_mode: Some(AppToolApproval::Approve),
|
||||
mcp_app_message_approval_mode: None,
|
||||
}
|
||||
);
|
||||
assert_eq!(mcp_tool_approval_is_remembered(&session, &key).await, true);
|
||||
@@ -1778,6 +1781,7 @@ async fn maybe_persist_mcp_tool_approval_reloads_session_config_for_custom_serve
|
||||
tool,
|
||||
&McpServerToolConfig {
|
||||
approval_mode: Some(AppToolApproval::Approve),
|
||||
mcp_app_message_approval_mode: None,
|
||||
}
|
||||
);
|
||||
assert_eq!(mcp_tool_approval_is_remembered(&session, &key).await, true);
|
||||
@@ -1827,6 +1831,7 @@ enabled = true
|
||||
tool,
|
||||
&McpServerToolConfig {
|
||||
approval_mode: Some(AppToolApproval::Approve),
|
||||
mcp_app_message_approval_mode: None,
|
||||
}
|
||||
);
|
||||
assert!(contents.contains(r#"[plugins."sample@test".mcp_servers.sample.tools.search]"#));
|
||||
@@ -1883,6 +1888,7 @@ async fn maybe_persist_mcp_tool_approval_writes_project_config_for_project_serve
|
||||
tool,
|
||||
&McpServerToolConfig {
|
||||
approval_mode: Some(AppToolApproval::Approve),
|
||||
mcp_app_message_approval_mode: None,
|
||||
}
|
||||
);
|
||||
assert!(contents.contains("[mcp_servers.docs.tools.search]"));
|
||||
|
||||
@@ -12,6 +12,8 @@ use codex_app_server_protocol::MarketplaceRemoveParams;
|
||||
use codex_app_server_protocol::MarketplaceRemoveResponse;
|
||||
use codex_app_server_protocol::MarketplaceUpgradeParams;
|
||||
use codex_app_server_protocol::MarketplaceUpgradeResponse;
|
||||
#[cfg(test)]
|
||||
use codex_app_server_protocol::McpServerProvider;
|
||||
|
||||
use codex_app_server_protocol::RequestId;
|
||||
|
||||
@@ -1044,6 +1046,7 @@ mod tests {
|
||||
let statuses = vec![
|
||||
McpServerStatus {
|
||||
name: "docs".to_string(),
|
||||
provider: McpServerProvider::Mcp,
|
||||
tools: HashMap::from([(
|
||||
"list".to_string(),
|
||||
Tool {
|
||||
@@ -1063,6 +1066,7 @@ mod tests {
|
||||
},
|
||||
McpServerStatus {
|
||||
name: "disabled".to_string(),
|
||||
provider: McpServerProvider::Mcp,
|
||||
tools: HashMap::new(),
|
||||
resources: Vec::new(),
|
||||
resource_templates: Vec::new(),
|
||||
|
||||
@@ -37,6 +37,7 @@ use codex_app_server_protocol::FileChangeRequestApprovalParams;
|
||||
use codex_app_server_protocol::FileUpdateChange;
|
||||
use codex_app_server_protocol::ItemStartedNotification;
|
||||
use codex_app_server_protocol::JSONRPCErrorError;
|
||||
use codex_app_server_protocol::McpServerProvider;
|
||||
use codex_app_server_protocol::McpServerStartupState;
|
||||
use codex_app_server_protocol::McpServerStatusUpdatedNotification;
|
||||
use codex_app_server_protocol::NetworkApprovalContext as AppServerNetworkApprovalContext;
|
||||
@@ -111,6 +112,7 @@ async fn handle_mcp_inventory_result_clears_committed_loading_cell() {
|
||||
app.handle_mcp_inventory_result(
|
||||
Ok(vec![McpServerStatus {
|
||||
name: "docs".to_string(),
|
||||
provider: McpServerProvider::Mcp,
|
||||
tools: HashMap::new(),
|
||||
resources: Vec::new(),
|
||||
resource_templates: Vec::new(),
|
||||
|
||||
@@ -48,6 +48,8 @@ use crate::wrapping::adaptive_wrap_lines;
|
||||
use base64::Engine;
|
||||
use codex_app_server_protocol::AskForApproval;
|
||||
use codex_app_server_protocol::McpAuthStatus;
|
||||
#[cfg(test)]
|
||||
use codex_app_server_protocol::McpServerProvider;
|
||||
use codex_app_server_protocol::McpServerStatus;
|
||||
use codex_app_server_protocol::McpServerStatusDetail;
|
||||
use codex_app_server_protocol::PermissionProfile as AppServerPermissionProfile;
|
||||
@@ -4198,6 +4200,7 @@ mod tests {
|
||||
|
||||
let statuses = vec![McpServerStatus {
|
||||
name: "plugin_docs".to_string(),
|
||||
provider: McpServerProvider::Mcp,
|
||||
tools: HashMap::from([(
|
||||
"lookup".to_string(),
|
||||
Tool {
|
||||
@@ -4239,6 +4242,7 @@ mod tests {
|
||||
|
||||
let statuses = vec![McpServerStatus {
|
||||
name: "plugin_docs".to_string(),
|
||||
provider: McpServerProvider::Mcp,
|
||||
tools: HashMap::from([(
|
||||
"lookup".to_string(),
|
||||
Tool {
|
||||
|
||||
Reference in New Issue
Block a user