RORK LABJP
VERIFY — Android developer verification becomes fully mandatory in September 2026. On Android 17 devices verification lives in the OS, so unverified apps can be blocked from new installs at the OS levelREVIEW — From September, responses are required when submitting new apps or updates to the App Store, and when notarizing for alternative distributionSDK — iOS 27 and Xcode 27 are expected to ship in September, but the requirement to submit iOS 27 SDK builds does not land until spring 2027TIMELINE — Rork's $15M seed and the Paperline acquisition were announced on April 9, 2026 — worth dating precisely rather than treating as breaking newsMAX — Rork Max emits native Swift for iPhone, iPad, Apple Watch, Apple TV, Vision Pro, and iMessage, while standard Rork generates cross-platform apps with React Native (Expo)EXPORT — If the automated Publish step fails, you can sync to GitHub and export the full React Native source for free, then finish the submission by handVERIFY — Android developer verification becomes fully mandatory in September 2026. On Android 17 devices verification lives in the OS, so unverified apps can be blocked from new installs at the OS levelREVIEW — From September, responses are required when submitting new apps or updates to the App Store, and when notarizing for alternative distributionSDK — iOS 27 and Xcode 27 are expected to ship in September, but the requirement to submit iOS 27 SDK builds does not land until spring 2027TIMELINE — Rork's $15M seed and the Paperline acquisition were announced on April 9, 2026 — worth dating precisely rather than treating as breaking newsMAX — Rork Max emits native Swift for iPhone, iPad, Apple Watch, Apple TV, Vision Pro, and iMessage, while standard Rork generates cross-platform apps with React Native (Expo)EXPORT — If the automated Publish step fails, you can sync to GitHub and export the full React Native source for free, then finish the submission by hand
Articles/AI Models
AI Models/2026-08-07Advanced

Cutting MCP Tools Didn't Make Anything Lighter — 2,377 Bytes of Definitions vs 110,298 Bytes of Response

I wrote a minimal MCP server for a wallpaper catalog and measured the byte cost of tool definitions against the byte cost of responses. Here is which side actually matters, and the real reason to merge tools.

MCP4Rork528React Native220Expo159Architecture22Measurement2advanced7

Premium Article

The MCP server behind my wallpaper app catalog had quietly grown to twelve tools.

List, search, find by condition, fetch one. The kind of accumulation where each addition seems reasonable on its own.

Working as an indie developer across several apps, tooling like this grows without anyone else ever reviewing it. That is usually why the growth goes unnoticed.

Assuming the count was the problem, I removed the six least-used tools. Nothing felt different.

Either I had not cut deeply enough, or I had cut in the wrong place. Guessing between those two is how afternoons disappear.

So I measured instead.

A dependency-free MCP server, written for measurement

Using the official SDK adds framing and session bookkeeping to every byte count. Since I wanted to isolate what costs what, I implemented JSON-RPC 2.0 over stdio directly.

MCP is, at its core, one JSON-RPC message per line on standard input and output. Supporting only initialize, tools/list, and tools/call takes about this much code.

#!/usr/bin/env node
// server.mjs — a minimal MCP server built for measurement (stdio / JSON-RPC 2.0)
import { createInterface } from "node:readline";
 
// 324 wallpaper records, matching the field layout of the real app
const CATALOG = Array.from({ length: 324 }, (_, i) => ({
  id: `wp_${String(i + 1).padStart(4, "0")}`,
  title: `Wallpaper ${i + 1}`,
  category: ["nature", "abstract", "minimal", "space", "city"][i % 5],
  width: 2796,
  height: 1290,
  bytes: 380000 + (i * 977) % 220000,
  sha256: Array.from({ length: 64 }, (_, k) => "0123456789abcdef"[(i * 7 + k) % 16]).join(""),
  tags: ["calm", "blue", "gradient", "night"].slice(0, (i % 4) + 1),
  license: "internal",
  createdAt: "2026-05-01T00:00:00Z",
  updatedAt: "2026-07-30T00:00:00Z",
  overlayScrim: { top: 0.42, bottom: 0.18 },
}));
 
