Skip to content

0007-MCP Server v2 Tool Expansion – Technical Design Document

Open Questions / Clarifications

All initial open questions are resolved:

  • cancel_build endpoint: the exact endpoint will be determined during implementation.
  • get_testers_in_group: the tool returns a parsed JSON list; the backing CSV export response is parsed inside the tool.
  • ✅ Secret variables: the API already returns secret variable values as empty strings, so no additional masking logic is required in the tools.
  • trigger_build and trigger_build_for_commit are merged into a single trigger_build tool with an optional commit argument.
  • ✅ Publish endpoints disambiguated as #1/#2 in the analysis: exact payload shapes will be verified against the publish service OpenAPI specification during implementation.

Introduction

Purpose: This document describes the design for the v2 expansion of the Appcircle MCP server (appcircleio/appcircle-mcp). MCP v1 shipped 17 read-only tools. v2 adds approximately 25 new tools and 4 filter extensions based on the tool analysis in Linear issue AI-36, and introduces the server's first write/action tools (trigger build, cancel build, send to testers, start/stop publish).

Scope: This work covers:

  • New read tools across Build, Testing Distribution, Enterprise App Store, Publish, and Reports modules.
  • New write tools: trigger_build, cancel_build, send_app_version_to_testers, update_app_version_release_notes, start_publish, stop_publish.
  • Filter support for the four existing profile list tools.
  • A new opt-in gating mechanism for write tools.
  • Log truncation for build and publish log tools.
  • A native Build Insights Report tool (get_build_insights_report) that computes aggregated build analytics server-side.

Out of scope:

  • Items marked v3 or omit in AI-36 (profile/configuration CRUD, repository binding, metadata updates, tester group CRUD, download links, deletes, binary uploads, connection administration).
  • Signing Identities (no new tools; write access to signing assets is intentionally excluded).
  • CodePush tooling (planned for v3-v4).
  • Authentication model changes. The server stays in front of the API Gateway with pass-through Bearer authentication (see BE-8493).

System Overview

The Appcircle MCP server exposes Appcircle Public API capabilities to MCP clients (Claude, Cursor, Claude Code, and similar). It acts as an API user: each tool call forwards the client-supplied Bearer token to the Public API and performs no authentication or rate limiting of its own.

v2 keeps this model and expands the tool surface in two directions:

  1. Deeper read coverage: build status and logs, commits, variable groups, testing groups and testers, store app versions by publish type, publish details, flows, metadata, and activity reports.
  2. First write surface: starting, cancelling, and managing builds, distributions, and publish flows. Write tools are disabled by default and must be enabled explicitly by the operator.

Architecture

Technology Stack: Python 3.10+, FastMCP, httpx, Pydantic, Starlette/Uvicorn. Unchanged from v1.

External Dependencies: Appcircle Public API (Build, Distribution, Store, Publish, Report services) behind the API Gateway. No new third-party dependencies are introduced; the dependency security requirements from BE-8571 (pinned versions, pip-audit, Dependabot) continue to apply.

Code structure: The v1 pattern is preserved: one file per tool under src/tools/<module>/, registered with the @mcp.tool() decorator, calling the shared ApiClient, returning the standard success/error envelope, and using handle_api_error for failures. One new module folder is added: src/tools/variables/ for the shared variable group tools.


Key Components

Component 1 – New Read Tools

  • Purpose: Extend conversational read access to build, distribution, store, publish, and reporting data.
  • Responsibilities: Each tool wraps one Public API endpoint, validates input locally, trims sensitive keys, and returns the standard envelope.
  • Tool inventory:
Module Tool Endpoint
Build get_last_commit GET /build/v2/commits/last-commit
Build get_build_status GET /build/v2/commits/{commitId}/builds/{buildId}/status
Build get_build_logs GET /build/v2/commits/{commitId}/builds/{buildId}/logs
Build download_build_configuration GET /build/v2/profiles/{id}/configurations/{configId}/content
Variables get_variable_groups GET /build/v2/variable-groups
Variables get_variables_in_group GET /build/v2/variable-groups/{id}/variables
Distribution get_testing_groups GET /distribution/v2/testing-groups
Distribution get_testers GET /distribution/v2/testing-groups/testers
Distribution get_testers_in_group GET /distribution/v2/testing-groups/{groupId}/testers (CSV parsed to JSON)
Store get_store_app_versions_by_publish_type GET /store/v2/profiles/{profileId}/app-versions/publish-types/{publishType}
Publish get_app_version_metadata GET /publish/v2/{platformType}/{profileId}/app-versions/{id}/metadata
Publish get_metadata_locales GET /publish/v2/{platformType}/{profileId}/metadata/locales
Publish get_intune_metadata GET /publish/v2/profiles/{platformType}/{profileId}/app-versions/{id}/intune/metadata
Publish get_publish_metadata_lock_status GET /publish/v2/{platformType}/{profileId}/metadata/lock
Publish get_publish_details GET /publish/v2/{platformType}/{profileId}/app-versions/{id}/publish
Publish get_publish_step_logs GET /publish/v2/{platformType}/{profileId}/publish/{publishId}/step/{stepId}/logs
Publish get_publish_flow_yaml GET /publish/v2/{platformType}/{profileId}/publish/{publishId}/flowdocument
Publish get_publish_flows GET /publish/v2/profiles/{platformType}/{profileId}/publishflows
Reports get_build_activity_log GET /report/v1/build/activity
Reports get_signing_activity_log GET /report/v2/sign/activity
Reports get_publish_activity_log GET /report/v1/publish/activity
Reports get_build_queue_waiting_report GET /report/v1/build/queue-waiting

Component 2 – Write Tools

  • Purpose: Allow MCP clients to perform a curated set of actions with real side effects.
  • Responsibilities: Input validation (UUID checks, non-empty tester lists with a size cap), explicit side-effect statements in tool descriptions, and mapping of invalid-state errors to recoverable error types.
  • Tool inventory:
Tool Endpoint Risk notes
trigger_build Without commit: PUT /build/v2/profiles/{id}/branch/{branchId}/workflow/{wfId} or PUT .../workflow/{wfId}. With optional commit argument: POST/PUT /build/v2/commits/{commitId or hash} variants Consumes build minutes
cancel_build Finalized during implementation Interrupts running work
send_app_version_to_testers POST /distribution/v2/profiles/{profileId}/app-versions/{id} Sends emails to external testers
update_app_version_release_notes PATCH /distribution/v2/profiles/{profileId}/app-versions/{id} Overwrites content
start_publish POST /publish/v2/{profileId}/publish/{publishId} (+ from-step / restart variants) Store-facing side effects
stop_publish POST /publish/v2/{profileId}/publish/{publishId} (stop variants) Interrupts running work

Component 3 – Write-Tool Gating

  • Purpose: Keep existing deployments read-only after upgrade and make write access an explicit operator decision.
  • Design: A new setting AC_MCP_ENABLE_WRITE_TOOLS (default false). When false, write tools are not registered at all, so they do not appear in the tool list. This is orthogonal to the existing TOOLSETS filter: the toolset selects the module, the flag selects read vs write.
  • Annotations: All write tools are registered with readOnlyHint: false; cancel_build and stop_publish additionally carry destructiveHint: true. Read tools receive readOnlyHint: true retroactively. Confirmation UX is the MCP client's responsibility; the server states side effects in tool descriptions.

Component 4 – Log Truncation

  • Purpose: Prevent get_build_logs and get_publish_step_logs from flooding the model context with full log dumps.
  • Design:
    • Default: return the tail of the last 200 lines, capped at 64 KB after line slicing.
    • Parameters: tail_lines (default 200, max 1000) and an optional grep case-insensitive substring filter applied before the tail.
    • The envelope meta includes total_lines, returned_lines, and truncated so the model knows it is looking at a slice.
    • get_build_logs accepts an optional step argument for per-step logs.

Component 5 – Filter Extensions

  • Purpose: Add a filters parameter to the four existing profile list tools (get_build_profiles, get_distribution_profiles, get_store_profiles, get_publish_profiles), backed by the corresponding GET .../profiles/filters endpoints.
  • Compatibility: The new argument is optional, so existing tool signatures remain backward compatible.

Component 6 – Build Insights Report Tool

  • Purpose: A native MCP tool (get_build_insights_report) that replaces a costly Claude Code skill. The skill made the LLM pull raw build history and workflow YAML into context and do all percentile / scoring / HTML math in-context. The tool now performs that computation server-side in Python and returns only the aggregated result as structured JSON (no HTML or narrative generation).
  • Sections (6, all shipped):
Section What it covers
Health Snapshot + Trends Success rate, build counts, top active/inactive profiles, daily and per-profile duration trends
Root Cause Top failing steps, MTTR (episode-based), flaky build groups, warning hotspots, zero-success workflows
Artifact Health Per-profile artifact size (installable extensions only: .ipa / .apk / .aab), biggest grower/shrinker
Workflow Quality Per-profile push/PR workflow YAML scoring against a best-practice component catalog, by OS
Queue Time Build queue wait stats plus daily trend, via the previously undocumented /report/v1/build/queue-waiting endpoint
Maturity Assessment Composite score (Reliability 30% / Discipline 30% / Speed 15% / Security 25%) plus a rule-based "top improvements" list
  • Architecture: A tool-specific subpackage src/tools/report/build_insights/:
    • fetch.py - paginated fetch-alls.
    • shared.py - MTTR / flaky / percentile helpers.
    • One compute module per section.
    • workflow_yaml.py - YAML parsing and scoring.
    • get_build_insights_report.py - thin wrapper that orchestrates the fetches and assembles the response.
  • Key design decisions:
    • Single tool with a sections parameter (defaults to all 6); structured JSON only, no HTML/narrative.
    • Scoped by organization_id. The default scope excludes cross-org build records that bleed into a main-org token's history; include_sub_orgs=True opts back in, with affected profiles flagged under unresolved_profiles.
    • Missing data is omitted (meta.omitted_sections / meta.omitted_subsections), never fabricated as zero. This also covers permission gaps: a single 401/403 (for example an admin-only endpoint) degrades just that fetch via a soft-fetch wrapper (meta.failed_fetches) instead of failing the whole report. Only the current-period build history fetch is hard-required.
    • Cache Health (cache pull/push duration, hit/miss) is deferred indefinitely: that data exists only in raw build logs, not in any structured API field, and this tool intentionally stays log-scrape-free.

Detailed Design

Naming and shape decisions (items flagged in AI-36):

  • start_build is renamed to trigger_build, as already struck through in the analysis.
  • trigger_build and trigger_build_for_commit are merged into a single trigger_build tool. An optional commit argument (commit ID or hash) selects the commit-based endpoint variants; when omitted, the build starts from the branch with the latest commit.
  • get_configurations is renamed to download_build_configuration. It returns the YAML content of a single configuration, and the original name collides with the v1 tool get_build_configuration_details.
  • get_variable_groups and get_variables_in_group appear under both Build and Publish in the analysis. Duplicate tool names are not allowed in MCP, so one shared implementation is registered once and exposed under both the build and publish toolsets.
  • get_testers_in_group is backed by a CSV export endpoint; the tool parses the CSV response and returns a JSON list, keeping the output consistent with the other tools and easier for LLM consumption.

Error handling:

  • handle_api_error is reused for all new tools.
  • HTTP 409/422 responses from trigger/cancel/start/stop calls map to error.type: "invalid_state" (for example, cancelling a finished build) so the model can recover gracefully.
  • Report tools with date ranges validate start_date <= end_date locally before calling the API.

Security Considerations:

  • Same pass-through authentication as v1; the server never stores tokens.
  • Response trimming follows the v1 EXCLUDED_PROFILE_KEYS pattern: secrets and internal keys are stripped from all new payloads.
  • Variable group endpoints already return secret variable values as empty strings, so no additional masking logic is needed. Tools pass the API response through and may annotate empty secret values for clarity.
  • No new write access for Signing Identities.

Dependency Security - Open Dependabot Alerts:

As of 2026-06-24, Dependabot reports 29 open alerts against ac-service-mcp (all in uv.lock): 9 high, 15 medium, 5 low. Most are transitive dependencies pulled in via FastMCP / Starlette / httpx / uvicorn. Remediation is to raise the affected pins and re-lock (uv lock), then confirm clean with pip-audit. Grouped by package:

