Skip to content

feat(agent): attach MCP servers to agent runs, gated by issue source - #131

Open
ArnabChatterjee20k wants to merge 8 commits into
mainfrom
feat/appwrite-mcp-agent
Open

feat(agent): attach MCP servers to agent runs, gated by issue source#131
ArnabChatterjee20k wants to merge 8 commits into
mainfrom
feat/appwrite-mcp-agent

Conversation

@ArnabChatterjee20k

Copy link
Copy Markdown
Member

Adds config-driven MCP support so the Claude agent can query the production Appwrite Cloud (via the Appwrite MCP server) instead of a local stack when investigating user-reported issues.

  • New McpServerConfig under ProviderConfig.mcp, keyed by server name, with a per-server sources list (default use: helpscout only; empty means all sources).
  • Runner renders matched servers to a private 0600 .mcp.json temp file, passes --mcp-config/--strict-mcp-config, and auto-allowlists mcp__ tools for both fix and Q&A runs. Secrets stay in env via ${VAR} expansion; runs without an issue never attach MCP.
  • Threads issue source through the execute path; wires mcp through both runner build sites (lib.rs, main.rs).
  • Example config + unit tests (round-trip, source gating, rendered config perms/shape).

What does this PR do?

(Provide a description of what this PR does.)

Test Plan

(Write your test plan here. If you changed any code, please provide us with clear instructions on how you verified your changes work.)

Related PRs and Issues

(If this PR is related to any other PR or resolves any issue or related to any issue link all related PR and issues here.)

Have you read the Contributing Guidelines on issues?

(Write your answer here.)

Adds config-driven MCP support so the Claude agent can query the
production Appwrite Cloud (via the Appwrite MCP server) instead of a
local stack when investigating user-reported issues.

- New McpServerConfig under ProviderConfig.mcp, keyed by server name,
  with a per-server sources list (default use: helpscout only; empty
  means all sources).
- Runner renders matched servers to a private 0600 .mcp.json temp file,
  passes --mcp-config/--strict-mcp-config, and auto-allowlists
  mcp__<server> tools for both fix and Q&A runs. Secrets stay in env
  via ${VAR} expansion; runs without an issue never attach MCP.
- Threads issue source through the execute path; wires mcp through both
  runner build sites (lib.rs, main.rs).
- Example config + unit tests (round-trip, source gating, rendered
  config perms/shape).
Copilot AI lite review requested due to automatic review settings August 5, 2026 08:13

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds config-driven MCP server support to Claude agent runs, so investigations can query production services via MCP (e.g., Appwrite) only when the run is tied to an issue and the issue’s source is allowlisted for that server.

Changes:

  • Introduces McpServerConfig and ProviderConfig.mcp (keyed by server name) with per-server source gating and TOML parsing/tests.
  • Threads MCP config through provider runner construction and updates the Claude runner to render a per-run temp MCP config and pass it to the Claude CLI.
  • Adds example configuration and unit tests for MCP config rendering and source matching.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/main.rs Plumbs provider mcp config into the runtime runner config.
src/lib.rs Ensures build_provider_runner carries mcp into ClaudeRunnerConfig.
crates/claudear-integrations/src/runner/claude.rs Implements MCP server matching/rendering and attaches MCP config + tool allowlisting per run; adds tests.
crates/claudear-integrations/Cargo.toml Adds tempfile dependency for rendered MCP config files.
crates/claudear-config/src/config.rs Adds McpServerConfig, ProviderConfig.mcp, source gating logic, and tests.
claudear.example.toml Documents example MCP server configuration and source gating.
Suppressed comments (1)

crates/claudear-integrations/src/runner/claude.rs:766

  • execute_with_env_and_attempt expects source: Option<&str>, but this passes Some(&issue.source) (type Option<&String>), which will not compile. Pass a &str instead (e.g. issue.source.as_str()).
            .execute_with_env_and_attempt(
                &prompt,
                &issue.short_id,
                env,
                None,
                project_dir,
                false,
                Some(&issue.source),
            )

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread crates/claudear-integrations/src/runner/claude.rs
Comment thread crates/claudear-integrations/src/runner/claude.rs Outdated
@greptile-apps

