Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions codex-rs/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions codex-rs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ members = [
"login",
"codex-mcp",
"mcp-server",
"memories/mcp",
"memories/read",
"memories/write",
"model-provider-info",
Expand Down Expand Up @@ -168,6 +169,7 @@ codex-keyring-store = { path = "keyring-store" }
codex-linux-sandbox = { path = "linux-sandbox" }
codex-lmstudio = { path = "lmstudio" }
codex-login = { path = "login" }
codex-memories-mcp = { path = "memories/mcp" }
codex-memories-read = { path = "memories/read" }
codex-memories-write = { path = "memories/write" }
codex-mcp = { path = "codex-mcp" }
Expand Down Expand Up @@ -461,6 +463,7 @@ unwrap_used = "deny"
[workspace.metadata.cargo-shear]
ignored = [
"codex-agent-graph-store",
"codex-memories-mcp",
"icu_provider",
"openssl-sys",
"codex-utils-readiness",
Expand Down
6 changes: 6 additions & 0 deletions codex-rs/memories/mcp/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
load("//:defs.bzl", "codex_rust_crate")

codex_rust_crate(
name = "mcp",
crate_name = "codex_memories_mcp",
)
30 changes: 30 additions & 0 deletions codex-rs/memories/mcp/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
[package]
edition.workspace = true
license.workspace = true
name = "codex-memories-mcp"
version.workspace = true

[lib]
name = "codex_memories_mcp"
path = "src/lib.rs"

[lints]
workspace = true

[dependencies]
anyhow = { workspace = true }
codex-utils-absolute-path = { workspace = true }
codex-utils-output-truncation = { workspace = true }
rmcp = { workspace = true, default-features = false, features = [
"schemars",
"server",
] }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
thiserror = { workspace = true }
tokio = { workspace = true, features = ["fs", "io-std"] }

[dev-dependencies]
pretty_assertions = { workspace = true }
tempfile = { workspace = true }
tokio = { workspace = true, features = ["fs", "macros"] }
113 changes: 113 additions & 0 deletions codex-rs/memories/mcp/src/backend.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
use serde::Serialize;
use std::future::Future;

pub const DEFAULT_LIST_MAX_RESULTS: usize = 2_000;
pub const MAX_LIST_RESULTS: usize = 2_000;
pub const DEFAULT_SEARCH_MAX_RESULTS: usize = 200;
pub const MAX_SEARCH_RESULTS: usize = 200;
pub const DEFAULT_READ_MAX_TOKENS: usize = 20_000;

/// Storage interface behind the memories MCP tools.
///
/// Implementations should return paths relative to the memory store and enforce
/// their own storage-specific access rules. The local implementation uses the
/// filesystem today; a later implementation can satisfy the same contract from a
/// remote backend.
pub trait MemoriesBackend: Clone + Send + Sync + 'static {
fn list(
&self,
request: ListMemoriesRequest,
) -> impl Future<Output = Result<ListMemoriesResponse, MemoriesBackendError>> + Send;

fn read(
&self,
request: ReadMemoryRequest,
) -> impl Future<Output = Result<ReadMemoryResponse, MemoriesBackendError>> + Send;

fn search(
&self,
request: SearchMemoriesRequest,
) -> impl Future<Output = Result<SearchMemoriesResponse, MemoriesBackendError>> + Send;
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ListMemoriesRequest {
pub path: Option<String>,
pub max_results: usize,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct ListMemoriesResponse {
pub path: Option<String>,
pub entries: Vec<MemoryEntry>,
pub truncated: bool,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ReadMemoryRequest {
pub path: String,
pub max_tokens: usize,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct ReadMemoryResponse {
pub path: String,
pub content: String,
pub truncated: bool,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SearchMemoriesRequest {
pub query: String,
pub path: Option<String>,
pub max_results: usize,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct SearchMemoriesResponse {
pub query: String,
pub path: Option<String>,
pub matches: Vec<MemorySearchMatch>,
pub truncated: bool,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct MemoryEntry {
pub path: String,
pub entry_type: MemoryEntryType,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum MemoryEntryType {
File,
Directory,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct MemorySearchMatch {
pub path: String,
pub line_number: usize,
pub line: String,
}

#[derive(Debug, thiserror::Error)]
pub enum MemoriesBackendError {
#[error("path '{path}' {reason}")]
InvalidPath { path: String, reason: String },
#[error("path '{path}' is not a file")]
NotFile { path: String },
#[error("query must not be empty")]
EmptyQuery,
#[error("I/O error while reading memories: {0}")]
Io(#[from] std::io::Error),
}

impl MemoriesBackendError {
pub fn invalid_path(path: impl Into<String>, reason: impl Into<String>) -> Self {
Self::InvalidPath {
path: path.into(),
reason: reason.into(),
}
}
}
14 changes: 14 additions & 0 deletions codex-rs/memories/mcp/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//! MCP access to Codex memories.
//!
//! This crate only exposes tools for discovering and reading memory files. The
//! policy that tells a model when to use those tools is injected elsewhere.

pub mod backend;
pub mod local;

mod schema;
mod server;

pub use local::LocalMemoriesBackend;
pub use server::MemoriesMcpServer;
pub use server::run_stdio_server;
Loading
Loading