> ## Documentation Index
> Fetch the complete documentation index at: https://docs.erstan.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Build agents over MCP

> Create, inspect, validate, and publish Erstan agents and their reusable Skill packages from an external MCP client.

Erstan's MCP server lets an external coding agent build and publish Agent Builder workflows while people keep control of intent and review. The authoring tools expose the live node catalog and a concise builder guide, then let the client create or update a complete draft graph, validate it, and publish the exact reviewed revision.

<Info>
  For Claude and Codex, install the official Erstan plugin with browser-based OAuth and authorize **Build** access. Use the Advanced API-key setup only for automation or MCP clients that do not support the plugin flow. See [Connect a Coding Agent](/developers/connect-coding-agent).
</Info>

<Warning>
  `publish_agent` makes a validated draft live and therefore requires an explicit user request, both authoring scopes, the latest opaque revision, and normal live edit/team access. The backend does not expose archive or delete through these tools. `test_agent` can run a draft, uses real tools, and therefore additionally requires `agents:run` and normal approval policies.

  `publish_agent_skill` likewise requires an explicit publication step and the latest Skill `currentVersion`. `create_agent_skill` always creates a draft.
</Warning>

### Backend-enforced guarantees

These restrictions are server controls, not prompt guardrails:

* `create_agent` always creates a draft, and `update_agent` writes a draft while preserving any retained published version.
* `publish_agent` validates the complete saved graph and publishes only the exact current revision/version. The authoring tool family exposes no archive or delete operation. `test_agent` runs the guarded current draft; `run_agent` is a separate capability that resolves only a retained published version.
* Every call re-checks the connected user's live workspace, team, and agent permissions. Advanced API keys use their creator's live permissions.
* `update_agent` and `publish_agent` require an opaque revision, and the backend atomically rejects stale revisions before mutation. The client handles that result by re-reading and reconciling; it cannot bypass the check by retrying.
* `validate_agent` never mutates, publishes, or executes the draft.
* Skill package validation never saves or executes code. Skill creation is draft-only, full-package updates require `expectedVersion`, and publication re-validates the current package.
* User-authored Skill actions run only through the isolated E2B action runtime. Declaring `sideEffects: "none"` disables action-runtime network access, but does not make workspace-authored code trusted or bypass write confirmation.

Consequently, authoring clients can publish only a valid, current, manageable draft and cannot archive or delete through MCP. Draft testing remains an explicit, separately scoped operation; prompts and Skills cannot bypass those controls.

## Authorize agent building

Install the plugin and authorize **Build** access for the normal interactive setup. This grants the connected-app permissions **View agents**, **Build agents**, **Run agents**, **Publish agents**, and **View runs**, plus read-only access to work, documents, and files. It does not grant task, document, or file writes.

### Advanced API-key setup

For a non-OAuth client, create a workspace API key in **Settings → API Keys** with both authoring scopes:

| Scope          | Allows                                                                                                                                                   |
| -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `agents:read`  | Read the node catalog and builder guide, inspect agents, discover Skills, validate a proposed Skill package, and read complete workspace Skill packages. |
| `agents:write` | Create Agent and Skill drafts. Paired with `agents:read`, it also enables version-guarded updates, validation, and publishing.                           |

Scopes are exact and additive. `create_agent` needs `agents:write`; `update_agent`, `validate_agent`, and `publish_agent` require both `agents:read` and `agents:write`. A practical authoring key therefore needs both scopes. The existing `agents:list` and `agents:run` scopes remain separate and apply only to discovering and running retained published versions.

## Access model

An OAuth connection acts as the user who approved it; an API key acts as the user who created it. Erstan evaluates that person's live workspace, team, and agent permissions on every tool call:

* `get_agent` can read only an agent the connection owner can normally see.
* `create_agent` must target an accessible team in the connection owner's workspace.
* `update_agent`, `validate_agent`, and `publish_agent` require the connection owner's normal edit permission for the target agent. Publishing also re-checks visibility of its current home team when one is assigned.

There is no separate agent allowlist. If the connection owner loses team membership or agent access, the connection loses it immediately.

