alloc-context 0.2.6__tar.gz → 0.2.7__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.
Files changed (184) hide show
  1. {alloc_context-0.2.6 → alloc_context-0.2.7}/PKG-INFO +1 -1
  2. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloc_context.egg-info/PKG-INFO +1 -1
  3. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloc_context.egg-info/SOURCES.txt +2 -2
  4. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/__init__.py +1 -1
  5. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/mcp/bazaar.py +2 -1
  6. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/mcp/bridge.py +39 -1
  7. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/mcp/bridge_portfolio.py +54 -0
  8. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/mcp/contracts.py +35 -0
  9. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/mcp/handlers.py +139 -0
  10. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/mcp/http.py +0 -14
  11. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/mcp/instructions.py +3 -1
  12. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/mcp/server.py +3 -0
  13. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/mcp/tool_catalog.py +61 -1
  14. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/mcp/tool_fields.py +10 -0
  15. alloc_context-0.2.7/alloccontext/mcp/validation.py +109 -0
  16. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/rollup/delta.py +10 -0
  17. alloc_context-0.2.7/alloccontext/rollup/expectation_review.py +594 -0
  18. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/user_config.py +25 -0
  19. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/x402_production_check.py +0 -1
  20. {alloc_context-0.2.6 → alloc_context-0.2.7}/pyproject.toml +1 -1
  21. {alloc_context-0.2.6 → alloc_context-0.2.7}/tests/test_bridge.py +25 -0
  22. alloc_context-0.2.7/tests/test_expectation_review.py +421 -0
  23. alloc_context-0.2.7/tests/test_mcp_validation.py +38 -0
  24. {alloc_context-0.2.6 → alloc_context-0.2.7}/tests/test_x402_production_check.py +3 -3
  25. alloc_context-0.2.6/alloccontext/mcp/glama.py +0 -51
  26. alloc_context-0.2.6/alloccontext/mcp/validation.py +0 -58
  27. alloc_context-0.2.6/tests/test_glama_well_known.py +0 -51
  28. alloc_context-0.2.6/tests/test_mcp_validation.py +0 -47
  29. {alloc_context-0.2.6 → alloc_context-0.2.7}/LICENSE +0 -0
  30. {alloc_context-0.2.6 → alloc_context-0.2.7}/README.md +0 -0
  31. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloc_context.egg-info/dependency_links.txt +0 -0
  32. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloc_context.egg-info/entry_points.txt +0 -0
  33. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloc_context.egg-info/requires.txt +0 -0
  34. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloc_context.egg-info/top_level.txt +0 -0
  35. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/__main__.py +0 -0
  36. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/config.py +0 -0
  37. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/constants.py +0 -0
  38. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/horizon.py +0 -0
  39. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/ingest/__init__.py +0 -0
  40. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/ingest/alt_quote_registry.py +0 -0
  41. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/ingest/alt_quote_store.py +0 -0
  42. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/ingest/alt_quotes.py +0 -0
  43. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/ingest/asset_registry.py +0 -0
  44. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/ingest/cf_benchmarks.py +0 -0
  45. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/ingest/cf_history.py +0 -0
  46. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/ingest/coinbase_client.py +0 -0
  47. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/ingest/coinbase_portfolio.py +0 -0
  48. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/ingest/coingecko.py +0 -0
  49. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/ingest/coinmarketcap.py +0 -0
  50. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/ingest/env_keys.py +0 -0
  51. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/ingest/etf_flows.py +0 -0
  52. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/ingest/exchange/__init__.py +0 -0
  53. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/ingest/exchange/coinbase_adapter.py +0 -0
  54. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/ingest/exchange/kraken_adapter.py +0 -0
  55. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/ingest/exchange/live.py +0 -0
  56. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/ingest/exchange/portfolio.py +0 -0
  57. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/ingest/exchange/registry.py +0 -0
  58. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/ingest/exchange/types.py +0 -0
  59. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/ingest/exchange_http.py +0 -0
  60. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/ingest/fear_greed.py +0 -0
  61. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/ingest/fred.py +0 -0
  62. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/ingest/http_errors.py +0 -0
  63. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/ingest/kalshi.py +0 -0
  64. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/ingest/kalshi_api.py +0 -0
  65. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/ingest/kalshi_client.py +0 -0
  66. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/ingest/kalshi_files.py +0 -0
  67. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/ingest/kalshi_state.py +0 -0
  68. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/ingest/kraken_client.py +0 -0
  69. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/ingest/kraken_portfolio.py +0 -0
  70. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/ingest/macro_calendar.py +0 -0
  71. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/ingest/macro_normalize.py +0 -0
  72. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/ingest/market_snapshots.py +0 -0
  73. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/ingest/outcome.py +0 -0
  74. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/ingest/parse_helpers.py +0 -0
  75. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/ingest/portfolio_holdings.py +0 -0
  76. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/ingest/quote_resolver.py +0 -0
  77. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/ingest/runner.py +0 -0
  78. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/ingest/wallet/__init__.py +0 -0
  79. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/ingest/wallet/alchemy.py +0 -0
  80. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/ingest/wallet/chains.py +0 -0
  81. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/ingest/wallet/curated_tokens.py +0 -0
  82. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/ingest/wallet/etherscan.py +0 -0
  83. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/ingest/wallet/portfolio.py +0 -0
  84. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/integrations/__init__.py +0 -0
  85. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/integrations/langchain.py +0 -0
  86. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/mcp/__init__.py +0 -0
  87. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/mcp/assets.py +0 -0
  88. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/mcp/payer.py +0 -0
  89. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/mcp/payment_middleware.py +0 -0
  90. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/mcp/setup.py +0 -0
  91. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/mcp/staleness.py +0 -0
  92. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/mcp/upstream.py +0 -0
  93. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/mcp/x402_bazaar_dynamic.py +0 -0
  94. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/mcp/x402_config.py +0 -0
  95. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/mcp/x402_pricing.py +0 -0
  96. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/mcp/x402_stables.py +0 -0
  97. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/rollup/__init__.py +0 -0
  98. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/rollup/allocation_analysis.py +0 -0
  99. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/rollup/band.py +0 -0
  100. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/rollup/breadth.py +0 -0
  101. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/rollup/cf_math.py +0 -0
  102. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/rollup/cluster.py +0 -0
  103. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/rollup/cluster_config.py +0 -0
  104. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/rollup/comparison.py +0 -0
  105. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/rollup/context.py +0 -0
  106. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/rollup/etf.py +0 -0
  107. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/rollup/fear_greed.py +0 -0
  108. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/rollup/macro.py +0 -0
  109. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/rollup/portfolio.py +0 -0
  110. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/rollup/portfolio_payload.py +0 -0
  111. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/rollup/rebalance.py +0 -0
  112. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/rollup/regime.py +0 -0
  113. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/rollup/sentiment.py +0 -0
  114. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/rollup/snapshots.py +0 -0
  115. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/rollup/tape.py +0 -0
  116. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/status_report.py +0 -0
  117. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/store/__init__.py +0 -0
  118. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/store/db.py +0 -0
  119. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/store/jsonutil.py +0 -0
  120. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/store/meta.py +0 -0
  121. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/store/retention.py +0 -0
  122. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/store/status.py +0 -0
  123. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/timeutil.py +0 -0
  124. {alloc_context-0.2.6 → alloc_context-0.2.7}/alloccontext/x402_smoke_redact.py +0 -0
  125. {alloc_context-0.2.6 → alloc_context-0.2.7}/setup.cfg +0 -0
  126. {alloc_context-0.2.6 → alloc_context-0.2.7}/tests/test_backup_sqlite.py +0 -0
  127. {alloc_context-0.2.6 → alloc_context-0.2.7}/tests/test_bridge_portfolio.py +0 -0
  128. {alloc_context-0.2.6 → alloc_context-0.2.7}/tests/test_bump_version.py +0 -0
  129. {alloc_context-0.2.6 → alloc_context-0.2.7}/tests/test_check_pypi_release_json.py +0 -0
  130. {alloc_context-0.2.6 → alloc_context-0.2.7}/tests/test_coinbase_portfolio.py +0 -0
  131. {alloc_context-0.2.6 → alloc_context-0.2.7}/tests/test_config_cli.py +0 -0
  132. {alloc_context-0.2.6 → alloc_context-0.2.7}/tests/test_context_bundle_schema.py +0 -0
  133. {alloc_context-0.2.6 → alloc_context-0.2.7}/tests/test_context_snapshots.py +0 -0
  134. {alloc_context-0.2.6 → alloc_context-0.2.7}/tests/test_db_schema.py +0 -0
  135. {alloc_context-0.2.6 → alloc_context-0.2.7}/tests/test_deploy.py +0 -0
  136. {alloc_context-0.2.6 → alloc_context-0.2.7}/tests/test_dev_stack.py +0 -0
  137. {alloc_context-0.2.6 → alloc_context-0.2.7}/tests/test_etf.py +0 -0
  138. {alloc_context-0.2.6 → alloc_context-0.2.7}/tests/test_exchanges_config.py +0 -0
  139. {alloc_context-0.2.6 → alloc_context-0.2.7}/tests/test_fear_greed.py +0 -0
  140. {alloc_context-0.2.6 → alloc_context-0.2.7}/tests/test_fred.py +0 -0
  141. {alloc_context-0.2.6 → alloc_context-0.2.7}/tests/test_holdings_scoped_delta_regime.py +0 -0
  142. {alloc_context-0.2.6 → alloc_context-0.2.7}/tests/test_holdings_scoped_market.py +0 -0
  143. {alloc_context-0.2.6 → alloc_context-0.2.7}/tests/test_horizon.py +0 -0
  144. {alloc_context-0.2.6 → alloc_context-0.2.7}/tests/test_ingest_outcome.py +0 -0
  145. {alloc_context-0.2.6 → alloc_context-0.2.7}/tests/test_ingest_runner.py +0 -0
  146. {alloc_context-0.2.6 → alloc_context-0.2.7}/tests/test_ingest_store_integration.py +0 -0
  147. {alloc_context-0.2.6 → alloc_context-0.2.7}/tests/test_kalshi_api.py +0 -0
  148. {alloc_context-0.2.6 → alloc_context-0.2.7}/tests/test_kraken_portfolio.py +0 -0
  149. {alloc_context-0.2.6 → alloc_context-0.2.7}/tests/test_langchain_integration.py +0 -0
  150. {alloc_context-0.2.6 → alloc_context-0.2.7}/tests/test_live_ingest_handlers.py +0 -0
  151. {alloc_context-0.2.6 → alloc_context-0.2.7}/tests/test_macro.py +0 -0
  152. {alloc_context-0.2.6 → alloc_context-0.2.7}/tests/test_market_breadth.py +0 -0
  153. {alloc_context-0.2.6 → alloc_context-0.2.7}/tests/test_mcp_assets_regime.py +0 -0
  154. {alloc_context-0.2.6 → alloc_context-0.2.7}/tests/test_mcp_bazaar.py +0 -0
  155. {alloc_context-0.2.6 → alloc_context-0.2.7}/tests/test_mcp_contracts.py +0 -0
  156. {alloc_context-0.2.6 → alloc_context-0.2.7}/tests/test_mcp_data_staleness.py +0 -0
  157. {alloc_context-0.2.6 → alloc_context-0.2.7}/tests/test_mcp_handlers.py +0 -0
  158. {alloc_context-0.2.6 → alloc_context-0.2.7}/tests/test_mcp_health.py +0 -0
  159. {alloc_context-0.2.6 → alloc_context-0.2.7}/tests/test_mcp_http_lifecycle.py +0 -0
  160. {alloc_context-0.2.6 → alloc_context-0.2.7}/tests/test_mcp_live_portfolio.py +0 -0
  161. {alloc_context-0.2.6 → alloc_context-0.2.7}/tests/test_mcp_server.py +0 -0
  162. {alloc_context-0.2.6 → alloc_context-0.2.7}/tests/test_mcp_x402.py +0 -0
  163. {alloc_context-0.2.6 → alloc_context-0.2.7}/tests/test_mcp_x402_http.py +0 -0
  164. {alloc_context-0.2.6 → alloc_context-0.2.7}/tests/test_mcp_x402_pricing.py +0 -0
  165. {alloc_context-0.2.6 → alloc_context-0.2.7}/tests/test_mcp_x402_stables.py +0 -0
  166. {alloc_context-0.2.6 → alloc_context-0.2.7}/tests/test_payer.py +0 -0
  167. {alloc_context-0.2.6 → alloc_context-0.2.7}/tests/test_portfolio_holdings.py +0 -0
  168. {alloc_context-0.2.6 → alloc_context-0.2.7}/tests/test_quote_resolver.py +0 -0
  169. {alloc_context-0.2.6 → alloc_context-0.2.7}/tests/test_rebalance.py +0 -0
  170. {alloc_context-0.2.6 → alloc_context-0.2.7}/tests/test_rollup.py +0 -0
  171. {alloc_context-0.2.6 → alloc_context-0.2.7}/tests/test_script_runtime.py +0 -0
  172. {alloc_context-0.2.6 → alloc_context-0.2.7}/tests/test_security_hardening.py +0 -0
  173. {alloc_context-0.2.6 → alloc_context-0.2.7}/tests/test_server_json.py +0 -0
  174. {alloc_context-0.2.6 → alloc_context-0.2.7}/tests/test_setup.py +0 -0
  175. {alloc_context-0.2.6 → alloc_context-0.2.7}/tests/test_snapshots_and_delta.py +0 -0
  176. {alloc_context-0.2.6 → alloc_context-0.2.7}/tests/test_status_report.py +0 -0
  177. {alloc_context-0.2.6 → alloc_context-0.2.7}/tests/test_tool_catalog.py +0 -0
  178. {alloc_context-0.2.6 → alloc_context-0.2.7}/tests/test_user_config.py +0 -0
  179. {alloc_context-0.2.6 → alloc_context-0.2.7}/tests/test_wallet_alchemy.py +0 -0
  180. {alloc_context-0.2.6 → alloc_context-0.2.7}/tests/test_wallet_etherscan.py +0 -0
  181. {alloc_context-0.2.6 → alloc_context-0.2.7}/tests/test_wallet_portfolio.py +0 -0
  182. {alloc_context-0.2.6 → alloc_context-0.2.7}/tests/test_workflows.py +0 -0
  183. {alloc_context-0.2.6 → alloc_context-0.2.7}/tests/test_x402_bazaar_dynamic.py +0 -0
  184. {alloc_context-0.2.6 → alloc_context-0.2.7}/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.6
