Skip to content

feat: promote memory to cache-memory tool and add first-class azure-devops tool#167

Merged
jamesadevine merged 8 commits into
mainfrom
feat/tools-frontmatter-refactor
Apr 14, 2026
Merged

feat: promote memory to cache-memory tool and add first-class azure-devops tool#167
jamesadevine merged 8 commits into
mainfrom
feat/tools-frontmatter-refactor

Conversation

@jamesadevine

Copy link
Copy Markdown
Collaborator

Summary

This PR refactors the tools: front-matter section to support first-class tool integrations alongside the existing bash/edit controls. Two changes:

1. Memory → cache-memory tool

  • Moved safe-outputs: memory: to tools: cache-memory:
  • Aligns with gh-aw's cache-memory tool pattern
  • Memory is a first-class tool (agent reads/writes files directly), not a safe-output (no NDJSON serialization)
  • clearMemory parameter auto-injection now keyed off tools.cache-memory
  • No backward compatibility — clean break from safe-outputs.memory

2. First-class Azure DevOps MCP

  • tools: azure-devops: auto-configures the ADO MCP container (node:20-slim + npx @azure-devops/mcp)
  • Auto-generates MCPG config entry, token mapping, and network allowlist
  • Supports toolsets (ADO API groups like repos, wit, core) and allowed (explicit tool filter)
  • Org auto-inferred from $(System.TeamFoundationCollectionUri) at runtime, overridable via org: field
  • Replaces manual mcp-servers: boilerplate for ADO integration

3. Directory restructure

  • Renamed src/tools/src/safeoutputs/ (safe-output MCP tool implementations)
  • Created new src/tools/ for first-class tool implementations
  • Moved memory.rstools/cache_memory.rs

Target front-matter shape

tools:
  bash: ["cat", "ls"]
  edit: true
  cache-memory:
    allowed-extensions: [.md, .json]
  azure-devops:
    toolsets: [repos, wit]
    allowed: [wit_get_work_item]
    org: myorg  # optional, auto-inferred

safe-outputs: continues to hold write-requiring tools (create-work-item, create-pr, etc.) unchanged.

Test Results

All 736 tests pass. 8 new tests for ADO MCP tool configuration, 9 new tests for type deserialization.

jamesadevine and others added 4 commits April 14, 2026 02:13
…irst-class tools

Rename the existing tools directory to safeoutputs to better reflect its
purpose (safe-output MCP tool implementations that serialize to NDJSON
in Stage 1 and execute in Stage 2).

Create a new src/tools directory for first-class tool implementations
that the compiler auto-configures (cache-memory, azure-devops).

Move memory.rs from safeoutputs to tools/cache_memory.rs since memory
is a first-class tool, not a safe-output.

Add CacheMemoryToolConfig and AzureDevOpsToolConfig types to
compile/types.rs with support for both boolean and object front-matter
formats. Extend ToolsConfig to include cache-memory and azure-devops
fields.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Move memory configuration from safe-outputs: memory: to tools:
cache-memory: in the front matter. This aligns with gh-aw's cache-memory
tool pattern where memory is a first-class tool, not a safe-output.

Key changes:
- Update has_memory detection in standalone.rs and onees.rs to read
  from tools.cache-memory instead of safe-outputs.memory
- Update main.rs Stage 2 executor to resolve MemoryConfig from
  tools.cache-memory
- Remove 'memory' from NON_MCP_SAFE_OUTPUT_KEYS and ALL_KNOWN_SAFE_OUTPUTS
- Update integration tests to use tools: cache-memory: format
- Update enabled-tools-args tests (memory no longer affects filtering)
- No backward compatibility for safe-outputs.memory

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add tools.azure-devops as a first-class compiler tool that auto-configures
the Azure DevOps MCP container in the MCPG config. This replaces the need
for manual mcp-servers configuration with boilerplate container/entrypoint
settings.

When tools.azure-devops is enabled, the compiler:
- Auto-generates a containerized stdio MCP entry (node:20-slim + npx
  @azure-devops/mcp) in the MCPG configuration
- Auto-maps ADO token (AZURE_DEVOPS_EXT_PAT) passthrough when
  permissions.read is configured