Disabled agents remain authorable when the connection owner still has normal workspace and team permission. Disabling prevents execution; it does not discard the draft. Archived agents cannot be updated or validated over MCP.

## Tools

`tools/list` advertises a tool only when the OAuth grant holds the corresponding connected-app permission or an advanced API key holds every scope shown below. The plugin's **Build** profile supplies the standard authoring permissions.

| Tool                      | Scope                                         | What it does                                                                                                                                                                            |
| ------------------------- | --------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `get_agent_node_catalog`  | `agents:read`                                 | Returns the live builder-visible Agent Builder node types, descriptions, aliases, sections, and supported configuration fields. Use it instead of relying on a copied or stale catalog. |
| `get_agent_builder_guide` | `agents:read`                                 | Returns concise graph-construction, node-configuration, validation, and lifecycle guidance for an external builder.                                                                     |
| `list_agent_tools`        | `agents:read`                                 | Lists tools that can be pinned to supported Agent nodes.                                                                                                                                |
| `list_agent_skills`       | `agents:read`                                 | Lists workspace and system Skills that can be pinned to Agent nodes.                                                                                                                    |
| `validate_agent_skill`    | `agents:read`                                 | Normalizes and validates a complete Skill package without saving, publishing, or executing it.                                                                                          |
| `create_agent_skill`      | `agents:read` + `agents:write`                | Creates a workspace Skill draft from a complete package. It never publishes or executes the Skill.                                                                                      |
| `get_agent_skill`         | `agents:read`                                 | Reads a workspace Skill's complete current package and `currentVersion`. System Skills remain read-only.                                                                                |
| `update_agent_skill`      | `agents:read` + `agents:write`                | Replaces or edits a workspace Skill using the required current `expectedVersion`.                                                                                                       |
| `publish_agent_skill`     | `agents:read` + `agents:write`                | Re-validates and publishes the current workspace Skill using the required current `expectedVersion`.                                                                                    |
| `get_agent`               | `agents:read`                                 | Reads an accessible agent's current authoring state, including its graph, draft status, `currentVersion`, and `builderUrl`.                                                             |
| `create_agent`            | `agents:write`                                | Creates a new draft agent in an accessible team from the supplied metadata, nodes, and edges.                                                                                           |
| `update_agent`            | `agents:read` + `agents:write`                | Updates an editable agent using its required opaque `revision`. Editing a published graph creates or advances its draft while the retained published version remains runnable.          |
| `validate_agent`          | `agents:read` + `agents:write`                | Validates an editable draft or proposed graph and returns `valid` plus structured `diagnostics`. It never publishes the agent.                                                          |
| `test_agent`              | `agents:read` + `agents:write` + `agents:run` | Runs the guarded current draft in Builder Preview. It executes real tools and preserves normal approval policies.                                                                       |
| `publish_agent`           | `agents:read` + `agents:write`                | Re-validates and publishes the exact manageable current draft identified by its required opaque `revision` and optional `version` guard.                                                |

<Note>
  MCP `get_agent` reads authoring state, including drafts. `list_agents` under `agents:list` is intentionally different: it lists only retained published versions that can be run externally.
</Note>

### Argument and response summary

