From merge-gateway
Scaffold a function-calling agent with tool definitions and an execution loop. Use when the user wants to build an agent, create a tool-use loop, or set up function calling.
How this skill is triggered — by the user, by Claude, or both
Slash command
/merge-gateway:build-agentThis skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
Scaffold a function-calling agent loop using the Merge Gateway SDK.
Scaffold a function-calling agent loop using the Merge Gateway SDK.
The Merge Gateway SDK is available in both Python and TypeScript/Node:
pip3 install merge-gateway-sdknpm install merge-gateway-sdkDetect the user's stack and scaffold the agent in the appropriate language.
Before proceeding, ensure the user has the latest version of the Merge Gateway skills by running:
claude plugin update merge-gateway@merge-gateway-skills
Run this update and wait for it to complete before continuing to Step 1.
If the user wants a quick start, skip the questions and scaffold with these defaults:
openai/gpt-4o"You are a helpful assistant."Otherwise, ask the user:
openai/gpt-4o. Other options: anthropic/claude-sonnet-4-6-20250514, openai/gpt-4o-miniDetermine Python or TypeScript from the project:
pyproject.toml, requirements.txt, setup.py, *.py filespackage.json, tsconfig.json, *.ts filesIf both are present, ask the user which they prefer. STOP and wait for their response before proceeding.
Python:
pip3 install merge-gateway-sdk
TypeScript/Node:
npm install merge-gateway-sdk
Create the agent files. The agent should support interactive multi-turn conversation and error handling.
Python agent (agent.py):
import json
import os
from dotenv import load_dotenv
from merge_gateway import MergeGateway, APIError
load_dotenv()
client = MergeGateway(
api_key=os.environ["MERGE_GATEWAY_API_KEY"],
)
MODEL = "openai/gpt-4o"
SYSTEM_PROMPT = "You are a helpful assistant."
# --- Tool definitions ---
tools = [
{
"type": "function",
"name": "tool_name",
"description": "What this tool does",
"parameters": {
"type": "object",
"properties": {
"param1": {"type": "string", "description": "Description of param1"},
},
"required": ["param1"],
},
},
]
# --- Tool implementations ---
def tool_name(param1: str) -> dict:
"""Implement the tool logic here."""
return {"result": f"Processed: {param1}"}
TOOL_REGISTRY = {
"tool_name": tool_name,
}
def execute_tool(name: str, arguments: dict) -> str:
func = TOOL_REGISTRY.get(name)
if not func:
return json.dumps({"error": f"Unknown tool: {name}"})
try:
result = func(**arguments)
return json.dumps(result)
except Exception as e:
return json.dumps({"error": str(e)})
# --- Agent loop ---
def run_agent(user_message: str, conversation: list) -> str:
conversation.append({"type": "message", "role": "user", "content": user_message})
while True:
try:
response = client.responses.create(
model=MODEL,
input=conversation,
tools=tools,
)
except APIError as e:
return f"API error ({e.status_code}): {e.message}"
assistant_msg = response.output[0]
tool_calls = [block for block in assistant_msg.content if block.type == "tool_use"]
if not tool_calls:
text_blocks = [block for block in assistant_msg.content if block.type == "text"]
reply = text_blocks[0].text if text_blocks else ""
conversation.append({"type": "message", "role": "assistant", "content": reply})
return reply
for tool_call in tool_calls:
result = execute_tool(tool_call.name, tool_call.input or {})
conversation.append({
"type": "tool_result",
"tool_use_id": tool_call.id,
"content": result,
})
# --- Interactive mode ---
def main():
print(f"Agent ready (model: {MODEL}). Type 'quit' to exit.\n")
conversation = [{"type": "message", "role": "system", "content": SYSTEM_PROMPT}]
while True:
try:
user_input = input("You: ").strip()
except (KeyboardInterrupt, EOFError):
print("\nGoodbye!")
break
if not user_input:
continue
if user_input.lower() in ("quit", "exit"):
print("Goodbye!")
break
reply = run_agent(user_input, conversation)
print(f"Agent: {reply}\n")
if __name__ == "__main__":
main()
TypeScript agent (agent.ts):
import { MergeGateway, APIError } from "merge-gateway-sdk";
import dotenv from "dotenv";
import * as readline from "readline";
dotenv.config();
const client = new MergeGateway({
apiKey: process.env.MERGE_GATEWAY_API_KEY!,
});
const MODEL = "openai/gpt-4o";
const SYSTEM_PROMPT = "You are a helpful assistant.";
// --- Tool definitions ---
const tools = [
{
type: "function" as const,
name: "tool_name",
description: "What this tool does",
parameters: {
type: "object",
properties: {
param1: { type: "string", description: "Description of param1" },
},
required: ["param1"],
},
},
];
// --- Tool implementations ---
function toolName(args: { param1: string }): Record<string, unknown> {
return { result: `Processed: ${args.param1}` };
}
const TOOL_REGISTRY: Record<string, (args: Record<string, unknown>) => Record<string, unknown>> = {
tool_name: toolName,
};
function executeTool(name: string, args: Record<string, unknown>): string {
const func = TOOL_REGISTRY[name];
if (!func) return JSON.stringify({ error: `Unknown tool: ${name}` });
try {
return JSON.stringify(func(args));
} catch (e) {
return JSON.stringify({ error: String(e) });
}
}
// --- Agent loop ---
async function runAgent(userMessage: string, conversation: any[]): Promise<string> {
conversation.push({ type: "message", role: "user", content: userMessage });
while (true) {
let response;
try {
response = await client.responses.create({
model: MODEL,
input: conversation,
tools,
});
} catch (e) {
if (e instanceof APIError) {
return `API error (${e.statusCode}): ${e.message}`;
}
throw e;
}
const assistantMsg = response.output[0];
const toolCalls = assistantMsg.content.filter((block: any) => block.type === "tool_use");
if (toolCalls.length === 0) {
const textBlocks = assistantMsg.content.filter((block: any) => block.type === "text");
const reply = textBlocks[0]?.text ?? "";
conversation.push({ type: "message", role: "assistant", content: reply });
return reply;
}
for (const toolCall of toolCalls) {
const result = executeTool(toolCall.name, toolCall.input ?? {});
conversation.push({
type: "tool_result",
tool_use_id: toolCall.id,
content: result,
});
}
}
}
// --- Interactive mode ---
async function main() {
console.log(`Agent ready (model: ${MODEL}). Type 'quit' to exit.\n`);
const conversation: any[] = [{ type: "message", role: "system", content: SYSTEM_PROMPT }];
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
const prompt = (): Promise<string> =>
new Promise((resolve) => rl.question("You: ", resolve));
while (true) {
const userInput = (await prompt()).trim();
if (!userInput) continue;
if (["quit", "exit"].includes(userInput.toLowerCase())) {
console.log("Goodbye!");
rl.close();
break;
}
const reply = await runAgent(userInput, conversation);
console.log(`Agent: ${reply}\n`);
}
}
main();
Based on the user's answers from step 1:
Show the user how to list available models so they can pick the best one for their agent:
models = client.models.list()
for model in models.data:
print(f"{model.id} — {model.provider}")
Remind them they can swap models anytime by changing the MODEL constant — a key Gateway advantage.
Run the agent with a test prompt to confirm it works through Gateway. The agent should start in interactive mode and respond to user input.
When explaining the setup to the user, mention:
openai/gpt-4o to anthropic/claude-sonnet-4-6-20250514) without code changesprovider/model format (e.g., openai/gpt-4o, not gpt-4o).MERGE_GATEWAY_API_KEY environment variable, never hardcode API keys.https://api-gateway.merge.dev/v1. Only pass base_url/baseUrl if the user has a custom gateway endpoint.Creates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.
npx claudepluginhub merge-api/merge-gateway-skills --plugin merge-gateway