Repository navigation
scanner: donkey scan derives a deterministic scan model of agents, tools and MCP servers #217
Description
Summary
donkey scan walks a repository statically, finds every agent entry point,
@donkey.tool marker and MCP server definition, and produces one deterministic
scan model. That model is the single input to both publish targets: direct
Exchange publish (#218) and the self-managed scanner service bundles (#1104
agent, #1105 MCP server, published via #1106).
$ donkey scan
found 1 agent support-triage (main.py:build)
found 6 tools @donkey.tool lookup_crm, issue_refund, …
found 1 mcp rag-server (server.py, streamable-http)
2 issues issue_refund: tautological description
$ donkey scan --json > scan.json # diffable in CI
Positioning
MuleSoft's Agent Scanners discover agents from Agentforce, Bedrock, Vertex AI
and Copilot Studio at runtime. donkey scan is the design-time / CI source
for agents built in plain Python that no cloud scanner can see. It feeds the
platform rather than competing with it: its output becomes the service bundles the
Anypoint self-managed scanner ingests (#1102), so code-defined agents land in the
same registry through the platform's own ingestion path. BG §2.5; the BG
do-not-build list rules out re-implementing Agent Scanners.
Current behavior (verified)
core/toolspec.py:@donkey.toolrecords aToolSpec(name, qualname,
signature, docstring) and is documented as the marker both the scanner and the
A2A card generator read. It carries no server/asset attribution (scanner: define how a @donkey.tool is attributed to an MCP server / Exchange asset #503).registry/publication.py:80:Publicationalready holdsname, version, description, tags, categories, contact, asset_id, which is the metadata both
targets need.registry/publication.py:116:Publication.export()is_verify.blocked; the
SDK no longer defines adonkey.yamlspec format (it went with the refused
provisioning control plane, refactor(cli): move the supported CLI to donkey_kit/cli and quarantine legacy provisioning #730). This issue must not reintroduce it.registry/publication.py:143,169:check_description_quality()and
content_digest()exist and are reused here, not re-specified.
Proposal
- A typed, in-memory scan model (agents, tools, MCP servers, description issues)
inregistry/, built onPublication/ToolSpecrather than a new file format.
Exchange (scanner: donkey publish registers the manifest with Exchange / Agent Registry #218) and the scanner bundles (scanner: build the agent service bundle (config.json + a2a-card.json ZIP) #1104/scanner: build the MCP server service bundle #1105) map from it; each target
owns its on-disk artifact. donkey scanprints a summary;--jsondumps the model canonically for CI diffs.- Identity: each service gets a stable ID per scanner: define how a @donkey.tool is attributed to an MCP server / Exchange asset #503's decision. scanner: build the agent service bundle (config.json + a2a-card.json ZIP) #1104 makes the
scanner service ID permanent and scanner: refuse to register the same service via Exchange and the self-managed scanner #1107 keys its duplicate guard on it, so scan
must never derive IDs from file paths or function names that change on refactor.
Until a tool is attributed, it is recorded as explicitlyunattributed, never
guessed. - A2A card: produced as a model object by a card generator shared with
donkey.serve()(a2a: donkey.serve() — an A2A listener in one line, wrapping the official a2a-sdk #224). File name and card version are a target concern
(a2a-card.jsonfor the scanner, pending verify: self-managed scanner is GA, and its push/commit/unstage contract #1101), not fixed here. - MCP servers: capture neutral facts (name, transport, endpoint, attributed
tools). The scanner's MCP artifact format is unpublished (scanner: build the MCP server service bundle #1105/verify: self-managed scanner is GA, and its push/commit/unstage contract #1101), and
transport matters to tools: decide how discover() and binding treat MCP servers the gateway does not front #1150 (stdio servers cannot be fronted by the gateway). - Description quality: run
check_description_quality()once, in scan. scanner: build the agent service bundle (config.json + a2a-card.json ZIP) #1104
and scanner: build the MCP server service bundle #1105 consume the result instead of re-checking.
Acceptance criteria
- Static analysis only; the scanner never imports or executes user code.
- Output is deterministic: same repo, same bytes for
--json, and a stable
content_digest(), so--if-changedworks for both targets. - A tool with a missing or tautological description is reported, not
silently emitted. - Every service has a stable ID per scanner: define how a @donkey.tool is attributed to an MCP server / Exchange asset #503; unattributed tools are marked
unattributed. - The A2A card comes from the generator shared with a2a: donkey.serve() — an A2A listener in one line, wrapping the official a2a-sdk #224, so the two cannot drift.
- No
donkey.yaml(or other new spec format) is written.
Cross-surface impact
- core:
core/toolspec.pyread-only here; anyToolSpecattribution field
lands via scanner: define how a @donkey.tool is attributed to an MCP server / Exchange asset #503. - registry: the scan model, reusing
Publication,check_description_quality
andcontent_digest. - tools: not affected (discovery/binding is tools: donkey.tools.discover() returns governed MCP tools as native framework tools #221/tools: decide how discover() and binding treat MCP servers the gateway does not front #1150).
- CLI:
donkey scan(+--json). - a2a: shared card generation with a2a: donkey.serve() — an A2A listener in one line, wrapping the official a2a-sdk #224.
- TypeScript parity (planned): flag that the model shape and determinism must match.
- docs/verified-apis.md: not affected; scanning is local and verifies no endpoint.
- Nextra docs site: update
publishing.mdxto show scan as the design-time
source that feeds both Exchange publish and the platform's self-managed scanner.
Keep the existing "complements MuleSoft's Agent Scanners" framing, and add the
GA caveat for the scanner path.
Out of scope
- Publishing to either target (scanner: donkey publish registers the manifest with Exchange / Agent Registry #218, cli: donkey publish --target self-managed-scanner #1106) and bundle building (scanner: build the agent service bundle (config.json + a2a-card.json ZIP) #1104, scanner: build the MCP server service bundle #1105).
- The GitHub Action (scanner: the scan-and-publish GitHub Action #219).
- Org-wide fleet scanning (deliberately not in this plan).
Depends on
- scanner: define how a @donkey.tool is attributed to an MCP server / Exchange asset #503: tool→server/asset attribution and the stable service-ID rule. Decide
that first so scan does not hard-code identity incidentally.
Related
- a2a: donkey.serve() — an A2A listener in one line, wrapping the official a2a-sdk #224: shared A2A card generation. epic(scanner): publish agents and MCP servers through the self-managed scanner (GA-gated) #1102: scanner epic; scan output is the input
to its bundles. scanner: refuse to register the same service via Exchange and the self-managed scanner #1107: duplicate guard keys on the same service ID.
tools: decide how discover() and binding treat MCP servers the gateway does not front #1150: MCP transport affects governability.
Not gated by #1101 (scanner GA): scan is useful on its own and for #218.
Activity
[-]scanner: fabric scan walks the repo and writes fabric.yaml + agent-card.json[/-][+]scanner: donkey scan walks the repo and writes donkey.yaml + agent-card.json[/+]tbolis-at-mulesoft commentedon Oct 12, 2026
Related: #1102 — scan output feeds the self-managed scanner service bundles (#1104 agent, #1105 MCP).
[-]scanner: donkey scan walks the repo and writes donkey.yaml + agent-card.json[/-][+]scanner: donkey scan derives a deterministic scan model of agents, tools and MCP servers[/+]tbolis-at-mulesoft commentedon Oct 20, 2026
Rescoped: #217 now emits a typed scan model (no
donkey.yaml, per #730) that feeds both publish targets, #218 and the self-managed scanner bundles #1104/#1105. Blocked by #503 so service identity and tool attribution are decided before scan bakes them in.