structuremappingmemory 1.0.0__tar.gz → 1.1.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.
Files changed (139) hide show
  1. {structuremappingmemory-1.0.0/structuremappingmemory.egg-info → structuremappingmemory-1.1.1}/PKG-INFO +32 -8
  2. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/README.md +28 -7
  3. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/pyproject.toml +8 -2
  4. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/__init__.py +1 -1
  5. structuremappingmemory-1.1.1/sma/mcp.py +393 -0
  6. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1/structuremappingmemory.egg-info}/PKG-INFO +32 -8
  7. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/structuremappingmemory.egg-info/SOURCES.txt +2 -0
  8. structuremappingmemory-1.1.1/structuremappingmemory.egg-info/entry_points.txt +4 -0
  9. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/structuremappingmemory.egg-info/requires.txt +4 -0
  10. structuremappingmemory-1.1.1/tests/test_mcp.py +138 -0
  11. structuremappingmemory-1.0.0/structuremappingmemory.egg-info/entry_points.txt +0 -2
  12. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/LICENSE +0 -0
  13. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/setup.cfg +0 -0
  14. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/__main__.py +0 -0
  15. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/agent/__init__.py +0 -0
  16. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/agent/adapter_draft.py +0 -0
  17. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/agent/api.py +0 -0
  18. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/agent/comparison.py +0 -0
  19. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/agent/llm.py +0 -0
  20. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/agent/policies.py +0 -0
  21. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/agent/service.py +0 -0
  22. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/cli.py +0 -0
  23. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/encoders/__init__.py +0 -0
  24. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/encoders/agentobs.py +0 -0
  25. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/encoders/base.py +0 -0
  26. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/encoders/code_treesitter.py +0 -0
  27. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/encoders/coverage.py +0 -0
  28. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/encoders/draft_adapter.py +0 -0
  29. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/encoders/healthcare.py +0 -0
  30. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/encoders/logs_drain.py +0 -0
  31. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/encoders/prose_tier1.py +0 -0
  32. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/encoders/structured.py +0 -0
  33. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/encoders/traces.py +0 -0
  34. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/eval/__init__.py +0 -0
  35. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/eval/agentic/__init__.py +0 -0
  36. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/eval/agentic/arms/__init__.py +0 -0
  37. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/eval/agentic/arms/cyber.py +0 -0
  38. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/eval/agentic/arms/discovery.py +0 -0
  39. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/eval/agentic/arms/finance.py +0 -0
  40. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/eval/agentic/arms/legal.py +0 -0
  41. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/eval/agentic/arms/medicine.py +0 -0
  42. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/eval/agentic/harness.py +0 -0
  43. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/eval/agentic/memories.py +0 -0
  44. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/eval/agentic/metrics.py +0 -0
  45. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/eval/agentic_qa/__init__.py +0 -0
  46. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/eval/agentic_qa/agent.py +0 -0
  47. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/eval/agentic_qa/metrics.py +0 -0
  48. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/eval/agentic_qa/pools.py +0 -0
  49. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/eval/arn.py +0 -0
  50. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/eval/baselines/__init__.py +0 -0
  51. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/eval/baselines/bge_dense.py +0 -0
  52. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/eval/baselines/bm25.py +0 -0
  53. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/eval/baselines/dense.py +0 -0
  54. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/eval/baselines/hipporag.py +0 -0
  55. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/eval/baselines/hybrid_rrf.py +0 -0
  56. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/eval/baselines/longcontext_llm.py +0 -0
  57. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/eval/baselines/rerank.py +0 -0
  58. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/eval/baselines/splade.py +0 -0
  59. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/eval/baselines/wl_kernel.py +0 -0
  60. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/eval/bugsinpy.py +0 -0
  61. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/eval/bugsinpy_families.py +0 -0
  62. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/eval/crossdomain.py +0 -0
  63. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/eval/diabetes.py +0 -0
  64. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/eval/drift_env.py +0 -0
  65. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/eval/drift_metrics.py +0 -0
  66. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/eval/family_labels.py +0 -0
  67. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/eval/fraud_elliptic/__init__.py +0 -0
  68. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/eval/fraud_elliptic/encoder.py +0 -0
  69. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/eval/fraud_elliptic/eval.py +0 -0
  70. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/eval/fraud_elliptic/test_encoder.py +0 -0
  71. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/eval/ieee_cis.py +0 -0
  72. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/eval/loghub.py +0 -0
  73. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/eval/loghub_eval.py +0 -0
  74. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/eval/longmemeval.py +0 -0
  75. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/eval/memory_backends/__init__.py +0 -0
  76. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/eval/memory_backends/base.py +0 -0
  77. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/eval/memory_backends/context_only.py +0 -0
  78. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/eval/memory_backends/rag_notes.py +0 -0
  79. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/eval/memory_backends/shared_llm.py +0 -0
  80. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/eval/memory_backends/sma_memory.py +0 -0
  81. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/eval/memory_backends/zep_graphiti.py +0 -0
  82. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/eval/metrics.py +0 -0
  83. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/eval/ontology_bench.py +0 -0
  84. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/eval/report.py +0 -0
  85. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/eval/ssb_eval.py +0 -0
  86. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/eval/ssb_generator.py +0 -0
  87. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/eval/stats.py +0 -0
  88. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/eval/transfer_eval.py +0 -0
  89. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/index/__init__.py +0 -0
  90. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/index/ann.py +0 -0
  91. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/index/content_vectors.py +0 -0
  92. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/index/inverted.py +0 -0
  93. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/index/macfac.py +0 -0
  94. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/ir/__init__.py +0 -0
  95. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/ir/canon.py +0 -0
  96. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/ir/schema.py +0 -0
  97. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/ir/sexpr.py +0 -0
  98. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/ir/signatures.py +0 -0
  99. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/match/__init__.py +0 -0
  100. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/match/conflicts.py +0 -0
  101. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/match/engine.py +0 -0
  102. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/match/explain.py +0 -0
  103. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/match/infer.py +0 -0
  104. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/match/kernels.py +0 -0
  105. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/match/mdl.py +0 -0
  106. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/match/merge_cpsat.py +0 -0
  107. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/match/merge_greedy.py +0 -0
  108. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/match/mh.py +0 -0
  109. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/match/ses.py +0 -0
  110. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/match/types.py +0 -0
  111. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/match/verifier.py +0 -0
  112. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/ontology/__init__.py +0 -0
  113. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/ontology/attack.py +0 -0
  114. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/ontology/cpc.py +0 -0
  115. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/ontology/graph.py +0 -0
  116. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/ontology/loader.py +0 -0
  117. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/ontology/mitre_xml.py +0 -0
  118. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/ontology/mount.py +0 -0
  119. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/ontology/rdf_loader.py +0 -0
  120. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/ontology/registry.py +0 -0
  121. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/ontology/router.py +0 -0
  122. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/ontology/usgaap.py +0 -0
  123. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/sage/__init__.py +0 -0
  124. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/sage/assimilate.py +0 -0
  125. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/sage/pools.py +0 -0
  126. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/sage/probabilities.py +0 -0
  127. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/store/__init__.py +0 -0
  128. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/store/lmdb_store.py +0 -0
  129. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/store/registry.py +0 -0
  130. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/store/wal.py +0 -0
  131. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/sma/ui/app.py +0 -0
  132. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/structuremappingmemory.egg-info/dependency_links.txt +0 -0
  133. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/structuremappingmemory.egg-info/top_level.txt +0 -0
  134. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/tests/test_bugsinpy_t3.py +0 -0
  135. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/tests/test_drift.py +0 -0
  136. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/tests/test_gates.py +0 -0
  137. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/tests/test_hipporag.py +0 -0
  138. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/tests/test_production_loop.py +0 -0
  139. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.1}/tests/test_stats.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: structuremappingmemory