greptile-apps Bot commented Aug 5, 2026

Copy link
Copy Markdown

Greptile Summary

The PR adds source-gated, configuration-driven MCP server attachment to Claude agent runs.

  • Adds MCP server transport, environment, source, and tool configuration.
  • Renders matched servers into private temporary MCP configuration files.
  • Threads issue sources through execution paths and wires MCP settings into both runner construction sites.
  • Adds parsing, source-gating, transport-validation, rendering, and file-permission tests.

Confidence Score: 4/5

The PR does not yet appear safe to merge because read-only runs can receive mutating MCP permissions and unrestricted server permissions can still fail to authorize namespaced tools.

Source-matched MCP permissions are appended uniformly to fix and read-only verification or reply runs, preserving the previously reported capability-boundary violation. The empty-tools path also still emits only the bare server permission rather than permissions matching the server's namespaced tools.

Files Needing Attention: crates/claudear-integrations/src/runner/claude.rs

Important Files Changed

Filename Overview
crates/claudear-config/src/config.rs Adds MCP configuration, source matching, and transport validation; the prior ambiguous and contradictory transport cases are now rejected.
crates/claudear-integrations/src/runner/claude.rs Implements MCP rendering and execution wiring, but previously reported permission-boundary and bare server-permission defects remain.
src/lib.rs Propagates provider MCP configuration into the library runner construction path.
src/main.rs Propagates default-provider MCP configuration into the application runner construction path.
claudear.example.toml Documents source-gated MCP configuration and explicit tool restrictions.
crates/claudear-integrations/Cargo.toml Adds the tempfile dependency required for private runtime MCP configuration files.

Reviews (7): Last reviewed commit: "refactor(agent): single tools allowlist ..." | Re-trigger Greptile

Comment thread crates/claudear-integrations/src/runner/claude.rs
Comment thread crates/claudear-integrations/src/runner/claude.rs
Comment thread crates/claudear-integrations/src/runner/claude.rs
- Pass issue source as &str via as_str() in verify/reply paths.
- Write rendered MCP config to the open temp handle instead of reopening
  the path (avoids Windows exclusive-lock failures).
- Validate exactly one of command/url per server; skip and warn otherwise
  so strict MCP loading never sees an ambiguous transport.
- Add per-server tools allowlist: scope to mcp__<server>__<tool> when set,
  else grant all via mcp__<server>. Lets read-only runs be scoped to read
  tools; read-only API key remains the enforced boundary.
- Example config shows read-only tool scoping; config test covers tools.
Copilot AI review requested due to automatic review settings August 5, 2026 08:32
Comment thread crates/claudear-integrations/src/runner/claude.rs

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.

Suppressed comments (1)

crates/claudear-integrations/src/runner/claude.rs:781

  • The doc comment says this renders into a .mcp.json file with 0600 permissions, but the implementation writes a temp file named claudear-mcp-*.json, and 0600 is only meaningful/guaranteed on Unix. Clarifying the wording avoids misleading readers about the on-disk filename and portability.
                    .error
                    .unwrap_or_else(|| "Failed to generate reply".to_string()),
            ))

Copilot AI review requested due to automatic review settings August 5, 2026 08:36

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.

Suppressed comments (1)

crates/claudear-integrations/src/runner/claude.rs:786

  • The doc comment says this renders a “.mcp.json”, but the tempfile is created with a generic .json suffix and is passed explicitly via --mcp-config. Updating the comment avoids confusion when debugging or searching for the file.
    /// Render matched MCP servers into a `.mcp.json` in a private temp file (0600),
    /// deleted when the returned handle drops. `${VAR}` in env is expanded by the CLI.

Comment on lines +920 to +939
// Tools to allowlist for the attached servers (empty when none). A server
// with an explicit `tools` list is scoped to `mcp__<server>__<tool>`;
// otherwise all of its tools are granted via `mcp__<server>`.
let mcp_tool_globs: Vec<String> = if mcp_config_file.is_some() {
matched_mcp
.iter()
.flat_map(|(name, cfg)| {
if cfg.tools.is_empty() {
vec![format!("mcp__{}", name)]
} else {
cfg.tools
.iter()
.map(|tool| format!("mcp__{}__{}", name, tool))
.collect()
}
})
.collect()
} else {
Vec::new()
};
- Read-only runs (Q&A/verify/reply) no longer get unscoped MCP tools; a
  server must declare an explicit `tools` allowlist to be usable there.
  Fix runs still default to all tools. Closes the read-only-boundary gap.
