Skip to main content

Agent Workflows

End-to-end examples for building with Lovelace agents over MCP

These examples show the most common patterns for using Lovelace agents programmatically through the MCP gateway. Each scenario is self-contained and copy-paste ready.

Required scopes

ScenarioScopes needed
Spawn, poll, retrieveworkspace:read, agents:read, agents:write
Knowledge-augmented agentAll of the above plus knowledge:read, knowledge:write
Multi-workspace orchestrationworkspace:read, agents:read, agents:write

See Authentication for how to request scopes in the OAuth flow.

Setup

Install the MCP SDK for your language:

bash
# Python
pip install mcp

# TypeScript / Node.js
npm install @modelcontextprotocol/sdk

All examples below assume a bearer token from the OAuth flow:

bash
export LOVELACE_TOKEN="your_bearer_token"

Scenario A: Spawn, poll, and retrieve

The simplest complete workflow: spawn an agent with a task, wait for it to finish, and read the output.

Python

python
import asyncio
import json
import os
from mcp import ClientSession
from mcp.client.streamable_http import streamablehttp_client

TOKEN = os.environ["LOVELACE_TOKEN"]
GATEWAY = "https://mcp.uselovelace.com/mcp"


async def wait_for_agent(
    session: ClientSession, agent_id: str, poll_interval: float = 2.0
) -> str:
    """Poll lovelace_get_agent_status until a terminal state is reached."""
    while True:
        result = await session.call_tool(
            "lovelace_get_agent_status", {"agentId": agent_id}
        )
        data = json.loads(result.content[0].text)
        if data["status"] in ("completed", "failed"):
            return data["status"]
        await asyncio.sleep(poll_interval)


async def main():
    async with streamablehttp_client(
        GATEWAY,
        headers={"Authorization": f"Bearer {TOKEN}"},
    ) as (read, write, _):
        async with ClientSession(read, write) as session:
            await session.initialize()

            # 1. Pick a workspace
            ws_result = await session.call_tool("lovelace_list_workspaces", {})
            workspaces = json.loads(ws_result.content[0].text)
            workspace_id = workspaces["workspaces"][0]["id"]

            # 2. Spawn an agent
            spawn_result = await session.call_tool(
                "lovelace_spawn_agent",
                {
                    "workspaceId": workspace_id,
                    "agentType": "general",
                    "task": (
                        "Summarize the five most important security considerations "
                        "for a public REST API"
                    ),
                },
            )
            agent_data = json.loads(spawn_result.content[0].text)
            agent_id = agent_data["agentId"]
            print(f"Spawned agent: {agent_id}")

            # 3. Wait for completion
            final_status = await wait_for_agent(session, agent_id)
            print(f"Agent finished with status: {final_status}")

            # 4. Fetch the result
            if final_status == "completed":
                output = await session.call_tool(
                    "lovelace_get_agent_result", {"agentId": agent_id}
                )
                result_data = json.loads(output.content[0].text)
                print(result_data["result"])


asyncio.run(main())

TypeScript

typescript
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";

const TOKEN = process.env.LOVELACE_TOKEN!;
const GATEWAY = "https://mcp.uselovelace.com/mcp";

function parseText(result: { content: unknown[] }): unknown {
  return JSON.parse((result.content[0] as { text: string }).text);
}

async function waitForAgent(
  client: Client,
  agentId: string,
  pollMs = 2000,
): Promise<string> {
  while (true) {
    const result = await client.callTool("lovelace_get_agent_status", {
      agentId,
    });
    const data = parseText(result) as { status: string };
    if (data.status === "completed" || data.status === "failed") {
      return data.status;
    }
    await new Promise((r) => setTimeout(r, pollMs));
  }
}

