jwt_sign MCP tool: create signed test JWTs from an AI agent
· 6 min read
Get the devtoolbox.cc MCP serverOpen the JWT Debugger tool
Tests for an authenticated API need tokens: an admin token, an expired one, a token for the wrong audience, a token signed with the wrong key. Writing these by hand means Base64URL-encoding JSON, computing an HMAC or an RSA signature, and joining the parts with dots. An assistant can write a script that does it, but it can’t produce a valid signature by typing. The signature has to be computed.
The devtoolbox.cc MCP server has a tool for this, jwt_sign. The assistant passes the header and claims as JSON, the tool signs them with your key and returns the token. It uses the same code as the encoder in the JWT debugger on this site. Installation is on the MCP page.
What the tool does
- Signs with HS256, HS384 and HS512 (shared secret), or RS, PS and ES algorithms (private key as PKCS#8 PEM or JWK).
- Takes the header and payload as JSON text, so claims come out exactly as written, including large numeric IDs.
- Uses
{"alg":"HS256","typ":"JWT"}as the header if you don’t give one. - Refuses to create unsigned tokens (
alg: none).
HMAC signatures, and RSA signatures with RS256, RS384 or RS512, are deterministic: the same header, payload and key always give the same token. PS and ES signatures contain randomness, so they differ on every call and are all valid.
Arguments
| Argument | Type | Meaning |
|---|---|---|
payload |
string | The claims as JSON. Required. |
header |
string | The header as JSON. Must name the alg. Default HS256 with typ. |
key |
string | Secret, PEM or JWK, inline. Use this only for throwaway test keys. |
key_path |
string | A file with the key. A trailing newline in a secret file is ignored. |
key_env |
string | The name of an environment variable holding the key. |
base64_secret |
boolean | The HMAC secret is Base64-encoded. Default false. |
Prefer key_path or key_env. The tool then reads the key itself and the secret never passes through the conversation.
Examples
These were run against the real tool with JWT_SECRET=your-256-bit-secret in the server’s environment. The claims of the well-known example token from jwt.io, with the default header:
{
"payload": "{\"sub\":\"1234567890\",\"name\":\"John Doe\",\"iat\":1516239022}",
"key_env": "JWT_SECRET"
}
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
That’s exactly the token jwt.io shows, character for character, because HS256 is deterministic. A test token with HS512, a key ID in the header and an expiry:
{
"header": "{\"alg\":\"HS512\",\"typ\":\"JWT\",\"kid\":\"test-2026\"}",
"payload": "{\"sub\":\"user-42\",\"aud\":\"api.example.com\",\"role\":\"admin\",\"exp\":1798761600}",
"key_env": "JWT_SECRET"
}
eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCIsImtpZCI6InRlc3QtMjAyNiJ9.eyJzdWIiOiJ1c2VyLTQyIiwiYXVkIjoiYXBpLmV4YW1wbGUuY29tIiwicm9sZSI6ImFkbWluIiwiZXhwIjoxNzk4NzYxNjAwfQ.BN_J7DfDr7TDrbjnjxsocSllmJ8PB38kcUxJp24oeWXHoh_LbUNpnrnnKA_1ET6uNmz36l_bhusb2wUlTZHc4g
The exp of 1798761600 is 2027-01-01 00:00 UTC. An assistant can get such values from the timestamp_convert tool instead of doing the arithmetic. Numbers are written exactly as given, which matters for IDs that don’t fit in a double:
{ "payload": "{\"sub\":\"svc\",\"tenant\":9007199254740993}", "key_env": "JWT_SECRET" }
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJzdmMiLCJ0ZW5hbnQiOjkwMDcxOTkyNTQ3NDA5OTN9.ovkSFrzj3b0rRzOL4oRfBI7Zs5Dx0oWJK6rlT1AAv10
Unsigned tokens are refused, since there’s no legitimate reason for a test suite to depend on them:
{ "header": "{\"alg\":\"none\"}", "payload": "{\"sub\":\"admin\"}", "key": "x" }
Refusing to create an unsigned (alg "none") token
A key that doesn’t fit the algorithm fails instead of producing a broken token. Here an RS256 header meets an HMAC secret, and the message comes from the signing library, which expected an RSA private key in PKCS#8 PEM format:
{ "header": "{\"alg\":\"RS256\"}", "payload": "{\"sub\":\"x\"}", "key_env": "JWT_SECRET" }
"pkcs8" must be PKCS#8 formatted string
For RS256, store the private key in a PEM file and pass key_path. Services verify those tokens with the matching public key, which jwt_decode_verify accepts as PEM, X.509 certificate or JWK.
Building a set of test tokens
An agent writing authentication tests can produce the whole matrix in one go:
- a valid token for each role your API knows;
- a token whose
expis in the past, and one whosenbfis in the future; - a token for the wrong
audand one from the wrongiss; - a token signed with a different secret, to check that verification really happens.
Each of those is one jwt_sign call with different claims or a different key. Saved as fixtures, they make tests independent of a running identity provider. Use dedicated test keys for this. Tokens signed with a production key are real credentials, wherever they end up.
Choosing exp, nbf and iat
The time claims are NumericDates: seconds since 1970, not milliseconds. A value with 13 digits is the most common mistake in hand-written tokens, and it produces a token that expires in the year 57,000. Some rules that keep test tokens realistic:
iatis when the token was issued,expwhen it expires. Access tokens usually live minutes to hours, not days.- For a “not yet valid” test, set
nbfin the future. - For an “expired” test, set
expin the past, not to zero. Some code treats0as “no expiry”.
The assistant can get exact values from the timestamp_convert tool of the same server:
{ "date": "2027-01-01T00:00:00Z" }
Seconds: 1798761600
Milliseconds: 1798761600000
ISO 8601: 2027-01-01T00:00:00.000Z
RFC 2822: Fri, 01 Jan 2027 00:00:00 +0000
UTC (+00:00): 2027-01-01 00:00:00
Relative: in 2 months
The seconds value is the exp used in the HS512 example above.
Keep the generated tokens next to the tests that use them, and regenerate them whenever the test signing key changes.
Things to ask your assistant
- “Create an HS256 token for
user-42with roleviewer, expiring in one hour, signed withJWT_SECRET.” - “Generate expired, not-yet-valid and wrong-audience tokens for the tests in
auth.spec.ts, signed withkeys/test-private.pem.” - “Re-sign the payload of
token.txtwithexpmoved to next week.”
To build a token interactively, the encoder in the JWT debugger shows header, payload and the signed result side by side. The Unix timestamp converter helps with exp, nbf and iat values.
Install
jwt_sign is part of the devtoolbox.cc MCP server, which runs locally and makes no network requests. Keys and tokens stay on your computer. Follow the steps for Claude Desktop, Claude Code, Cursor or VS Code on the MCP install page.