Rick Hightower — Part 7 ของซีรีส์ 14 part. 3 movement ที่ take agent past Read/Edit/Bash: Custom tool (wire code + API), MCP (external service), Subagent (delegation). ทั้ง 3 แก้ปัญหาเดียวกัน: context window
Your agent works until the task gets real, then it cannot reach your API, cannot delegate, and drowns its own context in noise.
3 Movement
Built-in Read/Edit/Bash = floor ไม่ใช่ ceiling. 3 movement ที่ grow agent up:
- Custom tools — code + API ของคุณ
- MCP — outside world
- Subagents — delegate focused work
ทุกอันเป็นวิธีกัน context window ไม่เต็ม noise
Movement 1: Custom Tool
Function ที่คุณเขียน Claude call ได้. Define 4 อย่าง:
- Name — ที่ Claude ใช้เรียก
- Description — Claude อ่านตัดสินใจเมื่อไหร่ต้องเรียก
- Input schema — argument
- Handler — async function ที่รันจริง
TS = tool() helper + Zod schema · Python = @tool decorator + dict map argument → type
# Python
from typing import Any
import subprocess
from claude_agent_sdk import tool, create_sdk_mcp_server, ToolAnnotations
@tool(
"run_tests",
"Run the buggy-shop pytest suite and return pass/fail counts and failures.",
{"path": str},
annotations=ToolAnnotations(readOnlyHint=True), # no side effects: safe to parallelize
)
async def run_tests(args: dict[str, Any]) -> dict[str, Any]:
result = subprocess.run(
["pytest", args["path"], "-q", "--tb=short"],
capture_output=True, text=True,
)
return {"content": [{"type": "text", "text": result.stdout or result.stderr}]}
# Wrap the tool in an in-process MCP server (runs inside your app, not a subprocess)
test_server = create_sdk_mcp_server(name="testing", version="1.0.0", tools=[run_tests])
//TypeScript
import { tool, createSdkMcpServer } from "@anthropic-ai/claude-agent-sdk";
import { execSync } from "node:child_process";
import { z } from "zod";
const runTests = tool(
"run_tests",
"Run the buggy-shop pytest suite and return pass/fail counts and failures.",
{ path: z.string().describe("Path to the test file or directory") },
async (args) => {
let out: string;
try {
out = execSync(`pytest ${args.path} -q --tb=short`, { encoding: "utf8" });
} catch (e: any) {
out = e.stdout || e.stderr; // pytest exits non-zero on failures
}
return { content: [{ type: "text", text: out }] };
},
{ annotations: { readOnlyHint: true } } // no side effects: safe to parallelize
);
const testServer = createSdkMcpServer({ name: "testing", version: "1.0.0", tools: [runTests] });
2 detail สำคัญ:
1. Handler return content array of result block — Claude เห็นเป็น tool result. Failure ต้อง add isError: true (Python) ไม่งั้น loop หยุดเงียบ
2. readOnlyHint annotation สำคัญกว่าที่ดู — mechanism ที่ให้ tool รัน parallel. Read ไม่มี side effect = mark read-only + agent fan out ได้. Tool เปลี่ยน state = sequential default
ใช้ tool — pass server ให้ mcp_servers + approve ด้วยชื่อ qualified. Pattern mcp__ = จำไว้ ใช้ทุก MCP tool
options = ClaudeAgentOptions(
mcp_servers={"testing": test_server},
allowed_tools=["Read", "Edit", "Grep", "mcp__testing__run_tests"],
)
Movement 2: MCP สำหรับ Tool ที่คุณไม่ได้เขียน
In-process server ข้างบน = 1 flavor ของ MCP. Model Context Protocol = open standard เชื่อม agent กับ external tool + data
Power จริง ไม่ใช่ tool ที่คุณเขียนเอง — คือให้ agent ใช้ server ที่คนอื่น build: GitHub, Slack, database, internal service. ไม่ต้องเขียน tool implementation — ชี้ agent ไปที่ server + server พา tool มาด้วย
MCP server มี 2 shape (doc บอกว่าอันไหน):
- command ให้รัน = stdio server (subprocess local ที่ SDK launch)
- URL = HTTP/SSE server (remote endpoint)
Configure ทั้งคู่ใน mcpServers option. Auth stdio ผ่าน env · Auth HTTP ผ่าน headers
import os
from claude_agent_sdk import ClaudeAgentOptions
options = ClaudeAgentOptions(
mcp_servers={
# stdio: a local subprocess the SDK launches
"github": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": {"GITHUB_TOKEN": os.environ["GITHUB_TOKEN"]},
},
# http: a remote server you reach over the network
"internal-api": {
"type": "http",
"url": "https://tools.internal.example.com/mcp",
"headers": {"Authorization": f"Bearer {os.environ['API_TOKEN']}"},
},
},
allowed_tools=["mcp__github__list_issues", "mcp__internal-api__*"],
)
Wildcard mcp__internal-api__* = approve ทุก tool จาก server นั้น. Config เก็บนอก code ได้ใน .mcp.json ที่ project root — โหลดเมื่อ settingSources รวม "project"
Gotcha: MCP tool ต้อง explicit allowedTools. permission_mode="acceptEdits" ไม่ cover — mode นั้น auto-approve เฉพาะ file edit + filesystem Bash. ถ้า Claude เห็น MCP tool แต่ไม่ call = ลืมเพิ่มใน allowedTools. ใช้ wildcard ไม่ใช่ bypassPermissions (bypass drop safety อื่นด้วย)
Tool มากเกิน: Tool Search
เชื่อม MCP server ใหญ่หลายตัว → ปัญหาใหม่. Tool definition กิน context ทุก turn — dozen ตัวเบียด actual work
Tool search แก้ — withhold tool definition + โหลดเฉพาะที่ Claude ต้องการ current turn. On by default. Tune ด้วย env ENABLE_TOOL_SEARCH:
auto:N— activate เมื่อ tool definition เกิน N% ของ context windowfalse— ปิด (definition ทั้งหมดโหลดทุก turn)
options = ClaudeAgentOptions(
mcp_servers={"enterprise-tools": {"type": "http", "url": "https://tools.example.com/mcp"}},
allowed_tools=["mcp__enterprise-tools__*"],
env={"ENABLE_TOOL_SEARCH": "auto:5"}, # search kicks in past 5% of context
)
Production: ปิด tool search ด้วย "false" เมื่อ tool set เล็ก (ต่ำกว่า ~10 tool ที่ fit context). Search step มี round trip — จำนวน tool น้อย = เสีย latency แก้ปัญหาที่ไม่มี
Movement 3: Subagent สำหรับ Delegation
2 movement แรก = ให้ agent reach ไกลขึ้น. Subagent = delegate — protect context
Subagent = agent instance แยกที่ main agent spawn จัดการ focused subtask. รันใน fresh conversation ของตัวเอง ทำงาน คืน final message ให้ parent. Intermediate file read + grep + tool call อยู่ใน subagent — ไม่แตะ main thread
Isolation = headline benefit. Reviewer subagent อ่าน 30 ไฟล์ — parent รับ 3 บรรทัดสรุป ไม่ใช่ 30 ไฟล์
Benefit อีก 2:
- Subagent รัน parallel ได้ — style checker + security scanner + test runner รันพร้อมกัน
- แต่ละ subagent มี system prompt เฉพาะ + tool set จำกัด — ทำได้เฉพาะงานที่ให้
Define ใน agents option + ต้องมี "Agent" ใน allowedTools (Agent = tool ที่ Claude ใช้ invoke):
# Python
from claude_agent_sdk import query, ClaudeAgentOptions, AgentDefinition
options = ClaudeAgentOptions(
allowed_tools=["Read", "Edit", "Grep", "Glob", "Agent"], # Agent enables delegation
agents={
"reviewer": AgentDefinition(
description="Reviews a proposed bug fix for correctness and regressions. "
"Use after a fix is written, before declaring the task done.",
prompt="You are a meticulous code reviewer. Check the fix for correctness, "
"edge cases, and regressions. Be concise: list only real issues.",
tools=["Read", "Grep", "Glob"], # read-only: it reviews, never edits
model="sonnet", # a cheaper model is plenty for review
),
},
)
Claude ตัดสินใจ invoke reviewer เมื่อ task match กับ description. เขียน description ระวังเหมือน skill. Force invoke = name subagent ใน prompt: “Use the reviewer agent to check this fix”
สิ่งสำคัญสุดต้องเข้าใจ: อะไรข้าม boundary ระหว่าง parent + child
Subagent context เริ่ม fresh. ไม่เห็น parent conversation, parent tool result, parent system prompt. Channel เดียวจาก parent → subagent = prompt string ตอน Agent tool invoke. ข้อมูลใดที่ subagent ต้องรู้ = ต้องอยู่ใน prompt นั้น. Subagent inherit tool definition + project CLAUDE.md — ไม่ inherit skill (ยกเว้น list ใน definition)
Gotcha 1: subagent ไม่ inherit permission prompt ของ parent — แต่ inherit dangerous permission mode. Parent รัน bypassPermissions/acceptEdits/auto = subagent ทุกตัว inherit mode นั้น — override per subagent ไม่ได้. Subagent ที่มี system prompt looser + inherited bypassPermissions = full autonomous system access. Plan ก่อน delegate — keep parent ใน default, add PreToolUse hook gate subagent action, หรือ upfront allow rule ที่ cover เฉพาะที่ subagent ต้องการ
Gotcha 2: subagent spawn subagent ต่อไม่ได้. อย่าใส่ "Agent" ใน tools array ของ subagent — configuration meaningless
3 Movement ต่อกัน
Code-maintenance agent ใช้ custom run_tests → เห็นที่ fail → fix source → call run_tests ซ้ำ confirm suite green → delegate ไป reviewer subagent (second opinion) ก่อนประกาศชัย. อยาก file fix เป็น GitHub issue หรือดึง live pricing จาก internal service = MCP server ห่างเดียว ไม่ต้องเขียน tool code ใหม่
3 movement 1 agent. Main context stay focus ที่ fix ไม่จม noise
ทำวันนี้
- Replace 1 ad-hoc shell command เป็น custom tool — สิ่งที่ agent รันด้วยมือบ่อย wrap ด้วย
@tool. MarkreadOnlyHint=Trueถ้าไม่มี side effect - Wire 1 MCP server ที่คุณไม่ได้เขียน — เพิ่ม GitHub หรือ database server ใน
mcpServers. Auth ผ่านenv/headers. Approve ด้วยmcp__server__*wildcard - Verify
allowedTools— MCP tool visible แต่ไม่ fire = เพิ่มลงallowedTools.acceptEditsไม่ cover MCP - Define 1 read-only subagent — เพิ่ม
reviewerในagents+ restricted tool + cheaper model +"Agent"ในallowedTools - Audit permission mode ก่อน delegate — confirm parent ไม่รัน
bypassPermissions/acceptEdits/auto
Takeaway
Built-in tool = floor. Custom tool เชื่อม agent กับ code + API ที่คุณคุม. MCP เชื่อม ecosystem ของ server ที่คนอื่นเขียน. Subagent delegate focused work
Thread ที่ผ่านทั้ง 3 = context window. Custom tool คืน structured result ไม่ใช่ raw shell noise. MCP tool search withhold definition. Subagent quarantine investigation หลัง 3 บรรทัดสรุป
Grow agent up + discipline ที่ keep sharp = deliberate ว่า agent ดูอะไรตอนนี้. Reach ไกล delegate สะอาด keep desk clear
แหล่งที่มา: “Claude Agent SDK Custom Tools and MCP: The Built-In Tools Got You Started.” (Part 7 of 14) โดย Rick Hightower ตีพิมพ์ 16 มิ.ย. 2026 บน Towards AI (Medium) · อ่านต้นฉบับ