Platform MCP
Streamable HTTP MCP for a Confluye workspace (JSON-RPC 2.0): read tools plus scope-gated typed write and Command tools.
External clients call one workspace at the workspace endpoint below, or the whole organization at the organization endpoint:
POST /api/v1/platform/mcp/{workspaceId}
Product setup (OAuth, Claude Code, ChatGPT): MCP. Path and env examples may
still say FLUXUS_*; those are compatibility names for the same Confluye host and token.
GET on this URL returns 405 with Allow: POST. There is no SSE GET transport.
Auth
Bearer required. Missing token: 401 JSON-RPC -32001 Bearer token is required. plus
WWW-Authenticate: Bearer resource_metadata="<origin>/.well-known/oauth-protected-resource/api/v1/platform/mcp/{workspaceId}".
API key. Must verify, match {workspaceId}, scope === "workspace", and have a userId.
Wrong workspace or personal/copilot: 403 -32003
(API key cannot access this workspace. / A workspace API key is required.).
For workflows.deploy, the key supplies AIMS service_token assurance. Its acting user must also
hold a current AIMS assignment for this workspace (or its organization) with role aims_manager or
system_owner and requiredAssurance: service_token.
OAuth. If the bearer is not an API key, Confluye verifies an access token whose audience
is the public resource URL (BETTER_AUTH_URL origin when set, else the request URL). Token
workspaceId must match the path and scopes must include mcp:read. User must still be an
active workspace member. Failures: 401 (unverified) or 403 -32003
(OAuth token cannot access this workspace. / Active workspace membership is required.).
OAuth sessions never advertise or authorize workflows.deploy; browser MFA does not turn an OAuth
grant into AIMS service-token authority. A workspace OAuth token's granted scopes are narrowed at
authorize and refresh time to the resource row's allowedScopes, so a token never carries a scope
outside the resource's policy — a read-only resource (mcp:read without mcp:write) yields only
read-only tokens. API-key auth is unaffected: a workspace API key's authority still comes from its
own scope and the acting user's workspace role.
Protected-resource metadata (GET well-known, Cache-Control: public, max-age=300):
{
"resource": "https://host/api/v1/platform/mcp/{workspaceId}",
"authorization_servers": ["<issuer>"],
"scopes_supported": ["mcp:read", "offline_access"],
"bearer_methods_supported": ["header"],
"resource_name": "Confluye Command Center MCP"
}
Protocol
JSON-RPC 2.0. Header MCP-Protocol-Version: 2025-06-18 on responses. Server info
confluye-platform 0.1.0. Max body 1 048 576 bytes (413 -32010).
method | Result |
|---|---|
initialize | protocolVersion, capabilities.tools.listChanged: false, serverInfo |
tools/list | { tools } — stable reads plus read-only authoring tools for every session; write tools when the session holds mcp:write and the corresponding rollout flag is enabled; workflows.deploy only for an AIMS-authorized workspace API key |
tools/call | { content: [{ type: "text", text }], structuredContent } |
Notification (no id): HTTP 202 empty body. Unknown method: -32601. Invalid JSON-RPC:
400 -32600. Parse error: 400 -32700. Invalid tool params: -32602. Capability
availability: -32020. Expected missing resources use -32004; stale proposals and other expected
conflicts use -32009. Unexpected failures remain masked as -32603 Platform MCP request failed.
capabilities.list advertised input allows maturity: "stable" only. The runtime parse
accepts preview | stable then forces maturity: "stable" before execution.
Read tools
Workflow destinations are exposed through workflows.targets and workflows.preview-target-publication. The first lists Production and named preview identities and state; optional includeHistory: true adds the latest 30 production deployments for rollback selection. The second requires execute authority and checks an exact workflowId, targetId, versionId, expectedRevision and UUID requestId, with optional promotion or historical activation context. A readiness result does not activate a destination or authorize a later changed request.
workflows.update-target accepts workflowId, confirm: true, an idempotencyKey, and the same change actions described in Workflow destinations. It requires write and execution authority. The publish action also requires the existing AIMS service-token and deployment authorization; an agent cannot create a human approval. Promotion carries its exact source and stop-source choice, and rollback checks the historical production snapshot again.
These carry mcp:read and are advertised to every authorized session. The set is derived from the
runtime registry (platformMcpReadToolNames); the exact advertised inventory is verified by
pnpm docs:check, so this table cannot silently drift from the server.
| Name | Scope | Purpose |
|---|---|---|
workspaces.list | mcp:read | Visible workspace directory with bounded search and cursor pagination |
capabilities.list | mcp:read | Capabilities, operations, readiness |
billing-usage.get | mcp:read | Read the workspace compute-usage / billing summary (no card data) |
billing-usage.organization | mcp:read | Owner/Admin organization usage grouped by workspace, workflow, and actor |
workflows.list | mcp:read | Workflows |
workflows.get | mcp:read | One definition plus candidate/active/synchronized execution state |
runs.list | mcp:read | Runs |
runs.get | mcp:read | One run, paginated steps/logs, optional redacted payload windows |
human-tasks.list | mcp:read | Monitor Human Tasks (read-only metadata; never drafts/form values) |
human-tasks.get | mcp:read | One Human Task's monitoring view; completion stays human-only |
documents.list | mcp:read | Documents |
documents.get | mcp:read | Details / optional text |
knowledge.list | mcp:read | Knowledge bases |
knowledge.get | mcp:read | Metadata |
knowledge.search | mcp:read | Indexed knowledge |
tables.list | mcp:read | Tables |
tables.get | mcp:read | Table + bounded rows |
Inspecting large executions
runs.get returns run metadata, node errors, and the first ten steps and logs by default.
Input/output and log metadata are omitted by default (input is null), so a large execution
remains inspectable without downloading its full payload. Use nodeId to select a node's steps.
This filter does not filter the run-wide logs.
Use stepOffset and logOffset independently with page.steps.nextOffset and
page.logs.nextOffset. stepLimit and logLimit accept 1–10; null next offsets mean the
end of that collection. Page totals describe the selected collection; the run's stepCount
and logCount describe the entire execution. Offsets are best used after the execution finishes;
an active execution can add or update entries between requests.
{ "workspaceId": "ws_example", "runId": "run_example", "nodeId": "codexCli_9" }
Set includePayloads: true to include input, output, and log metadata as redacted JSON text
windows, each with format: "json-text", text, offset, totalChars, and nextOffset.
Each window contains up to 2,048 JavaScript string characters. Request the same run/node/page
with payloadOffset set to that field's nextOffset, concatenate its text windows in order,
and JSON-parse only after reaching null. Offsets apply independently to every payload in the
response; follow each field's own cursor. Secrets are redacted before splitting into windows.
Messages and errors have bounded previews; messagePayload contains a log's full redacted
message when payloads are requested. Payload omission and pagination do not mean data was lost.
Agents may monitor Human Tasks through human-tasks.list / human-tasks.get, but claiming,
drafting, and completing a Human Task are human-only actions and are never advertised as tools.
Workflow authoring and debugging
Read-only clients can search the current block catalog, inspect a block's config template/schema, validate a
candidate, and obtain a secret-safe diff. Debug actions require mcp:write, a current Member-or-higher role,
confirm: true, and an idempotencyKey.
| Name | Scope | Purpose |
|---|---|---|
catalog.search | mcp:read | Search allowed blocks by label, aliases, category, description, and runtime type |
catalog.get | mcp:read | Read one block's current config template and schema without its internal executor |
workflows.validate | mcp:read | Validate graph structure, credentials, integrations, schedule mode, and version |
workflows.diff | mcp:read | Separate topology, config-key, credential-reference, and trigger-schema changes |
workflows.prepare-test-data | mcp:write | Persist a redacted pin for one node against an exact workflow version and optional executionTargetId |
workflows.test | mcp:write | Run the candidate version in simulated dry-run mode |
workflows.execute-step | mcp:write | Execute one node or bounded path; live effects require an explicitly pinned version |
workflows.diff never returns credential values. Non-template credential fields are reported only as
<configured>. workflows.execute-step rejects stale pins when the source node config changed, rejects a
stop node that is not reachable from the start node, and reports the run id plus the node ids actually executed.
Typed publication lifecycle
workflows.propose is strictly non-mutating, carries the workflow read permission, and is advertised
to every authorized session (mcp:read). The mutating operations — workflows.apply,
workflows.update-partial, workflows.deploy, and workflows.execute — are advertised and callable
only when the session holds mcp:write; read-only sessions neither see nor can invoke them.
workflows.deploy is further restricted to an AIMS-authorized workspace API key and is hidden from
OAuth discovery. All five reuse the canonical capability executors, so the same validation,
confirmation, and idempotency rules apply on every surface.
| Name | Scope | Purpose |
|---|---|---|
workflows.propose | mcp:read | Read-only: validate a bounded graph + schedule mode + expected version; returns a semantic diff, validation report, blocked modes, missing prerequisites, and a planHash. For an existing workflow, an omitted expectedVersion is pinned to its current version. Adaptive-job recurrence is reported as a non-deployable blocked mode (it stays on the Command scheduling path) |
workflows.apply | mcp:write | Create/update a draft from the same hash-bound arguments sent to workflows.propose, plus planHash, idempotencyKey, and confirm: true |
workflows.update-partial | mcp:write | Apply a small ordered batch of typed surgical edits — mergeNodeConfig, renameNode, moveNode, addNode, removeNode, addEdge, removeEdge, setEdgeLabel — to the workflow's current candidate graph in one call, bound to an optional expectedVersion, idempotencyKey, and confirm: true; no separate propose step |
workflows.deploy | mcp:write | Deploy the pinned draft version, bound to planHash, expectedVersion, scheduleMode, idempotencyKey, and confirm: true; requires an AIMS-authorized workspace service-token key |
workflows.execute | mcp:write | Run active (default) or candidate, bound to idempotencyKey and confirm: true; poll the returned run id through runs.get |
For an existing target, callers may omit expectedVersion from workflows.propose: the server pins the
current version while constructing the proposal. workflows.apply may omit it again or pass the exact
expectedVersion returned by workflows.propose; both produce the reviewed normalized intent. A changed
target, argument (including node position), schedule mode, or expected version reshapes the
server-normalized planHash, so apply returns the actionable conflict -32009 /
workflow_proposal_stale and requires a fresh workflows.propose. expectedVersion is also rechecked
atomically under the workflow's advisory lock. An invalid confirmed graph returns -32602 /
workflow_invalid; unexpected implementation or storage failures remain masked as -32603. Only the
mechanical workflow mode publishes through this path. Adaptive-job recurrence remains a Command
scheduling feature, but command.submit is not a governed deployment path.
workflows.deploy has an additional AIMS boundary: it is advertised and callable only for a workspace
API key, which supplies service_token assurance. The key's acting user must have a current
aims_manager or system_owner assignment whose required assurance is also service_token. OAuth,
including an MFA-authenticated browser session, cannot deploy through MCP.
The same authorized service-token principal can call the read-only aims.governance.get tool with a
workflowId. It returns only the current stage, next action, active/candidate version status, bounded
deployment-readiness blockers, and a URL that hands the human decision back to Settings → AI
Governance. It does not expose evidence, rationales, approval records, manifests, profiles, or hashes. A saved workflow
change the active governance version does not cover yet is reported as the review_required blocker; the same
next action on an already-current workflow is only the standing governance-only change opportunity and is not
reported as a blocker.
If the AIMS assignment lookup itself fails, only the AIMS capabilities fail closed: workflows.deploy
and aims.governance.get are neither advertised nor callable and return -32003, while every unrelated
MCP tool in the session keeps working. The server emits a sanitized operational signal naming the surface,
workspace, and client; no database or error detail reaches the client or the log line.
MCP does not create or approve AIMS assessments, choose a renewal policy, or activate an AIMS system
version. A human completes those controlled steps in Settings → AI Governance, including independent
approval and either a dated renewal or No scheduled renewal. Once that version is active and its
authoritative production manifest is current, the service-token principal may call workflows.deploy.
Do not submit a natural-language deploy request through command.submit; it does not replace the typed,
governed deploy operation.
workflows.update-partial
Unlike apply, update-partial needs no separate propose call: it takes workflowId, an ordered
operations array, an optional expectedVersion (defaults to the workflow's current version when
omitted), idempotencyKey, and confirm: true, and applies the whole batch atomically against the
workflow's current candidate graph in one call. Either every operation in the array applies and the
resulting graph passes validation, or nothing is persisted. A schema-invalid or runtime-failing operation
returns workflow_update_partial_operation_failed and names the failing operation's index and declared
op; a final whole-batch validation failure returns workflow_invalid with the graph validation reason,
without attributing it to a single operation. addNode accepts an optional caller-supplied
nodeId, which lets later operations in the same batch address the new node; the ID must be unique in
the workflow. The result carries version, planHash, applied (one { op, summary, warnings } entry
per operation, with the resolved nodeId on addNode), and the same diff / validation shapes
workflows.propose / workflows.apply already publish. workflows.update-partial only ever writes
name, description, nodes, and edges on the workflow's draft — it never promotes a workflow to
production; workflows.deploy remains the only path that activates a version.
MCP tool discovery advertises the full typed operation union. At tools/call time, operation validation
is deferred to the canonical executor so malformed entries receive the same indexed domain error as
direct executor callers instead of a generic boundary-schema error.
update-partial rejects with the same actionable conflict -32009 /
workflow_collaboration_session_active when a live-collaboration session is already active on the
workflow, so a human editing the graph in the canvas and a typed caller never race each other's edits.
The mergeNodeConfig operation deep-merges its patch object into the target node's existing config
using JSON Merge Patch (RFC 7396) semantics, with one deliberate deviation: an array-valued field merges
by index, so the patch array's value at index i overwrites the existing array's value at index i,
preserving any existing tail beyond the patch's length, rather than replacing the array wholesale. A
null value at a key deletes that key. A patch that touches a managed integration key (credential
bindings or manifest fields) is rejected — use the workflow's existing integration-config path for those
instead. Before recursive schema parsing, the operation input is limited to 16 nesting levels, 1,000
object keys, 100 values in any array, 5,000 decoded values, and 65,536 decoded JSON bytes. Exceeding a
limit returns workflow_update_partial_operations_invalid without evaluating or persisting the patch.
workflows.get returns executionState with candidateVersionId, activeVersionId,
reviewStatus, synchronizedVersionId, and reviewReady. workflows.execute defaults to the active
production version. Pass target: "candidate" to test the candidate; it defaults to dry run. A live
candidate execution additionally requires dryRun: false and the matching expectedVersionId.
The result carries requestedTarget, versionId, resourceMode, and reviewIdentity, matching the
run evidence shown to humans. Candidate and active are not isolated runtimes: a live candidate uses
real workspace resources, and active v1 remains production until an explicit deploy promotes v2.
Knowledge Document lifecycle
documents.status reads an owned document's current indexing generation, indexing status, and
knowledge-base links; it is non-mutating and advertised to every authorized session (mcp:read).
The lifecycle mutations are advertised and callable only with mcp:write. Every operation reuses the
canonical capability executor, so document/knowledge-base ACLs, the generation-aware CAS, and idempotent
replay behave identically on MCP, the API, the Agent Runtime, and the knowledgeDocument workflow node.
| Name | Scope | Purpose |
|---|---|---|
documents.status | mcp:read | Read an owned document's indexing generation, status, and knowledge-base links |
documents.create | mcp:write | Create an owned text document in explicit knowledge bases and enqueue indexing |
documents.edit | mcp:write | Replace owned document metadata/content and enqueue re-indexing |
documents.trash | mcp:write | Reversibly trash a document and drop it from keyword and vector retrieval |
documents.restore | mcp:write | Restore a trashed document under a fresh indexing generation |
documents.link | mcp:write | Link an owned document to owned knowledge bases and re-index it |
documents.unlink | mcp:write | Remove knowledge-base links and their retrieval eligibility |
documents.reindex | mcp:write | Issue a fresh indexing generation so a stale job cannot publish stale content |
Each mutation carries the same envelope as the publication lifecycle: confirm: true plus an
idempotencyKey (missing either is -32602 with zero executor dispatch). Pass the document's current
expectedIndexingGeneration to bind the mutation to the state you read from documents.status; a losing
compare-and-set fails closed without mutating. Claiming, drafting, and completing a Human Task remain
human-only and are never advertised here.
Workspace management
workspaces.rename changes only the workspace display name. The slug, route key, URLs, id,
membership, and visibility are unchanged, so every integration, API key, agent card, and bookmarked URL
keeps working. It reuses the canonical capability executor and the same audited domain service the
Settings UI, tRPC, and REST rename share, so authorization and audit are identical on every surface.
| Name | Scope | Purpose |
|---|---|---|
workspaces.rename | mcp:write | Rename the workspace display name only; slug, URLs, and integrations stay stable |
The mutation carries the same envelope as the other lifecycles: confirm: true plus an idempotencyKey
(missing either is -32602 with zero executor dispatch; a repeated key replays the recorded result
rather than renaming twice). On the organization endpoint it is a workspace-bound tool: the call must name
its workspaceId, which is re-authorized against the caller's live membership and the grant's workspace
ceiling before it runs. There is no organization-global rename.
Workspace role authorization
An OAuth or API-key scope states what the client was granted; it says nothing about what the acting
user may do. Every mutating tool therefore also passes the shared workspace role/permission matrix
against the principal's current membership: workflows.apply, workflows.update-partial,
workflows.deploy, workflows.execute, and the documents.* lifecycle require Member, Admin, or
Owner; workspaces.rename requires Admin or Owner (an explicit workspace-admin action). A Viewer (or a principal with no
current membership) is refused with 403-class -32003
(MCP tool <name> requires a workspace role that can write. / … can admin. / … can execute.) before any executor call.
Read tools, workflows.propose, and documents.status need membership only.
That workspace role is separate from AIMS authorization. workflows.deploy additionally requires the
workspace API key posture and a current AIMS aims_manager or system_owner service-token assignment;
the deployment gate rechecks that assignment at execution time.
Governed capability waves
The following preview operations are selected from the canonical capability registry. They are visible only to
write-capable MCP sessions while FLUXUS_MCP_CAPABILITY_WAVES is enabled. Registry schemas, workspace roles,
confirmation, idempotency, egress controls, and the canonical executor remain authoritative.
| Area | Tools |
|---|---|
| Workflow/run control | workflows.create-from-template, runs.cancel, runs.rerun, schedules.run |
| MCP connections | mcp-connections.list, mcp-connections.get, mcp-connections.authorization-handoff, mcp-connections.create, mcp-connections.update, mcp-connections.refresh, mcp-connections.configure-tool, mcp-connections.test, mcp-connections.delete |
| Files and knowledge | files.list, files.get, files.read, files.delete, knowledge.sync |
| Mail and feeds | mailer.list, mailer.get, mailer.reply, rss.list, rss.get |
| Web and research | web.search, web.read, web.crawl, research.run |
| Artifacts and skills | presentation.create, image.generate, skills.load |
Connection administration returns endpoint metadata, credential-reference names, status, and discovered tool
schemas; it never returns resolved headers or secret values. Creating a connection accepts only header templates
such as Bearer {{credential.remote_api_key}}. Consent and credential exchange remain human UI actions.
Command tools
The higher-level natural-language authoring path (platformMcpCommandToolNames) remains available
alongside the typed lifecycle. command.status is a read; command.submit, command.confirm, and
command.cancel require mcp:write and are hidden from read-only sessions.
command.submit may author or schedule work, but it is not the route for AIMS-governed deployment;
clients must use the typed workflows.deploy operation after human governance is complete.
| Name | Scope | Purpose |
|---|---|---|
command.submit | mcp:write | Submit a natural-language Command job (validation, confirmation, idempotency) |
command.status | mcp:read | Poll a submitted Command job's status and any pending confirmation |
command.confirm | mcp:write | Confirm a pending Command job's proposed mutation |
command.cancel | mcp:write | Cancel a submitted Command job |
Unknown or non-allowlisted name: -32602 Unknown MCP tool: …. Read-only sessions cannot see or
call any write-scoped tool. Executors redact secret-shaped strings.
Organization endpoint
One connection for every workspace an access grant reaches:
POST /api/v1/platform/mcp/org/{organizationId}
Auth. OAuth 2.1 tokens whose audience is the organization resource
(https://<origin>/api/v1/platform/mcp/org/{organizationId}, claim organization_id), or an
organization API key. Workspace tokens and workspace, personal, or copilot keys are refused
(401 / 403 -32003). An OAuth session also needs an access grant: the reach the user chose on
the consent page (all workspaces or selected ones; read only or read and write). Without one the
endpoint answers 403 -32003 and asks the client to authorize again.
Targeting. Every workspace-bound tool advertises a required workspaceId (the registry schema plus
that one property) and its description starts with Acts in the workspace identified by workspaceId.
workspaces.list and capabilities.list take none; workspaces.list returns only the workspaces the
grant reaches. billing-usage.organization keeps its own workspaceId, which is both the target and
the filter.
Per-call authorization. The target must be inside the grant and inside the organization, the user
must currently hold access there, and the effective authority is token scopes ∩ grant permission ∩ the
user's live role and capabilities. A workspace outside the grant, outside the organization, unknown, or
where membership was lost returns one message: Workspace is not available to this connection. Call workspaces.list to discover the workspace ids this connection may use. (-32003). A missing
workspaceId returns -32602 naming the tool. AIMS-gated tools (workflows.deploy,
aims.governance.get) are never advertised here.
tools/list is the union the scopes and grant permission allow (read-only grants hide write tools);
each call is still authorized for its target. Audit events carry the target workspace and
metadata.surface: "organization". The user rate-limit key is org:{organizationId}:{userId}. Rollout
flag: FLUXUS_MCP_ORGANIZATION_HUB.
Rate limits
Three sliding windows, 120 / 60s each:
| Dimension | Key |
|---|---|
| IP | x-real-ip or unknown |
| Client | OAuth clientId or api-key:{userId} |
| User | {workspaceId}:{userId} (organization endpoint: org:{organizationId}:{userId}) |
Exceeded: 429 -32029 MCP rate limit exceeded. IP is checked before auth.
Audit and idempotency
Each tools/call writes platform.mcp.call (no bearer stored). Audit failure does not fail the
RPC. Read tools, workflows.propose, and documents.status take no Idempotency-Key; the write-scoped
workflows.apply, workflows.update-partial, workflows.deploy, workflows.execute, and
documents.* lifecycle tools require an idempotencyKey argument, so one key retried returns the first
draft, partial-update version, deployment, run, or document mutation rather than creating a second.
Deploy audit events are attributed to the authenticated external principal.
Errors (HTTP + JSON-RPC)
| HTTP | error.code | Message / cause |
|---|---|---|
| 401 | -32001 | Missing/invalid bearer |
| 403 | -32003 | Workspace / scope / membership |
| 405 | — | GET |
| 413 | -32010 | Body > 1 MiB |
| 429 | -32029 | Rate limit |
| 400 | -32700 / -32600 | Parse / invalid request |
| 200 | -32601 / -32602 / -32004 / -32009 / -32020 | Method/params/not found/conflict/availability |
| 200 | -32603 | Masked unexpected implementation or storage error |
Expected workflow publication failures return -32004 for workflow_not_found or -32009 for a
blocked/conflicting publication. Their safe error.data contains code, retryable, and, for an AIMS
block, the bounded blockers list. Arbitrary diagnostic details are not returned.
Gaps
- GET/SSE not implemented (
405). tools/listdoes not emitlistChangednotifications.capabilities.listschema vs runtimematuritydiffer (preview is accepted then overwritten).- Rate limiters are in-process (not shared across instances).
- Read tools and the read-only
workflows.propose/documents.statusare available to every authorized session; the typedworkflows.apply,workflows.update-partial,workflows.execute, anddocuments.*lifecycle mutations requiremcp:writeand a Member-or-higher workspace role.workflows.deployadditionally requires a workspace API key and a current AIMS service-tokenaims_managerorsystem_ownerassignment. The per-workflow MCP servers remain a different surface (/api/v1/workflows/mcp/...).
