CLAUDE LABJP
FORK — Claude Code 2.1.212 changes what /fork does: it copies your conversation into a new background session with its own row in claude agents, so you can keep working. The old in-session subagent is now /subtaskLIMITS — WebSearch calls are now capped at 200 per session by default, and subagent spawns get the same 200 ceiling, so a runaway search or delegation loop stops on its ownMCPBG — MCP tool calls running past two minutes now move to the background automatically, keeping the session usable. Tune the threshold with CLAUDE_CODE_MCP_AUTO_BACKGROUND_MSPLANFIX — Fixed plan mode auto-running file-modifying Bash commands such as touch and rm without a permission prompt or an SDK canUseTool callbackSONNET5 — Claude Sonnet 5 is running on introductory pricing of $2 per million input tokens and $10 per million output. After August 31 it moves to $3 and $15IPO — Bankers are reportedly lining up investor meetings for Anthropic ahead of a possible public listing as soon as OctoberFORK — Claude Code 2.1.212 changes what /fork does: it copies your conversation into a new background session with its own row in claude agents, so you can keep working. The old in-session subagent is now /subtaskLIMITS — WebSearch calls are now capped at 200 per session by default, and subagent spawns get the same 200 ceiling, so a runaway search or delegation loop stops on its ownMCPBG — MCP tool calls running past two minutes now move to the background automatically, keeping the session usable. Tune the threshold with CLAUDE_CODE_MCP_AUTO_BACKGROUND_MSPLANFIX — Fixed plan mode auto-running file-modifying Bash commands such as touch and rm without a permission prompt or an SDK canUseTool callbackSONNET5 — Claude Sonnet 5 is running on introductory pricing of $2 per million input tokens and $10 per million output. After August 31 it moves to $3 and $15IPO — Bankers are reportedly lining up investor meetings for Anthropic ahead of a possible public listing as soon as October
Articles/API & SDK
API & SDK/2026-06-02Advanced

Beyond Tools in MCP: Designing with Resources, Prompts, and Sampling

Cramming everything into MCP tools hits a wall fast. Here is how resources, prompts, and sampling untangle a server, told through a real wallpaper-app asset manager I cut from 14 tools down to 5.

MCP45Claude API115TypeScript24Server DesignIndie Development8

Premium Article

I'm Masaki Hirokawa, an artist and indie developer. I want to walk back through the moment I started writing an MCP server so Claude could work directly with my wallpaper-app assets. My first server implemented every capability as a tool. Listing assets, reading metadata, running a fixed review routine, generating captions — all tools. It worked, but once the tool count crept up to fourteen, Claude started hesitating over which one to call, and I lost track of where things lived myself.

That was when it clicked that MCP offers resource, prompt, and sampling alongside tool, and I had been forcing things that belonged elsewhere into tools. Using the real refactor that cut those fourteen tools down to five, this piece lays out how to choose among the three primitives. The subject is the asset-management server for the wallpaper apps I've built as an indie developer since 2014 — apps that have passed 50 million downloads across iOS and Android.

What happens when tools carry everything

When you start with MCP, the first thing you reach for is almost always a tool. Register a function with server.tool() and Claude can call it — that simplicity is the draw. I went the same way. But expressing everything through tools surfaces a few concrete problems.

The first is that Claude's selection accuracy drops. Tools are presented to the model as operations with side effects. When read-only data fetches are also tools, the model has to judge "is this safe to call?" every time, and the more similarly named tools pile up, the more often it grabs the wrong one. On my server I kept both get_wallpaper_meta and get_wallpaper_info, and Claude regularly called the opposite of what I wanted.

The second is that routines can't be reused. "Review this wallpaper's metadata for the App Store" follows nearly the same steps every time. As a tool, the steps live in the prompt I rewrite on every call instead of accumulating on the server side.

The third is that there's nowhere to put light inference inside server processing. If I want to generate a human-facing caption from a filename, the server could hold an API key and call Claude itself — but then the server becomes the billing party, and cost management for a one-person operation gets complicated fast.

Here is a simplified version of the "everything is a tool" state before the refactor.

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
 
