# Authoring Via Mcp

# Author blueprints from MCP (Claude Desktop, Cursor, ChatGPT-with-MCP)

> **Audience:** a Claude session connected to Amdahl via MCP that wants to read, draft, validate, and persist agent blueprints without ever leaving the chat.
> **Prerequisites:** an Amdahl MCP API key with `artifact:write` scope (the customer-agent bundle is sufficient); the Amdahl MCP server connected to your client.
> **End state:** a published tenant-owned blueprint authored interactively, gated through the same validation moat the artifact registry applies, ready to be read by the next session.

This guide pairs with [`tutorial.md`](./tutorial.md) (which walks through forking from the in-app copilot). The shape is the same — fork or compose, customize, validate, persist — but every step here goes through the `blueprints` MCP coarse tool so an external Claude session can run the whole loop without the Console UI.

## Why a coarse tool for blueprints

Blueprints land as `platform_artifacts` rows with `artifact_type = 'agent_blueprint'`. You could already create one via `artifacts create`, but that path loses two things:

1. **Moat validation.** The artifact registry's Zod schema catches structural errors, but six invariants beyond that (duplicate step ids, broken `$step.field` references, unknown `prompt://` fragments, sub-blueprint slugs that don't resolve, sub-blueprint cycles, malformed tool ids in the policy allowlist) only catch with the moat. The `blueprints` tool runs the moat before every write.
2. **Catalog discovery.** Authoring a v1 blueprint without knowing the 8 step kinds or the 19 reusable prompt fragments is brutal. The `describe_step_kinds` + `list_prompt_fragments` actions give you the full catalog with copy-pasteable examples.

`blueprints` is a recipe tool, not a one-shot runner. There is no run-blueprint tool on the MCP surface — when you (an MCP-connected Claude session) want to act on a blueprint inline, you ARE the runner: read the recipe (`get`, or `read_resource agent_blueprint://<id>`), walk the step graph in your own reasoning loop, make the primitive calls yourself. The `blueprints` tool is for authoring + discovery + scheduling, not inline execution — its `create_schedule` / `update_schedule` / `delete_schedule` actions DO register an unattended run on the headless SDK runner. (That headless runner also drives saved blueprints to completion for other platform-initiated runs — manual "Run now" (REST), event/webhook triggers, replay, and backtest sweeps; `agents.run_blueprint` exists for one-shot runs on REST + the Anthropic tool surface, just not on MCP. See [`../../blueprint-runner-sdk.md`](../../blueprint-runner-sdk.md).)

## Action catalog

| Action                  | Purpose                                                                  | Required params     |
| ----------------------- | ------------------------------------------------------------------------ | ------------------- |
| `list`                  | Catalog every blueprint visible to this tenant (starters + tenant rows). | —                   |
| `get`                   | Fetch the full v1 DSL body for one blueprint.                            | `id` (UUID or slug) |
| `validate`              | Run the moat against a draft body without writing it.                    | `content`           |
| `describe_step_kinds`   | Return the v1 step grammar with example bodies.                          | —                   |
| `list_prompt_fragments` | Catalog every registered prompt fragment.                                | —                   |
| `create`                | New tenant blueprint.                                                    | `content`           |
| `update`                | Patch tenant blueprint.                                                  | `id`                |
| `delete`                | Soft-delete (archive).                                                   | `id`                |
| `unarchive`             | Restore an archived row.                                                 | `id`                |
| `fork`                  | Wraps `agents.fork_blueprint`.                                           | `source`            |

Every write action returns the same `{success, blueprint, error}` envelope so the wire shape stays uniform regardless of which path you hit.

## Walkthrough — author a new blueprint from scratch

Goal: produce a tenant blueprint that runs the standard research → drafting flow but pins the channel to LinkedIn and locks the writing voice to one author.

### Step 1 — Orient on the v1 grammar

Before touching the body, read the step-kind catalog. This is a single MCP call:

```text
blueprints describe_step_kinds
```

You'll see all 8 kinds (`tool`, `llm`, `loop`, `branch`, `parallel`, `blueprint`, `transform`, `assert`) with field tables and complete example bodies. Pay attention to:

- The reference syntax (`$inputs.X`, `$step_id.field`, `$now`, `$today`, `$random_uuid`, `$secret.X`, `$builtin.X`). Every `$`-prefixed string in the blueprint must satisfy one of these resolvers, or the moat rejects it.
- The `prompt://` URI scheme. `llm` steps consume prompt fragments via this scheme; the renderer inlines the fragment body when the step prompt is composed.
- The `output_alias` field common to every step. Aliasing an output makes downstream `$alias.field` references stable across step-id rename refactors.

### Step 2 — Pull the prompt fragment catalog

