KRIT.JUNSREE PERSONAL WEBSITE
MEDIUM

Custom Tools + MCP: 3 Movement ที่ Grow Agent Up (Claude Agent SDK Part 7)

Rick Hightower — Part 7. 3 movement past Read/Edit/Bash: Custom tool (@tool decorator + readOnlyHint สำหรับ parallel), MCP (stdio subprocess vs HTTP endpoint, mcp__server__tool naming, wildcard + tool search auto:N), Subagent (isolation + parallel + restricted tool + inherit permission mode gotcha). ทั้ง 3 แก้ปัญหาเดียวกัน: context window — Custom tool คืน structured result, MCP search withhold definition, Subagent quarantine investigation

By krit.junsree@gmail.com ◆ Jul 10, 2026 ◆ 22 MIN READ Beginner

☰ ON THIS PAGE

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
# 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
//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

text
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

typescript
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 อื่นด้วย)

เชื่อม 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 window
  • false — ปิด (definition ทั้งหมดโหลดทุก turn)
text
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
# 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. Mark readOnlyHint=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) · อ่านต้นฉบับ

Leave a Reply

Your email address will not be published. Required fields are marked *