Skip to main content
Glama
README.md
# Context Shrink

![Python](https://img.shields.io/badge/Python-3.10%2B-blue)
![MCP](https://img.shields.io/badge/MCP-stdio-lightgrey)
![Client](https://img.shields.io/badge/client-OpenCode-green)
[![PyPI](https://img.shields.io/pypi/v/context-shrink-mcp)](https://pypi.org/project/context-shrink-mcp/)
![License](https://img.shields.io/badge/license-MIT-blue)

๐ŸŒ **English** ยท [Tรผrkรงe](./README.tr.md)

**Context Shrink** is a tiny MCP server that keeps requests to **any OpenAI-compatible endpoint** under the model's context limit. It measures the context before the call, then either **drops** the oldest messages or **compacts** them into a summary โ€” instead of letting the server reject the request. It works with, among others:

- oMLX
- llama.cpp (`llama-server`)
- LM Studio
- vLLM
- the OpenAI API
- any OpenAI-compatible gateway

See [Compatible backends](#-compatible-backends) for the full list.

> **Token counting:** uses `tiktoken` when it is installed (`cl100k_base` by default) for a close approximation, and falls back to a tokenizer-free estimate (character count รท 4) otherwise. Either way it is a guard rail, not an exact budget โ€” set `CONTEXT_SHRINK_TOKENIZER` (or the legacy `OMLX_SHRINK_*` / `OPENAI_SHRINK_*` names) to change the behaviour.

---

## ๐Ÿ”Œ Compatible backends

The server only needs an **OpenAI-compatible `/v1/chat/completions` endpoint** โ€”
local model servers, self-hosted GPU engines and OpenAI gateways all qualify.
Point it at the endpoint with `CONTEXT_SHRINK_BASE_URL` and `CONTEXT_SHRINK_MODEL`.
The wire protocol, headers and error shapes are summarised in
[`docs/reference/OPENAI-API-REFERENCE.md`](./docs/reference/OPENAI-API-REFERENCE.md).

### Quick config for the most common ones

| Backend | Base URL | Model value | Notes |
| :--- | :--- | :--- | :--- |
| oMLX | `http://127.0.0.1:8000/v1` | *(default)* | The original target |
| Ollama | `http://127.0.0.1:11434/v1` | e.g. `qwen2.5-coder` | No API key needed |
| llama.cpp (`llama-server`) | `http://127.0.0.1:8080/v1` | your GGUF name | CPU/GPU, lightweight |
| LM Studio | `http://127.0.0.1:1234/v1` | loaded model id | Works out of the box |
| vLLM | `http://127.0.0.1:8000/v1` | served model id | Production-grade, PagedAttention |
| Text Generation Inference (TGI) | `http://127.0.0.1:8080/v1` | served model id | HuggingFace, streaming |
| SGLang | `http://127.0.0.1:30000/v1` | served model id | High throughput |
| LocalAI | `http://127.0.0.1:8080/v1` | model name | Drop-in replacement |
| OpenAI / gateway | `https://api.openai.com/v1` | e.g. `gpt-4o-mini` | Set an API key |

Example with Ollama:

```bash
export CONTEXT_SHRINK_BASE_URL="http://127.0.0.1:11434/v1"
export CONTEXT_SHRINK_MODEL="qwen2.5-coder"
```

### โ˜๏ธ Remote / cloud OpenAI-compatible providers

Local is not the only option: every major provider exposes an OpenAI-compatible
`/v1/chat/completions` endpoint, so point the server at it and set
`CONTEXT_SHRINK_API_KEY`. All of these need a key or token.

- **OpenAI**
  - Base URL: https://api.openai.com/v1
  - Model: gpt-4o-mini
- **Azure OpenAI**
  - Base URL: https://{resource}.openai.azure.com/openai/deployments/{deployment}
  - Model: your deployment name
  - Uses the `api-key` header instead of `Authorization` โ€” see below
- **DeepSeek**
  - Base URL: https://api.deepseek.com/v1
  - Model: deepseek-chat
- **Groq**
  - Base URL: https://api.groq.com/openai/v1
  - Model: llama-3.3-70b-versatile
  - Very fast
- **Together AI**
  - Base URL: https://api.together.xyz/v1
  - Model: meta-llama/Llama-3.3-70B-Instruct-Turbo
- **Fireworks AI**
  - Base URL: https://api.fireworks.ai/inference/v1
  - Model: accounts/fireworks/models/llama-v3p3-70b-instruct
- **Mistral API**
  - Base URL: https://api.mistral.ai/v1
  - Model: mistral-large-latest
- **OpenRouter**
  - Base URL: https://openrouter.ai/api/v1
  - Model: any model slug
  - Routes to 200+ models
- **HuggingFace Inference Endpoints**
  - Base URL: https://{your-endpoint}/v1
  - Model: your model
  - Private endpoint
- **Cloudflare Workers AI**
  - Base URL: https://api.cloudflare.com/client/v4/accounts/{account-id}/ai/v1
  - Model: @cf/meta/llama-3.3-70b-instruct-fp8

Azure OpenAI does not use a bearer `Authorization` header; pass its secret as a
custom header:

```bash
export CONTEXT_SHRINK_BASE_URL="https://myresource.openai.azure.com/openai/deployments/my-deployment"
export CONTEXT_SHRINK_MODEL="my-deployment"
export CONTEXT_SHRINK_EXTRA_HEADERS='{"api-key": "azure-secret"}'
```

Additional static headers for any gateway are accepted through
`CONTEXT_SHRINK_EXTRA_HEADERS` as a JSON object (for example an org id or a
custom routing header).

### Full catalogue

Anything below can serve as the summarisation endpoint; unlisted
OpenAI-compatible endpoints work too.

- **Local model servers:**
  - Ollama
  - llama.cpp (`llama-server`)
  - LM Studio
  - Jan
  - GPT4All (Python API)
  - llamafile (`--server`)
  - KoboldCpp
  - Oobabooga Text Generation WebUI (OpenAI extension)
  - MLC LLM
  - Petals (distributed)
- **GPU / production engines:**
  - vLLM
  - Aphrodite Engine (vLLM fork)
  - Text Generation Inference (TGI)
  - SGLang
  - NVIDIA NIM / TensorRT-LLM
  - ExLlamaV2 + TabbyAPI
  - Xinference
  - OpenLLM (BentoML)
  - FastChat (LMSYS)
  - LocalAI
- **Proxies & routers:**
  - LiteLLM (100+ providers)
  - OpenRouter
  - Portkey Gateway
  - One API / New API
  - community Ollama-to-OpenAI proxies
- **Libraries / custom stacks:**
  - `llama-cpp-python` (`llama_cpp.server`)
  - Transformers + FastAPI
  - AnythingLLM / Msty

---

## ๐Ÿ“– Overview

oMLX profiles for this class of models run **32K context** (`max_context_window: 32768`). That is a deliberate choice: with a 4-bit KV cache it keeps the runtime footprint at roughly 17โ€“20 GB per model and keeps prefill short on Apple Silicon. Raising the limit is not free โ€” every extra token of context is paid for in memory and prefill time.

The cost appears in a long session. Three things keep growing until the total
crosses the ceiling:

- the system prompt
- the conversation history
- the tool results

Once it crosses, the server refuses the whole request:

```text
Prompt is too long: 39514 tokens > 32768 maximum context length
```

At that point the agent stops. Nothing was wrong with the question โ€” the prompt was simply 6.7K tokens too big. The usual workarounds are all unsatisfying:

| Workaround | Why it falls short |
| :--- | :--- |
| Raise `max_context_window` | Moves the wall, does not remove it; costs memory and prefill speed on the same machine |
| Truncate by hand | Manual, easy to forget, throws away whole sessions |
| Start a new session | Loses the context that made the task work in the first place |

So this server takes the cheap path: **keep 32K, send a smaller prompt.**

Read the full walkthrough (why 32K, the three compaction modes, the environment configuration, and the known limitations):

**[English โ†’](./README.md)** ยท **[Tรผrkรงe โ†’](./README.tr.md)**

---

## โœ… Recommendation

Use it if you run oMLX or another local OpenAI-compatible server with a fixed context window and you have hit `Prompt is too long` at least once. It is a single Python file and needs no GPU. `estimate_context` and `trim_context` are pure arithmetic โ€” they cost nothing at inference time. `compact_context` only calls the local model when you ask it to summarise (or you can hand it a summary yourself and it calls nothing at all).

You probably do not need it if your context window is large enough for your sessions, or if your client already compacts history on its own.

---

## ๐Ÿ› ๏ธ Tech Stack & Architecture

- **Language & Runtime:** Python 3.10+, standard library only
- **Protocol:** Model Context Protocol (MCP) over stdio transport
- **Client:** OpenCode (Code Mode groups tools under `tools.Context_Shrink.*`)
- **Compaction Engine:** Chunked summarisation via the local OpenAI-compatible endpoint
- **Token Counting:** optional `tiktoken` (default `cl100k_base`) with a character heuristic fallback โ€” no hard dependency
- **Dependencies:** `mcp` (MCP SDK) required; `tiktoken` optional โ€” no GPU
- **Target Server:** any OpenAI-compatible server โ€” see [Compatible backends](#-compatible-backends)
- **Defaults:** `max_context_tokens = 32768`, `keep_last = 8`, `threshold = 0.8`

---

## โœจ Features

- ๐Ÿงฎ **Accurate-enough estimate** โ€” `estimate_context` returns the estimated token count, the remaining budget and a `should_trim` flag
- โœ‚๏ธ **Drop oldest messages** โ€” `trim_context` returns the message list with the oldest messages removed, keeping the most recent `keep_last`
- ๐Ÿง  **Compact instead of drop** โ€” `compact_context` replaces the oldest messages with a **summary**, keeping leading system messages and the most recent `keep_last` verbatim
- ๐Ÿ“ **Three compaction modes** โ€” summary provided, model-generated, or plan-only (`needs_summary: true`, no message content echoed back)
- ๐ŸŒ **Optional real tokenizer** โ€” uses `tiktoken` when installed, otherwise a tokenizer-free estimate (chars รท 4); `CONTEXT_SHRINK_TOKENIZER` controls the choice
- ๐Ÿ”Œ **Plain MCP** โ€” any MCP client can use the server, not just OpenCode
- ๐Ÿงฉ **Chunked summarisation** โ€” long histories are summarised in pieces so the local model does not overflow while summarising
- ๐Ÿ”’ **No telemetry, no network calls** except the optional local summariser request

---

## ๐Ÿ“ธ Screenshots

_(No UI โ€” this is a headless MCP server. Tool output is JSON on stdio.)_

---

## ๐Ÿ—๏ธ Architecture

```mermaid
flowchart LR
    classDef client fill:#e3f2fd,stroke:#1976d2,color:#0d47a1
    classDef decision fill:#fff3e0,stroke:#ef6c00,color:#e65100
    classDef action fill:#e8f5e9,stroke:#2e7d32,color:#1b5e20
    classDef model fill:#fce4ec,stroke:#c2185b,color:#880e4f

    Client["๐Ÿง‘โ€๐Ÿ’ป Your coding agent"]:::client
    Client -->|"before every request"| Fits{"Does it fit the<br/>context window?"}:::decision

    Fits -->|"yes"| Model
    Fits -->|"no"| Choice{"What should happen?"}:::decision

    Choice -->|"measure only"| Measure["๐Ÿ“Š Report the size<br/>nothing is changed"]:::action
    Choice -->|"drop oldest"| Trim["โœ‚๏ธ Shorten the history<br/>newest messages stay"]:::action
    Choice -->|"summarise"| Compact["๐Ÿ—œ๏ธ Summarise the oldest part<br/>recent messages stay word-for-word"]:::action

    Trim --> Model
    Compact --> Model
    Model["๐Ÿค– Local model<br/>(oMLX)"]:::model
```


---

### `compact_context` in three modes

1. **Summary provided** โ€” pass `summary="..."` and no model call is made. Use
   this when the agent (or the user) already wrote the summary.
2. **Model-generated** โ€” pass `model="..."` (and optionally `base_url`), or set
   the environment variables below, and the server summarises the oldest
   messages for you. Long histories are summarised in chunks so the local model
   does not overflow while summarising.
3. **Plan only** โ€” with neither, the call returns `needs_summary: true`, the
   exact `summary_prompt` to use, and the token counts. **No message content is
   echoed back**, so the result stays small; call again with `summary=<text>`.
   Pass `model=""` to force this step even when the environment sets a model.

`compact_context` returns the assembled `messages` list only in modes 1 and 2,
and leaves the list untouched (`compacted: false`) when it is already within
`threshold` unless you pass `force: true`.

> **No model endpoint required:** modes 1 and 3 work without any
> `CONTEXT_SHRINK_*` variable. The environment variables only enable mode 2.

---

## ๐Ÿš€ Installation & Quick Start

### Install (one line)

The package is on PyPI: [`context-shrink-mcp`](https://pypi.org/project/context-shrink-mcp/).

```bash
pip install context-shrink-mcp     # the package + the `context-shrink` command
```

Or skip the install and let `uvx` fetch and run it โ€” no checkout, no manual copy
(install [`uv`](https://docs.astral.sh/uv/) once). Register the server with
OpenCode in one line:

```bash
# from PyPI
opencode mcp add context-shrink --global -- uvx --from context-shrink-mcp context-shrink

# or straight from GitHub (no package index needed)
opencode mcp add context-shrink --global -- uvx --from git+https://github.com/mahirfatih/context-shrink-mcp context-shrink
```

`uvx` resolves the `mcp` dependency in an isolated environment and runs the
server over stdio. Pin a version with `context-shrink-mcp@1.0.0` (PyPI) or
`โ€ฆcontext-shrink-mcp@v1.0.0` (GitHub).

Enable model mode on the same command:

```bash
opencode mcp add context-shrink --global \
  --env CONTEXT_SHRINK_BASE_URL=http://127.0.0.1:8000/v1 \
  --env CONTEXT_SHRINK_MODEL=Qwen3.6-35B-A3B-4bit \
  --env CONTEXT_SHRINK_API_KEY=omlx-... \
  -- uvx --from context-shrink-mcp context-shrink
```

> Without `--global`, `opencode mcp add` writes to the project config
> (`./opencode.jsonc`) instead of `~/.config/opencode/opencode.jsonc`.

The numbered steps below are the repository-checkout path โ€” use them to run the
file directly or from a dedicated virtualenv.

### 1. Prerequisites

| Requirement | Version / Notes |
| :--- | :--- |
| **Python** | 3.10+ (tested against `/opt/homebrew/bin/python3`) |
| **MCP SDK** | `pip install "mcp>=2.0"` |
| **Tokenizer** *(optional)* | `pip install tiktoken` for real token counting; falls back to chars รท 4 otherwise |
| **Client** | OpenCode (or any MCP client) |
| **Model server** | oMLX or another local OpenAI-compatible server (optional, for model mode) |

### 2. Install the SDK

```bash
pip install "mcp>=2.0"
```

### 3. Copy the server

Copy `context_shrink.py` into a directory of your choice (for example `~/.config/opencode/mcp/`). A repository checkout also ships a four-line `context-shrink.py` shim that runs the same module, kept for existing setups that point at the hyphenated name.

> **Note:** The `python3` in the `command` array may not be the one you expect โ€”
> depending on your setup it could be `/opt/homebrew/bin/python3`, `/usr/bin/python3`,
> or somewhere else. Verify the path with `which python3` (or `which python`)
> and use the absolute path in the config instead of the bare `python3` if needed.

### 4. Register with OpenCode

Add the server to your OpenCode config (`~/.config/opencode/opencode.jsonc`):

```jsonc
{
  "mcp": {
    "servers": {
      "Context Shrink": {
        "type": "local",
        "command": [
          "python3",
          "/absolute/path/to/context_shrink.py"
        ],
        "timeout": 180000
      }
    }
  }
}
```

Config changes apply without a restart โ€” OpenCode watches config files and reloads MCP servers after a save. To force a full reload, run `/reload`.

### 5. (Optional) Enable model mode

Set the environment variables under `environment` in the MCP entry so `compact_context` can summarise with the local model. The values below are the ones this project was developed with โ€” a local **oMLX** server on Apple Silicon, serving `Qwen3.6-35B-A3B-4bit`:

```jsonc
"environment": {
  "CONTEXT_SHRINK_MODEL": "Qwen3.6-35B-A3B-4bit",
  "CONTEXT_SHRINK_BASE_URL": "http://127.0.0.1:8000/v1",
  "CONTEXT_SHRINK_API_KEY": "omlx-...",
  "CONTEXT_SHRINK_TIMEOUT": "180",
  "CONTEXT_SHRINK_TOKENIZER": "auto"
}
```

### A real setup (this project's development machine)

This server was written for, and is used daily on, an Apple Silicon Mac driving a
local **oMLX** server โ€” the 32K context profile it targets is documented in
[`mahirfatih/omlx-m1-max-benchmark`](https://github.com/mahirfatih/omlx-m1-max-benchmark).
The OpenCode entry used there runs the server from a dedicated virtualenv and
points the summariser at the local model:

```jsonc
{
  "mcp": {
    "servers": {
      "Context Shrink": {
        "type": "local",
        "command": [
          "/absolute/path/to/context-shrink-venv/bin/python",
          "/absolute/path/to/context_shrink.py"
        ],
        "environment": {
          "CONTEXT_SHRINK_MODEL": "Qwen3.6-35B-A3B-4bit",
          "CONTEXT_SHRINK_BASE_URL": "http://127.0.0.1:8000/v1",
          "CONTEXT_SHRINK_API_KEY": "omlx-...",
          "CONTEXT_SHRINK_TOKENIZER": "auto"
        },
        "timeout": 180000
      }
    }
  }
}
```

> The `CONTEXT_SHRINK_API_KEY` is the token the local oMLX server expects; it is
> redacted here. Replace the paths and the key with your own.

### Configuration

`compact_context` reads its endpoint and model from the environment when the
arguments are not given.

The canonical names are `CONTEXT_SHRINK_*`. The legacy `OMLX_SHRINK_*` names and
the vendor-neutral `OPENAI_SHRINK_*` aliases are identical in meaning and are
used only when every higher-precedence name is unset. Precedence:

```text
CONTEXT_SHRINK_*  >  OMLX_SHRINK_* (legacy)  >  OPENAI_SHRINK_*
```

| Variable | Default | Meaning |
| :--- | :--- | :--- |
| `CONTEXT_SHRINK_BASE_URL` / `OMLX_SHRINK_BASE_URL` / `OPENAI_SHRINK_BASE_URL` | `http://127.0.0.1:8000/v1` | OpenAI-compatible base URL (a trailing `/chat/completions` is also accepted) |
| `CONTEXT_SHRINK_MODEL` / `OMLX_SHRINK_MODEL` / `OPENAI_SHRINK_MODEL` | *(empty)* | Model name to summarise with; empty disables the model mode |
| `CONTEXT_SHRINK_API_KEY` / `OMLX_SHRINK_API_KEY` / `OPENAI_SHRINK_API_KEY` | *(empty)* | Bearer token, if the server needs one |
| `CONTEXT_SHRINK_TIMEOUT` / `OMLX_SHRINK_TIMEOUT` / `OPENAI_SHRINK_TIMEOUT` | `180` | Summariser request timeout, seconds (invalid values fall back to 180) |
| `CONTEXT_SHRINK_TOKENIZER` / `OMLX_SHRINK_TOKENIZER` / `OPENAI_SHRINK_TOKENIZER` | `auto` | Token counter: `auto` (tiktoken `cl100k_base` if installed, else heuristic), a tiktoken encoding name, or `heuristic`/`off` to always use chars รท 4 |
| `CONTEXT_SHRINK_MAX_CONTEXT_TOKENS` / `OMLX_SHRINK_MAX_CONTEXT_TOKENS` / `OPENAI_SHRINK_MAX_CONTEXT_TOKENS` | `32768` | Default context budget for every tool call |
| `CONTEXT_SHRINK_EXTRA_HEADERS` / `OMLX_SHRINK_EXTRA_HEADERS` / `OPENAI_SHRINK_EXTRA_HEADERS` | *(empty)* | Extra HTTP headers as a JSON object, for gateways such as Azure OpenAI (`{"api-key": "..."}`) |
| `CONTEXT_SHRINK_LOG_LEVEL` / `OMLX_SHRINK_LOG_LEVEL` / `OPENAI_SHRINK_LOG_LEVEL` | `INFO` | stderr log level (use `DEBUG` when troubleshooting); an invalid name falls back to `INFO` |

### Common pitfalls

- An **empty `CONTEXT_SHRINK_MODEL`** puts `compact_context` into plan-only mode:
  it returns a `needs_summary` plan instead of calling a model.
- A **too-small `max_context_tokens`** makes every call look over-budget; set it
  to the model's real context window (the default is `32768`).
- **`*_EXTRA_HEADERS` must be a JSON object** โ€” a string, array or malformed JSON
  is ignored with a warning.
- A **remote/cloud `CONTEXT_SHRINK_BASE_URL`** forwards your messages to that
  endpoint; only point it at a server you trust.
- Out-of-range `max_context_tokens` values are clamped to `1โ€ฆ1000000` so a typo
  cannot exhaust memory.

### 6. Use it

- Call `estimate_context` with the current message list.
- If `should_trim` is `true`, call `trim_context` and send the returned list, or
  `compact_context` to keep the gist instead of dropping it.
- An agent can do this automatically before every request when the estimate
  crosses the limit.

Example `estimate_context` call:

```json
{
  "messages": [
    { "role": "user", "content": "first message" },
    { "role": "assistant", "content": "first answer" },
    { "role": "user", "content": "second message" }
  ],
  "max_context_tokens": 32768
}
```

Example `trim_context` call:

```json
{
  "messages": [ "..." ],
  "keep_last": 8,
  "max_context_tokens": 32768
}
```

Compaction flow, model mode:

```json
{
  "messages": [ "..." ],
  "keep_last": 8,
  "model": "Qwen3.6-35B-A3B-4bit",
  "base_url": "http://127.0.0.1:8000/v1"
}
```

Compaction flow, agent mode (two calls):

1. `compact_context({ "messages": [...], "force": true })` โ†’ `needs_summary: true`
   plus the `summary_prompt`.
2. The agent writes the summary, then calls again with
   `{ "messages": [...], "force": true, "summary": "<that text>" }` โ†’ the
   compacted `messages` list.

### Tool parameters

| Tool | Parameter | Default | Meaning |
| :--- | :--- | :--- | :--- |
| *(all)* | `messages` | *(required)* | Conversation as `role` / `content` pairs |
| `estimate_context` | `max_context_tokens` | `32768` | Budget to compare the estimate against |
| `trim_context` | `keep_last` | `8` | Newest messages always kept |
| `trim_context` | `max_context_tokens` | `32768` | Budget to trim down to |
| `compact_context` | `keep_last` | `8` | Newest messages kept verbatim |
| `compact_context` | `max_context_tokens` | `32768` | Context budget |
| `compact_context` | `threshold` | `0.8` | Compact only above this fraction of the budget (unless `force`) |
| `compact_context` | `force` | `false` | Compact even when under `threshold` |
| `compact_context` | `summary` | *(none)* | Pre-written summary (mode 1) |
| `compact_context` | `model` | *(env)* | Model for a generated summary (mode 2); `""` forces plan-only |
| `compact_context` | `base_url` | *(env)* | Endpoint override for the summariser |
| `compact_context` | `api_key` | *(env)* | Bearer token override for the summariser |
| `compact_context` | `summary_budget_tokens` | `512` | Upper bound for the generated summary |
| `compact_context` | `summary_role` | `"system"` | Role of the message that carries the summary |

`estimate_context` also returns a `tokenizer` field naming the counter in use
(for example `tiktoken:cl100k_base` or `heuristic(chars/4)`).

### Minimal clients

[`examples/python-client.py`](./examples/python-client.py) and
[`examples/js-client.js`](./examples/js-client.js) are runnable clients: each
starts the server over stdio, lists its tools and calls `estimate_context`.

```bash
python examples/python-client.py                          # uses ./context_shrink.py
python examples/python-client.py /path/to/context_shrink.py

cd examples && npm install && node js-client.js           # same client in Node
```

Run the Python client with the same interpreter that has `mcp` installed; the
Node client needs Node 18+ and `npm install` once.

---

## ๐Ÿ“‚ Project Structure

```
context-shrink-mcp/
โ”œโ”€โ”€ context_shrink.py        # Single-file MCP server (the whole product)
โ”œโ”€โ”€ context-shrink.py        # Four-line shim that runs the module above
โ”œโ”€โ”€ pyproject.toml           # Packaging + the `context-shrink` console script
โ”œโ”€โ”€ README.md                # English quick tour
โ”œโ”€โ”€ README.tr.md             # Turkish quick tour
โ”œโ”€โ”€ CONTRIBUTING.md          # Setup, tests, commit conventions
โ”œโ”€โ”€ CHANGELOG.md             # Keep a Changelog release notes
โ”œโ”€โ”€ docs/                    # Documentation tree (see docs/README.md)
โ”‚   โ”œโ”€โ”€ README.md            # Doc map
โ”‚   โ”œโ”€โ”€ guides/MIGRATION.md  # Rename / migration notes
โ”‚   โ”œโ”€โ”€ guides/RELEASING.md  # Version, build and publish steps
โ”‚   โ”œโ”€โ”€ reference/OPENAI-API-REFERENCE.md  # Endpoint, headers, errors
โ”‚   โ””โ”€โ”€ testing/TESTING.md   # Suites, runner, live tests
โ”œโ”€โ”€ requirements.txt         # Runtime dependency (mcp)
โ”œโ”€โ”€ requirements-dev.txt     # Dev dependencies (mcp, pytest, ruff, tiktoken)
โ”œโ”€โ”€ pytest.ini               # pytest config (testpaths, python_files)
โ”œโ”€โ”€ .ruff.toml               # Ruff lint configuration
โ”œโ”€โ”€ .pre-commit-config.yaml  # Optional local git hooks
โ”œโ”€โ”€ .github/workflows/ci.yml # CI: lint + compile + tests
โ”œโ”€โ”€ test/                    # Test runner + suites
โ”‚   โ”œโ”€โ”€ test.sh              # Single test runner (ruff + pytest)
โ”‚   โ”œโ”€โ”€ README.md            # Test folder guide
โ”‚   โ”œโ”€โ”€ py/                  # Python suites
โ”‚   โ”‚   โ”œโ”€โ”€ context-shrink-tests.py       # Helpers, tokenizer, fixtures
โ”‚   โ”‚   โ”œโ”€โ”€ context-shrink-mcp-tests.py   # STDIO end-to-end round-trip
โ”‚   โ”‚   โ””โ”€โ”€ context-shrink-live-tests.py  # Live oMLX integration (opt-in)
โ”‚   โ””โ”€โ”€ js/                  # Node integration tests (MCP JS SDK)
โ”œโ”€โ”€ examples/                # Runnable examples
โ”‚   โ”œโ”€โ”€ python-client.py     # Minimal MCP client: list tools + call one
โ”‚   โ””โ”€โ”€ js-client.js         # The same client in Node (MCP JS SDK)
โ”œโ”€โ”€ LICENSE                  # MIT
โ””โ”€โ”€ .gitignore
```

---

## ๐Ÿงช Testing & CI

```bash
./test/test.sh              # install dev deps, then ruff + pytest
./test/test.sh --lint       # ruff only
./test/test.sh --no-install # skip the pip install step
./test/test.sh --only test/py/context-shrink-tests.py::test_compact_uses_provided_summary
```

- **Runner:** [`test/test.sh`](./test/test.sh) installs the dev dependencies, runs Ruff, then pytest. The interpreter is the project `.venv` when present, otherwise `python3`.
- **Suites:** [`test/py/context-shrink-tests.py`](./test/py/context-shrink-tests.py) covers the pure helpers (token estimation, trimming, compaction splitting and chunking), the tokenizer resolution logic and the env-var parsing โ€” no network. [`test/py/context-shrink-mcp-tests.py`](./test/py/context-shrink-mcp-tests.py) adds an end-to-end round-trip over the real STDIO transport (handshake โ†’ `tools/list` โ†’ `tools/call`).
- **Live tests (opt-in):** [`test/py/context-shrink-live-tests.py`](./test/py/context-shrink-live-tests.py) and the Node suite in [`test/js/`](./test/js) hit a real endpoint. They are skipped unless `CONTEXT_SHRINK_LIVE=1` is set and the endpoint answers, so the default run stays offline. The API key is read from `CONTEXT_SHRINK_API_KEY`, never stored in the repo.
- **Details:** [`docs/testing/TESTING.md`](./docs/testing/TESTING.md) has the full suite map, flags and live-test setup.
- **Node tests:** `cd test/js && npm install && npm test` runs the protocol tests through the official MCP JS SDK; the model test is gated by the same `CONTEXT_SHRINK_LIVE` flag.
- **CI:** [`.github/workflows/ci.yml`](./.github/workflows/ci.yml) runs Ruff, a `py_compile` check, and `pytest`. It is **manual only** โ€” trigger it with `workflow_dispatch` (Actions tab โ†’ CI โ†’ Run workflow).

Run the server by hand to exercise it through a client:

```bash
python3 context_shrink.py   # starts the stdio server; Ctrl-C to stop
```

---

## ๐Ÿ“š Documentation

- [README.md](./README.md) โ€” this file.
- [README.tr.md](./README.tr.md) โ€” Turkish translation.
- [context_shrink.py](./context_shrink.py) โ€” source of truth for tool schemas and behaviour.
- [pyproject.toml](./pyproject.toml) โ€” packaging and the `context-shrink` console script.
- [pypi.org/project/context-shrink-mcp](https://pypi.org/project/context-shrink-mcp/) โ€” the published package.
- [CONTRIBUTING.md](./CONTRIBUTING.md) โ€” development setup, tests, and commit conventions.
- [CHANGELOG.md](./CHANGELOG.md) โ€” release notes.
- [docs/README.md](./docs/README.md) โ€” the documentation map.
- [docs/reference/OPENAI-API-REFERENCE.md](./docs/reference/OPENAI-API-REFERENCE.md) โ€” the endpoint, headers and error shapes this server relies on.
- [docs/testing/TESTING.md](./docs/testing/TESTING.md) โ€” the suites, the runner and the opt-in live tests.
- [docs/guides/MIGRATION.md](./docs/guides/MIGRATION.md) โ€” renaming from oMLX Prompt Shrink.
- [docs/guides/RELEASING.md](./docs/guides/RELEASING.md) โ€” version, build and publish steps.
- [examples/python-client.py](./examples/python-client.py) โ€” a runnable MCP client that lists the tools and calls one.
- [examples/js-client.js](./examples/js-client.js) โ€” the same client in Node, built on the official MCP JS SDK.
- [MCP Python SDK](https://github.com/modelcontextprotocol/python-sdk) โ€” the transport and `MCPServer` API this file is built on.

---

## ๐Ÿ“ Notes

- Default context limit: `32768` tokens โ€” change it to match your profile.
- Trim policy: oldest messages first; the last `keep_last` messages are always kept.
- Compact policy: leading `system` messages are kept, the oldest of the rest are replaced by one `system` message holding the summary, and the last `keep_last` messages are kept verbatim.
- The tool surface is plain MCP, so any MCP client can use the server, not just OpenCode.

---

## ๐Ÿ” Migrating from oMLX Prompt Shrink

The project was renamed from **oMLX Prompt Shrink** to the vendor-neutral
**Context Shrink**; the tool names are unchanged and the old repository URL
redirects. The file path, tool namespace, repository and environment-variable
mapping is in [`docs/guides/MIGRATION.md`](./docs/guides/MIGRATION.md).

---

## ๐Ÿ”— Background

The 32K profile and the `Prompt is too long` failure this server addresses are documented in [`mahirfatih/omlx-m1-max-benchmark`](https://github.com/mahirfatih/omlx-m1-max-benchmark): `docs/reference/PROFILES.md` explains why the context window is set to 32K, and the README has the recommendation section for this server.

---

## ๐Ÿ” Permissions & Privacy

| Setting / Key | Purpose | Location |
| :--- | :--- | :--- |
| `CONTEXT_SHRINK_BASE_URL` | OpenAI-compatible base URL | environment / argument |
| `CONTEXT_SHRINK_MODEL` | Model used for summarisation | environment / argument |
| `CONTEXT_SHRINK_API_KEY` | Bearer token for the local server | environment / argument |
| `CONTEXT_SHRINK_TIMEOUT` | Summariser request timeout (seconds) | environment |
| `CONTEXT_SHRINK_TOKENIZER` | Token counter selection (`auto` / encoding / `heuristic`) | environment |

> **Privacy:** The server makes **no network requests** of its own except the optional summariser call to the local OpenAI-compatible endpoint you configure. No telemetry, no analytics, no data leaves the machine. All state is in-memory for the duration of a tool call.

**Version:** `1.0.0`

---

## ๐Ÿ“„ License

This project is licensed under the MIT License.

TDQS

A3.8/5.0

Scored across 3 tools

Disambiguation4/5

Each tool maps to a distinct stage of context management: estimate (measure), trim (drop oldest), compact (summarize oldest). Boundaries are mostly clear, though trim_context and compact_context both trigger when context exceeds a limit and differ only in how old messages are handled, which could cause slight hesitation in selection.

Naming Consistency5/5

All three tools follow a consistent verb_noun convention (estimate_context, trim_context, compact_context) with parallel suffixes. Predictable and easy to scan.

Tool Count5/5

Three tools is a tight, well-scoped set for a focused context-shrinking utility. Each tool corresponds to a necessary step in the workflow (measure, reduce, summarize), with no redundant tools.

Completeness4/5

The estimate-trim-compact lifecycle covers the core domain well. A minor gap exists: there is no way to inspect or report the resulting message list after trimming/compacting beyond the returned plan, but the surface is largely complete for the stated purpose.

Maintenance

ActivityMaintained
ResponsivenessNo recent community issues