Connect from your own code
HTTP-level details for building a custom MCP client against EPD.
What you’ll do
Use one of the standard MCP SDKs (TypeScript, Python) to connect from your own service or agent. EPD’s MCP server speaks the official Model Context Protocol over Streamable HTTP — anything that respects the spec works.
Endpoint
POST https://api.epd.com/mcp
Authorization: Bearer <api_key>
epd-version: 2026-02-11
Content-Type: application/json
| Property | Value |
|---|---|
| Transport | Streamable HTTP |
| JSON-RPC version | 2.0 |
| Session model | Stateless — no Mcp-Session-Id |
| Rate limit | 60 requests / minute, per key |
Each request is a single POST carrying a JSON-RPC envelope. Each response carries the result for that one request. There is no persistent session, so you do not need to call initialize repeatedly — but the tool listing is computed fresh per request.
TypeScript / Node — official SDK
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
const transport = new StreamableHTTPClientTransport(
new URL("https://api.epd.com/mcp"),
{
requestInit: {
headers: {
Authorization: `Bearer ${process.env.EPD_KEY}`,
"epd-version": "2026-02-11",
},
},
},
);
const client = new Client(
{ name: "my-app", version: "1.0.0" },
{ capabilities: {} },
);
await client.connect(transport);
const tools = await client.listTools();
console.log(tools.tools.map((t) => t.name));
const result = await client.callTool({
name: "list_customers",
arguments: { limit: 5 },
});
console.log(result.content);
Python — official SDK
import os
import asyncio
from mcp import ClientSession
from mcp.client.streamable_http import streamablehttp_client
async def main():
headers = {
"Authorization": f"Bearer {os.environ['EPD_KEY']}",
"epd-version": "2026-02-11",
}
url = "https://api.epd.com/mcp"
async with streamablehttp_client(url, headers=headers) as (read, write, _):
async with ClientSession(read, write) as session:
await session.initialize()
tools = await session.list_tools()
print([t.name for t in tools.tools])
result = await session.call_tool(
"list_customers", {"limit": 5}
)
print(result.content)
asyncio.run(main())
Raw HTTP (for understanding what’s on the wire)
A tools/list request:
curl https://api.epd.com/mcp \
-H "Authorization: Bearer $EPD_KEY" \
-H "epd-version: 2026-02-11" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/list"
}'
A tools/call:
curl https://api.epd.com/mcp \
-H "Authorization: Bearer $EPD_KEY" \
-H "epd-version: 2026-02-11" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "list_customers",
"arguments": { "limit": 5 }
}
}'
Idempotency in your client
Write tools accept an idempotency_key parameter (or use one of EPD’s built-in defaults). For destructive flows that you may retry, generate the key once and pass it on every retry — see MCP idempotency.
Errors
MCP responses follow the same EPD error shape inside the JSON-RPC result.content. See MCP errors.