mental-models-mcp 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mental_models_mcp-0.1.0/LICENSE +21 -0
- mental_models_mcp-0.1.0/MANIFEST.in +3 -0
- mental_models_mcp-0.1.0/PKG-INFO +133 -0
- mental_models_mcp-0.1.0/README.md +105 -0
- mental_models_mcp-0.1.0/pyproject.toml +70 -0
- mental_models_mcp-0.1.0/setup.cfg +4 -0
- mental_models_mcp-0.1.0/src/mental_models_mcp/__init__.py +5 -0
- mental_models_mcp-0.1.0/src/mental_models_mcp/__main__.py +6 -0
- mental_models_mcp-0.1.0/src/mental_models_mcp/catalog.py +211 -0
- mental_models_mcp-0.1.0/src/mental_models_mcp/data/index.json +287 -0
- mental_models_mcp-0.1.0/src/mental_models_mcp/py.typed +0 -0
- mental_models_mcp-0.1.0/src/mental_models_mcp/router.py +177 -0
- mental_models_mcp-0.1.0/src/mental_models_mcp/server.py +310 -0
- mental_models_mcp-0.1.0/src/mental_models_mcp/telemetry.py +247 -0
- mental_models_mcp-0.1.0/src/mental_models_mcp/updates.py +153 -0
- mental_models_mcp-0.1.0/src/mental_models_mcp.egg-info/PKG-INFO +133 -0
- mental_models_mcp-0.1.0/src/mental_models_mcp.egg-info/SOURCES.txt +22 -0
- mental_models_mcp-0.1.0/src/mental_models_mcp.egg-info/dependency_links.txt +1 -0
- mental_models_mcp-0.1.0/src/mental_models_mcp.egg-info/entry_points.txt +3 -0
- mental_models_mcp-0.1.0/src/mental_models_mcp.egg-info/requires.txt +6 -0
- mental_models_mcp-0.1.0/src/mental_models_mcp.egg-info/top_level.txt +1 -0
- mental_models_mcp-0.1.0/tests/test_catalog_and_router.py +113 -0
- mental_models_mcp-0.1.0/tests/test_e2e_stdio.py +97 -0
- mental_models_mcp-0.1.0/tests/test_telemetry_redaction.py +62 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Surendran B
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mental-models-mcp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Multi-disciplinary mental models, cognitive orientation coaching, and dialectical reasoning engine for AI agents and humans. Zero API keys.
|
|
5
|
+
Author-email: Surendran B <reachsuren@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://mental-models.builditwithai.xyz
|
|
8
|
+
Project-URL: Repository, https://github.com/surendranb/mental-models-mcp
|
|
9
|
+
Project-URL: Issues, https://github.com/surendranb/mental-models-mcp/issues
|
|
10
|
+
Keywords: mental-models,munger-latticework,decision-making,cognitive-coaching,systems-thinking,first-principles,inversion,mcp,mcp-server,model-context-protocol,ai-agent,claude,claude-code,cursor
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
19
|
+
Requires-Python: >=3.10
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Requires-Dist: mcp<3,>=2.0.0
|
|
23
|
+
Requires-Dist: requests>=2.31.0
|
|
24
|
+
Provides-Extra: dev
|
|
25
|
+
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
|
26
|
+
Requires-Dist: pytest-asyncio>=0.23.0; extra == "dev"
|
|
27
|
+
Dynamic: license-file
|
|
28
|
+
|
|
29
|
+
# Mental Models MCP
|
|
30
|
+
|
|
31
|
+
> Multi-disciplinary mental models, cognitive orientation coaching, and dialectical reasoning engine for AI agents and humans. Zero API keys required.
|
|
32
|
+
|
|
33
|
+
`mental-models-mcp` equips AI assistants and human decision-makers with Charlie Munger's latticework of mental models across systems thinking, decision analysis, strategic risk, cognitive biases, and first-principles engineering.
|
|
34
|
+
|
|
35
|
+
Built for anyone navigating high-stakes decisions under uncertainty—software engineers evaluating architectural forks, founders choosing product trade-offs, analysts diagnosing systemic incidents, researchers stress-testing hypotheses, and autonomous AI agents seeking to avoid agreeable sycophancy.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Quickstart
|
|
40
|
+
|
|
41
|
+
### Option A — Any MCP Harness (incl. Claude Desktop, Cursor, Antigravity, Windsurf)
|
|
42
|
+
|
|
43
|
+
Add to your client configuration:
|
|
44
|
+
|
|
45
|
+
```json
|
|
46
|
+
{
|
|
47
|
+
"mcpServers": {
|
|
48
|
+
"mental-models": {
|
|
49
|
+
"command": "uvx",
|
|
50
|
+
"args": ["mental-models-mcp"]
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Or via NPX:
|
|
57
|
+
|
|
58
|
+
```json
|
|
59
|
+
{
|
|
60
|
+
"mcpServers": {
|
|
61
|
+
"mental-models": {
|
|
62
|
+
"command": "npx",
|
|
63
|
+
"args": ["-y", "mental-models-mcp"]
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Option B — Claude Code CLI Plugin
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
claude plugin marketplace add surendranb/mental-models-mcp
|
|
73
|
+
claude plugin install mental-models-mcp
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Option C — Standalone CLI Execution
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
uvx mental-models-mcp
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## Core Tools
|
|
85
|
+
|
|
86
|
+
1. **`route_mental_model(problem, domain, problem_type)`**:
|
|
87
|
+
Lightweight L0 index scoring candidate frameworks against your situation. Defaults to `status: "NONE"` on routine syntax or trivial formatting tasks to conserve context tokens.
|
|
88
|
+
2. **`get_mental_model(model_id, mode)`**:
|
|
89
|
+
Fetches the operational playbook for a mental model with 4 execution modes:
|
|
90
|
+
- `checklist` *(Default)*: Step-by-step thinking protocol, failure boundaries, and diagnostic probing questions.
|
|
91
|
+
- `explain`: Core principle, historical origin, and worked engineering example.
|
|
92
|
+
- `challenge_assumptions`: Dialectical anti-sycophancy sparring to uncover confirmation bias and unstated assumptions.
|
|
93
|
+
- `full`: Complete unabridged markdown playbook.
|
|
94
|
+
3. **`list_mental_models(category, discipline)`**:
|
|
95
|
+
Categorical and topical exploration across the 150-model master taxonomy.
|
|
96
|
+
4. **`skills_list()` / `skill_read(name)`**:
|
|
97
|
+
Dynamic runtime onboarding and reference guides fetched live from GitHub with local fallback.
|
|
98
|
+
5. **`check_for_updates()`**:
|
|
99
|
+
PyPI version verification and automated upgrade hints.
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## Catalog & Taxonomy
|
|
104
|
+
|
|
105
|
+
The latticework indexes **150 mental models** across 12 disciplines:
|
|
106
|
+
- Systems Thinking (Feedback Loops, Theory of Constraints, Tragedy of the Commons)
|
|
107
|
+
- Decision Analysis (First Principles, Inversion, Opportunity Cost, Two-Way Doors)
|
|
108
|
+
- Strategic Risk (Antifragility, Chesterton's Fence, Cynefin Framework, Lindy Effect, Via Negativa)
|
|
109
|
+
- Cognitive Biases (Confirmation Bias, Hanlon's Razor, Incentive-Caused Bias, Sunk Cost Fallacy)
|
|
110
|
+
- First-Principles Engineering (Conway's Law, Gall's Law, Goodhart's Law, Occam's Razor)
|
|
111
|
+
- Computer Systems (Little's Law, CAP Theorem, Backpressure, Amdahl's Law)
|
|
112
|
+
- Microeconomics & Game Theory (Pareto Efficiency, Nash Equilibrium, Comparative Advantage)
|
|
113
|
+
- Evolutionary Biology (Red Queen Effect, Aposematism, Evolutionary Mismatch)
|
|
114
|
+
- Physics & Thermodynamics (Entropy, Critical Mass, Activation Energy)
|
|
115
|
+
- Military Strategy (OODA Loop, Sun Tzu's Asymmetry, Defense in Depth)
|
|
116
|
+
- Cryptography & Trust (Zero-Knowledge Proofs, Byzantine Fault Tolerance)
|
|
117
|
+
- Capital Allocation & Finance (Compounding, Ergodicity, Kelly Criterion)
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## Telemetry & Privacy
|
|
122
|
+
|
|
123
|
+
`mental-models-mcp` collects anonymous usage telemetry to monitor server reliability, tool latency, and query patterns across client harnesses.
|
|
124
|
+
- Operational metrics: tool execution duration, status codes, and result counts.
|
|
125
|
+
- Intent analysis: query intent truncated up to 300 characters.
|
|
126
|
+
- Secret hygiene: credentials, API keys, and authorization tokens are automatically scrubbed (`[REDACTED]`).
|
|
127
|
+
- Opt out any time by setting `DO_NOT_TRACK=1` or `MCP_TELEMETRY_OPT_OUT=1`.
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## License
|
|
132
|
+
|
|
133
|
+
MIT © [Surendran B](https://github.com/surendranb)
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# Mental Models MCP
|
|
2
|
+
|
|
3
|
+
> Multi-disciplinary mental models, cognitive orientation coaching, and dialectical reasoning engine for AI agents and humans. Zero API keys required.
|
|
4
|
+
|
|
5
|
+
`mental-models-mcp` equips AI assistants and human decision-makers with Charlie Munger's latticework of mental models across systems thinking, decision analysis, strategic risk, cognitive biases, and first-principles engineering.
|
|
6
|
+
|
|
7
|
+
Built for anyone navigating high-stakes decisions under uncertainty—software engineers evaluating architectural forks, founders choosing product trade-offs, analysts diagnosing systemic incidents, researchers stress-testing hypotheses, and autonomous AI agents seeking to avoid agreeable sycophancy.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Quickstart
|
|
12
|
+
|
|
13
|
+
### Option A — Any MCP Harness (incl. Claude Desktop, Cursor, Antigravity, Windsurf)
|
|
14
|
+
|
|
15
|
+
Add to your client configuration:
|
|
16
|
+
|
|
17
|
+
```json
|
|
18
|
+
{
|
|
19
|
+
"mcpServers": {
|
|
20
|
+
"mental-models": {
|
|
21
|
+
"command": "uvx",
|
|
22
|
+
"args": ["mental-models-mcp"]
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Or via NPX:
|
|
29
|
+
|
|
30
|
+
```json
|
|
31
|
+
{
|
|
32
|
+
"mcpServers": {
|
|
33
|
+
"mental-models": {
|
|
34
|
+
"command": "npx",
|
|
35
|
+
"args": ["-y", "mental-models-mcp"]
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Option B — Claude Code CLI Plugin
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
claude plugin marketplace add surendranb/mental-models-mcp
|
|
45
|
+
claude plugin install mental-models-mcp
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Option C — Standalone CLI Execution
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
uvx mental-models-mcp
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Core Tools
|
|
57
|
+
|
|
58
|
+
1. **`route_mental_model(problem, domain, problem_type)`**:
|
|
59
|
+
Lightweight L0 index scoring candidate frameworks against your situation. Defaults to `status: "NONE"` on routine syntax or trivial formatting tasks to conserve context tokens.
|
|
60
|
+
2. **`get_mental_model(model_id, mode)`**:
|
|
61
|
+
Fetches the operational playbook for a mental model with 4 execution modes:
|
|
62
|
+
- `checklist` *(Default)*: Step-by-step thinking protocol, failure boundaries, and diagnostic probing questions.
|
|
63
|
+
- `explain`: Core principle, historical origin, and worked engineering example.
|
|
64
|
+
- `challenge_assumptions`: Dialectical anti-sycophancy sparring to uncover confirmation bias and unstated assumptions.
|
|
65
|
+
- `full`: Complete unabridged markdown playbook.
|
|
66
|
+
3. **`list_mental_models(category, discipline)`**:
|
|
67
|
+
Categorical and topical exploration across the 150-model master taxonomy.
|
|
68
|
+
4. **`skills_list()` / `skill_read(name)`**:
|
|
69
|
+
Dynamic runtime onboarding and reference guides fetched live from GitHub with local fallback.
|
|
70
|
+
5. **`check_for_updates()`**:
|
|
71
|
+
PyPI version verification and automated upgrade hints.
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## Catalog & Taxonomy
|
|
76
|
+
|
|
77
|
+
The latticework indexes **150 mental models** across 12 disciplines:
|
|
78
|
+
- Systems Thinking (Feedback Loops, Theory of Constraints, Tragedy of the Commons)
|
|
79
|
+
- Decision Analysis (First Principles, Inversion, Opportunity Cost, Two-Way Doors)
|
|
80
|
+
- Strategic Risk (Antifragility, Chesterton's Fence, Cynefin Framework, Lindy Effect, Via Negativa)
|
|
81
|
+
- Cognitive Biases (Confirmation Bias, Hanlon's Razor, Incentive-Caused Bias, Sunk Cost Fallacy)
|
|
82
|
+
- First-Principles Engineering (Conway's Law, Gall's Law, Goodhart's Law, Occam's Razor)
|
|
83
|
+
- Computer Systems (Little's Law, CAP Theorem, Backpressure, Amdahl's Law)
|
|
84
|
+
- Microeconomics & Game Theory (Pareto Efficiency, Nash Equilibrium, Comparative Advantage)
|
|
85
|
+
- Evolutionary Biology (Red Queen Effect, Aposematism, Evolutionary Mismatch)
|
|
86
|
+
- Physics & Thermodynamics (Entropy, Critical Mass, Activation Energy)
|
|
87
|
+
- Military Strategy (OODA Loop, Sun Tzu's Asymmetry, Defense in Depth)
|
|
88
|
+
- Cryptography & Trust (Zero-Knowledge Proofs, Byzantine Fault Tolerance)
|
|
89
|
+
- Capital Allocation & Finance (Compounding, Ergodicity, Kelly Criterion)
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## Telemetry & Privacy
|
|
94
|
+
|
|
95
|
+
`mental-models-mcp` collects anonymous usage telemetry to monitor server reliability, tool latency, and query patterns across client harnesses.
|
|
96
|
+
- Operational metrics: tool execution duration, status codes, and result counts.
|
|
97
|
+
- Intent analysis: query intent truncated up to 300 characters.
|
|
98
|
+
- Secret hygiene: credentials, API keys, and authorization tokens are automatically scrubbed (`[REDACTED]`).
|
|
99
|
+
- Opt out any time by setting `DO_NOT_TRACK=1` or `MCP_TELEMETRY_OPT_OUT=1`.
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## License
|
|
104
|
+
|
|
105
|
+
MIT © [Surendran B](https://github.com/surendranb)
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "mental-models-mcp"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Multi-disciplinary mental models, cognitive orientation coaching, and dialectical reasoning engine for AI agents and humans. Zero API keys."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
authors = [
|
|
12
|
+
{name = "Surendran B", email = "reachsuren@gmail.com"}
|
|
13
|
+
]
|
|
14
|
+
requires-python = ">=3.10"
|
|
15
|
+
dependencies = [
|
|
16
|
+
"mcp>=2.0.0,<3",
|
|
17
|
+
"requests>=2.31.0",
|
|
18
|
+
]
|
|
19
|
+
keywords = [
|
|
20
|
+
"mental-models",
|
|
21
|
+
"munger-latticework",
|
|
22
|
+
"decision-making",
|
|
23
|
+
"cognitive-coaching",
|
|
24
|
+
"systems-thinking",
|
|
25
|
+
"first-principles",
|
|
26
|
+
"inversion",
|
|
27
|
+
"mcp",
|
|
28
|
+
"mcp-server",
|
|
29
|
+
"model-context-protocol",
|
|
30
|
+
"ai-agent",
|
|
31
|
+
"claude",
|
|
32
|
+
"claude-code",
|
|
33
|
+
"cursor"
|
|
34
|
+
]
|
|
35
|
+
classifiers = [
|
|
36
|
+
"Development Status :: 4 - Beta",
|
|
37
|
+
"Intended Audience :: Developers",
|
|
38
|
+
"Programming Language :: Python :: 3",
|
|
39
|
+
"Programming Language :: Python :: 3.10",
|
|
40
|
+
"Programming Language :: Python :: 3.11",
|
|
41
|
+
"Programming Language :: Python :: 3.12",
|
|
42
|
+
"Programming Language :: Python :: 3.13",
|
|
43
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
[project.urls]
|
|
47
|
+
Homepage = "https://mental-models.builditwithai.xyz"
|
|
48
|
+
Repository = "https://github.com/surendranb/mental-models-mcp"
|
|
49
|
+
Issues = "https://github.com/surendranb/mental-models-mcp/issues"
|
|
50
|
+
|
|
51
|
+
[project.scripts]
|
|
52
|
+
mental-models-mcp = "mental_models_mcp.server:main"
|
|
53
|
+
mental-models = "mental_models_mcp.server:main"
|
|
54
|
+
|
|
55
|
+
[project.optional-dependencies]
|
|
56
|
+
dev = [
|
|
57
|
+
"pytest>=8.0.0",
|
|
58
|
+
"pytest-asyncio>=0.23.0",
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
[tool.setuptools.packages.find]
|
|
62
|
+
where = ["src"]
|
|
63
|
+
|
|
64
|
+
[tool.setuptools.package-data]
|
|
65
|
+
mental_models_mcp = ["py.typed", "data/*.json"]
|
|
66
|
+
|
|
67
|
+
[tool.pytest.ini_options]
|
|
68
|
+
asyncio_mode = "auto"
|
|
69
|
+
asyncio_default_fixture_loop_scope = "function"
|
|
70
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
"""Catalog indexing, dynamic GitHub fetching, and local cache management for mental models."""
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
import re
|
|
7
|
+
import urllib.request
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any, Dict, List, Optional, Tuple
|
|
10
|
+
|
|
11
|
+
GITHUB_RAW_BASE = "https://raw.githubusercontent.com/surendranb/mental-models-mcp/main"
|
|
12
|
+
LOCAL_CACHE_DIR = Path.home() / ".cache" / "mental-models"
|
|
13
|
+
|
|
14
|
+
_CACHED_INDEX: Optional[Dict[str, Any]] = None
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def get_repo_root() -> Path:
|
|
18
|
+
"""Best effort to locate local repo root if running from source checkout."""
|
|
19
|
+
return Path(__file__).resolve().parents[2]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def load_index(force_reload: bool = False) -> Dict[str, Any]:
|
|
23
|
+
"""Loads models index from local package data, source checkout, or GitHub raw."""
|
|
24
|
+
global _CACHED_INDEX
|
|
25
|
+
if _CACHED_INDEX is not None and not force_reload:
|
|
26
|
+
return _CACHED_INDEX
|
|
27
|
+
|
|
28
|
+
# 1. Check bundled package data
|
|
29
|
+
package_data = Path(__file__).parent / "data" / "index.json"
|
|
30
|
+
if package_data.exists():
|
|
31
|
+
try:
|
|
32
|
+
_CACHED_INDEX = json.loads(package_data.read_text(encoding="utf-8"))
|
|
33
|
+
return _CACHED_INDEX
|
|
34
|
+
except Exception:
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
# 2. Check local repo root
|
|
38
|
+
repo_models_index = get_repo_root() / "models" / "index.json"
|
|
39
|
+
if repo_models_index.exists():
|
|
40
|
+
try:
|
|
41
|
+
_CACHED_INDEX = json.loads(repo_models_index.read_text(encoding="utf-8"))
|
|
42
|
+
return _CACHED_INDEX
|
|
43
|
+
except Exception:
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
# 3. Check disk cache
|
|
47
|
+
cache_index = LOCAL_CACHE_DIR / "index.json"
|
|
48
|
+
if cache_index.exists():
|
|
49
|
+
try:
|
|
50
|
+
_CACHED_INDEX = json.loads(cache_index.read_text(encoding="utf-8"))
|
|
51
|
+
return _CACHED_INDEX
|
|
52
|
+
except Exception:
|
|
53
|
+
pass
|
|
54
|
+
|
|
55
|
+
# 4. Fetch from GitHub raw
|
|
56
|
+
try:
|
|
57
|
+
url = f"{GITHUB_RAW_BASE}/models/index.json"
|
|
58
|
+
req = urllib.request.Request(url, headers={"User-Agent": "mental-models-mcp"})
|
|
59
|
+
with urllib.request.urlopen(req, timeout=4.0) as resp:
|
|
60
|
+
data = json.loads(resp.read().decode("utf-8"))
|
|
61
|
+
_CACHED_INDEX = data
|
|
62
|
+
LOCAL_CACHE_DIR.mkdir(parents=True, exist_ok=True)
|
|
63
|
+
cache_index.write_text(json.dumps(data, indent=2), encoding="utf-8")
|
|
64
|
+
return _CACHED_INDEX
|
|
65
|
+
except Exception:
|
|
66
|
+
pass
|
|
67
|
+
|
|
68
|
+
return {"version": "0.0.0", "categories": [], "models": []}
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def get_model_metadata(model_id: str) -> Optional[Dict[str, Any]]:
|
|
72
|
+
"""Lookup model definition from index by ID (normalized)."""
|
|
73
|
+
norm = (model_id or "").strip().lower().replace("-", "_")
|
|
74
|
+
index = load_index()
|
|
75
|
+
for m in index.get("models", []):
|
|
76
|
+
if m.get("id", "").lower().replace("-", "_") == norm:
|
|
77
|
+
return m
|
|
78
|
+
return None
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def fetch_model_markdown(model_path: str) -> Tuple[Optional[str], str]:
|
|
82
|
+
"""Fetches model markdown from local repo, disk cache, or GitHub raw."""
|
|
83
|
+
# 1. Local disk cache
|
|
84
|
+
cached_path = LOCAL_CACHE_DIR / model_path
|
|
85
|
+
if cached_path.exists():
|
|
86
|
+
try:
|
|
87
|
+
return cached_path.read_text(encoding="utf-8"), "cache"
|
|
88
|
+
except Exception:
|
|
89
|
+
pass
|
|
90
|
+
|
|
91
|
+
# 2. Local repository workspace (if developing locally)
|
|
92
|
+
repo_path = get_repo_root() / model_path
|
|
93
|
+
if repo_path.exists():
|
|
94
|
+
try:
|
|
95
|
+
return repo_path.read_text(encoding="utf-8"), "local"
|
|
96
|
+
except Exception:
|
|
97
|
+
pass
|
|
98
|
+
|
|
99
|
+
# 3. GitHub raw fetch
|
|
100
|
+
url = f"{GITHUB_RAW_BASE}/{model_path}"
|
|
101
|
+
try:
|
|
102
|
+
req = urllib.request.Request(url, headers={"User-Agent": "mental-models-mcp"})
|
|
103
|
+
with urllib.request.urlopen(req, timeout=5.0) as resp:
|
|
104
|
+
content = resp.read().decode("utf-8", "replace")
|
|
105
|
+
# Cache locally for offline reliability
|
|
106
|
+
try:
|
|
107
|
+
cached_path.parent.mkdir(parents=True, exist_ok=True)
|
|
108
|
+
cached_path.write_text(content, encoding="utf-8")
|
|
109
|
+
except Exception:
|
|
110
|
+
pass
|
|
111
|
+
return content, "github"
|
|
112
|
+
except Exception:
|
|
113
|
+
pass
|
|
114
|
+
|
|
115
|
+
return None, "missing"
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def parse_markdown_sections(text: str) -> Dict[str, str]:
|
|
119
|
+
"""Splits markdown into titled sections."""
|
|
120
|
+
sections: Dict[str, str] = {}
|
|
121
|
+
current_title = "header"
|
|
122
|
+
current_lines: List[str] = []
|
|
123
|
+
|
|
124
|
+
for line in text.splitlines():
|
|
125
|
+
if line.startswith("## "):
|
|
126
|
+
if current_lines:
|
|
127
|
+
sections[current_title] = "\n".join(current_lines).strip()
|
|
128
|
+
current_title = line[3:].strip()
|
|
129
|
+
current_lines = [line]
|
|
130
|
+
else:
|
|
131
|
+
current_lines.append(line)
|
|
132
|
+
|
|
133
|
+
if current_lines:
|
|
134
|
+
sections[current_title] = "\n".join(current_lines).strip()
|
|
135
|
+
|
|
136
|
+
return sections
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def slice_playbook(markdown_text: str, mode: str, metadata: Dict[str, Any]) -> str:
|
|
140
|
+
"""Slices markdown playbook by mode to prevent context bloat."""
|
|
141
|
+
mode_clean = (mode or "checklist").strip().lower()
|
|
142
|
+
if mode_clean == "full":
|
|
143
|
+
return markdown_text
|
|
144
|
+
|
|
145
|
+
sections = parse_markdown_sections(markdown_text)
|
|
146
|
+
header = sections.get("header", f"# {metadata.get('title', 'Mental Model')}")
|
|
147
|
+
|
|
148
|
+
# Section matchers
|
|
149
|
+
core_principle = next((v for k, v in sections.items() if "core principle" in k.lower()), "")
|
|
150
|
+
failure_boundaries = next((v for k, v in sections.items() if "not to use" in k.lower() or "failure" in k.lower()), "")
|
|
151
|
+
thinking_steps = next((v for k, v in sections.items() if "operational" in k.lower() or "steps" in k.lower()), "")
|
|
152
|
+
probing_questions = next((v for k, v in sections.items() if "probing" in k.lower() or "questions" in k.lower()), "")
|
|
153
|
+
worked_example = next((v for k, v in sections.items() if "example" in k.lower()), "")
|
|
154
|
+
|
|
155
|
+
parts = [header]
|
|
156
|
+
|
|
157
|
+
if mode_clean == "checklist":
|
|
158
|
+
# Actionable focus: failure boundaries + operational steps + diagnostic probing
|
|
159
|
+
if failure_boundaries:
|
|
160
|
+
parts.append(failure_boundaries)
|
|
161
|
+
if thinking_steps:
|
|
162
|
+
parts.append(thinking_steps)
|
|
163
|
+
if probing_questions:
|
|
164
|
+
parts.append(probing_questions)
|
|
165
|
+
elif mode_clean == "explain":
|
|
166
|
+
# Conceptual understanding: core principle + failure boundaries + worked example
|
|
167
|
+
if core_principle:
|
|
168
|
+
parts.append(core_principle)
|
|
169
|
+
if failure_boundaries:
|
|
170
|
+
parts.append(failure_boundaries)
|
|
171
|
+
if worked_example:
|
|
172
|
+
parts.append(worked_example)
|
|
173
|
+
elif mode_clean == "challenge_assumptions":
|
|
174
|
+
# Cognitive coaching / anti-sycophancy: probing questions + failure boundaries
|
|
175
|
+
if probing_questions:
|
|
176
|
+
parts.append(probing_questions)
|
|
177
|
+
if failure_boundaries:
|
|
178
|
+
parts.append(failure_boundaries)
|
|
179
|
+
else:
|
|
180
|
+
# Unknown mode fallback to full
|
|
181
|
+
return markdown_text
|
|
182
|
+
|
|
183
|
+
# If parsing found matching sections, join them; otherwise fallback to full text
|
|
184
|
+
if len(parts) > 1:
|
|
185
|
+
return "\n\n".join(p for p in parts if p.strip())
|
|
186
|
+
return markdown_text
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def list_models_catalog(category: str = "", discipline: str = "") -> List[Dict[str, Any]]:
|
|
190
|
+
"""List all models in index, optionally filtered."""
|
|
191
|
+
index = load_index()
|
|
192
|
+
models = index.get("models", [])
|
|
193
|
+
cat_norm = (category or "").strip().lower()
|
|
194
|
+
disc_norm = (discipline or "").strip().lower()
|
|
195
|
+
|
|
196
|
+
filtered = []
|
|
197
|
+
for m in models:
|
|
198
|
+
if cat_norm and cat_norm != m.get("category", "").lower():
|
|
199
|
+
continue
|
|
200
|
+
if disc_norm and disc_norm not in m.get("discipline", "").lower():
|
|
201
|
+
continue
|
|
202
|
+
filtered.append({
|
|
203
|
+
"id": m.get("id"),
|
|
204
|
+
"title": m.get("title"),
|
|
205
|
+
"category": m.get("category"),
|
|
206
|
+
"discipline": m.get("discipline"),
|
|
207
|
+
"description": m.get("description"),
|
|
208
|
+
"domains": m.get("domains", []),
|
|
209
|
+
"problem_types": m.get("problem_types", []),
|
|
210
|
+
})
|
|
211
|
+
return filtered
|