An MCP server and standalone CLI tool that provides cached access to Maestro/BAR (Build Asset Registry) data for the .NET build infrastructure. Exposes 20 tools for querying subscriptions, builds, channels, and health status, plus triggering subscription updates and managing cache via the Model Context Protocol. Also works as a standalone CLI (mstro) for humans — same cached data, same authentication, no AI required.
Built with Squad — meet the squad.
The Maestro/BAR API powers .NET's dependency flow infrastructure, but working with it directly is painful:
- Subscription IDs are GUIDs — you need to query by repo/channel, not memorize UUIDs
- Build staleness requires multi-step lookup — compare last-applied build vs latest available, then compute commit distance via GitHub
- No cross-process caching — every
darcinvocation hits the API fresh, even for data that changes every 5–15 minutes - Codeflow status is opaque — backflow PRs, tracked PRs, and subscription history are scattered across multiple API endpoints
mstro wraps all of this in a single tool with smart caching (SQLite, shared across all MCP clients), automatic authentication (reuses darc Entra ID credentials), and enriched output (commit distance, health scoring, freshness checks). Whether you're an AI agent investigating a stale subscription or a human triaging build flow, you get the same fast, accurate answers.
dnx (new in .NET 10) auto-downloads and runs NuGet tool packages — no install step required:
dnx lewing.maestro.mcpThis is the recommended approach for MCP server configuration (see below). MCP stdio mode is the default.
dotnet tool install -g lewing.maestro.mcpFor repo-local installation via a tool manifest:
dotnet new tool-manifest # if .config/dotnet-tools.json doesn't exist
dotnet tool install --local lewing.maestro.mcpdotnet pack src/MaestroTool
dotnet tool install -g --add-source src/MaestroTool/nupkg lewing.maestro.mcpAfter installation, mstro is available globally.
# Prerequisites: .NET 10 SDK
git clone https://github.com/lewing/maestro.mcp.git
cd maestro.mcp
dotnet buildNuGet feed requirement: The
Microsoft.DotNet.ProductConstructionService.Clientpackage is published to the dotnet-eng Azure Artifacts feed. The includednuget.configreferences this feed.
mstro is a full CLI for humans. Every query uses the same cached API layer as the MCP server.
# List subscriptions for a repo
mstro subscriptions --source-repository https://github.com/dotnet/runtime
# Get a specific subscription by ID
mstro subscription <guid>
# Check subscription health for a target repo (includes commit distance)
mstro subscription-health https://github.com/dotnet/sdk --include-commit-details
# Get the latest build for a repo on a channel
mstro latest-build https://github.com/dotnet/runtime --channel-name ".NET 10.0.1xx SDK"
# Get a specific build by ID
mstro build 302353
# List all channels
mstro channels
# List default channel mappings
mstro default-channels --repository https://github.com/dotnet/runtime
# Check build freshness via aka.ms
mstro build-freshness ".NET 10.0.1xx SDK"
# List active codeflow (backflow) PRs
mstro codeflow-prs --channel-name ".NET 10 Engineering"
# Get tracked PR for a subscription
mstro tracked-pr <guid>
# Get backflow status for a VMR build
mstro backflow-status 302627
# View subscription update history
mstro subscription-history <guid>
# Get build dependency graph
mstro build-graph 302353
# Get dependency flow graph for a channel
mstro flow-graph 1234 --days 14
# Get codeflow status (forward/backflow) for a repository
mstro codeflow-statuses https://github.com/dotnet/dotnet --branch main
# Trigger a subscription update (requires auth)
mstro trigger-subscription <guid> 302353
# Trigger all daily subscriptions (requires auth)
mstro trigger-daily-update
# Cache management
mstro cache status
mstro cache clearAll commands support --json for structured output and --no-cache to bypass the cache.
mstro automatically detects how it's launched:
| Context | Behavior |
|---|---|
| Terminal (no args) | Shows --help with available commands |
| MCP host (stdin piped) | Starts MCP server mode automatically |
| With subcommand | Runs the specified CLI command |
This means existing MCP configurations ("command": "mstro" with no args) continue to work unchanged — mstro detects the piped stdin and enters MCP server mode.
Add the following to your MCP client config. The --yes flag ensures dnx doesn't prompt for confirmation:
{
"servers": {
"maestro": {
"type": "stdio",
"command": "dotnet",
"args": ["dnx", "--yes", "lewing.maestro.mcp"]
}
}
}If you've installed
lewing.maestro.mcpas a global tool, you can use"command": "mstro"with"args": []instead ofdnx.
{
"servers": {
"maestro": {
"type": "stdio",
"command": "dotnet",
"args": ["dnx", "--yes", "lewing.maestro.mcp"],
"env": {
"MAESTRO_BAR_TOKEN": "your-token-here"
}
}
}
}dotnet run --project src/MaestroTool.McpThe HTTP server listens on http://localhost:5000 by default.
| Client | Config file | Top-level key |
|---|---|---|
| GitHub Copilot CLI | ~/.copilot/mcp.json |
servers |
| VS Code / GitHub Copilot | .vscode/mcp.json |
servers |
| Claude Desktop (macOS) | ~/Library/Application Support/Claude/claude_desktop_config.json |
mcpServers |
| Claude Desktop (Windows) | %APPDATA%\Claude\claude_desktop_config.json |
mcpServers |
| Claude Code / Cursor | .cursor/mcp.json |
mcpServers |
The server implements a 3-tier authentication cascade:
- Explicit PAT — Set
MAESTRO_BAR_TOKENenvironment variable - Cached Entra ID — Reuses credentials from
darc authenticate(~/.darc/.auth-record-*) - Anonymous — Read-only fallback (may be rate-limited)
Recommended: Run darc authenticate once (from arcade-services) to cache credentials, then rely on automatic Entra ID authentication.
The server includes action tools for triggering subscription updates. These are non-destructive operations — they trigger processing but don't delete or modify subscription configuration.
Action tools include built-in deduplication with a 2-minute cooldown. If the same action is triggered again within the cooldown window, the server returns a notice instead of re-executing. This prevents duplicate triggers from LLM retries or multiple concurrent skills.
Future versions may include destructive actions (delete subscription, remove default channel, etc.). These will be disabled by default and require an explicit opt-in:
{
"servers": {
"maestro": {
"type": "stdio",
"command": "dotnet",
"args": ["dnx", "--yes", "lewing.maestro.mcp"],
"env": {
"MAESTRO_ENABLE_DESTRUCTIVE_ACTIONS": "true"
}
}
}
}The server registers 20 MCP tools for querying and triggering Maestro/BAR operations:
| Tool Name | Description | Key Parameters |
|---|---|---|
maestro_subscriptions |
List all subscriptions | sourceRepository, targetRepository, channelName, targetBranch (all optional filters); noCache: bypass cache |
maestro_subscription |
Get a subscription by GUID ID, with health diagnostic | subscriptionId: UUID; noCache: bypass cache |
maestro_latest_build |
Get the latest build for a repository, optionally filtered by channel name | repository: repo URL; channelName (optional); noCache: bypass cache |
maestro_build |
Get a build by its BAR build ID | buildId: build ID; noCache: bypass cache |
maestro_builds |
List builds, optionally filtered by repository, channel, commit, or build number | repository, channelName, commit, buildNumber, count (all optional); noCache: bypass cache |
maestro_channels |
List all channels | noCache: bypass cache |
maestro_channel |
Get a specific channel by ID or name | channelId: channel ID (integer) or channel name (string); noCache: bypass cache |
maestro_default_channels |
Get default channels for a repository | repository: source repository URL; noCache: bypass cache |
maestro_subscription_health |
Check subscription health for a target repository — detects stale subscriptions | targetRepository: repo URL; noCache, includeCommitDetails, validate (optional bools) |
maestro_build_freshness |
Check build freshness for a channel via aka.ms redirect | channel: channel short name (e.g. '10.0.1xx'); noCache: bypass cache |
maestro_trigger_subscription |
Trigger a subscription — provide buildId directly, or sourceRepository + channelName to auto-resolve |
subscriptionId: UUID; buildId (optional); sourceRepository, channelName (optional, for auto-resolve); force (optional bool) |
maestro_trigger_daily_update |
Trigger all daily-update subscriptions | None |
maestro_codeflow_prs |
List active codeflow (tracked) pull requests managed by Maestro | channelName (optional); noCache: bypass cache |
maestro_codeflow_pr |
Get the tracked PR for a specific subscription | subscriptionId: UUID; noCache: bypass cache |
maestro_backflow_status |
Get backflow status for a VMR build | vmrBuildId: build ID; noCache: bypass cache |
maestro_subscription_history |
Get update history for a subscription — shows timestamped actions and errors | subscriptionId: UUID; noCache: bypass cache |
maestro_build_graph |
Get the full dependency graph for a build | buildId: BAR build ID; noCache: bypass cache |
maestro_flow_graph |
Get the dependency flow graph for a channel | channelId: channel ID; days: lookback (default 7); includeArcade, includeBuildTimes, includeDisabledSubscriptions (optional bools); noCache: bypass cache |
maestro_codeflow_statuses |
Get codeflow status (forward flow and backflow) for a repository and branch | repositoryUrl: repo URL (default: VMR); branch: branch name (default: main); noCache: bypass cache |
maestro_clear_cache |
Clear the shared SQLite cache | None |
Tools follow a consistent naming pattern:
maestro_{resource} # get one by ID (e.g. maestro_build)
maestro_{resources} # list (e.g. maestro_builds)
maestro_{resource}_{aspect} # detail query (e.g. maestro_subscription_health)
maestro_{verb}_{resource} # action (e.g. maestro_trigger_subscription)
All read tools accept an optional noCache boolean parameter. When set to true, the cached entry for that request is invalidated before fetching, guaranteeing a fresh API call. Use this after triggering actions or when investigating rapidly changing state.
The maestro_clear_cache tool provides a full cache reset — useful when doing bulk operations or debugging stale data.
The maestro_codeflow_prs, maestro_codeflow_pr, maestro_backflow_status, and maestro_subscription_history tools expose the same PCS PullRequest APIs used by the VMR codeflow tools (see dotnet/dotnet#4952). These tools enable visibility into codeflow PRs, backflow status, and subscription update history without requiring direct VMR access.
All read tool responses include a retrieval timestamp header indicating when the data was fetched and whether it came from cache or a fresh API call:
_Retrieved: 2026-02-18 15:30:45Z (cached)_
Beyond the raw Maestro/PCS APIs, this MCP server provides several value-added enhancements to improve visibility and decision-making:
Subscription Health Scoring — Compares each subscription's last-applied build against the latest available build on its channel, calculating exact BuildsBehind count. For GitHub-hosted source repos, the GitHub Compare API computes precise commit distance. For Azure DevOps repos, AzDo commit APIs provide detailed commit histories with author, message, and date.
Build Freshness via aka.ms — Resolves aka.ms short links to their final blob storage destination, then inspects the Last-Modified HTTP header to determine when a channel was last updated — without downloading artifacts. Includes SSRF protection against internal network redirects.
Channel Name Resolution — All tools accepting channelName perform automatic case-insensitive lookup to resolve human-friendly names (e.g., ".NET 10.0.1xx SDK") to numeric channel IDs. Callers never need to look up IDs manually.
Backflow Status Aggregation — For a given VMR build, aggregates per-branch backflow status including commit distance and subscription details across all target repositories.
State Oscillation Detection — Detects stuck subscriptions by analyzing update history for alternating ApplyingUpdates ↔ MergingPullRequest state cycles (per arcade-services#6090). Runs automatically for stale subscriptions during health checks.
Source-Manifest Tracing — For subscriptions targeting dotnet/dotnet, reads src/source-manifest.json from the VMR to determine what commit the VMR actually consumed, cross-referencing against the subscription's last-applied build.
Tracked PR Diagnosis — For stale subscriptions with tracked codeflow PRs, checks the GitHub PR state (merged-but-not-cleared, closed-but-not-cleared, blocked-by-CI, or active) to explain why the subscription is stuck.
Flow Graph Visualization — Formats the dependency flow graph with build times, longest-path critical path indicators, and channel routing information for end-to-end dependency analysis.
Human-Readable Formatting — All tool responses use emoji status indicators (✅ Current,
The project is split into three layers:
src/
├── MaestroTool/ # CLI tool + stdio MCP server (dotnet tool)
│ └── Program.cs
├── MaestroTool.Core/ # Shared library — Maestro API logic + MCP tool definitions
│ ├── MaestroMcpTools.cs # MCP tool definitions ([McpServerToolType])
│ ├── MaestroService.cs # Cached business logic
│ ├── CacheService.cs # SQLite-backed cross-process cache
│ ├── IMaestroApiClient.cs # API abstraction
│ └── MaestroApiClient.cs # PCS client wrapper with auth cascade
├── MaestroTool.Mcp/ # MCP HTTP server (ASP.NET Core)
│ └── Program.cs
└── MaestroTool.Tests/ # Unit tests (167 tests)
- MaestroTool — Dual-mode entry point: standalone CLI for humans and stdio MCP server for AI agents. Packaged as a dotnet tool. Default entry point for
dnx/dotnet toolusage. - MaestroTool.Mcp — HTTP MCP server for remote/shared deployments.
- MaestroTool.Core — All business logic, caching, API client, and MCP tool definitions. Shared by both hosts.
Multiple mstro instances (e.g., VS Code, Copilot CLI, and Claude Desktop running simultaneously) share a single SQLite cache at ~/.mstro/cache.db. This eliminates redundant PCS API calls across clients.
graph TB
subgraph "MCP Clients"
VSCode["VS Code<br/>Copilot Extension"]
CLI["GitHub<br/>Copilot CLI"]
Claude["Claude<br/>Desktop"]
end
subgraph "mstro Instances (separate processes)"
M1["mstro<br/>MaestroMcpTools<br/>↓<br/>MaestroService"]
M2["mstro<br/>MaestroMcpTools<br/>↓<br/>MaestroService"]
M3["mstro<br/>MaestroMcpTools<br/>↓<br/>MaestroService"]
end
subgraph "Shared State"
DB[("~/.mstro/cache.db<br/>SQLite (WAL mode)<br/>─────────────<br/>cache table (data)<br/>actions table (dedup)")]
end
subgraph "External API"
PCS["Maestro / PCS API<br/>maestro.dot.net"]
end
VSCode -- "stdio" --> M1
CLI -- "stdio" --> M2
Claude -- "stdio" --> M3
M1 -- "read/write" --> DB
M2 -- "read/write" --> DB
M3 -- "read/write" --> DB
M1 -. "on cache miss" .-> PCS
M2 -. "on cache miss" .-> PCS
M3 -. "on cache miss" .-> PCS
style DB fill:#f9f,stroke:#333,stroke-width:2px
style PCS fill:#bbf,stroke:#333
Key design decisions:
- WAL (Write-Ahead Logging) mode enables concurrent reads across processes without blocking
- Separate tables for data cache and action dedup —
maestro_clear_cacheonly clears data, trigger cooldowns survive - Per-key TTL expiration with periodic cleanup of expired rows
- 10,000 entry cap with auto-eviction when capacity is reached
The CacheService uses SQLite with WAL mode for cross-process cache sharing. All mstro instances share ~/.mstro/cache.db:
| Data Type | TTL | Reason |
|---|---|---|
| Subscriptions (list) | 5 minutes | Subscriptions change infrequently |
| Latest builds (by channel/repo) | 5 minutes | Builds are published frequently |
| Channels (list) | 15 minutes | Channels are rarely added/removed |
| Build by ID | 30 minutes | Builds are immutable once published |
| Build freshness (derived) | 10 minutes | Computed; cached to avoid repeated API calls |
TTLs are configurable in CacheService if stricter freshness is needed. Expired entries are automatically cleaned up periodically.
Run the test suite:
dotnet testThe test suite includes:
- 167 unit tests covering
CacheService,MaestroService, security hardening, and tool behavior. - Framework: xUnit + NSubstitute for mocking.
- Coverage: cache hit/miss, TTL expiration, null handling, error scenarios, commit distance, SSRF validation, corruption recovery.
Tests are located in src/MaestroTool.Tests/.
MIT
For questions or issues, please open an issue in the repository. When modifying the authentication logic or adding new tools, ensure all tests pass and update this README accordingly.