mcp-tool-hub
MCP Tool Hub
A modular, extensible Model Context Protocol multi-server hub — built in TypeScript for IT automation teams.
Give your LLM access to real-world tools: files, Git, web content, and persistent memory. Deploy to any number of client machines simultaneously via Ansible.
Architecture
mcp-tool-hub/
├── packages/
│ ├── core/ ← Shared types + BaseMCPServer abstract class
│ ├── server-filesystem/ ← Sandboxed local file access (read/write/list/delete)
│ ├── server-git/ ← Git log, diff, file contents, branches, status
│ ├── server-fetch/ ← Web fetching: HTML, JSON APIs, URL health checks
│ └── server-memory/ ← Persistent JSON knowledge base (survives restarts)
├── host/ ← Orchestrator: registry + CLI + stdio JSON interface
├── ansible/ ← Playbook, inventory, systemd service templates
└── docs/ ← How to add new servers (with template)The architecture follows the Model Context Protocol pattern:
Each server is completely independent — its own package, its own build
The registry in the host maps
toolName → serverat runtimeThe CLI exposes a stdio JSON interface — any LLM integration sends
{"toolName":"...", "arguments":{...}}on stdin, reads the result from stdoutAdding a new server = create a package, extend
BaseMCPServer, register incli.ts
Quick Start
1. Install & Build
git clone https://github.com/your-org/mcp-tool-hub.git
cd mcp-tool-hub
npm install
npm run build2. Configure
cp .env.example .env
# Edit .env with your paths and settings3. Run
# Via npm
npm run start --workspace=host
# Or directly
node host/dist/cli.js4. Call a tool
Send JSON to stdin, get JSON from stdout:
echo '{"toolName":"read_file","arguments":{"path":"hello.txt"}}' | node host/dist/cli.jsAvailable Tools
📁 Filesystem Server
All operations sandboxed to MCP_FS_ROOT. Path traversal (../) is blocked.
Tool | Description |
| Read file contents (utf8 or base64) |
| Write or append to a file |
| List directory contents (optionally recursive) |
| Delete a file |
| Move or rename a file |
| Get size, dates, and type of a path |
🔀 Git Server
Read-only. No write operations.
Tool | Description |
| Commit history for a repo or file |
| File contents at a specific commit/branch |
| Diff between two refs |
| Working tree status |
| List branches (local + optional remote) |
| Full commit details and diff |
🌐 Fetch Server
Supports optional domain allowlist via MCP_FETCH_ALLOWED_DOMAINS.
Tool | Description |
| Fetch HTML or text from a URL |
| Fetch and parse a JSON API response |
| Check if a URL is reachable (HEAD request) |
🧠 Memory Server
Persistent across restarts. Backed by a JSON file.
Tool | Description |
| Store a value with key, namespace, and tags |
| Retrieve a value by key |
| Full-text search across all entries |
| Delete an entry |
| List all namespaces with counts |
| Delete all entries in a namespace |
Ansible Deployment
Deploy to all your client machines simultaneously:
cd ansible
# First time
ansible-playbook -i inventory.yml deploy-mcp-hub.yml
# Update only (rebuild + restart)
ansible-playbook -i inventory.yml deploy-mcp-hub.yml --tags update
# Deploy to specific group
ansible-playbook -i inventory.yml deploy-mcp-hub.yml --limit serversThe playbook:
Installs Node.js 20 (if not present)
Creates a dedicated
mcp-hubsystem userCopies and builds the project
Writes the
.envconfig from your Ansible variablesInstalls and starts a systemd service (auto-restart on failure)
Per-host variables in inventory.yml let you configure different allowed domains, log levels, and paths per machine group.
Adding a New Server
See docs/adding-a-new-server.template.ts for the full template with comments.
In short:
// 1. Create packages/server-myservice/src/my-server.ts
export class MyServer extends BaseMCPServer {
constructor(options: MyOptions) {
super(SERVER_INFO, options);
this.registerTool("my_tool", this.handleMyTool.bind(this));
}
private async handleMyTool(args) {
return this.ok({ result: "done" });
}
}
// 2. Register in host/src/cli.ts
hub.use(new MyServer({ apiKey: process.env.MY_API_KEY! }));Ideas: server-slack, server-postgres, server-docker, server-ansible, server-ssh, server-jira
Environment Variables
Variable | Default | Description |
|
| Root for all hub data |
|
| Filesystem sandbox root |
|
| Git repos base path |
|
| Memory store file |
| (empty = all) | Comma-separated domain allowlist |
|
|
|
Security Notes
Filesystem: Strictly sandboxed. Path traversal attacks return an error, not data.
Git: Read-only. No
commit,push, orcloneoperations exposed.Fetch: Optional domain allowlist prevents SSRF to internal services.
Systemd service: Runs as a non-root user with
PrivateTmp=trueandNoNewPrivileges=true.
Requirements
Node.js ≥ 18 (for native
fetchAPI)Git (for
server-git)Linux with systemd (for Ansible deployment)