Skip to main content
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. For ordinary JavaScript application code, see the SDK quickstart and SDK reference. SDK/REST write options are not interchangeable with MCP arguments; use the live schema for the chosen surface.
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.
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 currentVersion paired with the approved Skill package. create_agent_skill always creates a draft. Re-reading detects drift; it is not permission to attach a fresh guard to unreviewed content.

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.
  • When the live guide advertises lifecycle.skillSavesAreDrafts: true, Skill saves stage working content and retain the previous published package. Agent presentation stages with the graph when lifecycle.agentPresentationVersioned: true. A missing capability is not evidence of staging support.
  • 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: 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. Published discovery uses agents:list; execution uses agents:run, which is also required for a draft preview. SDK/REST authoring inventories additionally use agents:list; do not assume their scopes or argument shapes are identical to MCP.

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.
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.

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": "..." }. A current read provides the opaque write revision paired with that content. An explicit historical version adds top-level selectedVersion and a read-only history revision; never use it to authorize a current update or publication.
  • 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 opaque revision paired with the edited baseline, and at least one metadata or graph change. version is an optional additional current-version guard. It does not move an agent to another team. Never decode or synthesize a revision, or substitute a newer token onto stale edits.
  • 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 opaque revision of the exact approved content; pass its currentVersion as optional version for a second guard. It returns authoring state plus published: true and alreadyPublished. An exact already-published version is a validated no-op. After a lost response, reuse the same supported idempotency key only with identical arguments, or re-read and reconcile content and lifecycle state. Do not infer success merely from a published status or retry blindly under a fresh key.
  • test_agent requires the selected draft’s version. Send its paired revision when advertised; explicit top-level skillVersions: [{ skillId, version }] require that revision and matching schema support. Never place these controls inside business input.
  • 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 skill.packageJson, currentVersion, and, on supporting servers, publishedVersion and hasDraft. The current package can be an unpublished draft. Send the complete candidate to update_agent_skill with the version paired with its baseline as expectedVersion; do not send only changed files.
  • publish_agent_skill accepts skillId and the approved package’s expectedVersion. A stale version returns skill_version_conflict; re-read and reconcile, and obtain approval for changed publication content.
Use stable idempotencyKey arguments for Agent mutations where the schema advertises them; reuse only for identical intent and arguments. MCP Skill create/update/publish tools do not expose that argument. After an ambiguous Skill mutation, reconcile the complete package and lifecycle state before any retry. The binding catalog can omit drafts, so absence from list_agent_skills does not prove a timed-out create failed. SDK/REST receipt guarantees do not automatically apply to arbitrary MCP calls.
  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. Keep the returned content and revision together. Before a later update, compare any remote changes with the intended edit; do not replace a stored guard without reconciling. Pass the revision paired with the edited baseline and optionally its current version.
  7. Use test_agent only when a real hosted preview is authorized. It is optional, not a publication gate. Publish only the exact validated content the user approved, with its paired guard; never publish dependencies implicitly.

Exact draft Skill previews

Inspect the live test_agent schema before offering an exact preview. Each explicit Skill version selection requires edit permission and must fit the Agent’s existing Skill policy. Unselected Skills and child Agents use published content. If the schema does not support the required revision and selections, report the capability gap; do not drop them, put them in input, widen policy, publish dependencies, or silently run a published Agent instead. Preview executes real tools with normal costs, approvals, and possible external effects. It seals the selected graph, so later edits/publication may fork and advance its version. Poll the returned run ID and detect drift before the next write. A successful preview neither publishes content nor authorizes publication.

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. Before editing a published Skill, read get_agent_builder_guide. With lifecycle.skillSavesAreDrafts: true, a save stages a working package while the prior published version remains live. Read the complete package, apply the intended edit, and send its paired currentVersion as expectedVersion. Omit status unless a separate availability change is authorized; setting it to draft is not how staging works.
  5. Publish only the exact validated package explicitly approved by the user, using its expectedVersion. A published status with hasDraft: true is valid: availability and the presence of working changes are different things.
