@softspark/ai-toolkit 4.3.0 → 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 CHANGED
@@ -7,6 +7,28 @@ 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
+
10
32
  ## v4.3.0 - inject-mcp extension API (2026-05-12)
11
33
 
12
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`.
package/README.md CHANGED
@@ -6,16 +6,16 @@
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
7
7
  [![Skills](https://img.shields.io/badge/skills-107-brightgreen)](app/skills/)
8
8
  [![Agents](https://img.shields.io/badge/agents-44-blue)](app/agents/)
9
- [![Tests](https://img.shields.io/badge/tests-1131%20passing-success)](tests/)
9
+ [![Tests](https://img.shields.io/badge/tests-1133%20passing-success)](tests/)
10
10
 
11
- ## What's New in v4.3.0
11
+ ## What's New in v4.3.1
12
12
 
13
- Minor release. Closes the asymmetry between `inject-rule` / `inject-hook` and MCP servers -- external tools (rag-mcp, jira-mcp, custom integrations) can now register MCP templates the same way they register rules and hooks.
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
- - **`ai-toolkit inject-mcp <file|url>`**: register an external MCP template into `~/.mcp.json` + every editor with a `global_path` (Claude, Cursor, Codex, Gemini, Windsurf, Cline, Augment, Copilot) in one command.
16
- - **`ai-toolkit remove-mcp <name>`**: strip injected servers from `~/.mcp.json` and every editor config in one command.
17
- - **URL templates + auto-refresh**: HTTPS-sourced templates are cached and re-fetched on every `ai-toolkit update`, just like URL rules and hooks.
18
- - **`--name` and `--force` flags**: explicit source naming for both files and URLs, plus collision-safe overwrites that still protect `ai-toolkit` built-in entries.
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 (1131 tests)
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.3.0",
4
+ "version": "4.3.1",
5
5
  "author": {
6
6
  "name": "SoftSpark",
7
7
  "url": "https://github.com/softspark"
@@ -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
  }
@@ -4,6 +4,7 @@
4
4
  # Fires on: PreToolUse (Bash)
5
5
  # Non-blocking: always exits 0 (warnings only).
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"
@@ -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"
@@ -22,6 +22,7 @@ source "$(dirname "$0")/_locate-toolkit.sh"
22
22
  # shellcheck source=_hook-io.sh
23
23
  source "$(dirname "$0")/_hook-io.sh"
24
24
 
25
+ # shellcheck disable=SC2034 # INPUT is consumed via sourced _hook-io.sh
25
26
  INPUT=$(cat)
26
27
  COMMAND=$(hook_command)
27
28
 
@@ -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
- FLAG="$HOME/.softspark/ai-toolkit/state/search-required.flag"
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
- FLAG="$HOME/.softspark/ai-toolkit/state/search-required.flag"
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
 
@@ -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" ] && \
@@ -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.flag`. Pairs with `user-prompt-submit.sh` (sets 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 flag still set). Together they enforce the global CLAUDE.md GOLDEN RULE without breaking offline/no-RAG installs.
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.flag` 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.
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.flag # Set by user-prompt-submit, cleared by search-tracker
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)
package/llms-full.txt CHANGED
@@ -8186,11 +8186,11 @@ Run `ai-toolkit update --local` to re-resolve and update the lock file.
8186
8186
  title: "Extension API Reference"
8187
8187
  category: reference
8188
8188
  service: ai-toolkit
8189
- tags: [extension-api, inject-rule, inject-hook, mcp-templates, integration, editors]
8190
- version: "1.4.0"
8189
+ tags: [extension-api, inject-rule, inject-hook, inject-mcp, mcp-templates, integration, editors]
8190
+ version: "1.5.0"
8191
8191
  created: "2026-04-07"
8192
- last_updated: "2026-04-15"
8193
- description: "Reference for ai-toolkit's extension API: inject-rule, inject-hook, remove-rule, remove-hook, and editor-aware MCP template management."
8192
+ last_updated: "2026-05-12"
8193
+ description: "Reference for ai-toolkit's extension API: inject-rule, inject-hook, inject-mcp, remove-* variants, and editor-aware MCP template management."
8194
8194
  ---
8195
8195
 
8196
8196
  # Extension API Reference
@@ -8209,8 +8209,10 @@ This design is intentional: ai-toolkit is a generic toolkit. Consumers (MCP serv
8209
8209
  | `remove-rule <name>` | `~/.claude/CLAUDE.md` | Strip markers by block name | Yes |
8210
8210
  | `inject-hook <file.json\|url> [name]` | `~/.claude/settings.json` | JSON `_source` tag per entry, URL cached + registered | Yes |
8211
8211
  | `remove-hook <name>` | `~/.claude/settings.json` | Strip all entries with matching `_source`, unregister URL source | Yes |
8212
+ | `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 |
8213
+ | `remove-mcp <name>` | `~/.mcp.json` + every editor with `global_path` | Strip all servers with matching `_source`, clean editor configs, unregister URL | Yes |
8212
8214
  | `add-rule <file.md\|url>` | `~/.softspark/ai-toolkit/rules/` | File copy + re-inject all rules on next `update` | Yes |
