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
PropertyValue
TransportStreamable HTTP
JSON-RPC version2.0
Session modelStateless — no Mcp-Session-Id
Rate limit60 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.