3
+ Version: 0.2.7
4
4
  Summary: Portfolio-aware crypto context — CEX or wallet holdings, holdings-scoped market, optional allocation analysis
5
5
  License: Elastic-2.0
6
6
  Project-URL: Homepage, https://mcp.alloc-context.com/llms.txt
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: alloc-context
3
- Version: 0.2.6
3
+ Version: 0.2.7
4
4
  Summary: Portfolio-aware crypto context — CEX or wallet holdings, holdings-scoped market, optional allocation analysis
5
5
  License: Elastic-2.0
6
6
  Project-URL: Homepage, https://mcp.alloc-context.com/llms.txt
@@ -70,7 +70,6 @@ alloccontext/mcp/bazaar.py
70
70
  alloccontext/mcp/bridge.py
71
71
  alloccontext/mcp/bridge_portfolio.py
72
72
  alloccontext/mcp/contracts.py
73
- alloccontext/mcp/glama.py
74
73
  alloccontext/mcp/handlers.py
75
74
  alloccontext/mcp/http.py
76
75
  alloccontext/mcp/instructions.py
@@ -98,6 +97,7 @@ alloccontext/rollup/comparison.py
98
97
  alloccontext/rollup/context.py
99
98
  alloccontext/rollup/delta.py
100
99
  alloccontext/rollup/etf.py
100
+ alloccontext/rollup/expectation_review.py
101
101
  alloccontext/rollup/fear_greed.py
102
102
  alloccontext/rollup/macro.py
103
103
  alloccontext/rollup/portfolio.py
@@ -127,9 +127,9 @@ tests/test_deploy.py
127
127
  tests/test_dev_stack.py
128
128
  tests/test_etf.py
129
129
  tests/test_exchanges_config.py
130
+ tests/test_expectation_review.py
130
131
  tests/test_fear_greed.py
131
132
  tests/test_fred.py
132
- tests/test_glama_well_known.py
133
133
  tests/test_holdings_scoped_delta_regime.py
134
134
  tests/test_holdings_scoped_market.py
135
135
  tests/test_horizon.py
@@ -1,3 +1,3 @@
1
1
  """AllocContext — portfolio-aware crypto context for agents (MCP)."""
2
2
 
3
- __version__ = "0.2.6"
3
+ __version__ = "0.2.7"
@@ -25,7 +25,8 @@ USE_DOCS_PATH = "docs/USE.md"
25
25
 
26
26
  PRIVACY_COMPACT_COPY = (
27
27
  "Privacy: nothing stored. One-time read-only fetch. Pass-through only — "
28
- "your keys, wallet address, and portfolio never persist on our servers."
28
+ "your keys, wallet address, theses, and portfolio never persist on our "
29
+ "servers."
29
30
  )
