boost-skill-cli 1.0.53__tar.gz → 1.0.55__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.
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/PKG-INFO +1 -1
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/boost_cli/_version.py +2 -2
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/boost_cli/core/frontmatter.py +8 -3
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/boost_skill_cli.egg-info/PKG-INFO +1 -1
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/boost_skill_cli.egg-info/SOURCES.txt +2 -0
- boost_skill_cli-1.0.55/docs/mcp-hub.html +473 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/docs/roadmap.html +20 -1
- boost_skill_cli-1.0.55/tests/unit/test_dense_fallback.py +463 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/tests/unit/test_frontmatter.py +11 -6
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/.gitignore +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/CLAUDE.md +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/CONTRIBUTING.md +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/LICENSE +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/MANIFEST.in +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/Makefile +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/README.md +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/SECURITY.md +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/boost +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/boost_cli/__init__.py +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/boost_cli/__main__.py +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/boost_cli/cli.py +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/boost_cli/cliparse.py +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/boost_cli/commands/__init__.py +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/boost_cli/commands/bmad.py +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/boost_cli/commands/configuration.py +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/boost_cli/commands/discovery.py +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/boost_cli/commands/hooks.py +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/boost_cli/commands/info.py +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/boost_cli/commands/intelligence.py +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/boost_cli/commands/pkg.py +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/boost_cli/commands/quality.py +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/boost_cli/commands/taps.py +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/boost_cli/commands/team.py +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/boost_cli/core/__init__.py +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/boost_cli/core/agents.py +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/boost_cli/core/ai.py +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/boost_cli/core/catalog.py +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/boost_cli/core/claude_settings.py +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/boost_cli/core/config.py +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/boost_cli/core/dense.py +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/boost_cli/core/embed.py +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/boost_cli/core/gitutil.py +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/boost_cli/core/journal.py +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/boost_cli/core/lockfile.py +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/boost_cli/core/logs.py +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/boost_cli/core/mcp.py +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/boost_cli/core/output.py +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/boost_cli/core/paths.py +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/boost_cli/core/policy.py +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/boost_cli/core/rag.py +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/boost_cli/core/registry.py +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/boost_cli/core/store.py +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/boost_cli/core/util.py +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/boost_cli/data/registries.json +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/boost_cli/errors.py +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/boost_cli/spin.py +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/boost_skill_cli.egg-info/dependency_links.txt +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/boost_skill_cli.egg-info/entry_points.txt +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/boost_skill_cli.egg-info/requires.txt +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/boost_skill_cli.egg-info/top_level.txt +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/docs/.claude/settings.local.json +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/docs/DEBUGGING.md +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/docs/demo.gif +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/docs/demo.tape +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/docs/design-roadmap.html +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/docs/index.html +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/docs/rag-architecture.md +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/index.html +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/pyproject.toml +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/scripts/build_registries.py +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/scripts/mutation_gate.py +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/setup.cfg +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/style/README.md +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/style/boost.css +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/style/boost.js +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/style/demo.html +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/tests/conftest.py +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/tests/functional/test_cli_bmad.py +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/tests/functional/test_cli_configuration.py +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/tests/functional/test_cli_discovery.py +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/tests/functional/test_cli_hooks.py +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/tests/functional/test_cli_info.py +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/tests/functional/test_cli_intelligence.py +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/tests/functional/test_cli_pkg.py +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/tests/functional/test_cli_quality.py +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/tests/functional/test_cli_taps.py +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/tests/functional/test_cli_team.py +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/tests/functional/test_everyday_loop.py +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/tests/make_fixture.py +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/tests/smoke.sh +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/tests/unit/test_agents.py +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/tests/unit/test_ai.py +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/tests/unit/test_catalog.py +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/tests/unit/test_claude_settings.py +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/tests/unit/test_cliparse.py +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/tests/unit/test_config.py +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/tests/unit/test_dense.py +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/tests/unit/test_embed.py +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/tests/unit/test_errors_and_cli_table.py +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/tests/unit/test_gitutil.py +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/tests/unit/test_journal.py +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/tests/unit/test_lockfile.py +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/tests/unit/test_logs.py +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/tests/unit/test_mcp.py +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/tests/unit/test_mutation_hardening.py +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/tests/unit/test_output.py +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/tests/unit/test_paths.py +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/tests/unit/test_policy.py +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/tests/unit/test_rag.py +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/tests/unit/test_registry.py +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/tests/unit/test_spin.py +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/tests/unit/test_store.py +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/tests/unit/test_token_parity.py +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/tests/unit/test_util.py +0 -0
- {boost_skill_cli-1.0.53 → boost_skill_cli-1.0.55}/tests/unit/test_version.py +0 -0
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '1.0.
|
|
22
|
-
__version_tuple__ = version_tuple = (1, 0,
|
|
21
|
+
__version__ = version = '1.0.55'
|
|
22
|
+
__version_tuple__ = version_tuple = (1, 0, 55)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -35,12 +35,17 @@ def _scalar(raw: str):
|
|
|
35
35
|
return ""
|
|
36
36
|
if len(s) >= 2 and s[0] == s[-1] and s[0] in "'\"":
|
|
37
37
|
return s[1:-1]
|
|
38
|
+
# YAML 1.2 core schema only: true/false and null/~. The 1.1 aliases
|
|
39
|
+
# (yes/no/on/off, none) are NOT coerced — they are ordinary English words
|
|
40
|
+
# that legitimately appear as a skill's name or tag ("on", "none"), and
|
|
41
|
+
# silently turning them into bool/None leaks the wrong type into search
|
|
42
|
+
# and ranking meta.
|
|
38
43
|
low = s.lower()
|
|
39
|
-
if low
|
|
44
|
+
if low == "true":
|
|
40
45
|
return True
|
|
41
|
-
if low
|
|
46
|
+
if low == "false":
|
|
42
47
|
return False
|
|
43
|
-
if low
|
|
48
|
+
if low == "null" or s == "~":
|
|
44
49
|
return None
|
|
45
50
|
try:
|
|
46
51
|
return int(s)
|
|
@@ -61,6 +61,7 @@ docs/demo.gif
|
|
|
61
61
|
docs/demo.tape
|
|
62
62
|
docs/design-roadmap.html
|
|
63
63
|
docs/index.html
|
|
64
|
+
docs/mcp-hub.html
|
|
64
65
|
docs/rag-architecture.md
|
|
65
66
|
docs/roadmap.html
|
|
66
67
|
docs/.claude/settings.local.json
|
|
@@ -91,6 +92,7 @@ tests/unit/test_claude_settings.py
|
|
|
91
92
|
tests/unit/test_cliparse.py
|
|
92
93
|
tests/unit/test_config.py
|
|
93
94
|
tests/unit/test_dense.py
|
|
95
|
+
tests/unit/test_dense_fallback.py
|
|
94
96
|
tests/unit/test_embed.py
|
|
95
97
|
tests/unit/test_errors_and_cli_table.py
|
|
96
98
|
tests/unit/test_frontmatter.py
|
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>boost — MCP as a hub (Phase 3)</title>
|
|
7
|
+
<meta name="description" content="How boost's MCP tool layer became an extensible registry: architecture, sequence diagrams, and an ELI5-vs-deep cheat sheet for the Phase-3 hub and its first reach-out tool, boost_discover_github.">
|
|
8
|
+
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Crect width='100' height='100' rx='20' fill='%2307080f'/%3E%3Ctext x='50' y='70' font-size='58' font-family='monospace' font-weight='bold' fill='%2322d3ee' text-anchor='middle'%3E%E2%9C%A6%3C/text%3E%3C/svg%3E">
|
|
9
|
+
<!-- Shared Aurora design system. Recolour everything by editing style/boost.css. -->
|
|
10
|
+
<link rel="stylesheet" href="../style/boost.css">
|
|
11
|
+
<style>
|
|
12
|
+
/* Page-specific layout — built only on boost.css :root tokens so it stays on-palette. */
|
|
13
|
+
main { padding-bottom: 40px; }
|
|
14
|
+
section { padding: 92px 0 8px; }
|
|
15
|
+
.hero { padding: 84px 0 8px; }
|
|
16
|
+
.hero h1 { font-size: clamp(38px, 6vw, 68px); font-weight: 800;
|
|
17
|
+
letter-spacing: -.035em; line-height: 1.05; max-width: 900px;
|
|
18
|
+
text-wrap: balance; margin-top: 24px; }
|
|
19
|
+
.hero p.lede { max-width: 680px; margin-top: 24px; font-size: 18.5px; color: var(--text-2); }
|
|
20
|
+
.crumbs { font-family: var(--mono); font-size: 12.5px; color: var(--text-3); margin-bottom: 4px; }
|
|
21
|
+
.crumbs a { color: var(--sky); text-decoration: none; }
|
|
22
|
+
.stats { margin-top: 58px; }
|
|
23
|
+
|
|
24
|
+
/* Diagram frame: a glass surface that scrolls its own wide SVG (body never scrolls X). */
|
|
25
|
+
.diagram { margin-top: 40px; padding: 22px; border: 1px solid var(--line);
|
|
26
|
+
border-radius: var(--radius-lg); background: var(--panel);
|
|
27
|
+
overflow-x: auto; }
|
|
28
|
+
.diagram svg { display: block; min-width: 640px; width: 100%; height: auto; }
|
|
29
|
+
.diagram + .caption, .caption { margin-top: 14px; font-size: 13.5px;
|
|
30
|
+
color: var(--text-3); font-family: var(--mono); }
|
|
31
|
+
|
|
32
|
+
/* SVG shared type via CSS classes (keeps the markup lean). */
|
|
33
|
+
svg .lbl { font-family: var(--mono); font-size: 13px; fill: var(--text); }
|
|
34
|
+
svg .lbl-s { font-family: var(--mono); font-size: 11px; fill: var(--text-2); }
|
|
35
|
+
svg .lbl-t { font-family: var(--mono); font-size: 11px; fill: var(--text-3); }
|
|
36
|
+
svg .head { font-family: var(--sans); font-size: 13.5px; font-weight: 700; fill: var(--text); }
|
|
37
|
+
svg .note { font-family: var(--mono); font-size: 10.5px; fill: var(--yellow); }
|
|
38
|
+
|
|
39
|
+
/* Cheat sheet: two synced columns of collapsibles (ELI5 | Deep technical). */
|
|
40
|
+
.cheat { margin-top: 40px; display: grid; grid-template-columns: 1fr 1fr; gap: 16px 18px; }
|
|
41
|
+
.cheat .col-head { font-family: var(--mono); font-size: 12px; font-weight: 700;
|
|
42
|
+
letter-spacing: .04em; padding: 6px 2px; }
|
|
43
|
+
.cheat .col-head.eli { color: var(--sky); }
|
|
44
|
+
.cheat .col-head.tech { color: var(--pink); }
|
|
45
|
+
details.card { border: 1px solid var(--line); border-radius: 14px;
|
|
46
|
+
background: var(--panel); padding: 0; transition: border-color .2s ease; }
|
|
47
|
+
details.card[open] { border-color: var(--line-hi); }
|
|
48
|
+
details.card > summary { cursor: pointer; list-style: none; padding: 16px 18px;
|
|
49
|
+
font-weight: 700; font-size: 15px; color: var(--text);
|
|
50
|
+
display: flex; align-items: center; gap: 10px; }
|
|
51
|
+
details.card > summary::-webkit-details-marker { display: none; }
|
|
52
|
+
details.card > summary::before { content: "▸"; color: var(--cyan);
|
|
53
|
+
font-size: 12px; transition: transform .2s ease; }
|
|
54
|
+
details.card[open] > summary::before { transform: rotate(90deg); }
|
|
55
|
+
details.card .body { padding: 0 18px 18px; font-size: 14.5px; color: var(--text-2);
|
|
56
|
+
line-height: 1.6; }
|
|
57
|
+
details.card .body p { margin-top: 10px; }
|
|
58
|
+
details.card .body code { font-family: var(--mono); font-size: 12.5px;
|
|
59
|
+
background: rgba(255,255,255,.05); padding: 1px 5px; border-radius: 5px;
|
|
60
|
+
color: var(--sky); }
|
|
61
|
+
details.eli5 > summary::before { color: var(--sky); }
|
|
62
|
+
details.tech > summary::before { color: var(--pink); }
|
|
63
|
+
.topic-tag { font-family: var(--mono); font-size: 11px; color: var(--text-3);
|
|
64
|
+
margin-left: auto; font-weight: 500; }
|
|
65
|
+
|
|
66
|
+
/* Before/after code panes. */
|
|
67
|
+
.split { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; margin-top: 40px; }
|
|
68
|
+
.split .window { margin: 0; }
|
|
69
|
+
.split .window pre { font-size: 12.5px; }
|
|
70
|
+
.tag-was { color: var(--pink); } .tag-now { color: var(--green); }
|
|
71
|
+
|
|
72
|
+
table.contract { width: 100%; border-collapse: collapse; margin-top: 32px; font-size: 14px; }
|
|
73
|
+
table.contract th, table.contract td { text-align: left; padding: 12px 14px;
|
|
74
|
+
border-bottom: 1px solid var(--line-soft); vertical-align: top; }
|
|
75
|
+
table.contract th { font-family: var(--mono); font-size: 12px; color: var(--text-2);
|
|
76
|
+
text-transform: uppercase; letter-spacing: .05em; }
|
|
77
|
+
table.contract td code { font-family: var(--mono); font-size: 12.5px; color: var(--sky); }
|
|
78
|
+
table.contract tr:last-child td { border-bottom: none; }
|
|
79
|
+
|
|
80
|
+
footer { margin-top: 96px; border-top: 1px solid var(--line-soft);
|
|
81
|
+
padding: 36px 0 56px; text-align: center; }
|
|
82
|
+
footer p { color: var(--text-3); font-size: 13px; }
|
|
83
|
+
footer a { color: var(--sky); text-decoration: none; }
|
|
84
|
+
|
|
85
|
+
@media (max-width: 820px) {
|
|
86
|
+
.cheat, .split { grid-template-columns: 1fr; }
|
|
87
|
+
.cheat .col-head.tech { margin-top: 8px; }
|
|
88
|
+
}
|
|
89
|
+
</style>
|
|
90
|
+
</head>
|
|
91
|
+
<body>
|
|
92
|
+
|
|
93
|
+
<main>
|
|
94
|
+
<!-- ===== hero ===== -->
|
|
95
|
+
<div class="wrap hero">
|
|
96
|
+
<p class="crumbs"><a href="index.html">boost</a> / docs / mcp-hub · Phase 3</p>
|
|
97
|
+
<span class="badge">RAG Phase 3 · shipped in PR #64</span>
|
|
98
|
+
<h1>MCP as a <span class="grad-text">hub</span>.</h1>
|
|
99
|
+
<p class="lede">boost's Model Context Protocol server used to dispatch tools through a flat
|
|
100
|
+
<code>if/elif</code> ladder. Phase 3 turns it into an <b>extensible registry</b>: every tool
|
|
101
|
+
self-registers a spec + handler, the server just iterates, and adding a capability — like the
|
|
102
|
+
new <code>boost_discover_github</code> reach-out — is one <code>register()</code> call with
|
|
103
|
+
zero dispatcher edits.</p>
|
|
104
|
+
|
|
105
|
+
<div class="stats">
|
|
106
|
+
<div class="stat reveal"><b>6</b><span>Registered MCP tools</span></div>
|
|
107
|
+
<div class="stat reveal"><b>0</b><span>Dispatcher edits to add one</span></div>
|
|
108
|
+
<div class="stat reveal"><b>89.6%</b><span>Mutation kill on core/</span></div>
|
|
109
|
+
<div class="stat reveal"><b>0</b><span>New runtime deps</span></div>
|
|
110
|
+
</div>
|
|
111
|
+
</div>
|
|
112
|
+
|
|
113
|
+
<!-- ===== architecture ===== -->
|
|
114
|
+
<section>
|
|
115
|
+
<div class="wrap">
|
|
116
|
+
<span class="eyebrow">Architecture</span>
|
|
117
|
+
<h2 class="sec-title">One registry, many handlers</h2>
|
|
118
|
+
<p class="sec-sub">The JSON-RPC server never knows what tools exist — it asks the
|
|
119
|
+
<code>Registry</code> in <code>core/mcp.py</code>. Handlers that reach out to the world
|
|
120
|
+
(like GitHub) probe for their dependency and <b>degrade to a message instead of crashing</b>.</p>
|
|
121
|
+
|
|
122
|
+
<div class="diagram reveal">
|
|
123
|
+
<svg viewBox="0 0 920 500" role="img" aria-label="Architecture: an MCP client speaks JSON-RPC over stdio to the boost server, which routes tools/list and tools/call through the core/mcp Registry to six self-registering handlers; the boost_discover_github handler reaches out to the gh CLI and degrades gracefully.">
|
|
124
|
+
<defs>
|
|
125
|
+
<marker id="ah" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
|
|
126
|
+
<path d="M0 0L10 5L0 10z" fill="#7dd3fc"/>
|
|
127
|
+
</marker>
|
|
128
|
+
<marker id="ahp" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
|
|
129
|
+
<path d="M0 0L10 5L0 10z" fill="#f472d0"/>
|
|
130
|
+
</marker>
|
|
131
|
+
</defs>
|
|
132
|
+
|
|
133
|
+
<!-- MCP client -->
|
|
134
|
+
<rect x="40" y="30" width="230" height="66" rx="12" fill="rgba(56,189,248,.08)" stroke="#38bdf8" stroke-width="1.2"/>
|
|
135
|
+
<text x="155" y="58" text-anchor="middle" class="head">MCP client</text>
|
|
136
|
+
<text x="155" y="78" text-anchor="middle" class="lbl-s">Claude Code · Cursor · Windsurf</text>
|
|
137
|
+
|
|
138
|
+
<!-- transport -->
|
|
139
|
+
<line x1="155" y1="96" x2="155" y2="150" stroke="#7dd3fc" stroke-width="1.4" marker-end="url(#ah)" marker-start="url(#ah)"/>
|
|
140
|
+
<text x="170" y="128" class="lbl-t">JSON-RPC 2.0 · newline-delimited over stdio</text>
|
|
141
|
+
|
|
142
|
+
<!-- server loop -->
|
|
143
|
+
<rect x="40" y="150" width="230" height="86" rx="12" fill="rgba(168,85,247,.08)" stroke="#a855f7" stroke-width="1.2"/>
|
|
144
|
+
<text x="155" y="178" text-anchor="middle" class="head">_mcp_serve_stdio()</text>
|
|
145
|
+
<text x="155" y="199" text-anchor="middle" class="lbl-s">read line → dispatch → send</text>
|
|
146
|
+
<text x="155" y="217" text-anchor="middle" class="lbl-t">initialize · ping · tools/list · tools/call</text>
|
|
147
|
+
|
|
148
|
+
<!-- registry -->
|
|
149
|
+
<rect x="360" y="120" width="250" height="150" rx="14" fill="rgba(34,211,238,.07)" stroke="#22d3ee" stroke-width="1.4"/>
|
|
150
|
+
<text x="485" y="148" text-anchor="middle" class="head">REGISTRY — core/mcp.py</text>
|
|
151
|
+
<text x="485" y="168" text-anchor="middle" class="lbl-t">ordered name → (spec, handler)</text>
|
|
152
|
+
<rect x="378" y="182" width="214" height="30" rx="7" fill="rgba(255,255,255,.04)" stroke="var(--line-hi)"/>
|
|
153
|
+
<text x="485" y="201" text-anchor="middle" class="lbl-s">.specs() → tools/list</text>
|
|
154
|
+
<rect x="378" y="220" width="214" height="30" rx="7" fill="rgba(255,255,255,.04)" stroke="var(--line-hi)"/>
|
|
155
|
+
<text x="485" y="239" text-anchor="middle" class="lbl-s">.call(name, args) → tools/call</text>
|
|
156
|
+
|
|
157
|
+
<!-- server to registry -->
|
|
158
|
+
<line x1="270" y1="193" x2="360" y2="193" stroke="#7dd3fc" stroke-width="1.4" marker-end="url(#ah)"/>
|
|
159
|
+
|
|
160
|
+
<!-- handlers -->
|
|
161
|
+
<text x="770" y="52" text-anchor="middle" class="head">Handlers (self-registered)</text>
|
|
162
|
+
<g font-family="var(--mono)">
|
|
163
|
+
<rect x="660" y="70" width="220" height="26" rx="6" fill="rgba(255,255,255,.04)" stroke="var(--line-hi)"/>
|
|
164
|
+
<text x="672" y="87" class="lbl-s">boost_search</text>
|
|
165
|
+
<rect x="660" y="102" width="220" height="26" rx="6" fill="rgba(255,255,255,.04)" stroke="var(--line-hi)"/>
|
|
166
|
+
<text x="672" y="119" class="lbl-s">boost_list</text>
|
|
167
|
+
<rect x="660" y="134" width="220" height="26" rx="6" fill="rgba(255,255,255,.04)" stroke="var(--line-hi)"/>
|
|
168
|
+
<text x="672" y="151" class="lbl-s">boost_info</text>
|
|
169
|
+
<rect x="660" y="166" width="220" height="26" rx="6" fill="rgba(255,255,255,.04)" stroke="var(--line-hi)"/>
|
|
170
|
+
<text x="672" y="183" class="lbl-s">boost_install</text>
|
|
171
|
+
<rect x="660" y="198" width="220" height="26" rx="6" fill="rgba(255,255,255,.04)" stroke="var(--line-hi)"/>
|
|
172
|
+
<text x="672" y="215" class="lbl-s">boost_doctor</text>
|
|
173
|
+
<rect x="660" y="230" width="220" height="26" rx="6" fill="rgba(244,114,208,.10)" stroke="#f472d0" stroke-width="1.2"/>
|
|
174
|
+
<text x="672" y="247" class="lbl-s" fill="#f9a8d4">boost_discover_github ✦ new</text>
|
|
175
|
+
</g>
|
|
176
|
+
<!-- registry to handlers -->
|
|
177
|
+
<line x1="610" y1="180" x2="660" y2="150" stroke="#7dd3fc" stroke-width="1.2" marker-end="url(#ah)"/>
|
|
178
|
+
<line x1="610" y1="205" x2="660" y2="243" stroke="#f472d0" stroke-width="1.2" marker-end="url(#ahp)"/>
|
|
179
|
+
|
|
180
|
+
<!-- reach-out -->
|
|
181
|
+
<rect x="620" y="330" width="260" height="72" rx="12" fill="rgba(244,114,208,.06)" stroke="#f472d0" stroke-width="1.2"/>
|
|
182
|
+
<text x="750" y="358" text-anchor="middle" class="head">gh CLI → GitHub code search</text>
|
|
183
|
+
<text x="750" y="378" text-anchor="middle" class="lbl-s">github_skill_search(query, limit)</text>
|
|
184
|
+
<text x="750" y="394" text-anchor="middle" class="lbl-t">one page · no cache · SKILL.md repos</text>
|
|
185
|
+
<line x1="770" y1="256" x2="770" y2="330" stroke="#f472d0" stroke-width="1.2" marker-end="url(#ahp)"/>
|
|
186
|
+
<rect x="352" y="345" width="234" height="42" rx="9" fill="rgba(250,204,21,.05)" stroke="#facc15" stroke-width="1"/>
|
|
187
|
+
<text x="469" y="363" text-anchor="middle" class="note">no gh / offline / failure</text>
|
|
188
|
+
<text x="469" y="379" text-anchor="middle" class="note">→ helpful message, never crash</text>
|
|
189
|
+
<line x1="620" y1="366" x2="586" y2="366" stroke="#facc15" stroke-width="1" stroke-dasharray="4 3" marker-end="url(#ah)"/>
|
|
190
|
+
|
|
191
|
+
<!-- core engine deps -->
|
|
192
|
+
<rect x="40" y="330" width="290" height="72" rx="12" fill="rgba(255,255,255,.03)" stroke="var(--line-hi)"/>
|
|
193
|
+
<text x="185" y="358" text-anchor="middle" class="head">core engine (read-only from here)</text>
|
|
194
|
+
<text x="185" y="379" text-anchor="middle" class="lbl-s">rag · catalog · store · lockfile · registry</text>
|
|
195
|
+
<line x1="185" y1="236" x2="185" y2="330" stroke="#676d86" stroke-width="1.1" stroke-dasharray="3 3" marker-end="url(#ah)"/>
|
|
196
|
+
<text x="196" y="290" class="lbl-t">handlers call the engine</text>
|
|
197
|
+
</svg>
|
|
198
|
+
</div>
|
|
199
|
+
<p class="caption">Fig 1 — The server holds no tool knowledge; it delegates every
|
|
200
|
+
<code>tools/list</code> / <code>tools/call</code> to the registry. New tools slot in on the
|
|
201
|
+
right without touching the loop on the left.</p>
|
|
202
|
+
</div>
|
|
203
|
+
</section>
|
|
204
|
+
|
|
205
|
+
<!-- ===== sequence ===== -->
|
|
206
|
+
<section>
|
|
207
|
+
<div class="wrap">
|
|
208
|
+
<span class="eyebrow">Sequence</span>
|
|
209
|
+
<h2 class="sec-title">A <code>boost_discover_github</code> call, end to end</h2>
|
|
210
|
+
<p class="sec-sub">The happy path reaches GitHub; the shaded branch is what happens when
|
|
211
|
+
<code>gh</code> is missing or the search fails — the server answers, never dies.</p>
|
|
212
|
+
|
|
213
|
+
<div class="diagram reveal">
|
|
214
|
+
<svg viewBox="0 0 920 470" role="img" aria-label="Sequence diagram of a tools/call for boost_discover_github: client to server to registry to handler; the handler probes for gh, calls github_skill_search which runs gh api, maps results, and returns text; a degradation branch returns a hint when gh is absent.">
|
|
215
|
+
<defs>
|
|
216
|
+
<marker id="sq" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto">
|
|
217
|
+
<path d="M0 0L10 5L0 10z" fill="#7dd3fc"/>
|
|
218
|
+
</marker>
|
|
219
|
+
<marker id="sqr" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto">
|
|
220
|
+
<path d="M0 0L10 5L0 10z" fill="#4ade80"/>
|
|
221
|
+
</marker>
|
|
222
|
+
</defs>
|
|
223
|
+
<!-- lifelines -->
|
|
224
|
+
<g class="lbl" text-anchor="middle">
|
|
225
|
+
<text x="80" y="34">Client</text>
|
|
226
|
+
<text x="270" y="34">Server</text>
|
|
227
|
+
<text x="450" y="34">Registry</text>
|
|
228
|
+
<text x="630" y="34">Handler</text>
|
|
229
|
+
<text x="840" y="34">gh · GitHub</text>
|
|
230
|
+
</g>
|
|
231
|
+
<g stroke="var(--line-hi)" stroke-width="1" stroke-dasharray="3 4">
|
|
232
|
+
<line x1="80" y1="44" x2="80" y2="440"/>
|
|
233
|
+
<line x1="270" y1="44" x2="270" y2="440"/>
|
|
234
|
+
<line x1="450" y1="44" x2="450" y2="440"/>
|
|
235
|
+
<line x1="630" y1="44" x2="630" y2="440"/>
|
|
236
|
+
<line x1="840" y1="44" x2="840" y2="440"/>
|
|
237
|
+
</g>
|
|
238
|
+
|
|
239
|
+
<!-- 1 tools/call -->
|
|
240
|
+
<line x1="80" y1="76" x2="270" y2="76" stroke="#7dd3fc" stroke-width="1.4" marker-end="url(#sq)"/>
|
|
241
|
+
<text x="84" y="70" class="lbl-s">tools/call · boost_discover_github {query,limit}</text>
|
|
242
|
+
<!-- 2 REGISTRY.call -->
|
|
243
|
+
<line x1="270" y1="112" x2="450" y2="112" stroke="#7dd3fc" stroke-width="1.4" marker-end="url(#sq)"/>
|
|
244
|
+
<text x="274" y="106" class="lbl-s">REGISTRY.call(name, args)</text>
|
|
245
|
+
<!-- 3 dispatch handler -->
|
|
246
|
+
<line x1="450" y1="148" x2="630" y2="148" stroke="#7dd3fc" stroke-width="1.4" marker-end="url(#sq)"/>
|
|
247
|
+
<text x="454" y="142" class="lbl-s">handler(args)</text>
|
|
248
|
+
|
|
249
|
+
<!-- 4 probe -->
|
|
250
|
+
<rect x="600" y="168" width="60" height="26" rx="6" fill="rgba(250,204,21,.06)" stroke="#facc15"/>
|
|
251
|
+
<text x="630" y="185" text-anchor="middle" class="note">probe</text>
|
|
252
|
+
<text x="558" y="212" class="lbl-t">shutil.which("gh")</text>
|
|
253
|
+
|
|
254
|
+
<!-- 5 github_skill_search -->
|
|
255
|
+
<line x1="630" y1="236" x2="840" y2="236" stroke="#7dd3fc" stroke-width="1.4" marker-end="url(#sq)"/>
|
|
256
|
+
<text x="634" y="230" class="lbl-s">github_skill_search → gh api search/code</text>
|
|
257
|
+
<!-- 6 results -->
|
|
258
|
+
<line x1="840" y1="272" x2="630" y2="272" stroke="#4ade80" stroke-width="1.4" marker-end="url(#sqr)"/>
|
|
259
|
+
<text x="634" y="266" class="lbl-s">items[] (repo · path · desc)</text>
|
|
260
|
+
<!-- 7 text back -->
|
|
261
|
+
<line x1="630" y1="308" x2="450" y2="308" stroke="#4ade80" stroke-width="1.4" marker-end="url(#sqr)"/>
|
|
262
|
+
<text x="454" y="302" class="lbl-s">(text, is_error=False)</text>
|
|
263
|
+
<line x1="450" y1="332" x2="270" y2="332" stroke="#4ade80" stroke-width="1.4" marker-end="url(#sqr)"/>
|
|
264
|
+
<line x1="270" y1="356" x2="80" y2="356" stroke="#4ade80" stroke-width="1.4" marker-end="url(#sqr)"/>
|
|
265
|
+
<text x="84" y="350" class="lbl-s">result.content[0].text</text>
|
|
266
|
+
|
|
267
|
+
<!-- degradation branch -->
|
|
268
|
+
<rect x="300" y="388" width="470" height="52" rx="10" fill="rgba(250,204,21,.05)" stroke="#facc15" stroke-width="1" stroke-dasharray="5 3"/>
|
|
269
|
+
<text x="316" y="410" class="note">alt · no gh / gh returns error / bad JSON</text>
|
|
270
|
+
<text x="316" y="428" class="lbl-s" fill="#facc15">handler returns ("install gh …" / "search failed …", is_error=True) — server still replies</text>
|
|
271
|
+
</svg>
|
|
272
|
+
</div>
|
|
273
|
+
<p class="caption">Fig 2 — Steps 5–6 are the only ones that touch the network. Everything
|
|
274
|
+
below step 4 has a mirror in the shaded <code>alt</code> branch, so a missing dependency is
|
|
275
|
+
a <i>message</i>, not an exception.</p>
|
|
276
|
+
</div>
|
|
277
|
+
</section>
|
|
278
|
+
|
|
279
|
+
<!-- ===== before / after ===== -->
|
|
280
|
+
<section>
|
|
281
|
+
<div class="wrap">
|
|
282
|
+
<span class="eyebrow">The refactor</span>
|
|
283
|
+
<h2 class="sec-title">From an <span class="tag-was">if/elif</span> ladder to a <span class="tag-now">registry</span></h2>
|
|
284
|
+
<p class="sec-sub">Same behaviour, same wire protocol — but adding tool #7 no longer means
|
|
285
|
+
editing the dispatcher, and an unknown tool resolves in exactly one place.</p>
|
|
286
|
+
|
|
287
|
+
<div class="split reveal">
|
|
288
|
+
<div class="window">
|
|
289
|
+
<div class="bar"><div class="dots"><i></i><i></i><i></i></div>
|
|
290
|
+
<div class="bar-title">before — configuration.py</div></div>
|
|
291
|
+
<pre><span class="cm"># flat dispatch: every tool is a branch</span>
|
|
292
|
+
<span class="tv">def</span> _mcp_tool(tool, args):
|
|
293
|
+
<span class="tv">if</span> tool == <span class="tok">"boost_search"</span>:
|
|
294
|
+
...
|
|
295
|
+
<span class="tv">if</span> tool == <span class="tok">"boost_list"</span>:
|
|
296
|
+
...
|
|
297
|
+
<span class="cm"># +1 tool = +1 branch here</span>
|
|
298
|
+
<span class="tv">return</span> None, False
|
|
299
|
+
|
|
300
|
+
_MCP_TOOLS = [ {<span class="cm">…5 hand-kept specs…</span>} ]</pre>
|
|
301
|
+
</div>
|
|
302
|
+
<div class="window">
|
|
303
|
+
<div class="bar"><div class="dots"><i></i><i></i><i></i></div>
|
|
304
|
+
<div class="bar-title">after — core/mcp.py + configuration.py</div></div>
|
|
305
|
+
<pre><span class="cm"># the seam: an ordered name → (spec, handler) map</span>
|
|
306
|
+
REGISTRY = mcp.<span class="tv">Registry</span>()
|
|
307
|
+
|
|
308
|
+
REGISTRY.register(
|
|
309
|
+
<span class="tok">"boost_discover_github"</span>,
|
|
310
|
+
<span class="tok">"Discover new SKILL.md repos…"</span>,
|
|
311
|
+
{<span class="tok">"type"</span>: <span class="tok">"object"</span>, <span class="tok">"properties"</span>: {…}},
|
|
312
|
+
_tool_discover_github) <span class="cm"># ← the only edit</span>
|
|
313
|
+
|
|
314
|
+
_MCP_TOOLS = REGISTRY.specs() <span class="cm"># list stays in sync</span></pre>
|
|
315
|
+
</div>
|
|
316
|
+
</div>
|
|
317
|
+
</div>
|
|
318
|
+
</section>
|
|
319
|
+
|
|
320
|
+
<!-- ===== cheat sheet ===== -->
|
|
321
|
+
<section>
|
|
322
|
+
<div class="wrap">
|
|
323
|
+
<span class="eyebrow">Cheat sheet</span>
|
|
324
|
+
<h2 class="sec-title">Explain it two ways</h2>
|
|
325
|
+
<p class="sec-sub">Every concept, twice: the <b class="tag-was" style="color:var(--sky)">ELI5</b> on
|
|
326
|
+
the left, the <b style="color:var(--pink)">deep technical</b> on the right. Open both and
|
|
327
|
+
compare — they describe the same thing at different altitudes.</p>
|
|
328
|
+
|
|
329
|
+
<div class="cheat reveal">
|
|
330
|
+
<div class="col-head eli">🧸 Explain like I'm 5</div>
|
|
331
|
+
<div class="col-head tech">🔬 Deep technical</div>
|
|
332
|
+
|
|
333
|
+
<!-- Registry -->
|
|
334
|
+
<details class="card eli5">
|
|
335
|
+
<summary>The toy box <span class="topic-tag">registry</span></summary>
|
|
336
|
+
<div class="body"><p>Imagine a toy box with labelled slots. Each toy knows its own
|
|
337
|
+
name and what it does, and it puts <i>itself</i> into a slot. When someone asks "what
|
|
338
|
+
toys do you have?" you just read the labels. When they say "play with the drum," you
|
|
339
|
+
grab the drum from its slot. You never keep a giant list in your head — the box does.</p></div>
|
|
340
|
+
</details>
|
|
341
|
+
<details class="card tech">
|
|
342
|
+
<summary><code>Registry</code>: ordered name → (spec, handler) <span class="topic-tag">core/mcp.py</span></summary>
|
|
343
|
+
<div class="body"><p><code>Registry</code> holds three parallel structures keyed by tool
|
|
344
|
+
name: an insertion-ordered name list, a <code>specs</code> map
|
|
345
|
+
(<code>{name, description, inputSchema}</code>), and a <code>handlers</code> map.
|
|
346
|
+
<code>register()</code> rejects empty or duplicate names; <code>specs()</code> returns
|
|
347
|
+
the JSON payload in registration order; <code>call(name, args)</code> looks up the
|
|
348
|
+
handler and returns <code>(None, False)</code> for anything unknown. It's pure,
|
|
349
|
+
stdlib-only, and mutation-gated.</p></div>
|
|
350
|
+
</details>
|
|
351
|
+
|
|
352
|
+
<!-- Handler -->
|
|
353
|
+
<details class="card eli5">
|
|
354
|
+
<summary>What each toy promises <span class="topic-tag">handler</span></summary>
|
|
355
|
+
<div class="body"><p>Every toy promises the same thing: "give me your ask, and I'll hand
|
|
356
|
+
back some words, plus a thumbs-up or thumbs-down." That's it. Because they all promise
|
|
357
|
+
the exact same shape, the toy box doesn't care which toy it grabbed — they're
|
|
358
|
+
interchangeable.</p></div>
|
|
359
|
+
</details>
|
|
360
|
+
<details class="card tech">
|
|
361
|
+
<summary>The handler contract <span class="topic-tag">(text, is_error)</span></summary>
|
|
362
|
+
<div class="body"><p>A handler is <code>fn(args: dict) -> (text, is_error)</code>.
|
|
363
|
+
<code>text</code> is the human-readable result; <code>is_error</code> becomes the MCP
|
|
364
|
+
<code>result.isError</code> flag. A handler may return <code>text is None</code> to mean
|
|
365
|
+
"no result," which the server renders identically to an unknown tool — so the
|
|
366
|
+
<code>-32602</code> path lives in exactly one place. Handlers close over the core engine
|
|
367
|
+
(<code>rag</code>, <code>catalog</code>, <code>store</code>) but the registry knows
|
|
368
|
+
nothing about them.</p></div>
|
|
369
|
+
</details>
|
|
370
|
+
|
|
371
|
+
<!-- discover_github -->
|
|
372
|
+
<details class="card eli5">
|
|
373
|
+
<summary>The scout that looks outside <span class="topic-tag">boost_discover_github</span></summary>
|
|
374
|
+
<div class="body"><p>Most toys play with things already in the room. This one is a scout:
|
|
375
|
+
it opens the door and looks down the street (GitHub) for new skills to bring home. If the
|
|
376
|
+
door is locked (no <code>gh</code> tool installed), it doesn't panic — it just says
|
|
377
|
+
"hey, you need a key, here's how to get one" and comes back inside.</p></div>
|
|
378
|
+
</details>
|
|
379
|
+
<details class="card tech">
|
|
380
|
+
<summary>First reach-out tool <span class="topic-tag">gh code search</span></summary>
|
|
381
|
+
<div class="body"><p><code>_tool_discover_github</code> probes <code>shutil.which("gh")</code>,
|
|
382
|
+
then calls <code>discovery.github_skill_search(query, limit)</code> — a single
|
|
383
|
+
<code>gh api search/code?q=filename:SKILL.md…</code> page (no cache write, limit clamped
|
|
384
|
+
to 100, query URL-encoded and appended to the filename filter). Results map to
|
|
385
|
+
<code>{repo, path, url, description}</code> and render one per line. Every failure mode
|
|
386
|
+
(<code>TimeoutExpired</code>, <code>OSError</code>, non-zero exit, bad JSON) returns
|
|
387
|
+
<code>None</code> so the handler emits a hint instead of raising.</p></div>
|
|
388
|
+
</details>
|
|
389
|
+
|
|
390
|
+
<!-- graceful degradation -->
|
|
391
|
+
<details class="card eli5">
|
|
392
|
+
<summary>Never cry, always answer <span class="topic-tag">degradation</span></summary>
|
|
393
|
+
<div class="body"><p>The rule for any toy that reaches outside: never throw a tantrum. If
|
|
394
|
+
something it needs is missing, it must still say something friendly and useful. That way
|
|
395
|
+
the whole toy box keeps working even when one toy can't reach its toy.</p></div>
|
|
396
|
+
</details>
|
|
397
|
+
<details class="card tech">
|
|
398
|
+
<summary>Probe-then-degrade <span class="topic-tag">core/ai.py pattern</span></summary>
|
|
399
|
+
<div class="body"><p>Mirrors the established <code>core/ai.py</code> discipline: detect the
|
|
400
|
+
dependency, and if absent return a short actionable message rather than raising. The MCP
|
|
401
|
+
server is a long-lived stdio loop — an uncaught exception in one <code>tools/call</code>
|
|
402
|
+
would be caught by the server's blanket handler, but degrading <i>inside</i> the tool
|
|
403
|
+
gives a precise, testable message and keeps <code>is_error</code> semantically correct.</p></div>
|
|
404
|
+
</details>
|
|
405
|
+
|
|
406
|
+
<!-- back-compat shims -->
|
|
407
|
+
<details class="card eli5">
|
|
408
|
+
<summary>Old handles still turn <span class="topic-tag">shims</span></summary>
|
|
409
|
+
<div class="body"><p>We rearranged the room, but we kept the old light switches on the wall
|
|
410
|
+
so anything that used to flip them still works. Nobody has to relearn where the switches
|
|
411
|
+
are.</p></div>
|
|
412
|
+
</details>
|
|
413
|
+
<details class="card tech">
|
|
414
|
+
<summary>Back-compat: <code>_MCP_TOOLS</code> / <code>_mcp_tool</code> <span class="topic-tag">no churn</span></summary>
|
|
415
|
+
<div class="body"><p><code>_MCP_TOOLS = REGISTRY.specs()</code> and a one-line
|
|
416
|
+
<code>_mcp_tool()</code> that delegates to <code>REGISTRY.call()</code> preserve the two
|
|
417
|
+
symbols the server loop and the existing test suite reference. The refactor is
|
|
418
|
+
behaviour-preserving: the
|
|
419
|
+
<code>tools/list</code> order is unchanged for the first five tools, and only the new
|
|
420
|
+
sixth entry is appended — so <code>TestMcp</code> needed a single one-line assertion
|
|
421
|
+
update.</p></div>
|
|
422
|
+
</details>
|
|
423
|
+
</div>
|
|
424
|
+
</div>
|
|
425
|
+
</section>
|
|
426
|
+
|
|
427
|
+
<!-- ===== extension contract ===== -->
|
|
428
|
+
<section>
|
|
429
|
+
<div class="wrap">
|
|
430
|
+
<span class="eyebrow">Extension contract</span>
|
|
431
|
+
<h2 class="sec-title">Adding tool #7</h2>
|
|
432
|
+
<p class="sec-sub">The whole point of the seam: a new capability — a database query, another
|
|
433
|
+
external API — is a handler plus one <code>register()</code>. No server edit, no dispatcher edit.</p>
|
|
434
|
+
|
|
435
|
+
<div class="window reveal" style="margin-top:36px;">
|
|
436
|
+
<div class="bar"><div class="dots"><i></i><i></i><i></i></div>
|
|
437
|
+
<div class="bar-title">your_new_tool.py</div></div>
|
|
438
|
+
<pre><span class="tv">def</span> _tool_x(args): <span class="cm"># fn(args) -> (text, is_error)</span>
|
|
439
|
+
<span class="tv">if</span> <span class="tv">not</span> _dependency_available():
|
|
440
|
+
<span class="tv">return</span> <span class="tok">"needs <dep> — here's how to get it"</span>, <span class="tv">True</span> <span class="cm"># degrade</span>
|
|
441
|
+
<span class="tv">return</span> _do_the_work(args), <span class="tv">False</span>
|
|
442
|
+
|
|
443
|
+
REGISTRY.register(
|
|
444
|
+
<span class="tok">"boost_x"</span>,
|
|
445
|
+
<span class="tok">"one-line description shown to the agent"</span>,
|
|
446
|
+
{<span class="tok">"type"</span>: <span class="tok">"object"</span>, <span class="tok">"properties"</span>: {…}},
|
|
447
|
+
_tool_x) <span class="cm"># tools/list + tools/call: automatic</span></pre>
|
|
448
|
+
</div>
|
|
449
|
+
|
|
450
|
+
<table class="contract">
|
|
451
|
+
<tr><th>Rule</th><th>Why</th></tr>
|
|
452
|
+
<tr><td>Handler returns <code>(text, is_error)</code></td><td>Uniform shape lets the registry stay tool-agnostic.</td></tr>
|
|
453
|
+
<tr><td>Reach-out tools <b>must</b> degrade, never raise</td><td>The stdio server is long-lived; a missing <code>gh</code>, offline network, or absent <code>[rag]</code> extra must not kill it.</td></tr>
|
|
454
|
+
<tr><td>Registration order = <code>tools/list</code> order</td><td>Deterministic surface; existing clients see a stable ordering with new tools appended.</td></tr>
|
|
455
|
+
<tr><td>Put reusable logic in <code>core/</code></td><td>It gets unit-tested <i>and</i> mutation-gated — the registry itself lives in <code>core/mcp.py</code> for exactly this reason.</td></tr>
|
|
456
|
+
</table>
|
|
457
|
+
</div>
|
|
458
|
+
</section>
|
|
459
|
+
|
|
460
|
+
<footer>
|
|
461
|
+
<div class="wrap">
|
|
462
|
+
<p>boost · <b>MCP as a hub</b> (RAG Phase 3) — see
|
|
463
|
+
<a href="rag-architecture.md">rag-architecture.md §8</a> ·
|
|
464
|
+
<a href="index.html">docs home</a> ·
|
|
465
|
+
<a href="https://github.com/jonnyeclectic/boost/pull/64">PR #64</a><br>
|
|
466
|
+
Styled with the shared <a href="../style/boost.css">Aurora</a> design system.</p>
|
|
467
|
+
</div>
|
|
468
|
+
</footer>
|
|
469
|
+
</main>
|
|
470
|
+
|
|
471
|
+
<script src="../style/boost.js" defer></script>
|
|
472
|
+
</body>
|
|
473
|
+
</html>
|
|
@@ -114,7 +114,7 @@
|
|
|
114
114
|
<!-- ================= SNAPSHOT ================= -->
|
|
115
115
|
<section class="block reveal">
|
|
116
116
|
<div class="stats">
|
|
117
|
-
<div class="stat"><b>
|
|
117
|
+
<div class="stat"><b>5</b><span>Shipped</span></div>
|
|
118
118
|
<div class="stat"><b>3</b><span>Next up</span></div>
|
|
119
119
|
<div class="stat"><b>4</b><span>Planned</span></div>
|
|
120
120
|
<div class="stat"><b>48</b><span>Loop finds</span></div>
|
|
@@ -174,6 +174,25 @@
|
|
|
174
174
|
</div>
|
|
175
175
|
</article>
|
|
176
176
|
|
|
177
|
+
<article class="cap rcard">
|
|
178
|
+
<div class="head"><span class="pill shipped">Shipped</span><span class="cat">Testing · Coverage</span></div>
|
|
179
|
+
<h3>Extension-free tests — <code>core/dense.py</code></h3>
|
|
180
|
+
<p>The dense-vector RAG backend's whole test module is
|
|
181
|
+
<code>skipif</code>-gated on the <code>[rag]</code> sqlite-vec C
|
|
182
|
+
extension, so on the default zero-dependency install
|
|
183
|
+
<em>every</em> degradation and ranking path went untested
|
|
184
|
+
(17.6% → 89% coverage). New tests force
|
|
185
|
+
<code>_load() → None</code> and drive the SQL helpers and
|
|
186
|
+
<code>retrieve()</code>'s cosine reducer through in-memory/fake
|
|
187
|
+
connections — killing mutants on every machine, extension or not.</p>
|
|
188
|
+
<div class="meta">
|
|
189
|
+
<span class="m">Complexity <b>M</b></span>
|
|
190
|
+
<span class="m hi">Impact <b>High</b></span>
|
|
191
|
+
<span class="m">Wow <b class="wow">★★★</b></span>
|
|
192
|
+
<span class="m">42 tests · +71pts coverage</span>
|
|
193
|
+
</div>
|
|
194
|
+
</article>
|
|
195
|
+
|
|
177
196
|
<article class="cap rcard">
|
|
178
197
|
<div class="head"><span class="pill shipped">Shipped</span><span class="cat">Infra · DX</span></div>
|
|
179
198
|
<h3>Autonomous ship-workflow & isolated worktree</h3>
|