8213
- | `mcp add <name...>` | `.mcp.json` | Merge `mcpServers` block from template | Yes |
8215
+ | `mcp add <name...>` | `.mcp.json` | Merge `mcpServers` block from built-in template | Yes |
8214
8216
  | `mcp install --editor <name...>` | Native editor MCP config | Render canonical template into editor format | Yes |
8215
8217
 
8216
8218
  ## inject-rule
@@ -8299,6 +8301,67 @@ npx @softspark/ai-toolkit remove-hook my-tool-hooks
8299
8301
 
8300
8302
  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.
8301
8303
 
8304
+ ## inject-mcp
8305
+
8306
+ 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`.
8307
+
8308
+ ```bash
8309
+ # From local file
8310
+ npx @softspark/ai-toolkit inject-mcp ./rag-mcp-template.json
8311
+
8312
+ # From URL (cached locally, auto-refreshed on update)
8313
+ npx @softspark/ai-toolkit inject-mcp https://example.com/rag-mcp-template.json
8314
+
8315
+ # With explicit source name (preferred when filename stem is generic)
8316
+ npx @softspark/ai-toolkit inject-mcp ./mcp/mcp-template.json --name rag-mcp
8317
+
8318
+ # With explicit target dir
8319
+ npx @softspark/ai-toolkit inject-mcp ./template.json /custom/target --name my-rag
8320
+
8321
+ # Force overwrite of servers with a different _source (collision resolution)
8322
+ npx @softspark/ai-toolkit inject-mcp ./conflict.json --force
8323
+ ```
8324
+
8325
+ **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`.
8326
+
8327
+ **Implementation:** `scripts/inject_mcp_cli.py`, `scripts/mcp_sources.py`, `scripts/url_fetch.py`.
8328
+
8329
+ **Input format:** Same as built-in templates in `app/mcp-templates/`:
8330
+ ```json
8331
+ {
8332
+ "name": "rag-mcp",
8333
+ "description": "Multi-tenant RAG over knowledge bases",
8334
+ "mcpServers": {
8335
+ "rag-mcp": {
8336
+ "type": "http",
8337
+ "url": "http://localhost:8081/mcp/sse?secret_key=${RAG_MCP_SECRET_KEY}"
8338
+ }
8339
+ }
8340
+ }
8341
+ ```
8342
+
8343
+ **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).
8344
+
8345
+ **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.
8346
+
8347
+ **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.
8348
+
8349
+ **Idempotency:** Re-running with the same source overwrites entries for that source cleanly -- no duplicates accumulate.
8350
+
8351
+ **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.
8352
+
8353
+ **Safety:** Only HTTPS URLs are accepted. The source name `ai-toolkit` is reserved.
8354
+
8355
+ ## remove-mcp
8356
+
8357
+ 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.
8358
+
8359
+ ```bash
8360
+ npx @softspark/ai-toolkit remove-mcp rag-mcp-template
8361
+ ```
8362
+
8363
+ 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.
8364
+
8302
8365
  ## mcp add / install