Package Alerts Highest severity Bump to (>=) Notes
python-multipart 6 High 0.0.31 DoS via unbounded headers/preamble; parameter smuggling
starlette 5 High 1.3.1 form-limit DoS, SSRF/UNC on Windows, Host-header path poisoning
pyjwt 5 High 2.13.0 Forged HS256 via JWK-as-HMAC, SSRF, algorithm allow-list bypass
authlib 3 Medium 1.6.12 OAuth/OIDC open redirect, CSRF when using cache
cryptography 2 High 48.0.1 Vulnerable bundled OpenSSL; buffer overflow on non-contiguous buffers
urllib3 2 High 2.7.0 Cross-origin header leak on redirect; decompression-bomb bypass
pip 2 Medium 26.1 Untrusted-control-sphere inclusion; tar/ZIP confusion
msgpack 1 High 1.2.1 OOB read / crash on Unpacker reuse after caught error
pydantic-settings 1 Medium 2.14.2 Symlink escape from secrets_dir enabling local file read
idna 1 Medium 3.15 idna.encode() bypass of the CVE-2024-3651 fix
pytest 1 Medium 9.0.3 Insecure tmpdir handling (dev/test-only dependency)

These bumps should land before the v2 release tag; remediation is tracked under AI-93.


Integration and Interfaces

External Interfaces: Appcircle Public API services: Build, Distribution, Store, Publish, Report. All calls go through the API Gateway with the client-supplied Bearer token.

Internal Interfaces: None. The MCP server does not communicate with internal services directly.


Deployment and Infrastructure

Cloud Deployment

Deployed to mcp.appcircle.io via the existing Jenkins pipeline and hardened image (BE-8494). The new AC_MCP_ENABLE_WRITE_TOOLS environment variable defaults to false; enabling it on cloud is a separate operational decision.

Self-Hosted Deployment

No script changes beyond the image tag (MCP service support already added in PL-41). Document the new environment variable in the self-hosted MCP configuration reference.

💡 Customer-facing behavior changes (new tools, write gating); a documentation issue is required for the docs.appcircle.io MCP page.


Testing and Quality Assurance

Test Strategy:

  • Unit tests: per tool under test/unit/<module>/, respx-mocked, covering success, API error, validation error, log truncation logic, CSV-to-JSON parsing for get_testers_in_group, and write-flag gating (write tools must be absent when AC_MCP_ENABLE_WRITE_TOOLS=false).
  • Integration tests: per tool under test/integration/, gated by APPCIRCLE_ACCESS_TOKEN. Write-tool integration tests run only against a dedicated test organization and are marked integration_write, excluded from the default CI run.

Quality Assurance: Existing CI pipeline with pip-audit remains unchanged (BE-8571). Code review per team standard.


Delivery Plan

  1. PR 1: Filter extensions for the four existing list tools + readOnlyHint annotations.
  2. PR 2: Build read tools (status, last commit, logs with truncation, configuration download) + shared variables module.
  3. PR 3: Distribution, Store, and Publish read tools.
  4. PR 4: Reports module.
  5. PR 5: Write-tool gating infrastructure + trigger_build (including commit variants), cancel_build.
  6. PR 6: Distribution write tools + start_publish, stop_publish.
  7. PR 7: Build Insights Report tool (get_build_insights_report) and its src/tools/report/build_insights/ subpackage.
  8. Documentation update and release note.

Server version bumps minor per PR; a tagged release follows once write tools land.


Risks and Mitigation Strategies

Risk Mitigation
Write tools triggered unintentionally by an LLM Disabled by default (AC_MCP_ENABLE_WRITE_TOOLS=false), destructiveHint annotations, explicit side-effect statements in descriptions
Log responses flooding model context Tail-based truncation with hard byte cap and truncation metadata
Endpoint details not yet finalized (cancel_build, Publish #1/#2 payload variants) Verify against the relevant service OpenAPI specifications during implementation, before PR 5/6 merge
trigger_build schema complexity after merging commit variants Optional commit argument with clear description; validation rejects conflicting argument combinations
Tool count growth degrading client tool selection Toolset filtering (TOOLSETS) remains available to scope the exposed surface

Appendix

  • Source analysis: Linear AI-36 ([MCP Analysis] MCP Tool Analysis for v2)
  • Related: BE-8322 (v1 tool analysis), BE-8380 (v1 tools), BE-8493 (gateway placement analysis), BE-8494 (image hardening and pipeline), BE-8571 (dependency security), PL-41 (self-hosted deployment)