Automatic port allocation and ngrok tunnel management for AI agents via MCP protocol.
- Features
- Prerequisites
- Installation
- Quick Start
- Configuration
- Usage
- Training AI Agents
- Architecture
- Security
- Troubleshooting
- Development
- Roadmap
- License
- Automatic Port Allocation: Assigns ports from configurable range (default: 3000-9000)
- ngrok Tunnel Creation: Creates public HTTPS tunnels automatically
- MCP Protocol Support: Four tools for AI agent integration
- CLI Interface: Manual port management via command line
- Terminal UI (TUI): Interactive interface built with Go and bubbletea
- Persistent Registry: JSON-based storage at
~/.yardmaster/registry.json - Security Features: HTTP Basic Auth and IP restrictions
- Custom Domains: Support for paid ngrok reserved domains
- Thread Safe: File locking prevents concurrent access corruption
- Node.js 18 or later
- ngrok account (free tier available)
- Go 1.21 or later (optional, only for TUI)
git clone https://github.com/croakingtoad/yardmaster.git
cd yardmaster
npm install
npm run build
npm link # Optional: makes 'yardmaster' command globally availableSet your ngrok auth token:
export NGROK_AUTH_TOKEN="your_token_here"Get your token from: https://dashboard.ngrok.com/get-started/your-authtoken
Verify installation:
yardmaster statusyardmaster register myapp
# Output: Port 3000 registered for 'myapp'
# ngrok URL: https://abc123.ngrok.app
# Your app is now accessible at https://abc123.ngrok.appyardmaster release myappyardmaster list# Required
export NGROK_AUTH_TOKEN="your_token_here"
# Optional - Security
export NGROK_BASIC_AUTH="username:password"
export NGROK_IP_ALLOW="1.2.3.4/32,10.0.0.0/8"
export NGROK_IP_DENY="192.168.1.0/24"
# Optional - Custom domain (requires paid ngrok account)
export NGROK_DOMAIN="your-subdomain.ngrok.dev"
# Optional - Port range
export PORT_RANGE_START=4000
export PORT_RANGE_END=5000Create ~/.yardmaster/config.json:
{
"port_range": {
"start": 3000,
"end": 9000
},
"ngrok": {
"auth_token": "your_token_here",
"region": "us"
}
}Available ngrok regions: us, eu, ap, au, sa, jp, in
Environment variables override config file settings.
# Register a port (auto-assign from range)
yardmaster register <app_name>
# Register specific port
yardmaster register <app_name> <port>
# List all active ports
yardmaster list
# Release a port
yardmaster release <app_name>
# Show configuration and status
yardmaster status
yardmaster configFor AI agents like Claude, add to your MCP config (e.g., claude_desktop_config.json):
{
"mcpServers": {
"yardmaster": {
"command": "node",
"args": ["/absolute/path/to/yardmaster/dist/index.js"],
"env": {
"NGROK_AUTH_TOKEN": "your_token_here"
}
}
}
}Requirements:
- Use absolute paths, not relative
- Include auth token in
envor set globally
register_port
- Input:
app_name(required),desired_port(optional) - Output:
{ port, ngrok_url, success, message } - Creates tunnel and registers port
release_port
- Input:
app_name(required) - Output:
{ port, success, message } - Closes tunnel and releases port
query_ports
- Input:
filter(optional) - Output:
{ total, registrations[] } - Lists active port registrations
get_available_port
- Input:
range_start(optional),range_end(optional) - Output:
{ port, success, message } - Returns next available port in range
Build and run the interactive terminal interface:
cd tui
go build -ldflags="-s -w" -o yardmaster-tui
./yardmaster-tuiFeatures:
- Event-driven updates (no polling)
- Pagination (20 ports per page)
- Security status display
- Vim-style navigation (j/k/h/l)
- Thread-safe file locking
Keyboard Shortcuts:
↑/↓orj/k- Navigate←/→orh/l- Change pageEnter- View detailsEsc- Go backr- Refreshq- Quit
See tui/README.md for complete documentation.
To ensure AI agents use Yardmaster instead of hardcoded ports, add instructions to your project or global configuration.
Add this to ~/.claude/CLAUDE.md to enable Yardmaster coordination for all projects:
## 🚂 Yardmaster Port Management - CRITICAL RULES
**BEFORE starting ANY server, API, or service, you MUST use Yardmaster for port allocation.**
### Mandatory Workflow
1. **Check** if yardmaster MCP is available (it should be)
2. **Query** existing ports: `mcp.callTool('yardmaster', 'query_ports')`
3. **Register** a port BEFORE starting server: `mcp.callTool('yardmaster', 'register_port', {app_name: 'project-name-component'})`
4. **Use** the returned port number in your server config
5. **Share** the returned ngrok URL with user (public access)
6. **Release** when done: `mcp.callTool('yardmaster', 'release_port', {app_name: 'project-name-component'})`
### Forbidden Actions
❌ **NEVER** hardcode ports (3000, 8080, 5173, 5000, 8000, etc.)
❌ **NEVER** start a server without registering with yardmaster first
❌ **NEVER** use `PORT=3000` or similar hardcoded environment variables
❌ **NEVER** skip yardmaster even if "just testing" or "quick prototype"
### Why This Matters
- Prevents port conflicts across projects
- Provides instant public URLs via ngrok
- Enables port coordination across multiple agents
- Tracks what's running where
- Automatic cleanup and port recycling
### Examples
#### Starting a Vite dev server:
\`\`\`javascript
// 1. Register port first
const result = await mcp.callTool('yardmaster', 'register_port', {
app_name: 'myproject-frontend'
});
// Returns: { port: 3024, ngrok_url: "https://abc123.ngrok.app" }
// 2. Configure vite with assigned port
// vite.config.ts:
export default defineConfig({
server: { port: 3024 } // Use result.port
})
// 3. Tell user the public URL
console.log("Frontend running at:", result.ngrok_url);
\`\`\`
#### Starting a Node/Express API:
\`\`\`javascript
const result = await mcp.callTool('yardmaster', 'register_port', {
app_name: 'myproject-api'
});
app.listen(result.port, () => {
console.log(\`API: \${result.ngrok_url}\`);
});
\`\`\`
#### Starting multiple services:
\`\`\`javascript
// Register each service with descriptive names
const frontend = await mcp.callTool('yardmaster', 'register_port', {app_name: 'myapp-web'});
const api = await mcp.callTool('yardmaster', 'register_port', {app_name: 'myapp-api'});
const db = await mcp.callTool('yardmaster', 'register_port', {app_name: 'myapp-postgres'});
// Use assigned ports for each service
// frontend.port, api.port, db.port
\`\`\`
### Naming Convention
Use descriptive app names: `{project}-{component}`
- ✅ `ecommerce-frontend`, `ecommerce-api`, `ecommerce-admin`
- ✅ `blog-web`, `blog-cms`, `blog-search`
- ❌ `app`, `test`, `server` (too generic)
### Error Handling
\`\`\`javascript
const result = await mcp.callTool('yardmaster', 'register_port', {
app_name: 'myapp-web'
});
if (!result.success) {
// Handle port unavailable - try a different name or check what's using ports
const ports = await mcp.callTool('yardmaster', 'query_ports');
console.log("Existing ports:", ports);
}
\`\`\`
### Cleanup
Always release ports when done:
\`\`\`bash
# At end of session or when stopping server
await mcp.callTool('yardmaster', 'release_port', {app_name: 'myapp-web'});
\`\`\`
**This is not optional. Always use Yardmaster for port management.**For project-specific instructions, create /path/to/your/project/CLAUDE.md:
## Port Management
This project uses Yardmaster for port coordination.
**Services:**
- Frontend: Register as `projectname-web`
- API: Register as `projectname-api`
- Database: Register as `projectname-db`
See global CLAUDE.md for full Yardmaster workflow.Starting a React development server:
// 1. Register port
const result = await mcp.callTool('yardmaster', 'register_port', {
app_name: 'frontend'
});
// Returns: { port: 3000, ngrok_url: "https://abc123.ngrok.app" }
// 2. Configure Vite with assigned port
// vite.config.ts:
export default defineConfig({
server: { port: 3000 } // Use result.port
})
// 3. Start server and share ngrok URL
npm run dev
// Tell user: "App running at https://abc123.ngrok.app"If desired port is unavailable:
// Attempt specific port
const result = await mcp.callTool('yardmaster', 'register_port', {
app_name: 'frontend',
desired_port: 3000
});
if (!result.success) {
// Auto-assign instead
const fallback = await mcp.callTool('yardmaster', 'register_port', {
app_name: 'frontend'
});
// Use fallback.port
}┌─────────────────────────┐
│ AI Agent (Claude) │
└───────────┬─────────────┘
│ MCP Protocol
┌───────────▼─────────────┐
│ Yardmaster MCP Server │
│ - register_port │
│ - release_port │
│ - query_ports │
│ - get_available_port │
└───────────┬─────────────┘
│
┌────────┴────────┐
│ │
┌──▼──────┐ ┌──────▼──────┐
│Registry │ │NgrokManager │
│(JSON) │ │(SDK) │
└─────────┘ └──────┬──────┘
│
┌──────▼──────┐
│ ngrok Cloud │
│ Public URLs │
└─────────────┘
Components:
- PortRegistry: Manages port allocations with JSON persistence and file locking
- NgrokManager: Wraps @ngrok/ngrok SDK for tunnel lifecycle
- MCP Server: Exposes tools for AI agent integration
- CLI: Command-line interface for manual operations
- TUI: Interactive terminal interface (Go/bubbletea)
HTTP Basic Auth protects tunnel endpoints:
export NGROK_BASIC_AUTH="username:password"
yardmaster register secure-appAll tunnels created after setting NGROK_BASIC_AUTH require username/password authentication.
Limit tunnel access by IP address using CIDR notation:
# Allow only specific IPs
export NGROK_IP_ALLOW="1.2.3.4/32,10.0.0.0/8"
# Deny specific IP ranges
export NGROK_IP_DENY="192.168.1.0/24"
yardmaster register restricted-appNote: IP restrictions require a paid ngrok account. Free accounts receive error ERR_NGROK_9017.
Security settings are stored in registry for each port:
{
"security": {
"basic_auth": true,
"ip_restrictions": false,
"custom_domain": true
}
}The TUI displays actual security status per port.
- Never commit
NGROK_AUTH_TOKENto version control - Use environment variables or user config files (
~/.yardmaster/config.json) - User config files are stored in home directory, outside repository
- Public ngrok tunnels without authentication are accessible by anyone
- Always use authentication or IP restrictions for production services
Cause: Auth token not set or invalid
Solution:
- Verify token:
echo $NGROK_AUTH_TOKEN - Validate at https://dashboard.ngrok.com/get-started/your-authtoken
- Check for typos (tokens are 48+ characters)
- Set in
~/.yardmaster/config.jsonas alternative
Cause: All ports in range (3000-9000 by default) are occupied
Solution:
- List active:
yardmaster list - Release unused:
yardmaster release <app_name> - Expand range: Set
PORT_RANGE_STARTandPORT_RANGE_END - Check system processes:
lsof -i :3000-9000
Cause: Port is registered to another application
Solution:
- Check registration:
yardmaster list - Release existing:
yardmaster release <app_name> - Use auto-assignment (omit port number)
Cause: Path error, missing token, or server crash
Solution:
- Use absolute path in MCP config (not relative)
- Verify
NGROK_AUTH_TOKENinenvsection or global environment - Test manually:
node /absolute/path/to/yardmaster/dist/index.js - Check MCP logs (location varies by client)
- Restart AI client
NGROK_BASIC_AUTH format:
- Must be
username:password(colon-separated) - Example:
NGROK_BASIC_AUTH="admin:secure123" - Both username and password required
NGROK_IP_ALLOW/DENY format:
- CIDR notation:
IP/prefix - Single IP:
1.2.3.4/32 - Multiple:
1.2.3.4/32,10.0.0.0/8(comma-separated) - IPv6 supported:
2001:db8::/32 - Prefix range: 0-32 (IPv4), 0-128 (IPv6)
npm install
npm run buildnpm run build- Compile TypeScriptnpm run dev- Watch modenpm test- Run test suitenpm run lint- ESLintnpm run type-check- TypeScript validation
TypeScript Tests (21 tests):
- Input validation (basic auth, CIDR notation)
- IPv4 and IPv6 validation
- Edge case handling
Go Tests (4 tests):
- Registry file parsing
- JSON error handling
- Port filtering
Run tests:
# TypeScript
npm test
# Go
cd tui && go test ./...cd tui
go build -ldflags="-s -w" -o yardmaster-tuiThe -ldflags="-s -w" flags strip debug symbols, reducing binary size by 30%.
yardmaster/
├── src/ # TypeScript source
│ ├── registry.ts # Port allocation
│ ├── ngrok-manager.ts # Tunnel management
│ ├── validation.ts # Input validation
│ └── index.ts # MCP server
├── tui/ # Go TUI application
│ ├── internal/
│ │ ├── models/ # State management
│ │ ├── registry/ # Registry parser
│ │ └── ui/ # View renderers
│ └── main.go
├── config/ # Default configuration
└── dist/ # Compiled JavaScript
- Custom ngrok domains and subdomains
- Authentication (Basic Auth, IP restrictions)
- Terminal UI with bubbletea
- File locking for concurrency safety
- Event-driven file watching (fsnotify)
- Input validation with error messages
- Test suite (25 tests total)
- Pagination for large port lists
- Web Dashboard (browser-based UI)
- Multi-Machine Aggregation (shared registry across machines)
- Analytics (port usage metrics and history)
- Docker Integration (auto-discover containerized apps)
- Webhooks (notifications on port events)
- Database Backend (SQLite/Postgres instead of JSON)
- Team Features (multi-user/organization support)
- Registry capacity: Tested with 1000+ ports
- Pagination: 20 ports per page in TUI
- File watching: Event-driven updates (zero polling overhead)
- Binary size: 3.3MB (TUI), stripped
- Startup time: <100ms for CLI operations
- Concurrency: Thread-safe with file locking (Node.js and Go)
MIT
Issues and pull requests are welcome. Please include tests for new features.
For bug reports, include:
- Operating system and version
- Node.js version
- Error messages (if any)
- Steps to reproduce
Repository: https://github.com/croakingtoad/yardmaster
Built by LOCOMOTIVE