Implement MCP 2026-07-28 (#2286)
Co-authored-by: Matt <77928207+mattzcarey@users.noreply.github.com> Co-authored-by: Konstantin Konstantinov <KKonstantinov@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
This commit is contained in:
parent
e4227d135c
commit
1823aae891
1 changed files with 69131 additions and 9001 deletions
41
examples/oauth-client-credentials/README.md
Normal file
41
examples/oauth-client-credentials/README.md
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
# oauth-client-credentials
|
||||
|
||||
OAuth 2.0 **`client_credentials`** grant — machine-to-machine MCP auth, fully self-verifying with no browser.
|
||||
|
||||
`client_credentials` is the grant a backend service uses to authenticate **as itself** (not on behalf of a user): it presents a pre-registered `client_id`/`client_secret` directly to the Authorization Server's token endpoint and receives a Bearer access token. There is no
|
||||
redirect, no authorization code, no user consent screen.
|
||||
|
||||
The interactive **authorization-code** flow (the one that opens a browser and asks a human to sign in) lives under [`../oauth/`](../oauth/README.md); the runner drives it headlessly via the demo AS's `OAUTH_DEMO_AUTO_CONSENT=1` auto-approve mode.
|
||||
|
||||
## What runs
|
||||
|
||||
- `server.ts` starts two listeners in one process:
|
||||
- the MCP **resource server** on `--port` — `createMcpHandler` behind `requireBearerAuth` from `@modelcontextprotocol/express`, advertising the AS via `mcpAuthMetadataRouter` (RFC 9728 + RFC 8414).
|
||||
- a minimal **`client_credentials`-only Authorization Server** on `--port + 1` (`createClientCredentialsAuthServer` from `@mcp-examples/shared`). The repo's full better-auth/OIDC demo AS only implements `authorization_code`, so this story ships its own purpose-built AS.
|
||||
- `client.ts` first asserts a bare request is `401` with a `WWW-Authenticate` challenge, then connects with a `ClientCredentialsProvider` on the transport. The SDK auth driver discovers the AS from the challenge, posts `grant_type=client_credentials` (HTTP Basic auth) to
|
||||
`/token`, attaches the returned Bearer token, and the `whoami` tool's `ctx.authInfo` carries the granted `clientId` and `scopes` end to end.
|
||||
|
||||
## Run it
|
||||
|
||||
```bash
|
||||
pnpm --filter @mcp-examples/oauth-client-credentials server -- --http --port 3000
|
||||
pnpm --filter @mcp-examples/oauth-client-credentials client -- --http http://127.0.0.1:3000/mcp
|
||||
```
|
||||
|
||||
HTTP-only; runs on both protocol eras (the client honours `--legacy` via `parseExampleArgs().era`).
|
||||
|
||||
## `private_key_jwt` client authentication
|
||||
|
||||
To authenticate the `client_credentials` grant with a signed JWT assertion (RFC 7523 §2.2) instead of a shared secret, swap `ClientCredentialsProvider` for `PrivateKeyJwtProvider`:
|
||||
|
||||
```ts
|
||||
import { PrivateKeyJwtProvider } from '@modelcontextprotocol/client';
|
||||
|
||||
const authProvider = new PrivateKeyJwtProvider({
|
||||
clientId: 'my-service',
|
||||
privateKey: pemEncodedKey,
|
||||
algorithm: 'RS256'
|
||||
});
|
||||
```
|
||||
|
||||
The full snippet lives in the client guide (`docs/client.md` → `auth_privateKeyJwt`). There is no runnable leg for it in this story — the in-repo `client_credentials` AS only implements `client_secret_basic`/`client_secret_post`.
|
||||
Loading…
Reference in a new issue