For an `llm` step, you usually want to compose one or more shared fragments (e.g. `content_writer/grounding_rules`, `content_writer/audience_scoping`). List them:

```text
blueprints list_prompt_fragments scheme="content_writer" include_body=true
```

Setting `include_body: true` inlines the fragment text so you don't have to make a separate `prompt://` resource read for every one. The lean projection (default) is fine for browsing.

### Step 3 — Draft the body

Compose the DSL in your reasoning loop. A minimal body looks like:

```json
{
  "schema_version": "1.0.0",
  "identity": {
    "slug": "linkedin-thought-leader",
    "name": "LinkedIn Thought Leader",
    "version": "1.0.0",
    "authored_by": "tenant",
    "purpose": "Research a topic and draft a LinkedIn post pinned to one voice.",
    "tags": ["linkedin", "thought_leader"]
  },
  "inputs": [
    {
      "name": "topic",
      "type": "string",
      "required": true,
      "description": "The topic to write about."
    },
    {
      "name": "author_id",
      "type": "string",
      "required": true,
      "description": "Voice + author profile UUID."
    }
  ],
  "outputs": [
    {
      "name": "post_artifact_id",
      "type": "string",
      "description": "The created content_piece UUID."
    }
  ],
  "policy": {
    "tool_allowlist": [
      "data.cluster_search",
      "data.search",
      "artifacts.create",
      "external_search.execute"
    ]
  },
  "trigger": { "kind": "manual" },
  "steps": [
    {
      "id": "research",
      "kind": "tool",
      "tool": "external_search.execute",
      "args": {
        "action": "enrich_topic",
        "topic_query": "$inputs.topic"
      },
      "output_alias": "research_brief"
    },
    {
      "id": "draft",
      "kind": "llm",
      "effort": "medium",
      "prompt_resources": [
        "prompt://content_writer/audience_scoping",
        "prompt://content_writer/grounding_rules",
        "prompt://content_writer/hook_patterns"
      ],
      "instructions": "Draft a LinkedIn post grounded in $research_brief. Pin the voice to author $inputs.author_id.",
      "output_schema": { "type": "object", "properties": { "post_body": { "type": "string" } } },
      "output_alias": "draft"
    },
    {
      "id": "persist",
      "kind": "tool",
      "tool": "artifacts.create",
      "args": {
        "artifact_type": "content_piece",
        "title": "LinkedIn: $inputs.topic",
        "content_json": {
          "schema_version": "1.0.0",
          "channel": "linkedin",
          "body": "$draft.post_body",
          "author_id": "$inputs.author_id",
          "grounded_in": ["$research_brief.artifact_id"]
        }
      },
      "output_alias": "post"
    }
  ],
  "outputs_mapping": {
    "post_artifact_id": "$post.id"
  }
}
```

### Step 4 — Dry-run through the moat

Before writing, check the body. The `validate` action runs the same moat the `create` path will apply:

```text
blueprints validate content=<the body above>
```

The response shape is `{ valid: boolean, errors: ValidationError[] }`. Errors carry a `code` discriminator (`schema_error`, `duplicate_step_id`, `unknown_reference`, `unknown_fragment`, `unresolved_sub_blueprint`, `circular_sub_blueprint`, `invalid_tool_id`) and a structured `details` blob the calling LLM can pattern-match to surface field-level UX.

Common moat hits during authoring:

- **`unknown_reference`** — you wrote `$research_breif.artifact_id` (typo). The moat lists every reference it walked through plus the dot-path of the offending occurrence.
- **`unknown_fragment`** — you used `prompt://content_writer/grounded_rules` (typo, real fragment is `grounding_rules`). Re-run `list_prompt_fragments` to fix.
- **`duplicate_step_id`** — easy to hit when you copy-paste a step block. Each step within a blueprint (across loop / branch / parallel descents) must have a unique id.

### Step 5 — Persist

Once `validate` returns `{ valid: true }`, persist:

```text
blueprints create content=<the body> status="draft"
```

The response carries `{ success: true, blueprint: { header, content } }` where `header` is the lean catalog projection (id, slug, name, version, source, authored_by, status, purpose, tags, timestamps) and `content` is the full DSL body. Save the `header.id` — that's the artifact UUID you'll use to update or invoke later.

The default status is `draft`. Flip to `published` via `update` once you're satisfied:

```text
blueprints update id="<the id>" status="published" change_summary="Promoted from draft after dry run"
```

## Walkthrough — fork a starter

If the platform-shipped starter is 90% of what you need, fork instead of authoring from scratch:

```text
blueprints fork source="plan-and-draft-window" new_slug="team-x-linkedin-flow" new_name="Team X LinkedIn Flow"
```

