Raw HTTP (JSON-RPC)
The server speaks MCP's Streamable HTTP transport, statelessly: each POST is a complete JSON-RPC 2.0 request and gets a plain JSON response. You don't need an MCP SDK, a session or an initialize handshake to call tools, which makes it easy to use from scripts, backends and CI.
| Method and URL | POST https://mcp.foodperfect.ai/mcp |
| Headers | Authorization: Bearer <key>, Content-Type: application/json, Accept: application/json, text/event-stream |
| Write tools | also Idempotency-Key: <unique per call> |
#curl
List the tools:
curl -sS https://mcp.foodperfect.ai/mcp \
-H "Authorization: Bearer $FOODPERFECT_API_KEY" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'Call a read tool:
curl -sS https://mcp.foodperfect.ai/mcp \
-H "Authorization: Bearer $FOODPERFECT_API_KEY" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"get_account","arguments":{}}}'Call a paid tool with an idempotency key:
curl -sS https://mcp.foodperfect.ai/mcp \
-H "Authorization: Bearer $FOODPERFECT_API_KEY" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"generate_promo","arguments":{"heading":"Lunch special","dishes":[{"name":"Lemongrass chicken","price":"$12"}],"ratio":"4:5"}}}'A successful tool call returns the result both as structuredContent (an object) and as JSON text in content:
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"content": [{ "type": "text", "text": "{\"plan\":\"growth\",\"totalBalanceUsd\":61.4, …}" }],
"structuredContent": { "plan": "growth", "totalBalanceUsd": 61.4, "…": "…" }
}
}#A tiny client
A helper that adds a fresh idempotency key to each write call and turns tool errors into exceptions.
const MCP_URL = 'https://mcp.foodperfect.ai/mcp';
const KEY = process.env.FOODPERFECT_API_KEY!;
let id = 0;
export async function callTool(name: string, args: Record<string, unknown> = {}, idempotencyKey = crypto.randomUUID()) {
const res = await fetch(MCP_URL, {
method: 'POST',
headers: {
Authorization: `Bearer ${KEY}`,
'Content-Type': 'application/json',
Accept: 'application/json, text/event-stream',
'Idempotency-Key': idempotencyKey, // ignored by read tools
},
body: JSON.stringify({ jsonrpc: '2.0', id: ++id, method: 'tools/call', params: { name, arguments: args } }),
});
const body = await res.json();
if (!res.ok) throw Object.assign(new Error(body.error?.message), body.error); // 401, 403, 429
if (body.error) throw new Error(body.error.message); // JSON-RPC error
const result = body.result;
if (result.isError) throw Object.assign(new Error(result.structuredContent?.error?.message), result.structuredContent?.error);
return result.structuredContent;
}
// const account = await callTool('get_account');
// const { generation } = await callTool('generate_photo', { dishes: [{ name: 'Margherita' }], styleId: 3 });import os, uuid, requests
MCP_URL = "https://mcp.foodperfect.ai/mcp"
KEY = os.environ["FOODPERFECT_API_KEY"]
class FoodPerfectError(Exception):
def __init__(self, error: dict):
super().__init__(error.get("message"))
self.code, self.retryable, self.details = error.get("code"), error.get("retryable"), error.get("details")
def call_tool(name: str, args: dict | None = None, idempotency_key: str | None = None) -> dict:
res = requests.post(MCP_URL, timeout=120, headers={
"Authorization": f"Bearer {KEY}",
"Content-Type": "application/json",
"Accept": "application/json, text/event-stream",
"Idempotency-Key": idempotency_key or str(uuid.uuid4()), # ignored by read tools
}, json={"jsonrpc": "2.0", "id": 1, "method": "tools/call",
"params": {"name": name, "arguments": args or {}}})
body = res.json()
if not res.ok:
raise FoodPerfectError(body["error"]) # 401, 403, 429
if "error" in body:
raise RuntimeError(body["error"]["message"]) # JSON-RPC error
result = body["result"]
if result.get("isError"):
raise FoodPerfectError(result["structuredContent"]["error"])
return result["structuredContent"]
# account = call_tool("get_account")
# photo = call_tool("generate_photo", {"dishes": [{"name": "Margherita"}], "styleId": 3})To retry a write safely, pass the same idempotencyKey again.
#Using an MCP SDK
Official MCP SDKs work too: point a Streamable HTTP client transport at https://mcp.foodperfect.ai/mcp with the Authorization header. Most SDKs set headers per transport rather than per call, so for write tools either create a transport per call with its own Idempotency-Key, or use the helper above.
#Other endpoints
| Endpoint | Auth | Returns |
|---|---|---|
GET /health |
none | { ok, service, version } |
GET /openapi.json |
none | A discovery document listing the tools and their schemas |
These live at the root of the MCP host, e.g. https://mcp.foodperfect.ai/mcp without /mcp.