8303
8366
 
8304
8367
  Merges one or more MCP server templates from `app/mcp-templates/` into the project's `.mcp.json`.
@@ -8337,6 +8400,8 @@ When `install` runs with `--scope project`, ai-toolkit also updates `.mcp.json`
8337
8400
  │ remove-rule <name> → CLAUDE.md │
8338
8401
  │ inject-hook <file|url> → settings.json │
8339
8402
  │ remove-hook <name> → settings.json │
8403
+ │ inject-mcp <file|url> → .mcp.json + editors │
8404
+ │ remove-mcp <name> → .mcp.json + editors │
8340
8405
  │ add-rule <file|url> → rules/ registry │
8341
8406
  │ mcp add <template> → .mcp.json │
8342
8407
  │ mcp install <template> → editor-native MCP │
@@ -8352,7 +8417,7 @@ When `install` runs with `--scope project`, ai-toolkit also updates `.mcp.json`
8352
8417
  (consumer) (consumer) (consumer)
8353
8418
  ```
8354
8419
 
8355
- ## Example: Registering Rules and Hooks from an External Tool
8420
+ ## Example: Registering Rules, Hooks, and MCP Servers from an External Tool
8356
8421
 
8357
8422
  An external tool's install script would call:
8358
8423
 
@@ -8360,14 +8425,14 @@ An external tool's install script would call:
8360
8425
  # Register rules into CLAUDE.md
8361
8426
  npx @softspark/ai-toolkit inject-rule ./rules/my-tool-rules.md
8362
8427
 
8363
- # Register hooks into settings.json
8428
+ # Register hooks into settings.json (auto-propagates to Codex)
8364
8429
  npx @softspark/ai-toolkit inject-hook ./hooks/my-tool-hooks.json
8365
8430
 
8366
- # Add an MCP server template
8367
- npx @softspark/ai-toolkit mcp add github
8431
+ # Register MCP server template into .mcp.json + all editor MCP configs
8432
+ npx @softspark/ai-toolkit inject-mcp ./mcp-template.json
8368
8433
 
8369
- # Render the same template into Cursor project config
8370
- npx @softspark/ai-toolkit mcp install --editor cursor --scope project github --target .
8434
+ # Alternative: pull MCP template from a URL (auto-refreshed on update)
8435
+ npx @softspark/ai-toolkit inject-mcp https://example.com/mcp-template.json
8371
8436
  ```
8372
8437
 
8373
8438
  To uninstall:
@@ -8375,6 +8440,7 @@ To uninstall:
8375
8440
  ```bash
8376
8441
  npx @softspark/ai-toolkit remove-rule my-tool-rules
8377
8442
  npx @softspark/ai-toolkit remove-hook my-tool-hooks
8443
+ npx @softspark/ai-toolkit remove-mcp my-tool
8378
8444
  ```
8379
8445
 
8380
8446
  All operations are idempotent — safe to run on every install or update.
@@ -9136,7 +9202,7 @@ First-match-wins per file. Built-in runners: `bats`, `pytest`, `vitest`, `jest`.
9136
9202
  | Script | `~/.softspark/ai-toolkit/hooks/search-tracker.sh` |
9137
9203
  | Fires | After any search-style tool call |
9138
9204
 
9139
- **Action:** Clears `~/.softspark/ai-toolkit/state/search-required.flag`. Pairs with `user-prompt-submit.sh` (sets 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 flag still set). Together they enforce the global CLAUDE.md GOLDEN RULE without breaking offline/no-RAG installs.
9205
+ **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.
9140
9206
 
