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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.9.0] - 2026-07-27

### Security

- **`mcp-execution-core`**: added `redact_urls_in_text`, which scans arbitrary already-assembled
Expand Down Expand Up @@ -3106,7 +3108,8 @@ Phase 6 (Optimization) is currently OPTIONAL and DEFERRED because:

---

[Unreleased]: https://github.com/bug-ops/mcp-execution/compare/v0.8.0...HEAD
[Unreleased]: https://github.com/bug-ops/mcp-execution/compare/v0.9.0...HEAD
[0.9.0]: https://github.com/bug-ops/mcp-execution/compare/v0.8.0...v0.9.0
[0.8.0]: https://github.com/bug-ops/mcp-execution/compare/v0.7.2...v0.8.0
[0.7.2]: https://github.com/bug-ops/mcp-execution/compare/v0.7.1...v0.7.2
[0.7.1]: https://github.com/bug-ops/mcp-execution/compare/v0.7.0...v0.7.1
Expand Down
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ This project adheres to a code of conduct that we expect all contributors to fol
### Prerequisites

**Required:**
- Rust 1.89 or higher (Edition 2024)
- Rust 1.91 or higher (Edition 2024)
- Cargo (comes with Rust)
- Node.js 18+ (for testing generated TypeScript code)
- Git
Expand Down Expand Up @@ -429,7 +429,7 @@ All PRs must pass:
- Tests on Linux, macOS, Windows
- Tests on stable and beta Rust
- Code coverage upload to codecov
- MSRV check (Rust 1.89)
- MSRV check (Rust 1.91)
- Security audit (cargo-deny)
- Documentation build
- Benchmark build
Expand Down
14 changes: 7 additions & 7 deletions Cargo.lock

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

14 changes: 7 additions & 7 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ default-members = [ "crates/*" ]
resolver = "3"

[workspace.package]
version = "0.8.0"
version = "0.9.0"
edition = "2024"
rust-version = "1.91"
authors = ["Andrei G <k05h31@gmail.com>"]
Expand All @@ -26,12 +26,12 @@ dirs = "6.0"
futures-util = "0.3"
handlebars = "6.4"
http = "1"
mcp-execution-codegen = { path = "crates/mcp-codegen", version = "0.8.0" }
mcp-execution-core = { path = "crates/mcp-core", version = "0.8.0" }
mcp-execution-files = { path = "crates/mcp-files", version = "0.8.0" }
mcp-execution-introspector = { path = "crates/mcp-introspector", version = "0.8.0" }
mcp-execution-server = { path = "crates/mcp-server", version = "0.8.0" }
mcp-execution-skill = { path = "crates/mcp-skill", version = "0.8.0" }
mcp-execution-codegen = { path = "crates/mcp-codegen", version = "0.9.0" }
mcp-execution-core = { path = "crates/mcp-core", version = "0.9.0" }
mcp-execution-files = { path = "crates/mcp-files", version = "0.9.0" }
mcp-execution-introspector = { path = "crates/mcp-introspector", version = "0.9.0" }
mcp-execution-server = { path = "crates/mcp-server", version = "0.9.0" }
mcp-execution-skill = { path = "crates/mcp-skill", version = "0.9.0" }
rayon = "1.12"
regex = "1.12"
rmcp = "2.1"
Expand Down
12 changes: 9 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,11 +82,14 @@ Or add to `Cargo.toml`:

```toml
[dependencies]
mcp-execution-core = "0.8"
mcp-execution-introspector = "0.8"
mcp-execution-codegen = "0.8"
mcp-execution-core = "0.9"
mcp-execution-introspector = "0.9"
mcp-execution-codegen = "0.9"
```

> [!WARNING]
> The standalone `mcp-execution-runtime` npm package has been removed as of v0.9.0. If you were using it, switch to the TypeScript bridge generated by `mcp-execution-cli generate`, which is actively maintained and receives all security updates.

</details>

