This PR introduces a `codex-utils-cargo-bin` utility crate that wraps/replaces our use of `assert_cmd::Command` and `escargot::CargoBuild`. As you can infer from the introduction of `buck_project_root()` in this PR, I am attempting to make it possible to build Codex under [Buck2](https://buck2.build) as well as `cargo`. With Buck2, I hope to achieve faster incremental local builds (largely due to Buck2's [dice](https://buck2.build/docs/insights_and_knowledge/modern_dice/) build strategy, as well as benefits from its local build daemon) as well as faster CI builds if we invest in remote execution and caching. See https://buck2.build/docs/getting_started/what_is_buck2/#why-use-buck2-key-advantages for more details about the performance advantages of Buck2. Buck2 enforces stronger requirements in terms of build and test isolation. It discourages assumptions about absolute paths (which is key to enabling remote execution). Because the `CARGO_BIN_EXE_*` environment variables that Cargo provides are absolute paths (which `assert_cmd::Command` reads), this is a problem for Buck2, which is why we need this `codex-utils-cargo-bin` utility. My WIP-Buck2 setup sets the `CARGO_BIN_EXE_*` environment variables passed to a `rust_test()` build rule as relative paths. `codex-utils-cargo-bin` will resolve these values to absolute paths, when necessary. --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/openai/codex/pull/8496). * #8498 * __->__ #8496
135 lines
4.1 KiB
Rust
135 lines
4.1 KiB
Rust
use std::ffi::OsString;
|
|
use std::path::PathBuf;
|
|
use std::time::Duration;
|
|
|
|
use codex_rmcp_client::ElicitationAction;
|
|
use codex_rmcp_client::ElicitationResponse;
|
|
use codex_rmcp_client::RmcpClient;
|
|
use codex_utils_cargo_bin::CargoBinError;
|
|
use futures::FutureExt as _;
|
|
use mcp_types::ClientCapabilities;
|
|
use mcp_types::Implementation;
|
|
use mcp_types::InitializeRequestParams;
|
|
use mcp_types::ListResourceTemplatesResult;
|
|
use mcp_types::ReadResourceRequestParams;
|
|
use mcp_types::ReadResourceResultContents;
|
|
use mcp_types::Resource;
|
|
use mcp_types::ResourceTemplate;
|
|
use mcp_types::TextResourceContents;
|
|
use serde_json::json;
|
|
|
|
const RESOURCE_URI: &str = "memo://codex/example-note";
|
|
|
|
fn stdio_server_bin() -> Result<PathBuf, CargoBinError> {
|
|
codex_utils_cargo_bin::cargo_bin("test_stdio_server")
|
|
}
|
|
|
|
fn init_params() -> InitializeRequestParams {
|
|
InitializeRequestParams {
|
|
capabilities: ClientCapabilities {
|
|
experimental: None,
|
|
roots: None,
|
|
sampling: None,
|
|
elicitation: Some(json!({})),
|
|
},
|
|
client_info: Implementation {
|
|
name: "codex-test".into(),
|
|
version: "0.0.0-test".into(),
|
|
title: Some("Codex rmcp resource test".into()),
|
|
user_agent: None,
|
|
},
|
|
protocol_version: mcp_types::MCP_SCHEMA_VERSION.to_string(),
|
|
}
|
|
}
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
|
|
async fn rmcp_client_can_list_and_read_resources() -> anyhow::Result<()> {
|
|
let client = RmcpClient::new_stdio_client(
|
|
stdio_server_bin()?.into(),
|
|
Vec::<OsString>::new(),
|
|
None,
|
|
&[],
|
|
None,
|
|
)
|
|
.await?;
|
|
|
|
client
|
|
.initialize(
|
|
init_params(),
|
|
Some(Duration::from_secs(5)),
|
|
Box::new(|_, _| {
|
|
async {
|
|
Ok(ElicitationResponse {
|
|
action: ElicitationAction::Accept,
|
|
content: Some(json!({})),
|
|
})
|
|
}
|
|
.boxed()
|
|
}),
|
|
)
|
|
.await?;
|
|
|
|
let list = client
|
|
.list_resources(None, Some(Duration::from_secs(5)))
|
|
.await?;
|
|
let memo = list
|
|
.resources
|
|
.iter()
|
|
.find(|resource| resource.uri == RESOURCE_URI)
|
|
.expect("memo resource present");
|
|
assert_eq!(
|
|
memo,
|
|
&Resource {
|
|
annotations: None,
|
|
description: Some("A sample MCP resource exposed for integration tests.".to_string()),
|
|
mime_type: Some("text/plain".to_string()),
|
|
name: "example-note".to_string(),
|
|
size: None,
|
|
title: Some("Example Note".to_string()),
|
|
uri: RESOURCE_URI.to_string(),
|
|
}
|
|
);
|
|
let templates = client
|
|
.list_resource_templates(None, Some(Duration::from_secs(5)))
|
|
.await?;
|
|
assert_eq!(
|
|
templates,
|
|
ListResourceTemplatesResult {
|
|
next_cursor: None,
|
|
resource_templates: vec![ResourceTemplate {
|
|
annotations: None,
|
|
description: Some(
|
|
"Template for memo://codex/{slug} resources used in tests.".to_string()
|
|
),
|
|
mime_type: Some("text/plain".to_string()),
|
|
name: "codex-memo".to_string(),
|
|
title: Some("Codex Memo".to_string()),
|
|
uri_template: "memo://codex/{slug}".to_string(),
|
|
}],
|
|
}
|
|
);
|
|
|
|
let read = client
|
|
.read_resource(
|
|
ReadResourceRequestParams {
|
|
uri: RESOURCE_URI.to_string(),
|
|
},
|
|
Some(Duration::from_secs(5)),
|
|
)
|
|
.await?;
|
|
let ReadResourceResultContents::TextResourceContents(text) =
|
|
read.contents.first().expect("resource contents present")
|
|
else {
|
|
panic!("expected text resource");
|
|
};
|
|
assert_eq!(
|
|
text,
|
|
&TextResourceContents {
|
|
text: "This is a sample MCP resource served by the rmcp test server.".to_string(),
|
|
uri: RESOURCE_URI.to_string(),
|
|
mime_type: Some("text/plain".to_string()),
|
|
}
|
|
);
|
|
|
|
Ok(())
|
|
}
|