CLAUDE LABJP
PRICING — September 1 was the scheduled date for the Sonnet 5 price increase, and it did not happen. The introductory $2/$10 per MTok now stands as the regular pricePARTNER — Salesforce and Anthropic announced Claudeforce, an expanded partnership. The Salesforce in Claude plugin ships with 37 prebuilt sales skills, from meeting prep to pipeline managementTRUST — Claudeforce serves Claude through Amazon Bedrock inside the Salesforce Trust Boundary, so data and inference never leave the security perimeter — an answer aimed squarely at regulated industriesBETA — Salesforce in Claude is with select pilot customers for now, with an open beta expected during SeptemberLIMITS — The 50% weekly-limit boost runs through September 13. From September 14 the permanent level is 25% above the pre-promotion baseline, roughly a 17% cut from todayRELEASE — Claude Code has shipped nothing since v2.1.251 on August 28. Against a pace of one release every 0.8 days, a four-day gap is among the longest yetPRICING — September 1 was the scheduled date for the Sonnet 5 price increase, and it did not happen. The introductory $2/$10 per MTok now stands as the regular pricePARTNER — Salesforce and Anthropic announced Claudeforce, an expanded partnership. The Salesforce in Claude plugin ships with 37 prebuilt sales skills, from meeting prep to pipeline managementTRUST — Claudeforce serves Claude through Amazon Bedrock inside the Salesforce Trust Boundary, so data and inference never leave the security perimeter — an answer aimed squarely at regulated industriesBETA — Salesforce in Claude is with select pilot customers for now, with an open beta expected during SeptemberLIMITS — The 50% weekly-limit boost runs through September 13. From September 14 the permanent level is 25% above the pre-promotion baseline, roughly a 17% cut from todayRELEASE — Claude Code has shipped nothing since v2.1.251 on August 28. Against a pace of one release every 0.8 days, a four-day gap is among the longest yet
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.

MCP51Claude API119TypeScript24Server DesignIndie Development9

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 $15 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 →