3
- Version: 1.0.0
3
+ Version: 1.1.1
4
4
  Summary: SMA-1: Structure-Mapping Agentic Memory — a structure-mapping retrieval memory that grounds language models in curated ontologies
5
5
  Author-email: Ayaz Khan <aak2259@columbia.edu>
6
6
  License: Apache-2.0
@@ -32,6 +32,7 @@ Requires-Dist: regex>=2024.5.15
32
32
  Requires-Dist: scikit-learn>=1.5
33
33
  Requires-Dist: typer>=0.12
34
34
  Requires-Dist: zstandard>=0.22
35
+ Requires-Dist: mcp>=1.0
35
36
  Provides-Extra: encoders
36
37
  Requires-Dist: drain3>=0.9.11; extra == "encoders"
37
38
  Requires-Dist: tree-sitter>=0.23; extra == "encoders"
@@ -50,11 +51,14 @@ Requires-Dist: networkx>=3; extra == "eval"
50
51
  Provides-Extra: local-llm
51
52
  Requires-Dist: huggingface-hub>=0.23; extra == "local-llm"
52
53
  Requires-Dist: llama-cpp-python>=0.2.90; extra == "local-llm"
54
+ Provides-Extra: mcp
55
+ Requires-Dist: mcp>=1.0; extra == "mcp"
53
56
  Dynamic: license-file
54
57
 
55
58
  # SMA-1: Structure-Mapping Agentic Memory
56
59
 
