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 withlist_tools; do not hard-code a name you have not seen in your ownlist_toolsresponse.
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#
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#
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#
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#
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#
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#
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#
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#
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)