On this page

Context

save-context

Can be held for approval

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

ParameterTypeRequiredNotes
nodesarrayOptionalProvide 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.
relationshipsarrayOptionalEach 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: true only when every node and relationship sent was written
  • nodes_merged: Nodes written (created or updated)
  • relationships_merged: Relationships written (created or updated)
  • errors: One message per item left out, naming it as node[i] or relationship[i]; absent when there are none
  • ontology_extended: A label or relationship type new to this project was added
  • new_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 from or to node 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 413 before 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 (UpstreamError in 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 an idempotency_key (REST or the Python SDK), re-send under a new key or none: a key whose call timed out answers 409 "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.