qaqnuz MCP public API reference

Tool examples#

Tool names are per-brand. The exposed tool set is brand-scoped and opt-in — there is no fixed global tool catalogue. The tool names below (get_order_status, create_order, search_contacts) are illustrative. Always discover the real names and input schemas for your key with list_tools; do not hard-code a name you have not seen in your own list_tools response.

The three scenarios below cover the shapes you will meet in practice: a read tool, an approval-gated write tool, and a validation failure.

1. Read tool (read side-effect)#

A read tool requires a read-scoped key (e.g. orders.read) and executes synchronously. This is the common case.

TypeScript#

ts
const res = await client.callTool({
  name: "get_order_status",
  arguments: { order_id: "ord_12345" },
});

if (!res.isError) {
  const order = JSON.parse(res.content[0]?.text ?? "null");
  console.log(`Order ${order.order_id} is ${order.status}`);
}

Python#

python
result = await session.call_tool(
    "get_order_status",
    arguments={"order_id": "ord_12345"},
)
if not result.isError:
    order = json.loads(result.content[0].text)
    print(f"Order {order['order_id']} is {order['status']}")

2. Approval-gated write tool (write / external side-effect)#

A write or external tool needs a key scoped for that action (e.g. orders.write). If the brand's policy marks the tool approval-required, the call is not executed: an approval request is queued and a permission error is returned containing the approval id. You then track that approval out-of-band; once approved, re-issue the call.

TypeScript#

ts
const res = await client.callTool({
  name: "create_order",
  arguments: { sku: "SKU-9", quantity: 2 },
});

if (res.isError) {
  const msg = res.content[0]?.text ?? "";
  // Approval-required responses include the approval request id in the message.
  if (msg.includes("requires approval")) {
    console.log("Queued for approval:", msg);
    // Track the approval out-of-band; retry once approved.
  } else {
    console.error("create_order failed:", msg);
  }
} else {
  const order = JSON.parse(res.content[0]?.text ?? "null");
  console.log("Created order:", order.id);
}

Python#

python
result = await session.call_tool(
    "create_order",
    arguments={"sku": "SKU-9", "quantity": 2},
)
msg = result.content[0].text if result.content else ""
if result.isError:
    if "requires approval" in msg:
        print("Queued for approval:", msg)
        # Track the approval out-of-band; retry once approved.
    else:
        print("create_order failed:", msg)
else:
    order = json.loads(msg)
    print("Created order:", order["id"])
No auto-execute over MCP. Approval-gated tools never run synchronously from an external key. This is a security property of the surface, not a transient limitation.

3. Handling a validation error#

If arguments do not satisfy the tool's inputSchema, the call fails with a validation error and a safe message. Validation failures are terminal for that input — fix the arguments rather than retrying the same call.

TypeScript#

ts
const res = await client.callTool({
  name: "search_contacts",
  arguments: {}, // missing required "query"
});

if (res.isError) {
  // Safe to surface/log: the message is sanitized.
  console.warn("validation:", res.content[0]?.text);
}

Python#

python
result = await session.call_tool(
    "search_contacts",
    arguments={},  # missing required "query"
)
if result.isError:
    print("validation:", result.content[0].text)

A reusable client helper#

A small wrapper that connects, discovers a tool, and calls it — useful as a starting point.

TypeScript#

ts
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";

export async function withQaqnuz<T>(
  fn: (client: Client) => Promise<T>,
): Promise<T> {
  const transport = new StdioClientTransport({
    command: "qaqnuz-mcp",
    args: [],
    env: { QAQNUZ_API_KEY: process.env.QAQNUZ_API_KEY! },
  });
  const client = new Client({ name: "my-integration", version: "1.0.0" });
  await client.connect(transport);
  try {
    return await fn(client);
  } finally {
    await client.close();
  }
}

// Usage:
await withQaqnuz(async (client) => {
  const { tools } = await client.listTools();
  if (!tools.some((t) => t.name === "get_order_status")) {
    throw new Error("get_order_status is not available to this key");
  }
  const res = await client.callTool({
    name: "get_order_status",
    arguments: { order_id: "ord_12345" },
  });
  return res.content[0]?.text;
});

Python#

python
import os
import json
from contextlib import asynccontextmanager
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client

@asynccontextmanager
async def qaqnuz_session():
    server = StdioServerParameters(
        command="qaqnuz-mcp",
        args=[],
        env={"QAQNUZ_API_KEY": os.environ["QAQNUZ_API_KEY"]},
    )
    async with stdio_client(server) as (read, write):
        async with ClientSession(read, write) as session:
            await session.initialize()
            yield session

# Usage:
async def run():
    async with qaqnuz_session() as session:
        listed = await session.list_tools()
        names = {t.name for t in listed.tools}
        if "get_order_status" not in names:
            raise RuntimeError("get_order_status is not available to this key")
        result = await session.call_tool(
            "get_order_status",
            arguments={"order_id": "ord_12345"},
        )
        return json.loads(result.content[0].text)