- Adds ADO-specific hosts to the network allowlist
- Supports toolsets (repos, wit, core etc.) as -d flags
- Supports explicit tool allow-list for MCPG filtering
- Auto-infers org from pipeline runtime variables with optional override
- Warns on conflict with manual mcp-servers.azure-devops entry

Front-matter example:
  tools:
    azure-devops:
      toolsets: [repos, wit]
      allowed: [wit_get_work_item]
      org: myorg  # optional, auto-inferred

Also adds ADO_ORG_NAME runtime extraction to the base template for
org auto-inference from $(System.TeamFoundationCollectionUri).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Update architecture tree to show src/safeoutputs/ and src/tools/
- Add cache-memory and azure-devops tool documentation under Tools Configuration
- Update memory safe-output section to point to new tools.cache-memory location
- Update front-matter example to show new tool entries
- Update 'Adding New Features' section with safeoutputs vs tools distinction
- Update azure-devops-mcp.md example to use tools.azure-devops instead of
  manual mcp-servers configuration

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown
Contributor

🔍 Rust PR Review

Summary: Solid structural refactor with one functional bug and two minor issues worth addressing before merge.

Findings

🐛 Bugs / Logic Issues

  • src/compile/types.rs + standalone.rs: The project field in AzureDevOpsOptions is exposed in the public API, documented in AGENTS.md (overridable via org: field), and tested in types.rs — but ado_config.project() is never called anywhere in the compilation pipeline. Users who set azure-devops: project: MyProject will see no effect; the value is silently discarded. Either wire it into the entrypoint args (probably a -p <project> flag if @azure-devops/mcp supports that) or remove the field and tests before shipping.

🔒 Security Concerns

  • templates/base.yml:257: The $(ADO_ORG_NAME) placeholder in the MCPG JSON config uses ADO variable expansion syntax, while all other in-config placeholders (${SAFE_OUTPUTS_PORT}, etc.) intentionally use bash ${} syntax to avoid ADO expansion. This means the sed escape relies on ADO leaving undefined variables unexpanded (\$(ADO_ORG_NAME) → bash literal $(ADO_ORG_NAME)). ADO's behavior for undefined variables is not guaranteed — the $$ form is the documented escape. If ADO does expand the undefined $(ADO_ORG_NAME) to empty string, the sed command silently fails and the ADO MCP starts with an empty org name. Recommend using ${ADO_ORG_NAME} as the placeholder in the generated JSON to stay consistent with the other substitutions and avoid the ADO-vs-bash ambiguity.

⚠️ Suggestions

  • src/compile/common.rs:629: MCPG_IMAGE is defined and documented in AGENTS.md as the {{ mcpg_image }} template marker, but standalone.rs handles no such marker, and base.yml still has ghcr.io/github/gh-aw-mcpg hardcoded directly. The constant is dead code. Either wire up the {{ mcpg_image }} replacement in standalone.rs (using MCPG_IMAGE) so the image name is controlled from one place, or remove the constant and the documentation entry.

  • src/mcp.rs:1364: Stale comment says "non-MCP keys like 'memory'" — memory was removed from NON_MCP_SAFE_OUTPUT_KEYS (now &[]) in this very PR. The comment should be updated or removed.

✅ What Looks Good

  • The src/tools/src/safeoutputs/ rename is a clean semantic split — the new names make the two-phase architecture (Stage 1 NDJSON serialization vs. first-class tool integration) much clearer.
  • The #[serde(untagged)] bool/object enum pattern for CacheMemoryToolConfig and AzureDevOpsToolConfig is consistent with ScheduleConfig / McpConfig — no surprises.
  • Single-quoted heredoc (<< 'MCPG_CONFIG_EOF') correctly prevents both bash and ADO expansion when writing the MCPG config, which is exactly right.
  • Token passthrough for the ADO MCP is correctly gated on permissions.read — won't silently fail for pipelines without a read service connection.
  • Warning on tools.azure-devops + mcp-servers.azure-devops double-configuration is good UX.
  • Test coverage for the new MCPG config generation is thorough (8 new integration tests + 9 type deserialization tests).

Generated by Rust PR Reviewer for issue #167 · ● 5.6M ·

