able contract that survives upstream API changes.
// src/projections/issue-projection.ts
import type { RawIssue, ProjectedIssue } from '../types';
export class IssueProjector {
private readonly allowedFields: ReadonlySet<string>;
constructor() {
this.allowedFields = new Set(['key', 'summary', 'status', 'priority', 'assignee', 'created']);
}
project(raw: RawIssue): ProjectedIssue {
const projected: Partial<ProjectedIssue> = {};
for (const field of this.allowedFields) {
if (field in raw.fields) {
projected[field as keyof ProjectedIssue] = raw.fields[field];
}
}
return {
key: raw.key,
...projected,
_ref: this.generateRef(raw.key, raw.id)
} as ProjectedIssue;
}
private generateRef(key: string, id: string): string {
const hash = Buffer.from(`${key}:${id}`).toString('base64url');
return `ref:/cache/issues/${hash}.json`;
}
}
Architecture Rationale: Allowlists default to dropping unknown fields. When an upstream API introduces new metadata, your projection remains unaffected. The _ref field points to a content-addressed disk location where the full payload is stored. The agent only materializes the full object when explicitly requested, keeping the immediate context lean.
Exposing one MCP tool per REST endpoint creates manifest bloat. A typical enterprise API with 80 endpoints generates 80 tool definitions, consuming thousands of tokens in the system prompt. Consolidate these into a single tool that accepts an operation discriminator.
// src/router/operation-dispatcher.ts
import { z } from 'zod';
import type { MCPToolDefinition } from '../types';
const OperationSchema = z.enum(['get', 'create', 'update', 'transition', 'search']);
export const buildConsolidatedTool = (): MCPToolDefinition => {
return {
name: 'enterprise_api.execute',
description: 'Execute a validated operation against the target system. Use "get" for retrieval, "create" for new records, "transition" for state changes, and "search" for filtered queries.',
inputSchema: {
type: 'object',
properties: {
operation: { type: 'string', enum: OperationSchema.options },
identifier: { type: 'string', description: 'Record key or ID' },
payload: { type: 'object', description: 'Operation-specific data' },
filters: { type: 'object', description: 'Server-side query parameters' },
full_context: { type: 'boolean', default: false, description: 'Return raw payload instead of projection' }
},
required: ['operation']
},
handler: async (args: z.infer<typeof OperationSchema>) => {
const validated = OperationSchema.parse(args.operation);
return dispatchOperation(validated, args);
}
};
};
Architecture Rationale: A single tool definition reduces manifest overhead by ~75%. The full_context flag acts as an escape hatch for edge cases where the model genuinely requires raw data. Server-side filtering via the filters property prevents the LLM from attempting pagination or client-side array manipulation, which is both unreliable and token-expensive.
3. Code-API Bridge (Shell Execution Pattern)
For agents with shell access, the most aggressive optimization is to bypass MCP tool definitions entirely. Expose a single tool that returns a CLI execution path and argument template. The agent runs the command locally, receives a trimmed JSON summary, and optionally dereferences a full payload from disk.
// src/bridge/shell-executor.ts
import { execSync } from 'child_process';
import type { BridgeResponse } from '../types';
export class ShellBridge {
constructor(private readonly cliPath: string) {}
async execute(args: Record<string, string>): Promise<BridgeResponse> {
const argString = Object.entries(args)
.map(([k, v]) => `--${k}=${v}`)
.join(' ');
const command = `node ${this.cliPath} execute ${argString}`;
const output = execSync(command, { encoding: 'utf-8' });
const lines = output.trim().split('\n');
const summary = JSON.parse(lines[0]);
const ref = lines[1]?.startsWith('ref:') ? lines[1].slice(4) : null;
return { summary, fullRef: ref, exitCode: 0 };
}
}
Architecture Rationale: This pattern reduces the MCP manifest to a single tool definition regardless of API complexity. Execution happens in a controlled subprocess, isolating network calls and retries from the agent's runtime. The stdout/stderr separation ensures the model only sees structured output, while disk references preserve auditability.
Pitfall Guide
1. Denylist Trimming
Explanation: Removing known noisy fields (delete result.iconUrl) creates brittle contracts. When the upstream API adds a new metadata field, it silently passes through, bloating the response.
Fix: Switch to allowlist projections. Explicitly declare required fields. Unknown fields are dropped by default, guaranteeing stable token consumption.
Explanation: Asking the model to iterate through arrays, parse cursors, or filter JSON client-side consumes excessive tokens and produces inconsistent results. Language models are not reliable data processors.
Fix: Push filtering and pagination to the server. Accept query parameters in the tool schema and return only the requested slice. Use deterministic cursors or offset limits.
Explanation: Raw responses often include self URLs, schema hints, expand directives, and nested status objects. These hold no operational value for the agent but consume context window space.
Fix: Implement a strict output sanitizer that strips HTTP-specific metadata before serialization. Only expose business-logic fields and explicit references.
Explanation: Mapping one tool per endpoint creates linear token growth. A 100-endpoint API generates a ~12KB manifest, paid on every conversation initialization.
Fix: Consolidate operations under a single action-discriminated tool. Use Zod or equivalent validation to enforce per-operation contracts without inflating the MCP definition.
5. Ignoring Context Window Budgeting
Explanation: Developers rarely calculate token costs per operation. Without budgeting, multi-step workflows exhaust context limits, causing silent truncation or degraded reasoning.
Fix: Implement token accounting per tool call. Log payload sizes, track cumulative context usage, and enforce hard limits on projection depth. Alert when thresholds approach.
6. Synchronous Large Payload Fetching
Explanation: Blocking the agent while streaming multi-megabyte responses ties up inference threads and increases latency.
Fix: Use async streaming with disk materialization. Write full payloads to a content-addressed store, return a lightweight reference, and allow the agent to fetch asynchronously when needed.
7. Unversioned Schema Evolution
Explanation: Upstream APIs change. Without versioning, projections break silently, and agents receive malformed data.
Fix: Embed API version headers in requests. Maintain projection schemas per version. Fail fast with explicit error codes when version mismatches occur.
Production Bundle
Action Checklist
Decision Matrix
| Scenario | Recommended Approach | Why | Cost Impact |
|---|
| Simple CRUD workflows (<10 endpoints) | Consolidated Dispatcher | Low manifest overhead, easy to maintain, sufficient context savings | ~1.5Γ reduction in tool definition tokens |
| Complex enterprise APIs (50+ endpoints) | Action-Discriminated Router + Projections | Prevents manifest bloat, enforces strict contracts, scales linearly | ~17.5Γ reduction in per-call payload tokens |
| Shell-capable agents (Claude Code, Cursor, Aider) | Code-API Bridge | Bypasses MCP manifest entirely, offloads execution, near-zero context cost | ~99Γ reduction in manifest overhead |
| High-frequency polling / monitoring | Async Streaming + Disk References | Prevents blocking, materializes full data only on demand, preserves audit trail | Reduces inference latency by 40β60% |
| Strict compliance / audit requirements | Versioned Projections + Content Hashing | Guarantees data integrity, enables deterministic replay, meets regulatory standards | Adds ~2β5% storage overhead, zero token penalty |
Configuration Template
// src/config/mcp-manifest.ts
import { buildConsolidatedTool } from '../router/operation-dispatcher';
import { IssueProjector } from '../projections/issue-projection';
import type { MCPManifest } from '../types';
export const generateManifest = (): MCPManifest => {
const projector = new IssueProjector();
return {
server: {
name: 'optimized-enterprise-gateway',
version: '2.1.0',
capabilities: ['tools', 'references']
},
tools: [
buildConsolidatedTool()
],
projections: {
issue: projector.project.bind(projector)
},
storage: {
backend: 'local-disk',
basePath: './cache/refs',
retention: '7d',
compression: 'gzip'
},
limits: {
maxPayloadKB: 50,
maxContextTokens: 128000,
retryAttempts: 3,
retryBackoff: 'exponential'
}
};
};
Quick Start Guide
- Initialize the projection layer: Create a
FieldProjector class for each domain object. Define allowed fields explicitly and generate content-addressed references for full payloads.
- Register the consolidated router: Replace individual endpoint tools with a single
execute tool. Map operations to an enum, attach Zod validation, and wire the handler to your projection layer.
- Configure disk references: Set up a content-addressed storage backend. Ensure full API responses are written asynchronously and only referenced in tool outputs.
- Deploy and benchmark: Run a test suite against your target API. Measure per-call payload sizes, tool definition overhead, and cumulative context usage. Validate that projections remain stable across API version updates.
- Enable shell bridging (optional): For agents with terminal access, expose a single CLI execution tool. Package the dispatcher as a standalone binary, configure stdout formatting, and verify reference resolution.