Skip to content

Custom agents

$ custom-agents · 5 min read · updated 2026-05-05

capsol is a plain MCP server over Streamable HTTP at /mcp/<capsule-id>. OAuth discovery + Dynamic Client Registration is the default hosted-agent path. Autonomous agents that cannot begin with OAuth should use agent enrollment and wait for approval.

FrameworkPackageTransport constructorLifecycle
LangChain (Python)langchain-mcp-adaptersMultiServerMCPClientLong-lived session
LangChain (JS)@langchain/mcp-adaptersMultiServerMCPClientLong-lived session
OpenAI Agents SDKopenai-agentsMCPServerStreamableHttpasync with ... as server:
Vercel AI SDKaiexperimental_createMCPClientClose explicitly (.close())
Mastra@mastra/mcpMCPClientLong-lived; tools cached at boot
Pydantic AIpydantic-aiMCPServerStreamableHTTPasync with agent.run_mcp_servers()

All use the same capsule URL: https://capsol.example.com/mcp/<capsule-id>. Give every autonomous agent its own OAuth grant or approved enrollment so activity, unread signals, and revocation stay independent.

Custom clients must follow MCP Streamable HTTP request headers: POST JSON-RPC to the capsule URL with Content-Type: application/json and Accept: application/json, text/event-stream. Browser-based clients should send requests from the hosted registry origin or loopback during local debugging; unexpected Origin headers are rejected.

Terminal window
pip install langchain-mcp-adapters langchain-openai langgraph
app.py
import asyncio
from langchain_mcp_adapters.client import MultiServerMCPClient
from langchain_openai import ChatOpenAI
from langgraph.prebuilt import create_react_agent
async def main():
client = MultiServerMCPClient({
"capsol": {
"transport": "streamable_http",
"url": "https://capsol.example.com/mcp/<capsule-id>",
},
})
tools = await client.get_tools()
agent = create_react_agent(ChatOpenAI(model="gpt-4o"), tools)
result = await agent.ainvoke({"messages": [("user", "list every capsol item")]})
print(result["messages"][-1].content)
asyncio.run(main())

Gotchas: transport: "streamable_http" (no SSE). MultiServerMCPClient holds a single long-lived session — don’t create one per request. Docs: https://github.com/langchain-ai/langchain-mcp-adapters

Terminal window
npm i @langchain/mcp-adapters @langchain/openai @langchain/langgraph
app.ts
import { MultiServerMCPClient } from "@langchain/mcp-adapters";
import { ChatOpenAI } from "@langchain/openai";
import { createReactAgent } from "@langchain/langgraph/prebuilt";
const client = new MultiServerMCPClient({
mcpServers: {
capsol: {
transport: "streamable_http",
url: "https://capsol.example.com/mcp/<capsule-id>",
},
},
});
const tools = await client.getTools();
const agent = createReactAgent({ llm: new ChatOpenAI({ model: "gpt-4o" }), tools });
const result = await agent.invoke({ messages: [{ role: "user", content: "list every capsol item" }] });
console.log(result.messages.at(-1)?.content);

Gotchas: Node 20+. transport: "streamable_http". Docs: https://www.npmjs.com/package/@langchain/mcp-adapters

Terminal window
pip install openai-agents
app.py
import asyncio
from agents import Agent, Runner
from agents.mcp import MCPServerStreamableHttp
async def main():
async with MCPServerStreamableHttp(
name="capsol",
params={"url": "https://capsol.example.com/mcp/<capsule-id>"},
) as capsol:
agent = Agent(
name="researcher",
instructions="Use capsol to recall project state before answering.",
mcp_servers=[capsol],
)
result = await Runner.run(agent, "what do we know about the auth flow?")
print(result.final_output)
asyncio.run(main())

Gotchas: MCPServerStreamableHttp is an async context manager. capsol’s tool names pass through verbatim — don’t rename. Docs: https://openai.github.io/openai-agents-python/mcp/

Terminal window
npm i ai @ai-sdk/openai
app.ts
import { experimental_createMCPClient, generateText } from "ai";
import { openai } from "@ai-sdk/openai";
const capsol = await experimental_createMCPClient({
transport: { type: "sse", url: "https://capsol.example.com/mcp/<capsule-id>" },
});
const tools = await capsol.tools();
const { text } = await generateText({
model: openai("gpt-4o"),
tools,
prompt: "summarise every capsol docs:// entry",
});
console.log(text);
await capsol.close();

Gotchas: import is still experimental_. Use maxSteps: 5+ when chaining tool calls. Always close() the client. Docs: https://sdk.vercel.ai/docs/ai-sdk-core/mcp-tools

Terminal window
npm i @mastra/core @mastra/mcp
agent.ts
import { Agent } from "@mastra/core/agent";
import { MCPClient } from "@mastra/mcp";
import { openai } from "@ai-sdk/openai";
const mcp = new MCPClient({
servers: {
capsol: { url: new URL("https://capsol.example.com/mcp/<capsule-id>") },
},
});
export const researcher = new Agent({
name: "researcher",
instructions: "Consult capsol before answering project questions.",
model: openai("gpt-4o"),
tools: await mcp.getTools(),
});

Gotchas: url must be a URL instance, not a string. Tool schemas cached at boot — restart after changing the capsule’s skills. Docs: https://mastra.ai/docs/agents/mcp-integration

Terminal window
pip install pydantic-ai
app.py
import asyncio
from pydantic_ai import Agent
from pydantic_ai.mcp import MCPServerStreamableHTTP
capsol = MCPServerStreamableHTTP(url="https://capsol.example.com/mcp/<capsule-id>")
agent = Agent("openai:gpt-4o", mcp_servers=[capsol], system_prompt="Consult capsol before answering.")
async def main():
async with agent.run_mcp_servers():
result = await agent.run("list every capsol item")
print(result.data)
asyncio.run(main())

Gotchas: wrap invocations in async with agent.run_mcp_servers() for session lifecycle. Python 3.10+. Docs: https://ai.pydantic.dev/mcp/client/

Every framework above holds one long-lived MCP session per capsol connection for the life of the agent. Do not open a session per tool call; every initialize round-trip counts. If you need higher throughput or separate identity, create multiple connections from the dashboard and load-balance across them.

Use the stable capsule URL (https://capsol.example.com/mcp/<capsule-id>). OAuth-capable agents should discover /.well-known/oauth-protected-resource/mcp/<capsule-id>, register at /oauth/register, and complete PKCE. Agents that cannot start with OAuth should use /v1/agent-enrollments, save the one-time enrollment token, and poll until approval. Pending polls do not return MCP connection details; after approval, the same token becomes the MCP credential. Tokens are never accepted in URLs. See Access.

Agents can also inspect /.well-known/capsol-agent.json for supported auth flows, grant endpoints, docs links, and CLI commands. Human operators can approve OAuth and enrollment grants from the dashboard; enrollment grants can also be approved with capsol grants approve <grant-id>.