jamesadevine and others added 3 commits April 14, 2026 10:31
Replace the runtime $(ADO_ORG_NAME) pipeline variable approach with
compile-time inference using the existing parse_ado_remote() function.
The compiler now extracts the org from the git remote URL when compiling.

Key changes:
- generate_mcpg_config() now returns Result and accepts inferred_org
- Compilation fails with a clear error if tools.azure-devops is enabled
  but no org can be determined (no explicit override + no ADO git remote)
- Remove $(ADO_ORG_NAME) runtime substitution from base.yml template
- Remove unused project field from AzureDevOpsOptions (not supported by
  @azure-devops/mcp and was silently discarded)
- Make get_git_remote_url public for use by the compiler
- Add test_ado_tool_no_org_fails and test_ado_tool_explicit_org_overrides_inferred

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The ADO MCP only accepts org as a positional arg. The project field was
added speculatively but is not a supported option. Keep only org (with
compile-time git remote inference and explicit override).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Replace hardcoded ghcr.io/github/gh-aw-mcpg in base.yml with
  {{ mcpg_image }} marker, replaced by MCPG_IMAGE constant in
  standalone.rs — single source of truth for the image name
- Fix stale comment in mcp.rs that still referenced 'memory' in
  NON_MCP_SAFE_OUTPUT_KEYS (now empty)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown
Contributor

🔍 Rust PR Review

Summary: Looks good overall — clean, well-structured refactor with solid test coverage. Two suggestions worth addressing before merge.

Findings

