Skip to main content
Glama
crzyc0d3r

mcp-context-engineering

by crzyc0d3r
README.md
# mcp-context-engineering

A small, runnable project that demonstrates **context engineering for MCP
servers**: keeping a Model Context Protocol server's footprint in the model's
context window small, so agents are cheaper and more accurate.

## The problem

When an MCP client (Claude Desktop, Cursor, an SDK app) connects to an MCP
server, it pulls **every** advertised tool definition - name, description and
full input schema - into the model's context. A server with 30-60+ tools can
burn well over 10k tokens on definitions before the agent does anything. That
causes two problems:

- **Wasted tokens.** You pay for tool definitions the agent will never call.
- **Reduced accuracy.** The model gets distracted by irrelevant tools and is
  more likely to pick the wrong one or hallucinate parameters.

## The two techniques

This project implements both halves of the fix on a catalog of 33 mock
"web-data" tools (Amazon, LinkedIn, TikTok, GitHub, Zillow, browser
automation, batch scraping, ...) organised into logical groups.

1. **Scope the tools you advertise.** Load only the capabilities an agent
   needs - either by whole *group* (`GROUPS=social`) or by *hand-picking*
   individual tools (`TOOLS=web_data_amazon_product,...`). Only those
   definitions ever reach the context.
2. **Optimise the output those tools return.** Strip token-wasting Markdown
   (bold/italic, image syntax, heading markers, code fences, link URLs) from
   scraped pages before they enter the context, keeping every word the model
   actually reads.

### Measured impact (from the bundled offline report)

Full catalog = 33 tools ≈ **4,556 tokens** of definitions if loaded un-scoped.

| Configuration                          | Tools | Def. tokens | Saved vs. all |
|----------------------------------------|:-----:|:-----------:|:-------------:|
| default (base tools only)              |   3   |     506     |    **89%**    |
| `GROUPS=ecommerce`                     |   9   |    1,318    |    **71%**    |
| `GROUPS=social`                        |  11   |    1,566    |    **66%**    |
| `GROUPS=social,business`               |  14   |    1,973    |    **57%**    |
| `TOOLS=` amazon,ebay,google_shopping   |   3   |     416     |    **91%**    |
| `GROUPS=research` + 1 custom tool      |   6   |     917     |    **80%**    |
| `PRO_MODE=true` (load everything)      |  33   |    4,556    |      0%       |

Strip-markdown on a scraped page: **243 → 149 tokens (~39% fewer).**

Numbers use a built-in heuristic token estimator; pass `--tiktoken` to the
report for exact counts if `tiktoken` is installed. The point is the *ratios*,
which are stable.

## The pattern in one sentence

Scope the tools you load, trim the output they return, and let the MCP server
handle the hard parts.

## Code map

```
mcp-context-engineering/
├── src/mcp_context_engineering/
│   ├── __init__.py          # Public API re-exports + version.
│   ├── tool_groups.py       # Source of truth for groups: BASE_TOOLS + 8 logical
│   │                        #   groups (ecommerce, social, business, research,
│   │                        #   finance, app_stores, browser, advanced_scraping)
│   │                        #   and helpers (all_tool_names, total_tool_count).
│   ├── tool_catalog.py      # Full catalog of 33 ToolSpecs: name, description,
│   │                        #   JSON input schema, and an OFFLINE mock handler
│   │                        #   each. Also MARKDOWN_TOOLS (which outputs to strip)
│   │                        #   and a SAMPLE_MARKDOWN_PAGE for the demo.
│   ├── context_config.py    # The scoping brain. Reads PRO_MODE / GROUPS / TOOLS,
│   │                        #   resolves the exact tool set (resolve_context),
│   │                        #   and defines named PRESETS.
│   ├── strip_markdown.py    # Dependency-free output optimiser: strips Markdown
│   │                        #   formatting, keeps words + code, links optional.
│   ├── token_utils.py       # Lightweight offline token estimator + tool-def
│   │                        #   token counting (tiktoken optional).
│   └── server.py            # The MCP server (official SDK low-level Server,
│   │                        #   stdio). Advertises only scoped tools; strips
│   │                        #   Markdown output. build_server() for tests.
├── scripts/
│   ├── run_server.py        # Launch the server over stdio (what a client runs).
│   └── token_report.py      # Offline demo: prints the savings tables above.
├── examples/
│   ├── claude_desktop_social_agent.json   # config: one group
│   ├── claude_desktop_price_monitor.json  # config: hand-picked tools
│   └── claude_desktop_pro_mode.json       # config: everything (baseline)
├── tests/
│   └── test_context_engineering.py        # 23 offline tests (unittest)
├── requirements.txt         # Just the official `mcp` SDK (tiktoken optional).
├── .env.example             # All config vars, documented.
└── .gitignore
```

