Skip to content

Commit ad8a9b4

Browse files
authored
feat: MCP (Model Context Protocol) integration (#57)
* feat: MCP (Model Context Protocol) integration Add support for external MCP tool servers as typed sandbox modules. MCP tools appear as host:mcp-<name> modules — identical to native plugins. MCP framework: - Config parser with validation, env var substitution, tool filtering - Client manager with lazy connect, timeouts, reconnect (max 3) - Plugin adapter generating TypeScript declarations from tool schemas - Sanitisation: tool names, descriptions, prompt injection detection - Approval store with SHA-256 config hashing Gateway plugin (plugins/mcp/): - Gates the entire MCP subsystem via normal plugin audit/approve flow - Individual servers require separate approval via /mcp enable SDK tools for LLM discovery: - list_mcp_servers() — configured servers, state, tool counts - mcp_server_info(name) — detailed info + TypeScript declarations - manage_mcp(action, name) — connect/disconnect servers Slash commands: /mcp list|enable|disable|info|approve|revoke Validator improvements: - strip_js_comments() — comments stripped once, all checks use clean source - require(), Buffer, process, __dirname, __filename are now hard errors - Handler return check with brace-matched scope (not string search) - Comment-safe parsing prevents false positives from quotes in comments Other fixes: - Double-prompt eliminated for plugins with no config schema - Canary injection findings filtered from audit RATING section - MCP module author type fixed (system, not mcp) - Dynamic .d.ts uses export declare function (not bare export) Docs: docs/MCP.md with config reference, security model, GitHub example Scripts: just mcp-setup-everything|github|filesystem|show-config Tests: 33 new tests (config, sanitise, audit, type gen, validator integration) Signed-off-by: Simon Davies <simongdavies@users.noreply.github.com> * fix: add /mcp commands to help, tab-completion, and history The /mcp commands were implemented in slash-commands.ts but missing from the COMMANDS registry in commands.ts. This caused: - /help not showing MCP commands - Tab-completion not offering /mcp suggestions - /mcp commands silently dropped from readline history Add 6 MCP command entries to COMMANDS array and 'mcp' GROUP_ALIAS so /help mcp works. Signed-off-by: Simon Davies <simongdavies@users.noreply.github.com> * fix: address 8 PR review comments 1. approval.ts: mkdir ~/.hyperagent/ before writing (first-run case) 2. config.ts: hash includes allowTools, denyTools, env key names 3. approval.ts: isMCPApproved checks tool list matches approved tools 4. plugin-adapter.ts: quote property names that aren't valid JS identifiers 5. plugins/mcp/index.ts: fix comment to match implementation (sentinel module) 6. index.ts: module_info author fixed to 'system' (was 'mcp') 7. validator.rs: arrow handler param check distinguishes () => from (event) => 8. validator.rs: strip_js_comments guards against regex literal // sequences Signed-off-by: Simon Davies <simongdavies@users.noreply.github.com> --------- Signed-off-by: Simon Davies <simongdavies@users.noreply.github.com>
1 parent 1c79da4 commit ad8a9b4

File tree

23 files changed

+7221
-2446
lines changed

23 files changed

+7221
-2446
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ Cargo.lock
2626

2727
# User data — never commit (approvals, config, logs, cache)
2828
approved-plugins.json
29+
approved-mcp.json
2930
fetch-log.jsonl
3031
config.json
3132
*.jsonl

Justfile

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -569,3 +569,121 @@ k8s-smoke-test:
569569
echo "════════════════════════════════════════"
570570
echo ""
571571
[ "$FAIL" -eq 0 ]
572+
573+
# ── MCP Setup Recipes ───────────────────────────────────────────────
574+
#
575+
# Helper recipes to configure MCP servers for testing and examples.
576+
# These write to ~/.hyperagent/config.json (gitignored).
577+
578+
# Set up the MCP "everything" test server (reference/test server with echo, add, etc.)
579+
[unix]
580+
mcp-setup-everything:
581+
#!/usr/bin/env bash
582+
set -euo pipefail
583+
CONFIG_DIR="$HOME/.hyperagent"
584+
CONFIG_FILE="$CONFIG_DIR/config.json"
585+
mkdir -p "$CONFIG_DIR"
586+
587+
if [ -f "$CONFIG_FILE" ]; then
588+
# Merge into existing config using node
589+
node -e "
590+
const fs = require('fs');
591+
const cfg = JSON.parse(fs.readFileSync('$CONFIG_FILE', 'utf8'));
592+
cfg.mcpServers = cfg.mcpServers || {};
593+
cfg.mcpServers.everything = {
594+
command: 'npx',
595+
args: ['-y', '@modelcontextprotocol/server-everything']
596+
};
597+
fs.writeFileSync('$CONFIG_FILE', JSON.stringify(cfg, null, 2) + '\n');
598+
"
599+
else
600+
echo '{ "mcpServers": { "everything": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-everything"] } } }' \
601+
| node -e "process.stdout.write(JSON.stringify(JSON.parse(require('fs').readFileSync('/dev/stdin','utf8')),null,2)+'\n')" \
602+
> "$CONFIG_FILE"
603+
fi
604+
echo "✅ MCP 'everything' server configured in $CONFIG_FILE"
605+
echo " Start the agent and run: /plugin enable mcp && /mcp enable everything"
606+
607+
# Set up the MCP GitHub server (requires GITHUB_TOKEN env var)
608+
[unix]
609+
mcp-setup-github:
610+
#!/usr/bin/env bash
611+
set -euo pipefail
612+
CONFIG_DIR="$HOME/.hyperagent"
613+
CONFIG_FILE="$CONFIG_DIR/config.json"
614+
mkdir -p "$CONFIG_DIR"
615+
616+
if [ -z "${GITHUB_TOKEN:-}" ]; then
617+
echo "⚠️ GITHUB_TOKEN not set. The GitHub MCP server needs it at runtime."
618+
echo " export GITHUB_TOKEN=ghp_your_token_here"
619+
echo " Continuing with config anyway..."
620+
fi
621+
622+
node -e "
623+
const fs = require('fs');
624+
const path = '$CONFIG_FILE';
625+
const cfg = fs.existsSync(path) ? JSON.parse(fs.readFileSync(path, 'utf8')) : {};
626+
cfg.mcpServers = cfg.mcpServers || {};
627+
cfg.mcpServers.github = {
628+
command: 'npx',
629+
args: ['-y', '@modelcontextprotocol/server-github'],
630+
env: { GITHUB_PERSONAL_ACCESS_TOKEN: '\${GITHUB_TOKEN}' },
631+
allowTools: [
632+
'list_issues', 'get_issue', 'search_issues',
633+
'list_pull_requests', 'get_pull_request',
634+
'search_repositories', 'get_file_contents'
635+
],
636+
denyTools: ['merge_pull_request', 'delete_branch', 'push_files']
637+
};
638+
fs.writeFileSync(path, JSON.stringify(cfg, null, 2) + '\n');
639+
"
640+
echo "✅ MCP 'github' server configured in $CONFIG_FILE"
641+
echo " Requires: export GITHUB_TOKEN=ghp_..."
642+
echo " Start the agent and run: /plugin enable mcp && /mcp enable github"
643+
644+
# Set up the MCP filesystem server (read-only access to a directory)
645+
[unix]
646+
mcp-setup-filesystem dir="/tmp/mcp-fs":
647+
#!/usr/bin/env bash
648+
set -euo pipefail
649+
CONFIG_DIR="$HOME/.hyperagent"
650+
CONFIG_FILE="$CONFIG_DIR/config.json"
651+
DIR="{{ dir }}"
652+
mkdir -p "$CONFIG_DIR" "$DIR"
653+
654+
node -e "
655+
const fs = require('fs');
656+
const path = '$CONFIG_FILE';
657+
const cfg = fs.existsSync(path) ? JSON.parse(fs.readFileSync(path, 'utf8')) : {};
658+
cfg.mcpServers = cfg.mcpServers || {};
659+
cfg.mcpServers.filesystem = {
660+
command: 'npx',
661+
args: ['-y', '@modelcontextprotocol/server-filesystem', '$DIR']
662+
};
663+
fs.writeFileSync(path, JSON.stringify(cfg, null, 2) + '\n');
664+
"
665+
echo "✅ MCP 'filesystem' server configured in $CONFIG_FILE"
666+
echo " Root directory: $DIR"
667+
echo " Start the agent and run: /plugin enable mcp && /mcp enable filesystem"
668+
669+
# Show current MCP config (if any)
670+
[unix]
671+
mcp-show-config:
672+
#!/usr/bin/env bash
673+
CONFIG_FILE="$HOME/.hyperagent/config.json"
674+
if [ -f "$CONFIG_FILE" ]; then
675+
node -e "
676+
const cfg = JSON.parse(require('fs').readFileSync('$CONFIG_FILE', 'utf8'));
677+
if (cfg.mcpServers) {
678+
console.log('Configured MCP servers:');
679+
for (const [name, s] of Object.entries(cfg.mcpServers)) {
680+
console.log(' ' + name + ': ' + s.command + ' ' + (s.args || []).join(' '));
681+
}
682+
} else {
683+
console.log('No MCP servers configured.');
684+
}
685+
"
686+
else
687+
echo "No config file found at $CONFIG_FILE"
688+
echo "Run: just mcp-setup-everything"
689+
fi

0 commit comments

Comments
 (0)