⚠️ Suggestions

  • src/compile/standalone.rs:555-576org and toolset values are not validated before insertion into MCPG entrypoint args

    User-supplied tools.azure-devops.org: and every entry in toolsets: are inserted directly into entrypointArgs with no character validation:

    entrypoint_args.push(org);             // from tools.azure-devops.org
    for toolset in ado_config.toolsets() {
        entrypoint_args.push(toolset.clone()); // user-controlled
    }

    JSON serialization prevents shell injection (args are passed as an exec array, not a shell string), but invalid values (e.g. an org name containing spaces or slashes) will produce a cryptic MCPG runtime failure instead of a clear compile-time error. The existing is_safe_tool_name helper in common.rs (ASCII alphanumerics + hyphens) would work as a validator for both:

    if !org.chars().all(|c| c.is_ascii_alphanumeric() || c == '-') {
        anyhow::bail!("Invalid ADO org name '{org}': must contain only alphanumerics and hyphens");
    }

    Toolset names (repos, wit, core, etc.) are also worth validating with the same rule, consistent with how safe-outputs tool names are validated. The allowed: list (goes into MCPG's tools field, not into shell args) is lower priority but symmetric.

  • src/compile/common.rs:852-855 — Generic warning for safe-outputs: memory: will silently break existing users

    After this PR, front matter that still contains safe-outputs: memory: hits the generic "unrecognized safe-output tool 'memory' — skipping" path with no actionable guidance. Users who upgrade without reading the changelog will see the warning but won't know why their memory feature stopped working.

    A specific deprecation hint would prevent confusion:

    if key == "memory" {
        eprintln!(
            "Warning: Agent '{}': 'safe-outputs: memory:' has moved to \
             'tools: cache-memory:'. Update your front matter to restore memory support.",
            front_matter.name
        );
        continue;
    }

    This doesn't affect correctness, but the breakage is otherwise silent beyond the generic warning.

✅ What Looks Good

  • Module restructuresafeoutputs/ vs tools/ distinction is clean and well-motivated. The mod.rs re-exports and import updates are thorough.
  • generate_mcpg_configResult<McpgConfig> — the function signature change is correct; all existing call sites were updated with .unwrap() in tests (appropriate) and ? in production paths.
  • {{ mcpg_image }} parameterization — good improvement; decouples image name from the version field and enables independent override later.
  • Precedence logic for org resolution (explicit > inferred > compile error) is clear, well-documented, and fully tested, including the no-org-fails case (test_ado_tool_no_org_fails).
  • permissions.read absence warninggenerate_mcpg_docker_env correctly emits a warning when tools.azure-devops is enabled but no read service connection is configured. The token-forwarding logic (ado_tool_needs_token) is correct.
  • tools.azure-devops: false is tested to produce no MCPG entry — important edge case.
  • Test coverage — 8 new ADO MCPG tests + 9 deserialization tests cover the main paths well.

Generated by Rust PR Reviewer for issue #167 · ● 2.4M ·

- Validate org name (alphanumerics + hyphens only) at compile time to
  catch invalid values early instead of cryptic MCPG runtime failures
- Validate toolset names with the same rule
- Add specific deprecation hint when safe-outputs: memory: is detected,
  directing users to tools: cache-memory:
- Add tests for invalid org and invalid toolset rejection

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@jamesadevine jamesadevine merged commit 39103e1 into main Apr 14, 2026
4 checks passed
@jamesadevine jamesadevine deleted the feat/tools-frontmatter-refactor branch April 14, 2026 11:19
@github-actions

Copy link
Copy Markdown
Contributor

🔍 Rust PR Review

Summary: Solid refactor — well-structured, correct security controls, clean module separation. One missing compile-time guard and a PR description inaccuracy worth noting.

Findings

⚠️ Suggestions

  • src/compile/standalone.rs (generate_mcpg_config): No compile-time warning when tools: azure-devops: is enabled but permissions.read is not configured. The ADO MCP unconditionally injects AZURE_DEVOPS_EXT_PAT: "" (passthrough), but that passthrough only gets wired into the MCPG Docker env when permissions.read is set (via generate_mcpg_docker_env). Without it, the MCP container starts with an empty PAT and every ADO API call will 401 silently. A validate_write_permissions-style check (or at least an eprintln! warning) at compile time would surface this before runtime.

  • src/compile/standalone.rs (generate_mcpg_config): The allowed list items from tools.azure-devops.allowed (e.g. wit_get_work_item) are placed directly into the MCPG JSON config without validation. These go into JSON (not a shell command), so there's no injection risk — but they can contain arbitrary Unicode or shell-special chars that would produce an invalid MCPG config. The existing is_safe_tool_name function in common.rs already validates alphanumeric+hyphens; applying it here would be consistent with how safe-outputs tool names are validated. Toolsets and org names are already validated; this one was missed.

  • PR description accuracy: The description says org is "auto-inferred from $(System.TeamFoundationCollectionUri) at runtime" — but the code infers it from the git remote URL at compile time (via get_git_remote_url + parse_ado_remote). This could mislead users who move the compiled pipeline to a different org; the org is baked into the generated mcpg-config.json at compile time, not resolved dynamically.

🐛 Bugs / Logic Issues

  • src/compile/common.rs (generate_enabled_tools_args): The memory key check now emits a warning and continues, but then falls through to the effective_mcp_tool_count == 0 early-return path — which returns String::new() (all tools allowed). If someone has safe-outputs: memory: plus a legitimate tool like create-pull-request, the continue means create-pull-request is still counted correctly and the --enabled-tools list is correct. ✅ This is fine; just confirming the logic is sound.

  • src/main.rs execute path: Old configs with safe-outputs: memory: will silently get no memory processing at stage 2 (only a compile-time eprintln! warning). The PR marks this as "No backward compatibility — clean break", so this is intentional, but worth verifying pipeline operators are aware that recompilation is required.

✅ What Looks Good

  • Security: Org names and toolset names are validated (alphanumeric + hyphens only) before being pushed into the entrypoint_args Vec. Even though those args ultimately go into JSON (not a raw shell string), the validation correctly prevents heredoc breakout and other shenanigans.
  • mcp_required_hosts("ado") correctly wires in the ADO domain allowlist when tools.azure-devops is enabled — so the network firewall gets updated automatically without user action.
  • The skip logic (if name == ADO_MCP_SERVER_NAME && mcp_servers.contains_key(...)) correctly prevents duplicate MCPG entries when a user has both tools.azure-devops and mcp-servers.azure-devops, and the warning message is clear.
  • Module rename src/tools/src/safeoutputs/ + new src/tools/ is a meaningful semantic distinction; the NON_MCP_SAFE_OUTPUT_KEYS being reduced to &[] is a correct consequence of moving memory out.
  • Test coverage for new deserialization paths (CacheMemoryToolConfig, AzureDevOpsToolConfig) is thorough, including Enabled(false) and partial-config cases.

Generated by Rust PR Reviewer for issue #167 · ● 3.2M ·

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