* `get_agent_node_catalog` accepts optional `nodeType`, `builderSection`, and `includeFieldDetails` filters. Set `includeActionTypes` to `true` to include valid action types and optionally set `maxActionTypes` from 10 to 500 (default 120). Its versioned, builder-visible response is the source of truth; hidden/internal nodes, event triggers, raw HTTP categories, and unsafe action names are filtered out.
* `get_agent_builder_guide` takes no arguments and returns versioned builder guidance, including the external tool-policy and baseline-tool rules below.
* `get_agent` takes `agentId` and an optional positive integer `version`. It returns `{ "agent": { ... }, "builderUrl": "..." }`; `agent.revision` is the current opaque write token. An explicit historical version also adds top-level `selectedVersion`, while `agent.currentVersion` still identifies the actual current graph version.
* `create_agent` requires `teamId`, `name`, `nodes`, and `edges`. Optional metadata includes `description`, `category`, `tags`, `difficulty`, `estimatedTime`, and `behaviorType` (`interactive` or `pipeline`). The new agent is shared with the selected team, and its response includes the first opaque `revision`.
* `update_agent` requires `agentId`, the latest opaque `revision`, and at least one metadata or graph change. `version` is an optional additional guard against editing a different current graph version. It does not move an agent to another team. Never decode, synthesize, or reuse an old revision.
* `validate_agent` can validate a saved draft by `agentId`, or validate proposed `name`, `behaviorType`, `nodes`, and `edges` before creation. Optional proposed fields overlay the saved graph when `agentId` is present. Its response includes `agentId`, `version`, `valid`, and structured `diagnostics` with severity, code, message, and relevant graph paths or IDs.
* `publish_agent` requires `agentId` and the latest opaque `revision`; pass `currentVersion` as optional `version` for a second guard. It returns the published authoring state plus `published: true` and `alreadyPublished`. A current already-published version is a validated no-op. If a response is lost, call `get_agent`: matching `status: "published"`, `currentVersion`, and `publishedVersion` confirms success; otherwise do not retry with the stale pre-publish revision.
* A canonical Skill package is `{ "version": 1, "entryPath": "SKILL.md", "files": [...], "actions": [...] }`. Every action points to an included file and declares `runtime: "e2b"`, `language`, and `sideEffects` (`none` or `external`).
* `validate_agent_skill` and `create_agent_skill` accept the complete object as either `package` or `packageJson`. Create also requires a display `name` and always returns a draft.
* `get_agent_skill` returns the complete package as `skill.packageJson`. Send that complete package to `update_agent_skill` with the latest `skill.currentVersion` as `expectedVersion`; do not send only changed files.
* `publish_agent_skill` accepts `skillId` and the latest `expectedVersion`. A stale version returns `skill_version_conflict`; re-read and reconcile instead of overwriting newer work.

## Recommended flow

1. Call `get_agent_builder_guide` once at the start of the build.
2. Call `get_agent_node_catalog` and choose only node types and fields present in the returned live catalog.
3. Construct the complete directed graph and call `create_agent` with the target `teamId`.
4. Keep the returned agent ID, opaque `revision`, `currentVersion`, and `builderUrl`.
5. Call `validate_agent`. The backend requires the complete proposed definition to pass validation. If an existing agent has unrelated pre-existing errors, stop before writing and ask whether the user wants to expand the change to repair them.
6. Use `get_agent` to re-read the persisted draft. For every update, pass its latest opaque `revision`; optionally pass `currentVersion` as `version` for an additional graph-version guard. Replace the stored revision with the new value returned by each successful update.
7. Use `test_agent` with the current draft version when an execution test is authorized, then inspect the returned run. After the user explicitly asks to make the validated draft live, call `publish_agent` with the latest revision and optional current version.

## Manage a Skill package

Use the same authorized connection for reusable Skill packages:

1. Build the complete package, including `SKILL.md`, every referenced text file or script, and action metadata.
2. Call `validate_agent_skill` and resolve every error. Validation does not save or run the package.
3. Call `create_agent_skill`; it always creates a draft. Review the returned `skill.packageJson`.
4. For later changes, call `get_agent_skill`, apply the intended edit to the complete package, and call `update_agent_skill` with its latest `currentVersion` as `expectedVersion`. Omit `status` to preserve it. A published Skill has no separate retained draft pointer, so an update to one changes its live package.
5. Publish only when explicitly requested by calling `publish_agent_skill` with the newly returned `currentVersion`.

ZIP import/export preserves action metadata in `.erstan/skill-package.json`; older `SKILL.md`-only archives remain valid. The manifest is transport metadata and is not included in `packageJson.files`.

## Agent Builder skill

The official plugin installs and versions this Skill automatically. The reference below is for advanced MCP clients that cannot install the plugin; Claude and Codex plugin users should not paste a separate copy into their projects. It teaches other clients how to use the backend-enforced authoring workflow without treating prompt instructions as the security boundary.