30
31
 
31
32
  PRIVACY_PILLAR_MARKERS = (
@@ -5,6 +5,7 @@ from typing import Any
5
5
  from alloccontext.mcp import handlers
6
6
  from alloccontext.mcp.bridge_portfolio import (
7
7
  attach_assets_scope,
8
+ attach_bridge_expectation_review,
8
9
  bridge_upstream_ready,
9
10
  build_upstream_context_args,
10
11
  default_bridge_app_config,
@@ -14,7 +15,7 @@ from alloccontext.mcp.bridge_portfolio import (
14
15
  resolve_bridge_assets,
15
16
  strip_upstream_allocation_regime,
16
17
  )
17
- from alloccontext.mcp.setup import allocation_not_configured
18
+ from alloccontext.mcp.setup import allocation_not_configured, upstream_payment_required
18
19
  from alloccontext.mcp.upstream import call_upstream_tool
19
20
  from alloccontext.user_config import (
20
21
  UserConfig,
@@ -46,6 +47,27 @@ def _effective_band(user: UserConfig, band: float | None) -> float | None:
46
47
  return user.band
47
48
 
48
49
 
50
+ def _effective_theses(
51
+ user: UserConfig,
52
+ theses: list[dict[str, Any]] | None,
53
+ ) -> list[dict[str, Any]] | None:
54
+ if theses is not None:
55
+ return theses
56
+ return user.theses
57
+
58
+
59
+ def _fetch_upstream_baseline(user: UserConfig, *, scope: str, recorded_at: str) -> dict[str, Any]:
60
+ return call_upstream_tool(
61
+ user,
62
+ "get_context_at",
63
+ {
64
+ "scope": scope,
65
+ "as_of": recorded_at,
66
+ "match": "at_or_before",
67
+ },
68
+ )
69
+
70
+
49
71
  def _bridge_tool_meta(tool_name: str) -> dict[str, Any]:
50
72
  return {
51
73
  "name": tool_name,
@@ -111,10 +133,13 @@ def create_bridge_server(user: UserConfig):
111
133
  assets: list[str] | None = None,
112
134
  target_pct: dict[str, float] | None = None,
113
135
  band: float | None = None,
136
+ theses: list[dict[str, Any]] | None = None,
114
137
  ) -> dict[str, Any]:
115
138
  validated_scope = handlers.validate_scope(scope)
116
139
  validated_freshness = handlers.validate_freshness(freshness)
117
140
  if not bridge_upstream_ready(user):
141
+ if _effective_theses(user, theses):
142
+ return upstream_payment_required()
118
143
  effective_assets, _assets_scope = resolve_bridge_assets(
119
144
  user,
120
145
  bridge_config,
@@ -156,6 +181,19 @@ def create_bridge_server(user: UserConfig):
156
181
  merged = merge_portfolio_into_bundle(bundle, portfolio)
157
182
  merged = strip_upstream_allocation_regime(merged)
158
183
  merged = merge_assets_omitted(merged, portfolio)
184
+ merged = attach_bridge_expectation_review(
185
+ user=user,
186
+ bundle=merged,
187
+ scope=validated_scope,
188
+ theses=_effective_theses(user, theses),
189
+ target_pct=_effective_target_pct(user, target_pct),
190
+ band=_effective_band(user, band),
191
+ fetch_baseline=lambda **kwargs: _fetch_upstream_baseline(
192
+ user,
193
+ scope=kwargs["scope"],
194
+ recorded_at=kwargs["recorded_at"],
195
+ ),
196
+ )
159
197
  return attach_assets_scope(merged, assets_scope)
160
198
 
161
199
  @mcp.tool(**_bridge_tool_meta("get_portfolio_state"))
@@ -15,6 +15,8 @@ from alloccontext.rollup.portfolio_payload import (
15
15
  attach_allocation_analysis_to_payload,
16
16
  portfolio_dict_from_snapshot,
17
17
  )
18
+ from alloccontext.mcp.validation import validate_band, validate_target_pct, validate_theses
19
+ from alloccontext.rollup.expectation_review import build_expectation_review
18
20
  from alloccontext.user_config import UserConfig
19
21
 
20
22
  AssetsScope = Literal["explicit", "portfolio", "default", "portfolio_unavailable"]
@@ -218,3 +220,55 @@ def strip_upstream_allocation_regime(bundle: dict[str, Any]) -> dict[str, Any]:
218
220
  result = dict(bundle)
219
221
  result["regime"] = updated
220
222
  return result
223
+
224
+
225
+ def attach_bridge_expectation_review(
226
+ *,
227
+ user: UserConfig,
228
+ bundle: dict[str, Any],
229
+ scope: str,
230
+ theses: list[dict[str, Any]] | None,
231
+ target_pct: dict[str, float] | None,
232
+ band: float | None,
233
+ fetch_baseline,
234
+ ) -> dict[str, Any]:
235
+ """Score local theses on a merged bridge bundle (baselines via upstream)."""
236
+ effective_theses = theses if theses is not None else user.theses
237
+ if not effective_theses:
238
+ return bundle
239
+
240
+ validated = validate_theses(effective_theses)
241
+ if not validated:
242
+ return bundle
243
+
244
+ by_recorded_at: dict[str, dict[str, Any] | None] = {}
245
+ baseline_bundles: dict[str, dict[str, Any] | None] = {}
246
+ for thesis in validated:
247
+ thesis_id = thesis["id"]
248
+ recorded_at = thesis["recorded_at"]
249
+ if recorded_at not in by_recorded_at:
250
+ baseline = fetch_baseline(scope=scope, recorded_at=recorded_at)
251
+ by_recorded_at[recorded_at] = (
252
+ baseline
253
+ if isinstance(baseline, dict) and baseline.get("as_of")
254
+ else None
255
+ )
256
+ baseline_bundles[thesis_id] = by_recorded_at[recorded_at]
257
+
258
+ effective_target = target_pct if target_pct is not None else user.target_allocation
259
+ if effective_target is not None:
260
+ effective_target = validate_target_pct(effective_target)
261
+ effective_band = band if band is not None else user.band
262
+ if effective_band is not None:
263
+ effective_band = validate_band(effective_band)
264
+
265
+ review = build_expectation_review(
266
+ baseline_bundles=baseline_bundles,
267
+ current_bundle=bundle,
268
+ theses=validated,
269
+ target_pct=effective_target,
270
+ band=effective_band,
271
+ )
272
+ result = dict(bundle)
273
+ result["expectation_review"] = review
274
+ return result
@@ -34,6 +34,22 @@ REGIME_AVAILABLE_KEYS = (
34
34
  "allocation",
35
35
  )
36
36
 
37
+ EXPECTATION_REVIEW_KEYS = (
38
+ "available",
39
+ "current_as_of",
40
+ "supported",
41
+ "weakened",
42
+ "unknown",
43
+ "claims",
44
+ )
45
+
46
+ EXPECTATION_CLAIM_KEYS = (
47
+ "thesis_id",
48
+ "type",
49
+ "status",
50
+ "evidence",
51
+ )
52
+
37
53
  MCP_TOOL_NAMES = frozenset(spec["tool_name"] for spec in mcp_tool_specs())
38
54
 
39
55
 
@@ -121,6 +137,25 @@ def validate_context_bundle(payload: dict[str, Any]) -> None:
121
137
  label="regime",
122
138
  when_available=REGIME_AVAILABLE_KEYS,
123
139
  )
140
+ review = payload.get("expectation_review")
141
+ if isinstance(review, dict) and review.get("available"):
142
+ assert_available_block(
143
+ review,
144
+ label="expectation_review",
145
+ when_available=EXPECTATION_REVIEW_KEYS,
146
+ )
147
+ for index, claim in enumerate(review.get("claims") or []):
148
+ if not isinstance(claim, dict):
149
+ raise AssertionError(f"expectation_review.claims[{index}] must be object")
150
+ assert_has_keys(
151
+ claim,
152
+ EXPECTATION_CLAIM_KEYS,
153
+ label=f"expectation_review.claims[{index}]",
154
+ )
155
+ if claim.get("status") == "unknown" and not claim.get("reason"):
156
+ raise AssertionError(
157
+ f"expectation_review.claims[{index}] unknown requires reason"
158
+ )
124
159
 
125
160
 
126
161
  def validate_market_context(payload: dict[str, Any]) -> None:
@@ -32,6 +32,11 @@ from alloccontext.mcp.validation import (
32
32
  validate_band,
33
33
  validate_nav_usd,
34
34
  validate_target_pct,
35
+ validate_theses,
36
+ )
37
+ from alloccontext.rollup.expectation_review import (
38
+ build_expectation_review,
39
+ theses_need_allocation_fit,
35
40
  )
36
41
  from alloccontext.rollup.comparison import compare_context_bundles
37
42
  from alloccontext.rollup.regime import build_regime_context
@@ -116,6 +121,116 @@ def _band_allocation_pct(portfolio: dict[str, Any]) -> dict[str, float]:
116
121
  return band_allocation_pct(portfolio.get("holdings") or [])
117
122
 
118
123
 
124
+ def _load_baseline_bundle(
125
+ conn: sqlite3.Connection,
126
+ *,
127
+ scope: Scope,
128
+ recorded_at: str,
129
+ ) -> dict[str, Any] | None:
130
+ try:
131
+ resolved = resolve_context_snapshot_as_of(
132
+ conn,
133
+ scope=scope,
134
+ as_of=recorded_at,
135
+ mode="at_or_before",
136
+ )
137
+ return load_context_bundle_snapshot(conn, scope=scope, as_of=resolved)
138
+ except SnapshotNotFoundError:
139
+ return None
140
+
141
+
142
+ def _prepare_baseline_bundle(
143
+ conn: sqlite3.Connection,
144
+ config,
145
+ *,
146
+ scope: Scope,
147
+ recorded_at: str,
148
+ ) -> dict[str, Any] | None:
149
+ """Load baseline snapshot and rebuild regime for market-wide claim scoring."""
150
+ raw = _load_baseline_bundle(conn, scope=scope, recorded_at=recorded_at)
151
+ if raw is None:
152
+ return None
153
+ return _attach_regime(dict(raw), config)
154
+
155
+
156
+ def _baseline_bundles_for_theses(
157
+ conn: sqlite3.Connection,
158
+ config,
159
+ *,
160
+ scope: Scope,
161
+ theses: list[dict[str, Any]],
162
+ ) -> dict[str, dict[str, Any] | None]:
163
+ by_recorded_at: dict[str, dict[str, Any] | None] = {}
164
+ result: dict[str, dict[str, Any] | None] = {}
165
+ for thesis in theses:
166
+ thesis_id = thesis["id"]
167
+ recorded_at = thesis.get("recorded_at") or ""
168
+ if recorded_at not in by_recorded_at:
169
+ by_recorded_at[recorded_at] = _prepare_baseline_bundle(
170
+ conn,
171
+ config,
172
+ scope=scope,
173
+ recorded_at=recorded_at,
174
+ )
175
+ result[thesis_id] = by_recorded_at[recorded_at]
176
+ return result
177
+
178
+
179
+ def _effective_allocation_inputs(
180
+ config,
181
+ *,
182
+ target_pct: dict[str, float] | None,
183
+ band: float | None,
184
+ ) -> tuple[dict[str, float] | None, float | None]:
185
+ effective_target = target_pct
186
+ if effective_target is None and config.portfolio.target_allocations:
187
+ effective_target = validate_target_pct(dict(config.portfolio.target_allocations))
188
+ effective_band = band
189
+ if effective_band is None and config.portfolio.rebalance_band is not None:
190
+ effective_band = validate_band(config.portfolio.rebalance_band)
191
+ return effective_target, effective_band
192
+
193
+
194
+ def _attach_expectation_review(
195
+ conn: sqlite3.Connection,
196
+ config,
197
+ bundle: dict[str, Any],
198
+ *,
199
+ scope: Scope,
200
+ theses: list[dict[str, Any]] | None,
201
+ target_pct: dict[str, float] | None,
202
+ band: float | None,
203
+ ) -> dict[str, Any]:
204
+ if not theses:
205
+ return bundle
206
+ validated = validate_theses(theses)
207
+ if not validated:
208
+ return bundle
209
+
210
+ baseline_bundles = _baseline_bundles_for_theses(
211
+ conn,
212
+ config,
213
+ scope=scope,
214
+ theses=validated,
215
+ )
216
+
217
+ effective_target, effective_band = _effective_allocation_inputs(
218
+ config,
219
+ target_pct=target_pct,
220
+ band=band,
221
+ )
222
+ review = build_expectation_review(
223
+ baseline_bundles=baseline_bundles,
224
+ current_bundle=bundle,
225
+ theses=validated,
226
+ target_pct=effective_target,
227
+ band=effective_band,
228
+ )
229
+ result = dict(bundle)
230
+ result["expectation_review"] = review
231
+ return result
232
+
233
+
119
234
  def _attach_allocation_analysis(
120
235
  bundle: dict[str, Any],
121
236
  config,
@@ -389,6 +504,7 @@ def get_context_bundle(
389
504
  assets: list[str] | None = None,
390
505
  target_pct: dict[str, float] | None = None,
391
506
  band: float | None = None,
507
+ theses: list[dict[str, Any]] | None = None,
392
508
  ) -> dict[str, Any]:
393
509
  now = (as_of or utc_now()).replace(microsecond=0)
394
510
  if now.tzinfo is None:
@@ -426,6 +542,13 @@ def get_context_bundle(
426
542
  save_snapshot=False,
427
543
  alt_symbols=alt_symbols,
428
544
  )
545
+ validated_theses = validate_theses(theses) if theses else []
546
+ need_allocation = theses_need_allocation_fit(validated_theses)
547
+ effective_target, effective_band = _effective_allocation_inputs(
548
+ config,
549
+ target_pct=target_pct,
550
+ band=band,
551
+ )
429
552
  if target_pct is not None or band is not None:
430
553
  bundle = _attach_allocation_analysis(
431
554
  bundle,
@@ -433,8 +556,24 @@ def get_context_bundle(
433
556
  target_pct=target_pct,
434
557
  band=band,
435
558
  )
559
+ elif need_allocation and effective_target is not None:
560
+ bundle = _attach_allocation_analysis(
561
+ bundle,
562
+ config,
563
+ target_pct=effective_target,
564
+ band=effective_band,
565
+ )
436
566
  bundle = apply_assets_filter_to_bundle(bundle, view_assets)
437
567
  bundle = _attach_regime(bundle, config)
568
+ bundle = _attach_expectation_review(
569
+ conn,
570
+ config,
571
+ bundle,
572
+ scope=scope,
573
+ theses=theses,
574
+ target_pct=target_pct,
575
+ band=band,
576
+ )
438
577
  if target_pct is not None:
439
578
  bundle["target_pct"] = validate_target_pct(target_pct)
440
579
  if band is not None:
@@ -1,7 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import contextlib
4
- import json
5
4
  import os
6
5
  from collections.abc import AsyncIterator
7
6
  from typing import Any
@@ -17,7 +16,6 @@ from alloccontext.mcp.bazaar import (
17
16
  build_well_known_x402,
18
17
  resolve_public_base_url,
19
18
  )
20
- from alloccontext.mcp.glama import build_glama_well_known
21
19
  from alloccontext.mcp.server import create_server
22
20
  from alloccontext.mcp.x402_config import (
23
21
  CDP_FACILITATOR_URL,
@@ -131,17 +129,6 @@ def _well_known_mcp_server_card() -> JSONResponse:
131
129
  return JSONResponse(build_mcp_server_card(version=__version__))
132
130
 
133
131
 
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
-
145
132
  def _is_loopback_host(host: str) -> bool:
146
133
  normalized = host.strip().lower()
147
134
  return normalized in {"127.0.0.1", "localhost", "::1"}
@@ -224,7 +211,6 @@ def build_http_app(
224
211
  Route("/llms.txt", lambda req: _llms_txt(settings)),
225
212
  Route("/.well-known/x402.json", lambda req: _well_known_x402(settings)),
226
213
  Route("/.well-known/mcp/server-card.json", lambda req: _well_known_mcp_server_card()),
227
- Route("/.well-known/glama.json", lambda req: _well_known_glama()),
228
214
  ]
229
215
 
230
216
  if not settings.enabled:
@@ -5,7 +5,9 @@ PRODUCT_INSTRUCTIONS = (
5
5
  "keys or a public EVM wallet address (keyless). Default responses include "
6
6
  "holdings[], market, sentiment, macro, regime, and delta. Allocation "
7
7
  "drift and rebalance are opt-in via target_pct or allocation_analysis. "
8
- "Privacy: nothing stored; one-time read-only; pass-through only. "
8
+ "Optional theses[] attach expectation_review (pass-through beliefs; "
9
+ "nothing stored). Privacy: nothing stored; one-time read-only; "
10
+ "pass-through only. "
9
11
  "setup objects explain missing config."
10
12
  )
11
13
 
@@ -26,6 +26,7 @@ from alloccontext.mcp.tool_fields import (
26
26
  MatchMode,
27
27
  NavUsd,
28
28
  OptionalTargetPct,
29
+ OptionalTheses,
29
30
  PriorAsOf,
30
31
  Scenarios,
31
32
  Scope,
@@ -117,6 +118,7 @@ def create_server(
117
118
  assets: Assets = None,
118
119
  target_pct: OptionalTargetPct = None,
119
120
  band: BandOptional = None,
121
+ theses: OptionalTheses = None,
120
122
  ) -> dict[str, Any]:
121
123
  """Return the full deterministic context bundle for daily or weekly scope."""
122
124
  validated_scope = handlers.validate_scope(scope)
@@ -131,6 +133,7 @@ def create_server(
131
133
  assets=assets,
132
134
  target_pct=target_pct,
133
135
  band=band,
136
+ theses=theses,
134
137
  )
135
138
  finally:
136
139
  conn.close()
@@ -118,6 +118,63 @@ BAND_SCHEMA: dict[str, Any] = {
118
118
  "description": "Drift band width as a fraction (e.g. 0.15 = 15% outside target).",
119
119
  }
120
120
 
121
+ THESES_SCHEMA: dict[str, Any] = {
122
+ "type": "array",
123
+ "description": (
124
+ "Optional local thesis entries for expectation_review (pass-through; "
125
+ "nothing stored). Each entry requires id, recorded_at, and claims[]."
126
+ ),
127
+ "items": {
128
+ "type": "object",
129
+ "properties": {
130
+ "id": {
131
+ "type": "string",
132
+ "description": "Stable thesis identifier for claim results.",
133
+ },
134
+ "recorded_at": {
135
+ "type": "string",
136
+ "description": "Thesis anchor ISO timestamp (baseline snapshot).",
137
+ },
138
+ "asset": {
139
+ "type": "string",
140
+ "description": "Optional context symbol for the thesis (not scored).",
141
+ },
142
+ "claims": {
143
+ "type": "array",
144
+ "description": "Structured claim objects scored deterministically.",
145
+ "items": {
146
+ "type": "object",
147
+ "properties": {
148
+ "type": {
149
+ "type": "string",
150
+ "description": "Claim type (e.g. RELATIVE_STRENGTH).",
151
+ },
152
+ "asset": {
153
+ "type": "string",
154
+ "description": "Held asset symbol for asset-scoped claims.",
155
+ },
156
+ "benchmark": {
157
+ "type": "string",
158
+ "description": "Benchmark symbol for RELATIVE_STRENGTH.",
159
+ },
160
+ "direction": {
161
+ "type": "string",
162
+ "description": "Expected direction (UP/DOWN, IMPROVING, etc.).",
163
+ },
164
+ },
165
+ "required": ["type"],
166
+ },
167
+ },
168
+ "rationale": {
169
+ "type": "array",
170
+ "items": {"type": "string"},
171
+ "description": "Agent context only — never scored.",
172
+ },
173
+ },
174
+ "required": ["id", "recorded_at", "claims"],
175
+ },
176
+ }
177
+
121
178
  MATCH_SCHEMA: dict[str, Any] = {
122
179
  "type": "string",
123
180
  "enum": ["exact", "at_or_before"],
@@ -216,7 +273,9 @@ TOOL_DESCRIPTIONS: dict[str, str] = {
216
273
  "snapshot. Use get_market_context for market-only; use get_context_at "
217
274
  "for a historical snapshot; use get_context_delta to compare two times. "
218
275
  "Optional target_pct and band attach allocation_analysis (opt-in drift "
219
- "math). freshness=cached uses the local ingest DB; freshness=live runs "
276
+ "math). Optional theses[] attaches expectation_review (deterministic "
277
+ "claim scoring vs recorded_at baseline; pass-through only). "
278
+ "freshness=cached uses the local ingest DB; freshness=live runs "
220
279
  "ingest first (may add latency; needs ingest API keys on the host)."
221
280
  ),
222
281
  "get_market_context": (
@@ -313,6 +372,7 @@ MCP_TOOL_SPECS: tuple[dict[str, Any], ...] = (
313
372
  "assets": ASSET_FILTER_SCHEMA,
314
373
  "target_pct": TARGET_PCT_SCHEMA,
315
374
  "band": BAND_SCHEMA,
375
+ "theses": THESES_SCHEMA,
316
376
  },
317
377
  },
318
378
  "example": {
@@ -63,6 +63,16 @@ BandOptional = Annotated[
63
63
  ),
64
64
  ),
65
65
  ]
66
+ OptionalTheses = Annotated[
67
+ list[dict[str, Any]] | None,
68
+ Field(
69
+ description=(
70
+ "Optional local thesis entries (id, recorded_at, claims[]) for "
71
+ "deterministic expectation_review scoring. Pass-through only — "
72
+ "nothing stored."
73
+ ),
74
+ ),
75
+ ]
66
76
  BandDefault = Annotated[
67
77
  float,
68
78
  Field(