diff --git a/.changeset/private-key-jwt-eddsa-pem.md b/.changeset/private-key-jwt-eddsa-pem.md new file mode 100644 index 0000000000..39ca95df2a --- /dev/null +++ b/.changeset/private-key-jwt-eddsa-pem.md @@ -0,0 +1,5 @@ +--- +'@modelcontextprotocol/client': patch +--- + +`PrivateKeyJwtProvider` and `createPrivateKeyJwtAuth()` now sign with an Ed25519 key given as a PEM string when the algorithm is `EdDSA` or `Ed25519`. They threw `Unsupported algorithm EdDSA` before, although the same key given as a `Uint8Array` or a JWK already worked. diff --git a/packages/client/src/client/authExtensions.ts b/packages/client/src/client/authExtensions.ts index e210786b7e..c7d6477408 100644 --- a/packages/client/src/client/authExtensions.ts +++ b/packages/client/src/client/authExtensions.ts @@ -63,7 +63,7 @@ export function createPrivateKeyJwtAuth(options: { const alg = options.alg; let key: unknown; if (typeof options.privateKey === 'string') { - if (alg.startsWith('RS') || alg.startsWith('ES') || alg.startsWith('PS')) { + if (alg.startsWith('RS') || alg.startsWith('ES') || alg.startsWith('PS') || alg.startsWith('Ed')) { key = await jose.importPKCS8(options.privateKey, alg); } else if (alg.startsWith('HS')) { key = new TextEncoder().encode(options.privateKey); diff --git a/packages/client/test/client/authExtensions.test.ts b/packages/client/test/client/authExtensions.test.ts index cb4f36a1e1..cdfdcdd960 100644 --- a/packages/client/test/client/authExtensions.test.ts +++ b/packages/client/test/client/authExtensions.test.ts @@ -393,6 +393,26 @@ describe('createPrivateKeyJwtAuth', () => { expect(parts).toHaveLength(3); }); + it.each(['EdDSA', 'Ed25519'])('creates a verifiable JWT when using an Ed25519 PEM private key with alg %s', async alg => { + const jose = await import('jose'); + const { privateKey, publicKey } = await jose.generateKeyPair(alg, { extractable: true }); + const pem = await jose.exportPKCS8(privateKey); + + const addClientAuth = createPrivateKeyJwtAuth({ + issuer: 'client-id', + subject: 'client-id', + privateKey: pem, + alg + }); + + const params = new URLSearchParams(); + await addClientAuth(new Headers(), params, 'https://auth.example.com/token', undefined); + + const { payload, protectedHeader } = await jose.jwtVerify(params.get('client_assertion')!, publicKey); + expect(protectedHeader.alg).toBe(alg); + expect(payload.iss).toBe('client-id'); + }); + it('uses metadata.issuer as audience when available', async () => { const addClientAuth = createPrivateKeyJwtAuth(baseOptions);