```markdown theme={null}
---
name: erstan-agent-builder
description: Create or update an Erstan Agent Builder draft over the erstan MCP server, validate its graph, and publish it only when explicitly requested. Use when asked to build, configure, revise, or publish an Erstan agent or its lanes.
---

# Erstan Agent Builder protocol

Use the connected `erstan` MCP server to author a draft. The Erstan backend
enforces permissions, revision-guarded writes and publishing, published-only public execution, and
optimistic concurrency. Treat server errors as authoritative control results.

## Procedure

1. Call `get_agent_builder_guide` at the start of every authoring request.
2. Call `get_agent_node_catalog`. Use only returned node types, fields, aliases,
   action types, and tool sources. Do not invent hidden or unsupported fields.
3. Establish whether this is a new or existing agent. For a new agent, require
   the target `teamId`. For an existing agent, require its `agentId` and call
   `get_agent` before designing changes.
4. Build a complete graph with stable node and edge IDs. Represent lanes as
   supported start nodes from the live catalog. Connect every lane's start to
   its own executable path. Lanes do not need edges to one another.
5. Preflight the proposed graph with `validate_agent`. For an existing agent,
   include its `agentId` so the proposed fields overlay the saved definition.
6. Persist through `create_agent` or `update_agent`. Supplying `nodes` or `edges`
   to an update replaces that entire array, so send the complete replacement
   graph rather than a fragment. Pass the latest opaque `revision`; optionally
   pass `currentVersion` as `version`. Do not decode or synthesize revisions.
7. Call `validate_agent` on the saved draft, then re-read the final state with
   `get_agent`.
8. If the user explicitly requested publishing, call `publish_agent` with the
   latest revision and optional current version only after saved validation passes.
   Otherwise stop at the validated draft and do not make it live.
9. Hand back the agent ID, current and published versions, `builderUrl`, validation
   result, publication result, and any remaining warnings. Do not claim that the
   draft was tested unless it was actually tested, or published unless
   `publish_agent` returned success.

For a new agent, resolve every error diagnostic before persistence. For an
existing agent, resolve errors caused by the requested change. If preflight
reports unrelated pre-existing errors, stop before writing: the backend validates
the complete candidate and will reject the update until those errors are fixed.
Report them and ask whether the user wants to expand the scope. Do not repair
unrelated behavior without approval.

## Revision conflicts

If `update_agent` or `publish_agent` returns `agent_revision_conflict` or `agent_version_conflict`,
make at most one automatic reconciliation attempt for that intended update:

1. Re-read with `get_agent`.
2. Compare the current graph with the intended change and preserve unrelated
   concurrent edits. If both edits touch the same nodes, edges, attachment
   points, or behavior, stop and ask the user instead of guessing.
3. Reapply only the requested change using the newly returned revision.
4. If it conflicts again, stop and report the concurrent edit instead of
   repeatedly overwriting newer work. Apply the same one-reconciliation limit
   to any later update made to correct validation errors.

The backend atomically rejects stale revisions before mutation. This retry
procedure reconciles user intent; it does not implement or replace concurrency
control.

If the user later approves a same-node merge or expanded repair, treat that as a
new intended update: re-read first, preflight the newly approved complete graph,
and use the latest revision and version.

## Lifecycle boundary

The authoring tools create, read, update, validate, test, and revision-safely
publish drafts. They expose no archive or delete operation. `test_agent` is the
separately scoped draft-test operation; `run_agent` executes retained published
versions. Never substitute one for the other, and never publish without an
explicit user request.
```

## Create a draft

The exact argument schema is available through MCP `tools/list`. A create call includes the target team, agent metadata, and the complete node-and-edge graph:

```json theme={null}
{
  "name": "create_agent",
  "arguments": {
    "teamId": "team_abc123",
    "name": "Request summarizer",
    "description": "Summarize a submitted request for review.",
    "behaviorType": "pipeline",
    "nodes": [
      {
        "id": "start_form",
        "type": "start",
        "position": { "x": 0, "y": 0 },
        "data": {
          "label": "External request",
          "triggerType": "input_form",
          "triggerConfig": {
            "inputForm": {
              "title": "Submit a request",
              "fields": [
                { "id": "request", "key": "request", "label": "Request", "type": "textarea", "required": true }
              ]
            }
          }
        }
      },
      {
        "id": "reply",
        "type": "agent",
        "position": { "x": 280, "y": 0 },
        "data": {
          "label": "Summarize request",
          "instructions": "Summarize {{input.request}} for a human reviewer.",
          "toolPolicy": "none"
        }
      }
    ],
    "edges": [
      { "id": "start-to-reply", "source": "start_form", "target": "reply" }
    ]
  }
}
```

