Skip to content

Commit 30dbb3a

Browse files
authored
fix: make authorizationUrl and launch exclusive (#31)
1 parent 99e6830 commit 30dbb3a

File tree

11 files changed

+229
-53
lines changed

11 files changed

+229
-53
lines changed

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/
131131
const authProvider = browserAuth({
132132
port: 3000,
133133
scope: "read write",
134+
launch: open, // Opens browser for OAuth consent
134135
store: inMemoryStore(), // Or fileStore() for persistence
135136
});
136137

@@ -155,17 +156,20 @@ import { browserAuth, inMemoryStore, fileStore } from "oauth-callback/mcp";
155156

156157
// Ephemeral storage (tokens lost on restart)
157158
const ephemeralAuth = browserAuth({
159+
launch: open,
158160
store: inMemoryStore(),
159161
});
160162

161163
// Persistent file storage (default: ~/.mcp/tokens.json)
162164
const persistentAuth = browserAuth({
165+
launch: open,
163166
store: fileStore(),
164167
storeKey: "my-app-tokens", // Namespace for multiple apps
165168
});
166169

167170
// Custom file location
168171
const customAuth = browserAuth({
172+
launch: open,
169173
store: fileStore("/path/to/tokens.json"),
170174
});
171175
```
@@ -179,6 +183,7 @@ const authProvider = browserAuth({
179183
clientId: "your-client-id",
180184
clientSecret: "your-client-secret",
181185
scope: "read write",
186+
launch: open, // Opens browser for OAuth consent
182187
store: fileStore(), // Persist tokens across sessions
183188
});
184189
```
@@ -283,6 +288,7 @@ Available from `oauth-callback/mcp`. Creates an MCP SDK-compatible OAuth provide
283288
- `clientSecret` (string): Pre-registered client secret (optional)
284289
- `store` (TokenStore): Token storage implementation (default: inMemoryStore())
285290
- `storeKey` (string): Storage key for tokens (default: "mcp-tokens")
291+
- `launch` (function): Callback to launch auth URL (e.g., `open`)
286292
- `authTimeout` (number): Authorization timeout in ms (default: 300000)
287293
- `successHtml` (string): Custom success page HTML
288294
- `errorHtml` (string): Custom error page HTML

docs/api/browser-auth.md

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -96,9 +96,11 @@ await client.connect(transport);
9696
Store tokens across sessions:
9797

9898
```typescript
99+
import open from "open";
99100
import { browserAuth, fileStore } from "oauth-callback/mcp";
100101

101102
const authProvider = browserAuth({
103+
launch: open,
102104
store: fileStore(), // Persists to ~/.mcp/tokens.json
103105
scope: "read write",
104106
});
@@ -111,10 +113,13 @@ const authProvider = browserAuth({
111113
If you have pre-registered OAuth credentials:
112114

113115
```typescript
116+
import open from "open";
117+
114118
const authProvider = browserAuth({
115119
clientId: process.env.OAUTH_CLIENT_ID,
116120
clientSecret: process.env.OAUTH_CLIENT_SECRET,
117121
scope: "read write admin",
122+
launch: open,
118123
store: fileStore(),
119124
});
120125
```
@@ -124,9 +129,11 @@ const authProvider = browserAuth({
124129
Store tokens in a specific location:
125130

126131
```typescript
132+
import open from "open";
127133
import { browserAuth, fileStore } from "oauth-callback/mcp";
128134

129135
const authProvider = browserAuth({
136+
launch: open,
130137
store: fileStore("/path/to/my-tokens.json"),
131138
storeKey: "my-app-production", // Namespace for multiple environments
132139
});
@@ -137,10 +144,13 @@ const authProvider = browserAuth({
137144
Configure the callback server:
138145

139146
```typescript
147+
import open from "open";
148+
140149
const authProvider = browserAuth({
141150
port: 8080,
142151
hostname: "127.0.0.1",
143152
callbackPath: "/oauth/callback",
153+
launch: open,
144154
store: fileStore(),
145155
});
146156
```
@@ -157,7 +167,10 @@ Ensure your OAuth app's redirect URI matches your configuration:
157167
Provide branded callback pages:
158168

159169
```typescript
170+
import open from "open";
171+
160172
const authProvider = browserAuth({
173+
launch: open,
161174
successHtml: `
162175
<!DOCTYPE html>
163176
<html>
@@ -208,7 +221,10 @@ const authProvider = browserAuth({
208221
Monitor OAuth flow for debugging:
209222

210223
```typescript
224+
import open from "open";
225+
211226
const authProvider = browserAuth({
227+
launch: open,
212228
onRequest: (req) => {
213229
const url = new URL(req.url);
214230
console.log(`[OAuth] ${req.method} ${url.pathname}`);
@@ -263,9 +279,12 @@ sequenceDiagram
263279
No pre-registration needed:
264280

265281
```typescript
282+
import open from "open";
283+
266284
// No clientId or clientSecret required!
267285
const authProvider = browserAuth({
268286
scope: "read write",
287+
launch: open,
269288
store: fileStore(), // Persist dynamically registered client
270289
});
271290

@@ -317,9 +336,11 @@ interface OAuthStore extends TokenStore {
317336
Ephemeral storage (tokens lost on restart):
318337

319338
```typescript
339+
import open from "open";
320340
import { browserAuth, inMemoryStore } from "oauth-callback/mcp";
321341

322342
const authProvider = browserAuth({
343+
launch: open,
323344
store: inMemoryStore(),
324345
});
325346
```
@@ -335,15 +356,18 @@ const authProvider = browserAuth({
335356
Persistent storage to JSON file:
336357

337358
```typescript
359+
import open from "open";
338360
import { browserAuth, fileStore } from "oauth-callback/mcp";
339361

340362
// Default location: ~/.mcp/tokens.json
341363
const authProvider = browserAuth({
364+
launch: open,
342365
store: fileStore(),
343366
});
344367

345368
// Custom location
346369
const customAuth = browserAuth({
370+
launch: open,
347371
store: fileStore("/path/to/tokens.json"),
348372
});
349373
```
@@ -388,6 +412,7 @@ class RedisStore implements TokenStore {
388412

389413
// Use custom store
390414
const authProvider = browserAuth({
415+
launch: open,
391416
store: new RedisStore(redisClient),
392417
});
393418
```
@@ -409,8 +434,10 @@ PKCE prevents authorization code interception attacks by:
409434
The provider automatically generates secure state parameters:
410435

411436
```typescript
437+
import open from "open";
438+
412439
// State is automatically generated and validated
413-
const authProvider = browserAuth();
440+
const authProvider = browserAuth({ launch: open });
414441
// No manual state handling needed!
415442
```
416443

@@ -430,8 +457,11 @@ Tokens are automatically managed with expiry tracking:
430457
File storage uses restrictive permissions:
431458

432459
```typescript
460+
import open from "open";
461+
433462
// Files are created with mode 0600 (owner read/write only)
434463
const authProvider = browserAuth({
464+
launch: open,
435465
store: fileStore(), // Secure file permissions
436466
});
437467
```
@@ -472,7 +502,10 @@ The provider includes automatic retry for transient failures:
472502
Configure timeout for different scenarios:
473503

474504
```typescript
505+
import open from "open";
506+
475507
const authProvider = browserAuth({
508+
launch: open,
476509
authTimeout: 600000, // 10 minutes for first-time setup
477510
});
478511
```
@@ -484,13 +517,15 @@ const authProvider = browserAuth({
484517
Full example with Dynamic Client Registration:
485518

486519
```typescript
520+
import open from "open";
487521
import { browserAuth, fileStore } from "oauth-callback/mcp";
488522
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
489523
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
490524

491525
async function connectToNotion() {
492526
// No client credentials needed - uses DCR!
493527
const authProvider = browserAuth({
528+
launch: open, // Opens browser for OAuth consent
494529
store: fileStore(), // Persist tokens and client registration
495530
scope: "read write",
496531
onRequest: (req) => {
@@ -536,24 +571,28 @@ connectToNotion();
536571
Support development, staging, and production:
537572

538573
```typescript
539-
import { browserAuth, fileStore } from "oauth-callback/mcp";
574+
import open from "open";
575+
import { browserAuth, fileStore, inMemoryStore } from "oauth-callback/mcp";
540576

541577
function createAuthProvider(environment: "dev" | "staging" | "prod") {
542578
const configs = {
543579
dev: {
544580
port: 3000,
581+
launch: open,
545582
store: inMemoryStore(), // No persistence in dev
546583
authTimeout: 60000,
547-
onRequest: (req) => console.log("[DEV]", req.url),
584+
onRequest: (req: Request) => console.log("[DEV]", req.url),
548585
},
549586
staging: {
550587
port: 3001,
588+
launch: open,
551589
store: fileStore("~/.mcp/staging-tokens.json"),
552590
storeKey: "staging",
553591
authTimeout: 120000,
554592
},
555593
prod: {
556594
port: 3002,
595+
launch: open,
557596
store: fileStore("~/.mcp/prod-tokens.json"),
558597
storeKey: "production",
559598
authTimeout: 300000,
@@ -576,9 +615,11 @@ const authProvider = createAuthProvider(
576615
While automatic refresh is pending full implementation, you can handle expired tokens:
577616

578617
```typescript
618+
import open from "open";
579619
import { browserAuth, fileStore } from "oauth-callback/mcp";
580620

581621
const authProvider = browserAuth({
622+
launch: open,
582623
store: fileStore(),
583624
scope: "offline_access", // Request refresh token
584625
});
@@ -701,8 +742,11 @@ describe("OAuth Flow Integration", () => {
701742
::: details Port Already in Use
702743

703744
```typescript
745+
import open from "open";
746+
704747
// Use a different port
705748
const authProvider = browserAuth({
749+
launch: open,
706750
port: 8080, // Try alternative port
707751
});
708752
```
@@ -712,8 +756,11 @@ const authProvider = browserAuth({
712756
::: details Tokens Not Persisting
713757

714758
```typescript
759+
import open from "open";
760+
715761
// Ensure you're using file store, not in-memory
716762
const authProvider = browserAuth({
763+
launch: open,
717764
store: fileStore(), // ✅ Persistent
718765
// store: inMemoryStore() // ❌ Lost on restart
719766
});
@@ -725,8 +772,11 @@ const authProvider = browserAuth({
725772
Some servers may not support Dynamic Client Registration:
726773

727774
```typescript
775+
import open from "open";
776+
728777
// Fallback to pre-registered credentials
729778
const authProvider = browserAuth({
779+
launch: open,
730780
clientId: "your-client-id",
731781
clientSecret: "your-client-secret",
732782
});

0 commit comments

Comments
 (0)