const TOOLS = [
  { name: "list_wallpapers",
    description: "Lists wallpapers in the catalog. Can be narrowed by category.",
    inputSchema: { type: "object", properties: {
      category: { type: "string", description: "Category name" },
      limit: { type: "integer", description: "Maximum number of records" } } } },
  { name: "search_wallpapers",
    description: "Searches catalog wallpapers by keyword across tags and titles.",
    inputSchema: { type: "object", properties: {
      query: { type: "string", description: "Search term" },
      limit: { type: "integer", description: "Maximum number of records" } }, required: ["query"] } },
  // …plus find_wallpapers / get_wallpaper / get_locale_keys /
  //   get_missing_translations / check_i18n_completeness / get_build_config /
  //   get_app_config / list_categories / get_catalog_stats / validate_catalog
];
 
function call(name, args = {}) {
  if (name === "list_wallpapers") {
    let rows = CATALOG;
    if (args.category) rows = rows.filter((r) => r.category === args.category);
    return rows.slice(0, args.limit ?? rows.length);
  }
  if (name === "list_wallpapers_projected") {
    let rows = CATALOG;
    if (args.category) rows = rows.filter((r) => r.category === args.category);
    return rows.slice(0, args.limit ?? rows.length)
      .map((r) => ({ id: r.id, title: r.title, category: r.category }));
  }
  if (name === "get_wallpaper") return CATALOG.find((r) => r.id === args.id) ?? null;
  if (name === "list_categories") return [...new Set(CATALOG.map((r) => r.category))];
  return { ok: true };
}
 
const rl = createInterface({ input: process.stdin });
rl.on("line", (line) => {
  if (!line.trim()) return;                 // Skip blank lines, or JSON.parse throws here
  const req = JSON.parse(line);
  let result;
  if (req.method === "initialize") {
    result = {
      protocolVersion: "2025-06-18",
      capabilities: { tools: {} },
      serverInfo: { name: "wallpaper-catalog", version: "0.1.0" },
    };
  } else if (req.method === "tools/list") {
    const n = req.params?.n ?? TOOLS.length; // Variable tool count, for measurement only
    result = { tools: TOOLS.slice(0, n) };
  } else if (req.method === "tools/call") {
    const payload = call(req.params.name, req.params.arguments);
    result = { content: [{ type: "text", text: JSON.stringify(payload) }] };
  } else {
    result = {};
  }
  process.stdout.write(JSON.stringify({ jsonrpc: "2.0", id: req.id, result }) + "\n");
});

Everything below was measured on Node.js v22.22.3 under Linux, with the server running as a child process and requests sent across standard input and output. These are not device numbers or numbers over a network — they are byte counts and local round trips.

The extra n parameter on tools/list is not part of the spec. It exists so I could vary the tool count one at a time and watch the response size move.

Tool definitions cost two orders of magnitude less than I assumed

Here is the size of the tools/list result, counted in UTF-8 bytes.

Toolstools/list responseAverage per tool
1297 bytes297 bytes
3873 bytes291 bytes
51,260 bytes252 bytes
71,665 bytes238 bytes
102,106 bytes211 bytes
122,377 bytes198 bytes

Dividing the span from one tool to twelve gives a marginal cost of 189.1 bytes per additional tool.

The six tools I had deleted therefore added up to roughly 1.2 KB.

That number alone explains why nothing changed. 1.2 KB is not a quantity anyone notices.

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
12 tool definitions cost 2,377 bytes; one response cost 110,298 bytes — measured, not estimated
A paginator that slices on record boundaries from a byte budget, with 99.3–100.0% fill rates
Measuring bigram similarity between tool descriptions to find the 45.5% collision worth merging
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

Rork 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

AI Models2026-04-02
Rork × MCP (Model Context Protocol) Complete Implementation Guide — Building Next-Gen Mobile Apps with AI Tool Integration
A complete guide to integrating MCP (Model Context Protocol) into your Rork app. Covers architecture design, streaming, authentication, and production operations with working code examples.
AI Models2026-06-19
Before You Pay $200/mo for Rork Max, Map How Far Expo Reaches in Three Tiers
Wanting widgets or Live Activities makes Rork Max tempting, but most of those features are reachable from the Expo setup that standard Rork generates. Here is how I sort each Apple-native feature into three tiers—reachable in Expo, reachable with a custom module, or where Max is the pragmatic answer—and verify which tier my app is in before paying.
AI Models2026-06-14
Calling Apple Foundation Models from a Rork (Expo) App: Bridging On-Device AI Through a Native Module
Rork generates Expo (React Native) apps, but Apple Foundation Models ships as a Swift framework you can't touch from JavaScript. Here's how to write an Expo Modules API bridge, gate it by availability, and fall back to the cloud on unsupported devices.
📚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 →