Creation always returns a draft. Save its returned `revision` for the next update. `currentVersion` may be passed as the optional `version` guard on update or validation, but it does not replace the required revision token for updates.

A decoded create response has this stable shape:

```json theme={null}
{
  "agent": {
    "id": "agent_abc123",
    "status": "draft",
    "currentVersion": 1,
    "publishedVersion": null,
    "revision": "rev_MjAyNi0wNy0xMlQwMDowMDowMC4wMDBa"
  },
  "builderUrl": "/agents/agent_abc123/edit"
}
```

## Update and validate

For an agent that has never been published, `update_agent` accepts editable metadata patches and optional complete replacement `nodes` and `edges`. Always pass the most recently returned `revision`; `version` is optional:

```json theme={null}
{
  "name": "update_agent",
  "arguments": {
    "agentId": "agent_abc123",
    "revision": "rev_MjAyNi0wNy0xMlQwMDo...",
    "version": 1,
    "description": "Summarize and prioritize new finance exceptions for review."
  }
}
```

Then validate the persisted draft:

```json theme={null}
{
  "name": "validate_agent",
  "arguments": {
    "agentId": "agent_abc123",
    "version": 1
  }
}
```

A successful tool call can still report `"valid": false`; inspect `diagnostics` for graph or configuration errors. Validation never publishes and does not make the draft externally runnable.

## Publish a validated draft

Publishing re-runs the external-authoring validation and the normal workflow publish checks before changing lifecycle state:

```json theme={null}
{
  "name": "publish_agent",
  "arguments": {
    "agentId": "agent_abc123",
    "revision": "rev_MjAyNi0wNy0xMlQwMDo...",
    "version": 1
  }
}
```

The revision must still match when the publish transaction claims the workflow row. A concurrent update returns `agent_revision_conflict` and publishes nothing. Re-read, reconcile the user's intent, validate again, and ask before publishing a changed graph. Repeating `publish_agent` with the current revision after the version is already live returns `alreadyPublished: true` without creating another version.

## External-authoring boundaries

The catalog and validation contract deliberately expose only builder-visible nodes and tools.

### Triggers

Externally authored start nodes may use `input_form`, `chat`, `schedule`, or `webhook`. `triggerType: "event"` is not externally authorable at all. In particular, `email.received` remains app-only. To receive a webhook, use `triggerType: "webhook"` rather than an event trigger.

### AI nodes and tool selection

Every externally authored AI/tool-capable node must explicitly set canonical top-level `data.toolPolicy` to a value advertised for that node by `get_agent_node_catalog`. AI Respond, AI Think, and AI Plan & Execute support `auto_discover`, `pinned_only`, and `none`. Other internal model and fixed-action AI nodes advertise only `pinned_only` and `none`. The saved top-level field is authoritative. Legacy or nested selection aliases such as `input.toolPolicy`, `toolMode`, `toolsMode`, `autoSelectTools`, and `autoToolDomainSelection` are rejected for external authoring; runtime and nested inputs can only narrow a saved policy and never promote it.

Use `none` when the model needs no tools; `none` exposes zero model tools in every run: no platform baseline, pinned, provider, discovered, workflow-edge, Task/artifact, or executable-skill tools. Attached Skill metadata may remain in context, but full Skill instructions never do. AI Respond retains its existing conversational pinned baseline under `pinned_only`. AI Think and AI Plan & Execute are stricter: outside an authorized assigned-Task run, `pinned_only` gives them canonical persisted `agentTools` or Plan & Execute `selectedActions` pins plus `skill_execute` for saved executable `skillIds`, plus the always-on Platform Internal Runtime tier (run plan ledger `write_todos`, bounded tool-result reads, isolated Workbench). A legacy saved Think or Plan node with no top-level policy is runtime-clamped to that same bounded surface, while new external definitions cannot omit the field. Across all capability profiles, any authorized assigned-Task run whose effective policy is not `none` may additionally restore only its Task/artifact model tools so it can service the bound task; this never grants Tool Hub discovery/meta tools or default Skill discovery.

