bluera-knowledge 0.31.0 → 0.33.0
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/.claude-plugin/plugin.json +23 -0
- package/.mcp.json +13 -0
- package/CHANGELOG.md +42 -0
- package/NOTICE +47 -0
- package/README.md +2 -2
- package/bun.lock +1978 -0
- package/dist/{chunk-B335UOU7.js → chunk-3TB7TDVF.js} +24 -3
- package/dist/chunk-3TB7TDVF.js.map +1 -0
- package/dist/{chunk-KCI4U6FH.js → chunk-KDZDLJUY.js} +2 -2
- package/dist/{chunk-AEXFPA57.js → chunk-YDTTD53Y.js} +158 -26
- package/dist/chunk-YDTTD53Y.js.map +1 -0
- package/dist/index.js +3 -3
- package/dist/mcp/bootstrap.js +10 -0
- package/dist/mcp/bootstrap.js.map +1 -1
- package/dist/mcp/server.d.ts +5 -3
- package/dist/mcp/server.js +2 -2
- package/dist/workers/background-worker-cli.js +2 -2
- package/hooks/check-ready.sh +109 -0
- package/hooks/hooks.json +97 -0
- package/hooks/job-status-hook.sh +51 -0
- package/hooks/posttooluse-bk-reminder.py +126 -0
- package/hooks/posttooluse-web-research.py +209 -0
- package/hooks/posttooluse-websearch-bk.py +158 -0
- package/hooks/pretooluse-bk-suggest.py +296 -0
- package/hooks/skill-activation.py +221 -0
- package/hooks/skill-rules.json +131 -0
- package/package.json +9 -2
- package/scripts/CLAUDE.md +65 -0
- package/scripts/auto-setup.sh +65 -0
- package/scripts/bench-regression.sh +345 -0
- package/scripts/dev.sh +16 -0
- package/scripts/doctor.sh +103 -0
- package/scripts/download-models.ts +188 -0
- package/scripts/export-web-store.ts +142 -0
- package/scripts/lib/mock-server.sh +70 -0
- package/scripts/mcp-wrapper.sh +91 -0
- package/scripts/setup.sh +224 -0
- package/scripts/statusline-module.sh +29 -0
- package/scripts/test-mcp-dev.js +260 -0
- package/scripts/validate-local.sh +412 -0
- package/scripts/validate-npm-release.sh +406 -0
- package/skills/add-folder/SKILL.md +48 -0
- package/skills/add-repo/SKILL.md +50 -0
- package/skills/advanced-workflows/SKILL.md +273 -0
- package/skills/cancel/SKILL.md +63 -0
- package/skills/check-status/SKILL.md +130 -0
- package/skills/crawl/SKILL.md +61 -0
- package/skills/doctor/SKILL.md +27 -0
- package/skills/eval/SKILL.md +222 -0
- package/skills/health/SKILL.md +72 -0
- package/skills/index/SKILL.md +48 -0
- package/skills/knowledge-search/SKILL.md +110 -0
- package/skills/remove-store/SKILL.md +52 -0
- package/skills/search/SKILL.md +80 -0
- package/skills/search/search.sh +63 -0
- package/skills/search-optimization/SKILL.md +199 -0
- package/skills/search-optimization/references/mistakes.md +21 -0
- package/skills/search-optimization/references/strategies.md +80 -0
- package/skills/skill-activation/SKILL.md +131 -0
- package/skills/statusline/SKILL.md +19 -0
- package/skills/store-lifecycle/SKILL.md +470 -0
- package/skills/stores/SKILL.md +54 -0
- package/skills/suggest/SKILL.md +118 -0
- package/skills/sync/SKILL.md +96 -0
- package/skills/test-plugin/SKILL.md +547 -0
- package/skills/uninstall/SKILL.md +65 -0
- package/skills/when-to-query/SKILL.md +160 -0
- package/dist/chunk-AEXFPA57.js.map +0 -1
- package/dist/chunk-B335UOU7.js.map +0 -1
- /package/dist/{chunk-KCI4U6FH.js.map → chunk-KDZDLJUY.js.map} +0 -0
package/scripts/setup.sh
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Bluera Knowledge Plugin - Setup Script
|
|
3
|
+
# Runs on: SessionStart (via auto-setup.sh) or manually
|
|
4
|
+
#
|
|
5
|
+
# Installs:
|
|
6
|
+
# - MCP wrapper script (bluera-knowledge-mcp)
|
|
7
|
+
# - Node.js dependencies (bun install / npm ci)
|
|
8
|
+
# - Playwright Chromium browser (for web crawling)
|
|
9
|
+
#
|
|
10
|
+
# Environment variables:
|
|
11
|
+
# NONINTERACTIVE=1 - Skip interactive prompts (for auto-setup)
|
|
12
|
+
|
|
13
|
+
set -e
|
|
14
|
+
|
|
15
|
+
# Get the plugin root directory
|
|
16
|
+
PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$(dirname "$(dirname "$0")")}"
|
|
17
|
+
|
|
18
|
+
# Non-interactive mode (set by auto-setup.sh)
|
|
19
|
+
NONINTERACTIVE="${NONINTERACTIVE:-}"
|
|
20
|
+
|
|
21
|
+
# Colors for output
|
|
22
|
+
GREEN='\033[0;32m'
|
|
23
|
+
YELLOW='\033[1;33m'
|
|
24
|
+
RED='\033[0;31m'
|
|
25
|
+
NC='\033[0m'
|
|
26
|
+
|
|
27
|
+
# Debug logging - writes JSON to same log file as bootstrap.ts
|
|
28
|
+
# Uses PROJECT_ROOT if available (set by Claude Code), else current dir
|
|
29
|
+
LOG_DIR="${PROJECT_ROOT:-.}/.bluera/bluera-knowledge/logs"
|
|
30
|
+
LOG_FILE="$LOG_DIR/app.log"
|
|
31
|
+
|
|
32
|
+
log_debug() {
|
|
33
|
+
local msg="$1"
|
|
34
|
+
mkdir -p "$LOG_DIR" 2>/dev/null || true
|
|
35
|
+
# macOS date doesn't support %3N, fallback to seconds-only
|
|
36
|
+
local timestamp
|
|
37
|
+
timestamp=$(date -u +"%Y-%m-%dT%H:%M:%S.000Z" 2>/dev/null || date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
38
|
+
echo "{\"time\":\"$timestamp\",\"level\":\"debug\",\"module\":\"setup.sh\",\"msg\":\"$msg\"}" >> "$LOG_FILE" 2>/dev/null || true
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
log_debug "Setup starting, PLUGIN_ROOT=$PLUGIN_ROOT, PROJECT_ROOT=${PROJECT_ROOT:-unset}"
|
|
42
|
+
|
|
43
|
+
echo -e "${YELLOW}[bluera-knowledge] Running setup...${NC}"
|
|
44
|
+
|
|
45
|
+
# =====================
|
|
46
|
+
# Node.js Dependencies
|
|
47
|
+
# =====================
|
|
48
|
+
|
|
49
|
+
log_debug "Checking node_modules at $PLUGIN_ROOT/node_modules"
|
|
50
|
+
|
|
51
|
+
# =====================
|
|
52
|
+
# Build Tools Check (required for native modules)
|
|
53
|
+
# =====================
|
|
54
|
+
|
|
55
|
+
log_debug "Checking build tools"
|
|
56
|
+
|
|
57
|
+
if command -v make &> /dev/null; then
|
|
58
|
+
log_debug "make found"
|
|
59
|
+
echo -e "${GREEN}[bluera-knowledge] Build tools available ✓${NC}"
|
|
60
|
+
else
|
|
61
|
+
log_debug "make not found, prompting for install"
|
|
62
|
+
echo -e "${YELLOW}[bluera-knowledge] Build tools (make) not found - required for native modules${NC}"
|
|
63
|
+
|
|
64
|
+
# Detect platform and set install command
|
|
65
|
+
if [ -f /etc/debian_version ]; then
|
|
66
|
+
INSTALL_CMD="sudo apt update && sudo apt install -y build-essential"
|
|
67
|
+
PLATFORM="Debian/Ubuntu"
|
|
68
|
+
elif [ -f /etc/fedora-release ] || [ -f /etc/redhat-release ]; then
|
|
69
|
+
INSTALL_CMD="sudo dnf groupinstall -y 'Development Tools'"
|
|
70
|
+
PLATFORM="Fedora/RHEL"
|
|
71
|
+
elif [ "$(uname)" = "Darwin" ]; then
|
|
72
|
+
INSTALL_CMD="xcode-select --install"
|
|
73
|
+
PLATFORM="macOS"
|
|
74
|
+
else
|
|
75
|
+
# Unknown platform - show manual instructions and exit
|
|
76
|
+
echo -e "${RED}[bluera-knowledge] Could not detect platform for auto-install${NC}"
|
|
77
|
+
echo -e "${YELLOW}Please install build tools manually:${NC}"
|
|
78
|
+
echo -e "${YELLOW} Debian/Ubuntu: sudo apt install build-essential${NC}"
|
|
79
|
+
echo -e "${YELLOW} Fedora/RHEL: sudo dnf groupinstall 'Development Tools'${NC}"
|
|
80
|
+
echo -e "${YELLOW} macOS: xcode-select --install${NC}"
|
|
81
|
+
exit 1
|
|
82
|
+
fi
|
|
83
|
+
|
|
84
|
+
echo -e "${YELLOW}Detected platform: ${PLATFORM}${NC}"
|
|
85
|
+
echo -e "${YELLOW}Install command: ${INSTALL_CMD}${NC}"
|
|
86
|
+
echo ""
|
|
87
|
+
|
|
88
|
+
# Non-interactive mode: cannot prompt for sudo, exit with instructions
|
|
89
|
+
if [[ -n "$NONINTERACTIVE" ]]; then
|
|
90
|
+
log_debug "Non-interactive mode, cannot prompt for build tools install"
|
|
91
|
+
echo -e "${RED}[bluera-knowledge] Build tools required but running non-interactively.${NC}"
|
|
92
|
+
echo -e "${YELLOW}Run manually: ${INSTALL_CMD}${NC}"
|
|
93
|
+
echo -e "${YELLOW}Then restart Claude Code.${NC}"
|
|
94
|
+
exit 1
|
|
95
|
+
fi
|
|
96
|
+
|
|
97
|
+
read -p "Install build tools now? [y/N] " -n 1 -r
|
|
98
|
+
echo ""
|
|
99
|
+
|
|
100
|
+
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
|
101
|
+
log_debug "User confirmed install, running: $INSTALL_CMD"
|
|
102
|
+
echo -e "${YELLOW}[bluera-knowledge] Installing build tools...${NC}"
|
|
103
|
+
if eval "$INSTALL_CMD"; then
|
|
104
|
+
log_debug "Build tools installed successfully"
|
|
105
|
+
echo -e "${GREEN}[bluera-knowledge] Build tools installed ✓${NC}"
|
|
106
|
+
else
|
|
107
|
+
log_debug "Build tools install failed"
|
|
108
|
+
echo -e "${RED}[bluera-knowledge] Build tools installation failed${NC}"
|
|
109
|
+
echo -e "${YELLOW}Please install manually and retry${NC}"
|
|
110
|
+
exit 1
|
|
111
|
+
fi
|
|
112
|
+
else
|
|
113
|
+
log_debug "User declined install"
|
|
114
|
+
echo -e "${YELLOW}[bluera-knowledge] Skipped. Please install build tools manually:${NC}"
|
|
115
|
+
echo -e "${YELLOW} ${INSTALL_CMD}${NC}"
|
|
116
|
+
exit 1
|
|
117
|
+
fi
|
|
118
|
+
fi
|
|
119
|
+
|
|
120
|
+
# =====================
|
|
121
|
+
# MCP Wrapper Script
|
|
122
|
+
# WORKAROUND: ${CLAUDE_PLUGIN_ROOT} not expanding in plugin .mcp.json
|
|
123
|
+
# Claude Code's ${CLAUDE_PLUGIN_ROOT} should work per docs, but doesn't in some environments.
|
|
124
|
+
# See: https://github.com/anthropics/claude-code/issues/9427
|
|
125
|
+
# See: https://code.claude.com/docs/en/mcp (Plugin MCP configuration)
|
|
126
|
+
# =====================
|
|
127
|
+
|
|
128
|
+
log_debug "Installing MCP wrapper script"
|
|
129
|
+
|
|
130
|
+
WRAPPER_DIR="$HOME/.local/bin"
|
|
131
|
+
WRAPPER_PATH="$WRAPPER_DIR/bluera-knowledge-mcp"
|
|
132
|
+
|
|
133
|
+
mkdir -p "$WRAPPER_DIR"
|
|
134
|
+
cp "$PLUGIN_ROOT/scripts/mcp-wrapper.sh" "$WRAPPER_PATH"
|
|
135
|
+
chmod +x "$WRAPPER_PATH"
|
|
136
|
+
|
|
137
|
+
# Check if ~/.local/bin is in PATH
|
|
138
|
+
if [[ ":$PATH:" != *":$WRAPPER_DIR:"* ]]; then
|
|
139
|
+
log_debug "Warning: $WRAPPER_DIR not in PATH"
|
|
140
|
+
echo -e "${YELLOW}[bluera-knowledge] Note: Add ~/.local/bin to your PATH if MCP server doesn't start${NC}"
|
|
141
|
+
echo -e "${YELLOW} export PATH=\"\$HOME/.local/bin:\$PATH\"${NC}"
|
|
142
|
+
fi
|
|
143
|
+
|
|
144
|
+
echo -e "${GREEN}[bluera-knowledge] MCP wrapper installed ✓${NC}"
|
|
145
|
+
|
|
146
|
+
# =====================
|
|
147
|
+
# Node.js Dependencies
|
|
148
|
+
# =====================
|
|
149
|
+
|
|
150
|
+
if [ -d "$PLUGIN_ROOT/node_modules" ]; then
|
|
151
|
+
log_debug "node_modules exists, skipping install"
|
|
152
|
+
echo -e "${GREEN}[bluera-knowledge] Node.js dependencies already installed ✓${NC}"
|
|
153
|
+
else
|
|
154
|
+
echo -e "${YELLOW}[bluera-knowledge] Installing Node.js dependencies...${NC}"
|
|
155
|
+
|
|
156
|
+
# Skip browser downloads during npm/bun install - browsers are installed separately below
|
|
157
|
+
export PUPPETEER_SKIP_DOWNLOAD=1
|
|
158
|
+
export PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1
|
|
159
|
+
|
|
160
|
+
if command -v bun &> /dev/null; then
|
|
161
|
+
log_debug "Starting bun install --frozen-lockfile"
|
|
162
|
+
(cd "$PLUGIN_ROOT" && bun install --frozen-lockfile) && \
|
|
163
|
+
{ log_debug "bun install complete"; echo -e "${GREEN}[bluera-knowledge] Node.js dependencies installed ✓${NC}"; } || \
|
|
164
|
+
{ log_debug "bun install FAILED"; echo -e "${RED}[bluera-knowledge] Failed to install Node.js dependencies${NC}"; exit 1; }
|
|
165
|
+
elif command -v npm &> /dev/null; then
|
|
166
|
+
# CRITICAL: --legacy-peer-deps required!
|
|
167
|
+
# tree-sitter-go@0.25 requires tree-sitter@^0.25
|
|
168
|
+
# tree-sitter-rust@0.24 requires tree-sitter@^0.22
|
|
169
|
+
# These peer deps conflict - no compatible version exists.
|
|
170
|
+
# Without --legacy-peer-deps, npm fails even though tree-sitter is optional.
|
|
171
|
+
log_debug "Starting npm install --legacy-peer-deps"
|
|
172
|
+
(cd "$PLUGIN_ROOT" && npm install --legacy-peer-deps) && \
|
|
173
|
+
{ log_debug "npm install complete"; echo -e "${GREEN}[bluera-knowledge] Node.js dependencies installed ✓${NC}"; } || \
|
|
174
|
+
{ log_debug "npm install FAILED"; echo -e "${RED}[bluera-knowledge] Failed to install Node.js dependencies${NC}"; exit 1; }
|
|
175
|
+
else
|
|
176
|
+
log_debug "Neither bun nor npm found"
|
|
177
|
+
echo -e "${RED}[bluera-knowledge] Neither bun nor npm found${NC}"
|
|
178
|
+
exit 1
|
|
179
|
+
fi
|
|
180
|
+
fi
|
|
181
|
+
|
|
182
|
+
# =====================
|
|
183
|
+
# Playwright Browser
|
|
184
|
+
# =====================
|
|
185
|
+
|
|
186
|
+
PLAYWRIGHT_BROWSERS_PATH="${PLAYWRIGHT_BROWSERS_PATH:-$HOME/.cache/ms-playwright}"
|
|
187
|
+
|
|
188
|
+
log_debug "Checking playwright at $PLAYWRIGHT_BROWSERS_PATH/chromium-*"
|
|
189
|
+
|
|
190
|
+
if ls "$PLAYWRIGHT_BROWSERS_PATH"/chromium-* 1>/dev/null 2>&1; then
|
|
191
|
+
log_debug "Playwright chromium exists, skipping install"
|
|
192
|
+
echo -e "${GREEN}[bluera-knowledge] Playwright Chromium already installed ✓${NC}"
|
|
193
|
+
else
|
|
194
|
+
echo -e "${YELLOW}[bluera-knowledge] Installing Playwright Chromium browser...${NC}"
|
|
195
|
+
|
|
196
|
+
log_debug "Starting npx playwright install chromium (this can take several minutes)"
|
|
197
|
+
if (cd "$PLUGIN_ROOT" && npx playwright install chromium); then
|
|
198
|
+
log_debug "Playwright install complete"
|
|
199
|
+
echo -e "${GREEN}[bluera-knowledge] Playwright Chromium installed ✓${NC}"
|
|
200
|
+
else
|
|
201
|
+
log_debug "Playwright install FAILED"
|
|
202
|
+
echo -e "${RED}[bluera-knowledge] Playwright browser install failed${NC}"
|
|
203
|
+
echo -e "${YELLOW}Manual fix: npx playwright install chromium${NC}"
|
|
204
|
+
exit 1
|
|
205
|
+
fi
|
|
206
|
+
fi
|
|
207
|
+
|
|
208
|
+
# =====================
|
|
209
|
+
# Python3 Check (Optional)
|
|
210
|
+
# =====================
|
|
211
|
+
|
|
212
|
+
log_debug "Checking python3"
|
|
213
|
+
|
|
214
|
+
if command -v python3 &> /dev/null; then
|
|
215
|
+
python_version=$(python3 -c 'import sys; print(".".join(map(str, sys.version_info[:2])))')
|
|
216
|
+
log_debug "Python $python_version found"
|
|
217
|
+
echo -e "${GREEN}[bluera-knowledge] Python ${python_version} available (for code-graph) ✓${NC}"
|
|
218
|
+
else
|
|
219
|
+
log_debug "Python3 not found"
|
|
220
|
+
echo -e "${YELLOW}[bluera-knowledge] Python3 not found - code-graph feature unavailable${NC}"
|
|
221
|
+
fi
|
|
222
|
+
|
|
223
|
+
log_debug "Setup complete"
|
|
224
|
+
echo -e "${GREEN}[bluera-knowledge] Setup complete ✓${NC}"
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# bluera-knowledge statusline module
|
|
3
|
+
# Injected into ~/.claude/statusline.sh between boundary comments:
|
|
4
|
+
# # --- bluera-knowledge ---
|
|
5
|
+
# # --- end bluera-knowledge ---
|
|
6
|
+
#
|
|
7
|
+
# Expected variables from parent script:
|
|
8
|
+
# PROJECT_DIR - project directory path (from statusline JSON input)
|
|
9
|
+
#
|
|
10
|
+
# Exports:
|
|
11
|
+
# BK_STATUS - formatted status string (e.g. " 📘●3")
|
|
12
|
+
|
|
13
|
+
get_bk_status() {
|
|
14
|
+
local project_dir="$1"
|
|
15
|
+
local store_count=""
|
|
16
|
+
local config_file="$project_dir/.bluera/bluera-knowledge/stores.config.json"
|
|
17
|
+
if [ -f "$config_file" ]; then
|
|
18
|
+
store_count=$(jq '.stores | length' "$config_file" 2>/dev/null)
|
|
19
|
+
[ "$store_count" = "0" ] && store_count=""
|
|
20
|
+
fi
|
|
21
|
+
|
|
22
|
+
if pgrep -f "bluera-knowledge.*bootstrap" >/dev/null 2>&1 || \
|
|
23
|
+
pgrep -f "bluera-knowledge-mcp" >/dev/null 2>&1; then
|
|
24
|
+
printf " 📘\033[32m●\033[0m%s" "$store_count"
|
|
25
|
+
else
|
|
26
|
+
printf " 📘\033[31m●\033[0m%s" "$store_count"
|
|
27
|
+
fi
|
|
28
|
+
}
|
|
29
|
+
BK_STATUS=$(get_bk_status "$PROJECT_DIR")
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* MCP Development Test Helper
|
|
4
|
+
*
|
|
5
|
+
* Spawns the MCP server and communicates with it directly via JSON-RPC over stdio.
|
|
6
|
+
* Used by test-plugin --dev to test MCP functionality without needing the plugin installed.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* ./scripts/test-mcp-dev.mjs call <tool-name> '<json-args>'
|
|
10
|
+
* ./scripts/test-mcp-dev.mjs call execute '{"command":"help"}'
|
|
11
|
+
* ./scripts/test-mcp-dev.mjs call search '{"query":"test"}'
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { spawn } from 'node:child_process';
|
|
15
|
+
import { createInterface } from 'node:readline';
|
|
16
|
+
import { dirname, join } from 'node:path';
|
|
17
|
+
import { fileURLToPath } from 'node:url';
|
|
18
|
+
|
|
19
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
20
|
+
const PROJECT_ROOT = join(__dirname, '..');
|
|
21
|
+
|
|
22
|
+
class MCPTestClient {
|
|
23
|
+
constructor() {
|
|
24
|
+
this.server = null;
|
|
25
|
+
this.messageId = 0;
|
|
26
|
+
this.pendingRequests = new Map();
|
|
27
|
+
this.initialized = false;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async start() {
|
|
31
|
+
return new Promise((resolve, reject) => {
|
|
32
|
+
const serverPath = join(PROJECT_ROOT, 'dist', 'mcp', 'server.js');
|
|
33
|
+
|
|
34
|
+
this.server = spawn('node', [serverPath], {
|
|
35
|
+
cwd: PROJECT_ROOT,
|
|
36
|
+
env: {
|
|
37
|
+
...process.env,
|
|
38
|
+
PROJECT_ROOT: PROJECT_ROOT,
|
|
39
|
+
DATA_DIR: '.bluera/bluera-knowledge/data',
|
|
40
|
+
CONFIG_PATH: '.bluera/bluera-knowledge/config.json',
|
|
41
|
+
},
|
|
42
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Handle stderr (logs)
|
|
46
|
+
this.server.stderr.on('data', (data) => {
|
|
47
|
+
// Suppress server logs in test output unless DEBUG
|
|
48
|
+
if (process.env.DEBUG) {
|
|
49
|
+
process.stderr.write(`[server] ${data}`);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// Handle stdout (JSON-RPC responses)
|
|
54
|
+
const rl = createInterface({ input: this.server.stdout });
|
|
55
|
+
rl.on('line', (line) => {
|
|
56
|
+
try {
|
|
57
|
+
const msg = JSON.parse(line);
|
|
58
|
+
if (msg.id !== undefined && this.pendingRequests.has(msg.id)) {
|
|
59
|
+
const { resolve, reject } = this.pendingRequests.get(msg.id);
|
|
60
|
+
this.pendingRequests.delete(msg.id);
|
|
61
|
+
if (msg.error) {
|
|
62
|
+
reject(new Error(msg.error.message || JSON.stringify(msg.error)));
|
|
63
|
+
} else {
|
|
64
|
+
resolve(msg.result);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
} catch {
|
|
68
|
+
// Not JSON or parse error - ignore
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
this.server.on('error', reject);
|
|
73
|
+
this.server.on('spawn', () => {
|
|
74
|
+
// Give server a moment to initialize
|
|
75
|
+
setTimeout(() => resolve(), 100);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async initialize() {
|
|
81
|
+
if (this.initialized) return;
|
|
82
|
+
|
|
83
|
+
// Send initialize request
|
|
84
|
+
const initResult = await this.sendRequest('initialize', {
|
|
85
|
+
protocolVersion: '2024-11-05',
|
|
86
|
+
capabilities: {},
|
|
87
|
+
clientInfo: { name: 'test-mcp-dev', version: '1.0.0' },
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// Send initialized notification
|
|
91
|
+
this.sendNotification('notifications/initialized', {});
|
|
92
|
+
|
|
93
|
+
this.initialized = true;
|
|
94
|
+
return initResult;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
sendRequest(method, params) {
|
|
98
|
+
return new Promise((resolve, reject) => {
|
|
99
|
+
const id = ++this.messageId;
|
|
100
|
+
const request = { jsonrpc: '2.0', id, method, params };
|
|
101
|
+
|
|
102
|
+
this.pendingRequests.set(id, { resolve, reject });
|
|
103
|
+
|
|
104
|
+
// Set timeout for request
|
|
105
|
+
setTimeout(() => {
|
|
106
|
+
if (this.pendingRequests.has(id)) {
|
|
107
|
+
this.pendingRequests.delete(id);
|
|
108
|
+
reject(new Error(`Request timeout: ${method}`));
|
|
109
|
+
}
|
|
110
|
+
}, 30000);
|
|
111
|
+
|
|
112
|
+
this.server.stdin.write(JSON.stringify(request) + '\n');
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
sendNotification(method, params) {
|
|
117
|
+
const notification = { jsonrpc: '2.0', method, params };
|
|
118
|
+
this.server.stdin.write(JSON.stringify(notification) + '\n');
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async callTool(name, args) {
|
|
122
|
+
if (!this.initialized) {
|
|
123
|
+
await this.initialize();
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const result = await this.sendRequest('tools/call', {
|
|
127
|
+
name,
|
|
128
|
+
arguments: args,
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
return result;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async listTools() {
|
|
135
|
+
if (!this.initialized) {
|
|
136
|
+
await this.initialize();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return this.sendRequest('tools/list', {});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
stop() {
|
|
143
|
+
if (this.server) {
|
|
144
|
+
this.server.kill('SIGTERM');
|
|
145
|
+
this.server = null;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Session mode - reads multiple commands from stdin, maintains server across calls
|
|
151
|
+
async function sessionMode() {
|
|
152
|
+
const client = new MCPTestClient();
|
|
153
|
+
await client.start();
|
|
154
|
+
|
|
155
|
+
const rl = createInterface({ input: process.stdin });
|
|
156
|
+
const results = [];
|
|
157
|
+
let lastResultId = null;
|
|
158
|
+
|
|
159
|
+
for await (const line of rl) {
|
|
160
|
+
const trimmed = line.trim();
|
|
161
|
+
if (!trimmed || trimmed.startsWith('#')) continue;
|
|
162
|
+
|
|
163
|
+
const spaceIndex = trimmed.indexOf(' ');
|
|
164
|
+
const toolName = spaceIndex > 0 ? trimmed.slice(0, spaceIndex) : trimmed;
|
|
165
|
+
let argsStr = spaceIndex > 0 ? trimmed.slice(spaceIndex + 1) : '{}';
|
|
166
|
+
|
|
167
|
+
// Substitute $LAST_ID with actual result ID from previous search
|
|
168
|
+
if (lastResultId && argsStr.includes('$LAST_ID')) {
|
|
169
|
+
argsStr = argsStr.replace(/\$LAST_ID/g, lastResultId);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const args = JSON.parse(argsStr);
|
|
173
|
+
const result = await client.callTool(toolName, args);
|
|
174
|
+
results.push(result);
|
|
175
|
+
|
|
176
|
+
// Extract result ID for next call (search results have results[0].id)
|
|
177
|
+
// Search results have a header line before JSON, so find first '{'
|
|
178
|
+
if (result?.content?.[0]?.text) {
|
|
179
|
+
const text = result.content[0].text;
|
|
180
|
+
const jsonStart = text.indexOf('{');
|
|
181
|
+
if (jsonStart >= 0) {
|
|
182
|
+
try {
|
|
183
|
+
const parsed = JSON.parse(text.slice(jsonStart));
|
|
184
|
+
if (parsed.results?.[0]?.id) {
|
|
185
|
+
lastResultId = parsed.results[0].id;
|
|
186
|
+
}
|
|
187
|
+
} catch {
|
|
188
|
+
// Not valid JSON or no results - ignore
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
client.stop();
|
|
195
|
+
console.log(JSON.stringify(results, null, 2));
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// CLI interface
|
|
199
|
+
async function main() {
|
|
200
|
+
const args = process.argv.slice(2);
|
|
201
|
+
|
|
202
|
+
if (args.length < 1) {
|
|
203
|
+
console.error('Usage: test-mcp-dev.js <command> [args...]');
|
|
204
|
+
console.error('Commands:');
|
|
205
|
+
console.error(' call <tool-name> <json-args> - Call an MCP tool (one-shot)');
|
|
206
|
+
console.error(' session - Read commands from stdin (persistent server)');
|
|
207
|
+
console.error(' list - List available tools');
|
|
208
|
+
console.error('');
|
|
209
|
+
console.error('Examples:');
|
|
210
|
+
console.error(' ./scripts/test-mcp-dev.js call execute \'{"command":"help"}\'');
|
|
211
|
+
console.error(' ./scripts/test-mcp-dev.js call search \'{"query":"test"}\'');
|
|
212
|
+
console.error(' ./scripts/test-mcp-dev.js list');
|
|
213
|
+
console.error('');
|
|
214
|
+
console.error('Session mode (maintains cache across calls):');
|
|
215
|
+
console.error(' echo -e \'search {"query":"test"}\\nget_full_context {"resultId":"$LAST_ID"}\' | ./scripts/test-mcp-dev.js session');
|
|
216
|
+
process.exit(1);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const command = args[0];
|
|
220
|
+
|
|
221
|
+
// Session mode handles its own client lifecycle
|
|
222
|
+
if (command === 'session') {
|
|
223
|
+
await sessionMode();
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const client = new MCPTestClient();
|
|
228
|
+
|
|
229
|
+
try {
|
|
230
|
+
await client.start();
|
|
231
|
+
|
|
232
|
+
if (command === 'call') {
|
|
233
|
+
if (args.length < 3) {
|
|
234
|
+
console.error('Usage: test-mcp-dev.js call <tool-name> <json-args>');
|
|
235
|
+
process.exit(1);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const toolName = args[1];
|
|
239
|
+
const toolArgs = JSON.parse(args[2]);
|
|
240
|
+
|
|
241
|
+
const result = await client.callTool(toolName, toolArgs);
|
|
242
|
+
|
|
243
|
+
// Output result as JSON for parsing by test-plugin
|
|
244
|
+
console.log(JSON.stringify(result, null, 2));
|
|
245
|
+
} else if (command === 'list') {
|
|
246
|
+
const result = await client.listTools();
|
|
247
|
+
console.log(JSON.stringify(result, null, 2));
|
|
248
|
+
} else {
|
|
249
|
+
console.error(`Unknown command: ${command}`);
|
|
250
|
+
process.exit(1);
|
|
251
|
+
}
|
|
252
|
+
} catch (error) {
|
|
253
|
+
console.error(`Error: ${error.message}`);
|
|
254
|
+
process.exit(1);
|
|
255
|
+
} finally {
|
|
256
|
+
client.stop();
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
main();
|