async function main() {
  const client = new Client({ name: "my-app", version: "1.0.0" });
  await client.connect(
    new StreamableHTTPClientTransport(new URL(GATEWAY), {
      requestInit: { headers: { Authorization: `Bearer ${TOKEN}` } },
    }),
  );

  // 1. Pick a workspace
  const wsResult = await client.callTool("lovelace_list_workspaces", {});
  const workspaces = parseText(wsResult) as { workspaces: { id: string }[] };
  const workspaceId = workspaces.workspaces[0].id;

  // 2. Spawn an agent
  const spawnResult = await client.callTool("lovelace_spawn_agent", {
    workspaceId,
    agentType: "general",
    task: "Summarize the five most important security considerations for a public REST API",
  });
  const agentData = parseText(spawnResult) as { agentId: string };
  const agentId = agentData.agentId;
  console.log(`Spawned agent: ${agentId}`);

  // 3. Wait for completion
  const finalStatus = await waitForAgent(client, agentId);
  console.log(`Agent finished: ${finalStatus}`);

  // 4. Fetch the result
  if (finalStatus === "completed") {
    const output = await client.callTool("lovelace_get_agent_result", {
      agentId,
    });
    const resultData = parseText(output) as { result: unknown };
    console.log(resultData.result);
  }

  await client.close();
}

main();

Scenario B: Knowledge-augmented agent

Store context in a workspace first, then spawn an agent that can search and build on it. Useful for giving an agent access to project-specific documents, runbooks, or accumulated knowledge.

Python

python
import asyncio
import json
import os
from mcp import ClientSession
from mcp.client.streamable_http import streamablehttp_client

TOKEN = os.environ["LOVELACE_TOKEN"]
GATEWAY = "https://mcp.uselovelace.com/mcp"


async def main():
    async with streamablehttp_client(
        GATEWAY,
        headers={"Authorization": f"Bearer {TOKEN}"},
    ) as (read, write, _):
        async with ClientSession(read, write) as session:
            await session.initialize()

            ws_result = await session.call_tool("lovelace_list_workspaces", {})
            workspace_id = json.loads(ws_result.content[0].text)["workspaces"][0]["id"]

            # 1. Store context in the workspace
            await session.call_tool(
                "lovelace_store_knowledge",
                {
                    "workspaceId": workspace_id,
                    "title": "API Rate Limiting Policy",
                    "content": (
                        "# API Rate Limiting Policy\n\n"
                        "All endpoints are limited to 1000 requests per minute per API key. "
                        "Burst allowance is 200 requests per second. "
                        "Clients that exceed the limit receive 429 responses with a "
                        "Retry-After header indicating when they may resume."
                    ),
                    "mimeType": "text/markdown",
                },
            )
            print("Knowledge stored.")

            # 2. Verify the knowledge is searchable
            search_result = await session.call_tool(
                "lovelace_search_knowledge",
                {
                    "workspaceId": workspace_id,
                    "query": "rate limit burst allowance",
                    "limit": 3,
                },
            )
            hits = json.loads(search_result.content[0].text)
            print(f"Found {hits['total']} matching documents.")

            # 3. Spawn an agent that will use the stored knowledge
            spawn_result = await session.call_tool(
                "lovelace_spawn_agent",
                {
                    "workspaceId": workspace_id,
                    "agentType": "general",
                    "task": (
                        "Using the workspace knowledge, write a concise client-side "
                        "retry strategy that respects our rate limiting policy."
                    ),
                },
            )
            agent_id = json.loads(spawn_result.content[0].text)["agentId"]

            # 4. Poll and retrieve
            while True:
                status = json.loads(
                    (
                        await session.call_tool(
                            "lovelace_get_agent_status", {"agentId": agent_id}
                        )
                    ).content[0].text
                )["status"]
                if status in ("completed", "failed"):
                    break
                await asyncio.sleep(2)

            if status == "completed":
                output = await session.call_tool(
                    "lovelace_get_agent_result", {"agentId": agent_id}
                )
                print(json.loads(output.content[0].text)["result"])


asyncio.run(main())

Scenario C: Multi-workspace orchestration

Spawn one agent per workspace in parallel and aggregate the results. Useful when you have multiple projects and want to run the same analysis across all of them.

Python

