MCP Tools
Overview of Flywheel MCP tool families.
Flywheel MCP tools are grouped by workflow area:
- Graph and node creation.
- Node inspection and summarization.
- Artifact publish and retrieval.
- Hook and automation management.
- Managed compute workflows.
- Import, export, and sharing workflows.
For conceptual hook guidance, see
Hooks And Automations. For exact hook inputs and outputs,
call flywheel_get_contract_section with the hooks section or inspect the
generated hook API reference. Hook definitions declare one or more events in
workflow_yaml.on; supported events are artifact.finalized and
node.published.
For exact tool inputs and outputs, call flywheel_get_contract from your MCP
host. Use flywheel_get_contract_section for focused sections such as graph,
artifacts, hooks, compute, import/export, and campaign workflows.
Edit a committed node
MCP node-body editing uses a stage lease and a full payload, not the repository
CLI's operation staging area. Start with a committed node titled
Documentation MCP example, empty content and summary, null repository context
fields, and revision 0 (for example, one just created with
flywheel_commit_new_node). For another node, read its current revision and
preserve its other body fields before editing.
Replace ${NODE_ID} with that node's ID and ${STAGE_SESSION_ID} with one
unique editing-session ID. These are documentation placeholders, not literal
MCP values. Reuse the same session ID in both requests.
Call flywheel_acquire_stage_lease with:
{
"node_id": "${NODE_ID}",
"stage_session_id": "${STAGE_SESSION_ID}",
"base_committed_revision": 0
}
After acquisition succeeds, call flywheel_commit_node with:
{
"node_id": "${NODE_ID}",
"stage_session_id": "${STAGE_SESSION_ID}",
"base_committed_revision": 0,
"staged_payload": {
"title": "Documentation MCP example",
"content": "",
"summary": "Documentation MCP commit",
"repo_context": {
"repo_url": null,
"branch_name": null,
"head_commit_sha": null,
"origin_host": null,
"updated_by": null,
"external_transcript_ref": null
}
}
}
The commit advances the node to revision 1, saves the summary
Documentation MCP commit, and releases the lease. Skipping acquisition
fails without changing the node. For a longer edit, use
flywheel_heartbeat_stage_lease; release an abandoned edit with
flywheel_release_stage_lease. Consult the tool contract for lease timing
and revision-conflict handling.
Share a node with an MCP collaborator
Node sharing identifies a collaborator by email, not by user ID. Sharing
modes are private, unlisted, and public; collaborator roles are
viewer, editor, and admin. This is a node-level policy, separate from
repository membership
below.
Grant one owned node's writer@example.test editor access:
flywheel_set_sharing_for_node({
"node_id": "<node-id>",
"sharing_mode": "private",
"collaborators": [{"email": "writer@example.test", "role": "editor"}]
})Immediately confirm the grant with the creator's own readback:
flywheel_get_node_sharing({"node_id": "<node-id>"})Apply the same sharing configuration to several owned nodes at once:
flywheel_set_sharing_for_nodes({
"node_ids": ["<node-a>", "<node-b>"],
"sharing_mode": "unlisted",
"collaborators": [{"email": "reader@example.test", "role": "viewer"}]
})flywheel_set_sharing_for_nodes applies one complete replacement policy to
every listed node; it is not an additive grant. Read each target node's
current sharing first, and use the bulk call only when the exact same
complete collaborator list and sharing mode should apply to every target.
When existing nodes carry different collaborator lists, replace each node
individually instead. The bulk response's skipped_non_owned lists node IDs
that were not applied because the caller does not own them; never report a
skipped node as updated, and read back every node the call did apply to.
One call changes at most 1000 distinct nodes; repeated node_ids count once.
A call that names more is refused with 422 and the server's message
Too many nodes: one request may change at most 1000 nodes, and this one names <count>.,
and nothing changes. Each call saves all or nothing: nodes the caller may not
change are skipped and listed in skipped with a reason, and if the save
fails no node changes. An error reported after the save (for example while
reading the result back) can leave it saved, so read sharing back after an
error. To share a larger set, split node_ids into calls of at most 1000
each. Every call is a separate save, so earlier calls stay saved if a later
one fails.
Only the node's creator or an existing admin collaborator can read or change
its sharing policy; a plain viewer or editor collaborator cannot call
flywheel_get_node_sharing or either sharing setter for that node.
Share a whole graph
flywheel_set_sharing_for_graph shares every node of a graph you may
change, by any one of its nodes whose sharing you may change, however large
the graph is:
flywheel_set_sharing_for_graph({
"node_id": "<a node of the graph you may share>",
"sharing_mode": "public",
"collaborators": [{"email": "reader@example.test", "role": "viewer"}]
})collaborators only adds people: everyone already named on a node keeps
their access, and leaving collaborators out changes only the visibility.
Nodes you may not change are skipped and counted by reason.
A graph of at most 1000 nodes is saved in that call, all or nothing, and the
answer is final (applied_count, skipped). A larger graph is saved by a
sharing job on the server, a few hundred nodes at a time: the answer is the
job (job_id, state, node_count, done_count, skipped_by_reason).
While it runs, part of the graph has the new setting and the rest keeps the
old one. Follow it until state is done or failed:
flywheel_get_graph_sharing_job({"job_id": "<job id>"})One job runs per graph at a time; sharing the same graph again while a job
runs is refused with 409 naming that job.
Remove one collaborator without disturbing the rest
The single-node setter always replaces the full collaborator list, so removing one person means resubmitting everyone else you want to keep:
- Call
flywheel_get_node_sharing({"node_id": "<node-id>"})and read itscollaboratorsarray. - Build a new list that contains every returned collaborator except the one
being removed, keeping each remaining collaborator's existing
emailandroleunchanged. - Call
flywheel_set_sharing_for_nodewith that new list and the node's currentsharing_mode. - Call
flywheel_get_node_sharingagain and confirm the removed email is absent and every retained collaborator/role is unchanged.
The setter accepts collaborator entries by email only. A readback entry
that has a user_id but no email cannot be re-expressed through this
setter — do not submit a replacement list that would silently drop that
entry. When every collaborator you intend to keep has a usable email, the
replacement above is safe; otherwise, leave that node's policy unchanged
rather than lose the entry.
Passing collaborators: [] removes every collaborator from the node. Use it
only when that is the explicit intent, never as a shortcut for removing one
person.
Repository membership uses a different identity
Repository membership is a separate hosted control surface from node
sharing. It identifies a collaborator by principal UUID, not email, and
every write carries a fresh UUIDv4 membership_change_id plus the
expected_roles_digest from a just-completed member read. It has no MCP
tool binding; it is an authenticated HTTP control-plane surface. Do not
describe a repository membership call as a node sharing call, and do not
expect the node sharing setters to accept a principal UUID.
Read current membership and its digest:
GET /v1/repositories/{repository_id}/members
-> {
"repository_id": "...",
"roles": [{"principal_id": "...", "role": "..."}],
"roles_digest": "..."
}Grant or change a member's role, reusing the digest from that read:
PUT /v1/repositories/{repository_id}/members/{principal_id}
Idempotency-Key: <membership_change_id>
{
"wire_version": 1,
"membership_change_id": "<fresh-uuidv4>",
"expected_roles_digest": "<digest-from-member-read>",
"role": "editor"
}Remove a member the same way, with its own request shape:
POST /v1/repositories/{repository_id}/membership-removals
Idempotency-Key: <membership_change_id>
{
"wire_version": 1,
"membership_change_id": "<fresh-uuidv4>",
"expected_roles_digest": "<digest-from-member-read>",
"principal_id": "<principal-id>"
}Campaign submissions
When participating in a campaign, treat metadata.campaign_role = "submission"
as the marker for the artifact that should be accepted by the campaign. Read
the campaign config before finalizing it. If the config declares
submission_policy.required_visibility = "public", make the attempt node public
first; private drafts should remain ordinary artifacts without the submission
role.
After a valid submission artifact is finalized, Flywheel creates a campaign
submission lifecycle record. Use flywheel_get_artifact_campaign_submission to
check the status for a known node/artifact pair,
flywheel_list_node_campaign_submissions to list statuses for an attempt node,
or flywheel_get_campaign_submission when you already have the submission id.
Invalid submissions return 422 during finalization and do not create lifecycle
records. forwarded means Flywheel handed the submission to campaign
automation; terminal states are scored, rejected, and failed.
Mutating tools can return HTTP 429 when a user exceeds write limits. See
Usage Limits for the current limits and retry
behavior.