mirror of
https://github.com/openai/codex.git
synced 2026-05-04 13:21:54 +03:00
feat: replace custom mcp-types crate with equivalents from rmcp (#10349)
We started working with MCP in Codex before
https://crates.io/crates/rmcp was mature, so we had our own crate for
MCP types that was generated from the MCP schema:
8b95d3e082/codex-rs/mcp-types/README.md
Now that `rmcp` is more mature, it makes more sense to use their MCP
types in Rust, as they handle details (like the `_meta` field) that our
custom version ignored. Though one advantage that our custom types had
is that our generated types implemented `JsonSchema` and `ts_rs::TS`,
whereas the types in `rmcp` do not. As such, part of the work of this PR
is leveraging the adapters between `rmcp` types and the serializable
types that are API for us (app server and MCP) introduced in #10356.
Note this PR results in a number of changes to
`codex-rs/app-server-protocol/schema`, which merit special attention
during review. We must ensure that these changes are still
backwards-compatible, which is possible because we have:
```diff
- export type CallToolResult = { content: Array<ContentBlock>, isError?: boolean, structuredContent?: JsonValue, };
+ export type CallToolResult = { content: Array<JsonValue>, structuredContent?: JsonValue, isError?: boolean, _meta?: JsonValue, };
```
so `ContentBlock` has been replaced with the more general `JsonValue`.
Note that `ContentBlock` was defined as:
```typescript
export type ContentBlock = TextContent | ImageContent | AudioContent | ResourceLink | EmbeddedResource;
```
so the deletion of those individual variants should not be a cause of
great concern.
Similarly, we have the following change in
`codex-rs/app-server-protocol/schema/typescript/Tool.ts`:
```
- export type Tool = { annotations?: ToolAnnotations, description?: string, inputSchema: ToolInputSchema, name: string, outputSchema?: ToolOutputSchema, title?: string, };
+ export type Tool = { name: string, title?: string, description?: string, inputSchema: JsonValue, outputSchema?: JsonValue, annotations?: JsonValue, icons?: Array<JsonValue>, _meta?: JsonValue, };
```
so:
- `annotations?: ToolAnnotations` ➡️ `JsonValue`
- `inputSchema: ToolInputSchema` ➡️ `JsonValue`
- `outputSchema?: ToolOutputSchema` ➡️ `JsonValue`
and two new fields: `icons?: Array<JsonValue>, _meta?: JsonValue`
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/10349).
* #10357
* __->__ #10349
* #10356
This commit is contained in:
@@ -10,22 +10,9 @@ use anyhow::Result;
|
||||
use anyhow::anyhow;
|
||||
use futures::FutureExt;
|
||||
use futures::future::BoxFuture;
|
||||
use mcp_types::CallToolRequestParams;
|
||||
use mcp_types::CallToolResult;
|
||||
use mcp_types::InitializeRequestParams;
|
||||
use mcp_types::InitializeResult;
|
||||
use mcp_types::ListResourceTemplatesRequestParams;
|
||||
use mcp_types::ListResourceTemplatesResult;
|
||||
use mcp_types::ListResourcesRequestParams;
|
||||
use mcp_types::ListResourcesResult;
|
||||
use mcp_types::ListToolsRequestParams;
|
||||
use mcp_types::ListToolsResult;
|
||||
use mcp_types::ReadResourceRequestParams;
|
||||
use mcp_types::ReadResourceResult;
|
||||
use mcp_types::RequestId;
|
||||
use mcp_types::Tool;
|
||||
use reqwest::header::HeaderMap;
|
||||
use rmcp::model::CallToolRequestParam;
|
||||
use rmcp::model::CallToolResult;
|
||||
use rmcp::model::ClientNotification;
|
||||
use rmcp::model::ClientRequest;
|
||||
use rmcp::model::CreateElicitationRequestParam;
|
||||
@@ -34,9 +21,16 @@ use rmcp::model::CustomNotification;
|
||||
use rmcp::model::CustomRequest;
|
||||
use rmcp::model::Extensions;
|
||||
use rmcp::model::InitializeRequestParam;
|
||||
use rmcp::model::InitializeResult;
|
||||
use rmcp::model::ListResourceTemplatesResult;
|
||||
use rmcp::model::ListResourcesResult;
|
||||
use rmcp::model::ListToolsResult;
|
||||
use rmcp::model::PaginatedRequestParam;
|
||||
use rmcp::model::ReadResourceRequestParam;
|
||||
use rmcp::model::ReadResourceResult;
|
||||
use rmcp::model::RequestId;
|
||||
use rmcp::model::ServerResult;
|
||||
use rmcp::model::Tool;
|
||||
use rmcp::service::RoleClient;
|
||||
use rmcp::service::RunningService;
|
||||
use rmcp::service::{self};
|
||||
@@ -62,9 +56,6 @@ use crate::oauth::StoredOAuthTokens;
|
||||
use crate::program_resolver;
|
||||
use crate::utils::apply_default_headers;
|
||||
use crate::utils::build_default_headers;
|
||||
use crate::utils::convert_call_tool_result;
|
||||
use crate::utils::convert_to_mcp;
|
||||
use crate::utils::convert_to_rmcp;
|
||||
use crate::utils::create_env_for_mcp_server;
|
||||
use crate::utils::run_with_timeout;
|
||||
|
||||
@@ -229,12 +220,11 @@ impl RmcpClient {
|
||||
/// https://modelcontextprotocol.io/specification/2025-06-18/basic/lifecycle#initialization
|
||||
pub async fn initialize(
|
||||
&self,
|
||||
params: InitializeRequestParams,
|
||||
params: InitializeRequestParam,
|
||||
timeout: Option<Duration>,
|
||||
send_elicitation: SendElicitation,
|
||||
) -> Result<InitializeResult> {
|
||||
let rmcp_params: InitializeRequestParam = convert_to_rmcp(params.clone())?;
|
||||
let client_handler = LoggingClientHandler::new(rmcp_params, send_elicitation);
|
||||
let client_handler = LoggingClientHandler::new(params.clone(), send_elicitation);
|
||||
|
||||
let (transport, oauth_persistor) = {
|
||||
let mut guard = self.state.lock().await;
|
||||
@@ -275,7 +265,7 @@ impl RmcpClient {
|
||||
.peer()
|
||||
.peer_info()
|
||||
.ok_or_else(|| anyhow!("handshake succeeded but server info was missing"))?;
|
||||
let initialize_result = convert_to_mcp(initialize_result_rmcp)?;
|
||||
let initialize_result = initialize_result_rmcp.clone();
|
||||
|
||||
{
|
||||
let mut guard = self.state.lock().await;
|
||||
@@ -296,28 +286,26 @@ impl RmcpClient {
|
||||
|
||||
pub async fn list_tools(
|
||||
&self,
|
||||
params: Option<ListToolsRequestParams>,
|
||||
params: Option<PaginatedRequestParam>,
|
||||
timeout: Option<Duration>,
|
||||
) -> Result<ListToolsResult> {
|
||||
let result = self.list_tools_with_connector_ids(params, timeout).await?;
|
||||
Ok(ListToolsResult {
|
||||
next_cursor: result.next_cursor,
|
||||
tools: result.tools.into_iter().map(|tool| tool.tool).collect(),
|
||||
})
|
||||
self.refresh_oauth_if_needed().await;
|
||||
let service = self.service().await?;
|
||||
let fut = service.list_tools(params);
|
||||
let result = run_with_timeout(fut, timeout, "tools/list").await?;
|
||||
self.persist_oauth_tokens().await;
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub async fn list_tools_with_connector_ids(
|
||||
&self,
|
||||
params: Option<ListToolsRequestParams>,
|
||||
params: Option<PaginatedRequestParam>,
|
||||
timeout: Option<Duration>,
|
||||
) -> Result<ListToolsWithConnectorIdResult> {
|
||||
self.refresh_oauth_if_needed().await;
|
||||
let service = self.service().await?;
|
||||
let rmcp_params = params
|
||||
.map(convert_to_rmcp::<_, PaginatedRequestParam>)
|
||||
.transpose()?;
|
||||
|
||||
let fut = service.list_tools(rmcp_params);
|
||||
let fut = service.list_tools(params);
|
||||
let result = run_with_timeout(fut, timeout, "tools/list").await?;
|
||||
let tools = result
|
||||
.tools
|
||||
@@ -327,7 +315,6 @@ impl RmcpClient {
|
||||
let connector_id = Self::meta_string(meta, "connector_id");
|
||||
let connector_name = Self::meta_string(meta, "connector_name")
|
||||
.or_else(|| Self::meta_string(meta, "connector_display_name"));
|
||||
let tool = convert_to_mcp(tool)?;
|
||||
Ok(ToolWithConnectorId {
|
||||
tool,
|
||||
connector_id,
|
||||
@@ -352,53 +339,43 @@ impl RmcpClient {
|
||||
|
||||
pub async fn list_resources(
|
||||
&self,
|
||||
params: Option<ListResourcesRequestParams>,
|
||||
params: Option<PaginatedRequestParam>,
|
||||
timeout: Option<Duration>,
|
||||
) -> Result<ListResourcesResult> {
|
||||
self.refresh_oauth_if_needed().await;
|
||||
let service = self.service().await?;
|
||||
let rmcp_params = params
|
||||
.map(convert_to_rmcp::<_, PaginatedRequestParam>)
|
||||
.transpose()?;
|
||||
|
||||
let fut = service.list_resources(rmcp_params);
|
||||
let fut = service.list_resources(params);
|
||||
let result = run_with_timeout(fut, timeout, "resources/list").await?;
|
||||
let converted = convert_to_mcp(result)?;
|
||||
self.persist_oauth_tokens().await;
|
||||
Ok(converted)
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub async fn list_resource_templates(
|
||||
&self,
|
||||
params: Option<ListResourceTemplatesRequestParams>,
|
||||
params: Option<PaginatedRequestParam>,
|
||||
timeout: Option<Duration>,
|
||||
) -> Result<ListResourceTemplatesResult> {
|
||||
self.refresh_oauth_if_needed().await;
|
||||
let service = self.service().await?;
|
||||
let rmcp_params = params
|
||||
.map(convert_to_rmcp::<_, PaginatedRequestParam>)
|
||||
.transpose()?;
|
||||
|
||||
let fut = service.list_resource_templates(rmcp_params);
|
||||
let fut = service.list_resource_templates(params);
|
||||
let result = run_with_timeout(fut, timeout, "resources/templates/list").await?;
|
||||
let converted = convert_to_mcp(result)?;
|
||||
self.persist_oauth_tokens().await;
|
||||
Ok(converted)
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub async fn read_resource(
|
||||
&self,
|
||||
params: ReadResourceRequestParams,
|
||||
params: ReadResourceRequestParam,
|
||||
timeout: Option<Duration>,
|
||||
) -> Result<ReadResourceResult> {
|
||||
self.refresh_oauth_if_needed().await;
|
||||
let service = self.service().await?;
|
||||
let rmcp_params: ReadResourceRequestParam = convert_to_rmcp(params)?;
|
||||
let fut = service.read_resource(rmcp_params);
|
||||
let fut = service.read_resource(params);
|
||||
let result = run_with_timeout(fut, timeout, "resources/read").await?;
|
||||
let converted = convert_to_mcp(result)?;
|
||||
self.persist_oauth_tokens().await;
|
||||
Ok(converted)
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub async fn call_tool(
|
||||
@@ -409,13 +386,23 @@ impl RmcpClient {
|
||||
) -> Result<CallToolResult> {
|
||||
self.refresh_oauth_if_needed().await;
|
||||
let service = self.service().await?;
|
||||
let params = CallToolRequestParams { arguments, name };
|
||||
let rmcp_params: CallToolRequestParam = convert_to_rmcp(params)?;
|
||||
let arguments = match arguments {
|
||||
Some(Value::Object(map)) => Some(map),
|
||||
Some(other) => {
|
||||
return Err(anyhow!(
|
||||
"MCP tool arguments must be a JSON object, got {other}"
|
||||
));
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
let rmcp_params = CallToolRequestParam {
|
||||
name: name.into(),
|
||||
arguments,
|
||||
};
|
||||
let fut = service.call_tool(rmcp_params);
|
||||
let rmcp_result = run_with_timeout(fut, timeout, "tools/call").await?;
|
||||
let converted = convert_call_tool_result(rmcp_result)?;
|
||||
let result = run_with_timeout(fut, timeout, "tools/call").await?;
|
||||
self.persist_oauth_tokens().await;
|
||||
Ok(converted)
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub async fn send_custom_notification(
|
||||
|
||||
Reference in New Issue
Block a user