python
import asyncio
import json
import os
from mcp import ClientSession
from mcp.client.streamable_http import streamablehttp_client

TOKEN = os.environ["LOVELACE_TOKEN"]
GATEWAY = "https://mcp.uselovelace.com/mcp"
TASK = "List the three most recently active agents and their status"


async def run_agent_in_workspace(
    session: ClientSession, workspace_id: str
) -> dict:
    spawn = await session.call_tool(
        "lovelace_spawn_agent",
        {"workspaceId": workspace_id, "agentType": "general", "task": TASK},
    )
    agent_id = json.loads(spawn.content[0].text)["agentId"]

    while True:
        status_data = json.loads(
            (
                await session.call_tool(
                    "lovelace_get_agent_status", {"agentId": agent_id}
                )
            ).content[0].text
        )
        if status_data["status"] in ("completed", "failed"):
            break
        await asyncio.sleep(2)

    if status_data["status"] == "completed":
        output = await session.call_tool(
            "lovelace_get_agent_result", {"agentId": agent_id}
        )
        return {"workspaceId": workspace_id, "result": json.loads(output.content[0].text)}
    return {"workspaceId": workspace_id, "error": "Agent failed"}


async def main():
    async with streamablehttp_client(
        GATEWAY,
        headers={"Authorization": f"Bearer {TOKEN}"},
    ) as (read, write, _):
        async with ClientSession(read, write) as session:
            await session.initialize()

            ws_result = await session.call_tool("lovelace_list_workspaces", {})
            workspace_ids = [
                ws["id"]
                for ws in json.loads(ws_result.content[0].text)["workspaces"]
            ]
            print(f"Running agents across {len(workspace_ids)} workspaces...")

            # Spawn all agents in parallel
            results = await asyncio.gather(
                *[run_agent_in_workspace(session, ws_id) for ws_id in workspace_ids]
            )

            for r in results:
                print(r)


asyncio.run(main())

TypeScript

typescript
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";

const TOKEN = process.env.LOVELACE_TOKEN!;
const GATEWAY = "https://mcp.uselovelace.com/mcp";
const TASK = "List the three most recently active agents and their status";

function parseText(result: { content: unknown[] }): unknown {
  return JSON.parse((result.content[0] as { text: string }).text);
}

async function runAgentInWorkspace(
  client: Client,
  workspaceId: string,
): Promise<{ workspaceId: string; result?: unknown; error?: string }> {
  const spawn = await client.callTool("lovelace_spawn_agent", {
    workspaceId,
    agentType: "general",
    task: TASK,
  });
  const agentId = (parseText(spawn) as { agentId: string }).agentId;

  while (true) {
    const statusData = parseText(
      await client.callTool("lovelace_get_agent_status", { agentId }),
    ) as { status: string };
    if (statusData.status === "completed" || statusData.status === "failed") {
      if (statusData.status === "failed") {
        return { workspaceId, error: "Agent failed" };
      }
      break;
    }
    await new Promise((r) => setTimeout(r, 2000));
  }

  const output = await client.callTool("lovelace_get_agent_result", {
    agentId,
  });
  return {
    workspaceId,
    result: (parseText(output) as { result: unknown }).result,
  };
}

async function main() {
  const client = new Client({ name: "my-app", version: "1.0.0" });
  await client.connect(
    new StreamableHTTPClientTransport(new URL(GATEWAY), {
      requestInit: { headers: { Authorization: `Bearer ${TOKEN}` } },
    }),
  );

  const wsResult = await client.callTool("lovelace_list_workspaces", {});
  const workspaceIds = (
    parseText(wsResult) as { workspaces: { id: string }[] }
  ).workspaces.map((ws) => ws.id);

  console.log(`Running agents across ${workspaceIds.length} workspaces...`);

  // Spawn all agents in parallel
  const results = await Promise.all(
    workspaceIds.map((id) => runAgentInWorkspace(client, id)),
  );

  console.log(results);
  await client.close();
}

main();

What's next