## Summary
`cargo test` has entails both running standard Rust tests and doctests.
It turns out that the doctest discovery is fairly slow, and it's a cost
you pay even for crates that don't include any doctests.
This PR disables doctests with `doctest = false` for crates that lack
any doctests.
For the collection of crates below, this speeds up test execution by
>4x.
E.g., before this PR:
```
Benchmark 1: cargo test -p codex-utils-absolute-path -p codex-utils-cache -p codex-utils-cli -p codex-utils-home-dir -p codex-utils-output-truncation -p codex-utils-path -p codex-utils-string -p codex-utils-template -p codex-utils-elapsed -p codex-utils-json-to-toml
Time (mean ± σ): 1.849 s ± 4.455 s [User: 0.752 s, System: 1.367 s]
Range (min … max): 0.418 s … 14.529 s 10 runs
```
And after:
```
Benchmark 1: cargo test -p codex-utils-absolute-path -p codex-utils-cache -p codex-utils-cli -p codex-utils-home-dir -p codex-utils-output-truncation -p codex-utils-path -p codex-utils-string -p codex-utils-template -p codex-utils-elapsed -p codex-utils-json-to-toml
Time (mean ± σ): 428.6 ms ± 6.9 ms [User: 187.7 ms, System: 219.7 ms]
Range (min … max): 418.0 ms … 436.8 ms 10 runs
```
For a single crate, with >2x speedup, before:
```
Benchmark 1: cargo test -p codex-utils-string
Time (mean ± σ): 491.1 ms ± 9.0 ms [User: 229.8 ms, System: 234.9 ms]
Range (min … max): 480.9 ms … 512.0 ms 10 runs
```
And after:
```
Benchmark 1: cargo test -p codex-utils-string
Time (mean ± σ): 213.9 ms ± 4.3 ms [User: 112.8 ms, System: 84.0 ms]
Range (min … max): 206.8 ms … 221.0 ms 13 runs
```
Co-authored-by: Codex <noreply@openai.com>
## DISCLAIMER
This is experimental and no production service must rely on this
## Why
Built-in MCPs are product-owned runtime capabilities, but they were
previously flattened into the same config-backed stdio path as
user-configured servers. That made them depend on a hidden `codex
builtin-mcp` re-exec path, exposed them through config-oriented CLI
flows, and erased distinctions the runtime needs to preserve—most
notably whether an MCP call should count as external context for
memory-mode pollution.
## What changed
- Model product-owned built-ins separately from config-backed MCP
servers via `BuiltinMcpServer` and `EffectiveMcpServer`.
- Launch built-ins in process through a reusable async transport instead
of the hidden `builtin-mcp` stdio subcommand.
- Keep config-oriented CLI operations such as `codex mcp
list/get/login/logout` scoped to configured servers, while merging
built-ins only into the effective runtime server set.
- Retain server metadata after launch so parallel-tool support and
context classification come from the live server set; built-in
`memories` is now classified as local Codex state rather than external
context.
## Test plan
- `cargo test -p codex-mcp`
- `cargo test -p codex-core --test suite
builtin_memories_mcp_call_does_not_mark_thread_memory_mode_polluted_when_configured`
---------
Co-authored-by: Codex <noreply@openai.com>
## Why
Memory search currently treats separators literally, so callers need to
know whether a stored term uses spaces, hyphens, or no separators at
all. That makes recall brittle for terms such as `MultiAgentV2` vs.
`multi agent v2` and `cold-resume` vs. `cold resume`.
## What changed
- Add an opt-in `normalized` mode to memory search that removes
non-alphanumeric separators after any requested case folding.
- Thread the new flag through the MCP `search` tool into the local
backend while keeping existing literal matching as the default.
- Reject queries that normalize to an empty string, and add regression
coverage for both normalized matching and that validation path.
## Testing
- `cargo test -p codex-memories-mcp`
## Why
Memory search currently supports either independent substring matches or
requiring every query to appear on the same line. That is too
restrictive for memory files where related terms often land on nearby
lines in the same note or bullet block.
## What changed
- Replace the old `all` match mode with explicit tagged modes:
`all_on_same_line` and `all_within_lines { line_count }`.
- Add windowed matching in `codex-rs/memories/mcp/src/local.rs` so
callers can require every query to appear within a bounded line range
while returning only the minimal qualifying windows.
- Reject invalid zero-width windows and update the MCP tool description
plus argument parsing to expose the new mode.
- Add coverage for same-line matching, windowed matching, and invalid
`line_count` input.
## Verification
- Added targeted coverage in `codex-rs/memories/mcp/src/local_tests.rs`
for `search_supports_all_within_lines_match_mode` and
`search_rejects_zero_line_window`.
- Added server-side parsing coverage in
`codex-rs/memories/mcp/src/server.rs` for
`search_args_accept_windowed_all_match_mode`.
## Why
The local memories root can contain implementation details such as
`.git` plus incidental OS metadata like `.DS_Store`. Those entries are
not authored memory content, so the memories MCP should keep them
invisible instead of exposing them through normal discovery or direct
lookup.
Only for local implementation ofc
## What changed
- Return `NotFound` for scoped `list`, `read`, and `search` requests
that include a hidden path component.
- Skip hidden files and directories while listing a directory or
recursively searching the memories tree.
- Add regression coverage for hidden files, hidden directories, and
hidden scoped requests across `list`, `read`, and `search`.
## Testing
- Added focused regression tests in `memories/mcp/src/local_tests.rs`
covering hidden-path behavior across the affected APIs.
## Why
The local memories MCP backend only rejected symlinks after resolving
the final path. That left room for scoped requests like
`skills/secret.md` to walk through a symlinked ancestor directory and
escape the configured memories root.
This change also makes missing scoped paths fail explicitly instead of
looking like an empty `list` / `search` result or a `NotFile` read
error.
## What Changed
- walk each scoped path component in
`LocalMemoriesBackend::resolve_scoped_path` and reject symlinked
ancestors before accessing the target
- reject scoped paths that traverse through a non-directory intermediate
component
- add a `NotFound` backend error for missing `read`, `list`, and
`search` paths and map it through the MCP server error conversion
- add coverage for missing paths and symlinked ancestor directories in
`codex-rs/memories/mcp/src/local_tests.rs`
## Testing
- added unit coverage in `codex-rs/memories/mcp/src/local_tests.rs` for
missing paths and symlinked ancestor directories across `read`, `list`,
and `search`
## Why
The memories MCP server currently keeps handwritten JSON Schema beside
the Rust types that actually serialize and deserialize the tool
payloads:
[`schema.rs`](2f5c06a29c/codex-rs/memories/mcp/src/schema.rs (L4-L133)),
[`server.rs`](2f5c06a29c/codex-rs/memories/mcp/src/server.rs (L44-L75)),
and
[`backend.rs`](2f5c06a29c/codex-rs/memories/mcp/src/backend.rs (L41-L117)).
That duplicates the tool contract and makes schema drift easier as the
API evolves.
## What changed
- derive `JsonSchema` for the memories tool arguments, responses, and
nested response types
- replace the handwritten schema builders with shared `schemars`
generation
- preserve the existing wire shape while generating schemas, including
nullable output `Option` fields and non-nullable optional input fields
- wire the `list`, `read`, and `search` tools to the generated schemas
## Verification
- CI pending
## Why
The memories MCP `search` tool only accepts a single substring today,
which makes it hard for clients to express combined queries or explain
why a line matched. This change adds the richer search shape needed for
the next client iteration while keeping the legacy single-`query` call
working.
## What changed
- accept either the legacy `query` field or a new `queries` array, plus
`match_mode: any|all`
- teach the local memories backend to evaluate multi-query line matches
and return `matched_queries` on each hit
- update the MCP input/output schema and add coverage for parser
behavior, ordering, pagination, case sensitivity, and match modes
## Testing
- added unit coverage in `memories/mcp/src/local_tests.rs` and
`memories/mcp/src/server.rs`
## Why
The paginated memories MCP `search` tool still returned only the
matching line text, which made it harder for clients to present useful
search results or decide whether they needed to follow up with a
separate `read` call. Adding a small amount of surrounding context makes
individual hits much more usable while keeping the search response
deterministic and line-addressable.
## What changed
- add an optional `context_lines` search argument and thread it through
the MCP server into the local memories backend
- change search matches to return the matched `line_number` plus a
`start_line_number` and multi-line `content` block for the requested
context window
- update the search tool schema and description to document the new
request/response shape
- extend the local backend tests to cover zero-context matches,
contextual results, pagination, and invalid cursors that point past the
end of the result set
## Testing
- Added targeted unit coverage in `memories/mcp/src/local_tests.rs`
- GitHub Actions are running for the branch
---------
Co-authored-by: Codex <noreply@openai.com>
## Why
The memories MCP `search` tool previously stopped once it hit
`max_results`, so callers could tell there were more matches via
`truncated` but had no way to fetch the rest of the result set. That
made large searches awkward for clients that need to keep paging through
a stable, deterministic view of the matches.
## What changed
- add an optional `cursor` field to `SearchMemoriesRequest` / tool input
and return `next_cursor` in `SearchMemoriesResponse`
- update the MCP schemas and tool wiring so clients can request
subsequent pages explicitly
- change the local memories backend to collect and sort the full scoped
match list, then slice the requested page and reject invalid cursors
- add unit coverage for paginated search results and invalid cursor
handling in `memories/mcp/src/local_tests.rs`
## Testing
- Added targeted unit coverage in `memories/mcp/src/local_tests.rs`
- GitHub Actions are running for the branch
## Why
The memories MCP `list` tool should behave like a directory listing, not
a recursive tree walk. Recursive results make pagination harder to
reason about, return unexpectedly deep paths for scoped requests, and no
longer match the intended tool contract.
## What Changed
- Changed the local memories backend so `list` returns only the
immediate children of the requested path.
- Preserved file-scoped requests by returning the file itself, and
missing paths by returning an empty result.
- Updated cursor handling to paginate over the shallow sibling set and
reject cursors past the available results.
- Updated the MCP tool description to say it lists immediate files and
directories under a path.
- Reworked the local backend tests to cover shallow top-level listing,
shallow scoped listing, sibling ordering, and pagination.
## Testing
- `cargo test -p codex-memories-mcp`
## Why
Large memories trees do not fit well into a single MCP `list` response.
This change makes the memories MCP server page `list` results so callers
can continue walking the tree without overfetching or relying on
ambiguous truncation.
## What changed
- add an optional `cursor` input to the memories MCP `list` API and
return `next_cursor` alongside `truncated` in the response
- paginate recursive local-memory traversal while preserving
lexicographic path order across directories
- reject malformed and out-of-range cursors as invalid MCP requests
- update the server/schema wiring and add coverage for pagination,
ordering, and cursor validation in `memories/mcp/src/local_tests.rs`
## Testing
- `cargo test -p codex-memories-mcp`
## Why
The memories MCP `read` tool already supports `line_offset`, but it
cannot return a bounded line range. That makes it awkward to page
through large memory files or request a small slice without relying on
token truncation.
## What changed
- add an optional `max_lines` parameter to the memories MCP `read` tool
schema and request parsing
- cap local backend reads to the requested number of lines before token
truncation
- treat `max_lines = 0` as an invalid request and surface it as
`invalid_params`
- add backend tests for bounded reads and invalid line request
validation
## Testing
- added coverage in `memories/mcp/src/local_tests.rs` for `max_lines`
reads and invalid `max_lines` / `line_offset` requests
## Why
Memory clients sometimes need to continue reading a file from a known
line instead of starting over from the top. Adding a line offset to the
`read` MCP keeps that resume logic simple and avoids re-reading
already-consumed content.
## What changed
- Added an optional `line_offset` argument to the memory `read` tool,
defaulting to `1`.
- Read content starting at the requested 1-indexed line before token
truncation, and return `start_line_number` in the response.
- Treat invalid offsets as invalid params errors and cover the new
behavior in `codex-rs/memories/mcp/src/local_tests.rs`.
## Testing
- Added unit tests for reading from a non-default starting line.
- Added unit tests for rejecting `0` and past-end line offsets.