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

# Anthropic SDK pattern

> Use Lavendly tools through Claude's tool-use loop from your own agent runtime.

This page shows how to expose Lavendly tools through Claude's
`tool_use` mechanism when you're building your own agent runtime,
without going through the MCP transport.

When to use this:

* You're hosting your own agent and want full control over the
  prompting and the loop.
* You're embedding Lavendly inside a larger product where the MCP
  stdio model doesn't fit (serverless, edge, etc.).
* You want to combine Lavendly tools with tools from other systems
  in one tool call surface.

## The pattern

<Steps>
  <Step title="Fetch the Lavendly operation catalog">
    `GET /v1/_schema` returns every operation with its input shape.
    Convert that into Claude's tool definition format once at startup.
  </Step>

  <Step title="Load a skill into your system prompt">
    Concatenate one or more skill bodies (storyteller, multi-clip,
    cost-aware) into the system prompt. This is what makes the agent
    use the tools well.
  </Step>

  <Step title="Run the tool-use loop">
    On each `tool_use` block from Claude, call the matching Lavendly
    HTTP endpoint. Return the result as `tool_result`. Repeat until
    Claude stops with `end_turn`.
  </Step>
</Steps>

## Reference implementation (Node)

```js theme={null}
import Anthropic from "@anthropic-ai/sdk";
import { Lavendly } from "@lavendly/sdk";
import { loadSkill } from "@lavendly/skills";

const anthropic = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
const vs        = new Lavendly({ apiKey: process.env.LAVENDLY_API_KEY });

// 1. Build tools from the live operation catalog
const schema = await vs.introspect.schema();
const tools  = Object.entries(schema.operations).map(([name, op]) => ({
  name,
  description: op.description ?? `${op.method} ${op.path}`,
  input_schema: op.input_schema,
}));

// 2. Compose the system prompt with the storyteller skill
const skill = await loadSkill("storyteller");
const system = `You are a video producer. Use the Lavendly tools to
fulfill the user's creative briefs.

${skill}`;

// 3. Run the loop
async function runAgent(userBrief) {
  const messages = [{ role: "user", content: userBrief }];

  for (;;) {
    const response = await anthropic.messages.create({
      model: "claude-sonnet-4-5",
      max_tokens: 4096,
      system,
      tools,
      messages,
    });

    messages.push({ role: "assistant", content: response.content });

    if (response.stop_reason !== "tool_use") {
      return response.content.find((c) => c.type === "text")?.text;
    }

    const toolResults = await Promise.all(
      response.content
        .filter((c) => c.type === "tool_use")
        .map(async (call) => ({
          type: "tool_result",
          tool_use_id: call.id,
          content: JSON.stringify(await vs.call(call.name, call.input)),
        })),
    );

    messages.push({ role: "user", content: toolResults });
  }
}

console.log(await runAgent("Make me a 5-second video of a fox in a bookshop."));
```

`vs.call(name, args)` is a thin dispatcher provided by the SDK that
forwards to the right HTTP endpoint based on the operation name. It
honors idempotency keys passed in `args.idempotency_key`.

## Reference implementation (Python)

```python theme={null}
import os, json
from anthropic import Anthropic
from lavendly import Lavendly
from lavendly.skills import load_skill

anthropic = Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])
vs        = Lavendly(api_key=os.environ["LAVENDLY_API_KEY"])

schema = vs.introspect.schema()
tools  = [
    {
        "name": name,
        "description": op.get("description") or f"{op['method']} {op['path']}",
        "input_schema": op["input_schema"],
    }
    for name, op in schema["operations"].items()
]

system = f"""You are a video producer. Use the Lavendly tools to
fulfill the user's creative briefs.

{load_skill("storyteller")}"""

def run_agent(user_brief):
    messages = [{"role": "user", "content": user_brief}]
    while True:
        resp = anthropic.messages.create(
            model="claude-sonnet-4-5",
            max_tokens=4096,
            system=system,
            tools=tools,
            messages=messages,
        )
        messages.append({"role": "assistant", "content": resp.content})

        if resp.stop_reason != "tool_use":
            return next((b.text for b in resp.content if b.type == "text"), None)

        tool_results = []
        for block in resp.content:
            if block.type == "tool_use":
                result = vs.call(block.name, block.input)
                tool_results.append({
                    "type": "tool_result",
                    "tool_use_id": block.id,
                    "content": json.dumps(result),
                })
        messages.append({"role": "user", "content": tool_results})

print(run_agent("Make me a 5-second video of a fox in a bookshop."))
```

## Composing skills

To layer multiple skills (storyteller + cost-aware), concatenate them
in priority order:

```js theme={null}
const system = [
  baseSystem,
  await loadSkill("cost-aware"),    // 'priority: high', comes first
  await loadSkill("storyteller"),   // domain skill
].join("\n\n");
```

The agent reads them in order and applies the high-priority
constraints over the domain choices.

## Why bother with the SDK pattern vs MCP?

|                        | MCP                                              | Anthropic SDK pattern                      |
| ---------------------- | ------------------------------------------------ | ------------------------------------------ |
| Setup                  | Add one entry to a config file                   | Code                                       |
| Tool transport         | stdio JSON-RPC                                   | In-process HTTP                            |
| Customization          | Limited, clients dictate the prompt              | Full, you own the system prompt            |
| Multi-tool composition | One MCP per server, runtime merges               | All tools in one catalog                   |
| Latency                | Subprocess per call                              | In-process                                 |
| Best for               | End users in Claude Desktop, Cursor, Claude Code | Engineers building their own agent product |

If you ever need both, say, internal tooling via the SDK pattern plus
end users via MCP, the same skill file works in either case.