> [!IMPORTANT]
Expand Down Expand Up @@ -184,6 +187,9 @@ mcp-execution-cli completions bash
> [!TIP]
> Use `--from-config` to load server settings from `~/.claude/mcp.json` instead of manual arguments.

> [!IMPORTANT]
> **v0.9 breaking change:** `generate <command> --http <url>` and `introspect <command> --http <url>` now require either `--from-config`, a positional command, or `--http`/`--sse`, not a combination. If you were combining a positional command with `--http` or `--sse`, use one or the other exclusively.

## Performance

| Metric | Target | Achieved |
Expand Down
5 changes: 4 additions & 1 deletion SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@

| Version | Supported |
| ------- | ------------------ |
| 0.6.x | :white_check_mark: |
| 0.9.x | :white_check_mark: |
| 0.8.x | :white_check_mark: |
| 0.7.x | :x: |
| 0.6.x | :x: |

## Reporting a Vulnerability

Expand Down
2 changes: 1 addition & 1 deletion crates/mcp-codegen/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Progressive loading TypeScript code generation for MCP tools. Achieves **98% tok

```toml
[dependencies]
mcp-execution-codegen = "0.8"
mcp-execution-codegen = "0.9"
```

Or with cargo-add:
Expand Down
2 changes: 1 addition & 1 deletion crates/mcp-core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Foundation types, traits, and error handling for MCP Code Execution.

```toml
[dependencies]
mcp-execution-core = "0.8"
mcp-execution-core = "0.9"
```

Or with cargo-add:
Expand Down
2 changes: 1 addition & 1 deletion crates/mcp-files/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ In-memory virtual filesystem for MCP tools organization and export.

```toml
[dependencies]
mcp-execution-files = "0.8"
mcp-execution-files = "0.9"
```