const server = new McpServer({ name: "wallpaper-assets", version: "1.0.0" });
 
// Read-only, yet all expressed as tools
server.tool("list_wallpapers", {}, async () => ({
  content: [{ type: "text", text: JSON.stringify(await db.listAll()) }],
}));
 
server.tool("get_wallpaper_meta", { id: z.string() }, async ({ id }) => ({
  content: [{ type: "text", text: JSON.stringify(await db.meta(id)) }],
}));
 
// A fixed review routine, also a tool (the caller rewrites the prompt each time)
server.tool("review_metadata", { id: z.string() }, async ({ id }) => ({
  content: [{ type: "text", text: `Review this metadata: ${JSON.stringify(await db.meta(id))}` }],
}));
 
// ... eleven more tools follow

That review_metadata is the telling one. All it does is assemble a review prompt and return it; it's a template, not an operation. It should have been a prompt from the start.

Resources: hand read-only data over as an address

A resource exposes the server's readable data under a URI. It's reachable at an address like wallpaper://1024, and the absence of side effects is expressed in its type. From Claude's vantage point it stops being "an operation to decide whether to call" and becomes "material it can reference," which separates it from the tool-selection noise.

The wallpaper list and individual metadata are exactly read-only data, so I moved them to resources.

import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
 
// The list is a fixed-URI resource
server.resource("wallpaper-list", "wallpaper://list", async (uri) => ({
  contents: [{
    uri: uri.href,
    mimeType: "application/json",
    text: JSON.stringify(await db.listAll()),
  }],
}));
 
// Individual metadata via a templated URI
server.resource(
  "wallpaper",
  new ResourceTemplate("wallpaper://{id}", { list: undefined }),
  async (uri, { id }) => ({
    contents: [{
      uri: uri.href,
      mimeType: "application/json",
      text: JSON.stringify(await db.meta(String(id))),
    }],
  })
);

That change alone removed three tools — list_wallpapers, get_wallpaper_meta, and get_wallpaper_info. Fetching data consolidated onto resources, and the mix-ups between similarly named tools dissolved on their own. I strongly recommend placing this line — reads are resources, writes and operations are tools — as the first fork in the design. What stays a tool is only the state-changing work, like re-running classification or regenerating a thumbnail.

One caveat: resources follow a "the client reads them explicitly" model. Claude doesn't silently read every resource in the flow of a conversation. When you want it to work with a specific wallpaper, the client has to attach that resource to the context or you have to nudge it to reference the resource. Misread this and it feels like "I made it a resource but Claude won't look at it" — but that's the designed behavior.

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
Concrete rules for choosing between resources, prompts, and sampling (reads become resources, human-triggered routines become prompts, in-server inference becomes sampling)
The actual refactor that took my 6-app wallpaper asset server from 14 tools to 5, with the TypeScript code
The client-support pitfall around sampling, and a fallback design that degrades quietly instead of breaking
Secure payment via Stripe · Cancel anytime

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Claude Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

Related Articles

API & SDK2026-07-08
Contract-Test Every Tool Before You Submit or Automate an MCP Connector
A connector that works once in a chat can still break silently in an unattended job through misread response shapes or double-fired writes. Here is a small harness that machine-checks tool descriptions, response contracts, idempotency, and latency, with measured numbers.
API & SDK2026-07-09
When the RAG Started Being Confidently Wrong — Field Notes on Measuring Retrieval Misses With Groundedness
In a Claude API RAG, the answers stay fluent while the facts drift. Often the cause is a silent recall decay on the retrieval side, missing the document that holds the answer. Field notes on measuring groundedness and retrieval hit rate and walking the system back, with working code and real numbers.
API & SDK2026-07-03
A 40% Lower Price Doesn't Mean a 40% Lower Bill — Measuring the Opus 4.8 to Sonnet 5 Migration by Cost per Completed Task
Sonnet 5's intro pricing looks ~40% cheaper than Opus 4.8, yet extra tool turns can flip the math. Working TypeScript for consumption vectors, a paired-run harness, and break-even turn counts.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →