jwt_decode_verify MCP tool: decode and verify JWTs in chat
· 6 min read
Get the devtoolbox.cc MCP serverOpen the JWT Debugger tool
“Why does the API reject this token?” is one of the most common debugging questions in any codebase that uses JWTs. A model can decode the header and payload of a token reasonably well, since that’s Base64URL and JSON. It can’t verify the signature, though, and it’s unreliable at the step people forget: turning exp: 1767229200 into a date and comparing it with the current time, which the model doesn’t know.
The devtoolbox.cc MCP server includes jwt_decode_verify, which does all three properly: decode, check the time claims against the real clock, and verify the signature with a key. It’s built on the same code as the JWT debugger on this site. Install instructions are on the MCP page.
What the tool reports
For any token (a leading Bearer is ignored), the tool returns:
- A status line: valid, expired (and how long ago), not yet valid, or no expiry claim.
- Warnings, for example for
alg: none, which means the token isn’t signed at all. - The header and payload, pretty-printed, with numbers exactly as they are in the token.
- The time claims
iat,nbfandexpas ISO 8601 dates. A claim with the wrong type, such as"exp": "tomorrow", or a number outside any representable date is reported as such instead of being ignored. - The signature check: verified, not verified with the reason, or not checked when no key was given.
Supported algorithms are HS256, HS384 and HS512 with a shared secret, and RS, PS and ES (256, 384, 512) with a public key as PEM, an X.509 certificate or a JWK.
Keeping the key out of the conversation
A JWT secret is a credential. Whoever has it can mint tokens your API will accept. The tool accepts the key three ways:
| Argument | Meaning |
|---|---|
key |
The key inline. Fine for test keys, not for production secrets. |
key_path |
A file with the secret, PEM or JWK. A trailing newline is ignored. |
key_env |
The name of an environment variable set for the MCP server. |
base64_secret |
The HMAC secret is Base64-encoded (common for some identity providers). |
With key_path or key_env, only the name of the file or variable appears in the chat. The secret itself goes from disk or environment straight into the verification code. The token itself is in the conversation, though, so treat production tokens with the same care.
Examples
The examples were run against the real tool with the clock set to 2026-10-24 12:00 UTC, and with JWT_SECRET=your-256-bit-secret in the server’s environment. First, a token that’s valid right now:
{
"token": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyLTQyIiwiYXVkIjoiYXBpLmV4YW1wbGUuY29tIiwiaWF0IjoxNzkyODQzMjAwLCJleHAiOjE3OTI4NDY4MDB9.fEkl8nmmIOFD-bdtC3nULihQsaiYrYeK81nITEeAkhk",
"key_env": "JWT_SECRET"
}
Status: Valid, expires in 1 hour
Header:
{
"alg": "HS256",
"typ": "JWT"
}
Payload:
{
"sub": "user-42",
"aud": "api.example.com",
"iat": 1792843200,
"exp": 1792846800
}
Time claims:
iat: 2026-10-24T12:00:00.000Z
exp: 2026-10-24T13:00:00.000Z
Signature: verified (HS256)
An older token that expired at the start of the year. Its signature is still correct, which is a useful distinction when an API says “401” and nothing else:
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyLTQyIiwicm9sZSI6ImVkaXRvciIsImlhdCI6MTc2NzIyNTYwMCwiZXhwIjoxNzY3MjI5MjAwfQ.8Gg9VGZ1P62UdsQK9jlJ3EZC1a7Bv2zxB5RD3Ky0kQw",
"key_env": "JWT_SECRET"
}
Status: Expired 10 months ago
Header:
{
"alg": "HS256",
"typ": "JWT"
}
Payload:
{
"sub": "user-42",
"role": "editor",
"iat": 1767225600,
"exp": 1767229200
}
Time claims:
iat: 2026-01-01T00:00:00.000Z
exp: 2026-01-01T01:00:00.000Z
Signature: verified (HS256)
The same token with the wrong secret. The payload still decodes, which is the point many people miss about JWTs: decoding proves nothing, only the signature check does.
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyLTQyIiwicm9sZSI6ImVkaXRvciIsImlhdCI6MTc2NzIyNTYwMCwiZXhwIjoxNzY3MjI5MjAwfQ.8Gg9VGZ1P62UdsQK9jlJ3EZC1a7Bv2zxB5RD3Ky0kQw",
"key": "wrong-secret"
}
Status: Expired 10 months ago
Header:
{
"alg": "HS256",
"typ": "JWT"
}
Payload:
{
"sub": "user-42",
"role": "editor",
"iat": 1767225600,
"exp": 1767229200
}
Time claims:
iat: 2026-01-01T00:00:00.000Z
exp: 2026-01-01T01:00:00.000Z
Signature: NOT verified: Invalid signature
A token with alg: none and an exp that isn’t a number. Both problems are reported, and the token is never treated as verified:
{ "token": "eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzdWIiOiJhZG1pbiIsImV4cCI6InRvbW9ycm93In0." }
Status: exp must be a NumericDate (seconds since 1970), not a string
Warning: alg "none": this token is unsigned and must never be trusted.
Header:
{
"alg": "none",
"typ": "JWT"
}
Payload:
{
"sub": "admin",
"exp": "tomorrow"
}
Time claims:
exp: exp must be a NumericDate (seconds since 1970), not a string
Signature: not checked (no key given)
A string that isn’t a JWT at all fails with a clear message:
{ "token": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ4In0" }
A JWT has 3 dot-separated parts, found 2
Clock skew
The tool compares exp and nbf with the computer’s clock, to the second. Services usually allow some leeway, often between a few seconds and a few minutes, because the clocks of the issuer and the API are never exactly in sync. A token the tool calls “expired 20 seconds ago” may still be accepted by a service with a one-minute leeway, and a token that’s “not valid until” a moment from now may already work. When a token fails close to its expiry, check the leeway setting of the service before suspecting the token.
Things to ask your assistant
- “This request in
failing.httpgets a 401. Decode the bearer token and check it against the key inJWT_SECRET.” - “Verify
token.txtwith the public key inkeys/auth-public.pem. Is it expired?” - “Which claims does the token in the
Authorizationheader ofrequest.logcarry?” - “Check the token from our identity provider with the JWK in
jwks/signing-key.json.”
For a token you just want to look at, the JWT debugger on this site decodes and verifies in the browser, and it doesn’t save tokens or keys anywhere. To read a single timestamp from a claim, the Unix timestamp converter shows it in any time zone.
What the tool doesn’t check
A valid signature and a future exp don’t make a token acceptable to your API. Services also check the audience (aud), the issuer (iss), sometimes a token ID against a revocation list, and the algorithm they expect. The tool shows these claims; deciding whether they’re right for your service is up to you or the agent. Verification also uses the algorithm named in the token’s header, so check that it’s the one your service expects. Accepting whatever algorithm a token claims is how “algorithm confusion” attacks work.
jwt_decode_verify runs inside the devtoolbox.cc MCP server on your computer, with no network access, so tokens and keys stay local. The companion tool jwt_sign creates test tokens. Setup for Claude Desktop, Claude Code, Cursor and VS Code is on the MCP install page.