mcp
High-security OAuth implementation for Model Context Protocol
Installation
npx @keycardai/mcpAsk AI about mcp
Powered by Claude Β· Grounded in docs
I know everything about mcp. Ask me about installation, configuration, usage, or troubleshooting.
0/500
Reviews
Documentation
Keycard TypeScript SDK
Preview. This SDK has not reached parity with the Keycard Python SDK. APIs may change between minor versions. The preview label will be removed once feature parity is reached.
A collection of TypeScript packages for Keycard services, organized as a pnpm workspace.
Requirements
- Node.js 18 or greater
- pnpm 9+
Packages
| Package | Description | npm |
|---|---|---|
@keycardai/oauth | Pure OAuth 2.0 primitives β JWKS key management, JWT signing/verification, authorization server discovery | |
@keycardai/mcp | MCP OAuth integration β Express middleware, token verification, client providers | |
@keycardai/sdk | Aggregate package re-exporting from oauth + mcp |
Key Concepts
- Zone β A Keycard environment that groups your identity providers, MCP resources, and access policies. Get your zone ID from console.keycard.ai.
- Delegated Access β Calling external APIs (Google, GitHub, Slack, etc.) on behalf of your authenticated users via RFC 8693 token exchange.
.grant()middleware β Declares which external APIs a route needs. Automatically exchanges the user's token for a scoped token before your handler runs.- AccessContext β The result of a grant. Contains exchanged tokens or errors. Non-throwing by design β always check
.hasErrors()before using tokens. - Application Credentials β How your server authenticates with Keycard for token exchange. Three types:
ClientSecret,WebIdentity(private key JWT),EKSWorkloadIdentity(AWS EKS).
Installation
For MCP Servers (Express)
If you're building an MCP server with Express:
npm install @keycardai/mcp @modelcontextprotocol/sdk
@keycardai/mcp requires @modelcontextprotocol/sdk as a peer dependency (^1.15.0).
This includes @keycardai/oauth as a dependency.
For OAuth Functionality Only
If you only need OAuth primitives (JWT signing, JWKS key management, discovery):
npm install @keycardai/oauth
Aggregate Package
For convenience, you can install the aggregate package which re-exports from both:
npm install @keycardai/sdk
Quick Start
Protect an MCP Server with Bearer Auth
import express from "express";
import { requireBearerAuth } from "@keycardai/mcp/server/auth/middleware/bearerAuth";
import { mcpAuthMetadataRouter } from "@keycardai/mcp/server/auth/router";
const app = express();
// Mount OAuth metadata endpoints (.well-known)
app.use(
mcpAuthMetadataRouter({
oauthMetadata: { issuer: "https://your-zone.keycard.cloud" },
}),
);
// Protect routes with bearer token verification. `issuers` pins the
// verifier to your zone so forged tokens from any other issuer are
// rejected before any JWKS lookup.
app.use(
"/api",
requireBearerAuth({
issuers: "https://your-zone.keycard.cloud",
requiredScopes: ["read"],
}),
);
app.get("/api/data", (req, res) => {
res.json({ message: "Authenticated!" });
});
Sign and Verify JWTs
import { JWTSigner } from "@keycardai/oauth/jwt/signer";
import { JWTVerifier } from "@keycardai/oauth/jwt/verifier";
import { JWKSOAuthKeyring } from "@keycardai/oauth/keyring";
// Sign a JWT
const keyring = new JWKSOAuthKeyring();
const signer = new JWTSigner(keyring);
const token = await signer.sign({
sub: "user-123",
aud: "https://api.example.com",
scope: "read write",
});
// Verify a JWT. `issuers` is required β tokens with any other `iss`
// are rejected before the keyring is consulted. `audiences` is optional.
const verifier = new JWTVerifier(keyring, {
issuers: "https://your-zone.keycard.cloud",
});
const claims = await verifier.verify(token);
MCP Client Provider
import { BaseOAuthClientProvider } from "@keycardai/mcp/client/auth/providers/base";
class MyOAuthProvider extends BaseOAuthClientProvider {
constructor() {
super(
{
redirect_uris: [new URL("http://localhost:3000/callback")],
client_name: "My MCP Client",
},
"your-client-id"
);
}
redirectToAuthorization(authorizationUrl: URL) {
// Redirect user to authorization URL
window.location.href = authorizationUrl.toString();
}
}
Examples
Runnable example projects with full setup instructions:
| Example | Description |
|---|---|
| Hello World Server | Minimal MCP server with bearer auth and OAuth metadata |
| Delegated Access | Token exchange for external APIs (GitHub) with error handling |
Delegated Access (Token Exchange)
The SDK supports OAuth 2.0 token exchange (RFC 8693) for delegated access β exchanging a user's bearer token for resource-specific tokens to call external APIs on behalf of authenticated users.
Setup with Client Credentials
import express from "express";
import { AuthProvider } from "@keycardai/mcp/server/auth/provider";
import { ClientSecret } from "@keycardai/mcp/server/auth/credentials";
import { requireBearerAuth } from "@keycardai/mcp/server/auth/middleware/bearerAuth";
import type { DelegatedRequest } from "@keycardai/mcp/server/auth/provider";
const authProvider = new AuthProvider({
zoneUrl: "https://your-zone.keycard.cloud",
applicationCredential: new ClientSecret("your-client-id", "your-client-secret"),
});
const app = express();
// 1. Verify the user's bearer token. `issuers` pins the verifier to your zone.
app.use(requireBearerAuth({ issuers: "https://your-zone.keycard.cloud" }));
// 2. Exchange for a resource-specific token
app.get(
"/api/github-user",
authProvider.grant("https://api.github.com"),
async (req, res) => {
const { accessContext } = req as DelegatedRequest;
if (accessContext.hasErrors()) {
return res.status(502).json(accessContext.getErrors());
}
const token = accessContext.access("https://api.github.com").accessToken;
const response = await fetch("https://api.github.com/user", {
headers: { Authorization: `Bearer ${token}` },
});
res.json(await response.json());
},
);
Error Handling
AccessContext never throws during token exchange β errors are captured and queryable:
const { accessContext } = req as DelegatedRequest;
// Check overall status
const status = accessContext.getStatus(); // "success" | "partial_error" | "error"
// Check for global errors (e.g., missing auth token)
if (accessContext.hasError()) {
console.error(accessContext.getError());
}
// Check for resource-specific errors
if (accessContext.hasResourceError("https://api.github.com")) {
console.error(accessContext.getResourceErrors("https://api.github.com"));
}
// List successes and failures
console.log("OK:", accessContext.getSuccessfulResources());
console.log("Failed:", accessContext.getFailedResources());
Multiple Resources
app.get(
"/api/dashboard",
authProvider.grant(["https://api.github.com", "https://api.slack.com"]),
async (req, res) => {
const { accessContext } = req as DelegatedRequest;
// Partial success: some resources may succeed while others fail
if (accessContext.getStatus() === "partial_error") {
// Handle gracefully β use what succeeded
}
const githubToken = accessContext.access("https://api.github.com").accessToken;
const slackToken = accessContext.access("https://api.slack.com").accessToken;
// ...
},
);
Standalone Usage (Without Express Middleware)
For non-Express contexts (e.g., MCP tool handlers), use exchangeTokens() directly:
const accessContext = await authProvider.exchangeTokens(
userBearerToken,
"https://api.github.com",
);
if (accessContext.hasErrors()) {
// Handle error
}
const token = accessContext.access("https://api.github.com").accessToken;
WebIdentity (Private Key JWT)
For servers that authenticate with private key JWT (RFC 7523) instead of client secrets:
import { AuthProvider } from "@keycardai/mcp/server/auth/provider";
import { WebIdentity } from "@keycardai/mcp/server/auth/credentials";
const authProvider = new AuthProvider({
zoneUrl: "https://your-zone.keycard.cloud",
applicationCredential: new WebIdentity({
serverName: "My MCP Server",
storageDir: "./mcp_keys", // RSA keys stored here
}),
});
EKS Workload Identity
For servers running on EKS with mounted pod identity tokens:
import { AuthProvider } from "@keycardai/mcp/server/auth/provider";
import { EKSWorkloadIdentity } from "@keycardai/mcp/server/auth/credentials";
const authProvider = new AuthProvider({
zoneUrl: "https://your-zone.keycard.cloud",
applicationCredential: new EKSWorkloadIdentity(),
// Discovers token from: KEYCARD_EKS_WORKLOAD_IDENTITY_TOKEN_FILE,
// AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE, or AWS_WEB_IDENTITY_TOKEN_FILE
});
Development
Install from Source
git clone git@github.com:keycardai/typescript-sdk.git
cd typescript-sdk
pnpm install
Build
pnpm run build
Packages build in dependency order: oauth first, then mcp, then sdk.
Test
pnpm run test
Type Check
pnpm run typecheck
Architecture
The SDK mirrors the Python SDK workspace structure:
packages/
oauth/ β Pure OAuth 2.0 primitives (no MCP dependency)
mcp/ β MCP-specific OAuth (depends on oauth + @modelcontextprotocol/sdk)
sdk/ β Aggregate re-exports
@keycardai/oauth is the foundational layer with zero MCP dependencies. @keycardai/mcp builds on top for MCP-specific concerns (Express middleware, MCP SDK type adapters). Extensions for specific frameworks branch out from there.
Known Limitations & Non-Goals
Current Limitations
- Alpha Status: All packages are in early development (
0.x.y). APIs may change between minor versions. - Express Only: Server-side middleware targets Express 5. Other frameworks (Fastify, Koa, Hono) are not supported yet.
- MCP Protocol Version: Requires
@modelcontextprotocol/sdk@^1.15.0as a peer dependency. Compatible with any 1.x release from 1.15.0 onward.
Non-Goals
- Standalone Identity Provider: These packages integrate with Keycard's hosted identity service, not standalone identity management.
- TypeScript Only: This SDK is TypeScript/JavaScript only. See the Python SDK for Python.
- Offline Operation: All authentication flows require network connectivity to Keycard services.
License
This project is licensed under the MIT License - see the LICENSE file for details.
Support
- GitHub Issues: https://github.com/keycardai/typescript-sdk/issues
- Documentation: https://docs.keycard.ai
- Python SDK: https://github.com/keycardai/python-sdk
- Email: support@keycard.ai