If the staged-save flag is absent or false, do not assume a published Skill can be edited privately. A confirmed legacy server may change live content on save; that requires explicit live-change authorization. Unknown behavior or a draft-only request must stop at validation, with the capability gap explained. Do not clone, change status, or publish as a workaround. Publish required Skills separately before the Agent. Publishing a shared Skill affects future consumers; snapshot-backed admitted runs and their continuations retain the immutable packages they selected. Missing snapshot identity on older runs is an evidence limitation, not proof that they used today’s current draft. 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.

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:
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:

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:
Then validate the persisted draft:
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:
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 ordinary tools or executable actions; it removes the platform baseline, pinned, provider, discovered, workflow-edge, Task/artifact, and skill_execute surfaces. An independently active skillPolicy may still expose scoped read-only Skill readers so the model can load reusable guidance on demand. AI Respond, AI Think, and AI Plan & Execute include the complete built-in Erstan catalog without connector setup or manual pins. This applies in pinned_only and auto_discover, including scheduled runs and runs without a Task. A compact baseline is bound directly; other built-ins are available through tool_search, tool_describe, and tool_invoke. Pins scope connector access and can prioritize built-ins or customize approval policies. Explicit none disables ordinary tools and executable actions. Tool denies, resource permissions, write approvals, and Skill policies still apply. Workbench uses the workspace’s configured sandbox. Other internal model and fixed-action steps keep their declared tools; an assigned Task may supply Task/artifact tools without general Tool Hub discovery. Use auto_discover only for exploratory or low-risk general assistance. Auto expands external connector discovery; the built-in Erstan catalog is also available in Pinned mode. Explicit tool pins remain available, and every discovered write retains the workspace execution policy. Pin any ordinary tool the workflow must have and retain its individual governance settings. toolPolicy: "auto_discover" controls ordinary-tool 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. skillPolicy independently governs read-only Skill access on AI Respond, AI Think, and AI Plan & Execute. Attached skillIds are metadata-only candidates under both active Skill policies. With skillPolicy: "auto_discover", skill_search, skill_describe, skill_read_file, and er_skill_read can access the full readable Skill catalog even when Tools are Pinned or None. On an authored node, skillPolicy: "pinned_only" restricts those readers exactly to canonical persisted data.skillIds; runtime or nested skillIds may narrow but never widen the set. Authorized thread bindings merge only for legacy/ad-hoc conversational nodes with no authored skillPolicy. Full instructions load on demand through the readers; attaching a Skill never eagerly injects its full instructions. skillPolicy: "none" disables all Skill readers and discovery. Executable Skill actions remain separately governed by toolPolicy: Tools None exposes no skill_execute, Tools Pinned limits it to attached executable Skills, and catalog-wide Skill action execution requires both policies to be Auto. 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 names tool_search, catalog_search, tool_describe, tool_invoke, tool_batch_invoke, skill_search, skill_describe, skill_read_file, er_skill_read, 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. A direct action (executionMode: "direct") runs one tool with the authored actionParameters and calls no model. It runs an Erstan tool (actionCategory: "erstan_tool") or one NetSuite Add-on tool: set actionCategory: "integration", set actionType to the Add-on tool, pin that tool as the only netsuite-addon entry in selectedActions, and give its arguments in actionParameters. The pin’s writePolicy is the step’s permission, so actionWritePolicy, agentTools on the step, and input bindings on the pin are refused. A pin of require_approval parks the run on an approval card that shows the resolved arguments: approving sends exactly those arguments once, and declining ends the run as cancelled. On AI For Each, onItemError: "continue" records a failed item in the loop output (failedItems, with failedItemCount and failedItemsComplete) and moves to the next item instead of failing the run; it needs a loop body of exactly one direct action or one code step that calls no model, and publish refuses any other body. A retryable error still ends the run. On a direct action, so do a declined approval and a permission refusal. A direct action on an Erstan tool that only reads (such as er_file_extract) also records any failure once the tool has started: an answer that says it failed, or an error the tool raised (for example a file that could not be read), with the tool’s own message as the item’s error. That includes a fault that repeats for every item, such as a model that is not connected. A refusal made before the tool starts still ends the run, and so does the same failed answer from a tool that writes. On a code step, every other failure is recorded for its item, including a fault that repeats for every item (a disabled feature or a configuration fault), so such a run completes with every item in failedItems. An item whose error has outcomeUnknown: true may have been written, so a later step must not send it again; a failed read never carries it. 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: 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. When lifecycle.agentPresentationVersioned: true is advertised, name, description, category, tags, difficulty, and estimatedTime stage with the graph and promote together on explicit publication. behaviorType remains immutable after the first publication. If the capability is absent or false, follow the live metadata restrictions rather than assuming staged edits are supported. Archive/delete and other app-only controls remain outside MCP. Stale or malformed revisions fail with validation_failed and an agent_revision_conflict or agent_revision_invalid message. Re-read with get_agent and reconcile the content instead of swapping in a new token. Unsupported post-publication behavior-type changes can return agent_published_metadata_immutable. Inaccessible or cross-workspace teams and agents return not_found without revealing whether the target exists.

Next steps

Connect an MCP client

Install the plugin and authorize Build access.

Agent Builder

Review and edit the generated graph in Erstan.

Preview, publish & versions

Test and publish the draft when it is ready.

API keys

Scope and permission details.