This demo showcases the complete end-to-end workflow of generating a Nomad Pack from an MCP server in the official registry and deploying it to a live Nomad cluster.
The integration demo demonstrates:
- Prerequisites Check: Validating environment and connectivity
- Pack Generation: Creating Nomad Pack from MCP Registry server definitions
- Pack Inspection: Examining generated templates and configuration
- Deployment Planning: Using nomad-pack to preview deployment with static ports
- Live Deployment: Deploying the MCP server to Nomad cluster with Traefik routing
- Job Verification: Checking job status and health
- Allocation Inspection: Examining allocation details and port configuration
- Log Analysis: Inspecting server logs for troubleshooting
- Claude Code Integration: Automated token generation and MCP server configuration
- Cleanup: Gracefully stopping jobs and removing pack artifacts and authentication files
The demo uses com.falkordb/QueryWeaver as the example MCP server:
- Package Type: OCI (Docker container)
- Transport Type: streamable-http (HTTP-based communication)
- Image:
docker.io/falkordb/queryweaver:0.0.11 - Description: FalkorDB-based MCP server providing graph database capabilities
- Port Configuration: Static host port 8091 (for reliable Traefik routing)
This server was chosen because it:
- Demonstrates Docker-based deployment with OCI images
- Showcases HTTP transport with external load balancer integration
- Is actively maintained and reliable
- Provides a realistic use case (graph database querying)
- Requires authentication (demonstrates token generation workflow)
The demo uses static port allocation (host_port=8091) instead of dynamic ports for these reasons:
- Simplified Load Balancer Integration: Traefik can connect directly to port 8091 without requiring Consul service discovery
- Predictable Access: The MCP server is always accessible at the same port across deployments
- Reliable Routing: Eliminates timeout issues that can occur with dynamic port resolution
While Nomad's dynamic port allocation is ideal for most workloads, static ports are simpler for external access scenarios where the load balancer doesn't have Consul integration configured.
export NOMAD_ADDR="http://your-nomad-cluster:4646"
export NOMAD_TOKEN="your-nomad-acl-token"-
nomad-mcp-pack - Build from this repository:
make build
-
nomad-pack - Install from HashiCorp:
# macOS brew install hashicorp/tap/nomad-pack # Linux/Other # See: https://github.com/hashicorp/nomad-pack#installation
-
nomad - Install Nomad CLI:
# macOS brew install nomad # Linux/Other # See: https://www.nomadproject.io/downloads
-
jq - JSON processor for automated token generation:
# macOS brew install jq # Linux (Debian/Ubuntu) apt-get install jq # Linux (RHEL/CentOS) yum install jq
- Nomad cluster must be accessible via NOMAD_ADDR
- Your NOMAD_TOKEN must have permissions to:
- Submit jobs
- Read job status
- Read allocation information
- Stop jobs
- At least one Nomad client node with:
- Docker driver enabled
- Sufficient resources (500 MHz CPU, 512 MiB memory)
- Internet access to pull Docker images
- Internet connectivity to:
- MCP Registry API (
registry.modelcontextprotocol.io) - Docker Hub (
docker.io) for image pulls
- MCP Registry API (
Run the demo with pauses between sections for presentation or learning:
cd demo/integration
./demo.shPress Enter at each pause to continue to the next section.
Run the demo without pauses for CI/CD or testing:
cd demo/integration
./demo.sh auto- Interactive Mode: ~5-10 minutes (depending on pause lengths)
- Automatic Mode: ~2-3 minutes
The demo provides color-coded output:
- 🔵 Blue: Section headers and important information
- 🟢 Green: Success messages and step descriptions
- 🟡 Yellow: Warnings and pause prompts
- 🔴 Red: Errors (if any)
- 🟣 Purple: Commands being executed
- 🔷 Cyan: Informational messages
- Prerequisites Check: Validates environment and connectivity
- Pack Generation: Creates Nomad Pack from MCP Registry
- Pack Inspection: Shows generated files and configuration
- Deployment Planning: Runs nomad-pack plan (dry-run) with static port configuration
- Deployment Execution: Deploys to Nomad cluster with static port 8091
- Job Verification: Checks job status
- Allocation Inspection: Examines allocation details and ports
- Log Inspection: Views MCP server startup logs
- Connecting to Claude Code: Automated token generation and MCP server configuration
- Cleanup: Stops job, removes pack artifacts and authentication files
- Summary: Recaps what was demonstrated
Job Status Example:
ID = com-falkordb-QueryWeaver-0-0-11-oci-streamable-http
Status = running
Type = service
Datacenters = dc1
Allocation Addresses Example:
Allocation Addresses:
Label Dynamic Address
*http yes 10.0.2.199:25102
Log Example:
INFO: Uvicorn running on http://0.0.0.0:5000 (Press CTRL+C to quit)
INFO: Application startup complete.
Section 8 - Token Generation Example:
✓ QueryWeaver backend is ready!
ℹ Creating QueryWeaver account...
ℹ Logging in...
ℹ Generating API token...
✓ Token generated successfully!
ℹ QUERYWEAVER_TOKEN=a1b2c3d4e5f6...
Run this command to add QueryWeaver to Claude Code:
$ claude mcp add --scope=user --transport=http \
--header="Authorization: Bearer a1b2c3d4e5f6..." \
queryweaver \
'http://hashistack-demo-dc1-7329a99ab7f6a3ba.elb.us-east-1.amazonaws.com/queryweaver/mcp'
Port Allocation with Static Port:
Allocation Addresses:
Label Dynamic Address
*http no 10.0.2.199:8091
Note: With static port allocation (host_port=8091), the Dynamic column shows "no" and the port is always 8091.
Symptom: nomad-pack plan fails with "No path to region"
Cause: The region specified doesn't match your Nomad cluster configuration
Solution:
# Check your cluster's region
nomad server members
# The demo auto-detects region, but you can override:
# Edit the NOMAD_REGION variable in demo.shSymptom: "NOMAD_ADDR environment variable is not set"
Solution:
export NOMAD_ADDR="http://your-nomad-cluster:4646"
export NOMAD_TOKEN="your-token"Symptom: Allocation fails with "Failed to pull image"
Possible Causes:
- Nomad client nodes can't reach Docker Hub
- Rate limiting from Docker Hub
- Network connectivity issues
Solution:
- Check Docker daemon is running on Nomad clients
- Verify internet connectivity from clients
- Consider using Docker Hub authentication for rate limits
Symptom: No port allocated or port conflicts
Cause: Network block not generated or ports exhausted
Solution:
- Verify pack template has network block for HTTP transport
- Check available ports on Nomad clients
- Review client configuration for port ranges
Symptom: Generation fails because pack directory exists
Solution: The demo cleans up automatically, but if needed:
rm -rf packs/com-falkordb-QueryWeaver-*You can modify the demo to pass custom variables:
# In demo.sh, change the nomad-pack run command:
nomad-pack run \
--var="region=$NOMAD_REGION" \
--var="cpu=1000" \
--var="memory=1024" \
--var="count=2" \
.The demo sets NOMAD_MCP_PACK_LOG_LEVEL=error for cleaner demonstration output. You can adjust this for debugging:
# More verbose output (shows info messages)
export NOMAD_MCP_PACK_LOG_LEVEL=info
./demo.sh
# Debug mode (shows all details including pack generation internals)
export NOMAD_MCP_PACK_LOG_LEVEL=debug
./demo.sh
# Warning level (only warnings and errors)
export NOMAD_MCP_PACK_LOG_LEVEL=warn
./demo.shAvailable log levels: debug, info, warn, error
Default in demo: error (cleanest output for presentations)
Default in nomad-mcp-pack: info (when not set)
Generated packs for HTTP-based MCP servers automatically include service registration with Consul. You can customize the service configuration:
# Custom service name
nomad-pack run --var="service_name=my-mcp-server" .
# Custom container port
nomad-pack run --var="container_port=5000" .
# Static host port (recommended for load balancer integration)
# Use host_port for static port allocation, or 0 for dynamic (default)
nomad-pack run \
--var="container_port=5000" \
--var="host_port=8091" \
.
# Add Traefik routing tags for external access
nomad-pack run \
--var='service_tags=["traefik.enable=true","traefik.http.middlewares.myserver-strip.stripprefix.prefixes=/myserver","traefik.http.routers.myserver.entrypoints=http","traefik.http.routers.myserver.rule=Host(`example.com`) && PathPrefix(`/myserver`)","traefik.http.routers.myserver.middlewares=myserver-strip"]' \
.Understanding Traefik Service Tags:
The demo configures Traefik for external access with these specific tags:
traefik.enable=true- Enables Traefik routing for this Consul servicetraefik.http.middlewares.queryweaver-strip.stripprefix.prefixes=/queryweaver- Creates a middleware that removes the/queryweaverprefix before forwarding requests to the backendtraefik.http.routers.queryweaver-mcp.entrypoints=http- Routes traffic through HTTP entrypoint (port 80)traefik.http.routers.queryweaver-mcp.rule=Host(...) && PathPrefix(...)- Matches requests for specific host and path prefixtraefik.http.routers.queryweaver-mcp.middlewares=queryweaver-strip- Applies the strip prefix middleware to this routertraefik.http.routers.queryweaver-mcp-secure.tls.certresolver=letsencrypt- Enables automatic HTTPS with Let's Encrypt
This configuration allows:
- External access:
http://your-load-balancer/queryweaver/mcp - Container sees:
/mcp(prefix/queryweaveris stripped by middleware) - Automatic HTTPS: Let's Encrypt handles TLS certificates
# Custom health check intervals
nomad-pack run \
--var="health_check_interval=60s" \
--var="health_check_timeout=10s" \
.Port Allocation Modes:
- Dynamic ports (default): Set
host_port=0or omit the variable. Nomad allocates a random available port. Requires load balancers to use Consul service discovery for port resolution. - Static ports: Set
host_portto a specific port number (e.g.,8091). The MCP server will always bind to that host port. Simpler for load balancer integration when service discovery is not configured.
See the generated pack's README for more details on service registration options.
To try different servers, modify DEMO_SERVER in demo.sh:
# Other OCI + HTTP servers found in the registry:
DEMO_SERVER="io.github.eat-pray-ai/yutu" # Transport: streamable-http
DEMO_SERVER="io.github.andrasfe/vulnicheck" # Transport: streamable-http
DEMO_SERVER="io.github.jgador/websharp" # Transport: streamable-http# Modify the nomad-pack commands:
nomad-pack plan --var="region=us-west" --var="datacenters=[\"dc2\",\"dc3\"]" .- Queries MCP Registry API for server metadata
- Resolves
@latestversions automatically - Validates server status (active vs deprecated)
- Extracts package and transport configuration
- Generates HCL templates compatible with nomad-pack
- Creates variables.hcl with configurable parameters
- Produces metadata.hcl with pack information
- Includes helper templates for reusable logic
- Automatically includes service registration for HTTP/SSE transport
- Infers sensible defaults for service names and container ports
- nomad-pack renders templates with variable substitution
- Validates job specification before submission
- Submits job to Nomad API with proper authentication
- Nomad pulls Docker images from registry
- Configures container with environment variables
- Maps dynamic ports for HTTP transport
- Monitors container health and logs
- Dynamic port allocation for HTTP-based servers
- Automatic container port mapping (configurable via variables)
- Port mapping visible in allocation addresses
- Automatic Consul service registration with TCP health checks
- Customizable service tags for load balancer integration (Traefik, Fabio, etc.)
- Configurable health check intervals and timeouts
- No network port required
- Process communication via stdin/stdout
- Simpler for local/embedded use cases
- Example: Most NPM-based MCP servers
- Requires network port allocation
- Client-server communication over HTTP
- Better for distributed deployments
- Example: This demo (QueryWeaver)
- Nomad allocates dynamic port automatically
- Port visible in allocation addresses
After successfully deploying QueryWeaver to your Nomad cluster, you can connect Claude Code (or other MCP clients) to use the server.
Section 9 of the demo automates the entire authentication workflow. The script:
- Waits for QueryWeaver backend (FalkorDB) to be fully ready (up to 90 seconds)
- Creates a QueryWeaver account via
/signup/emailAPI - Logs in and saves session cookie to
/tmp/queryweaver_cookies.txt - Generates an API token using the authenticated session
- Provides the complete
claude mcp addcommand with your token
Example Output from Demo:
✓ QueryWeaver backend is ready!
ℹ Creating QueryWeaver account...
ℹ Logging in...
ℹ Generating API token...
✓ Token generated successfully!
ℹ QUERYWEAVER_TOKEN=abc123xyz456...
Run this command to add QueryWeaver to Claude Code:
$ claude mcp add --scope=user --transport=http \
--header="Authorization: Bearer abc123xyz456..." \
queryweaver \
'http://your-load-balancer/queryweaver/mcp'Simply copy and run the claude mcp add command shown in the demo output.
The automated authentication uses cookie-based sessions to mirror browser-based authentication:
sequenceDiagram
Demo->>QueryWeaver: POST /signup/email
QueryWeaver-->>Demo: Account created
Demo->>QueryWeaver: POST /login/email
QueryWeaver-->>Demo: Session cookie
Note over Demo: Save cookie to /tmp/queryweaver_cookies.txt
Demo->>QueryWeaver: POST /tokens/generate (with cookie)
QueryWeaver-->>Demo: API token
Demo->>User: Display claude mcp add command
If the automated flow fails or you need to generate a token manually:
Step 1: Create Account
# Replace with your Traefik/load balancer URL
QUERYWEAVER_URL="http://your-nomad-cluster/queryweaver"
curl -X POST ${QUERYWEAVER_URL}/signup/email \
-H "Content-Type: application/json" \
-d '{
"firstName": "Demo",
"lastName": "User",
"email": "demo@example.com",
"password": "DemoPassword123!"
}'Step 2: Login and Generate Token
# Login to get session cookie
curl -X POST ${QUERYWEAVER_URL}/login/email \
-H "Content-Type: application/json" \
-c /tmp/queryweaver_cookies.txt \
-d '{
"email": "demo@example.com",
"password": "DemoPassword123!"
}'
# Generate API token using session
TOKEN_RESPONSE=$(curl -s -X POST ${QUERYWEAVER_URL}/tokens/generate \
-b /tmp/queryweaver_cookies.txt \
-H "Content-Type: application/json")
# Extract token using jq
QUERYWEAVER_TOKEN=$(echo "$TOKEN_RESPONSE" | jq -r '.token_id')
echo "Token: $QUERYWEAVER_TOKEN"Step 3: Add to Claude Code
Use the Claude Code CLI to add the MCP server:
claude mcp add --scope=user --transport=http \
--header="Authorization: Bearer $QUERYWEAVER_TOKEN" \
queryweaver \
'http://your-load-balancer/queryweaver/mcp'Once configured, ask Claude Code to use QueryWeaver's capabilities:
- list_databases - Retrieve available databases
- connect_database - Establish database connections
- database_schema - Fetch schema information
- query_database - Execute Text2SQL queries
Example: "How many databases do I have connected to QueryWeaver?"
Symptom: API calls return "Gateway Timeout" or 504 errors during token generation.
Cause: QueryWeaver backend (FalkorDB) takes 30-60 seconds to initialize after deployment.
Solution: The demo waits automatically. If generating tokens manually, wait 60 seconds after deployment before attempting authentication.
Symptom: "Failed to parse token response as JSON" or empty token.
Possible Causes:
- Backend not fully initialized (wait longer)
- Network connectivity issues
- Invalid credentials
Solution:
- Verify deployment is running:
nomad job status <job-name> - Test health endpoint:
curl http://your-load-balancer/queryweaver/docs - If
/docsreturns 200, retry token generation
Symptom: Claude Code shows "MCP server unavailable" or timeout errors.
Debugging Steps:
-
Verify deployment is running:
nomad job status com-falkordb-QueryWeaver-0-0-11-oci-streamable-http
-
Test MCP endpoint directly:
curl -i -H 'Accept: text/event-stream' \ -H "Authorization: Bearer $QUERYWEAVER_TOKEN" \ 'http://your-load-balancer/queryweaver/mcp'
You should see SSE headers and an MCP protocol response.
-
Check static port allocation:
nomad alloc status <alloc-id>
Verify port 8091 is allocated (not a dynamic port).
-
Verify Traefik routing:
curl -i http://your-load-balancer/queryweaver/docs
Should return 200 OK with QueryWeaver API documentation.
Important: QueryWeaver uses ephemeral storage (FalkorDB in-container). Tokens do not persist across deployments.
When you redeploy or restart the job, you must:
- Generate a new token (either via demo or manually)
- Update Claude Code configuration with the new token
- Restart your Claude Code session
Quick token refresh:
# Login with existing account
curl -X POST ${QUERYWEAVER_URL}/login/email \
-H "Content-Type: application/json" \
-c /tmp/queryweaver_cookies.txt \
-d '{"email":"demo@example.com","password":"DemoPassword123!"}'
# Generate new token
curl -X POST ${QUERYWEAVER_URL}/tokens/generate \
-b /tmp/queryweaver_cookies.txt | jq -r '.token_id'After running this demo, you can:
-
Connect and Use the MCP Server:
- Follow the steps above to configure Claude Code
- Test QueryWeaver's database capabilities
- Connect to FalkorDB instances for graph queries
-
Explore Other Demos:
../generate/- Pack generation features../watch/- Continuous monitoring and generation
-
Deploy Your Own MCP Servers:
- Browse the MCP Registry
- Generate packs for servers you need
- Customize variables for your environment
-
Production Deployment:
- Add resource constraints
- Configure health checks
- Set up service discovery
- Enable monitoring and alerting
- Implement backup/restore procedures
- Configure Traefik for external access
-
Contribute:
- Report issues on GitHub
- Submit pull requests
- Share your MCP server deployments
- Nomad Documentation
- Nomad Pack Documentation
- MCP Registry
- MCP Protocol Specification
- FalkorDB Documentation
This demo is part of the nomad-mcp-pack project and follows the same license.