alloc-context 0.2.0__tar.gz → 0.2.1__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.
- {alloc_context-0.2.0 → alloc_context-0.2.1}/PKG-INFO +11 -2
- {alloc_context-0.2.0 → alloc_context-0.2.1}/README.md +9 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloc_context.egg-info/PKG-INFO +11 -2
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloc_context.egg-info/SOURCES.txt +3 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/__init__.py +1 -1
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/mcp/bazaar.py +63 -10
- alloc_context-0.2.1/alloccontext/mcp/glama.py +51 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/mcp/http.py +25 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/mcp/server.py +101 -61
- alloc_context-0.2.1/alloccontext/mcp/tool_fields.py +130 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/x402_production_check.py +12 -2
- {alloc_context-0.2.0 → alloc_context-0.2.1}/pyproject.toml +7 -3
- alloc_context-0.2.1/tests/test_glama_well_known.py +51 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/tests/test_mcp_bazaar.py +28 -0
- alloc_context-0.2.1/tests/test_mcp_server.py +47 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/tests/test_workflows.py +6 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/tests/test_x402_production_check.py +3 -3
- alloc_context-0.2.0/tests/test_mcp_server.py +0 -26
- {alloc_context-0.2.0 → alloc_context-0.2.1}/LICENSE +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloc_context.egg-info/dependency_links.txt +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloc_context.egg-info/entry_points.txt +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloc_context.egg-info/requires.txt +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloc_context.egg-info/top_level.txt +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/__main__.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/config.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/constants.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/horizon.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/ingest/__init__.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/ingest/alt_quote_registry.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/ingest/alt_quote_store.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/ingest/alt_quotes.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/ingest/asset_registry.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/ingest/cf_benchmarks.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/ingest/cf_history.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/ingest/coinbase_client.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/ingest/coinbase_portfolio.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/ingest/coingecko.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/ingest/coinmarketcap.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/ingest/env_keys.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/ingest/etf_flows.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/ingest/exchange/__init__.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/ingest/exchange/coinbase_adapter.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/ingest/exchange/kraken_adapter.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/ingest/exchange/live.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/ingest/exchange/portfolio.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/ingest/exchange/registry.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/ingest/exchange/types.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/ingest/exchange_http.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/ingest/fear_greed.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/ingest/fred.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/ingest/http_errors.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/ingest/kalshi.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/ingest/kalshi_api.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/ingest/kalshi_client.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/ingest/kalshi_files.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/ingest/kalshi_state.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/ingest/kraken_client.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/ingest/kraken_portfolio.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/ingest/macro_calendar.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/ingest/macro_normalize.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/ingest/market_snapshots.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/ingest/outcome.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/ingest/parse_helpers.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/ingest/portfolio_holdings.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/ingest/quote_resolver.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/ingest/runner.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/mcp/__init__.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/mcp/assets.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/mcp/bridge.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/mcp/bridge_portfolio.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/mcp/contracts.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/mcp/handlers.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/mcp/instructions.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/mcp/payer.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/mcp/payment_middleware.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/mcp/setup.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/mcp/staleness.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/mcp/upstream.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/mcp/validation.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/mcp/x402_bazaar_dynamic.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/mcp/x402_config.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/mcp/x402_pricing.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/mcp/x402_stables.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/rollup/__init__.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/rollup/allocation_analysis.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/rollup/band.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/rollup/breadth.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/rollup/cf_math.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/rollup/cluster.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/rollup/cluster_config.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/rollup/comparison.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/rollup/context.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/rollup/delta.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/rollup/etf.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/rollup/fear_greed.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/rollup/macro.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/rollup/portfolio.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/rollup/portfolio_payload.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/rollup/rebalance.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/rollup/regime.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/rollup/sentiment.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/rollup/snapshots.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/rollup/tape.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/status_report.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/store/__init__.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/store/db.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/store/jsonutil.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/store/meta.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/store/retention.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/store/status.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/timeutil.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/user_config.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/alloccontext/x402_smoke_redact.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/setup.cfg +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/tests/test_backup_sqlite.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/tests/test_bridge.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/tests/test_bridge_portfolio.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/tests/test_bump_version.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/tests/test_coinbase_portfolio.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/tests/test_config_cli.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/tests/test_context_bundle_schema.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/tests/test_context_snapshots.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/tests/test_db_schema.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/tests/test_deploy.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/tests/test_dev_stack.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/tests/test_etf.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/tests/test_exchanges_config.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/tests/test_fear_greed.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/tests/test_fred.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/tests/test_holdings_scoped_delta_regime.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/tests/test_holdings_scoped_market.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/tests/test_horizon.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/tests/test_ingest_outcome.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/tests/test_ingest_runner.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/tests/test_ingest_store_integration.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/tests/test_kalshi_api.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/tests/test_kraken_portfolio.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/tests/test_live_ingest_handlers.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/tests/test_macro.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/tests/test_market_breadth.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/tests/test_mcp_assets_regime.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/tests/test_mcp_contracts.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/tests/test_mcp_data_staleness.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/tests/test_mcp_handlers.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/tests/test_mcp_health.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/tests/test_mcp_http_lifecycle.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/tests/test_mcp_live_portfolio.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/tests/test_mcp_validation.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/tests/test_mcp_x402.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/tests/test_mcp_x402_http.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/tests/test_mcp_x402_pricing.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/tests/test_mcp_x402_stables.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/tests/test_payer.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/tests/test_portfolio_holdings.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/tests/test_quote_resolver.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/tests/test_rebalance.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/tests/test_rollup.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/tests/test_script_runtime.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/tests/test_security_hardening.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/tests/test_server_json.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/tests/test_setup.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/tests/test_snapshots_and_delta.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/tests/test_status_report.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/tests/test_user_config.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/tests/test_x402_bazaar_dynamic.py +0 -0
- {alloc_context-0.2.0 → alloc_context-0.2.1}/tests/test_x402_smoke_redact.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: alloc-context
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: Portfolio-aware crypto context for agents — holdings, market, optional allocation analysis
|
|
5
5
|
License: Elastic-2.0
|
|
6
6
|
Project-URL: Homepage, https://github.com/negillett/alloc-context
|
|
@@ -9,7 +9,7 @@ Project-URL: Repository, https://github.com/negillett/alloc-context
|
|
|
9
9
|
Project-URL: Issues, https://github.com/negillett/alloc-context/issues
|
|
10
10
|
Project-URL: Changelog, https://github.com/negillett/alloc-context/releases
|
|
11
11
|
Project-URL: MCP Server, https://mcp.alloc-context.com/mcp
|
|
12
|
-
Keywords: mcp,x402,bitcoin,ethereum,portfolio,allocation,
|
|
12
|
+
Keywords: mcp,x402,bitcoin,ethereum,crypto,cryptocurrency,portfolio,allocation,holdings,rebalance,coinbase,kraken,agents
|
|
13
13
|
Classifier: Development Status :: 4 - Beta
|
|
14
14
|
Classifier: Intended Audience :: Developers
|
|
15
15
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -44,6 +44,9 @@ Dynamic: license-file
|
|
|
44
44
|
|
|
45
45
|
# AllocContext
|
|
46
46
|
|
|
47
|
+
[](https://smithery.ai/server/@negillett/alloc-context)
|
|
48
|
+
[](https://glama.ai/mcp/servers/negillett/alloc-context)
|
|
49
|
+
|
|
47
50
|
mcp-name: io.github.negillett/alloc-context
|
|
48
51
|
|
|
49
52
|
**Portfolio-aware crypto context for agents** — discover holdings, market,
|
|
@@ -110,6 +113,7 @@ Not financial advice.
|
|
|
110
113
|
| **Discovery** | [llms.txt](https://mcp.alloc-context.com/llms.txt), [x402 manifest](https://mcp.alloc-context.com/.well-known/x402.json) |
|
|
111
114
|
| **Pricing** | **$0.02** cached context/math · **$0.05** live ingest or portfolio |
|
|
112
115
|
| **Payment** | x402 on Base — USDC or EURC |
|
|
116
|
+
| **Market scope** | Holdings-scoped (BTC/ETH OHLC; alt quote snapshots); bridge auto-scopes from portfolio |
|
|
113
117
|
|
|
114
118
|
Agents and wallets connect directly to the hosted endpoint — see
|
|
115
119
|
[agent-integration.md](docs/agent-integration.md). The Cursor bridge above
|
|
@@ -128,6 +132,11 @@ combines local portfolio reads with this upstream for market context.
|
|
|
128
132
|
| `check_allocation_bands` | Batch band checks for multiple target scenarios |
|
|
129
133
|
| `get_portfolio_state` | Live NAV and holdings from Kraken or Coinbase |
|
|
130
134
|
|
|
135
|
+
Market context is **holdings-scoped**: band assets (BTC/ETH) use OHLC bars; alt
|
|
136
|
+
holdings (e.g. HYPE) use quote snapshots when cached. The bridge auto-scopes
|
|
137
|
+
`assets` from your portfolio (symbols only upstream). See
|
|
138
|
+
[context-bundle.md#market-coverage](docs/context-bundle.md#market-coverage).
|
|
139
|
+
|
|
131
140
|
See [mcp.md](docs/mcp.md) for arguments, pricing, and resources.
|
|
132
141
|
|
|
133
142
|
## Self-host and development
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# AllocContext
|
|
2
2
|
|
|
3
|
+
[](https://smithery.ai/server/@negillett/alloc-context)
|
|
4
|
+
[](https://glama.ai/mcp/servers/negillett/alloc-context)
|
|
5
|
+
|
|
3
6
|
mcp-name: io.github.negillett/alloc-context
|
|
4
7
|
|
|
5
8
|
**Portfolio-aware crypto context for agents** — discover holdings, market,
|
|
@@ -66,6 +69,7 @@ Not financial advice.
|
|
|
66
69
|
| **Discovery** | [llms.txt](https://mcp.alloc-context.com/llms.txt), [x402 manifest](https://mcp.alloc-context.com/.well-known/x402.json) |
|
|
67
70
|
| **Pricing** | **$0.02** cached context/math · **$0.05** live ingest or portfolio |
|
|
68
71
|
| **Payment** | x402 on Base — USDC or EURC |
|
|
72
|
+
| **Market scope** | Holdings-scoped (BTC/ETH OHLC; alt quote snapshots); bridge auto-scopes from portfolio |
|
|
69
73
|
|
|
70
74
|
Agents and wallets connect directly to the hosted endpoint — see
|
|
71
75
|
[agent-integration.md](docs/agent-integration.md). The Cursor bridge above
|
|
@@ -84,6 +88,11 @@ combines local portfolio reads with this upstream for market context.
|
|
|
84
88
|
| `check_allocation_bands` | Batch band checks for multiple target scenarios |
|
|
85
89
|
| `get_portfolio_state` | Live NAV and holdings from Kraken or Coinbase |
|
|
86
90
|
|
|
91
|
+
Market context is **holdings-scoped**: band assets (BTC/ETH) use OHLC bars; alt
|
|
92
|
+
holdings (e.g. HYPE) use quote snapshots when cached. The bridge auto-scopes
|
|
93
|
+
`assets` from your portfolio (symbols only upstream). See
|
|
94
|
+
[context-bundle.md#market-coverage](docs/context-bundle.md#market-coverage).
|
|
95
|
+
|
|
87
96
|
See [mcp.md](docs/mcp.md) for arguments, pricing, and resources.
|
|
88
97
|
|
|
89
98
|
## Self-host and development
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: alloc-context
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: Portfolio-aware crypto context for agents — holdings, market, optional allocation analysis
|
|
5
5
|
License: Elastic-2.0
|
|
6
6
|
Project-URL: Homepage, https://github.com/negillett/alloc-context
|
|
@@ -9,7 +9,7 @@ Project-URL: Repository, https://github.com/negillett/alloc-context
|
|
|
9
9
|
Project-URL: Issues, https://github.com/negillett/alloc-context/issues
|
|
10
10
|
Project-URL: Changelog, https://github.com/negillett/alloc-context/releases
|
|
11
11
|
Project-URL: MCP Server, https://mcp.alloc-context.com/mcp
|
|
12
|
-
Keywords: mcp,x402,bitcoin,ethereum,portfolio,allocation,
|
|
12
|
+
Keywords: mcp,x402,bitcoin,ethereum,crypto,cryptocurrency,portfolio,allocation,holdings,rebalance,coinbase,kraken,agents
|
|
13
13
|
Classifier: Development Status :: 4 - Beta
|
|
14
14
|
Classifier: Intended Audience :: Developers
|
|
15
15
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -44,6 +44,9 @@ Dynamic: license-file
|
|
|
44
44
|
|
|
45
45
|
# AllocContext
|
|
46
46
|
|
|
47
|
+
[](https://smithery.ai/server/@negillett/alloc-context)
|
|
48
|
+
[](https://glama.ai/mcp/servers/negillett/alloc-context)
|
|
49
|
+
|
|
47
50
|
mcp-name: io.github.negillett/alloc-context
|
|
48
51
|
|
|
49
52
|
**Portfolio-aware crypto context for agents** — discover holdings, market,
|
|
@@ -110,6 +113,7 @@ Not financial advice.
|
|
|
110
113
|
| **Discovery** | [llms.txt](https://mcp.alloc-context.com/llms.txt), [x402 manifest](https://mcp.alloc-context.com/.well-known/x402.json) |
|
|
111
114
|
| **Pricing** | **$0.02** cached context/math · **$0.05** live ingest or portfolio |
|
|
112
115
|
| **Payment** | x402 on Base — USDC or EURC |
|
|
116
|
+
| **Market scope** | Holdings-scoped (BTC/ETH OHLC; alt quote snapshots); bridge auto-scopes from portfolio |
|
|
113
117
|
|
|
114
118
|
Agents and wallets connect directly to the hosted endpoint — see
|
|
115
119
|
[agent-integration.md](docs/agent-integration.md). The Cursor bridge above
|
|
@@ -128,6 +132,11 @@ combines local portfolio reads with this upstream for market context.
|
|
|
128
132
|
| `check_allocation_bands` | Batch band checks for multiple target scenarios |
|
|
129
133
|
| `get_portfolio_state` | Live NAV and holdings from Kraken or Coinbase |
|
|
130
134
|
|
|
135
|
+
Market context is **holdings-scoped**: band assets (BTC/ETH) use OHLC bars; alt
|
|
136
|
+
holdings (e.g. HYPE) use quote snapshots when cached. The bridge auto-scopes
|
|
137
|
+
`assets` from your portfolio (symbols only upstream). See
|
|
138
|
+
[context-bundle.md#market-coverage](docs/context-bundle.md#market-coverage).
|
|
139
|
+
|
|
131
140
|
See [mcp.md](docs/mcp.md) for arguments, pricing, and resources.
|
|
132
141
|
|
|
133
142
|
## Self-host and development
|
|
@@ -62,6 +62,7 @@ alloccontext/mcp/bazaar.py
|
|
|
62
62
|
alloccontext/mcp/bridge.py
|
|
63
63
|
alloccontext/mcp/bridge_portfolio.py
|
|
64
64
|
alloccontext/mcp/contracts.py
|
|
65
|
+
alloccontext/mcp/glama.py
|
|
65
66
|
alloccontext/mcp/handlers.py
|
|
66
67
|
alloccontext/mcp/http.py
|
|
67
68
|
alloccontext/mcp/instructions.py
|
|
@@ -70,6 +71,7 @@ alloccontext/mcp/payment_middleware.py
|
|
|
70
71
|
alloccontext/mcp/server.py
|
|
71
72
|
alloccontext/mcp/setup.py
|
|
72
73
|
alloccontext/mcp/staleness.py
|
|
74
|
+
alloccontext/mcp/tool_fields.py
|
|
73
75
|
alloccontext/mcp/upstream.py
|
|
74
76
|
alloccontext/mcp/validation.py
|
|
75
77
|
alloccontext/mcp/x402_bazaar_dynamic.py
|
|
@@ -117,6 +119,7 @@ tests/test_etf.py
|
|
|
117
119
|
tests/test_exchanges_config.py
|
|
118
120
|
tests/test_fear_greed.py
|
|
119
121
|
tests/test_fred.py
|
|
122
|
+
tests/test_glama_well_known.py
|
|
120
123
|
tests/test_holdings_scoped_delta_regime.py
|
|
121
124
|
tests/test_holdings_scoped_market.py
|
|
122
125
|
tests/test_horizon.py
|
|
@@ -43,28 +43,49 @@ SERVICE_TITLE = (
|
|
|
43
43
|
# CDP Bazaar indexes service_name (≤32 chars) and up to five tags from payments.
|
|
44
44
|
BAZAAR_SERVICE_NAME = "AllocContext portfolio MCP"
|
|
45
45
|
SERVICE_TAGS = (
|
|
46
|
+
"crypto",
|
|
47
|
+
"cryptocurrency",
|
|
48
|
+
"bitcoin",
|
|
46
49
|
"btc",
|
|
50
|
+
"ethereum",
|
|
47
51
|
"eth",
|
|
48
52
|
"holdings",
|
|
49
53
|
"portfolio",
|
|
50
|
-
"
|
|
51
|
-
"
|
|
52
|
-
"agent-tools",
|
|
53
|
-
"mcp",
|
|
54
|
+
"allocation",
|
|
55
|
+
"rebalance",
|
|
54
56
|
"sentiment",
|
|
55
57
|
"macro",
|
|
56
|
-
"
|
|
57
|
-
"
|
|
58
|
+
"coinbase",
|
|
59
|
+
"kraken",
|
|
60
|
+
"agent-tools",
|
|
61
|
+
"mcp",
|
|
62
|
+
"x402",
|
|
63
|
+
)
|
|
64
|
+
BAZAAR_INDEX_TAGS = (
|
|
65
|
+
"crypto",
|
|
66
|
+
"cryptocurrency",
|
|
67
|
+
"portfolio",
|
|
68
|
+
"holdings",
|
|
69
|
+
"btc",
|
|
58
70
|
)
|
|
59
|
-
BAZAAR_INDEX_TAGS = ("btc", "eth", "portfolio", "holdings", "mcp")
|
|
60
71
|
|
|
61
72
|
DISCOVERY_KEYWORD_MARKERS = (
|
|
73
|
+
"crypto",
|
|
74
|
+
"cryptocurrency",
|
|
75
|
+
"digital assets",
|
|
76
|
+
"crypto portfolio",
|
|
62
77
|
"portfolio allocation",
|
|
78
|
+
"portfolio context",
|
|
63
79
|
"allocation drift",
|
|
64
80
|
"rebalance plan",
|
|
65
81
|
"fear and greed",
|
|
66
82
|
"etf flows",
|
|
67
83
|
"holdings",
|
|
84
|
+
"holdings-scoped",
|
|
85
|
+
"coinbase",
|
|
86
|
+
"kraken",
|
|
87
|
+
"market context",
|
|
88
|
+
"sentiment",
|
|
68
89
|
)
|
|
69
90
|
|
|
70
91
|
LISTING_DESCRIPTION = (
|
|
@@ -616,9 +637,12 @@ portfolio never persist on our servers.
|
|
|
616
637
|
|
|
617
638
|
## Search keywords
|
|
618
639
|
|
|
619
|
-
bitcoin, ethereum, btc, eth,
|
|
620
|
-
|
|
621
|
-
|
|
640
|
+
bitcoin, ethereum, btc, eth, crypto, cryptocurrency, digital assets, altcoin,
|
|
641
|
+
stablecoin, crypto portfolio, portfolio allocation, portfolio context, holdings,
|
|
642
|
+
holdings-scoped, coinbase, kraken, market context, market data, sentiment,
|
|
643
|
+
macro calendar, etf flows, allocation drift, allocation bands, rebalance plan,
|
|
644
|
+
fear and greed, fear greed index, nav, agent tools, ai agents, mcp, x402,
|
|
645
|
+
model context protocol, context bundle
|
|
622
646
|
|
|
623
647
|
## Examples
|
|
624
648
|
|
|
@@ -673,3 +697,32 @@ def build_well_known_x402(
|
|
|
673
697
|
},
|
|
674
698
|
},
|
|
675
699
|
}
|
|
700
|
+
|
|
701
|
+
|
|
702
|
+
def build_mcp_server_card(*, version: str) -> dict[str, Any]:
|
|
703
|
+
"""Smithery static server card (SEP-1649) — free metadata when POST /mcp is x402."""
|
|
704
|
+
return {
|
|
705
|
+
"serverInfo": {
|
|
706
|
+
"name": SERVICE_TITLE,
|
|
707
|
+
"version": version,
|
|
708
|
+
"description": LISTING_DESCRIPTION,
|
|
709
|
+
},
|
|
710
|
+
"authentication": {
|
|
711
|
+
"required": True,
|
|
712
|
+
"schemes": ["x402"],
|
|
713
|
+
"description": (
|
|
714
|
+
"x402 exact payment on Base mainnet (USDC or EURC) per tool call; "
|
|
715
|
+
"see /.well-known/x402.json for pricing."
|
|
716
|
+
),
|
|
717
|
+
},
|
|
718
|
+
"tools": [
|
|
719
|
+
{
|
|
720
|
+
"name": spec["tool_name"],
|
|
721
|
+
"description": spec["description"],
|
|
722
|
+
"inputSchema": spec["input_schema"],
|
|
723
|
+
}
|
|
724
|
+
for spec in _MCP_TOOLS
|
|
725
|
+
],
|
|
726
|
+
"resources": [],
|
|
727
|
+
"prompts": [],
|
|
728
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""Glama connector well-known metadata (/.well-known/glama.json)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
GLAMA_CONNECTOR_SCHEMA = "https://glama.ai/mcp/schemas/connector.json"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _repo_glama_json_path() -> Path:
|
|
14
|
+
# alloccontext/mcp/glama.py -> repo root (glama.json lives beside pyproject.toml)
|
|
15
|
+
return Path(__file__).resolve().parents[2] / "glama.json"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def build_glama_well_known() -> dict[str, Any]:
|
|
19
|
+
"""Load glama.json for Glama ownership verification on the hosted domain."""
|
|
20
|
+
path = _repo_glama_json_path()
|
|
21
|
+
if not path.is_file():
|
|
22
|
+
msg = f"glama.json not found at {path}"
|
|
23
|
+
raise FileNotFoundError(msg)
|
|
24
|
+
|
|
25
|
+
data = json.loads(path.read_text(encoding="utf-8"))
|
|
26
|
+
maintainers: list[dict[str, str]] = []
|
|
27
|
+
|
|
28
|
+
email_override = os.environ.get("GLAMA_MAINTAINER_EMAIL", "").strip()
|
|
29
|
+
if email_override:
|
|
30
|
+
maintainers = [{"email": email_override}]
|
|
31
|
+
else:
|
|
32
|
+
connector_emails = data.get("connector_emails") or []
|
|
33
|
+
for entry in connector_emails:
|
|
34
|
+
if isinstance(entry, str) and "@" in entry:
|
|
35
|
+
maintainers.append({"email": entry})
|
|
36
|
+
if not maintainers:
|
|
37
|
+
raw = data.get("maintainers") or []
|
|
38
|
+
for entry in raw:
|
|
39
|
+
if isinstance(entry, dict) and entry.get("email"):
|
|
40
|
+
maintainers.append({"email": str(entry["email"])})
|
|
41
|
+
elif isinstance(entry, str) and "@" in entry:
|
|
42
|
+
maintainers.append({"email": entry})
|
|
43
|
+
|
|
44
|
+
if not maintainers:
|
|
45
|
+
msg = "glama.json maintainers must include at least one email"
|
|
46
|
+
raise ValueError(msg)
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
"$schema": GLAMA_CONNECTOR_SCHEMA,
|
|
50
|
+
"maintainers": maintainers,
|
|
51
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import contextlib
|
|
4
|
+
import json
|
|
4
5
|
import os
|
|
5
6
|
from collections.abc import AsyncIterator
|
|
6
7
|
from typing import Any
|
|
@@ -12,9 +13,11 @@ from starlette.routing import Mount, Route
|
|
|
12
13
|
|
|
13
14
|
from alloccontext.mcp.bazaar import (
|
|
14
15
|
build_llms_txt,
|
|
16
|
+
build_mcp_server_card,
|
|
15
17
|
build_well_known_x402,
|
|
16
18
|
resolve_public_base_url,
|
|
17
19
|
)
|
|
20
|
+
from alloccontext.mcp.glama import build_glama_well_known
|
|
18
21
|
from alloccontext.mcp.server import create_server
|
|
19
22
|
from alloccontext.mcp.x402_config import (
|
|
20
23
|
CDP_FACILITATOR_URL,
|
|
@@ -119,6 +122,26 @@ def _well_known_x402(settings: X402Settings) -> JSONResponse:
|
|
|
119
122
|
return JSONResponse(payload)
|
|
120
123
|
|
|
121
124
|
|
|
125
|
+
def _well_known_mcp_server_card() -> JSONResponse:
|
|
126
|
+
public_base = resolve_public_base_url()
|
|
127
|
+
if not public_base:
|
|
128
|
+
return JSONResponse({"error": "discovery metadata unavailable"}, status_code=404)
|
|
129
|
+
from alloccontext import __version__
|
|
130
|
+
|
|
131
|
+
return JSONResponse(build_mcp_server_card(version=__version__))
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _well_known_glama() -> JSONResponse:
|
|
135
|
+
public_base = resolve_public_base_url()
|
|
136
|
+
if not public_base:
|
|
137
|
+
return JSONResponse({"error": "discovery metadata unavailable"}, status_code=404)
|
|
138
|
+
try:
|
|
139
|
+
payload = build_glama_well_known()
|
|
140
|
+
except (FileNotFoundError, ValueError, json.JSONDecodeError) as exc:
|
|
141
|
+
return JSONResponse({"error": str(exc)}, status_code=404)
|
|
142
|
+
return JSONResponse(payload)
|
|
143
|
+
|
|
144
|
+
|
|
122
145
|
def _is_loopback_host(host: str) -> bool:
|
|
123
146
|
normalized = host.strip().lower()
|
|
124
147
|
return normalized in {"127.0.0.1", "localhost", "::1"}
|
|
@@ -154,6 +177,8 @@ def build_http_app(
|
|
|
154
177
|
Route("/health", _make_health_handler(config_path)),
|
|
155
178
|
Route("/llms.txt", lambda req: _llms_txt(settings)),
|
|
156
179
|
Route("/.well-known/x402.json", lambda req: _well_known_x402(settings)),
|
|
180
|
+
Route("/.well-known/mcp/server-card.json", lambda req: _well_known_mcp_server_card()),
|
|
181
|
+
Route("/.well-known/glama.json", lambda req: _well_known_glama()),
|
|
157
182
|
]
|
|
158
183
|
|
|
159
184
|
if not settings.enabled:
|
|
@@ -6,6 +6,26 @@ from typing import Any
|
|
|
6
6
|
from alloccontext.config import load_config
|
|
7
7
|
from alloccontext.mcp import handlers
|
|
8
8
|
from alloccontext.mcp.instructions import PRODUCT_INSTRUCTIONS, REBALANCE_HINT_GUIDE
|
|
9
|
+
from alloccontext.mcp.tool_fields import (
|
|
10
|
+
AllocationPct,
|
|
11
|
+
ApiKey,
|
|
12
|
+
ApiSecret,
|
|
13
|
+
AsOf,
|
|
14
|
+
Assets,
|
|
15
|
+
BandDefault,
|
|
16
|
+
BandOptional,
|
|
17
|
+
CurrentAsOf,
|
|
18
|
+
Exchange,
|
|
19
|
+
ExchangeKrakenDefault,
|
|
20
|
+
Freshness,
|
|
21
|
+
MatchMode,
|
|
22
|
+
NavUsd,
|
|
23
|
+
OptionalTargetPct,
|
|
24
|
+
PriorAsOf,
|
|
25
|
+
Scenarios,
|
|
26
|
+
Scope,
|
|
27
|
+
TargetPct,
|
|
28
|
+
)
|
|
9
29
|
from alloccontext.store.db import connect
|
|
10
30
|
|
|
11
31
|
|
|
@@ -78,19 +98,21 @@ def create_server(
|
|
|
78
98
|
@mcp.tool(
|
|
79
99
|
name="get_context_bundle",
|
|
80
100
|
description=(
|
|
81
|
-
"
|
|
82
|
-
"macro, regime hints, and delta vs the prior saved
|
|
83
|
-
"
|
|
84
|
-
"
|
|
85
|
-
"
|
|
101
|
+
"Return the full read-only ContextBundle JSON: portfolio holdings, "
|
|
102
|
+
"market, sentiment, macro, regime hints, and delta vs the prior saved "
|
|
103
|
+
"snapshot. Use get_market_context for market-only; use get_context_at "
|
|
104
|
+
"for a historical snapshot; use get_context_delta to compare two times. "
|
|
105
|
+
"Optional target_pct and band attach allocation_analysis (opt-in drift "
|
|
106
|
+
"math). freshness=cached uses the local ingest DB; freshness=live runs "
|
|
107
|
+
"ingest first (may add latency; needs ingest API keys on the host)."
|
|
86
108
|
),
|
|
87
109
|
)
|
|
88
110
|
def get_context_bundle(
|
|
89
|
-
scope:
|
|
90
|
-
freshness:
|
|
91
|
-
assets:
|
|
92
|
-
target_pct:
|
|
93
|
-
band:
|
|
111
|
+
scope: Scope = "daily",
|
|
112
|
+
freshness: Freshness = "cached",
|
|
113
|
+
assets: Assets = None,
|
|
114
|
+
target_pct: OptionalTargetPct = None,
|
|
115
|
+
band: BandOptional = None,
|
|
94
116
|
) -> dict[str, Any]:
|
|
95
117
|
"""Return the full deterministic context bundle for daily or weekly scope."""
|
|
96
118
|
validated_scope = handlers.validate_scope(scope)
|
|
@@ -112,16 +134,17 @@ def create_server(
|
|
|
112
134
|
@mcp.tool(
|
|
113
135
|
name="get_market_context",
|
|
114
136
|
description=(
|
|
115
|
-
"
|
|
116
|
-
"FRED indicators, ETF flows, and market breadth
|
|
117
|
-
"(
|
|
137
|
+
"Return read-only fused market backdrop: sentiment (Fear & Greed, "
|
|
138
|
+
"Kalshi), macro events, FRED indicators, ETF flows, and market breadth "
|
|
139
|
+
"(no portfolio holdings). Use get_context_bundle when you also need "
|
|
140
|
+
"holdings, delta, or regime. freshness=cached uses the local ingest DB; "
|
|
118
141
|
"freshness=live runs ingest first (requires ingest API keys on the host)."
|
|
119
142
|
),
|
|
120
143
|
)
|
|
121
144
|
def get_market_context(
|
|
122
|
-
scope:
|
|
123
|
-
freshness:
|
|
124
|
-
assets:
|
|
145
|
+
scope: Scope = "daily",
|
|
146
|
+
freshness: Freshness = "cached",
|
|
147
|
+
assets: Assets = None,
|
|
125
148
|
) -> dict[str, Any]:
|
|
126
149
|
"""Return ContextBundle subset for daily or weekly scope."""
|
|
127
150
|
validated_scope = handlers.validate_scope(scope)
|
|
@@ -141,18 +164,22 @@ def create_server(
|
|
|
141
164
|
@mcp.tool(
|
|
142
165
|
name="get_rebalance_plan",
|
|
143
166
|
description=(
|
|
144
|
-
"USD deltas and exchange
|
|
145
|
-
"split.
|
|
146
|
-
"
|
|
147
|
-
"
|
|
167
|
+
"Compute read-only USD deltas and suggested exchange move lines to "
|
|
168
|
+
"reach a BTC/ETH/CASH target split. Pure math — no exchange API calls. "
|
|
169
|
+
"Requires allocation_pct, target_pct, and nav_usd. Use "
|
|
170
|
+
"get_portfolio_state or get_context_bundle when you need live or cached "
|
|
171
|
+
"weights first. Use check_allocation_band for pass/fail drift only; use "
|
|
172
|
+
"check_allocation_bands for multiple scenarios. Optional band adds a "
|
|
173
|
+
"band_check block alongside the plan. exchange=kraken|coinbase adjusts "
|
|
174
|
+
"move wording only."
|
|
148
175
|
),
|
|
149
176
|
)
|
|
150
177
|
def get_rebalance_plan(
|
|
151
|
-
allocation_pct:
|
|
152
|
-
target_pct:
|
|
153
|
-
nav_usd:
|
|
154
|
-
exchange:
|
|
155
|
-
band:
|
|
178
|
+
allocation_pct: AllocationPct,
|
|
179
|
+
target_pct: TargetPct,
|
|
180
|
+
nav_usd: NavUsd,
|
|
181
|
+
exchange: ExchangeKrakenDefault = "kraken",
|
|
182
|
+
band: BandOptional = None,
|
|
156
183
|
) -> dict[str, Any]:
|
|
157
184
|
"""Compute rebalance plan from current allocation and NAV."""
|
|
158
185
|
return handlers.get_rebalance_plan(
|
|
@@ -166,18 +193,21 @@ def create_server(
|
|
|
166
193
|
@mcp.tool(
|
|
167
194
|
name="get_portfolio_state",
|
|
168
195
|
description=(
|
|
169
|
-
"
|
|
170
|
-
"
|
|
171
|
-
"
|
|
172
|
-
"
|
|
196
|
+
"Fetch live read-only portfolio NAV, holdings[], and band weights from "
|
|
197
|
+
"Kraken or Coinbase credentials passed in this call (never stored). "
|
|
198
|
+
"Requires exchange, api_key, and api_secret. Use get_context_bundle "
|
|
199
|
+
"for cached market and history without exchange keys. Optional "
|
|
200
|
+
"target_pct attaches allocation_analysis; optional band sets drift "
|
|
201
|
+
"width when target_pct is supplied. Returns an error payload on invalid "
|
|
202
|
+
"credentials or unsupported exchange — no side effects."
|
|
173
203
|
),
|
|
174
204
|
)
|
|
175
205
|
def get_portfolio_state(
|
|
176
|
-
exchange:
|
|
177
|
-
api_key:
|
|
178
|
-
api_secret:
|
|
179
|
-
target_pct:
|
|
180
|
-
band:
|
|
206
|
+
exchange: Exchange,
|
|
207
|
+
api_key: ApiKey,
|
|
208
|
+
api_secret: ApiSecret,
|
|
209
|
+
target_pct: OptionalTargetPct = None,
|
|
210
|
+
band: BandOptional = None,
|
|
181
211
|
) -> dict[str, Any]:
|
|
182
212
|
"""Fetch live portfolio state using caller-supplied read-only API keys."""
|
|
183
213
|
return handlers.get_portfolio_state(
|
|
@@ -192,16 +222,19 @@ def create_server(
|
|
|
192
222
|
@mcp.tool(
|
|
193
223
|
name="check_allocation_band",
|
|
194
224
|
description=(
|
|
195
|
-
"
|
|
196
|
-
"target_pct
|
|
197
|
-
"
|
|
198
|
-
"
|
|
225
|
+
"Read-only drift check: are BTC/ETH/CASH band weights outside the "
|
|
226
|
+
"drift band vs target_pct? Returns rebalance_hint (within_band, "
|
|
227
|
+
"consider_rebalance, etc.). Requires allocation_pct and target_pct; "
|
|
228
|
+
"band defaults to 0.15. Single-scenario only — use check_allocation_bands "
|
|
229
|
+
"for multiple targets in one call. Use get_rebalance_plan when you need "
|
|
230
|
+
"USD move lines, not just a hint. For bundle drift, pass target_pct on "
|
|
231
|
+
"get_context_bundle to attach allocation_analysis instead."
|
|
199
232
|
),
|
|
200
233
|
)
|
|
201
234
|
def check_allocation_band(
|
|
202
|
-
allocation_pct:
|
|
203
|
-
target_pct:
|
|
204
|
-
band:
|
|
235
|
+
allocation_pct: AllocationPct,
|
|
236
|
+
target_pct: TargetPct,
|
|
237
|
+
band: BandDefault = 0.15,
|
|
205
238
|
) -> dict[str, Any]:
|
|
206
239
|
"""Evaluate allocation drift against band width (default 0.15 = 15%)."""
|
|
207
240
|
return handlers.check_band(allocation_pct, target_pct, band)
|
|
@@ -209,18 +242,20 @@ def create_server(
|
|
|
209
242
|
@mcp.tool(
|
|
210
243
|
name="get_context_at",
|
|
211
244
|
description=(
|
|
212
|
-
"Load a
|
|
213
|
-
"
|
|
214
|
-
"
|
|
245
|
+
"Load a read-only ContextBundle snapshot from ingest history at a "
|
|
246
|
+
"point in time. Use get_context_bundle for the latest snapshot; use "
|
|
247
|
+
"get_context_delta to compare two timestamps. Read-only; returns an "
|
|
248
|
+
"unavailable payload when no snapshot matches as_of and match. Optional "
|
|
249
|
+
"target_pct and band attach allocation_analysis to the historical bundle."
|
|
215
250
|
),
|
|
216
251
|
)
|
|
217
252
|
def get_context_at(
|
|
218
|
-
as_of:
|
|
219
|
-
scope:
|
|
220
|
-
match:
|
|
221
|
-
assets:
|
|
222
|
-
target_pct:
|
|
223
|
-
band:
|
|
253
|
+
as_of: AsOf,
|
|
254
|
+
scope: Scope = "daily",
|
|
255
|
+
match: MatchMode = "at_or_before",
|
|
256
|
+
assets: Assets = None,
|
|
257
|
+
target_pct: OptionalTargetPct = None,
|
|
258
|
+
band: BandOptional = None,
|
|
224
259
|
) -> dict[str, Any]:
|
|
225
260
|
validated_scope = handlers.validate_scope(scope)
|
|
226
261
|
if match not in ("exact", "at_or_before"):
|
|
@@ -243,15 +278,18 @@ def create_server(
|
|
|
243
278
|
@mcp.tool(
|
|
244
279
|
name="get_context_delta",
|
|
245
280
|
description=(
|
|
246
|
-
"Compare two ContextBundle snapshots and return
|
|
247
|
-
"
|
|
281
|
+
"Compare two read-only ContextBundle snapshots and return "
|
|
282
|
+
"notable_shifts between them. Requires prior_as_of; omit current_as_of "
|
|
283
|
+
"to diff against the latest live bundle. Use get_context_at to load one "
|
|
284
|
+
"snapshot without diffing. Read-only; no ingest unless you combine with "
|
|
285
|
+
"a live current_as_of path."
|
|
248
286
|
),
|
|
249
287
|
)
|
|
250
288
|
def get_context_delta(
|
|
251
|
-
prior_as_of:
|
|
252
|
-
scope:
|
|
253
|
-
current_as_of:
|
|
254
|
-
assets:
|
|
289
|
+
prior_as_of: PriorAsOf,
|
|
290
|
+
scope: Scope = "daily",
|
|
291
|
+
current_as_of: CurrentAsOf = None,
|
|
292
|
+
assets: Assets = None,
|
|
255
293
|
) -> dict[str, Any]:
|
|
256
294
|
validated_scope = handlers.validate_scope(scope)
|
|
257
295
|
conn = connect(config.paths.db)
|
|
@@ -270,14 +308,16 @@ def create_server(
|
|
|
270
308
|
@mcp.tool(
|
|
271
309
|
name="check_allocation_bands",
|
|
272
310
|
description=(
|
|
273
|
-
"
|
|
274
|
-
"scenarios in one call. Each scenario
|
|
275
|
-
"name and band (default 0.15)."
|
|
311
|
+
"Read-only batch drift check: evaluate allocation_pct against multiple "
|
|
312
|
+
"target_pct/band scenarios in one call. Each scenario requires "
|
|
313
|
+
"target_pct; optional name and band (default 0.15). Use "
|
|
314
|
+
"check_allocation_band for a single target. Use get_rebalance_plan when "
|
|
315
|
+
"you need USD move lines after identifying drift."
|
|
276
316
|
),
|
|
277
317
|
)
|
|
278
318
|
def check_allocation_bands(
|
|
279
|
-
allocation_pct:
|
|
280
|
-
scenarios:
|
|
319
|
+
allocation_pct: AllocationPct,
|
|
320
|
+
scenarios: Scenarios,
|
|
281
321
|
) -> dict[str, Any]:
|
|
282
322
|
return handlers.check_allocation_bands(allocation_pct, scenarios)
|
|
283
323
|
|