save-context
Save nodes and relationships into the project's context graph (Simple mode). Needs the fabric:ontology.write grant.
curl -sS -X POST https://fabric.wexa.ai/mcp/$FABRIC_PROJECT_ID \
-H "authorization: Bearer $FABRIC_API_KEY" \
-H "Mcp-Session-Id: $MCP_SESSION_ID" \
-H "content-type: application/json" -H "accept: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"save-context","arguments":{"nodes":[{"label":"FixtureCustomer","key":"id","properties":{"id":"c-1001","name":"Acme"}}]}}}'{
"content": [
{
"type": "text",
"text": "{\"lifecycle_id\":\"qlc_000168\",\"result\":{\"nodes_merged\":1,\"relationships_merged\":0,\"ontology_extended\":true,\"new_labels\":[\"FixtureCustomer\"],\"success\":true}}"
}
],
"isError": false
}Parameters
| Parameter | Type | Required | Notes |
|---|---|---|---|
nodes | array | Optional | Provide at least one node or relationship across nodes and relationships. Each node is {label, key, properties}; key names the merge-key property, which must be in properties, starts with a letter and holds only letters, digits and underscores. Property names may not start with _. At most 10,000 nodes and relationships per call, counted together; more is refused (413) and nothing is written, so split the batch. A bad or platform-managed label is refused. |
relationships | array | Optional | Each is {type, from, to, properties}, where from and to are {label, key, value}. Endpoints are looked up, never created: send their nodes in this call or an earlier one. |
Errors
ValidationError(422): the project is an Advanced/manual project. Nothing was written.UpstreamError(502): nodes were sent and none was written; the message lists why for each item. A partial write is not an error: see partial writes.UpstreamError(504): the call ran past about 25 seconds. None, part or all of the batch may have been written; re-sending is safe.
Show full response
A real response, captured from a live call to save-context:
{
"content": [
{
"type": "text",
"text": "{\"lifecycle_id\":\"qlc_000168\",\"result\":{\"nodes_merged\":1,\"relationships_merged\":0,\"ontology_extended\":true,\"new_labels\":[\"FixtureCustomer\"],\"success\":true}}"
}
],
"isError": false
}success:trueonly when every node and relationship sent was writtennodes_merged: Nodes written (created or updated)relationships_merged: Relationships written (created or updated)errors: One message per item left out, naming it asnode[i]orrelationship[i]; absent when there are noneontology_extended: A label or relationship type new to this project was addednew_labels,new_rel_types: Which ones
More details
Full description from the tool, as the gateway advertises it:
Save nodes and relationships into this project's context graph (Simple/auto mode). The ontology self-organizes: new labels/relationship types are registered automatically. Nodes: [{label,key,properties}]; relationships: [{type,from:{label,key,value},to:{label,key,value},properties}]. Relationship properties ARE persisted. Scalars store as-is; arrays AND nested objects are stored JSON-encoded (so they read back as a string, not a list); a null value is not stored at all. Property names may not start with _ (reserved for graph metadata). Round-trip: a node returned by query-context can be passed straight back here. The server owns project_id, row_pk, updated_at and wx_source and rewrites all four on every write, so leaving them in the payload is harmless — wx_source is dropped rather than stored, and setting it to anything other than "save-context" is refused because delete-context trusts it.
Partial writes: check success
A call that sends nodes and writes none of them fails, and both SDKs raise. A call that writes
some items succeeds — and so does a batch of only relationships, even when none was written:
it answers 200 with success: false, the counts of what landed, and one errors entry per item left
out. The SDKs return that result rather than raising, so the counts are not lost — your code has to
check success.
out = fabric.save_context(nodes=nodes, relationships=relationships)
if not out["success"]:
print(out["nodes_merged"], out["relationships_merged"], out.get("errors"))What ends up in errors instead of failing the call:
- A relationship whose
fromortonode does not exist —relationship[3] (ISSUED_TO): endpoint node(s) not found. Endpoints are looked up, never created: send the nodes in the same call or an earlier one. - A batch write that failed on the server —
not confirmed written … re-sending the save is safe. - A node whose access scope cannot be worked out, when the project has scope tagging on.
A merge key — a key, from.key or to.key — must start with a letter followed only by letters, digits
and underscores (order_id, not order-id or order id). Any other key refuses the whole call with 422
before anything is written, naming the item, e.g. node[1] (Order): invalid merge key "my-key".
Limits and re-sending
- Size: at most 10,000 nodes + relationships per call, counted together. More is refused with
413before the approval gate. A few thousand items per call is a comfortable size. - Time: about 25 seconds per call. A call that runs out answers
504(UpstreamErrorin both SDKs). It may have written none, part or all of the batch: writing stops at the timeout, but a batch already running on the server can still land. - Re-sending: every write is an idempotent MERGE on the merge key, so sending the same batch
again — after a timeout, or to retry the items listed in
errors— never creates duplicates. If you sent anidempotency_key(REST or the Python SDK), re-send under a new key or none: a key whose call timed out answers409"outcome unknown" and is never run again. A re-send can time out again: the server checks every relationship type that is new to the project before writing anything, and that check is slow on a large graph. If it does, split the batch into smaller calls with fewer new relationship types in each.
Marked consequential: it is audited in full, and a policy rule may hold it for a person to approve. See Policy and approvals.
Every tool call works the same way: How every tool call works.