0007-MCP Server v2 Tool Expansion – Technical Design Document¶
Open Questions / Clarifications¶
All initial open questions are resolved:
- ✅
cancel_buildendpoint: 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_buildandtrigger_build_for_commitare merged into a singletrigger_buildtool with an optionalcommitargument. - ✅ Publish endpoints disambiguated as
#1/#2in 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
v3oromitin 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:
- 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.
- 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(defaultfalse). When false, write tools are not registered at all, so they do not appear in the tool list. This is orthogonal to the existingTOOLSETSfilter: the toolset selects the module, the flag selects read vs write. - Annotations: All write tools are registered with
readOnlyHint: false;cancel_buildandstop_publishadditionally carrydestructiveHint: true. Read tools receivereadOnlyHint: trueretroactively. Confirmation UX is the MCP client's responsibility; the server states side effects in tool descriptions.
Component 4 – Log Truncation¶
- Purpose: Prevent
get_build_logsandget_publish_step_logsfrom 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 optionalgrepcase-insensitive substring filter applied before the tail. - The envelope
metaincludestotal_lines,returned_lines, andtruncatedso the model knows it is looking at a slice. get_build_logsaccepts an optionalstepargument for per-step logs.
Component 5 – Filter Extensions¶
- Purpose: Add a
filtersparameter to the four existing profile list tools (get_build_profiles,get_distribution_profiles,get_store_profiles,get_publish_profiles), backed by the correspondingGET .../profiles/filtersendpoints. - 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
sectionsparameter (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=Trueopts back in, with affected profiles flagged underunresolved_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.
- Single tool with a
Detailed Design¶
Naming and shape decisions (items flagged in AI-36):
start_buildis renamed totrigger_build, as already struck through in the analysis.trigger_buildandtrigger_build_for_commitare merged into a singletrigger_buildtool. An optionalcommitargument (commit ID or hash) selects the commit-based endpoint variants; when omitted, the build starts from the branch with the latest commit.get_configurationsis renamed todownload_build_configuration. It returns the YAML content of a single configuration, and the original name collides with the v1 toolget_build_configuration_details.get_variable_groupsandget_variables_in_groupappear 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 thebuildandpublishtoolsets.get_testers_in_groupis 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_erroris 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_datelocally before calling the API.
Security Considerations:
- Same pass-through authentication as v1; the server never stores tokens.
- Response trimming follows the v1
EXCLUDED_PROFILE_KEYSpattern: 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 forget_testers_in_group, and write-flag gating (write tools must be absent whenAC_MCP_ENABLE_WRITE_TOOLS=false). - Integration tests: per tool under
test/integration/, gated byAPPCIRCLE_ACCESS_TOKEN. Write-tool integration tests run only against a dedicated test organization and are markedintegration_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¶
- PR 1: Filter extensions for the four existing list tools +
readOnlyHintannotations. - PR 2: Build read tools (status, last commit, logs with truncation, configuration download) + shared variables module.
- PR 3: Distribution, Store, and Publish read tools.
- PR 4: Reports module.
- PR 5: Write-tool gating infrastructure +
trigger_build(including commit variants),cancel_build. - PR 6: Distribution write tools +
start_publish,stop_publish. - PR 7: Build Insights Report tool (
get_build_insights_report) and itssrc/tools/report/build_insights/subpackage. - 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)