@softspark/ai-toolkit 4.2.5 → 4.3.1
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.
- package/CHANGELOG.md +56 -0
- package/README.md +8 -8
- package/app/.claude-plugin/plugin.json +1 -1
- package/app/hooks/_hook-io.sh +14 -0
- package/app/hooks/commit-quality.sh +1 -0
- package/app/hooks/governance-capture.sh +1 -0
- package/app/hooks/guard-config.sh +1 -0
- package/app/hooks/guard-destructive.sh +1 -0
- package/app/hooks/revert-guard.sh +1 -0
- package/app/hooks/search-tracker.sh +5 -1
- package/app/hooks/session-start.sh +4 -0
- package/app/hooks/stop-search-check.sh +3 -1
- package/app/hooks/test-cohesion.sh +1 -0
- package/app/hooks/track-usage.sh +1 -0
- package/app/hooks/user-prompt-submit.sh +2 -1
- package/bin/ai-toolkit.js +43 -0
- package/kb/reference/extension-api.md +77 -11
- package/kb/reference/hooks-catalog.md +3 -3
- package/kb/reference/mcp-templates.md +6 -4
- package/llms-full.txt +86 -18
- package/manifest.json +1 -1
- package/package.json +1 -1
- package/scripts/inject_mcp_cli.py +514 -0
- package/scripts/install.py +2 -1
- package/scripts/install_steps/markers.py +40 -0
- package/scripts/mcp_sources.py +162 -0
- package/scripts/paths.py +2 -0
- package/scripts/test_cohesion.py +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,62 @@ Versioning follows [Semantic Versioning](https://semver.org/).
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
+
## v4.3.1 - per-session search-first flag (2026-05-19)
|
|
11
|
+
|
|
12
|
+
Patch release. Fixes a cross-session race condition in the search-first enforcement trio (`user-prompt-submit.sh` + `search-tracker.sh` + `stop-search-check.sh`): the single global flag file `~/.softspark/ai-toolkit/state/search-required.flag` was shared by every parallel Claude Code window, so a Stop in session B could consume session A's flag (or vice versa), blocking unrelated turns with someone else's prompt. Also unblocks `bats 1.13` regression in the test-cohesion runner default.
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
- **Search-first flag is now per-session** - `user-prompt-submit.sh`, `search-tracker.sh`, and `stop-search-check.sh` key the flag by `session_id` from the hook stdin payload (`search-required-<session_id>.flag`), falling back to `transcript_path` basename, then `default`. Parallel sessions no longer interfere with each other. Each Stop check reads only its own session's flag.
|
|
17
|
+
- **`bats 1.13` regression in test-cohesion runner** - `scripts/test_cohesion.py` no longer passes `--no-parallelize-within-files` (now requires `--jobs 2` in bats 1.13). Default sequential mode is used, which is what the hook expected anyway.
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
- **`hook_session_id()` helper in `_hook-io.sh`** - Resolves and sanitizes the per-session key for any hook that needs to scope state to a single Claude Code window.
|
|
22
|
+
- **`session-start.sh` GC** - Stale per-session search-required flags older than 60 minutes are deleted on every `SessionStart`, so crashed sessions do not leave residue.
|
|
23
|
+
- **Two new bats tests** in `tests/test_search_first_flow.bats` verifying per-session isolation and that `search-tracker` only clears its own session's flag.
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
|
|
27
|
+
- **`kb/reference/hooks-catalog.md`** - Documents the new per-session flag layout, GC behavior, and isolation guarantee.
|
|
28
|
+
- **`README.md`** - Test count badge 1131 → 1133.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## v4.3.0 - inject-mcp extension API (2026-05-12)
|
|
33
|
+
|
|
34
|
+
Minor release. Closes the asymmetry between `inject-rule` / `inject-hook` and MCP servers by adding `inject-mcp` and `remove-mcp` as first-class members of the extension API. External tools (rag-mcp, jira-mcp, custom integrations) can now register their MCP templates the same way they register rules and hooks -- from a local file or HTTPS URL, with full editor propagation and auto-refresh on `ai-toolkit update`.
|
|
35
|
+
|
|
36
|
+
### Added
|
|
37
|
+
|
|
38
|
+
- **`ai-toolkit inject-mcp <file|url> [--name <name>] [--force]`** - Inject an external MCP server template into `~/.mcp.json` (toolkit source-of-truth) and propagate it to every editor with a `global_path` (Claude, Cursor, Codex, Gemini, Windsurf, Cline, Augment, Copilot). Servers in `~/.mcp.json` are tagged with `_source` for idempotent re-injection; native editor configs receive the same servers without `_source`.
|
|
39
|
+
- **`ai-toolkit remove-mcp <name>`** - Strip all servers tagged with the given source from `~/.mcp.json` and every editor config, unregister URL sources, and remove cached template files.
|
|
40
|
+
- **`--name <name>` flag** - Override the auto-derived source name for both local files and URLs. Required when filename stem is generic (e.g., `mcp-template.json` → `--name rag-mcp`).
|
|
41
|
+
- **`--force` flag** - Overwrite servers tagged with a different `_source`. Without `--force`, collisions exit with code 3. Entries tagged `"_source": "ai-toolkit"` are protected even with `--force`.
|
|
42
|
+
- **URL fetch + cache + auto-refresh** - HTTPS templates are cached in `~/.softspark/ai-toolkit/mcp-templates/external/<name>.json` and registered in `sources.json` with sha256 pin. On every `ai-toolkit update`, URL-sourced templates are re-fetched and re-injected; cached version is used on fetch failure.
|
|
43
|
+
- **`scripts/mcp_sources.py`** - Source registry for external MCP templates, mirroring `hook_sources.py`.
|
|
44
|
+
- **`scripts/inject_mcp_cli.py`** - CLI entry point implementing both inject and remove modes.
|
|
45
|
+
- **`refresh_url_mcp()` in `install_steps/markers.py`** - Update flow integration, called from `install.py` alongside `refresh_url_hooks()`.
|
|
46
|
+
|
|
47
|
+
### Changed
|
|
48
|
+
|
|
49
|
+
- **`paths.py`** - Added `MCP_TEMPLATES_DIR` and `EXTERNAL_MCP_DIR` constants.
|
|
50
|
+
- **`bin/ai-toolkit.js`** - Registered `inject-mcp` / `remove-mcp` handlers and help text.
|
|
51
|
+
- **`kb/reference/extension-api.md`** - Documented inject-mcp / remove-mcp + flags. Version bumped to 1.5.0.
|
|
52
|
+
- **`kb/reference/mcp-templates.md`** - Added External Templates section pointing to inject-mcp. Version bumped to 1.2.0.
|
|
53
|
+
|
|
54
|
+
### Tests
|
|
55
|
+
|
|
56
|
+
- **`tests/test_inject_mcp.bats`** - 15 bats cases covering local-file inject, URL fetch via fixture, `--name` override, `--force` collision override, ai-toolkit source protection, idempotent re-inject, editor propagation to Cursor (JSON) and Codex (TOML), `_source` strip from native configs, sources.json registry, `--remove` cleanup, HTTPS-only enforcement.
|
|
57
|
+
|
|
58
|
+
### Verification
|
|
59
|
+
|
|
60
|
+
- 15/15 bats cases passing in `test_inject_mcp.bats`.
|
|
61
|
+
- E2E smoke test: injecting an external `mcp-template.json` writes 9 files (`~/.mcp.json` + 8 editor configs) with `_source` only in source-of-truth and stripped from native configs.
|
|
62
|
+
- No regressions in `test_inject_hook.bats` or `test_mcp_manager.bats`.
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
10
66
|
## v4.2.5 - Hook safety and no-RAG compatibility (2026-05-12)
|
|
11
67
|
|
|
12
68
|
Patch release. Hardens Claude Code hook enforcement while keeping the toolkit safe for users who do not have RAG/MCP search providers installed.
|
package/README.md
CHANGED
|
@@ -6,16 +6,16 @@
|
|
|
6
6
|
[](LICENSE)
|
|
7
7
|
[](app/skills/)
|
|
8
8
|
[](app/agents/)
|
|
9
|
-
[](tests/)
|
|
10
10
|
|
|
11
|
-
## What's New in v4.
|
|
11
|
+
## What's New in v4.3.1
|
|
12
12
|
|
|
13
|
-
Patch release
|
|
13
|
+
Patch release. Fixes a cross-session race in the search-first enforcement trio: the single global `search-required.flag` was shared by every parallel Claude Code window, so a Stop in one session could block another with someone else's prompt. Also unblocks the `bats 1.13` regression in the test-cohesion runner.
|
|
14
14
|
|
|
15
|
-
- **
|
|
16
|
-
- **
|
|
17
|
-
-
|
|
18
|
-
- **
|
|
15
|
+
- **Per-session search-first flag**: `user-prompt-submit.sh`, `search-tracker.sh`, and `stop-search-check.sh` now key the flag by `session_id` (`search-required-<session_id>.flag`), so parallel Claude Code windows no longer interfere with each other.
|
|
16
|
+
- **Stale-flag GC**: `session-start.sh` deletes per-session flags older than 60 minutes, so crashed sessions leave no residue.
|
|
17
|
+
- **`bats 1.13` compatibility**: `scripts/test_cohesion.py` no longer passes `--no-parallelize-within-files` (it now requires `--jobs 2` in bats 1.13 and was redundant in sequential mode anyway).
|
|
18
|
+
- **Two new isolation tests**: `tests/test_search_first_flow.bats` verifies per-session flag scoping for both Stop and PostToolUse paths.
|
|
19
19
|
|
|
20
20
|
See [CHANGELOG.md](CHANGELOG.md) for full history.
|
|
21
21
|
|
|
@@ -148,7 +148,7 @@ ai-toolkit/
|
|
|
148
148
|
│ └── ARCHITECTURE.md # Full system design
|
|
149
149
|
├── kb/ # Reference docs, procedures, plans
|
|
150
150
|
├── scripts/ # Validation, install, evaluation scripts
|
|
151
|
-
├── tests/ # Bats test suite (
|
|
151
|
+
├── tests/ # Bats test suite (1133 tests)
|
|
152
152
|
└── CHANGELOG.md
|
|
153
153
|
```
|
|
154
154
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-toolkit",
|
|
3
3
|
"description": "Professional-grade Claude Code toolkit with persona presets, skill security auditor, expanded lifecycle hooks, experimental opt-in plugin packs, benchmark harvesting, and multi-tool support.",
|
|
4
|
-
"version": "4.
|
|
4
|
+
"version": "4.3.1",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "SoftSpark",
|
|
7
7
|
"url": "https://github.com/softspark"
|
package/app/hooks/_hook-io.sh
CHANGED
|
@@ -19,6 +19,20 @@ hook_prompt() {
|
|
|
19
19
|
hook_json '.prompt // .tool_info.user_prompt // .user_prompt // .input // empty'
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
hook_session_id() {
|
|
23
|
+
local sid
|
|
24
|
+
sid=$(hook_json '.session_id // empty')
|
|
25
|
+
if [ -z "$sid" ] || [ "$sid" = "null" ]; then
|
|
26
|
+
local tp
|
|
27
|
+
tp=$(hook_json '.transcript_path // empty')
|
|
28
|
+
if [ -n "$tp" ] && [ "$tp" != "null" ]; then
|
|
29
|
+
sid=$(basename "$tp" .jsonl)
|
|
30
|
+
fi
|
|
31
|
+
fi
|
|
32
|
+
[ -z "$sid" ] && sid="default"
|
|
33
|
+
printf '%s' "$sid" | LC_ALL=C tr -c 'a-zA-Z0-9_-' '_'
|
|
34
|
+
}
|
|
35
|
+
|
|
22
36
|
hook_command() {
|
|
23
37
|
hook_json '.tool_input.command // .tool_input.command_line // .tool_info.command // .tool_info.command_line // .command // empty'
|
|
24
38
|
}
|
|
@@ -13,6 +13,7 @@ LOG_FILE="$LOG_DIR/governance.log"
|
|
|
13
13
|
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
14
14
|
SESSION="${CLAUDE_SESSION_ID:-unknown}"
|
|
15
15
|
|
|
16
|
+
# shellcheck disable=SC2034 # INPUT is consumed via sourced _hook-io.sh
|
|
16
17
|
INPUT=$(cat)
|
|
17
18
|
# shellcheck source=_hook-io.sh
|
|
18
19
|
source "$(dirname "$0")/_hook-io.sh"
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
# Fires on: PreToolUse (Edit|Write|MultiEdit)
|
|
5
5
|
# Exit 2 = block the tool call. Stderr message goes to Claude as feedback.
|
|
6
6
|
|
|
7
|
+
# shellcheck disable=SC2034 # INPUT is consumed via sourced _hook-io.sh
|
|
7
8
|
INPUT=$(cat)
|
|
8
9
|
# shellcheck source=_hook-io.sh
|
|
9
10
|
source "$(dirname "$0")/_hook-io.sh"
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
# Fires on: PreToolUse (Bash)
|
|
5
5
|
# Exit 2 = block the command. Stderr message goes to Claude as feedback.
|
|
6
6
|
|
|
7
|
+
# shellcheck disable=SC2034 # INPUT is consumed via sourced _hook-io.sh
|
|
7
8
|
INPUT=$(cat)
|
|
8
9
|
# shellcheck source=_hook-io.sh
|
|
9
10
|
source "$(dirname "$0")/_hook-io.sh"
|
|
@@ -12,7 +12,11 @@
|
|
|
12
12
|
|
|
13
13
|
# shellcheck source=_profile-check.sh
|
|
14
14
|
source "$(dirname "$0")/_profile-check.sh"
|
|
15
|
+
# shellcheck source=_hook-io.sh
|
|
16
|
+
source "$(dirname "$0")/_hook-io.sh"
|
|
15
17
|
|
|
16
|
-
|
|
18
|
+
# shellcheck disable=SC2034 # INPUT is consumed via sourced _hook-io.sh
|
|
19
|
+
INPUT=$(cat 2>/dev/null || true)
|
|
20
|
+
FLAG="$HOME/.softspark/ai-toolkit/state/search-required-$(hook_session_id).flag"
|
|
17
21
|
rm -f "$FLAG" 2>/dev/null
|
|
18
22
|
exit 0
|
|
@@ -22,6 +22,10 @@ if [ -n "$TOOLKIT_DIR" ] && command -v python3 >/dev/null 2>&1; then
|
|
|
22
22
|
${SESSION_ID_INPUT:+--session-id "$SESSION_ID_INPUT"} >/dev/null 2>&1 || true
|
|
23
23
|
fi
|
|
24
24
|
|
|
25
|
+
# 1b. GC stale per-session search-required flags (older than 60 min)
|
|
26
|
+
find "$HOME/.softspark/ai-toolkit/state" -maxdepth 1 -name 'search-required-*.flag' \
|
|
27
|
+
-type f -mmin +60 -delete 2>/dev/null || true
|
|
28
|
+
|
|
25
29
|
# 2. Check for updates (cached, max once per 24h, non-blocking)
|
|
26
30
|
VERSION_MSG=$(python3 "$TOOLKIT_DIR/scripts/version_check.py" 2>/dev/null)
|
|
27
31
|
if [ -n "$VERSION_MSG" ]; then
|
|
@@ -21,7 +21,9 @@ source "$(dirname "$0")/_search-capability.sh"
|
|
|
21
21
|
|
|
22
22
|
[ "${CLAUDE_SKIP_SEARCH_FIRST:-0}" = "1" ] && exit 0
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
# shellcheck disable=SC2034 # INPUT is consumed via sourced _hook-io.sh
|
|
25
|
+
INPUT=$(cat 2>/dev/null || true)
|
|
26
|
+
FLAG="$HOME/.softspark/ai-toolkit/state/search-required-$(hook_session_id).flag"
|
|
25
27
|
[ -f "$FLAG" ] || exit 0
|
|
26
28
|
|
|
27
29
|
if ! ai_toolkit_has_search_provider; then
|
|
@@ -32,6 +32,7 @@ source "$(dirname "$0")/_hook-io.sh"
|
|
|
32
32
|
[ "${CLAUDE_HOOK_BOOTSTRAP:-0}" = "1" ] && exit 0
|
|
33
33
|
[ "${CLAUDE_SKIP_COHESION:-0}" = "1" ] && exit 0
|
|
34
34
|
|
|
35
|
+
# shellcheck disable=SC2034 # INPUT is consumed via sourced _hook-io.sh
|
|
35
36
|
INPUT=$(cat)
|
|
36
37
|
FILE_PATH=$(hook_file_path)
|
|
37
38
|
|
package/app/hooks/track-usage.sh
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
STATS_FILE="${HOME}/.softspark/ai-toolkit/stats.json"
|
|
11
11
|
|
|
12
12
|
# Read prompt from stdin (Claude Code passes JSON with .prompt field)
|
|
13
|
+
# shellcheck disable=SC2034 # INPUT is consumed via sourced _hook-io.sh
|
|
13
14
|
INPUT=$(cat)
|
|
14
15
|
# shellcheck source=_hook-io.sh
|
|
15
16
|
source "$(dirname "$0")/_hook-io.sh"
|
|
@@ -13,6 +13,7 @@ source "$(dirname "$0")/_hook-io.sh"
|
|
|
13
13
|
source "$(dirname "$0")/_search-capability.sh"
|
|
14
14
|
|
|
15
15
|
# Read prompt from stdin (Claude Code passes JSON with .prompt field)
|
|
16
|
+
# shellcheck disable=SC2034 # INPUT is consumed via sourced _hook-io.sh
|
|
16
17
|
INPUT=$(cat)
|
|
17
18
|
PROMPT_TEXT=$(hook_prompt)
|
|
18
19
|
LOWERED="$(printf '%s' "$PROMPT_TEXT" | tr '[:upper:]' '[:lower:]')"
|
|
@@ -22,7 +23,7 @@ LOWERED="$(printf '%s' "$PROMPT_TEXT" | tr '[:upper:]' '[:lower:]')"
|
|
|
22
23
|
# Cleared by search-tracker.sh after smart_query / hybrid_search_kb / web runs.
|
|
23
24
|
PROMPT_LEN=${#PROMPT_TEXT}
|
|
24
25
|
STATE_DIR="$HOME/.softspark/ai-toolkit/state"
|
|
25
|
-
FLAG="$STATE_DIR/search-required.flag"
|
|
26
|
+
FLAG="$STATE_DIR/search-required-$(hook_session_id).flag"
|
|
26
27
|
mkdir -p "$STATE_DIR" 2>/dev/null
|
|
27
28
|
if [ "$PROMPT_LEN" -gt 30 ] && \
|
|
28
29
|
[ "${CLAUDE_SKIP_SEARCH_FIRST:-0}" != "1" ] && \
|
package/bin/ai-toolkit.js
CHANGED
|
@@ -69,6 +69,8 @@ const COMMANDS = {
|
|
|
69
69
|
'remove-rule': 'Unregister a rule from ~/.softspark/ai-toolkit/rules/ and remove its block from CLAUDE.md',
|
|
70
70
|
'inject-hook': 'Inject external hooks (file or URL) into ~/.claude/settings.json (URL hooks auto-refresh on update)',
|
|
71
71
|
'remove-hook': 'Remove injected hooks by source name from ~/.claude/settings.json (also unregisters URL source)',
|
|
72
|
+
'inject-mcp': 'Inject external MCP template (file or URL) into ~/.mcp.json + all editor MCP configs (URL templates auto-refresh on update)',
|
|
73
|
+
'remove-mcp': 'Remove injected MCP servers by source name from ~/.mcp.json and all editor configs',
|
|
72
74
|
validate: 'Verify toolkit integrity',
|
|
73
75
|
doctor: 'Check install health, hooks, and artifact drift',
|
|
74
76
|
eject: 'Export standalone config (no symlinks, no toolkit dependency)',
|
|
@@ -257,6 +259,14 @@ function showHelp() {
|
|
|
257
259
|
console.log('\nOptions for remove-hook:');
|
|
258
260
|
console.log(' <source-name> Source tag to remove (also unregisters URL source if present)');
|
|
259
261
|
console.log(' [target-dir] Target dir containing .claude/settings.json (default: $HOME)');
|
|
262
|
+
console.log('\nOptions for inject-mcp:');
|
|
263
|
+
console.log(' <template-file-or-url> Path to JSON file or HTTPS URL with {"mcpServers": {...}}');
|
|
264
|
+
console.log(' [target-dir] Target dir for .mcp.json + editor configs (default: $HOME)');
|
|
265
|
+
console.log(' --name <name> Override source name (default: filename/URL stem)');
|
|
266
|
+
console.log(' --force Overwrite servers tagged with a different _source');
|
|
267
|
+
console.log('\nOptions for remove-mcp:');
|
|
268
|
+
console.log(' <source-name> Source tag to remove (also unregisters URL source and cleans editor configs)');
|
|
269
|
+
console.log(' [target-dir] Target dir containing .mcp.json (default: $HOME)');
|
|
260
270
|
console.log('\nOptions for add-rule:');
|
|
261
271
|
console.log(' <rule-file> Path to .md rule file or HTTPS URL to register globally');
|
|
262
272
|
console.log(' [rule-name] Override rule name (default: filename/URL stem without .md)');
|
|
@@ -417,6 +427,37 @@ function handleRemoveHook(args) {
|
|
|
417
427
|
run(scriptPath('inject_hook_cli.py'), ['--remove', sourceName, targetDir]);
|
|
418
428
|
}
|
|
419
429
|
|
|
430
|
+
/**
|
|
431
|
+
* Handle `ai-toolkit inject-mcp` -- injects external MCP template (file or URL) into .mcp.json
|
|
432
|
+
* and propagates to all editor MCP configs.
|
|
433
|
+
* @param {string[]} args
|
|
434
|
+
*/
|
|
435
|
+
function handleInjectMcp(args) {
|
|
436
|
+
const source = args[0];
|
|
437
|
+
if (!source) {
|
|
438
|
+
console.error('Usage: ai-toolkit inject-mcp <template-file-or-url> [target-dir] [--name <name>] [--force]');
|
|
439
|
+
process.exit(1);
|
|
440
|
+
}
|
|
441
|
+
const isUrl = source.startsWith('https://') || source.startsWith('http://');
|
|
442
|
+
const resolvedSource = isUrl ? source : path.resolve(CWD, source);
|
|
443
|
+
const remaining = args.slice(1);
|
|
444
|
+
run(scriptPath('inject_mcp_cli.py'), [resolvedSource, ...remaining]);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Handle `ai-toolkit remove-mcp` -- removes injected MCP servers by source name.
|
|
449
|
+
* @param {string[]} args
|
|
450
|
+
*/
|
|
451
|
+
function handleRemoveMcp(args) {
|
|
452
|
+
const sourceName = args[0];
|
|
453
|
+
if (!sourceName) {
|
|
454
|
+
console.error('Usage: ai-toolkit remove-mcp <template-source-name> [target-dir]');
|
|
455
|
+
process.exit(1);
|
|
456
|
+
}
|
|
457
|
+
const targetDir = args[1] || process.env.HOME;
|
|
458
|
+
run(scriptPath('inject_mcp_cli.py'), ['--remove', sourceName, targetDir]);
|
|
459
|
+
}
|
|
460
|
+
|
|
420
461
|
/**
|
|
421
462
|
* Handle `ai-toolkit mcp` -- delegates to mcp_manager.py with subcommand.
|
|
422
463
|
* @param {string[]} args
|
|
@@ -566,6 +607,8 @@ const SPECIAL_HANDLERS = {
|
|
|
566
607
|
'add-rule': handleAddRule,
|
|
567
608
|
'inject-hook': handleInjectHook,
|
|
568
609
|
'remove-hook': handleRemoveHook,
|
|
610
|
+
'inject-mcp': handleInjectMcp,
|
|
611
|
+
'remove-mcp': handleRemoveMcp,
|
|
569
612
|
'llms-txt': (_args) => generateLlmsTxt(),
|
|
570
613
|
'antigravity-rules': (_args) => run(scriptPath('generate_antigravity.py'), [CWD]),
|
|
571
614
|
'cursor-mdc': (_args) => run(scriptPath('generate_cursor_mdc.py'), [CWD]),
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
title: "Extension API Reference"
|
|
3
3
|
category: reference
|
|
4
4
|
service: ai-toolkit
|
|
5
|
-
tags: [extension-api, inject-rule, inject-hook, mcp-templates, integration, editors]
|
|
6
|
-
version: "1.
|
|
5
|
+
tags: [extension-api, inject-rule, inject-hook, inject-mcp, mcp-templates, integration, editors]
|
|
6
|
+
version: "1.5.0"
|
|
7
7
|
created: "2026-04-07"
|
|
8
|
-
last_updated: "2026-
|
|
9
|
-
description: "Reference for ai-toolkit's extension API: inject-rule, inject-hook,
|
|
8
|
+
last_updated: "2026-05-12"
|
|
9
|
+
description: "Reference for ai-toolkit's extension API: inject-rule, inject-hook, inject-mcp, remove-* variants, and editor-aware MCP template management."
|
|
10
10
|
---
|
|
11
11
|
|
|
12
12
|
# Extension API Reference
|
|
@@ -25,8 +25,10 @@ This design is intentional: ai-toolkit is a generic toolkit. Consumers (MCP serv
|
|
|
25
25
|
| `remove-rule <name>` | `~/.claude/CLAUDE.md` | Strip markers by block name | Yes |
|
|
26
26
|
| `inject-hook <file.json\|url> [name]` | `~/.claude/settings.json` | JSON `_source` tag per entry, URL cached + registered | Yes |
|
|
27
27
|
| `remove-hook <name>` | `~/.claude/settings.json` | Strip all entries with matching `_source`, unregister URL source | Yes |
|
|
28
|
+
| `inject-mcp <file.json\|url> [name] [--force]` | `~/.mcp.json` + every editor with `global_path` | JSON `_source` tag per server, URL cached + registered, full editor propagation | Yes |
|
|
29
|
+
| `remove-mcp <name>` | `~/.mcp.json` + every editor with `global_path` | Strip all servers with matching `_source`, clean editor configs, unregister URL | Yes |
|
|
28
30
|
| `add-rule <file.md\|url>` | `~/.softspark/ai-toolkit/rules/` | File copy + re-inject all rules on next `update` | Yes |
|
|
29
|
-
| `mcp add <name...>` | `.mcp.json` | Merge `mcpServers` block from template | Yes |
|
|
31
|
+
| `mcp add <name...>` | `.mcp.json` | Merge `mcpServers` block from built-in template | Yes |
|
|
30
32
|
| `mcp install --editor <name...>` | Native editor MCP config | Render canonical template into editor format | Yes |
|
|
31
33
|
|
|
32
34
|
## inject-rule
|
|
@@ -115,6 +117,67 @@ npx @softspark/ai-toolkit remove-hook my-tool-hooks
|
|
|
115
117
|
|
|
116
118
|
The argument is the source name (file stem used during `inject-hook`). If no entries with that source are present, the command exits 0 silently.
|
|
117
119
|
|
|
120
|
+
## inject-mcp
|
|
121
|
+
|
|
122
|
+
Injects an external MCP server template into `~/.mcp.json` (toolkit source-of-truth) and propagates it to every editor that exposes a `global_path` in `EDITOR_SPECS`. Symmetric with `inject-hook` -- accepts both local file paths and HTTPS URLs, with cache + auto-refresh on `ai-toolkit update`.
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
# From local file
|
|
126
|
+
npx @softspark/ai-toolkit inject-mcp ./rag-mcp-template.json
|
|
127
|
+
|
|
128
|
+
# From URL (cached locally, auto-refreshed on update)
|
|
129
|
+
npx @softspark/ai-toolkit inject-mcp https://example.com/rag-mcp-template.json
|
|
130
|
+
|
|
131
|
+
# With explicit source name (preferred when filename stem is generic)
|
|
132
|
+
npx @softspark/ai-toolkit inject-mcp ./mcp/mcp-template.json --name rag-mcp
|
|
133
|
+
|
|
134
|
+
# With explicit target dir
|
|
135
|
+
npx @softspark/ai-toolkit inject-mcp ./template.json /custom/target --name my-rag
|
|
136
|
+
|
|
137
|
+
# Force overwrite of servers with a different _source (collision resolution)
|
|
138
|
+
npx @softspark/ai-toolkit inject-mcp ./conflict.json --force
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
**Flags:** `--name <name>` overrides the auto-derived source name (works for both local files and URLs). `--force` overwrites servers tagged with a different `_source`. Positional `template-name` is supported only for URL sources (legacy positional grammar inherited from `inject-hook`); for local files use `--name`.
|
|
142
|
+
|
|
143
|
+
**Implementation:** `scripts/inject_mcp_cli.py`, `scripts/mcp_sources.py`, `scripts/url_fetch.py`.
|
|
144
|
+
|
|
145
|
+
**Input format:** Same as built-in templates in `app/mcp-templates/`:
|
|
146
|
+
```json
|
|
147
|
+
{
|
|
148
|
+
"name": "rag-mcp",
|
|
149
|
+
"description": "Multi-tenant RAG over knowledge bases",
|
|
150
|
+
"mcpServers": {
|
|
151
|
+
"rag-mcp": {
|
|
152
|
+
"type": "http",
|
|
153
|
+
"url": "http://localhost:8081/mcp/sse?secret_key=${RAG_MCP_SECRET_KEY}"
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
**Source name derivation:** `rag-mcp-template.json` → `"rag-mcp-template"`. For URLs: `https://example.com/rag-mcp-template.json` → `"rag-mcp-template"`. Every server in the `mcpServers` block is tagged with `"_source": "<source-name>"` inside `~/.mcp.json` only; native editor configs receive the same servers **without** the `_source` field (some clients reject unknown keys).
|
|
160
|
+
|
|
161
|
+
**URL support:** When an HTTPS URL is provided, the JSON is fetched, validated, cached in `~/.softspark/ai-toolkit/mcp-templates/external/<name>.json`, and registered in `sources.json`. On every `ai-toolkit update`, URL-sourced templates are re-fetched and re-injected automatically. If the fetch fails during update, the cached version is used.
|
|
162
|
+
|
|
163
|
+
**Editor propagation:** Every editor with a `global_path` in `EDITOR_SPECS` is updated -- Claude (`~/.claude.json`), Cursor (`~/.cursor/mcp.json`), GitHub Copilot (`~/.copilot/mcp-config.json`), Gemini CLI (`~/.gemini/settings.json`), Windsurf (`~/.codeium/windsurf/mcp_config.json`), Cline (`~/.cline/data/settings/cline_mcp_settings.json`), Augment (`~/.augment/settings.json`), Codex CLI (`~/.codex/config.toml`). Per-editor failures are non-fatal -- the command reports a warning and continues.
|
|
164
|
+
|
|
165
|
+
**Idempotency:** Re-running with the same source overwrites entries for that source cleanly -- no duplicates accumulate.
|
|
166
|
+
|
|
167
|
+
**Collisions:** If a server name in `~/.mcp.json` already exists under a *different* `_source` tag, the command exits with code 3 unless `--force` is passed. Entries tagged `"_source": "ai-toolkit"` are protected even with `--force` -- the built-in template namespace cannot be hijacked.
|
|
168
|
+
|
|
169
|
+
**Safety:** Only HTTPS URLs are accepted. The source name `ai-toolkit` is reserved.
|
|
170
|
+
|
|
171
|
+
## remove-mcp
|
|
172
|
+
|
|
173
|
+
Strips all server entries from `~/.mcp.json` that carry a given `_source` tag, cleans the same server names from every editor `global_path`, and (if URL-sourced) unregisters from `sources.json` and removes the cached file.
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
npx @softspark/ai-toolkit remove-mcp rag-mcp-template
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
The argument is the source name (file stem used during `inject-mcp`). If no entries with that source are present, the command exits 0 silently. `ai-toolkit` source is reserved and cannot be removed via this command.
|
|
180
|
+
|
|
118
181
|
## mcp add / install
|
|
119
182
|
|
|
120
183
|
Merges one or more MCP server templates from `app/mcp-templates/` into the project's `.mcp.json`.
|
|
@@ -153,6 +216,8 @@ When `install` runs with `--scope project`, ai-toolkit also updates `.mcp.json`
|
|
|
153
216
|
│ remove-rule <name> → CLAUDE.md │
|
|
154
217
|
│ inject-hook <file|url> → settings.json │
|
|
155
218
|
│ remove-hook <name> → settings.json │
|
|
219
|
+
│ inject-mcp <file|url> → .mcp.json + editors │
|
|
220
|
+
│ remove-mcp <name> → .mcp.json + editors │
|
|
156
221
|
│ add-rule <file|url> → rules/ registry │
|
|
157
222
|
│ mcp add <template> → .mcp.json │
|
|
158
223
|
│ mcp install <template> → editor-native MCP │
|
|
@@ -168,7 +233,7 @@ When `install` runs with `--scope project`, ai-toolkit also updates `.mcp.json`
|
|
|
168
233
|
(consumer) (consumer) (consumer)
|
|
169
234
|
```
|
|
170
235
|
|
|
171
|
-
## Example: Registering Rules and
|
|
236
|
+
## Example: Registering Rules, Hooks, and MCP Servers from an External Tool
|
|
172
237
|
|
|
173
238
|
An external tool's install script would call:
|
|
174
239
|
|
|
@@ -176,14 +241,14 @@ An external tool's install script would call:
|
|
|
176
241
|
# Register rules into CLAUDE.md
|
|
177
242
|
npx @softspark/ai-toolkit inject-rule ./rules/my-tool-rules.md
|
|
178
243
|
|
|
179
|
-
# Register hooks into settings.json
|
|
244
|
+
# Register hooks into settings.json (auto-propagates to Codex)
|
|
180
245
|
npx @softspark/ai-toolkit inject-hook ./hooks/my-tool-hooks.json
|
|
181
246
|
|
|
182
|
-
#
|
|
183
|
-
npx @softspark/ai-toolkit mcp
|
|
247
|
+
# Register MCP server template into .mcp.json + all editor MCP configs
|
|
248
|
+
npx @softspark/ai-toolkit inject-mcp ./mcp-template.json
|
|
184
249
|
|
|
185
|
-
#
|
|
186
|
-
npx @softspark/ai-toolkit mcp
|
|
250
|
+
# Alternative: pull MCP template from a URL (auto-refreshed on update)
|
|
251
|
+
npx @softspark/ai-toolkit inject-mcp https://example.com/mcp-template.json
|
|
187
252
|
```
|
|
188
253
|
|
|
189
254
|
To uninstall:
|
|
@@ -191,6 +256,7 @@ To uninstall:
|
|
|
191
256
|
```bash
|
|
192
257
|
npx @softspark/ai-toolkit remove-rule my-tool-rules
|
|
193
258
|
npx @softspark/ai-toolkit remove-hook my-tool-hooks
|
|
259
|
+
npx @softspark/ai-toolkit remove-mcp my-tool
|
|
194
260
|
```
|
|
195
261
|
|
|
196
262
|
All operations are idempotent — safe to run on every install or update.
|
|
@@ -381,7 +381,7 @@ First-match-wins per file. Built-in runners: `bats`, `pytest`, `vitest`, `jest`.
|
|
|
381
381
|
| Script | `~/.softspark/ai-toolkit/hooks/search-tracker.sh` |
|
|
382
382
|
| Fires | After any search-style tool call |
|
|
383
383
|
|
|
384
|
-
**Action:** Clears `~/.softspark/ai-toolkit/state/search-required
|
|
384
|
+
**Action:** Clears `~/.softspark/ai-toolkit/state/search-required-<session_id>.flag` (per-session, keyed by `session_id` from the hook stdin payload, falling back to `transcript_path` basename, then `default`). Pairs with `user-prompt-submit.sh` (sets the flag on long technical prompts only when a search provider is detected or strict mode is enabled) and `stop-search-check.sh` (blocks Stop if the calling session's flag is still set). Together they enforce the global CLAUDE.md GOLDEN RULE without breaking offline/no-RAG installs and without cross-session interference when multiple Claude Code windows run in parallel.
|
|
385
385
|
|
|
386
386
|
Non-blocking (exit 0). Skipped when `TOOLKIT_HOOK_PROFILE=minimal`.
|
|
387
387
|
|
|
@@ -394,7 +394,7 @@ Non-blocking (exit 0). Skipped when `TOOLKIT_HOOK_PROFILE=minimal`.
|
|
|
394
394
|
| Script | `~/.softspark/ai-toolkit/hooks/stop-search-check.sh` |
|
|
395
395
|
| Fires | When Claude finishes a response |
|
|
396
396
|
|
|
397
|
-
**Action:** If `search-required
|
|
397
|
+
**Action:** If `search-required-<session_id>.flag` for the calling session is still present (no search tool ran during this turn) and a search provider is still detectable, emits `{"decision":"block","reason":"..."}` to continue the conversation with a search-first reminder. If no RAG/Web provider is detected, it clears the stale flag and exits 0, so offline/no-MCP users are not blocked. Flags are scoped by `session_id` from the hook stdin payload so a Stop in session B never consumes session A's flag (and vice versa). Stale per-session flags older than 60 minutes are GC'd on the next `SessionStart`.
|
|
398
398
|
|
|
399
399
|
**Overrides:** `CLAUDE_SKIP_SEARCH_FIRST=1`, `AI_TOOLKIT_SEARCH_FIRST=off`, or `AI_TOOLKIT_SEARCH_FIRST=strict` to force enforcement. Skipped when `TOOLKIT_HOOK_PROFILE=minimal`.
|
|
400
400
|
|
|
@@ -456,7 +456,7 @@ Set in `.claude/settings.local.json`:
|
|
|
456
456
|
├── rules/ # Registered rules (add-rule.sh)
|
|
457
457
|
├── state/ # Per-session runtime state (NEW)
|
|
458
458
|
│ ├── session-edits.json # Append-only edit log per session
|
|
459
|
-
│ ├── search-required
|
|
459
|
+
│ ├── search-required-<sid>.flag # Per-session: set by user-prompt-submit, cleared by search-tracker/stop-search-check, GC'd at SessionStart (>60min)
|
|
460
460
|
│ ├── loaded-instructions.log # Audit trail of which rules entered context
|
|
461
461
|
│ └── test-cohesion-last.log # Last cohesion test command output
|
|
462
462
|
└── hooks/ # Hook scripts (copied on install)
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
title: "MCP Server Templates"
|
|
3
3
|
category: reference
|
|
4
4
|
service: ai-toolkit
|
|
5
|
-
tags: [mcp, templates, servers, configuration, editors]
|
|
6
|
-
version: "1.
|
|
5
|
+
tags: [mcp, templates, servers, configuration, editors, inject-mcp, external-templates]
|
|
6
|
+
version: "1.2.0"
|
|
7
7
|
created: "2026-04-07"
|
|
8
|
-
last_updated: "2026-
|
|
9
|
-
description: "Reference for 26 MCP server templates
|
|
8
|
+
last_updated: "2026-05-12"
|
|
9
|
+
description: "Reference for 26 built-in MCP server templates, external template injection via inject-mcp, and native editor MCP installation support."
|
|
10
10
|
---
|
|
11
11
|
|
|
12
12
|
# MCP Server Templates
|
|
@@ -15,6 +15,8 @@ description: "Reference for 26 MCP server templates plus native editor MCP insta
|
|
|
15
15
|
|
|
16
16
|
ai-toolkit ships 26 ready-to-use MCP server configuration templates in `app/mcp-templates/`. Each template is a JSON file that defines the canonical `mcpServers` block for a specific service. Templates can be merged into the project's `.mcp.json` and rendered into editor-native MCP config files via the `ai-toolkit mcp` CLI subcommand.
|
|
17
17
|
|
|
18
|
+
**External templates:** Tools outside the toolkit (MCP servers, plugins, custom integrations) can register their own MCP templates via `ai-toolkit inject-mcp <file|url>` -- the toolkit caches the template, tags every server with a `_source` field, and propagates the config to every editor that exposes a `global_path`. URL-sourced templates are auto-refreshed on every `ai-toolkit update`. See [PATH: kb/reference/extension-api.md] for the inject-mcp / remove-mcp reference.
|
|
19
|
+
|
|
18
20
|
## CLI
|
|
19
21
|
|
|
20
22
|
```bash
|