- Validate transport consistency: reject `command` with a non-stdio type
  and `url` with stdio, not just presence, so --strict-mcp-config never
  sees a contradictory server. Added has_valid_transport() + tests.
- Clarify render_mcp_config doc (temp filename, 0600 is Unix-only).
Copilot AI review requested due to automatic review settings August 5, 2026 08:53
@ArnabChatterjee20k
ArnabChatterjee20k removed the request for review from Copilot August 5, 2026 08:56
Read-only runs (Q&A/verify/reply) now draw tools only from a dedicated
per-server readonly_tools list, never from `tools` (which may include
mutating tools used by fix runs). Since a tool's capability cannot be
verified at config time, the operator must explicitly list non-mutating
tools for read-only use; with none listed, read-only runs get no MCP
tools. Closes the remaining production-mutation path.
Copilot AI review requested due to automatic review settings August 5, 2026 08:58

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.

Suppressed comments (1)

crates/claudear-integrations/src/runner/claude.rs:808

  • The HTTP/SSE transport branch in render_mcp_config (when cfg.url is set) is currently untested; the only added unit test covers the stdio/command path. Adding a test that asserts the rendered JSON includes the correct type/url/headers (and does not include stdio-only fields like command/args/env) would help prevent regressions.
            } else if let Some(ref url) = cfg.url {
                // http/sse transport
                entry.insert(
                    "type".to_string(),
                    json!(cfg.transport.clone().unwrap_or_else(|| "http".to_string())),

Asserts the url branch emits type/url/headers and omits stdio-only
fields (command/args/env). Addresses review coverage gap.
Copilot AI review requested due to automatic review settings August 5, 2026 09:16

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.

Suppressed comments (3)

crates/claudear-integrations/src/runner/claude.rs:3845

  • Same as the stdio test: reopening the NamedTempFile by path can be brittle on Windows. Prefer seeking and parsing from the already-open handle.
        let file = ClaudeAgentRunner::render_mcp_config(&servers).expect("render");
        let doc: serde_json::Value =
            serde_json::from_str(&std::fs::read_to_string(file.path()).unwrap()).unwrap();

crates/claudear-integrations/src/runner/claude.rs:963

  • The comment says this always ignores any repo .mcp.json, but the CLI flags are only added when mcp_config_file is set. Either clarify the comment to reflect the conditional behavior, or always pass an explicit (possibly empty) MCP config when you need to enforce source-gated attachment.
        // Load only our rendered MCP config, ignoring any repo .mcp.json.

crates/claudear-integrations/src/runner/claude.rs:3817

  • These tests reopen the temp file via std::fs::read_to_string(file.path()), but render_mcp_config explicitly avoids reopening handles due to Windows locking behavior. To keep the tests robust across platforms, read via the already-open handle (seek back to start) and parse with serde_json::from_reader.

This issue also appears on line 3843 of the same file.

        let file = ClaudeAgentRunner::render_mcp_config(&servers).expect("render");
        let doc: serde_json::Value =
            serde_json::from_str(&std::fs::read_to_string(file.path()).unwrap()).unwrap();

- Reword the strict-mcp-config comment: flags are only added when a
  config is attached; no MCP flags when nothing matches.
- Render tests read the temp file via reopen() instead of by path,
  matching render_mcp_config's Windows-safe handle write.
Copilot AI review requested due to automatic review settings August 5, 2026 09:34

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

The API key is the access boundary, so a separate read-only tool list
added complexity without a real guarantee. Collapse to one `tools` array
(empty = all of the server's tools) applied uniformly to fix and Q&A
runs. Drop readonly_tools.
Copilot AI review requested due to automatic review settings August 5, 2026 10:04

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

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.

2 participants