内容
# Token Saver MCP Java
An MCP server that lets Claude Code / Codex / Wibey (or any MCP-compliant coding agent) delegate simple, well-scoped tasks to your local LLM — code snippets, isolated refactors, documentation, code reviews, basic Q&A — so you spend premium tokens on coordination and architecture, not on work a local model can already do.
This is a complete rewrite in Java on [langchain4j](https://github.com/langchain4j/langchain4j) (local LLM client), [langgraph4j](https://github.com/langgraph4j/langgraph4j) (orchestration), and the official [MCP Java SDK](https://github.com/modelcontextprotocol/java-sdk) (stdio server).
Credit for the original idea and the Python/FastMCP prototype goes to [csabakecskemeti/cc_token_saver_mcp](https://github.com/csabakecskemeti/cc_token_saver_mcp) — this project builds on that concept with a graph-based classify/delegate/escalate flow, server-side file reading, timeouts, and actionable errors.
## How it works
The server exposes two MCP tools — `query_local_llm` and `query_local_llm_with_context` — that Claude Code is instructed to try first for simple subtasks. Every call runs through a small langgraph4j graph instead of just forwarding straight to the model:
```
START -> classify -> delegate -> END (task judged within the local model's competence)
\-> escalate -> END (task judged too complex; local LLM never called)
```
`classify` makes a cheap, low-temperature call to the local model to judge whether the task is actually simple enough for it. If not, `escalate` returns a structured `ESCALATE: ...` message telling Claude Code to handle the task itself — so a task that's too complex for the local model comes back as a clear signal, not a low-quality answer.
## Features
- **Server-side file reading.** Both tools accept an optional `file_paths` array. The server reads the files itself and passes their content straight to the local LLM — Claude Code passes a path instead of `Read()`-ing the file into its own context first. This is where the real token savings on large files come from.
- **Bounded waits.** Both tools accept an optional `timeout_seconds` (default from `LOCAL_LLM_TIMEOUT_SECONDS`), enforced with a hard timeout around the call so a stalled local backend can't hang a tool call indefinitely.
- **Actionable errors.** Backend failures (connection refused, unreachable host, timeout) come back as a clear message naming the configured URL and the next step — e.g. *"Local LLM backend unreachable at http://127.0.0.1:8000/v1. Check that it's running..."* — with `isError: true` set correctly, instead of a raw exception string.
- **Thinking-mode control.** Qwen3-style reasoning models can burn hundreds of tokens on a hidden chain-of-thought preamble before answering (measured: ~46s/228 tokens for a trivial "say hello" vs ~1s/1 token with it off). `LOCAL_LLM_ENABLE_THINKING` (default `false`) passes `chat_template_kwargs.enable_thinking` through to the backend. The internal classifier always runs with thinking off regardless of this setting, since a one-word verdict never needs it. No-op on models/backends that don't recognize the field.
## Requirements
- Java 17+
- Maven
- A local OpenAI-compatible LLM server (LM Studio, Ollama, vLLM, MLX, etc.)
## Build
```
mvn -q package
```
Produces a single runnable jar at `target/token-saver-mcp-java.jar`.
## Configuration
Create a `.env` file next to `pom.xml`:
```
# Local LLM Configuration
OPENAI_API_KEY=none
OPENAI_BASE_URL=http://127.0.0.1:8000/v1
LOCAL_MODEL_NAME=Qwen3.6-35B-A3B-8bit
LOCAL_LLM_TEMPERATURE=0.7
LOCAL_LLM_TOP_P=0.80
LOCAL_LLM_MAX_TOKENS=32768
LOCAL_LLM_TIMEOUT_SECONDS=60
LOCAL_LLM_ENABLE_THINKING=false
```
`.env` is resolved relative to the jar's own install directory, not the process's working directory, so it's found correctly even when `~/.claude.json` launches the jar from an arbitrary project folder. Real environment variables (e.g. set directly in the `~/.claude.json` server entry) take precedence over `.env`.
## Register with your coding agent
This is a standard stdio MCP server built on the official MCP Java SDK — nothing here is Claude-Code-specific, so any MCP-compliant client that can spawn a subprocess and speak JSON-RPC over stdin/stdout can use it.
### Claude Code
Either edit `~/.claude.json` directly:
```json
"mcpServers": {
"token-saver": {
"type": "stdio",
"command": "java",
"args": ["-jar", "<path>/token-saver-mcp-java/target/token-saver-mcp-java.jar"]
}
}
```
or use the CLI, which validates the config for you (`--scope user` makes it available across all your projects, not just this one):
```
claude mcp add --scope user token-saver -- java -jar <path>/token-saver-mcp-java/target/token-saver-mcp-java.jar
```
### Codex CLI
Add to `~/.codex/config.toml` (or `.codex/config.toml` in a trusted project, to scope it to that project only):
```toml
[mcp_servers.token-saver]
command = "java"
args = ["-jar", "<path>/token-saver-mcp-java/target/token-saver-mcp-java.jar"]
```
### Other MCP clients
Point any MCP-compliant client (Wibey, or others) at the same stdio command — `java -jar <path>/token-saver-mcp-java/target/token-saver-mcp-java.jar` — following that client's own server-registration mechanism.
## Tools
**`query_local_llm`** — `prompt` (required), `file_paths`, `system_message`, `temperature`, `max_tokens`, `timeout_seconds` (all optional).
**`query_local_llm_with_context`** — `prompt` (required), `context` and/or `file_paths` (at least one required), `task_type` (`code_review` | `documentation` | `refactor` | `general`), `system_message`, `timeout_seconds` (all optional).