[mcp] Support MCP Apps part 3 - Add mcp tool call support. (#17364)

- [x] Add a new app-server method so that MCP Apps can call their own
MCP server directly.
This commit is contained in:
Matthew Zeng
2026-04-10 21:39:19 -07:00
committed by GitHub
parent f8bb088617
commit b7139a7e8f
112 changed files with 871 additions and 238 deletions

View File

@@ -129,12 +129,7 @@ pub fn generate_ts_with_options(
}
// Ensure our header is present on all TS files (root + subdirs like v2/).
let mut ts_files = Vec::new();
let should_collect_ts_files =
options.ensure_headers || (options.run_prettier && prettier.is_some());
if should_collect_ts_files {
ts_files = ts_files_in_recursive(out_dir)?;
}
let ts_files = ts_files_in_recursive(out_dir)?;
if options.ensure_headers {
let worker_count = thread::available_parallelism()
@@ -179,6 +174,8 @@ pub fn generate_ts_with_options(
}
}
trim_trailing_whitespace_in_ts_files(&ts_files)?;
Ok(())
}
@@ -1942,6 +1939,32 @@ fn ts_files_in_recursive(dir: &Path) -> Result<Vec<PathBuf>> {
Ok(files)
}
fn trim_trailing_whitespace_in_ts_files(paths: &[PathBuf]) -> Result<()> {
for path in paths {
let content = fs::read_to_string(path)
.with_context(|| format!("Failed to read {}", path.display()))?;
let trimmed = trim_trailing_line_whitespace(&content);
if trimmed != content {
fs::write(path, trimmed)
.with_context(|| format!("Failed to write {}", path.display()))?;
}
}
Ok(())
}
pub(crate) fn trim_trailing_line_whitespace(content: &str) -> String {
let mut trimmed = String::with_capacity(content.len());
for line in content.split_inclusive('\n') {
if let Some(line_without_newline) = line.strip_suffix('\n') {
trimmed.push_str(line_without_newline.trim_end_matches([' ', '\t']));
trimmed.push('\n');
} else {
trimmed.push_str(line.trim_end_matches([' ', '\t']));
}
}
trimmed
}
/// Generate an index.ts file that re-exports all generated types.
/// This allows consumers to import all types from a single file.
fn generate_index_ts(out_dir: &Path) -> Result<PathBuf> {

View File

@@ -470,6 +470,11 @@ client_request_definitions! {
response: v2::McpResourceReadResponse,
},
McpServerToolCall => "mcpServer/tool/call" {
params: v2::McpServerToolCallParams,
response: v2::McpServerToolCallResponse,
},
WindowsSandboxSetupStart => "windowsSandbox/setupStart" {
params: v2::WindowsSandboxSetupStartParams,
response: v2::WindowsSandboxSetupStartResponse,

View File

@@ -29,6 +29,7 @@ use codex_protocol::config_types::WebSearchMode;
use codex_protocol::config_types::WebSearchToolConfig;
use codex_protocol::items::AgentMessageContent as CoreAgentMessageContent;
use codex_protocol::items::TurnItem as CoreTurnItem;
use codex_protocol::mcp::CallToolResult as CoreMcpCallToolResult;
use codex_protocol::mcp::Resource as McpResource;
pub use codex_protocol::mcp::ResourceContent as McpResourceContent;
use codex_protocol::mcp::ResourceTemplate as McpResourceTemplate;
@@ -2017,6 +2018,48 @@ pub struct McpResourceReadResponse {
pub contents: Vec<McpResourceContent>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct McpServerToolCallParams {
pub thread_id: String,
pub server: String,
pub tool: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub arguments: Option<JsonValue>,
#[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub meta: Option<JsonValue>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct McpServerToolCallResponse {
pub content: Vec<JsonValue>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub structured_content: Option<JsonValue>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub is_error: Option<bool>,
#[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub meta: Option<JsonValue>,
}
impl From<CoreMcpCallToolResult> for McpServerToolCallResponse {
fn from(result: CoreMcpCallToolResult) -> Self {
Self {
content: result.content,
structured_content: result.structured_content,
is_error: result.is_error,
meta: result.meta,
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]

View File

@@ -5,6 +5,7 @@ use crate::ServerRequest;
use crate::export::GENERATED_TS_HEADER;
use crate::export::filter_experimental_ts_tree;
use crate::export::generate_index_ts_tree;
use crate::export::trim_trailing_line_whitespace;
use crate::protocol::common::visit_client_response_types;
use crate::protocol::common::visit_server_response_types;
use anyhow::Context;
@@ -68,6 +69,9 @@ pub fn generate_typescript_schema_fixture_subtree_for_tests() -> Result<BTreeMap
filter_experimental_ts_tree(&mut files)?;
generate_index_ts_tree(&mut files);
for content in files.values_mut() {
*content = trim_trailing_line_whitespace(content);
}
Ok(files
.into_iter()