57
60
  [![CI](https://github.com/ayazkhan27/SMA-1/actions/workflows/ci.yml/badge.svg)](https://github.com/ayazkhan27/SMA-1/actions/workflows/ci.yml)
61
+ [![PyPI](https://img.shields.io/pypi/v/structuremappingmemory)](https://pypi.org/project/structuremappingmemory/)
58
62
  [![License: Apache-2.0](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](LICENSE)
59
63
  [![HF Space](https://img.shields.io/badge/%F0%9F%A4%97%20Demo-Space-orange)](https://huggingface.co/spaces/zephyr27/SMA-1-demo)
60
64
 
@@ -103,13 +107,13 @@ adapters.
103
107
  On a **memory-swap benchmark** where only the retriever varies, SMA beats a strong
104
108
  RAG + KG baseline suite on the rare/long-tail slice across five domains:
105
109
 
106
- | Domain | SMA tail top-5 | best RAG | Δ | Holm-significant |
107
- |---|:--:|:--:|:--:|:--:|
108
- | Medicine (HPO/MONDO) | 0.949 | 0.606 | **+0.333** | yes |
109
- | Finance (US-GAAP) | 0.418 | 0.231 | **+0.167** | yes |
110
- | Genomics (GO) | 0.849 | 0.682 | **+0.156** | yes |
111
- | Legal (CPC, all-query) | 0.941 | 0.870 | **+0.064** | yes |
112
- | Cyber (ATT&CK) | 0.766 | 0.749 | +0.073 | directional* |
110
+ | Domain | SMA tail top-5 | best RAG | Δ tail top-5 | Cliff's δ | Holm-significant |
111
+ |---|:--:|:--:|:--:|:--:|:--:|
112
+ | Medicine (HPO/MONDO) | 0.949 | 0.606 | **+0.343** | 0.333 | yes |
113
+ | Finance (US-GAAP) | 0.418 | 0.231 | **+0.187** | 0.167 | yes |
114
+ | Genomics (GO) | 0.849 | 0.682 | **+0.167** | 0.156 | yes |
115
+ | Legal (CPC, all-query) | 0.941 | 0.870 | **+0.071** | 0.064 | yes |
116
+ | Cyber (ATT&CK) | 0.766 | 0.749 | +0.017 | 0.073 | directional* |
113
117
 
114
118
  \* Cyber survives Holm across domains but not a conservative
115
119
  Bonferroni-over-baselines selection correction (p=0.035 → 0.17); reported as
@@ -142,6 +146,26 @@ logistic-regression baseline wins. The advantage is specific to structure.
142
146
 
143
147
  ## Quick start
144
148
 
149
+ ```bash
150
+ pip install structuremappingmemory # from PyPI (import name: sma)
151
+ ```
152
+
153
+ ### Use as an MCP server (Codex, Claude Code, Claude Desktop)
154
+
155
+ SMA ships a Model Context Protocol server so an agentic LLM can mount your ontologies and
156
+ retrieve **structural analogs + cite-or-abstain + novelty** as tools. One command, zero
157
+ install (via [`uv`](https://docs.astral.sh/uv/)):
158
+
159
+ ```bash
160
+ codex mcp add sma -- uvx structuremappingmemory # Codex CLI
161
+ claude mcp add sma -- uvx structuremappingmemory # Claude Code
162
+ ```
163
+
164
+ `/mcp` to confirm, then ask it to `mount_ontology` / `retrieve` / `novelty`. Full guide,
165
+ manifest config, and alternatives (pipx / pip): [`docs/MCP.md`](docs/MCP.md).
166
+
167
+ Or for development, with the evaluation/encoder extras:
168
+
145
169
  ```bash
146
170
  python -m venv .venv && . .venv/bin/activate
147
171
  pip install -e ".[encoders,eval]"
@@ -1,6 +1,7 @@
1
1
  # SMA-1: Structure-Mapping Agentic Memory
2
2
 
3
3
  [![CI](https://github.com/ayazkhan27/SMA-1/actions/workflows/ci.yml/badge.svg)](https://github.com/ayazkhan27/SMA-1/actions/workflows/ci.yml)
4
+ [![PyPI](https://img.shields.io/pypi/v/structuremappingmemory)](https://pypi.org/project/structuremappingmemory/)
4
5
  [![License: Apache-2.0](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](LICENSE)
5
6
  [![HF Space](https://img.shields.io/badge/%F0%9F%A4%97%20Demo-Space-orange)](https://huggingface.co/spaces/zephyr27/SMA-1-demo)
6
7
 
@@ -49,13 +50,13 @@ adapters.
49
50
  On a **memory-swap benchmark** where only the retriever varies, SMA beats a strong
50
51
  RAG + KG baseline suite on the rare/long-tail slice across five domains:
51
52
 
52
- | Domain | SMA tail top-5 | best RAG | Δ | Holm-significant |
53
- |---|:--:|:--:|:--:|:--:|
54
- | Medicine (HPO/MONDO) | 0.949 | 0.606 | **+0.333** | yes |
55
- | Finance (US-GAAP) | 0.418 | 0.231 | **+0.167** | yes |
56
- | Genomics (GO) | 0.849 | 0.682 | **+0.156** | yes |
57
- | Legal (CPC, all-query) | 0.941 | 0.870 | **+0.064** | yes |
58
- | Cyber (ATT&CK) | 0.766 | 0.749 | +0.073 | directional* |
53
+ | Domain | SMA tail top-5 | best RAG | Δ tail top-5 | Cliff's δ | Holm-significant |
54
+ |---|:--:|:--:|:--:|:--:|:--:|
55
+ | Medicine (HPO/MONDO) | 0.949 | 0.606 | **+0.343** | 0.333 | yes |
56
+ | Finance (US-GAAP) | 0.418 | 0.231 | **+0.187** | 0.167 | yes |
57
+ | Genomics (GO) | 0.849 | 0.682 | **+0.167** | 0.156 | yes |
58
+ | Legal (CPC, all-query) | 0.941 | 0.870 | **+0.071** | 0.064 | yes |
59
+ | Cyber (ATT&CK) | 0.766 | 0.749 | +0.017 | 0.073 | directional* |
59
60
 
60
61
  \* Cyber survives Holm across domains but not a conservative
61
62
  Bonferroni-over-baselines selection correction (p=0.035 → 0.17); reported as
@@ -88,6 +89,26 @@ logistic-regression baseline wins. The advantage is specific to structure.
88
89
 
89
90
  ## Quick start
90
91
 
92
+ ```bash
93
+ pip install structuremappingmemory # from PyPI (import name: sma)
94
+ ```
95
+
96
+ ### Use as an MCP server (Codex, Claude Code, Claude Desktop)
97
+
98
+ SMA ships a Model Context Protocol server so an agentic LLM can mount your ontologies and
99
+ retrieve **structural analogs + cite-or-abstain + novelty** as tools. One command, zero
100
+ install (via [`uv`](https://docs.astral.sh/uv/)):
101
+
102
+ ```bash
103
+ codex mcp add sma -- uvx structuremappingmemory # Codex CLI
104
+ claude mcp add sma -- uvx structuremappingmemory # Claude Code
105
+ ```
106
+
107
+ `/mcp` to confirm, then ask it to `mount_ontology` / `retrieve` / `novelty`. Full guide,
108
+ manifest config, and alternatives (pipx / pip): [`docs/MCP.md`](docs/MCP.md).
109
+
110
+ Or for development, with the evaluation/encoder extras:
111
+
91
112
  ```bash
92
113
  python -m venv .venv && . .venv/bin/activate
93
114
  pip install -e ".[encoders,eval]"
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
5
5
  [project]
6
6
  # PyPI distribution name (the import name stays `sma`); `sma` is taken on PyPI.
7
7
  name = "structuremappingmemory"
8
- version = "1.0.0"
8
+ version = "1.1.1"
9
9
  description = "SMA-1: Structure-Mapping Agentic Memory — a structure-mapping retrieval memory that grounds language models in curated ontologies"
10
10
  readme = "README.md"
11
11
  requires-python = ">=3.10"
@@ -35,7 +35,8 @@ dependencies = [
35
35
  "regex>=2024.5.15",
36
36
  "scikit-learn>=1.5",
37
37
  "typer>=0.12",
38
- "zstandard>=0.22"
38
+ "zstandard>=0.22",
39
+ "mcp>=1.0"
39
40
  ]
40
41
 
41
42
  [project.optional-dependencies]
@@ -60,6 +61,9 @@ local-llm = [
60
61
  "huggingface-hub>=0.23",
61
62
  "llama-cpp-python>=0.2.90"
62
63
  ]
64
+ mcp = [
65
+ "mcp>=1.0"
66
+ ]
63
67
 
64
68
  [project.urls]
65
69
  Homepage = "https://github.com/ayazkhan27/SMA-1"
@@ -68,6 +72,8 @@ Issues = "https://github.com/ayazkhan27/SMA-1/issues"
68
72
 
69
73
  [project.scripts]
70
74
  sma = "sma.cli:main"
75
+ sma-mcp = "sma.mcp:main"
76
+ structuremappingmemory = "sma.mcp:main"
71
77
 
72
78
  [tool.setuptools.packages.find]
73
79
  include = ["sma*"]
@@ -1,5 +1,5 @@
1
1
  """SMA-1: Structure-Mapping Agentic Memory MVP."""
2
2
 
3
3
  __all__ = ["__version__"]
4
- __version__ = "0.1.0"
4
+ __version__ = "1.1.1"
5
5
 
@@ -0,0 +1,393 @@
1
+ """SMA-1 Model Context Protocol (MCP) server.
2
+
3
+ Exposes the structure-mapping memory as MCP tools so an MCP client (Codex CLI,
4
+ Claude, the OpenAI Agents SDK, ...) can:
5
+
6
+ * mount curated ontologies (one shared core, many domains),
7
+ * index a case base (records expressed as ontology term-ids),
8
+ * retrieve *structurally-analogous* prior cases by logical structure
9
+ (is-a subsumption + typed relations + rarity weighting), with a checkable
10
+ structural citation, a cite-or-abstain decision, and an expectation-violation
11
+ **novelty** flag.
12
+
13
+ This is the analogical-memory layer for a discovery loop: the LLM generates and
14
+ verifies; SMA grounds each step in structurally-analogous precedent and flags the
15
+ genuinely never-seen, which surface-similarity (vector RAG) cannot do.
16
+
17
+ Design notes
18
+ ------------
19
+ * The engine (:class:`SmaEngine`) is import-light: it uses only the SMA core
20
+ (``sma.ontology`` / ``sma.index`` / ``sma.sage``), NOT the eval baselines
21
+ (bm25 / dense / hipporag) and NOT the ``mcp`` SDK -- so it is unit-testable
22
+ without the transport dependency. ``mcp`` is imported lazily in
23
+ :func:`build_server` / :func:`main`.
24
+ * Cite-or-abstain gates on the RAW structural grounding score (the codebase is
25
+ explicit that the normalized confidence saturates and does not separate
26
+ known/unknown). A per-ontology ``ground_threshold`` is calibrated offline; when
27
+ unset there is no gate (every non-empty result is "grounded") -- calibrate it.
28
+
29
+ Run as a stdio MCP server::
30
+
31
+ python -m sma.mcp # or: sma-mcp
32
+
33
+ Configure a manifest of ontologies to auto-register via the ``SMA_MANIFEST`` env
34
+ var (see ``examples/sma_manifest.example.json``).
35
+ """
36
+ from __future__ import annotations
37
+
38
+ import json
39
+ import os
40
+ from dataclasses import dataclass, field
41
+ from typing import Any
42
+
43
+ from sma.index.macfac import MacFacIndex
44
+ from sma.ontology import (
45
+ MountedOntology,
46
+ load_attack_stix,
47
+ load_capec,
48
+ load_cpc,
49
+ load_cwe,
50
+ load_mitre_xml,
51
+ load_obo,
52
+ load_ontology,
53
+ load_owl,
54
+ load_owl_dir,
55
+ load_rdflib,
56
+ load_usgaap,
57
+ mount,
58
+ )
59
+ from sma.sage.pools import SagePool
60
+
61
+ # Format -> loader. "auto" dispatches obo/owl by file extension.
62
+ _LOADERS = {
63
+ "auto": load_ontology,
64
+ "obo": load_obo,
65
+ "owl": load_owl,
66
+ "owl_dir": load_owl_dir,
67
+ "rdf": load_rdflib,
68
+ "ttl": load_rdflib,
69
+ "stix": load_attack_stix,
70
+ "attack": load_attack_stix,
71
+ "cpc": load_cpc,
72
+ "xbrl": load_usgaap,
73
+ "usgaap": load_usgaap,
74
+ "cwe": load_cwe,
75
+ "capec": load_capec,
76
+ "mitre_xml": load_mitre_xml,
77
+ }
78
+
79
+ SUPPORTED_FORMATS = sorted(_LOADERS)
80
+
81
+ _SENTINEL = object()
82
+
83
+
84
+ def _load_graph(path: str, fmt: str | None):
85
+ fmt = (fmt or "auto").lower()
86
+ loader = _LOADERS.get(fmt)
87
+ if loader is None:
88
+ raise ValueError(f"unknown ontology format {fmt!r}; supported: {SUPPORTED_FORMATS}")
89
+ return loader(path)
90
+
91
+
92
+ def _envfloat(name: str, default: float | None) -> float | None:
93
+ val = os.environ.get(name)
94
+ if val is None or val == "":
95
+ return default
96
+ try:
97
+ return float(val)
98
+ except ValueError:
99
+ return default
100
+
101
+
102
+ @dataclass
103
+ class _Ont:
104
+ """A registered ontology and (lazily) its mounted lattice + case index."""
105
+
106
+ name: str
107
+ path: str
108
+ fmt: str = "auto"
109
+ ground_threshold: float | None = None
110
+ novelty_threshold: float = 0.5
111
+ mounted: MountedOntology | None = None
112
+ cases: list[dict] = field(default_factory=list)
113
+ _index: Any = None
114
+ _pool: Any = None
115
+ _keymap: dict[str, str] = field(default_factory=dict)
116
+
117
+
118
+ class SmaEngine:
119
+ """The MCP-free core: register/mount ontologies, index cases, retrieve, novelty.
120
+
121
+ Mirrors ``sma.eval.agentic.memories.SmaMemory`` (build_case + MacFacIndex +
122
+ SagePool) without importing the eval module's heavy baseline dependencies.
123
+ """
124
+
125
+ def __init__(self) -> None:
126
+ self.onts: dict[str, _Ont] = {}
127
+ self.default_ground_threshold = _envfloat("SMA_GROUND_THRESHOLD", None)
128
+ self.default_novelty_threshold = _envfloat("SMA_NOVELTY_THRESHOLD", 0.5) or 0.5
129
+
130
+ # -- registration / mounting -------------------------------------------
131
+ def register(
132
+ self,
133
+ name: str,
134
+ path: str,
135
+ fmt: str = "auto",
136
+ ground_threshold: float | None = None,
137
+ novelty_threshold: float | None = None,
138
+ ) -> None:
139
+ self.onts[name] = _Ont(
140
+ name=name,
141
+ path=path,
142
+ fmt=(fmt or "auto"),
143
+ ground_threshold=(
144
+ ground_threshold if ground_threshold is not None else self.default_ground_threshold
145
+ ),
146
+ novelty_threshold=(
147
+ novelty_threshold if novelty_threshold is not None else self.default_novelty_threshold
148
+ ),
149
+ )
150
+
151
+ def _get(self, name: str) -> _Ont:
152
+ o = self.onts.get(name)
153
+ if o is None:
154
+ raise KeyError(f"ontology {name!r} is not registered; mount it first ({sorted(self.onts)})")
155
+ return o
156
+
157
+ def _ensure_mounted(self, name: str) -> _Ont:
158
+ o = self._get(name)
159
+ if o.mounted is None:
160
+ o.mounted = mount(_load_graph(o.path, o.fmt))
161
+ return o
162
+
163
+ def mount_ontology(self, name: str, path: str, fmt: str = "auto") -> dict:
164
+ self.register(name, path, fmt)
165
+ o = self._ensure_mounted(name)
166
+ return {
167
+ "ontology": name,
168
+ "format": o.fmt,
169
+ "concepts": len(o.mounted.graph.terms),
170
+ "indexed_cases": len(o.cases),
171
+ }
172
+
173
+ # -- indexing -----------------------------------------------------------
174
+ def _rebuild(self, o: _Ont) -> None:
175
+ o._keymap = {}
176
+ o._pool = SagePool("mcp", assimilation_threshold=0.2)
177
+ cases = []
178
+ for c in o.cases:
179
+ case = o.mounted.build_case(frozenset(c["term_ids"]), metadata={"key": c["key"]})
180
+ o._keymap[case.case_id] = c["key"]
181
+ cases.append(case)
182
+ o._pool.assimilate(case)
183
+ o._index = MacFacIndex(config=o.mounted.config, canon=o.mounted.canon)
184
+ o._index.build(cases)
185
+
186
+ def index_cases(self, name: str, cases: list[dict]) -> int:
187
+ """Append cases (each ``{key, term_ids, text?}``) and (re)build the index."""
188
+ o = self._ensure_mounted(name)
189
+ for c in cases:
190
+ if "key" not in c or "term_ids" not in c:
191
+ raise ValueError("each case needs 'key' and 'term_ids'")
192
+ o.cases.append({"key": str(c["key"]), "term_ids": list(c["term_ids"]), "text": c.get("text", "")})
193
+ self._rebuild(o)
194
+ return len(o.cases)
195
+
196
+ # -- encoding (no LLM): raw text -> ontology term-ids -------------------
197
+ def encode_text(self, name: str, text: str, max_terms: int = 20) -> dict:
198
+ """Deterministic lexical match of term NAMES in ``text`` -> term-ids.
199
+
200
+ A starter encoder (no LLM, honoring the no-LLM-in-extraction principle).
201
+ Replace with a domain-specific encoder for production recall.
202
+ """
203
+ o = self._ensure_mounted(name)
204
+ hay = f" {text.lower()} "
205
+ hits: list[tuple[str, str]] = []
206
+ for tid, term in o.mounted.graph.terms.items():
207
+ nm = (getattr(term, "name", "") or "").strip().lower()
208
+ if len(nm) >= 3 and f" {nm} " in hay:
209
+ hits.append((tid, term.name))
210
+ hits.sort(key=lambda h: -len(h[1])) # prefer more specific (longer) names
211
+ hits = hits[:max_terms]
212
+ return {"ontology": name, "term_ids": [h[0] for h in hits], "matched_names": [h[1] for h in hits]}
213
+
214
+ def _resolve_terms(self, o: _Ont, term_ids, text) -> frozenset[str]:
215
+ if term_ids:
216
+ return frozenset(term_ids)
217
+ if text:
218
+ return frozenset(self.encode_text(o.name, text)["term_ids"])
219
+ raise ValueError("provide either term_ids or text")
220
+
221
+ # -- retrieval / novelty ------------------------------------------------
222
+ def retrieve(
223
+ self,
224
+ name: str,
225
+ term_ids=None,
226
+ text: str = "",
227
+ k: int = 5,
228
+ ground_threshold=_SENTINEL,
229
+ ) -> dict:
230
+ o = self._ensure_mounted(name)
231
+ tids = self._resolve_terms(o, term_ids, text)
232
+ if not tids:
233
+ return {"ontology": name, "abstain": True, "reason": "no query term-ids (encoding matched nothing)",
234
+ "citations": [], "novelty": 1.0, "novelty_flag": True, "query_term_ids": []}
235
+ if o._index is None:
236
+ return {"ontology": name, "abstain": True, "reason": "no cases indexed",
237
+ "citations": [], "novelty": 1.0, "novelty_flag": True, "query_term_ids": sorted(tids)}
238
+ qc = o.mounted.build_case(tids)
239
+ res = o._index.retrieve(qc, k=k, shortlist=80, fac_budget=40)
240
+ nov = float(o._pool.expectation_violation(qc))
241
+ thr = o.ground_threshold if ground_threshold is _SENTINEL else ground_threshold
242
+ top = max((r.score for r in res), default=0.0) or 1.0
243
+ citations = [
244
+ {
245
+ "id": o._keymap.get(r.case_id, ""),
246
+ "score": round(float(r.score), 4),
247
+ "confidence": round(min(max(r.score / top, 0.0), 1.0), 4),
248
+ "rank": i,
249
+ }
250
+ for i, r in enumerate(res, 1)
251
+ ]
252
+ grounded = bool(res) and (thr is None or res[0].score >= thr)
253
+ return {
254
+ "ontology": name,
255
+ "query_term_ids": sorted(tids),
256
+ "abstain": not grounded,
257
+ "abstain_threshold": thr,
258
+ "novelty": round(nov, 4),
259
+ "novelty_flag": nov >= o.novelty_threshold,
260
+ "citations": citations if grounded else [],
261
+ "note": (
262
+ None if grounded
263
+ else "top grounding score below threshold -> no structural precedent; do not fabricate an answer"
264
+ ),
265
+ }
266
+
267
+ def novelty(self, name: str, term_ids=None, text: str = "") -> dict:
268
+ o = self._ensure_mounted(name)
269
+ tids = self._resolve_terms(o, term_ids, text)
270
+ if o._pool is None or not tids:
271
+ return {"ontology": name, "novelty": 1.0, "novelty_flag": True, "query_term_ids": sorted(tids)}
272
+ qc = o.mounted.build_case(tids)
273
+ nov = float(o._pool.expectation_violation(qc))
274
+ return {
275
+ "ontology": name,
276
+ "query_term_ids": sorted(tids),
277
+ "novelty": round(nov, 4),
278
+ "novelty_flag": nov >= o.novelty_threshold,
279
+ }
280
+
281
+ def list_ontologies(self) -> dict:
282
+ return {
283
+ "ontologies": [
284
+ {
285
+ "name": o.name,
286
+ "format": o.fmt,
287
+ "mounted": o.mounted is not None,
288
+ "concepts": (len(o.mounted.graph.terms) if o.mounted is not None else None),
289
+ "indexed_cases": len(o.cases),
290
+ "ground_threshold": o.ground_threshold,
291
+ "novelty_threshold": o.novelty_threshold,
292
+ }
293
+ for o in self.onts.values()
294
+ ],
295
+ "supported_formats": SUPPORTED_FORMATS,
296
+ }
297
+
298
+
299
+ def load_manifest(engine: SmaEngine, path: str) -> None:
300
+ """Register ontologies (and optionally eager-index cases) from a JSON manifest.
301
+
302
+ ``{"ontologies": [{"name","path","format"?,"ground_threshold"?,"novelty_threshold"?}],
303
+ "cases": {"<ontology>": [{"key","term_ids","text"?}, ...]}}``
304
+ """
305
+ with open(path) as fh:
306
+ data = json.load(fh)
307
+ for o in data.get("ontologies", []):
308
+ engine.register(
309
+ o["name"], o["path"], o.get("format", "auto"),
310
+ o.get("ground_threshold"), o.get("novelty_threshold"),
311
+ )
312
+ for name, cases in (data.get("cases") or {}).items():
313
+ if cases:
314
+ engine.index_cases(name, cases)
315
+
316
+
317
+ def build_server(engine: SmaEngine | None = None):
318
+ """Construct the FastMCP server (imports the ``mcp`` SDK lazily)."""
319
+ from mcp.server.fastmcp import FastMCP
320
+
321
+ engine = engine or SmaEngine()
322
+ manifest = os.environ.get("SMA_MANIFEST")
323
+ if manifest and os.path.exists(manifest):
324
+ load_manifest(engine, manifest)
325
+
326
+ server = FastMCP("sma-1")
327
+
328
+ @server.tool()
329
+ def list_ontologies() -> dict:
330
+ """List registered ontologies, their concept counts, indexed-case counts, and supported source formats."""
331
+ return engine.list_ontologies()
332
+
333
+ @server.tool()
334
+ def mount_ontology(name: str, path: str, format: str = "auto") -> dict:
335
+ """Register and mount a curated ontology as a structural lattice.
336
+
337
+ `format` is auto-detected for OBO/OWL; pass one of the supported formats
338
+ (stix, cpc, xbrl, cwe, capec, mitre_xml, rdf, ...) otherwise. Returns the
339
+ concept count once mounted."""
340
+ return engine.mount_ontology(name, path, format)
341
+
342
+ @server.tool()
343
+ def index_cases(ontology: str, cases: list[dict]) -> dict:
344
+ """Add cases to an ontology's memory. Each case is {"key","term_ids",["text"]}.
345
+
346
+ `key` is what gets cited back; `term_ids` are the ontology term-ids that
347
+ encode the case's structure. Returns the total number of indexed cases."""
348
+ total = engine.index_cases(ontology, cases)
349
+ return {"ontology": ontology, "indexed_cases": total}
350
+
351
+ @server.tool()
352
+ def encode_text(ontology: str, text: str, max_terms: int = 20) -> dict:
353
+ """Deterministically map free text to ontology term-ids via term-name matching (no LLM).
354
+
355
+ Use to turn an abstract/finding into `term_ids` before retrieve/novelty.
356
+ This is a starter encoder; swap in a domain encoder for better recall."""
357
+ return engine.encode_text(ontology, text, max_terms)
358
+
359
+ @server.tool()
360
+ def retrieve(
361
+ ontology: str,
362
+ text: str = "",
363
+ term_ids: list[str] | None = None,
364
+ k: int = 5,
365
+ ground_threshold: float | None = None,
366
+ ) -> dict:
367
+ """Retrieve STRUCTURALLY-ANALOGOUS prior cases by logical structure (not surface similarity).
368
+
369
+ Provide `term_ids` (preferred) or `text` (encoded on the fly). Returns ranked
370
+ citations {id, score, confidence, rank}, a calibrated cite-or-abstain decision
371
+ (`abstain`=True means no structural precedent -- do NOT fabricate), and a
372
+ `novelty` score (high = a case unlike anything indexed)."""
373
+ gt = _SENTINEL if ground_threshold is None else ground_threshold
374
+ return engine.retrieve(ontology, term_ids=term_ids, text=text, k=k, ground_threshold=gt)
375
+
376
+ @server.tool()
377
+ def novelty(ontology: str, text: str = "", term_ids: list[str] | None = None) -> dict:
378
+ """Score how novel a case is vs. the indexed memory (expectation-violation).
379
+
380
+ High `novelty` / `novelty_flag`=True means the case is structurally unlike
381
+ anything seen -- a candidate genuinely-new pattern."""
382
+ return engine.novelty(ontology, term_ids=term_ids, text=text)
383
+
384
+ return server, engine
385
+
386
+
387
+ def main() -> None:
388
+ server, _ = build_server()
389
+ server.run()
390
+
391
+
392
+ if __name__ == "__main__":
393
+ main()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: structuremappingmemory
3
- Version: 1.0.0
3
+ Version: 1.1.1
4
4
  Summary: SMA-1: Structure-Mapping Agentic Memory — a structure-mapping retrieval memory that grounds language models in curated ontologies
5
5
  Author-email: Ayaz Khan <aak2259@columbia.edu>
6
6
  License: Apache-2.0
@@ -32,6 +32,7 @@ Requires-Dist: regex>=2024.5.15
32
32
  Requires-Dist: scikit-learn>=1.5
33
33
  Requires-Dist: typer>=0.12
34
34
  Requires-Dist: zstandard>=0.22
35
+ Requires-Dist: mcp>=1.0
35
36
  Provides-Extra: encoders
36
37
  Requires-Dist: drain3>=0.9.11; extra == "encoders"
37
38
  Requires-Dist: tree-sitter>=0.23; extra == "encoders"
@@ -50,11 +51,14 @@ Requires-Dist: networkx>=3; extra == "eval"
50
51
  Provides-Extra: local-llm
51
52
  Requires-Dist: huggingface-hub>=0.23; extra == "local-llm"
52
53
  Requires-Dist: llama-cpp-python>=0.2.90; extra == "local-llm"
54
+ Provides-Extra: mcp
55
+ Requires-Dist: mcp>=1.0; extra == "mcp"
53
56
  Dynamic: license-file
54
57
 
55
58
  # SMA-1: Structure-Mapping Agentic Memory
56
59
 
57
60
  [![CI](https://github.com/ayazkhan27/SMA-1/actions/workflows/ci.yml/badge.svg)](https://github.com/ayazkhan27/SMA-1/actions/workflows/ci.yml)
61
+ [![PyPI](https://img.shields.io/pypi/v/structuremappingmemory)](https://pypi.org/project/structuremappingmemory/)
58
62
  [![License: Apache-2.0](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](LICENSE)
59
63
  [![HF Space](https://img.shields.io/badge/%F0%9F%A4%97%20Demo-Space-orange)](https://huggingface.co/spaces/zephyr27/SMA-1-demo)
60
64
 
@@ -103,13 +107,13 @@ adapters.
103
107
  On a **memory-swap benchmark** where only the retriever varies, SMA beats a strong
104
108
  RAG + KG baseline suite on the rare/long-tail slice across five domains:
105
109
 
106
- | Domain | SMA tail top-5 | best RAG | Δ | Holm-significant |
107
- |---|:--:|:--:|:--:|:--:|
108
- | Medicine (HPO/MONDO) | 0.949 | 0.606 | **+0.333** | yes |
109
- | Finance (US-GAAP) | 0.418 | 0.231 | **+0.167** | yes |
110
- | Genomics (GO) | 0.849 | 0.682 | **+0.156** | yes |
111
- | Legal (CPC, all-query) | 0.941 | 0.870 | **+0.064** | yes |
112
- | Cyber (ATT&CK) | 0.766 | 0.749 | +0.073 | directional* |
110
+ | Domain | SMA tail top-5 | best RAG | Δ tail top-5 | Cliff's δ | Holm-significant |
111
+ |---|:--:|:--:|:--:|:--:|:--:|
112
+ | Medicine (HPO/MONDO) | 0.949 | 0.606 | **+0.343** | 0.333 | yes |
113
+ | Finance (US-GAAP) | 0.418 | 0.231 | **+0.187** | 0.167 | yes |
114
+ | Genomics (GO) | 0.849 | 0.682 | **+0.167** | 0.156 | yes |
115
+ | Legal (CPC, all-query) | 0.941 | 0.870 | **+0.071** | 0.064 | yes |
116
+ | Cyber (ATT&CK) | 0.766 | 0.749 | +0.017 | 0.073 | directional* |
113
117
 
114
118
  \* Cyber survives Holm across domains but not a conservative
115
119
  Bonferroni-over-baselines selection correction (p=0.035 → 0.17); reported as
@@ -142,6 +146,26 @@ logistic-regression baseline wins. The advantage is specific to structure.
142
146
 
143
147
  ## Quick start
144
148
 
149
+ ```bash
150
+ pip install structuremappingmemory # from PyPI (import name: sma)
151
+ ```
152
+
153
+ ### Use as an MCP server (Codex, Claude Code, Claude Desktop)
154
+
155
+ SMA ships a Model Context Protocol server so an agentic LLM can mount your ontologies and
156
+ retrieve **structural analogs + cite-or-abstain + novelty** as tools. One command, zero
157
+ install (via [`uv`](https://docs.astral.sh/uv/)):
158
+
159
+ ```bash
160
+ codex mcp add sma -- uvx structuremappingmemory # Codex CLI
161
+ claude mcp add sma -- uvx structuremappingmemory # Claude Code
162
+ ```
163
+
164
+ `/mcp` to confirm, then ask it to `mount_ontology` / `retrieve` / `novelty`. Full guide,
165
+ manifest config, and alternatives (pipx / pip): [`docs/MCP.md`](docs/MCP.md).
166
+
167
+ Or for development, with the evaluation/encoder extras:
168
+
145
169
  ```bash
146
170
  python -m venv .venv && . .venv/bin/activate
147
171
  pip install -e ".[encoders,eval]"
@@ -4,6 +4,7 @@ pyproject.toml
4
4
  sma/__init__.py
5
5
  sma/__main__.py
6
6
  sma/cli.py
7
+ sma/mcp.py
7
8
  sma/agent/__init__.py
8
9
  sma/agent/adapter_draft.py
9
10
  sma/agent/api.py
@@ -130,5 +131,6 @@ tests/test_bugsinpy_t3.py
130
131
  tests/test_drift.py
131
132
  tests/test_gates.py
132
133
  tests/test_hipporag.py
134
+ tests/test_mcp.py
133
135
  tests/test_production_loop.py
134
136
  tests/test_stats.py
@@ -0,0 +1,4 @@
1
+ [console_scripts]
2
+ sma = sma.cli:main
3
+ sma-mcp = sma.mcp:main
4
+ structuremappingmemory = sma.mcp:main