Conversation
`ToolKind` is a compile-time enum whose names are `&'static str`, so a tool discovered from an MCP server at runtime has no representation in it. Rather than widen the enum and push dynamic names through every match arm, the MCP client state lives in a parallel runtime layer that the existing tool chain consults without knowing where a tool came from. This commit adds that layer on its own: server configuration with transport validation, JSON Schema to parameter conversion, the exposed-name scheme `mcp_<server>_<tool>`, and a process-wide store that de-duplicates exposed names across servers and remembers why a server contributed no tools. Nothing reaches the layer yet. `connect_all` validates the declarations and installs an empty tool list per server, `call_tool` reports every known tool as not connected, and no configuration section feeds either of them, so the behaviour of the CLI is unchanged. The `rmcp` client is not wired up here. `text::t_fmt` mirrors the copies already living in the CLI and the workspace crate; those are left untouched because consolidating them is a separate change, and the MCP messages need the helper before that happens.
…lKind The prompt builder needs a call template for every tool it lists, and MCP tools have no `ToolKind` to hand it. Passing `&ToolKind` to the parsers also made each of them reach for `ToolKind::parameters()` on their own, which kept the enum's ownership model inside the rendering path. A `ToolTemplate` view carries only what a template needs — the tool name and, per parameter, its name, kind and required flag — and is built either from a `ToolKind` or, later, from a discovered MCP tool. The parsers take the view and stop caring where the tool came from. Parameters are held in a `Vec` rather than a slice because `ToolKind::parameters()` returns an owned `Vec`; a slice would force a temporary to outlive the call. The registry still hands its parser a `&ToolKind`, so this commit alone does not build; the registry call site and the tool set follow in the next change.
The parsers accept only names present in `EnabledToolSet`, so a tool a server exposes is invisible to them — and to the model — until the set learns about it. Two name kinds now share one set: built-in names stay `&'static str` from the compile-time enum, MCP names are owned strings resolved at runtime. The parsers keep reading a single uniform set and remain unaware of a tool's origin. The cache fingerprint puts built-ins in canonical order and MCP names sorted, so an unchanged set always produces the same sequence and the registry can keep reusing its lookup structures. The prompt lists MCP tools after the built-ins, rendered through the same template entry point. They are switched on per server rather than per tool, so the `[tools]` flags do not apply to them. A parsed MCP call still cannot run: the executor routes by `ToolKind` and does not consult the MCP store yet, so it reports such a call as an unknown tool. Routing and approval follow in the next change.
A parsed MCP call had nowhere to go: routing started from `ToolKind`, and `from_name` returns `None` for a runtime-discovered tool, so the call fell into the unknown-tool branch even though the parsers had just accepted it. The executor now tries the MCP store once the built-in lookup misses. Both paths validate and coerce parameters through the same `ParamSpec` view, which is what let the built-in specs move out of the `ToolKind` methods without duplicating the rules for MCP tools. Every MCP call is marked `NeedsApproval`, and no MCP call is pre-checked, so each one reaches the user before it runs. A server's tool descriptions are untrusted input, and a description that reads as an instruction must not be able to execute anything on its own; the approval gate is the control that holds regardless of what a server claims about its tools. `call_tool` still reports a known tool as not connected because no client is wired up yet, and no configuration section produces servers, so nothing observable changes for a workspace without MCP declarations.
There was no way to tell the program which MCP servers to connect to, so the runtime layer added earlier had nothing to consume. The `[[mcp.servers]]` entries parse into the runtime configuration, with project declarations replacing global ones as a whole rather than merging per server; a half-overridden global list would be harder to reason about than an explicit either-or. The transport label is kept as a raw string until validation, so a typo becomes a reported issue that skips only that entry instead of failing the whole file during deserialization. Unknown labels fall back to stdio for the remaining checks, so one bad field does not hide a second problem in the same entry. Each rejected entry surfaces as a warning line naming the offending key, rendered in both locales. `save_mcp_servers` writes the section back on its own, leaving `save_project` and every other table untouched. The section is parsed but not yet consumed: `connect_all` still has no caller in the CLI, so declaring a server changes nothing observable.
ChangeLog Check Report👋 Thank you for your PR! Changes have been detected in the If this PR has an impact on users or developers, please consider updating the CHANGELOG. If not, you may ignore this message. 👋 感谢你的 PR!检测到 如果此 PR 对用户或开发者有影响,请考虑更新 CHANGELOG。如果不需要,可以忽略此消息。 |
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
The MCP layer could read a server declaration but never talk to one: `connect_all` validated the configuration and installed an empty tool list per server, and every call was answered with "not connected". Declaring a working server changed nothing. `connect_all` now spawns each enabled server over stdio, completes the handshake and lists its tools, which are converted through the existing schema parser. Spawning, the handshake and discovery share a ten-second budget, and one `tools/call` gets thirty; a server that hangs is recorded as failed rather than blocking the session. Every declaration still keeps its slot with the reason it contributed nothing, so invalid, disabled and unreachable servers stay distinguishable for the management menu. `rmcp` is added without its default features, since a client needs only `client` and the stdio transport; the defaults would pull in the whole server-side handler stack. Results are mapped onto the chain's own result type: text passes through, while images, audio and resources become markers naming what was dropped, so the model learns a payload exists without the chain pretending to carry it. A result the server marked as an error becomes a failed call, keeping a server-side failure from reading as success. Calls use the single-request entry point, so SEP-2322 `input_required` rounds are not driven and a server that needs them surfaces as a failed call instead of hanging. The HTTP transport is declared and validated but still rejected at connect time with its own message; its client follows separately. No CLI entry point calls `connect_all` yet, so a workspace that declares a server still behaves as before. Verified with `cargo fmt`, `cargo clippy --workspace --all-targets -- -D warnings` and `cargo test --workspace`. Tests cover the result mapping; spawning a server and the handshake are not covered yet.
The delivery section told contributors not to run the full `./scripts/ci.*` locally, but never named `cargo test --workspace`. That gap left the heaviest local command unmentioned, so a full workspace run — which builds and executes every test binary — still looked sanctioned. Local verification is now limited to the crates a change actually touches, with the full suite left to PR CI. The coverage bullet is reworded to match: reading the cached report stays the default, and a local recount goes through `cargo llvm-cov` rather than a separate full test run followed by a second pass over the results. The coverage thresholds are unchanged; only where they are measured moved.
An `http` server could be declared and passed validation, but connecting to it was refused outright: the transport was not wired up, so the entry only ever produced a failure naming the transport as unsupported. A remote server was therefore impossible to use. The HTTP client is now wired up behind the same connection path as stdio. The handshake, the tool discovery call and the conversion of what a server reports are shared by both transports, so a change to how a server's tools are read cannot apply to one and miss the other. `rmcp` gains its reqwest-backed HTTP client along with the `reqwest` feature, which selects rustls for TLS; rustls keeps the build free of a system TLS library dependency on every platform. This pulls a substantial dependency chain (reqwest, hyper, rustls, tower) into the crate for the first time, which is the bulk of the lockfile change. The error message for an unsupported transport is removed along with its two locale entries: no path can produce it any more, and keeping an unreachable string in the catalogue invites it to be mistranslated later. Tool results, timeouts, per-server failure recording and the approval gate are unchanged; only how the connection is established differs. Verified with `cargo fmt`, `cargo clippy --workspace --all-targets -- -D warnings`, `cargo check -p manualaid-core --all-targets` and `cargo test -p manualaid-core --lib` (374 passed). The HTTP path itself is not yet covered by a test that reaches a server.
To Continue DevelopmentInstall manualaid-cli and run it in ur project folder. Use System prompts with this compress text. The compress text should be placed in /compress-fence. Compress Session
核心诉求与意图
关键技术背景
涉及的文件与代码已提交(8 个提交,基
|
|
直接编译origin/main是仅1.41 GB的target,而在feat/mcp-support 分支(在我 Before clean (origin/feat/mcp-support: c99c88b)TOTAL: 124.45 GB (270884 files) After clean (origin/main: 40a6a76)TOTAL: 1.42 GB (2555 files) |

Summary
Related Issue
Checklist
CHANGELOG_ZH_CN.mdanddocs/changelog/CHANGELOG_ZH_CN.mdif applicable.bug,enhancement,documentation,refactor,prompt).中文
概述
相关 Issue
检查清单
CHANGELOG_ZH_CN.md和docs/changelog/CHANGELOG_ZH_CN.md。bug、enhancement、documentation、refactor、prompt)。