Field projection layer for MCP tools. Reduce LLM context window usage by letting agents request only the fields they need.
MCP tools often return large JSON responses. When an agent only needs a few fields, the full response wastes precious context window tokens.
Without mcp-distill (1,157 tokens):
{"id": "123", "name": "Item", "description": "...(2000 chars)...", "metadata": {...}, "content": "...(10000 chars)..."}
With mcp-distill (23 tokens):
{"id": "123", "name": "Item"}
pip install git+https://github.com/shoegazerstella/mcp-distill.gitOr with uv:
uv add git+https://github.com/shoegazerstella/mcp-distill.gitfrom fastmcp import FastMCP
from mcp_distill import projectable
mcp = FastMCP("my-server")
@mcp.tool
@projectable(fields=["id", "name", "metadata.created_by"])
def get_resource(resource_id: str) -> dict:
"""Fetch a resource by ID."""
return {
"id": resource_id,
"name": "Example Resource",
"description": "A" * 2000, # Large field - not advertised
"metadata": {
"created_by": "admin",
"huge_audit_log": [...], # Large field - not advertised
},
"content": "B" * 10000, # Large field - not advertised
}The fields parameter in @projectable() tells the agent what fields it can request:
@projectable(fields=["id", "name", "metadata.created_by"])Tool: get_resource
Description: Fetch a resource by ID.
Projectable fields: id, name, metadata.created_by
Parameters:
- resource_id: string (required)
- _fields: array of strings (optional)
# Agent needs just the ID and name
get_resource(resource_id="123", _fields=["id", "name"])
# Returns: {"id": "123", "name": "Example Resource"}
# Agent needs everything (omits _fields)
get_resource(resource_id="123")
# Returns: full response| Pattern | Description | Example |
|---|---|---|
field |
Top-level field | "id", "name" |
a.b.c |
Nested field (dot notation) | "metadata.created_by" |
items.* |
All fields in object | "config.*" |
*.field |
Field from all top-level objects | "*.id" |
items.*.field |
Field from each item in array/dict | "results.*.name" |
from mcp_distill import project
data = {
"id": 1,
"name": "Item",
"nested": {"a": 1, "b": 2},
"large_blob": "x" * 10000,
}
result = project(data, ["id", "name", "nested.a"])
# {"id": 1, "name": "Item", "nested": {"a": 1}}from mcp_distill import Projector
slim = Projector(["id", "name", "status"])
for item in large_dataset:
yield slim.apply(item)from mcp_distill import projectable
@projectable(fields=["id", "name", "email"])
def fetch_user(user_id: str) -> dict:
return db.get_user(user_id)
# With projection
fetch_user("123", _fields=["id", "name"])
# Without projection (full response)
fetch_user("123")@projectable(
fields=["id", "name", ...], # Available fields (shown to agent)
field_param="_fields", # Parameter name (default: "_fields")
field_description="...", # Custom parameter description
)project(data: Any, fields: list[str] | None) -> Anyprojector = Projector(["id", "name"])
result = projector.apply(data)git clone https://github.com/shoegazerstella/mcp-distill
cd mcp-distill
uv sync --dev
uv run pytestContributions are welcome! Please feel free to submit a Pull Request.
MIT