The fork is a thin wrapper around the existing `agents.fork_blueprint` Operation (same handler underneath as the in-app copilot's fork button). The fork:

- Creates a fresh tenant artifact with a new UUID.
- Resets `identity.version` to `1.0.0`.
- Sets `identity.authored_by` to `tenant`.
- Lands at `status: 'draft'` and `visibility: 'private'`.
- Stamps `metadata.forked_from = { kind: 'starter'|'tenant', id, slug, version }` on the artifact row so the ancestry graph is reconstructable.

The source is unmodified. Forking a tenant blueprint that has itself been forked works the same way — the new fork's `metadata.forked_from` points at the immediate parent; walking the chain reconstructs the full ancestry.

## Walkthrough — iterate on a tenant blueprint

After the first `create`, the loop is read → edit → validate → update:

```text
# 1. Read the current body
blueprints get id="<the id>"

# 2. Edit in your reasoning loop (mutate one or more fields)
# 3. Dry-run the patched body
blueprints validate content=<patched body>

# 4. Persist (content is REPLACE-semantic — pass the full new body, not a JSON Patch)
blueprints update id="<the id>" content=<patched body> change_summary="Tightened the hook step"
```

Three notes on `update`:

- **Content is replace-semantic.** The patched body is the WHOLE new `content_json` — top-level keys you omit are dropped. This mirrors how the artifact registry handles content updates (the legacy merge semantic was retired because it bricked schema-tightening migrations).
- **Top-level patch fields are surface metadata.** `title`, `description`, `status`, `tags`, `metadata` patch the artifact row's columns and stay independent from `content_json`. You can flip `status` without touching the recipe.
- **The state machine gates `status` transitions.** `agent_blueprint` follows `draft → published → archived`. Trying to flip `draft → archived` without going through `published` returns a `validation_failed` envelope from the registry's state-machine gate.

## Lifecycle — delete + recover

`delete` is soft (archive). The row stays in `platform_artifacts` with `archived_at` set; `unarchive` restores it. Hard delete is reserved for admin / GDPR paths and is NOT exposed on the `blueprints` MCP surface — use the generic `artifacts.delete` Operation (REST + Anthropic) if you really need the row gone.

```text
blueprints delete id="<the id>"      # archive (reversible)
blueprints unarchive id="<the id>"   # restore
```

Archived rows are excluded from `blueprints list` by default. Pass `include_archived: true` to surface them.

## Wire envelope reference

All write actions (`create` / `update` / `delete` / `unarchive` / `fork`) return one of:

```json
{
  "success": true,
  "blueprint": {
    "header": {
      /* lean projection */
    },
    "content": {
      /* full DSL */
    }
  }
}
```

```json
{
  "success": false,
  "error": {
    "code": "invalid_argument" | "not_found" | "slug_conflict" | "validation_failed" | "unauthorized" | "internal_error",
    "message": "...",
    "details": { /* structured field-level context, optional */ }
  }
}
```

`slug_conflict` carries `details = { field: 'identity.slug', slug, conflict_artifact_id }`. The frontend (and your reasoning loop) can map this to the slug field for inline correction.

`validation_failed` carries `details.errors[]` — the same `ValidationError[]` shape `validate` returns. Each entry has `{ code, path, message, severity, details? }`.

## Anti-patterns

- **Don't look for a one-shot run-blueprint tool here.** There is no run-blueprint tool on the MCP surface. If you find yourself wanting to "run" a blueprint inline from here, you instead want to walk the recipe yourself. Read `get`, then make the primitive calls (`data.query`, `external_search.execute`, `artifacts.create`, etc.) directly. (To register an UNATTENDED run from MCP, use the `blueprints` `create_schedule` action — that hands the blueprint to the headless SDK runner. One-shot server-side runs live on REST + the Anthropic tool surface via `agents.run_blueprint`, not on MCP; see [`../../blueprint-runner-sdk.md`](../../blueprint-runner-sdk.md).)
- **Don't fork via `artifacts.create({artifact_type: 'agent_blueprint', ...})`.** It works, but skips the lineage stamp + the moat. Use `blueprints fork` (or the underlying `agents.fork_blueprint` op).
- **Don't compose a body without `validate`.** The artifact registry will reject moat violations on `create` / `update` anyway, but `validate` returns the full error list in one round-trip; the registry short-circuits on the first violation.
- **Don't bake secrets into the body.** Every secret reference goes through `$secret.X` so the resolver substitutes from the tenant's keyring at render time. Literal API keys in a blueprint body are stored unencrypted on the artifact row and surface in `get` to every agent in the workspace.

## Related docs

- [Blueprint DSL reference (auto-generated)](../api-reference/blueprint-dsl.md) — the canonical Zod schema dump for every field on every step kind.
- [Tutorial — fork the calendar starter](./tutorial.md) — same loop from the in-app copilot UI.