Use `auto_discover` only for exploratory or low-risk general assistance. For AI Think and AI Plan & Execute, only an explicitly saved canonical top-level `data.toolPolicy: "auto_discover"` enables the expanded platform baseline, Tool Hub, and default Skill discovery. Default Skill discovery on those nodes also requires `skillPolicy: "auto_discover"`; `skillPolicy` never raises the tool-policy ceiling. Explicit pins remain available, and every discovered write retains the workspace execution policy. Pin any tool the workflow must have and retain its individual governance settings. `auto_discover` controls discovery only; it never changes an explicit per-tool `writePolicy`. Only a server-owned `platformMinimumWritePolicy` may make the effective execution policy stricter. Use `pinned_only` for deterministic, production, ERP/accounting, NetSuite, or write-capable work.

Attached `skillIds` are metadata-only candidates under both active Skill policies. With `skillPolicy: "auto_discover"`, `skill_search`, `skill_describe`, and `skill_read_file` can access the full readable Skill catalog. With `skillPolicy: "pinned_only"`, those Skill tools are restricted to the attached set. Full instructions load on demand through `skill_describe`; attaching a Skill never eagerly injects its full instructions. `skillPolicy: "none"` continues to disable Skill tools and discovery.

`selectedActions` remain explicit Plan & Execute pins. They can coexist with additional `agentTools` and with `toolPolicy: "auto_discover"`; selecting or removing an action does not replace an explicit node policy. Each selected action may carry its own `writePolicy` (`allow`, `require_approval`, or `deny`) in-app. External authors should omit that field to inherit the catalog default or use an accepted stricter override.

Conversational Agent nodes declare pins in `agentTools`. Do not combine `toolPolicy: "auto_discover"` with a non-empty `selectedActions` array on AI Respond: `selectedActions` is legacy pinned-only state there, so the external API rejects that contradictory shape instead of letting persistence silently change the policy. Use `selectedActions` only on node types whose live catalog advertises it, such as Action or AI Plan & Execute.

With `pinned_only` or `auto_discover`, every explicit pinned tool must be visible in the live catalog returned for the connection owner:

* Every Erstan `er_*` binding must explicitly set `source: "erstan"`; Erstan source is never inferred. The name must be catalog-visible. Hidden/internal names, including `er_agent_builder_*`, are denied even when disguised with function prefixes, aliases, nested `selectedActions`, or `toolKeys`.
* Connector source names, tool names, and explicit `connectionId` values must all be visible to the connection owner in the current workspace.
* Within one node, normally pin each canonical runtime tool name once. The only accepted duplicate is one `agentTools` occurrence mirrored by one legacy `selectedActions` occurrence with the same trimmed/lowercased exact raw identity, the same explicit normalized source, and no conflicting `connectionId` (identical when both are set). Function prefixes are not ignored for compatibility: `functions.foo` and `foo` are different raw aliases. Duplicates within either array, different raw aliases that canonicalize to one runtime name, conflicting connections, and different sources are rejected. Source conflicts use `pinned_tool_runtime_name_source_conflict`; all other incompatible duplicates use `pinned_tool_runtime_name_duplicate_conflict`.
* The runtime-owned Tool Hub and skill meta names `tool_search`, `catalog_search`, `tool_describe`, `tool_invoke`, `tool_batch_invoke`, `skill_search`, `skill_describe`, `skill_read_file`, and `skill_execute` cannot be externally pinned under any connector source. Validation returns `pinned_tool_runtime_name_reserved` before consulting the connector catalog.
* `providerTools` is unavailable for external authoring at both node and nested-input level. Use validated `agentTools` bindings on conversational Agents, or `selectedActions` only on node types whose live catalog advertises that field.
* Both `actionCategory: "http"` and the integration alias `webhook_outbound` are raw HTTP actions and are denied.
* If supplied, a pinned tool's `writePolicy` may be `require_approval` or `deny`. External authoring cannot introduce `writePolicy: "allow"`, automatic-write flags, or `autoApproveWrites`. An update or publish may preserve an identical existing `allow` binding configured in Erstan, but cannot add, move, or duplicate one.

Call `get_agent_node_catalog` with `includeActionTypes: true` when selecting an action node. Do not invent an `actionType` that is absent from its bounded response.

### Canonical graph fields

`node.type` and `data.nodeType` must resolve to the same canonical node type. For direct action nodes, `actionType` is authoritative: a legacy `selectedTool` value cannot override it or name a hidden tool. Hidden node types, deprecated group containers, unknown action types, and invalid graph shapes are also rejected.

Aliases returned by the node catalog may be accepted as input, but they are canonicalized before persistence. Subsequent `get_agent` responses return the canonical `node.type` and matching canonical `data.nodeType` when that field is present.

### Child agent references

An `ai-agent` node uses `targetWorkflowId` to invoke another saved agent. The referenced child must be currently readable by the connection owner and belong to the same workspace as the parent. Self-reference is denied.

For a public or automated parent run, Erstan executes the retained published child snapshot. A newer child draft is not exposed to that run, and a child with no published version cannot be invoked from that surface. Test child changes as appropriate, then publish the validated revision in the app or with `publish_agent` before relying on it from an external or automated parent.

### Resource limits

Limits are checked before persistence or connector-catalog loading:

| Resource                                                     | Limit                                                                                                                      |
| ------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------- |
| Nodes                                                        | 100 per graph                                                                                                              |
| Edges                                                        | 250 per graph                                                                                                              |
| Serialized `nodes` plus `edges`                              | 1,000,000 bytes (1 MB)                                                                                                     |
| Pinned tools                                                 | 50 pinned tool bindings per node; 200 across the complete graph. Multiple naming aliases and `toolKeys` count as bindings. |
| Agent name / description                                     | 200 / 20,000 characters                                                                                                    |
| Category, difficulty, estimated time                         | 200 characters each                                                                                                        |
| Tags                                                         | 50 tags, 100 characters per tag                                                                                            |
| Agent, team, node, edge, connection, and graph-reference IDs | 500 characters                                                                                                             |

`validate_agent` returns at most 200 normal diagnostics plus a final `diagnostics_truncated` marker when more problems exist.

## Published agents and drafts

Updating a live agent does not replace its public version. Erstan preserves the last published snapshot for normal `agents:list` and `agents:run` calls while the newer draft is edited. Draft nodes and prompts are not exposed to public runs until the exact validated revision is published in the app or through `publish_agent`.

After an agent has ever been published, its MCP metadata is immutable: `name`, `description`, `category`, `tags`, `difficulty`, `estimatedTime`, and `behaviorType` cannot be changed externally. `update_agent` accepts graph-only `nodes` and/or `edges` changes, atomically forks a draft when necessary, and returns a fresh revision. Make metadata changes in the Erstan app; validate and publish graph drafts with the guarded MCP flow above.

Stale or malformed revisions fail with `validation_failed` and an `agent_revision_conflict` or `agent_revision_invalid` message. Re-read with `get_agent` instead of retrying with the stale token. Published metadata changes fail with `agent_published_metadata_immutable`.

Inaccessible or cross-workspace teams and agents return `not_found` without revealing whether the target exists.

## Next steps

<CardGroup cols={2}>
  <Card title="Connect an MCP client" icon="bot" href="/developers/connect-coding-agent">
    Install the plugin and authorize **Build** access.
  </Card>

  <Card title="Agent Builder" icon="workflow" href="/agents/builder">
    Review and edit the generated graph in Erstan.
  </Card>

  <Card title="Preview, publish & versions" icon="rocket" href="/agents/publish-lifecycle">
    Test and publish the draft when it is ready.
  </Card>

  <Card title="API keys" icon="key-round" href="/developers/api-keys">
    Scope and permission details.
  </Card>
</CardGroup>
