Repository navigation
MCP evolver_asset_publish can never submit a valid bundle: single-asset body vs the hub's Gene+Capsule requirement, and gep-sdk capsule.schema.json forbids the required validation field #638
Description
Summary
The MCP publish path (evolver_asset_publish) cannot succeed for any hub-compliant bundle, and it reports success while the asset never reaches the hub. Two independent defects combine, and the receipt hides both.
All three findings below were reproduced on 2026-10-11 against evomap.ai with a single asset submit.
Environment
@evomap/evolver2.0.42 (global npm install, Windows 11 x64)@evomap/evolver-mcp(bundled) ->dist/proxyClient.js@evomap/gep-sdk1.14.0 ->schemas/capsule.schema.json,schemas/gene.schema.json- proxy: running,
hub_auth_status: ok, nodenode_cd8a0948549519f6,hub_mode: public
Defect 1: the MCP tool is structurally limited to one asset, but the hub requires a pair
The tool schema accepts exactly one asset object:
{ "type": "object", "required": ["asset"], "properties": { "asset": { "type": "object" } } }proxyClient.js then hardcodes the single-asset body (line ~174):
submitAsset(asset) {
// MCP publishing remains durable and outage-tolerant; the bare route is reserved for V1 synchronous callers.
return this.call('POST', '/asset/submit?mode=async', this.modeBoundBody({
assets: [asset],
request_id: randomUUID(),
}));
}The hub's publish contract (GET /a2a/skill?topic=publish) requires payload.assets to contain a Gene and a Capsule, so every MCP submit is a 400 invalid_request. The same file already has a submitAssetBundle(bundle) helper that posts to /asset/submit, but it is not exposed through MCP.
Defect 2: capsule.schema.json forbids the field the hub requires
@evomap/gep-sdk 1.14.0 schemas/capsule.schema.json:
"required": ["type", "schema_version", "id", "trigger", "gene", "summary",
"confidence", "blast_radius", "outcome", "asset_id"],
"additionalProperties": falseThere is no validation property at all, and additionalProperties: false rejects it. schemaGate.js therefore reports Capsule: 多余字段(additionalProperties:false 会 reject): validation for a hub-compliant Capsule.
The hub spec (GET /a2a/skill?topic=structure) says the opposite:
| validation | Yes | Array of validation commands -- same sandbox rules as Gene.validation: node/npm/npx only, min 1 command, each min 10 chars, self-contained |
Because both sides set additionalProperties: false, no single Capsule can satisfy the local wire schema and the hub spec at the same time. The same divergence exists on the Gene side, where gene.schema.json additionally requires id, constraints, schema_version and validation, none of which the hub lists as required (schema_version is explicitly "No", validation "Recommended").
A side effect: evolver_gep_build returns wire_valid: false with Gene: 缺 required 字段: constraints / 缺 required 字段: asset_id even for input that it just built an asset_id for, because the gate validates the pre-build input.
Defect 3: the receipt is false green
Submitting one valid Gene through the MCP tool returns:
{ "status": "pending", "publish_status": "pending", "queued": true, "stored": true }The proxy's own mailbox (~/.evolver/proxy/mailbox.db, WAL mode) records:
type: asset_submit | status: failed | attempts: 1 | dlq: 1
lastError: publish invalid_request: hub 400
and evolver_asset_fetch for that asset_id returns null. At the time of writing the mailbox holds 5 messages, all status: failed, all with the same hub 400. Note that the rows live in the -wal file, so a plain copy of mailbox.db alone shows an empty messages table.
There is no signal in the receipt that lets a caller distinguish "queued for real" from "will fail forever".
Reproduction
npm i -g @evomap/evolver
# start the proxy, then from any MCP client:
evolver_gep_build({ asset: <a valid Gene with validation commands> })
evolver_asset_publish({ asset: <that Gene> }) # -> pending / queued: true / stored: true
# then:
sqlite3 ~/.evolver/proxy/mailbox.db "SELECT status, dlq, lastError FROM messages;"
evolver_asset_fetch({ asset_id: <the id> }) # -> nullThe hub itself accepts the bundle
To confirm this is client-side, the same content published directly works:
POST https://evomap.ai/a2a/publish
Authorization: Bearer <node-scoped secret from the proxy's mailbox kv>
Idempotency-Key: <uuid>
{ "protocol": "gep-a2a", "protocol_version": "1.0.0", "message_type": "publish",
"message_id": "...", "sender_id": "node_...", "timestamp": "...",
"payload": { "assets": [Gene, Capsule, EvolutionEvent] } }
Response: 200, decision: accept, reason: auto_promoted, bundle_id: bundle_21f098ad2cecb001. The Gene (sha256:7710499dc7da20f2...) is readable through GET /a2a/fetch with status: promoted, gdi_score: 37.8, source_node_id matching the node. Note that a user-level ek_ API key is rejected on this endpoint with auth_scope_mismatch; only the node-scoped secret works.
Suggested fixes
- Expose the pair through MCP, for example
evolver_asset_publish_bundle({ assets: [...] }), or letevolver_asset_publishacceptassetsas an array while keepingassetfor the single case. - Add
validationtocapsule.schema.json(required, min 1 command, node/npm/npx) and drop the Gene fields the hub does not define, so the local wire schema and the hub spec are compatible instead of mutually exclusive. Better, generate the local schemas from the same source the hub serves at/a2a/skill?topic=structure. - Stop reporting
queued: true / stored: truebefore the hub accepts. Either return the hub's decision, or expose the queue state so a caller can tell a real queue entry from a permanent failure, and surfacedlq/lastErrorinstead ofstored: true.
Scope note
This is reported against EvoMap/evolver because proxyClient.js, the MCP tool surface and the bundled @evomap/gep-sdk schemas all ship here. Related but distinct: #620 (publish returns success but assets never become visible) and #621 (moved to evolver-codex-plugin).
Activity
Paraphern commentedon Oct 22, 2026
The worst part of this report is that the publish path reports success while the asset never reaches the hub — a lying receipt is more damaging than a failing one, because downstream automation keys on the success signal. Two mechanical guards: (a) validate the publish body against the hub's published schema before submit — the single-asset vs bundle mismatch becomes a client-side validation error, not a hub rejection; (b) tie the receipt to the hub's actual state — publish returns an artifact id, a follow-up call confirms it's addressable, and only then does the receipt say success. Receipts that assert rather than verify are a class we care about a lot — ours are hash-verifiable and signed for exactly this reason.