mcp-token-saver 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mcp_token_saver-0.1.0/.gitignore +74 -0
- mcp_token_saver-0.1.0/CHANGELOG.md +118 -0
- mcp_token_saver-0.1.0/LICENSE +21 -0
- mcp_token_saver-0.1.0/PKG-INFO +521 -0
- mcp_token_saver-0.1.0/README.md +480 -0
- mcp_token_saver-0.1.0/pyproject.toml +81 -0
- mcp_token_saver-0.1.0/src/mcp_token_saver/__init__.py +50 -0
- mcp_token_saver-0.1.0/src/mcp_token_saver/_bedrock_retry.py +110 -0
- mcp_token_saver-0.1.0/src/mcp_token_saver/api.py +653 -0
- mcp_token_saver-0.1.0/src/mcp_token_saver/config.py +106 -0
- mcp_token_saver-0.1.0/src/mcp_token_saver/errors.py +36 -0
- mcp_token_saver-0.1.0/src/mcp_token_saver/events.py +90 -0
- mcp_token_saver-0.1.0/src/mcp_token_saver/pricing.py +208 -0
- mcp_token_saver-0.1.0/src/mcp_token_saver/protocols.py +26 -0
- mcp_token_saver-0.1.0/src/mcp_token_saver/providers/__init__.py +0 -0
- mcp_token_saver-0.1.0/src/mcp_token_saver/providers/bedrock_titan.py +57 -0
- mcp_token_saver-0.1.0/src/mcp_token_saver/providers/hash_provider.py +47 -0
- mcp_token_saver-0.1.0/src/mcp_token_saver/providers/mock.py +41 -0
- mcp_token_saver-0.1.0/src/mcp_token_saver/py.typed +0 -0
- mcp_token_saver-0.1.0/src/mcp_token_saver/router/__init__.py +1 -0
- mcp_token_saver-0.1.0/src/mcp_token_saver/router/base.py +52 -0
- mcp_token_saver-0.1.0/src/mcp_token_saver/router/embeddings_topk.py +40 -0
- mcp_token_saver-0.1.0/src/mcp_token_saver/router/hybrid.py +40 -0
- mcp_token_saver-0.1.0/src/mcp_token_saver/router/llm_router.py +143 -0
- mcp_token_saver-0.1.0/src/mcp_token_saver/serialization.py +33 -0
- mcp_token_saver-0.1.0/src/mcp_token_saver/stores/__init__.py +0 -0
- mcp_token_saver-0.1.0/src/mcp_token_saver/stores/faiss_inmem.py +81 -0
- mcp_token_saver-0.1.0/src/mcp_token_saver/stores/numpy_store.py +48 -0
- mcp_token_saver-0.1.0/src/mcp_token_saver/stores/pure_python.py +56 -0
- mcp_token_saver-0.1.0/src/mcp_token_saver/tokenizer.py +43 -0
- mcp_token_saver-0.1.0/tests/__init__.py +0 -0
- mcp_token_saver-0.1.0/tests/conftest.py +19 -0
- mcp_token_saver-0.1.0/tests/property/__init__.py +0 -0
- mcp_token_saver-0.1.0/tests/property/test_event_roundtrip.py +91 -0
- mcp_token_saver-0.1.0/tests/property/test_pricing_roundtrip.py +44 -0
- mcp_token_saver-0.1.0/tests/property/test_router_invariants.py +67 -0
- mcp_token_saver-0.1.0/tests/unit/__init__.py +0 -0
- mcp_token_saver-0.1.0/tests/unit/test_api.py +168 -0
- mcp_token_saver-0.1.0/tests/unit/test_config.py +54 -0
- mcp_token_saver-0.1.0/tests/unit/test_events.py +71 -0
- mcp_token_saver-0.1.0/tests/unit/test_pricing.py +63 -0
- mcp_token_saver-0.1.0/tests/unit/test_store_pure_python.py +51 -0
- mcp_token_saver-0.1.0/tests/unit/test_tokenizer.py +23 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# --- Python ---
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.so
|
|
6
|
+
|
|
7
|
+
# Distribution / packaging
|
|
8
|
+
.Python
|
|
9
|
+
build/
|
|
10
|
+
develop-eggs/
|
|
11
|
+
dist/
|
|
12
|
+
downloads/
|
|
13
|
+
eggs/
|
|
14
|
+
.eggs/
|
|
15
|
+
lib/
|
|
16
|
+
lib64/
|
|
17
|
+
parts/
|
|
18
|
+
sdist/
|
|
19
|
+
var/
|
|
20
|
+
wheels/
|
|
21
|
+
wheelhouse/*
|
|
22
|
+
!wheelhouse/.gitkeep
|
|
23
|
+
share/python-wheels/
|
|
24
|
+
*.egg-info/
|
|
25
|
+
.installed.cfg
|
|
26
|
+
*.egg
|
|
27
|
+
MANIFEST
|
|
28
|
+
|
|
29
|
+
# Virtual environments
|
|
30
|
+
.venv/
|
|
31
|
+
venv/
|
|
32
|
+
env/
|
|
33
|
+
ENV/
|
|
34
|
+
.python-version
|
|
35
|
+
|
|
36
|
+
# Testing / coverage
|
|
37
|
+
.pytest_cache/
|
|
38
|
+
.coverage
|
|
39
|
+
.coverage.*
|
|
40
|
+
htmlcov/
|
|
41
|
+
.tox/
|
|
42
|
+
.nox/
|
|
43
|
+
coverage.xml
|
|
44
|
+
*.cover
|
|
45
|
+
.cache
|
|
46
|
+
.hypothesis/
|
|
47
|
+
|
|
48
|
+
# Type checking
|
|
49
|
+
.mypy_cache/
|
|
50
|
+
.pyre/
|
|
51
|
+
.pytype/
|
|
52
|
+
.ruff_cache/
|
|
53
|
+
|
|
54
|
+
# IDEs
|
|
55
|
+
.idea/
|
|
56
|
+
.vscode/
|
|
57
|
+
*.swp
|
|
58
|
+
*.swo
|
|
59
|
+
|
|
60
|
+
# OS
|
|
61
|
+
.DS_Store
|
|
62
|
+
Thumbs.db
|
|
63
|
+
|
|
64
|
+
# --- Docker ---
|
|
65
|
+
*.pid
|
|
66
|
+
docker-compose.override.yml
|
|
67
|
+
.docker/
|
|
68
|
+
|
|
69
|
+
# --- Project-local ---
|
|
70
|
+
.env
|
|
71
|
+
.env.local
|
|
72
|
+
|
|
73
|
+
# Personal dev-only scripts (not shipped)
|
|
74
|
+
scripts/test-install.sh
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `mcp-token-saver` are documented here.
|
|
4
|
+
|
|
5
|
+
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
|
|
6
|
+
the project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased] — pre-PyPI hardening
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **Async API.** `TokenSaver.afilter_tools(...)` and the module-level
|
|
13
|
+
`afilter_tools(...)` offload the sync routing dispatch to a worker thread via
|
|
14
|
+
`asyncio.to_thread`, so async gateways (FastAPI, Starlette, aiohttp) no
|
|
15
|
+
longer block their event loop during Haiku roundtrips.
|
|
16
|
+
- **`TokenSaver.aindex_catalog(...)`** async wrapper for the catalog embedding
|
|
17
|
+
step. Prevents the ~30-50s Titan Embed loop from blocking during FastAPI
|
|
18
|
+
`lifespan` startup.
|
|
19
|
+
- **Retry with exponential backoff for every Bedrock call.**
|
|
20
|
+
`_bedrock_retry.call_with_retry` wraps `invoke_model` in the Titan embedding
|
|
21
|
+
provider and the LLM router with jittered retries (3 attempts by default) on
|
|
22
|
+
transient error codes (`ThrottlingException`, `ServiceUnavailableException`,
|
|
23
|
+
`InternalServerException`, timeouts). Non-transient failures (validation,
|
|
24
|
+
auth) still fail fast.
|
|
25
|
+
- **`TokenSaverConfig.provider_kind`** with values `"auto"` (default),
|
|
26
|
+
`"bedrock"` (force Bedrock, fail loud), `"hash"` (force
|
|
27
|
+
`HashEmbeddingProvider`, guaranteed zero Bedrock billing). Fixes the
|
|
28
|
+
"accidental AWS invoice from ambient `AWS_ACCESS_KEY_ID`" footgun.
|
|
29
|
+
- **`TokenSaverConfig.hybrid_low_similarity_action`** with three modes:
|
|
30
|
+
`"haiku_shortlist"` (default), `"haiku_full"`, `"fallback_full"`. Controls
|
|
31
|
+
how the `hybrid` strategy handles low-confidence embeddings queries.
|
|
32
|
+
- **`TokenSaver(..., routing_strategy=...)`** — injection point for a custom
|
|
33
|
+
routing strategy. Bypasses the built-in `embeddings_topk / llm_router /
|
|
34
|
+
hybrid` dispatch. Also available as
|
|
35
|
+
`TokenSaver.default(..., routing_strategy=...)`. Docs include an OpenAI
|
|
36
|
+
GPT-4o-mini example.
|
|
37
|
+
- **`TOKEN_SAVER_STORE`** env var for the demo gateway to switch between
|
|
38
|
+
`numpy` (default) and `faiss` vector stores without touching code. Falls
|
|
39
|
+
back to numpy with a warning if the `[faiss]` extra is missing.
|
|
40
|
+
- **`TOKEN_SAVER_ROUTER_MODEL`** env var for the demo gateway to override the
|
|
41
|
+
Haiku model id (e.g. to point at a different region's cross-region inference
|
|
42
|
+
profile).
|
|
43
|
+
- **`PricingRegistry.from_dict`** and **`PricingRegistry.merge_file`** — the
|
|
44
|
+
former builds a fresh registry from the canonical `{"models": {...}}` shape,
|
|
45
|
+
the latter mutates an existing registry in place. `load_file` still returns
|
|
46
|
+
a fresh registry (classmethod).
|
|
47
|
+
- **Curated third-party pricing defaults** for OpenAI (`gpt-4o`,
|
|
48
|
+
`gpt-4o-mini`, `gpt-4-turbo`, `text-embedding-3-*`) and Anthropic direct API
|
|
49
|
+
model ids so the OpenAI custom-router example works out of the box.
|
|
50
|
+
- **CHANGELOG.md** (this file).
|
|
51
|
+
|
|
52
|
+
### Changed
|
|
53
|
+
|
|
54
|
+
- **Concise INFO tracing on every routing decision.** The gateway logs one
|
|
55
|
+
line per `filter_tools` call and per `index_catalog` boot, with strategy,
|
|
56
|
+
top similarity, tokens saved, Haiku cost, and latency. Explicit
|
|
57
|
+
`LLM router: invoking …` lines when the LLM router is used.
|
|
58
|
+
- **`RoutingEvent` gets a `__post_init__`** that coerces `selected_tool_names`
|
|
59
|
+
from list/iterable to tuple, validates non-negative token counts, and
|
|
60
|
+
canonicalises float precision so JSON round-trips are lossless.
|
|
61
|
+
- **Default Haiku model id** now `eu.anthropic.claude-haiku-4-5-20251001-v1:0`
|
|
62
|
+
(previously the US-only `anthropic.claude-3-5-haiku-20241022-v1:0`, which
|
|
63
|
+
Bedrock rejected in EU regions with a `ValidationException`).
|
|
64
|
+
- **`RoutingEvent.latency_ms`** is now `float` instead of `int` — measurements
|
|
65
|
+
from `time.perf_counter()` were being truncated on the way in.
|
|
66
|
+
- **LLM router response parser hardened.** `_extract_json_array` handles raw
|
|
67
|
+
JSON, markdown-fenced JSON, and JSON embedded in prose so future Claude
|
|
68
|
+
releases that decorate output do not crash the router.
|
|
69
|
+
- **`README`** rewritten to document the three strategies, the pluggable
|
|
70
|
+
interfaces, the custom-routing example, the pricing extension mechanism,
|
|
71
|
+
and the demo tuning knobs.
|
|
72
|
+
|
|
73
|
+
### Removed
|
|
74
|
+
|
|
75
|
+
- **`mcp_token_saver.events.RoutingCosts`.** Duplicate of the (correct)
|
|
76
|
+
`router.base.RoutingCosts`. The `events` module now exports only
|
|
77
|
+
`RoutingEvent` and `EventBus`.
|
|
78
|
+
- **`mcp_token_saver.stores.numpy_inmem.InMemoryNumpyStore`.** Despite the
|
|
79
|
+
name it was pure Python (no numpy), i.e. an unnecessary duplicate of
|
|
80
|
+
`PurePythonStore`. Removed from `__init__` exports and from the vector
|
|
81
|
+
stores table in `README`.
|
|
82
|
+
- **Langfuse integration** — the sink, the docker-compose services
|
|
83
|
+
(`postgres`, `clickhouse`, `redis`, `langfuse-web`, `langfuse-worker`), the
|
|
84
|
+
`example/langfuse/` directory, all `LANGFUSE_*` env vars, and the
|
|
85
|
+
`[langfuse]` install extra. The demo now ships a minimal 4-service stack
|
|
86
|
+
(mcp-server, gateway, agent, ui). Routing metrics live in the Streamlit UI
|
|
87
|
+
and the structured gateway logs.
|
|
88
|
+
|
|
89
|
+
### Fixed
|
|
90
|
+
|
|
91
|
+
- **`hybrid` strategy no longer AttributeErrors** on low-similarity queries
|
|
92
|
+
(the code referenced `cfg.hybrid_low_similarity_action` but the field did
|
|
93
|
+
not exist in `TokenSaverConfig`).
|
|
94
|
+
- **`llm_router` and `hybrid` were dispatched as pure labels**, not actually
|
|
95
|
+
invoked. `TokenSaver.filter_tools` now truly dispatches to
|
|
96
|
+
`LLMRouterStrategy` / `HybridStrategy` based on `strategy_name`.
|
|
97
|
+
- **`RoutingCosts` import mismatch** between strategies (`events` version) and
|
|
98
|
+
the `RoutingStrategy` protocol (`router.base` version) was raising
|
|
99
|
+
`TypeError: missing 4 required positional arguments` when `llm_router` /
|
|
100
|
+
`hybrid` were selected. Both strategies now import from `router.base`.
|
|
101
|
+
- **Test suite realigned with production code.** `test_api.py` and
|
|
102
|
+
`test_router_invariants.py` were written against an older constructor
|
|
103
|
+
signature (`strategy=..., pricing_registry=...`) and referenced classes that
|
|
104
|
+
no longer existed. Rewritten to match the current `TokenSaver(pricing=...,
|
|
105
|
+
routing_strategy=...)` shape and use the deduplicated `router.base`
|
|
106
|
+
imports.
|
|
107
|
+
- **Demo gateway boot** — `mcp_server`, `mcp_gateway`, `agent`, and `ui`
|
|
108
|
+
`Dockerfile`s were failing on hatchling metadata due to missing
|
|
109
|
+
`README.md` files. Cleaned up.
|
|
110
|
+
- **`agent` and `ui` container CMDs** referenced modules
|
|
111
|
+
(`agent.main:app`, `src/ui/app.py`) that were not on the container's
|
|
112
|
+
`PYTHONPATH`. Package layouts fixed so `uvicorn agent.main:app` and
|
|
113
|
+
`streamlit run /app/src/ui/app.py` both resolve.
|
|
114
|
+
|
|
115
|
+
## [0.1.0] — initial internal draft
|
|
116
|
+
|
|
117
|
+
- Initial semantic tool filtering skeleton, three routing strategies,
|
|
118
|
+
Bedrock-only demo stack, Langfuse observability sink.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 mcp-token-saver contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|