jhipsterSampleApplication
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@jhipsterSampleApplicationvalidate my session and show my MCP tool scopes"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
jhipsterSampleApplication — JHipster + MCPHub tenant
A JHipster 9 application (Spring Boot 4 + Angular) that also acts as an MCPHub TENANT_DELEGATED tenant: MCPHub sends users to this app's login page, then exchanges and validates sessions through HMAC-signed API calls.
This guide covers four ways to run it:
Option | Use it for | Database | URL |
Day-to-day development | H2 in-memory | ||
Production-like run on your machine | PostgreSQL container | ||
Hosted deployment | Render PostgreSQL |
| |
Making a local app reachable from MCPHub | (whatever is running locally) |
|
Contents
Related MCP server: genpark-event-driven-webhook-signature-verifier-skill
Prerequisites
Tool | Version | Needed for |
any | Cloning | |
21 | Options 1 and the Jib image build | |
≥ 24.14 (optional) | Only if you run | |
Docker + Compose v2 | recent | Option 2 (and building the image yourself) |
recent | Option 4 | |
A Render account | — | Option 3 |
You do not need to install Maven: the repo ships the Maven wrapper (./mvnw, or .\mvnw.cmd on Windows PowerShell/cmd).
Windows: the examples use bash syntax. In Git Bash they work as written. In PowerShell, use
.\mvnw.cmdinstead of./mvnw,.\npmw.cmdinstead of./npmw, and$env:NAME = "value"instead ofexport NAME=value.
Project setup
git clone https://github.com/aircwou/jhipster-mcp-demo.git
cd jhipster-mcp-demo
# Optional: install client dependencies up front (Maven also does this on first build).
# CYPRESS_INSTALL_BINARY=0 skips the ~500 MB Cypress download if you don't run e2e tests.
CYPRESS_INSTALL_BINARY=0 ./npmw installThe first build downloads Maven dependencies, Node, and npm packages, so it takes a few minutes. Later builds are much faster.
Configuration
Spring profiles
Profile | Activated by | What it does |
| Default for | H2 in-memory DB, port 8085, debug logging. Also pulls in |
| Included by | Supplies the JWT secret and the MCPHub settings from |
|
| PostgreSQL, port 8080, all secrets must come from environment variables |
In dev, values in application-secret-samples.yml override the MCP_CONTRACT_* environment variables (the profile file wins over the ${…} placeholders in application.yml). To change them in dev, edit that file, or use Spring's own variable names, which take priority over YAML files: e.g. MCP_CONTRACT_ALLOWEDCALLBACKHOSTS (no underscores between words).
Environment variables (prod / Docker / Render)
Variable | Required | Description |
| Yes | Base64 of ≥ 64 random bytes; signs user JWTs (HS512). |
| Yes | Exactly 64 hex characters. Must equal the key configured for this tenant in MCPHub. The app refuses to start in prod if it is missing, malformed, or the sample placeholder. |
| Yes, for MCP login | Comma-separated host names MCPHub may redirect back to, e.g. |
| No | Served at |
| No | Session lifetime, default |
| No | Auth-code lifetime, default |
| Yes (prod) | JDBC URL, e.g. |
| Yes (prod) | Database credentials. |
| No | Port the Docker image listens on (default |
Generate secrets:
openssl rand -base64 64 | tr -d '\n' # JHIPSTER_SECURITY_AUTHENTICATION_JWT_BASE64_SECRET
openssl rand -hex 32 # MCP_CONTRACT_HMAC_KEY (only if you are creating the key; otherwise copy it from MCPHub)For Docker Compose, put these in a .env file. Start from the template; .env is git-ignored:
cp .env.example .env1. Run locally
Uses the dev profile: an in-memory H2 database (data, including MCP sessions, is lost on restart) and sample users loaded at startup.
Option A: single process (simplest)
Builds the Angular client once and serves everything from Spring Boot:
CYPRESS_INSTALL_BINARY=0 ./mvnw spring-boot:run -Pdev,webapp -ntpPowerShell:
$env:CYPRESS_INSTALL_BINARY = "0"; .\mvnw.cmd spring-boot:run "-Pdev,webapp" -ntpOpen http://localhost:8085 and sign in with admin / admin or user / user.
Option B: live reload for front-end work
Run the back end and the Angular dev server in two terminals:
./npmw run backend:start # Spring Boot on :8085 (skips the client build)
./npmw start # Angular dev server, proxies API calls to :8085Open http://localhost:9000. The browser refreshes when you change client files.
Option C: production profile on your machine
./npmw run docker:db:up # PostgreSQL in Docker on 127.0.0.1:5432
./mvnw -Pprod -DskipTests -ntp clean verify # builds target/*.jar
export SPRING_DATASOURCE_URL=jdbc:postgresql://localhost:5432/jhipsterSampleApplication
export SPRING_DATASOURCE_USERNAME=jhipsterSampleApplication
export JHIPSTER_SECURITY_AUTHENTICATION_JWT_BASE64_SECRET="$(openssl rand -base64 64 | tr -d '\n')"
export MCP_CONTRACT_HMAC_KEY=<64-hex-key>
export MCP_CONTRACT_ALLOWED_CALLBACK_HOSTS=lockmcp.ai,www.lockmcp.ai
java -jar target/*.jar # http://localhost:80802. Run with Docker Compose
src/main/docker/app.yml starts the app (prod profile) and PostgreSQL.
Step 1: create .env with at least the two required secrets (see Configuration):
cp .env.example .env
# edit .envStep 2: build the image (either way produces jhipstersampleapplication:latest):
# Option 1: Dockerfile. Needs only Docker.
docker build -t jhipstersampleapplication .
# Option 2: Jib. Needs JDK 21, no Dockerfile involved.
./npmw run java:docker # amd64
./npmw run java:docker:arm64 # Apple Silicon / arm64Step 3: start it. Pass --env-file .env. Compose does not pick up the repo-root .env automatically when the compose file is in another folder.
docker compose --env-file .env -f src/main/docker/app.yml up -d --waitOpen http://localhost:8080. --wait returns once the app's health check (/management/health) passes. First start takes about 30 to 60 seconds while Liquibase creates the schema.
docker compose --env-file .env -f src/main/docker/app.yml logs -f app # follow logs
docker compose --env-file .env -f src/main/docker/app.yml down # stop
docker compose --env-file .env -f src/main/docker/app.yml down -v # stop and delete the databaseNotes:
Ports are bound to
127.0.0.1only. Use Cloudflare Tunnel to expose the app.The PostgreSQL container uses
trustauth and has no volume by default. Fine locally, not for production (see the comments inpostgresql.yml).Only need the database (e.g. for local Option C)?
./npmw run docker:db:up/./npmw run docker:db:down.
3. Deploy to Render
Render has no native Java runtime, so the service is built from the repo's Dockerfile. It compiles the client and server inside Docker and listens on Render's PORT.
Step 1: create the database
Render Dashboard → New → Postgres.
Pick a name and a region. Use the same region for the web service so they can talk over the private network.
Create it, then open Connections and note Hostname (internal), Port, Database, Username, and Password.
Step 2: create the web service
Push this repo to GitHub/GitLab. Render deploys from your remote, not from your machine.
Render Dashboard → New → Web Service → connect the repository.
Settings:
Language / Runtime:
DockerBranch:
mainDockerfile Path:
./Dockerfile(Root Directory: leave empty)Region: same as the database
Instance type: at least 512 MB RAM. The free tier works for a demo but sleeps after inactivity (see Troubleshooting).
Environment Variables: add these. Use the internal hostname from Step 1:
Key
Value
SPRING_DATASOURCE_URLjdbc:postgresql://<internal-hostname>:5432/<database>SPRING_DATASOURCE_USERNAME<username>SPRING_DATASOURCE_PASSWORD<password>JHIPSTER_SECURITY_AUTHENTICATION_JWT_BASE64_SECREToutput of
openssl rand -base64 64 | tr -d '\n'MCP_CONTRACT_HMAC_KEYthe 64-hex key from MCPHub
MCP_CONTRACT_ALLOWED_CALLBACK_HOSTSe.g.
lockmcp.ai,www.lockmcp.aiMCP_CONTRACT_DOMAIN_VERIFICATION_TOKEN(optional) token from MCPHub
Render shows a postgresql://… URL. Spring needs the
jdbc:postgresql://form with the username and password in separate variables, so don't paste the Render URL as-is.Don't set
PORT. Render provides it and the image binds to it.Advanced → Health Check Path:
/management/healthClick Create Web Service. The first build takes several minutes (Maven + Angular production build). Watch the Logs tab until you see
Application 'jhipsterSampleApplication' is running!.
Step 3: after the first deploy
Your app is at
https://<service-name>.onrender.com. Optionally add a custom domain under Settings → Custom Domains.Change the
adminanduserpasswords immediately. The sample accounts are created in every profile.Register the public URL in MCPHub (see Registering the app in MCPHub).
Every push to
maintriggers a redeploy (Auto-Deploy is on by default).
4. Expose a local app with Cloudflare Tunnel
Use this when MCPHub (or anyone else) needs to reach an app running on your machine. The tunnel connects outbound to Cloudflare, so there are no open ports and no port forwarding.
Pick the local port that matches how you run the app:
How it runs | Local URL to expose |
Option 1A / 1B (dev) |
|
Option 1C or Docker Compose (prod) |
|
Install cloudflared
# Windows
winget install --id Cloudflare.cloudflared
# macOS
brew install cloudflared
# Debian / Ubuntu (amd64)
curl -L -o cloudflared.deb https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb
sudo dpkg -i cloudflared.deb
cloudflared --versionQuick tunnel (no account, random URL)
cloudflared tunnel --url http://localhost:8085cloudflared prints a URL like https://<random-words>.trycloudflare.com. It changes every time you restart, so it's good for a quick test and bad for anything you register in MCPHub.
Named tunnel on your own domain (stable URL)
Requires a domain whose DNS is managed by Cloudflare. The example uses jhipster-raw.example.com; replace it with yours.
# 1. Authenticate. Opens a browser; pick the zone (domain). Saves ~/.cloudflared/cert.pem
cloudflared tunnel login
# 2. Create the tunnel. Prints a tunnel UUID and writes ~/.cloudflared/<UUID>.json
cloudflared tunnel create jhipster-raw
# 3. Point a hostname at it (creates a proxied CNAME in Cloudflare DNS)
cloudflared tunnel route dns jhipster-raw jhipster-raw.example.comCreate
~/.cloudflared/config.yml(Windows:%USERPROFILE%\.cloudflared\config.yml):
tunnel: jhipster-raw
credentials-file: /home/<you>/.cloudflared/<UUID>.json # Windows: C:\Users\<you>\.cloudflared\<UUID>.json
ingress:
- hostname: jhipster-raw.example.com
service: http://localhost:8085
- service: http_status:404Validate and run:
cloudflared tunnel ingress validate
cloudflared tunnel run jhipster-rawThe app is now at https://jhipster-raw.example.com. Cloudflare terminates TLS; your app still serves plain HTTP locally. Keep the terminal open, or install it as a background service with cloudflared service install (see Cloudflare's docs).
Dashboard-managed alternative: in Cloudflare Zero Trust → Networks → Tunnels, create a tunnel, add a Public Hostname pointing to
http://localhost:8085, and run the command it shows:cloudflared tunnel run --token <TOKEN>.
The app must be running before traffic arrives. If it isn't, Cloudflare returns a 502 Bad Gateway.
Registering the app in MCPHub
Replace https://<your-host> with your Render URL or tunnel hostname.
MCPHub field | URL | Method |
Login URL |
| GET (browser) |
Exchange URL |
| POST, HMAC-signed |
Validate URL |
| POST, HMAC-signed |
Refresh |
| POST, HMAC-signed |
Logout |
| POST, HMAC-signed |
Users directory |
| GET, HMAC-signed |
Roles directory |
| GET, unsigned |
Domain verification |
| GET |
Checklist:
The HMAC key in MCPHub and in the app are identical.
The host of MCPHub's
callback_urlis listed inMCP_CONTRACT_ALLOWED_CALLBACK_HOSTS.The OpenAPI contract the hub uses is
mcp-openapi.yaml.
Users, roles, and MCP tool scopes
Sample accounts
Created by Liquibase on first start, in every profile (dev, Docker, Render):
Login | Password | Roles | |
|
|
|
|
|
|
|
|
Roles
Role | Exists in the database? | In the app | MCP tool scopes (from |
| Yes (seeded) | Full access, including the Administration menu (user management, authorities, metrics, logs) | All 31 operations: account, users, bank accounts, labels, operations, authorities |
| Yes (seeded) | Normal user: own account plus bank accounts, labels, operations |
|
| No | Not usable until you create it (see below) |
|
How the scopes work:
The scopes are configured under
mcp.contract.tool-scopesinapplication.yml, keyed by role name. Each name is anoperationIdfrommcp-openapi.yaml.A user's
toolScopes, returned by/api/mcp/exchangeand/api/mcp/validate, is the union of the scopes of all their roles. For example,admingets theROLE_ADMINandROLE_USERscopes combined./api/mcp/roleslists the roles that exist in the database with their scopes. Roles that appear only inapplication.yml(likeROLE_MANAGERtoday) are not reported to MCPHub.Scopes are not permissions. The app still enforces its own access rules. The user-management operations (
getAllUsers,createUser,updateUser,getUser,deleteUser) call/api/admin/users, which requiresROLE_ADMIN. AROLE_USERorROLE_MANAGERsession is offered those tools but gets403 Forbiddenwhen it calls them. Remove them from those roles if you don't want MCPHub to offer them.
Adding a role (e.g. ROLE_MANAGER)
Create the role. Sign in as
admin, go to Administration → Authority (/authority), and createROLE_MANAGER. Or call the API:curl -X POST http://localhost:8085/api/authorities \ -H "Authorization: Bearer <admin JWT>" -H "Content-Type: application/json" \ -d '{"name":"ROLE_MANAGER"}'Don't edit
liquibase/data/authority.csvon an existing database. Liquibase has already applied it, and changing the file makes startup fail with a checksum error.Assign it. Go to Administration → User management → Edit user → Profiles, and tick the role.
Set its tool scopes in
application.ymlundertool-scopes. Keep the bracket-and-quotes form, which preserves the exact key:'[ROLE_MANAGER]': - getAccount - getAllBankAccountsRestart the app.
Re-import roles in MCPHub so it picks up the new role from
/api/mcp/roles.
In dev, the H2 database is in-memory. Roles and users created through the UI or API disappear on restart, but changes to application.yml stay.
Common commands
Task | Command |
Run dev (single process) |
|
Run back end only (dev) |
|
Run Angular dev server |
|
Build production jar |
|
Build Docker image (Dockerfile) |
|
Build Docker image (Jib) |
|
Start app + DB in Docker |
|
Start only PostgreSQL |
|
Back-end tests |
|
Front-end tests |
|
Lint client |
|
Health check |
|
Troubleshooting
Symptom | Cause / fix |
| Running a non-dev profile without a valid key. Set it to 64 hex chars (no quotes, no spaces). |
|
|
| You ran Docker Compose without |
MCP login page says "The callback URL is not permitted for this tenant." | The |
Exchange/validate return | Signature check failed. Either (a) the key differs from MCPHub's; (b) the URL registered in MCPHub differs from the one the request arrives on (scheme, host, and path are all signed, e.g. |
| Wrong path. It's |
| Stop the other process, or change |
Docker: | Another container or a local PostgreSQL is using the port. Stop it, or change the host side of |
| Windows line endings: |
npm install is very slow or fails downloading Cypress | Set |
Cloudflare | App not running, or the tunnel points at the wrong port (8085 dev vs 8080 prod). |
Render deploy fails with No open ports detected or health check failures | The app is still starting (Liquibase + JVM). Check the logs for the real error, usually a missing env var or a wrong |
Render: | Web service and database are in different regions, or you used the external hostname without SSL. Use the internal hostname in the same region. |
Render free tier: first request after idle takes about a minute | Free services spin down when idle. MCPHub calls during spin-up may time out; use a paid instance for anything real. |
Render build killed / out of memory | The Angular production build is memory-hungry. Retry, or use a larger instance type. |
| Git is signed in as a GitHub account without write access to the remote. Get added as a collaborator, or sign in with the right account. |
Security notes
Never commit real secrets.
application-secret-samples.ymlis for local development only. In prod, use environment variables (.envlocally, the Render dashboard on Render)..envis git-ignored.If a real HMAC key or JWT secret was ever committed or pushed, rotate it. Removing it in a later commit does not remove it from git history.
The sample accounts
admin/adminanduser/userexist in every profile. Change those passwords on any publicly reachable deployment, including Cloudflare quick tunnels./api/mcp/rolesand/.well-known/mcp-hub-verification.jsonare intentionally unauthenticated. Every other MCP back-channel endpoint requires a valid HMAC signature.
Testing
./mvnw verify # Spring Boot unit + integration tests
./npmw test # Angular unit tests (Vitest)
./npmw run e2e # Cypress e2e; start the app first with ./npmw run app:start
./mvnw gatling:test # Gatling performance testsFor e2e, set CYPRESS_E2E_USERNAME / CYPRESS_E2E_PASSWORD to override the default credentials.
References
This server cannot be deployed
Maintenance
Related MCP Connectors
Governed MCP gateway: one endpoint for your tools, with credential custody and audit log.
Remote MCP for A2A caller identity, scope policy, verdict receipts, and audit history.
OAuth scope approvals and consent receipts for remote MCP servers.
Prove end users to agents and apps: login-links, OIDC clients, and API keys over remote MCP
Related MCP Servers
- AlicenseNot gradedqualityBmaintenanceEnables clients to access multiple backend MCP servers through a single endpoint, with OAuth 2.1 authorization, namespaced tools, and secure credential management.2MIT
- FlicenseNot gradedqualityBmaintenanceEnables verifying event-driven webhook signatures and providing HMAC replay protection for AI agents and MCP clients.8-
- AlicenseNot gradedqualityBmaintenanceEnables managing and observing Technitium DNS Server through MCP, including read-only or read-write tool surfaces, scoped bearer authentication, audit logging, and confirmation gates for destructive operations.MIT

ForIT MCP Gatewayofficial
AlicenseNot gradedqualityBmaintenanceEnables turning selected OpenAPI operations into authenticated MCP tools with branded OAuth, operation allowlisting, and live schema refreshes.Apache 2.0