9141
9207
  Non-blocking (exit 0). Skipped when `TOOLKIT_HOOK_PROFILE=minimal`.
9142
9208
 
@@ -9149,7 +9215,7 @@ Non-blocking (exit 0). Skipped when `TOOLKIT_HOOK_PROFILE=minimal`.
9149
9215
  | Script | `~/.softspark/ai-toolkit/hooks/stop-search-check.sh` |
9150
9216
  | Fires | When Claude finishes a response |
9151
9217
 
9152
- **Action:** If `search-required.flag` 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.
9218
+ **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`.
9153
9219
 
9154
9220
  **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`.
9155
9221
 
@@ -9211,7 +9277,7 @@ Set in `.claude/settings.local.json`:
9211
9277
  ├── rules/ # Registered rules (add-rule.sh)
9212
9278
  ├── state/ # Per-session runtime state (NEW)
9213
9279
  │ ├── session-edits.json # Append-only edit log per session
9214
- │ ├── search-required.flag # Set by user-prompt-submit, cleared by search-tracker
9280
+ │ ├── search-required-<sid>.flag # Per-session: set by user-prompt-submit, cleared by search-tracker/stop-search-check, GC'd at SessionStart (>60min)
9215
9281
  │ ├── loaded-instructions.log # Audit trail of which rules entered context
9216
9282
  │ └── test-cohesion-last.log # Last cohesion test command output
9217
9283
  └── hooks/ # Hook scripts (copied on install)
@@ -9911,11 +9977,11 @@ Global-only clients are configured explicitly via `ai-toolkit mcp install --edit
9911
9977
  title: "MCP Server Templates"
9912
9978
  category: reference
9913
9979
  service: ai-toolkit
9914
- tags: [mcp, templates, servers, configuration, editors]
9915
- version: "1.1.0"
9980
+ tags: [mcp, templates, servers, configuration, editors, inject-mcp, external-templates]
9981
+ version: "1.2.0"
9916
9982
  created: "2026-04-07"
9917
- last_updated: "2026-04-12"
9918
- description: "Reference for 26 MCP server templates plus native editor MCP installation support."
9983
+ last_updated: "2026-05-12"
9984
+ description: "Reference for 26 built-in MCP server templates, external template injection via inject-mcp, and native editor MCP installation support."
9919
9985
  ---
9920
9986
 
9921
9987
  # MCP Server Templates
@@ -9924,6 +9990,8 @@ description: "Reference for 26 MCP server templates plus native editor MCP insta
9924
9990
 
9925
9991
  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.
9926
9992
 
9993
+ **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.
9994
+
9927
9995
  ## CLI
9928
9996
 
9929
9997
  ```bash
package/manifest.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "4.3.0",
2
+ "version": "4.3.1",
3
3
  "components": {
4
4
  "agents": {
5
5
  "description": "44 specialized agents (orchestrator, backend, frontend, security, devops, etc.)",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@softspark/ai-toolkit",
3
- "version": "4.3.0",
3
+ "version": "4.3.1",
4
4
  "description": "AI coding toolkit: 107 skills, 44 agents, 12-editor write-through (Claude, Cursor, Windsurf, Copilot, Gemini, Cline, Roo, Aider, Augment, Antigravity, Codex, opencode), machine-enforced safety constitution, SARIF audit, signed npm provenance.",
5
5
  "keywords": [
6
6
  "claude",
@@ -33,7 +33,7 @@ import sys
33
33
  from pathlib import Path
34
34
 
35
35
  DEFAULT_RUNNERS: dict[str, str] = {
36
- "bats": "bats --no-parallelize-within-files",
36
+ "bats": "bats",
37
37
  "pytest": "pytest -x",
38
38
  "vitest": "npx vitest run",
39
39
  "jest": "npx jest --bail",