Tenant Delegated — Troubleshooting
Most Tenant Delegated problems surface at one of two moments: domain verification (Step 2) or provisioning (Step 4), where LockMCP runs a live certification suite against your endpoints. This page maps the exact messages you'll see back to what to fix.
This page applies only to connections using the Tenant Delegated identity model.
Step 2 — Domain verification
| Symptom | Cause | Fix |
|---|---|---|
| Verification never turns green | The DNS TXT record hasn't propagated yet | TXT records can take minutes to hours. Confirm with dig TXT yourdomain.com (or nslookup -type=TXT) before re-checking. |
| Record looks right but check fails | The record was added to the wrong name, or quoted incorrectly | Verify the exact host label the wizard shows. Some DNS providers auto-append your domain — adding the full name again produces a doubled record. |
| Well-known file returns 404 | The file isn't served at the exact path, or a router intercepts it | Fetch the URL the wizard shows in a browser. It must return the raw token as 200, not a redirect or an HTML page. |
Provisioning is blocked until this passes — attempting Step 4 early returns:
Verify the connection's domain before provisioningStep 4 — Provisioning fails
Before running certification, LockMCP checks you've completed Step 3. If anything's missing:
Configure the login/exchange/validate URLs and generate an HMAC key firstAll three URLs and a generated HMAC key are required. Use Test endpoints in Step 3 first — it's a fast reachability check that catches typos before the full suite runs.
Certification checks and what makes them fail
Provisioning runs seven checks. Every failure is reported as checkName: message.
| Check | Fails when | Fix |
|---|---|---|
httpsEnforcement | Any configured URL isn't HTTPS — Non-HTTPS URLs detected: … | All endpoints must be HTTPS. Plain HTTP is rejected outright. |
loginRedirect | Login URL returns something other than 200 or 302, or returns 200 without the required hidden fields | Your login page must accept the hub's query parameters and either render the form or redirect. A 200 response must include hidden callback_url and code_challenge fields. |
exchangeInvalidCode | Exchange returns 2xx for a code that was never issued, or throws a 5xx | Your exchange endpoint must return a 4xx for unknown codes. Accepting a fabricated code is a security failure, not a soft warning. A 5xx means an unhandled exception — check your logs. |
validateUnknownToken | Validate doesn't return { valid: false } for an unknown token, returns non-JSON, or rejects the hub's signature | Return { "valid": false } with a 200 for unrecognised tokens. See the HMAC row below for signature rejections. |
singleUseEnforcement | Exchange accepts a fabricated code across two consecutive attempts | Codes must be single-use and unknown codes must consistently fail. Both attempts must return 4xx. |
pkceAcceptance | Login page returns a 5xx when PKCE parameters are present | Your login page must tolerate code_challenge / code_challenge_method parameters even if you don't use them yet. |
responseLatency | Validate takes longer than 5000 ms | Validate is called on tool requests — it must be fast. Cache session lookups; don't call third-party services inline. |
HMAC signature rejected
Validate endpoint rejected the hub's HMAC signature (HTTP 401): "invalid_signature".
Check that MCP_CONTRACT_HMAC_KEY in your app matches the key stored in the hub.The single most common cause is a key mismatch — the key was regenerated in the wizard but not redeployed to your app, or it was copied with surrounding whitespace. Regenerating the key in Step 3 invalidates the old one immediately.
If the key definitely matches, the signature computation is the next suspect. See HMAC Signing — the usual culprits are signing a re-serialized body instead of the exact raw bytes received, or omitting the timestamp/nonce from the signing string.
"unreachable" on any check
Exchange endpoint unreachable: connect ECONNREFUSED
Validate endpoint unreachable: getaddrinfo ENOTFOUNDThese are network-level failures, not contract failures — LockMCP never got a response. Check that the host resolves publicly, the port is open to the internet, and that no firewall or WAF is blocking datacenter traffic. A self-signed or expired TLS certificate also lands here.
After activation
| Symptom | Likely cause |
|---|---|
Authentication failed: issuer not recognized | The connection isn't Active, or the token was minted for a different connection. Each connection has its own endpoint and its own issuer. |
Authentication failed: token has expired | Normal — the client should re-authenticate. Persistent expiry usually means clock skew on your server. |
Session has been revoked or expired | Your validate endpoint returned { valid: false }. Expected after a logout or password change on your side. |
| Connected, but no tools appear | Tools exist but aren't published, or the user's role has no access. Check Tool Catalog and the role's tool access. |
| Users get in but with wrong permissions | Role mapping in Step 3 is missing an entry, or maps to a role a login can't assign (Step 3 and the connection page flag these). Unmapped role names from your app are ignored — they don't fall back to a default. |
| A role assigned in Members disappears | With a role mapping set, each sign-in sets the connection's roles from the mapping and replaces manual ones. Change the mapping instead. The audit log shows Roles replaced by app login. |
Still stuck
Re-run Test endpoints in Step 3 to get a fast pass/fail per endpoint, then re-run provisioning — certification results are recorded each time, so you can compare runs. The Contract Specification is authoritative on exact request and response shapes if a check disagrees with what you think you're returning.