Or with cargo-add:
Expand Down
2 changes: 1 addition & 1 deletion crates/mcp-introspector/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ MCP server introspection using the official [rmcp](https://docs.rs/rmcp) SDK.

```toml
[dependencies]
mcp-execution-introspector = "0.8"
mcp-execution-introspector = "0.9"
```

Or with cargo-add:
Expand Down
2 changes: 1 addition & 1 deletion crates/mcp-skill/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Or add to your `Cargo.toml`:

```toml
[dependencies]
mcp-execution-skill = "0.8"
mcp-execution-skill = "0.9"
```

> [!IMPORTANT]
Expand Down
127 changes: 115 additions & 12 deletions specs/cli/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,17 @@ do, without going through the MCP-server layer at all: read
server, generate progressive-loading TypeScript, render SKILL.md, and
manage/validate server configuration and the local runtime environment.

The crate is a library (`src/lib.rs`) with a thin binary entry point
(`src/main.rs`: parse `Cli`, call `runner::init_logging`, call
`runner::execute_command`, then `std::process::exit` on the returned
`ExitCode`). `lib.rs` exposes `pub mod cli`, `pub mod runner`, `pub mod
actions`, `pub mod commands`, and `pub mod formatters` (plus a re-exported
`ServerAction`) — `cli::{Cli, Commands}` and `runner`'s
command-execution/exit-code-classification entry points are genuine public
library API, not merely an implementation detail of the compiled binary, so
an external crate or integration test can drive command parsing/execution
in-process (issue #188).

## 2. Subcommands (`Commands` enum, `src/cli.rs`)

| Subcommand | Purpose | Key flags |
Expand All @@ -48,6 +59,15 @@ manage/validate server configuration and the local runtime environment.

Global flags on `Cli` (apply to every subcommand): `-v/--verbose` (DEBUG log
level), `--format {json,text,pretty}` (default `pretty`, case-insensitive).
`--format` is typed as `mcp_execution_core::cli::OutputFormat` directly via
clap's `PossibleValuesParser` (mapped through `OutputFormat::from_str`),
not a raw `String` parsed post-hoc: `--help` lists the three possible
values, an invalid value (e.g. `--format xml`) is rejected by clap itself
before any command handler runs (routed through the same
`runner::report_and_classify`/`ExitCode::INVALID_INPUT` path as a
handler-level failure — see [[#5. runner.rs]]), and
`completions`-generated shell scripts complete `--format` from the same
three values (issue #206).

`introspect`/`generate` flatten a shared `ServerFlags` (`#[derive(Args)]`,
private fields, `cli.rs`) holding `from_config`/`server`/`args`/`env`/`cwd`/
Expand Down Expand Up @@ -226,29 +246,65 @@ command.

`run(source: ServerSource, name: Option<String>, output_dir: Option<PathBuf>, dry_run: bool, output_format) -> Result<ExitCode>`:

1. `resolve_server_config` → `discover_server_info` (introspects; applies
`--name` override to `ServerInfo.id` if given, so generated
directory/literals use the custom name rather than the raw command).
1. `resolve_server_config` → `discover_server_info`: if `--name` is given,
it is validated via `mcp_execution_skill::validate_server_id` **before**
the connection attempt and only overrides `ServerInfo.id` once valid —
an invalid `--name` (traversal shape, absolute path, or simply outside
`validate_server_id`'s `[a-z0-9-]` charset, e.g. `My Server!`) is now a
hard `INVALID_INPUT` error instead of being silently slugified into a
different id. **Breaking behavior change** from the pre-#311 CLI, which
constructed `ServerId::new(custom_name)` directly with no validation.
2. If the server has zero tools: logs a warning and returns
`ExitCode::SUCCESS` (not an error) without generating anything.
3. `ProgressiveGenerator::generate` (uncategorized — no LLM step in this
3. `resolve_server_dir_name` turns `server_info.id` into the directory
name, re-validating it via `validate_server_id` regardless of which arm
produced the id: `derive_server_id_from_path_or_name` (stdio command),
`derive_server_id_from_url` (http/sse — see [[#3. common.rs]]), or the
already-validated `--name` override. This check is a redundant backstop
for those three arms, but the **sole** enforcement point when the id
came straight from an unvalidated `--from-config` `mcp.json` key with
no `--name` override —
the error message differs accordingly (names `mcp.json` and suggests a
ready-to-use `--name` slug vs. framing the failure as an internal error
for the other arms, since reaching it there would mean one of their own
checks has a bug) (issue #311).
4. `ProgressiveGenerator::generate` (uncategorized — no LLM step in this
path, unlike `mcp-server`'s `save_categorized_tools`).
4. `resolve_base_dir(output_dir)` — defaults to `~/.claude/servers`.
5. **`--dry-run`**: renders a `DryRunResult` (`FilePreview` per file: path +
5. `resolve_base_dir(output_dir)` — defaults to `~/.claude/servers`.
6. **`--dry-run`**: renders a `DryRunResult` (`FilePreview` per file: path +
size, human-readable `format_size`) **without writing anything to
disk** — the only place in this workspace that previews generated
output without ever touching the filesystem.
6. Otherwise: `FilesBuilder::from_generated_code(code, "/").build_and_export(&base_dir)`
— see [[../files/spec#FilesBuilder::build_and_export]] for the
per-top-level-group atomicity this implies (a re-run with fewer tools
deletes stale tool files in that server's own directory, but never
touches sibling servers under the same `base_dir`).
7. Success output names the required post-export step
7. Otherwise: `FilesBuilder::from_generated_code(code, "/").build()`, then
`FileSystem::export_to_filesystem_with_options(output_path,
&ExportOptions::new().with_confine_to(base_dir))` — **not**
`FilesBuilder::build_and_export`, which treats its target as a
shared multi-server root; `generate` instead publishes one server's
whole directory (`output_path = base_dir.join(server_dir_name)`) per
call, getting `export_to_filesystem_with_options`'s own per-call atomic
staging/swap (a re-run with fewer tools deletes stale tool files in that
server's own directory, but never touches sibling servers under the
same `base_dir`). `with_confine_to(base_dir)` is a second,
defense-in-depth layer behind the id-sanitization in step 3: a future
caller that skipped that sanitization fails loudly instead of writing
outside `base_dir`. See [[../files/spec#5. Atomic Export
(`export_to_filesystem_with_options`)]] and
[[../files/spec#8. Cross-Crate Contracts]].
8. Success output names the required post-export step
(`NPM_INSTALL_HINT`: run `npm install` before type-checking the
generated package — issue #257's fix, since the generated `package.json`
declares `@types/node` as a `devDependency` that isn't installed by
`generate` itself).

`Text`/`Pretty` output (both the success report and the `--dry-run`
preview) escapes the MCP server's handshake-supplied `server_name` via
`formatters::escape_display` before interpolating it into a freeform
`"Server: {name} ({id})"` line — always JSON-quoting the value, even when
it contains no control characters, so a benign name like `Test Server`
renders as `Server: "Test Server" (id)`, not just a malicious one (issue
#299). `Json` output is unaffected, since `serde_json` already escapes
string values.

## 8. `skill` Command (`commands/skill.rs`)

`run(server, servers_dir, output_path, skill_name, hints, overwrite, output_format) -> Result<ExitCode>`:
Expand Down Expand Up @@ -287,6 +343,53 @@ single source of truth:
handshake (the entry's own full configured timeout applies), the
authoritative single-target check.

`ServerEntry.status`/`ServerInfo.status` are typed as a closed
`ServerStatus` enum (`Available`/`Unavailable`,
`#[serde(rename_all = "lowercase")]`), not a bare `String` — a **breaking**
type change from the pre-#318 CLI, though it serializes identically
(`"available"`/`"unavailable"`) so `--format json` consumers are
unaffected.

`info`/`validate` distinguish "entry absent from `mcp.json`" from "entry
present but invalid" via `get_mcp_server_entry` (looks up the raw
`McpServerEntry` only) rather than `get_mcp_server` (which also eagerly ran
`build_core_config`'s security validation — see [[#3. common.rs]] —
making the two cases indistinguishable through one `with_context` "not
found" wrapping):

- `server info` on an entry that is present but fails `build_core_config`
(e.g. an invalid URL scheme) or fails introspection now reports a
structured `ServerInfo` with `"status": "unavailable"` through the
normal `output_format` path — not a raw, unformatted `anyhow` error —
while still returning `ExitCode::ERROR` (issue #305). Only a genuinely
absent entry propagates as `Err` (and thus a raw error report). A known
gap: `ServerInfo` has no field naming *why* the server is unavailable
(invalid config vs. failed handshake).
- `server validate` on a present-but-invalid entry reports the actual
`build_core_config`/precheck failure message in `ValidationResult` (e.g.
`"Server '{name}' has an invalid configuration: {e}"`), not the generic
`"Server not found"` message reserved for a genuinely absent entry (issue
#304).

`build_command_string` (feeds `list`/`info`/`validate` output, printed
unconditionally, never gated behind `--verbose`) redacts the same way
`ServerConfig`'s own `Debug` impl does (issue #346): stdio `command` is
routed through `sanitize_path_for_error` (home directory/username scrub);
stdio `args` are replaced **wholesale** with
`mcp_execution_core::REDACTED_PLACEHOLDER` per entry, rendered as a
space-joined shell-shaped string (`"docker <redacted> <redacted>"`), since
a single argument routinely holds an entire secret with no key/value half
worth preserving — unlike `mcp_execution_core::RedactedItems`'s
Rust-`Debug`-list rendering, which would be awkward to embed in
`--format json` output; http/sse `url` is redacted via `RedactedUrl`
(strips userinfo credentials and any query string, keeps scheme/host/path
readable), falling back to redacting the whole string if it fails to
parse. `validate_command`'s own "URL is not well-formed" precheck message
(`url_precheck_message`, built *before* `build_command_string` runs) is
redacted the same way, closing a gap where a malformed-but-credentialed
URL could leak via the precheck message even though `build_command_string`
itself was already safe (issue #346, S1).

## 10. `setup` Command (`commands/setup.rs`)

Validates the local runtime is ready to execute generated tools:
Expand Down
Loading