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
| Scenario | Scopes needed |
|---|---|
| Spawn, poll, retrieve | workspace:read, agents:read, agents:write |
| Knowledge-augmented agent | All of the above plus knowledge:read, knowledge:write |
| Multi-workspace orchestration | workspace:read, agents:read, agents:write |
See Authentication for how to request scopes in the OAuth flow.
Setup
Install the MCP SDK for your language:
# Python
pip install mcp
# TypeScript / Node.js
npm install @modelcontextprotocol/sdk
All examples below assume a bearer token from the OAuth flow:
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
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
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
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
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
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
- Tools Reference — Exact input/output schemas for all tools
- Resources Reference — Read agent status and output as MCP resources
- Authentication — Complete OAuth flow and scope reference
- Client Setup — Connect from Claude Desktop, Claude Code, Cursor, and more