Skip to content

Commit 4582dfc

Browse files
committed
Resolving merge conflicts
1 parent 25f288b commit 4582dfc

File tree

8 files changed

+1739
-1
lines changed

8 files changed

+1739
-1
lines changed

docs/client.md

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,3 +62,59 @@ These examples show how to:
6262
- Perform dynamic client registration if needed.
6363
- Acquire access tokens.
6464
- Attach OAuth credentials to Streamable HTTP requests.
65+
66+
#### Cross-App Access Middleware
67+
68+
The `withCrossAppAccess` middleware enables secure authentication for MCP clients accessing protected servers through OAuth-based Cross-App Access flows. It automatically handles token acquisition and adds Authorization headers to requests.
69+
70+
```typescript
71+
import { Client } from '@modelcontextprotocol/client';
72+
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/client';
73+
import { withCrossAppAccess } from '@modelcontextprotocol/client';
74+
75+
// Configure Cross-App Access middleware
76+
const enhancedFetch = withCrossAppAccess({
77+
idpUrl: 'https://idp.example.com',
78+
mcpResourceUrl: 'https://mcp-server.example.com',
79+
mcpAuthorisationServerUrl: 'https://mcp-auth.example.com',
80+
idToken: 'your-id-token',
81+
idpClientId: 'your-idp-client-id',
82+
idpClientSecret: 'your-idp-client-secret',
83+
mcpClientId: 'your-mcp-client-id',
84+
mcpClientSecret: 'your-mcp-client-secret',
85+
scope: ['read', 'write'] // Optional scopes
86+
})(fetch);
87+
88+
// Use the enhanced fetch with your client transport
89+
const transport = new StreamableHTTPClientTransport(
90+
new URL('https://mcp-server.example.com/mcp'),
91+
enhancedFetch
92+
);
93+
94+
const client = new Client({
95+
name: 'secure-client',
96+
version: '1.0.0'
97+
});
98+
99+
await client.connect(transport);
100+
```
101+
102+
The middleware performs a two-step OAuth flow:
103+
104+
1. Exchanges your ID token for an authorization grant from the IdP
105+
2. Exchanges the grant for an access token from the MCP authorization server
106+
3. Automatically adds the access token to all subsequent requests
107+
108+
**Configuration Options:**
109+
110+
- **`idpUrl`**: Identity Provider's base URL for OAuth discovery
111+
- **`idToken`**: Identity token obtained from user authentication with the IdP
112+
- **`idpClientId`** / **`idpClientSecret`**: Credentials for authentication with the IdP
113+
- **`mcpResourceUrl`**: MCP resource server URL (used in token exchange request)
114+
- **`mcpAuthorisationServerUrl`**: MCP authorization server URL for OAuth discovery
115+
- **`mcpClientId`** / **`mcpClientSecret`**: Credentials for authentication with the MCP server
116+
- **`scope`**: Optional array of scope strings (e.g., `['read', 'write']`)
117+
118+
**Token Caching:**
119+
120+
The middleware caches the access token after the first successful exchange, so the token exchange flow only happens once. Subsequent requests reuse the cached token without additional OAuth calls.

packages/client/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
"eventsource-parser": "catalog:runtimeClientOnly",
5151
"jose": "catalog:runtimeClientOnly",
5252
"pkce-challenge": "catalog:runtimeShared",
53+
"qs": "catalog:runtimeClientOnly",
5354
"zod": "catalog:runtimeShared"
5455
},
5556
"peerDependencies": {
@@ -74,6 +75,7 @@
7475
"@types/content-type": "catalog:devTools",
7576
"@types/cross-spawn": "catalog:devTools",
7677
"@types/eventsource": "catalog:devTools",
78+
"@types/qs": "^6.9.18",
7779
"@typescript/native-preview": "catalog:devTools",
7880
"@eslint/js": "catalog:devTools",
7981
"eslint": "catalog:devTools",

packages/client/src/client/middleware.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { FetchLike } from '@modelcontextprotocol/core';
22

33
import type { OAuthClientProvider } from './auth.js';
44
import { auth, extractWWWAuthenticateParams, UnauthorizedError } from './auth.js';
5+
import { getAccessToken, XAAOptions } from './xaa-util.js';
56

67
/**
78
* Middleware function that wraps and enhances fetch functionality.
@@ -230,6 +231,35 @@ export const withLogging = (options: LoggingOptions = {}): Middleware => {
230231
};
231232
};
232233

234+
/**
235+
* Creates a fetch wrapper that handles Cross App Access authentication automatically.
236+
*
237+
* This wrapper will:
238+
* - Add Authorization headers with access tokens
239+
*
240+
* @param options - XAA configuration options
241+
* @returns A fetch middleware function
242+
*/
243+
export const withCrossAppAccess = (options: XAAOptions): Middleware => {
244+
return wrappedFetchFunction => {
245+
let accessToken: string | undefined = undefined;
246+
247+
return async (url, init = {}): Promise<Response> => {
248+
if (!accessToken) {
249+
accessToken = await getAccessToken(options, wrappedFetchFunction);
250+
}
251+
252+
const headers = new Headers(init.headers);
253+
254+
headers.set('Authorization', `Bearer ${accessToken}`);
255+
256+
init.headers = headers;
257+
258+
return wrappedFetchFunction(url, init);
259+
};
260+
};
261+
};
262+
233263
/**
234264
* Composes multiple fetch middleware functions into a single middleware pipeline.
235265
* Middleware are applied in the order they appear, creating a chain of handlers.

0 commit comments

Comments
 (0)