mcp-distill

Field projection layer for MCP tools. Reduce context window usage by up to 95%.

Context Saved: ~95% Zero Dependencies
01//THE PROBLEM

Context Window Waste

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
{
  "id": "123",
  "name": "Item",
  "description": "...(2000 chars)...",
  "metadata": {...},
  "content": "...(10000 chars)..."
}

With mcp-distill

23
{
  "id": "123",
  "name": "Item"
}
-98% TOKENS
02//INSTALLATION

Get Started

pip install git+https://github.com/shoegazerstella/mcp-distill.git

Or with uv:

uv add git+https://github.com/shoegazerstella/mcp-distill.git
03//QUICK START

Basic Usage

Python
from 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
    }
04//HOW IT WORKS

Three Steps

1. Advertise available fields

The fields parameter tells the agent what fields it can request:

@projectable(fields=["id", "name", "metadata.created_by"])

2. Agent sees them in tool description

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)

3. Agent requests only what it needs

# Agent needs just 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
05//FIELD SYNTAX

Supported Patterns

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"
06//ADVANCED USAGE

Additional APIs

Standalone function

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}}

Reusable projector

from mcp_distill import Projector

slim = Projector(["id", "name", "status"])

for item in large_dataset:
    yield slim.apply(item)

Decorator on any function

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")
07//API REFERENCE

Core Functions

@projectable

@projectable(
    fields=["id", "name", ...],  # Available fields (shown to agent)
    field_param="_fields",       # Parameter name (default: "_fields")
    field_description="...",     # Custom parameter description
)

project(data, fields)

project(data: Any, fields: list[str] | None) -> Any

Projector

projector = Projector(["id", "name"])
result = projector.apply(data)