### How the pieces fit

`tool_groups.py` defines which tool *names* belong to which group.
`tool_catalog.py` gives each name a full definition (description + schema) and a
mock handler. `context_config.py` reads the environment and decides the exact
subset of names to expose. `server.py` asks `context_config` for that subset,
advertises only those definitions via `tools/list`, and - when a
`MARKDOWN_TOOLS` tool is called - runs its output through `strip_markdown.py`
before returning it. `token_utils.py` powers the offline `token_report.py`,
which quantifies both wins without touching the network.

## Data flow

```mermaid
flowchart TD
    subgraph Config["Configuration (env vars)"]
        E["PRO_MODE / GROUPS / TOOLS<br/>STRIP_MARKDOWN"]
    end

    E --> RC["context_config.resolve_context()"]
    TG["tool_groups.py<br/>(group -> tool names)"] --> RC
    RC -->|"scoped list of tool names"| SRV["server.py (MCP Server)"]
    TC["tool_catalog.py<br/>(name -> description, schema, handler)"] --> SRV

    subgraph MCP["MCP session (stdio)"]
        CLIENT["MCP client / LLM agent"]
        SRV
    end

    SRV -->|"tools/list: ONLY scoped definitions"| CLIENT
    CLIENT -->|"tools/call(name, args)"| SRV
    SRV -->|"handler() output"| STRIP["strip_markdown.py<br/>(markdown tools only)"]
    STRIP -->|"trimmed text"| CLIENT

    RC -.offline.-> REPORT["scripts/token_report.py"]
    TC -.offline.-> REPORT
    TU["token_utils.py"] -.-> REPORT
    REPORT -.-> OUT["savings tables"]
```

## Quick start

```bash
# 1. (optional) create a virtualenv
python -m venv .venv && source .venv/bin/activate    # Windows: .venv\Scripts\activate

# 2. install the one dependency
pip install -r requirements.txt

# 3. see the token savings - fully offline, no key, no network
python scripts/token_report.py
python scripts/token_report.py --json      # machine-readable

# 4. run the tests
python -m unittest discover -s tests -v
```

## Running the MCP server

The server speaks MCP over stdio and is configured entirely through environment
variables:

```bash
# default: just the small base tool set
python scripts/run_server.py

# a focused social-media agent
GROUPS=social python scripts/run_server.py

# hand-pick exactly the tools a price monitor needs
TOOLS=web_data_amazon_product,web_data_ebay_product,web_data_google_shopping \
    python scripts/run_server.py

# the un-scoped baseline (loads everything)
PRO_MODE=true python scripts/run_server.py

# disable output trimming
STRIP_MARKDOWN=false GROUPS=social python scripts/run_server.py
```

Valid group ids: `ecommerce`, `social`, `business`, `research`, `finance`,
`app_stores`, `browser`, `advanced_scraping`. See `.env.example` for the full
list of variables.

### Wiring into an MCP client

Copy one of the files in `examples/` into your client's server config (for
Claude Desktop that is `claude_desktop_config.json`), replace `/ABSOLUTE/PATH`
with the path to your checkout, and restart the client. The three examples show
a scoped group, a hand-picked set, and the load-everything baseline.

## Notes on the tools

Every tool handler in this project returns **canned, offline sample data**.
There is no API key and no network access anywhere - the goal is to demonstrate
the context-engineering pattern, not to scrape live sites. To make it real, swap
the handlers in `tool_catalog.py` for calls to an actual web-data backend and
read its credentials from an environment variable (a placeholder,
`WEB_DATA_API_KEY`, is documented in `.env.example`).

## Built on / inspired by

- **Model Context Protocol Python SDK** - the official SDK this server uses:
  https://github.com/modelcontextprotocol/python-sdk
- **Protocol docs & spec:** https://modelcontextprotocol.io
- **Bright Data MCP server** - an open-source MCP server that popularised the
  tool-group scoping and strip-markdown output optimisation modelled here:
  https://github.com/brightdata/brightdata-mcp

## License

MIT (see `LICENSE` if present, or treat the sample code as MIT-licensed).