Skip to content

ArcGIS Pro MCP Studio v0.3.0

Choose a tag to compare

@github-actions github-actions released this 03 Oct 14:24
· 20 commits to main since this release

Second development preview: typed MCP results with output schemas, resources and prompts; geoprocessing discovery, risk tiers and dry runs; approval for project and feature edits; deterministic agent evaluations; synthetic CC0 test data; a live operation matrix over every operation; and committed live ArcGIS Pro acceptance evidence, including the first full operation matrix run. Unsigned; not a production release.

Added

  • Live acceptance tooling and committed evidence. tools/run-acceptance.ps1 (Windows PowerShell 5.1 compatible) records the commit, .NET and ArcGIS Pro facts, and the hashes of the built and loaded add-in DLLs, runs the live harnesses by section, and with -Commit writes docs/acceptance/<date>-<sha7>/ (manifest.json, summary.md, SHA256SUMS). Committed manifests list only evidence the folder contains, with paths relative to it ("evidencePathsRelative": true), and the committed stress/summary.json names each layout capture by its committed image (images/layout-<case>.png). The script rejects an explicit -PipeName that is not a live ArcGIS Pro discovery record, reports ambiguous cached add-in DLLs, and records -VisuallyInspected notes (visuallyInspected[]) and their -VisualNotesSource, and -RunNotes disclosures about how the run was carried out (runNotes[] and a "Run notes" section of summary.md). A hand-written ANNEX.md at a folder root, outside SHA256SUMS and titled as added after the run, is the only file allowed in a committed folder after the run. docs/acceptance/README.md defines the folder contract; a Core test validates every committed manifest and requires each listed evidence path to exist (folders that predate relative paths are exempt). The first two evidence runs, on 2026-09-28 and 2026-09-29, covered verify, the loaded-DLL match, host probe, the MCP smoke test and the three bundled urban workflows x 3 runs on ArcGIS Pro 3.7.1 in default mode, with layouts visually inspected. They used the earlier third-party test data, so their folders are kept in the private development archive only (see Public history). Autonomous mode and the feature/GP/ArcPy section are not covered.
  • First full live operation matrix evidence: docs/acceptance/2026-10-03-e7deee2 (commit e7deee2, ArcGIS Pro 3.7.1.1904, default mode) adds the operation matrix to verify, the DLL match, smoke and 3 workflows x 3 runs: 41/41 operations covered, 90/90 cases passed, eight dockpane approval cards (7 approved, 1 denied). The cards were decided by Claude through computer use at the maintainer's direction, not by the maintainer's own clicks; the first project.open card expired and was re-queued, and ArcGIS Pro's modal "Save all edits?" prompt during the final project.open was answered by the same means, which is what saved the matrix's feature edits (annex). Autonomous mode and the separate feature/GP/ArcPy section are not covered.
  • ARCGIS_PRO_MCP_REVISION_LOG=1 diagnostics: the host logs every workspace revision change with the state behind it (project URI, map and layout names), the event behind each revision advance and the outcome and timing of every post-write settle (settle-ok, settle-unsettled or settle-timeout with drain and sampling milliseconds, sample count and published revision), to one file per ArcGIS Pro process. The log stops growing at 50 MB, and log lines are not built while logging is off. tools/analyze-revision-log.py (standard library only) attributes each settle to the audited operation that wrote it and reports, per operation, the settle count, budget-cap rate, unsettled count and p50/p95 drain, sample and total times. See deployment.
  • SECURITY.md with supported versions, private reporting through GitHub security advisories, and a threat model summary.
  • CONTRIBUTING.md with prerequisites, build, test and packaging commands, and pull request conventions.
  • This changelog and a public roadmap.
  • Bundled workflows are embedded in ArcGISProMCP.Core and seeded into %LOCALAPPDATA%\ArcGISProMCP\workflows at start-up, so workflow_list is populated on a fresh install. Seeding is a no-op once a workflow is present; existing files are never overwritten.
  • Workflow operation allowlists: an optional allowedOperations list on workflows, enforced on save and run and reported as the validation issue operation_not_allowed. Operation descriptors gain executesUserCode (true for gp.run and arcpy.run-script), which registry_search and registry_browse entries also report. Allowlist entries are operation ids and are not version-pinned.
  • registry_search accepts capabilities and maxRisk filters.
  • Approval cards show a "Runs user code" warning for arcpy.* runs and for gp.run requests that use a custom toolbox (.pyt/.atbx/.tbx) or a Python expression; matching gp.run results carry a user_code_execution notice.
  • Audit records gain kind (operation, dry-run or approval), autonomousBypass, decision and actor. Unknown operation ids and every local approve/deny decision are now audited. An audit write failure for an unknown operation id adds the audit_write_failed notice like every other invocation, and a failed approval audit write in the dockpane is reported in the activity feed.
  • Audit log rotation: operations.jsonl rotates once it exceeds 16 MiB and the newest five rotated files are kept. The log is appended through a handle that shares read, write and delete access with other processes; if rotation fails (for example while another ArcGIS Pro process holds the file), it is skipped and the record is still appended.
  • MCP resources: arcgis://project/state plus templates for operations, workflows (id@version), skills and observation handles.
  • MCP prompts: one per bundled skill (skill.<id>) and, while ArcGIS Pro is running, one per saved workflow (run.<id>) with arguments from the workflow parameters.
  • Typed tool results: every tool returns { ok, result, error } as structuredContent (and the same JSON as its text block) with an outputSchema, and sets isError on failure with error = code, message, retryable, revision. registry_invoke and workflow_run results with success: false are errors that keep result. Bridge results are typed records shared by the add-in and the gateway (ArcGISProMCP.Bridge.Protocol.BridgeContracts).
  • Every tool declares readOnlyHint, destructiveHint, idempotentHint and openWorldHint (always false). Only registry_invoke and workflow_run are destructive.
  • registry_describe returns resultSchema, the JSON schema of the operation's registry_invoke result envelope. Its data member is the operation's outputSchema, now declared by 15 stable-shape operations (project.get, map.list, layer.list, feature.*, table.query, table.statistics, metadata.get, layout.list, layout.inspect, view.capture); data stays unconstrained for the rest, including gp.*.
  • registry_invoke accepts dryRun: the operation is validated statically and never executed, needs no revision or approval token, and neither queues a review nor consumes a token. A completed dry run is not isError even when it reports valid: false. Combining dryRun with idempotencyKey fails with dry_run_idempotency_conflict.
  • approval_status accepts waitSeconds (0-120) to wait for the dockpane decision instead of polling. Each wait holds a pipe slot, so the host allows at most two concurrent waits; further calls return the current status immediately with a waitNotice asking the client to poll.
  • tests/ArcGISProMCP.Server.Tests: in-process MCP client/server harness with a scriptable fake bridge and tools/list, resources/list and prompts/list snapshots (UPDATE_SNAPSHOTS=1 regenerates them).
  • Geoprocessing operations gp.search and gp.describe (ReadOnly) over the installed system toolbox metadata, returning execution names, risk tiers and the positional parameter signature gp.run expects.
  • gp.query (ReadOnly, no review) runs only the allowlisted read-only system tools management.GetCount, management.GetRasterProperties and management.GetCellValue, resolved against the system toolboxes, with no map outputs, no overwrite and no history.
  • Geoprocessing risk tiers (UserCode, Destructive, ConsumesCredits, ReadOnlyQuery, Standard). Approval cards for gp.run say "Modifies/deletes input data in place" or "Consumes ArcGIS Online credits"; results carry gp_mutates_input, gp_consumes_credits or gp_tool_not_indexed notices.
  • Static gp.run dry runs: operations can implement IDryRunnableOperation; gp.run dry runs validate parameters against the catalog and report the tier, confirmation, user-code and autonomous-refusal flags without executing. ArcGIS Pro has no validate-only geoprocessing API, so this is static validation, not the tool's own validation.
  • The registry now has 41 operations (39 without the opt-in ArcPy pair); the MCP tool count stays 16.
  • Draft MCP registry packaging: dotnet pack on ArcGISProMCP.Server now produces the ArcGISProMCP.Gateway NuGet dotnet tool (package type McpServer, command arcgis-pro-mcp) with .mcp/server.json for io.github.nicoazel/arcgis-pro-mcp and the bundled skills. tools/pack-gateway.ps1 packs into artifacts/packages and verifies the package; nothing is published. The single-file release bundle is unchanged. See deployment.
  • Agent evaluations (evals/, see evals/README.md): deterministic suites that run in every dotnet test without model calls. E1 routes 30 natural-language tasks through registry_search over the add-in's 41 real descriptors; E2 routes 30 tasks through gp.search over the installed ArcGIS Pro system toolboxes (skipped when Pro is absent) plus a 5-task subset over synthetic toolboxes that always runs. Held-out sets (16 + 16 tasks) were written before search tuning. Each suite reports recall@1, recall@5, MRR and misses, fails when one more task misses the top 5 than at the baseline in evals/baseline.json or MRR drops more than 0.05 below it, and can write a scorecard to evals/results/<date>-<sha7>/. A Python live harness (evals/live/) runs a Claude model against a running gateway by hand; it is not part of CI.
  • MCP contract tests in tests/ArcGISProMCP.Server.Tests: an initialize snapshot, the bridge method and parameter shape of every tool (snapshot), success and error envelopes of every tool validated against its outputSchema, and malformed-call handling.
  • tests/ArcGISProMCP.Operations.Tests: behavior tests for the portable operations over fake ArcGIS services ("fake Pro": real descriptors, registry and executor), and a guard that every portable descriptor is identical to what the add-in registered before the operations moved.
  • tests/ArcGISProMCP.Testing shared test library: FakePro, the scenario loader, FakeHostRuntime running the add-in's real bridge request handler, and InProcessBridgeClient. Not shipped.
  • End-to-end tests (MCP client -> gateway -> bridge handler -> registry/executor -> fake ArcGIS services) covering the approval chain for feature.delete, feature.update and project.save; gp.search, gp.describe, gp.run dry run and run; autonomous refusal; and stale-revision and workspace_changed handling.
  • tools/ArcGISProMCP.FakeHost development and eval host that serves a scenario project over the real named pipe with a discovery record (interactive y/n approvals; --auto-approve for eval runs only; --autonomous, --toolboxes), plus the tools/test-fakehost.ps1 smoke test. Not shipped.
  • E3 golden trajectories (evals/trajectories, 8 tasks, 4 of which exercise approval) replayed against the end-to-end server and graded on schemaValidArgs, approvalDiscipline (null for a trajectory with no gated invoke; one approval covers one invoke; gated invokes must carry expectedRevision) and taskSuccess; evals/README.md explains how to run live evals against FakeHost.
  • Synthetic test data. tests/data is now generated by tools/create-synthetic-test-data.py (ArcPy, fixed seed 2272; ProposedMassing derived through tools/create-urban-massing-fixture.py) and dedicated to the public domain under CC0 1.0. It replaces the earlier third-party-derived set (git history is not rewritten) and keeps every dataset name, field, geometry type and value the bundled workflows, the stress runner and the tests rely on; tests/data/expected-statistics.json records the feature counts and sums. tests/data/README.md documents the provenance, licence, coordinate system and layout, and a Core test checks that each shapefile set is complete, that only generated files are present, that no local or drive-letter path (:\) and no third-party, vendor or place name is embedded in the generated data (SHP/, MasterPlan.gdb/, expected-statistics.json), and that the expected statistics are consistent. The generator scans the build and the README before publishing, and validates the extent window, the DesignSites GlobalID field and the Z-enabled massing.
  • Live operation matrix: tools/run-live-operations.ps1 with its ordered plan tools/live-operations-plan.json exercises all 41 operations against a disposable project in default mode (happy paths plus negative cases: invalid arguments, stale revision, missing and broken layers, confirmation_required without a token, dry runs), asks the operator to approve or deny eight review cards in the dockpane, and writes per-call request/result JSON, summary.json and errors.md with a message audit. It refuses a host in autonomous mode and a project outside -DisposableRoot, copies test data instead of touching tests/data, and supports -PlanOnly, -SkipCards (a pre-run without an operator; card cases are recorded as skipped, the summary records skipCards: true, so the section cannot pass), -Offline and -OperatorTimeoutSeconds. The script exits 1 unless the summary reports allPassed; an aborted run records aborted and never passes. Each card case counts one decision however often its card was queued (queued counts every request), an approved DENY card has its token cancelled before the case fails and is counted as approvedDenyCard, a refused approval request is counted as requestFailed (neither as approved nor skipped), a gateway that times out is stopped and restarted on the next call, and the broken-layer case is skipped with the locked files named when ArcGIS Pro keeps the shapefile open. tools/run-acceptance.ps1 gains the operations section; a Core test checks that the plan covers every operation descriptor, that a passed operations step comes with operations/summary.json, and that committed operation summaries show a live host, no -SkipCards pre-run, no abort, one decision per card case, no failed case and full coverage.
  • The ArcPy scripts the live feature/GP/ArcPy acceptance script expects (hello.py, nonzero.py, timeout.py, setup_acceptance.py) are committed under tools/arcpy-scripts, so the configured ArcPy script root can point at the repository.
  • GitHub issue forms (bug report, feature request), a pull request template and CODE_OF_CONDUCT.md (Contributor Covenant 2.1). README badges for CI, licence and status.
  • A static GitHub Pages landing page (site/) and .github/workflows/pages.yml, which deploys site/ with SHA-pinned actions on pushes to main that change it, or by hand, once the repository variable PAGES_ENABLED is true.
  • -SkipTests for tools/verify-release.ps1 and tools/package-release.ps1 (CI packages without re-running tests). pack-addin.ps1, verify-release.ps1 and package-release.ps1 declare #Requires -Version 7.0.
  • Tag-triggered release workflow (.github/workflows/release.yml): a pushed vX.Y.Z or vX.Y.Z-rc.N tag on main whose version matches Directory.Build.props, server.json and Config.daml runs tools/package-release.ps1 with the full test suite and creates a draft GitHub release (a prerelease for -rc.N) with the bundle, SHA256SUMS and notes from this changelog (tools/extract-release-notes.ps1). A final release needs a dated changelog heading. The maintainer reviews and publishes the draft.
  • Stable error codes instead of operation_failed for common caller mistakes. A missing map, layer, layout or map frame fails with map_not_found, layer_not_found, layout_not_found or frame_not_found (raised by the shared add-in resolvers; the fake services used in tests match). An argument value the project does not accept fails with invalid_arguments: an unknown basemap, a layer that is not a feature layer or has the wrong geometry type for symbology.*, label.configure, table.* or layer.set-elevation, an unknown field for table.* or symbology.set-unique-values, an existing element of another type in layout.ensure-surround, or a feature.* argument the layer does not accept (an unknown or read-only field, an attribute value that does not fit its field, a geometry of the wrong type or with too few positions, a malformed target, envelope, spatial relationship or selection mode). The live operation matrix asserts invalid_arguments for its three feature.create negative cases. Operations raise it explicitly through the new Core OperationException.InvalidArgument. A plain ArgumentException from an operation stays operation_failed: ArcGIS SDK calls also throw it for internal failures, possibly after a change, and invalid_arguments tells a client that nothing ran. Layout elements placed off the page fail with element_outside_page (see Fixed). Found by the live operation matrix.

Changed

  • Breaking: in autonomous mode gp.run refuses Destructive and UserCode tools, tools the toolbox catalog cannot classify (fail closed), and requests flagged as running user code, with destructive_tool_requires_review, unless the request carries a person-issued approval token. Curated destructive tools (management.Delete, Delete_management, ...) are classified Destructive even when the catalog has not indexed them. A request that carries a token takes the normal confirmation path: a valid token runs it (audited with autonomousBypass: false) and an invalid one fails with confirmation_required without falling back to the bypass. The refusal message says to request local review with approval_request and retry with the token.
  • Breaking: tool text content is now the { ok, result, error } envelope rather than the bare bridge result; read result (or structuredContent.result). Failed bridge calls are isError results instead of protocol errors, and skill_get for an unknown id returns skill_not_found. registry_browse and workflow_run results now always carry all their members (unused ones are null). Enum fields such as risk remain numbers.
  • Breaking: project.open, project.save and feature.update now require a local-review approval token (or autonomous mode). Dockpane buttons that trigger them approve through the same audited approval queue.
  • Breaking: a workflow whose workspace revision changes mid-run now stops with workspace_changed (reporting stoppedAtStep, stepIndex, expectedRevision, currentRevision) instead of retrying the step against the new revision. continueOnError does not override this, and completed steps are not rolled back.
  • Breaking: saved workflows can no longer use gp.run or arcpy.run-script unless their allowedOperations lists them. When allowedOperations is present it is exhaustive.
  • Breaking: skill manifests without a non-empty allowedOperations are skipped when loading. The bundled master-cartography skill no longer lists project.save.
  • Breaking: resource.read payloads use camelCase field names (mimeType, name, createdAt) instead of PascalCase.
  • tools/test-mcp.ps1 checks every tool's hints and envelope outputSchema and reads results from structuredContent.
  • Registry search ignores articles and prepositions in queries.
  • Tool descriptions no longer over-promise: registry_validate is described as a schema and revision check that does not resolve layers or paths, and the registry_search limit is documented as 1–100 to match the registry clamp.
  • JsonLineAuditLog moved to ArcGISProMCP.Core.Infrastructure, and the Add-In resource store facade is renamed ProResourceStore.
  • The live feature/GP/ArcPy acceptance script documents that it requires autonomous mode and asserts the autonomous_control notice for feature.update and project.save.
  • One release status across all documentation: development preview; supported configuration is an interactive same-user workstation with dockpane approvals; autonomous mode is an opt-in expert setting, not recommended.
  • docs/production-readiness.md merged into docs/deployment.md, which now also covers the implemented surface, acceptance evidence and known limits. The 2026-09-09 live pass is described as local maintainer notes; reviewable evidence is committed under docs/acceptance/.
  • docs/security.md describes the implemented per-PID multi-instance discovery, states that gp.run can execute arbitrary Python (Python toolboxes, script tools, Calculate Field expressions) without ArcPy, and documents that workflows cannot execute confirmation-gated steps in default mode.
  • The release bundle no longer includes the roadmap in its docs/ folder.
  • Bundled workflows are now version 1.2.0 and no longer end with project.save; save the project with an explicit project.save call. Legends and scale bars sit at least 0.35 in above the bottom edge of their map frame, clear of the basemap attribution (the mixed-use legend moves to the scene frame), and a Core test checks surround placement statically.
  • Dependencies: Microsoft.Extensions.Hosting 10.0.12 and Microsoft.NET.Test.Sdk 18.10.1. Dependabot ignores semver-major updates of xunit.v3 and xunit.runner.visualstudio, whose 4.x releases move to Microsoft.Testing.Platform, until a deliberate migration.
  • Operation input schemas are built with a typed JsonSchemas builder that self-checks each schema at startup; the string-based JsonSchemas.ObjectSchema helper is removed. Migrated schemas are semantically identical to the originals, guarded by a captured fixture.
  • Operations seam: a new Esri-free ArcGISProMCP.Operations project holds the operation base class, every operation schema, and 21 of the 41 operations (project.*, map.*, layer.list, view.capture, feature.*, gp.*, arcpy.*), which call ArcGIS Pro through synchronous DTO services (IProjectService, IMapService, ILayerService, IViewCaptureService, IFeatureService, IGeoprocessingService) implemented in the add-in. Descriptors are unchanged. Layout, symbology, table, metadata and the remaining layer operations stay in the add-in. Source-text tests for the moved operations are replaced by behavior tests against fakes; the remaining source-text tests cover code that still needs ArcGIS Pro or WPF.
  • registry_search and gp.search rank with a shared search index: punctuation stripping, camel-case splitting, light stemming, whole-word over prefix over compound matches, a softened inverse document frequency and a small synonym map of general GIS vocabulary. gp.search gives the core system toolboxes a modest prior. The held-out gains come from the matching changes (stemming, camel-case splitting, IDF, the core-toolbox prior); an ablation showed that synonyms only moved the main suites' recall@5 (held-out recall@5 was the same with or without them), so synonym entries fitted to single main-suite tasks were removed. Measured recall@5 before tuning -> now: registry-search 0.833 -> 0.867, gp-search 0.667 -> 0.900, held-out registry-search 0.938 -> 0.938, held-out gp-search 0.688 -> 0.875 (gp suites against ArcGIS Pro 3.7.1; ablation table in evals/README.md).
  • feature.query and feature.select read a light layer schema (IFeatureService.Schema) instead of the full description, so they no longer evaluate editability before a read. Results are unchanged.
  • tools/package-release.ps1 fails if development-only hosts (FakeHost) or test support (ArcGISProMCP.Testing, scenario files) reach the release archive.
  • CI: GitHub Actions are pinned to commit SHAs; pushes run only for main and v* tags, and the concurrency group uses the PR number; the workflow is split into build-test, package and lint jobs with read-only permissions; test projects are discovered instead of listed, so new test projects run without editing the workflow; build-test runs the FakeHost smoke test, and the package job does not re-run the tests; Dependabot updates NuGet packages and Actions weekly; PSScriptAnalyzer (clean across tools/) and ruff (tools/ and evals/) lint the scripts.
  • layout.ensure-surround anchors the surround at x, y and lets ArcGIS Pro size it from its style. A scale bar keeps its style height and may round its width to whole divisions (accepted from half to one and a half times the requested width); legends and north arrows still fit inside the requested box. bounds reports the actual size, and a surround_resized notice says when it differs from the request. A newly created surround that does not converge is removed again.

Fixed

  • project.open hung while the current project had pending feature edits: ArcGIS Pro showed its modal "Save all edits?" prompt and the MCP call waited until a person answered it. project.open now refuses with pending_edits when the project has unsaved feature edits and otherwise with unsaved_project_changes when it has unsaved project changes, before anything is opened: a live spot check on ArcGIS Pro 3.7.1 showed the modal "Save changes to ?" prompt on every dirty project, including after map.ensure on a just-saved project and on an untouched project, which ArcGIS Pro marks dirty right after opening it. In practice project.open therefore usually needs a project.save first. The refusals are checked through the new Core IExecutionPrecondition before the executor validates the approval token, so they do not spend the token (the operation re-checks when it runs). project.save now saves pending feature edits before the .aprx, reports editsSaved, and fails with edits_not_saved (leaving the .aprx unsaved) when ArcGIS Pro cannot save them; its description no longer claims that saving the .aprx persists every pending change. Found by live acceptance (the operation matrix's final project.open).
  • feature.query and feature.select returned objectId: -1 and globalId: null, and feature.select could match nothing, whenever specific fields were requested: the query only fetched the requested columns. The ObjectID and GlobalID columns are now always fetched. Found by the live operation matrix.
  • The project's unsaved (dirty) flag is no longer part of the workspace revision. ArcGIS clears it about 2.5 s after project.save returns, which invalidated an approval granted for the next call (project.open in the live matrix). Edits made in Pro still advance the revision through host events, but an edit that raises none of the subscribed events (layer order, standalone tables, map properties, layout page setup, bookmarks, project items, which map a map frame shows) no longer invalidates approvals, even on a just-saved project (known gaps).
  • Workflows stopped with workspace_changed right after layer.add in live ArcGIS Pro, because deferred ArcGIS notifications arrived after the 50 ms settle window. The revision log showed the cause: ArcGIS raises layout element and map member events as echoes of an MCP write up to about half a second after it returns. Writes now settle by draining the host once (main CIM thread, ArcGIS UI dispatcher at application-idle, main CIM thread again), then waiting until ArcGIS has raised no workspace event for 300 ms (the operation matrix pre-run caught a layout element echo of a data-source swap arriving 6 ms after a revision-quiet window), and then for a 150 ms quiet period (three unchanged samples 50 ms apart). The 3 s settle budget bounds this waiting and is cancellable; when it runs out the write publishes a plain snapshot. Every snapshot is read on ArcGIS Pro's main CIM thread, so the budget does not bound how long a write takes to return: while Pro is busy (for example loading a newly added layer), that read waits for it, and one write after layer.add took about 30 s in a live run. The revision log records every settle's outcome and timing. Both early evidence runs (2026-09-28 and 2026-09-29, kept in the private development archive only) passed 9/9 (3 workflows x 3 runs). Map member events whose only hints are ConnectionStatus or Expansion no longer advance the revision, and neither do layout element events ArcGIS raises by itself (map frame navigation and activation, legend/scale bar/north arrow refreshes, map frame property refreshes), which a live stress run showed arriving about 1 s after symbology.set-simple returned. Which view has focus is no longer part of the workspace revision, so pane switches no longer cause spurious workspace_revision_mismatch. Found by live acceptance.
  • feature.* operations failed on shapefile layers with "The definition of the table ... does not support this operation": ArcGIS throws from HasGlobalID() for shapefiles instead of returning false. Found by live acceptance; the GlobalID check now treats the two exceptions the SDK documents for it, NotSupportedException and GeodatabaseException, as no GlobalID.
  • layer.add with name reused an existing same-named layer even when its data source was broken or pointed at a different dataset, and echoed the requested source for it (Created: false), so the next step failed. A same-named layer whose data connection is healthy is now never removed: it is reused when its path matches the request (ignoring case, trailing separators, / vs \ and an optional .shp) or names the same workspace and dataset (a file geodatabase feature class with or without its feature dataset), reused with a source_unverified warning when ArcGIS reports no dataset path (for example service layers), and otherwise only repaired in place (Repaired: true, symbology kept) or left unchanged with a layer_source_mismatch warning. Only a broken layer is repaired in place or, as a last resort, removed and re-added at the same position (Replaced: true), with a layer_repaired notice. An in-place swap counts as successful when the layer is no longer broken, and any remaining path difference is reported rather than triggering a re-add. Source is the path the layer actually reads, with the request in RequestedSource, plus DataSourceStatus (ok, broken, unverified, mismatch). The swap keeps the shapefile folder or file geodatabase open until ArcGIS has replaced the data source. Found by live ArcGIS Pro acceptance.
  • Operations on a layer with a broken data source (feature.*, table.query, table.statistics, symbology.set-simple, symbology.set-unique-values, layout.set-frame-extent) failed with operation_failed "Object reference not set to an instance of an object." They now fail with layer_data_source_unavailable and a message naming the layer and how to repair it. Operations can raise the new Core OperationException(code, message), which the executor reports with its own code; codes the executor, workflows, bridge or gateway report themselves (confirmation_required, workspace_revision_mismatch, workspace_changed, invalid_arguments, operation_not_found, ...) are rejected. Found by live ArcGIS Pro acceptance.
  • feature.layer.describe returns spatialReference: null instead of failing when a feature class has no spatial reference.
  • Malformed tool arguments (a missing required argument or a value of the wrong JSON type) return the standard envelope with error.code invalid_arguments, a message naming the argument and retryable: false, instead of the SDK's generic error text without structuredContent. Unknown tool names remain a JSON-RPC error.
  • In-process hosts built concurrently could intermittently publish the injected bridge client as a required bridge tool argument (a race on the reflection ParameterInfo cache that the MCP SDK relies on). Tools and resources are now built once per process, and the server tests run in parallel again.
  • The dockpane no longer shows a hard-coded "Skills 1" count. Skills are loaded by the MCP gateway process, which the add-in cannot see, so the expander header now shows only the workflow count.
  • invalid_workflow errors from workflow_save and workflow_run prefix each issue with its code (for example operation_not_allowed: ...), so clients can tell policy violations from other validation failures.
  • A request cancelled by ArcGIS Pro shutting down now fails with the retryable code host_stopping instead of the generic bridge_request_failed. docs/security.md states that keyed requests cannot be cancelled by callers once started.
  • Idempotent registry and workflow requests run under the host lifetime rather than the first caller's cancellation token, so cancelling one caller no longer cancels work another caller with the same key is waiting on.
  • table.query on a layer whose shapefile was renamed after it was added failed with operation_failed "A geodatabase exception has occurred." ArcGIS keeps the table handle and flags the layer broken only later, so opening the table succeeds and the next read throws. Reads of layer data (table.*, feature.*, symbology.*) now report a geodatabase error as layer_data_source_unavailable when the dataset was not found or the layer's local files are gone; the original error is kept for the audit trail, not in the message. Other geodatabase errors, such as an invalid where clause, are unchanged. Found by the live operation matrix.
  • layout.ensure-surround refused a 2.5 x 0.5 in scale bar ("did not accept its requested frame binding and bounds") because ArcGIS Pro sizes the scale bar height from its style. See Changed. Found by the live operation matrix.
  • layout.add-map-frame accepted a frame placed entirely off the page. Map frames and surrounds must now lie on the page, and layout.set-text anchors on it (0.01 in tolerance); otherwise the call fails with element_outside_page, naming the element and the page size, before anything is created. A Core test checks that every bundled workflow places its frames, surrounds and text on the page. Found by the live operation matrix.
  • map.activate failed with a NullReferenceException when an open map pane had no map view yet; the pane lookup is now null-safe. Found by the operation matrix pre-run.
  • tools/run-urban-stress.ps1 runs against a git-ignored copy of the fixtures instead of tests/data, so the lock files ArcGIS Pro keeps next to open data no longer land in the repository fixtures and fail the fixture tests during a live session. The copy lives in artifacts/urban-stress/fixtures/<key>/, where the key is the first 12 hex digits of the SHA-256 of tests/data/expected-statistics.json: a complete copy is reused only while the key matches (so data sources keep a stable path across runs), a data change gets a fresh copy, an interrupted copy is redone, and lock files are removed from a new copy.

Security

  • Confirmation now covers project replacement (project.open), project persistence (project.save) and attribute/geometry edits (feature.update).
  • Workflows can no longer silently adopt a newer workspace revision and continue writing against state nobody reviewed. Only a successful write step advances the run's expected revision; a read-only step or a continueOnError failure that observes a mid-run change no longer authorizes the following writes.
  • User-code operations are opt-in per workflow, and approvals that execute Python are visibly labelled.
  • gp.run requests for Calculate Field, Calculate Fields and Calculate Value are labelled as running user code even without an explicit expression type, because ArcGIS Pro defaults it to Python 3. Python toolboxes called by an imported alias remain a documented false negative for the user-code label; autonomous mode refuses them as unclassified.
  • Approval decisions, autonomous bypasses and unknown operation ids are recorded in the audit log.
  • User .atbx toolboxes are read as untrusted archives: each metadata entry is bounded to 16 MiB of decompressed bytes whatever size it declares, an archive may hold at most 10,000 entries and 64 MiB of metadata, and script (.py) entries are only checked for presence, never read. An archive over a limit is reported as unreadable.
  • Approvals stay in the ArcGIS Pro dockpane: MCP elicitation is intentionally not used, so the client UI cannot become the approval authority. approval_status waitSeconds only waits for the dockpane decision.
  • A dockpane button click (for example Open project) can no longer approve an identical request that an MCP client queued for review. The panel's self-approval always creates its own approval entry (IApprovalService.Request(..., reuseExisting: false)), and the transient entry no longer flashes in the approval cards.
  • The gateway never selects a development FakeHost automatically. Discovery records carry hostKind (arcgis-pro | fakehost; missing means arcgis-pro), and FakeHost records are used only when selected with ARCGIS_PRO_MCP_HOST_PID/ARCGIS_PRO_MCP_PIPE or the development opt-in ARCGIS_PRO_MCP_ALLOW_FAKEHOST=true. FakeHost prefixes its project name with [FakeHost] and warns when ArcGIS Pro is running.

Removed

  • Rhino-era documents (docs/integrated-rhino-*, docs/master-plan-demo.md) and the internal docs/FRESH_CONTEXT_HANDOFF.md.
  • Machine-specific checkout paths from the manual acceptance procedure.

Latest committed live acceptance evidence: docs/acceptance/2026-10-03-e7deee2 (commit e7deee2). It covers only the commit, ArcGIS Pro version and sections it names.

Unsigned development preview. Verify the bundle against SHA256SUMS and its inner checksums.sha256, then follow deployment.