croakingtoad/yardmaster
Port registry for AI agents in development environments with ngrok integration, CLI, and MCP server.
Yardmaster
Automatic port allocation and ngrok tunnel management for AI agents via MCP protocol.
Table of Contents
- Features
- Prerequisites
- Installation
- Quick Start
- Configuration
- Usage
- Training AI Agents
- Architecture
- Security
- Troubleshooting
- Development
- Roadmap
- License
Features
- 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
Prerequisites
- Node.js 18 or later
- ngrok account (free tier available)
- Go 1.21 or later (optional, only for TUI)
Installation
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 statusQuick Start
Register and use a port:
yardmaster register myapp
# Output: Port 3000 registered for 'myapp'
# ngrok URL: https://abc123.ngrok.app
# Your app is now accessible at https://abc123.ngrok.appRelease when done:
yardmaster release myappView all registrations:
yardmaster listConfiguration
Environment Variables
# 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=5000User Config File
Create ~/.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.
Usage
CLI Commands
# 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 configMCP Server
For 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
Available MCP Tools
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
Terminal UI (TUI)
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.
Training AI Agents
To ensure AI agents use Yardmaster instead of hardcoded ports, add instructions to your project or global configuration.
Global Configuration (Recommended)
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.**Project-Level Configuration
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.Example 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"Handling Port Conflicts
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
}Architecture
┌─────────────────────────┐
│ 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)
Security
Authentication
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.
IP Restrictions
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 Metadata
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.
Important Security Notes
- 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
Troubleshooting
"Failed to initialize ngrok"
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
"No available ports in range"
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
"Port already in use"
Cause: Port is registered to another application
Solution:
- Check registration:
yardmaster list - Release existing:
yardmaster release <app_name> - Use auto-assignment (omit port number)
MCP Server Not Responding
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
Invalid Input Errors
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)
Development
Setup
npm install
npm run buildScripts
npm run build- Compile TypeScriptnpm run dev- Watch modenpm test- Run test suitenpm run lint- ESLintnpm run type-check- TypeScript validation
Testing
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 ./...Build TUI
cd tui
go build -ldflags="-s -w" -o yardmaster-tuiThe -ldflags="-s -w" flags strip debug symbols, reducing binary size by 30%.
Project Structure
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
Roadmap
Completed Features
- 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
Planned Features
- 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)
Performance
- 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)
License
MIT
Contributing
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