Skip to content

feat(mcp): read a working-directory .mcp.json, honor enabled:false, name unmodelled keys; bump v1.1.29 - #79

Merged
laohanlinux merged 3 commits into
mainfrom
feat/mcp-project-file
Sep 15, 2026
Merged

laohanlinux merged 3 commits into
mainfrom
feat/mcp-project-file

Conversation

@laohanlinux

Copy link
Copy Markdown
Contributor

Three commits on top of v1.1.28 (#78), all in the MCP configuration area, plus the v1.1.29 bump.

1. Read <workdir>/.mcp.json — the Claude Code project file

Sources are now, lowest precedence first: <workdir>/.mcp.json~/.tact/.mcp.json
<workdir>/.tact/.mcp.json → installed plugins. A repository's shared configuration
works out of the box, but cannot silently take over a server the user declared — a
displaced declaration is still reported.

  • It belongs to the project rather than the user, so an unparseable one is a warning and
    a skip
    , not the hard error a native config gets.
  • McpProjectConfig gains enabled (default true) and a flattened extra map:
    • "enabled": false (the Codex convention) is resolved — it can shadow and be shadowed —
      but never connected; mcp list reports disabled (enabled: false), mcp get describes
      it without dialing.
    • Keys Tact does not model (enabled_tools, omit_tools_from, startup_timeout_sec,
      tools.<name>.output_token_limit) are named one by one in mcp list plus a warning in
      the log. The listing is the channel that matters: tact-ui installs a tracing subscriber
      only when RUST_LOG/tokio_console is set, so a warning alone would be invisible.

2. Fix plugin-bundle MCP parsing

A plugin-root .mcp.json using the standard {"mcpServers": {...}} wrapper was read as a
flat name→config map, producing a bogus server that was then skipped. parse_plugin_mcp_document
accepts the wrapper (which wins when present) and falls back to the flat form.

3. Rename the native MCP config to .mcp.json

Tact's two native config files were mcp.json (~/.tact/mcp.json, <workdir>/.tact/mcp.json)
while the same concept is .mcp.json everywhere else: Claude Code's project file, the file a
plugin bundle's mcpServers points at, VS Code's .vscode/mcp.json. Both scopes are now
.mcp.json, kept in the single MCP_CONFIG_FILE constant. Nothing is done for compatibility:
a leftover mcp.json is an unrelated file, neither a source nor reported.

Docs: Ch 8 source table and entry semantics, Ch 21, Ch 26 entry (en/zh), README.

Verification

  • scripts/check-rust.sh green on the pushed tree: cargo fmt --check, clippy --all-targets -D warnings, and the four-crate test suite.
  • no_proxy=127.0.0.1,localhost required for the loopback tests in this environment.

…ame unmodelled keys

Sources are now, lowest precedence first: <workdir>/.mcp.json (Claude Code
project file), ~/.tact/mcp.json, <workdir>/.tact/mcp.json, installed plugins.
A repository's shared configuration works out of the box without being able to
silently take over a server the user declared; a displaced declaration is still
reported. That file belongs to the project rather than to the user, so an
unparseable one is a warning and a skip instead of the hard error a native
mcp.json gets.

McpProjectConfig gains `enabled` (default true) and a flattened `extra` map:

- "enabled": false (the Codex convention, used by OpenAI's bundled
  unified-computer-use) is resolved -- it can shadow and be shadowed -- but
  never connected; `mcp list` reports it as `disabled (enabled: false)` and
  `mcp get` describes it without dialing.
- Keys Tact does not model (enabled_tools, omit_tools_from,
  startup_timeout_sec, tools.<name>.output_token_limit) are named one by one in
  `mcp list`, with a matching warning in the log. The listing is the channel
  that matters: tact-ui installs a tracing subscriber only when RUST_LOG or
  tokio_console is set, so a warning alone would be invisible.

Also fixes a plugin-bundle parse: a plugin-root .mcp.json using the standard
{"mcpServers": {...}} wrapper was read as a flat name->config map, producing a
bogus server that was then skipped. parse_plugin_mcp_document accepts the
wrapper (which wins when present) and falls back to the flat form.

Docs: Ch 8 source table and entry semantics, Ch 21, and a Ch 26 entry (en/zh).
Tact's two native config files were named `mcp.json`
(`~/.tact/mcp.json`, `<workdir>/.tact/mcp.json`) while the same concept is
`.mcp.json` everywhere else: Claude Code's project file, the file a plugin
bundle's `mcpServers` points at, VS Code's `.vscode/mcp.json`. One concept
with two names only breeds doubt about which to write.

Both scopes are now `.mcp.json`, with the path kept in the single
`MCP_CONFIG_FILE` constant. The old name is not read and nothing is done for
compatibility: a leftover `mcp.json` is an unrelated file, neither a source
nor reported.

Source order is unchanged (`<workdir>/.mcp.json` → `~/.tact/.mcp.json` →
`<workdir>/.tact/.mcp.json` → installed plugins); `mcp add`/`remove` write the
new paths. `scope_hint` now matches the Claude Code project file by full path
rather than by `.mcp.json` suffix, which would also have caught Tact's own
`.tact/.mcp.json`. Docs (Ch 8, 21, 26, README) and tests follow the rename.
@laohanlinux
laohanlinux merged commit 1ae0e93 into main Sep 15, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant