Integration protocols

Audience: someone plugging Maidan into an existing agent stack (Cursor, Claude Desktop, a Python/TS agent, another org's A2A agent, n8n, Slack).

Providers.md covers where it runs — Postgres host, S3, OIDC. This page covers how it talks.

Protocol facts come from the code (SUPPORTED_PROTOCOL_VERSIONS, POST /a2a/v1/rpc, the Agent Card) and are current. The market commentary below was written on 2026-08-25 and ages faster than the code does.

MCP 2026-07-28 is the default; every revision since 2024-11-05 is accepted. initialize echoes 2026-07-28, 2025-11-25, 2025-06-18, 2025-03-26 or 2024-11-05 — whichever the client asks for — and every revision from 2025-03-26 on is served over the same stateless Streamable HTTP (no Mcp-Session-Id; optional SEP-2243 Mcp-Method/Mcp-Name headers). Only 2024-11-05 keeps the SSE-session model. The 2025 revisions matter most: the official TypeScript SDK 2.0 and the MCP Inspector request 2025-11-25 and do not accept 2026-07-28, and until Cluster 412 their handshake with Maidan failed. scripts/mcp-inspector.sh runs the Inspector against a real server to keep it that way. See Required protocol upgrades.

What the official conformance suite says. scripts/mcp-conformance.sh runs the suite's frozen requirement sets for 2025-11-25 and 2026-07-28 against a dev instance. Every check that fails is listed with its reason in scripts/mcp-conformance/. Most are scenarios built on the suite's own fixture tools, or features Maidan does not declare. A 2026-07-28 request is told apart from an earlier one per request and held to that revision. Its _meta and routing headers are validated (SEP-2575, SEP-2243), and a method it removed answers 404 with -32601, so all of server-stateless and http-header-validation that does not need a fixture passes, while earlier clients keep their behaviour. Maidan does not implement the optional tasks extension (SEP-2663), so its checks stay listed with that reason. A request with no credential, the only kind a DNS-rebinding page could use, is refused when its Host or Origin names another site (see Production, "DNS rebinding").


The 2026 stack (do not pick a winner)

These are layers, not alternatives. Pickaxe / AAIF / Linux Foundation all say the same thing in 2026: MCP won tools; A2A won peers; a UI protocol is emerging on top.

LayerProtocolJobAnalogy
CapabilityMCP (Anthropic → AAIF)Agent ↔ tools / dataUSB-C
CoordinationA2A (Google → Linux Foundation)Agent ↔ agent tasksPhone line
PresentationAG-UI (CopilotKit) or Maidan WS//uiAgent ↔ human surfaceScreen
Existing ITREST + OpenAPI, WebSocket, webhooks, OIDC, Prometheus/OTLPThe stack they already runPlumbing

IBM's Agent Communication Protocol (BeeAI) merged into A2A on 2025-08-29. Do not implement it. Zed's Agent Client Protocol is a different ACP (editor ↔ coding agent, LSP-shaped). OpenTag uses that one. Maidan optionally dispatches an ACP worker; it must not become Maidan's native workspace protocol.

Start with MCP. Add A2A when a second autonomous agent must discover and delegate. Do not invent a fourth agent protocol.


What Maidan already speaks (code, 2026-08-25)

One model, one capability map, four primary transports plus the IT surfaces.

SurfaceWhereStatusHonest caveat
REST + OpenAPI 3.1GET /openapi.json, utoipaProductionNo workspaces.list. Create via POST /workspaces. Hero bootstrap is REST/CLI, not MCP.
MCP JSON-RPCPOST /mcpProduction, negotiates 2026-07-28, 2025-11-25, 2025-06-18, 2025-03-26, 2024-11-05SUPPORTED_PROTOCOL_VERSIONS, default 2026-07-28. POST /mcp is stateless (JSON-RPC in/out).
MCP Streamable HTTPPOST/GET/DELETE /mcp/streamableProduction; stateless from 2025-03-26 on (+ 2024-11-05 session)A POST from any revision 2025-03-26 or later lands cold: one JSON-RPC response on its own POST, no Mcp-Session-Id, a notification answered 202, optional SEP-2243 Mcp-Method/Mcp-Name headers. Sessions are opt-in: only a 2024-11-05 client — by its initialize or its MCP-Protocol-Version header — gets the SSE-session model (first POST opens SSE + Mcp-Session-Id). GET opens server→client notifications. Live-wait rides GET /mcp/stream, not a POST session.
MCP SSE (legacy-shaped)GET /mcp/stream, GET /mcp/notificationsProductionFine for Maidan live-wait. A stateless resources/subscribe is kept in the database, so GET /mcp/notifications on any replica delivers it. HTTP+SSE is deprecated in the MCP spec (SEP-2596); migrate clients toward Streamable HTTP, not a third Maidan transport.
MCP stdiomaidan mcp-stdioProductionThe desktop-client path (Claude Desktop / local Cursor). Same JSON-RPC, SQLite or Postgres.
WebSocketGET /ws/subscribeProductionResumable cursors, capability event:subscribe. This is Maidan's agent↔UI live path.
A2A v1.0 (JSON-RPC, HTTP+JSON)POST /a2a/v1/rpc, /a2a/v1/message:send and the rest of §11Production; checked by the official A2A TCK in CI (scripts/a2a-tck.sh; excluded cases in scripts/a2a-tck/exclusions.txt)Every v1.0 operation: SendMessage, SendStreamingMessage, GetTask, ListTasks, CancelTask, SubscribeToTask, the four push-config operations, GetExtendedAgentCard. Requests send A2A-Version: 1.0; a request without it is 0.3 and refused. A contextId is a Maidan thread, and the message is posted as the token's member. Parts are text or URL. The gRPC binding (opt-in, MAIDAN_A2A_GRPC_ADDR) serves the official lf.a2a.v1.A2AService from the unmodified v1.0.1 a2a.proto, every operation included, and the TCK runs over it too. POST /a2a/v1/events is federation ingest, not A2A.
A2A Agent CardGET /.well-known/agent-card.jsonProduction (spec v1.0)supportedInterfaces (JSONRPC, HTTP+JSON, optional GRPC), capabilities, skills, a bearer securitySchemes entry; cacheable (ETag, Cache-Control). The extended card needs a token.
Federation cardGET /.well-known/maidan.jsonProductionMaidan-to-Maidan, not A2A.
Outbound webhooks/workspaces/:wid/webhooks, mention-webhookProductionSigned POSTs of event envelopes. The n8n / Zapier / Make path.
Slash commands/workspaces/:wid/slash-commandsProductionHTTP callbacks, Slack-shaped.
FSM hooksfsm_hooksProductionThread state machine → HTTP.
Human authOIDC discoveryProductionSession cookies for /ui. Agents use capability bearers.
App OAuth/oauth/app/tokenProductionInstalled apps, not MCP resource-server OAuth (RFC 8707).
MetricsGET /metrics + OTLP smoke in CIProductionPrometheus text. Plug into the scrape they already run.

MCP tool count is 250. There is no MCP create workspace or member. An agent creates a channel with create_channel and a thread with create_thread (both workspace:write). Workspace and member bootstrap stay on REST or the CLI; then MCP for claim / wait / post / transition_thread.

Every tool in tools/list carries annotations: a title and explicit readOnlyHint, destructiveHint, idempotentHint and openWorldHint, the same on /mcp, /mcp/streamable, /mcp/worker and /mcp/reviewer. openWorldHint is true only for a tool that itself reaches outside Maidan, such as post_message running a slash command's HTTP receiver or search_messages calling a remote embedding provider. The reason for each value is in crates/maidan-mcp/tests/fixtures/tool-annotations.json, and tool_annotations_contract fails when a tool lacks a hint or disagrees with that table.

MCP Apps (io.modelcontextprotocol/ui, SEP-1865 stable 2026-01-26). One View: the inline approval card at ui://maidan/approval-card.html (text/html;profile=mcp-app), linked from get_approval_gate and approval_decide with _meta.ui.resourceUri (visibility model and app) and fed from their structuredContent. ChatGPT reads the standard _meta.ui.resourceUri, so there is no openai/outputTemplate alias. The card is static and holds nothing of any workspace: it calls the tools back through the host's tools/call and opens a confirmation link with ui/open-link. Negotiation follows the spec's server behaviour: a client that declares capabilities without the extension gets the catalog, resource list and results it got before; a client that declares it hears capabilities.extensions["io.modelcontextprotocol/ui"] back from initialize and server/discover (that discover result, and a tools/list with the link taken out, are cacheScope: "private", since they answer what the request declared). A stateless 2025 request declares nothing, so it is offered the link and structuredContent, which a host without MCP Apps ignores. The details, and how the card answers a gate, are in Integration.

MCP discovery and cache hints

server/discover answers with no handshake before it: the revisions Maidan supports (supportedVersions), its capabilities, the server instructions, and serverInfo under _meta["io.modelcontextprotocol/serverInfo"]. MCP 2026-07-28 has no initialize, so a client that speaks only that revision reads the instructions here. That discover result, and an initialize that negotiates 2026-07-28, omit resources.subscribe: on that revision the flag means per-resource updates through subscriptions/listen, which Maidan does not implement. initialize for every 2025 revision and 2024-11-05 still sets resources.subscribe and still serves resources/subscribe and resources/unsubscribe, with the same instructions, on POST /mcp and on POST /mcp/streamable. Every result carries resultType: "complete", except one: approval_decide, when a model asks to accept a gate that needs a person, answers a 2026-07-28 client that declared URL-mode elicitation with resultType: "input_required" carrying a URL-mode elicitation/create (never a form) pointing at a one-time console link. Any other client gets the link in the result. The model cannot complete the confirmation itself.

Every result of the six cacheable operations carries a ttlMs and a cacheScope (SEP-2549; CacheableResult in the 2026-07-28 schema). The official TypeScript client caches on them by default and caps a TTL at 24 hours (MAX_CACHE_TTL_MS). public means a shared gateway may hand the result to any caller, so Maidan uses it only for a result that is the same bytes whoever asks. A hint never stands in for authorization: every call is checked against its token, whatever a client has cached. POST /mcp/worker and POST /mcp/reviewer are the exception to the filtered list: each serves one profile, and a tool the token cannot call is refused when it is called. The choices live in crates/maidan-mcp/src/caching.rs, and cache_hints_contract checks this table against it.

ResultttlMscacheScopeWhy
server/discover3600000publicVersions, capabilities and instructions are the same for every caller and change only with a release.
tools/list on /mcp, /mcp/streamable3600000privateFiltered to the token's capabilities, so two tokens get two lists. The catalog changes only with a release, and Maidan cannot announce a deploy (notifications/tools/list_changed never fires), so the TTL bounds how long a client keeps a list from before one.
tools/list on /mcp/worker, /mcp/reviewer3600000publicA fixed profile, sorted by name, the same bytes for every caller, so a shared cache can keep it (SEP-2567). A tool the token cannot call stays in the list and is refused at tools/call.
prompts/list3600000publicThe same for every caller; changes only with a release.
resources/templates/list3600000publicThe same for every caller; changes only with a release.
resources/list3600000privateLists the caller's own workspace, which its token fixes.
resources/read of maidan://artifacts/{sha256}60000privateThe URI names the bytes, but the read returns the workspace ref (kind, mime_type, filename), which a later upload of the same SHA updates. A minute, the same as a workspace or channel record. Private because access is per workspace.
resources/read of maidan://workspaces/{id}, maidan://channels/{id}60000privateChanges on a rename, a topic edit or an archive. A subscriber hears notifications/resources/updated and drops its copy at once.
resources/read of maidan://boots/{channel_id}60000privateThe workspace boot for a channel. It changes when the glossary or an accepted decision changes, not when a thread is claimed. Private because access is per caller.
resources/read of maidan://threads/{id}0privateChanges with every post, claim and transition.

Who shows up with which protocol

They already runPoint them atDo not
Cursor, Claude Desktop, VS Code, Claude Code, ChatGPT connectorsMCP over POST /mcp / Streamable HTTP / stdio, at whichever revision the client requests (2024-11-05 through 2026-07-28). Verified against the official TypeScript SDK 2.0 via the MCP Inspector (2025-11-25); the others are not yet verified by Maidan's own tests.—
A Python / TS agent they wroteREST + WS, or MCP if they already have an MCP client. There are thin SDKs for TypeScript, Python, Go and Rust in sdk/, at 0.1.0.An in-process Crew.kickoff. Maidan is the orchestrator.
LangGraph / CrewAI / OpenAI Agents SDKRecipe on REST+WS (or MCP tools). Those frameworks speak MCP as of 2026; they do not need a Maidan-native runtime.A LangGraph checkpointer inside Maidan.
Another vendor's agent (Salesforce, SAP, Bedrock, Foundry)A2A Agent Card + JSON-RPC.IBM ACP. It is A2A now.
n8n / Zapier / Make / "we have webhooks"Outbound webhooks + REST. OpenAPI for the REST half.A GraphQL gateway.
Humans in SlackThe Slack projector (HTTP Events API); link a channel with the link_slack_channel MCP tool. Agents stay on MCP/A2A.Making Slack the datastore. Socket Mode as Marketplace default.
Humans in GitHub / GitLab / GiteaThe GitHub projector (App / webhooks); link an issue with link_github_issue. Agents use the official GitHub MCP for diffs.Reimplementing GitHub MCP. Opening PRs as Maidan. Ambient on every PR.
Humans in the browser / a React app/ui on this server (the board) and /ws/subscribe.Native AG-UI. CopilotKit is a frontend stack, not a workspace.
Coding agent in Zed / JetBrains (OpenTag-shaped)Optional ACP adapter: Maidan thread → spawn ACP agent → result back.Replacing A2A or MCP with Zed ACP.
Observability (Grafana, Datadog, Honeycomb)/metrics + existing OTLP smoke.OpenTelemetry as a fourth agent protocol.
SSO they already pay forOIDC (Providers.md).SAML-in-core. MCP-spec OAuth only if remote MCP hosts refuse bearer tokens.

Market evidence (why this order)

Researched 2026-08-25. Quote the primary sources if you blog; do not inflate.

  • MCP is the default connect story. Public writeups in 2026 treat it as the de facto agent↔tool standard (Cursor, Claude, ChatGPT, Gemini, JetBrains, Vercel AI SDK). Spec current rev is 2026-07-28: stateless Streamable HTTP, Mcp-Method / Mcp-Name headers, capabilities on every request _meta, sessions gone. Anthropic rolled that rev across Claude products the same day. Maidan has not.
  • A2A is the default peer story. Linux Foundation, v1.0, 150+ orgs (AWS, Microsoft, Google, IBM, Salesforce, SAP, ServiceNow), cloud embeddings in Azure AI Foundry / Copilot Studio / Bedrock AgentCore. JSON-RPC over HTTP is the common public binding; gRPC and HTTP+JSON are spec bindings, not requirements. GitHub a2aproject/A2A ~25k stars (snapshot in Expansion Bets).
  • AAIF (Agentic AI Foundation, Linux Foundation, Dec 2025) now governs MCP and A2A together. Building a private third protocol in 2026 is the anti-pattern those posts keep naming.
  • IBM ACP is dead as a product. Merged into A2A 2025-08-29. Docs redirect. Mention it only to tell people to use A2A.
  • Zed ACP is real and adjacent. Editor ↔ coding agent. OpenTag (~1.3k stars) is the Slack-shaped dispatcher. Adapter later, not native.
  • AG-UI is the emerging agent↔frontend event stream (CopilotKit). Complements MCP/A2A. Maidan already has WS event envelopes. Do not dual-implement a CopilotKit runtime until humans-in-browser is the north star.
  • ANP (decentralized DID agent marketplace), AP2 (agent payments), A2UI (Google generative UI widgets): watch, do not build.
  • GraphQL / gRPC as Maidan's primary API: nobody asking for a Slack-shaped workspace leads with GraphQL. A2A's optional gRPC binding is for A2A, not a rewrite of /workspaces.

Required protocol upgrades

2024-11-05-only MCP is not a shippable state, and neither is 2026-07-28-only. The official TypeScript SDK 2.0 requests 2025-11-25 and rejects a 2026-07-28 answer. A pack or public cut that advertises MCP while SUPPORTED_PROTOCOL_VERSIONS = ["2024-11-05"] will bounce modern clients. Do not "freeze on 2024" as the strategy. Temporary honesty (J2) until the upgrade lands is not the same as accepting 2024 forever.

ProtocolCode today (2026-08-25)RequiredID
MCP✅ 2026-07-28 shipped (default; 2025-11-25, 2025-06-18, 2025-03-26 and 2024-11-05 accepted since Cluster 412). Stateless Streamable HTTP (no Mcp-Session-Id), SEP-2243 Mcp-Method/Mcp-Name headers, live-wait on GET /mcp/stream/WS.Done–303.J3 ✅
A2A Agent Card✅ Spec v1.0 card, TCK-checkedSpec v1.0 supportedInterfaces (JSONRPC)J4 ✅
A2A partsEgress text-only (v267)File/data parts when artifacts existJ5
MCP OAuth (RFC 8707)Capability bearersOnly if a real 2026 host refuses bearer after J3J6

J3 — MCP 2026-07-28 (do this; do not sticker it)

Spec: https://blog.modelcontextprotocol.io/posts/2026-07-28/

What has to change in this tree (maidan-mcp + mcp_streamable.rs):

  1. SUPPORTED_PROTOCOL_VERSIONS includes 2026-07-28 and that rev is what initialize returns to current clients.
  2. Streamable HTTP POST carries Mcp-Method and Mcp-Name (SEP-2243) so a gateway can route without parsing JSON.
  3. Stateless core: capabilities / protocol version from _meta (or the headers) on each request. A 2026 client must not need Mcp-Session-Id.
  4. GET /mcp/streamable + protocol-level sessions are not 2026. Keep Maidan live-wait as GET /mcp/stream / WS / wait_for_* tools. Do not tell a 2026 client that GET-session is Streamable HTTP 2026.
  5. Tests: initialize with 2026-07-28 succeeds; a Cursor-shaped client that omits a session id can tools/call. README/Integration advertise 2026 only after 1–4 are green.

Optional one-release fallback: still accept 2024-11-05 initialize from old stdio clients if it does not revive the session lie. Default and docs are 2026. Staying 2024-only is not an option.

J3 is Hardening (protocol upgrade), not Bet 2. Bet 2 M.0 is J3. The pack (M.1) and public cut wait on it. Do not sneak this into a docs PR.

Gaps worth closing

The rest of this page is maintainer planning, kept here so the protocol decisions and the work they imply stay together. If you are integrating, you can stop reading at this line.

J3 shipped (2026-07-28). The rest is adapters + honesty. No new native protocol.

IDGapSizeNotes
J1This pageDocsWritten 2026-08-25. Keep true when SUPPORTED_PROTOCOL_VERSIONS changes.
J2✅ RetiredDocsWas: "temporary honesty (today 2024-11-05)". No longer needed — J3 shipped; README/Integration now advertise 2026-07-28.
J3✅ MCP 2026-07-28 shippedDoneNegotiation → stateless streamable core → SEP-2243 routing headers → advertise (default flip + card/reference/Integration). 2024-11-05 still accepted.
J4✅ A2A Agent Card → spec v1.0 supportedInterfacesDoneKeep JSON-RPC URL. Advertise protocolBinding: JSONRPC. Do not add gRPC just to fill the array. Signed JWS cards are enterprise-later.
J5A2A file/data partsCluster (after 267 text)Ingress already preserves structured content; egress is text-only. Round-trip files when an artifact already exists.
J6MCP OAuth resource-server (RFC 8707)Spike, then maybeRemote Claude/Cursor may insist. Today: capability bearers. Implement only if a real host refuses the bearer. Do not replace workspace capabilities with a second ACL.
J7Webhook + OpenAPI recipe for n8n/ZapierDocsThey already work. Show one signed webhook + one REST post.
J8LangGraph / CrewAI / Agents SDK recipeDocs / examples/ (Bet 2/3)REST+WS or MCP tools. No in-process runtime.

Already covered elsewhere, do not duplicate here: Slack Events projector (Bet 1), thin TS SDK (Bet 3), MCP examples/ pack (Bet 2 M.1), create-* MCP tools (no — seed via REST).


Do not chase

TemptationWhy not
A fourth agent protocol ("Maidan Protocol")MCP+A2A+REST is the industry stack. AAIF exists so you do not do this.
IBM ACP / BeeAI nativeMerged into A2A.
Zed ACP as the workspaceWrong layer. Optional worker adapter.
Native AG-UI / CopilotKit runtimeWS + /ui already present the events. AG-UI when the north star is a React product.
A2A gRPC or HTTP+JSON bindings "for completeness"JSON-RPC is what public agents speak. Add a binding when a cloud (Foundry/Bedrock) blocks on it.
GraphQL gatewayOpenAPI is the IT path.
gRPC for /workspacesSame.
ANP, AP2, A2UI, MCP Apps as requiredWatch lists. Not adoption blockers.
MCP HTTP+SSE as a new transportWe already have /mcp/stream. Spec says migrate to Streamable HTTP.
MCP create-workspace tools so an IDE can bootstrapHero seed is REST/CLI by design; the existing catalog already covers it.
OpenAI Assistants / Responses as a native wireThose clients speak MCP now.
Teams/Discord as first-class protocolsSlack projector first if any chat bridge.
GitHub MCP as Maidan toolsOfficial server is the repo wire. We ingest webhooks.
Replacing capability bearers with only OIDC for agentsHumans are OIDC. Agents are scoped tokens. Keep the split.

Integrator decision tree

  1. Single agent, needs Maidan tools → MCP 2026-07-28 (shipped; stdio local, stateless Streamable HTTP remote). Older clients may request 2024-11-05.
  2. Need live events in your own UI → WebSocket subscribe (or MCP SSE live-wait).
  3. Need to script / generate a client / talk to n8n → REST + OpenAPI, optionally webhooks.
  4. A second agent must delegate to Maidan or vice versa → A2A JSON-RPC + Agent Card (J4).
  5. Humans already live in Slack → the Slack projector, not a new protocol.
  6. Humans already live in GitHub/GitLab → the GitHub projector, not Copilot.
  7. Editor coding agent should work a Maidan thread → ACP adapter later, not now.

If two of those apply, use two transports. That is the design (README: "one surface, four transports").


See also