@opensaas/stack-auth 0.1.3 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +42 -0
- package/CLAUDE.md +4 -1
- package/dist/mcp/better-auth.d.ts +101 -0
- package/dist/mcp/better-auth.d.ts.map +1 -0
- package/dist/mcp/better-auth.js +142 -0
- package/dist/mcp/better-auth.js.map +1 -0
- package/dist/mcp/index.d.ts +7 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +6 -0
- package/dist/mcp/index.js.map +1 -0
- package/package.json +7 -3
- package/src/mcp/better-auth.ts +166 -0
- package/src/mcp/index.ts +16 -0
- package/tests/mcp-adapter.test.ts +352 -0
- package/tsconfig.tsbuildinfo +1 -1
package/.turbo/turbo-build.log
CHANGED
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,47 @@
|
|
|
1
1
|
# @opensaas/stack-auth
|
|
2
2
|
|
|
3
|
+
## 0.1.4
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- d013859: **BREAKING CHANGE**: Migrate MCP functionality into core and auth packages
|
|
8
|
+
|
|
9
|
+
The `@opensaas/stack-mcp` package has been deprecated and its functionality has been split into:
|
|
10
|
+
- `@opensaas/stack-core/mcp` - Auth-agnostic MCP runtime and handlers
|
|
11
|
+
- `@opensaas/stack-auth/mcp` - Better Auth OAuth adapter
|
|
12
|
+
|
|
13
|
+
**Migration required:**
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
// Before
|
|
17
|
+
import { createMcpHandlers } from '@opensaas/stack-mcp'
|
|
18
|
+
const { GET, POST, DELETE } = createMcpHandlers({ config, auth, getContext })
|
|
19
|
+
|
|
20
|
+
// After
|
|
21
|
+
import { createMcpHandlers } from '@opensaas/stack-core/mcp'
|
|
22
|
+
import { createBetterAuthMcpAdapter } from '@opensaas/stack-auth/mcp'
|
|
23
|
+
const { GET, POST, DELETE } = createMcpHandlers({
|
|
24
|
+
config,
|
|
25
|
+
getSession: createBetterAuthMcpAdapter(auth),
|
|
26
|
+
getContext,
|
|
27
|
+
})
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**Why this change?**
|
|
31
|
+
- Reduces package count in the monorepo
|
|
32
|
+
- Core package handles auth-agnostic MCP protocol
|
|
33
|
+
- Auth package provides Better Auth specific adapter
|
|
34
|
+
- Better-auth is no longer a dependency of core
|
|
35
|
+
- Enables support for custom auth providers beyond Better Auth
|
|
36
|
+
|
|
37
|
+
**New features:**
|
|
38
|
+
- `McpSessionProvider` type for custom auth integration
|
|
39
|
+
- More generic `McpAuthConfig` type supporting custom auth providers
|
|
40
|
+
- Core MCP functionality available without auth dependencies
|
|
41
|
+
|
|
42
|
+
- Updated dependencies [d013859]
|
|
43
|
+
- @opensaas/stack-core@0.1.4
|
|
44
|
+
|
|
3
45
|
## 0.1.3
|
|
4
46
|
|
|
5
47
|
### Patch Changes
|
package/CLAUDE.md
CHANGED
|
@@ -110,9 +110,12 @@ authConfig({
|
|
|
110
110
|
- Session flows through context to all access control functions
|
|
111
111
|
- Generator creates Prisma schema with auth tables
|
|
112
112
|
|
|
113
|
-
### With
|
|
113
|
+
### With MCP (Model Context Protocol)
|
|
114
114
|
|
|
115
|
+
- Auth provides Better Auth MCP adapter via `@opensaas/stack-auth/mcp`
|
|
115
116
|
- MCP plugin enables OAuth for AI assistants
|
|
117
|
+
- `createBetterAuthMcpAdapter()` converts Better Auth instance to session provider
|
|
118
|
+
- Works with core MCP runtime from `@opensaas/stack-core/mcp`
|
|
116
119
|
- Requires `rawOpensaasContext` from `.opensaas/context.ts`:
|
|
117
120
|
|
|
118
121
|
```typescript
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Better Auth integration for MCP OAuth authentication
|
|
3
|
+
* Provides session handling and authentication utilities
|
|
4
|
+
*/
|
|
5
|
+
import type { McpSession, McpSessionProvider } from '@opensaas/stack-core/mcp';
|
|
6
|
+
/**
|
|
7
|
+
* Better Auth instance type (flexible)
|
|
8
|
+
* Uses minimal typing to avoid tight coupling with better-auth package
|
|
9
|
+
*/
|
|
10
|
+
export type BetterAuthInstance = {
|
|
11
|
+
api: any;
|
|
12
|
+
[key: string]: any;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Create Better Auth MCP session adapter
|
|
16
|
+
* Converts Better Auth instance into MCP session provider
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```typescript
|
|
20
|
+
* import { createMcpHandlers } from '@opensaas/stack-core/mcp'
|
|
21
|
+
* import { createBetterAuthMcpAdapter } from '@opensaas/stack-auth/mcp'
|
|
22
|
+
* import config from '@/opensaas.config'
|
|
23
|
+
* import { auth } from '@/lib/auth'
|
|
24
|
+
* import { getContext } from '@/.opensaas/context'
|
|
25
|
+
*
|
|
26
|
+
* const { GET, POST, DELETE } = createMcpHandlers({
|
|
27
|
+
* config,
|
|
28
|
+
* getSession: createBetterAuthMcpAdapter(auth),
|
|
29
|
+
* getContext
|
|
30
|
+
* })
|
|
31
|
+
*
|
|
32
|
+
* export { GET, POST, DELETE }
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
export declare function createBetterAuthMcpAdapter(auth: BetterAuthInstance): McpSessionProvider;
|
|
36
|
+
/**
|
|
37
|
+
* Create MCP request handler with Better Auth OAuth authentication
|
|
38
|
+
* Wraps an MCP handler to automatically authenticate and inject session
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```typescript
|
|
42
|
+
* import { withMcpAuth } from '@opensaas/stack-auth/mcp'
|
|
43
|
+
* import { auth } from '@/lib/auth'
|
|
44
|
+
* import { createMcpServer } from '@/.opensaas/mcp/server'
|
|
45
|
+
*
|
|
46
|
+
* const handler = withMcpAuth(auth, async (req, session) => {
|
|
47
|
+
* const server = createMcpServer(session)
|
|
48
|
+
* return server.handleRequest(req)
|
|
49
|
+
* })
|
|
50
|
+
*
|
|
51
|
+
* export { handler as GET, handler as POST, handler as DELETE }
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
export declare function withMcpAuth(auth: BetterAuthInstance, handler: (req: Request, session: McpSession) => Promise<Response> | Response): (req: Request) => Promise<Response>;
|
|
55
|
+
/**
|
|
56
|
+
* Convert MCP session to OpenSaaS context session
|
|
57
|
+
* Allows using MCP session with OpenSaaS access control
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* ```typescript
|
|
61
|
+
* const mcpSession = await auth.api.getMcpSession({ headers: req.headers })
|
|
62
|
+
* const context = getContext(mcpSessionToContextSession(mcpSession))
|
|
63
|
+
* const posts = await context.db.post.findMany()
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
export declare function mcpSessionToContextSession(mcpSession: McpSession): {
|
|
67
|
+
userId: string;
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* Validate MCP session scopes
|
|
71
|
+
* Check if session has required OAuth scopes
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* ```typescript
|
|
75
|
+
* if (!hasScopes(session, ['read:posts', 'write:posts'])) {
|
|
76
|
+
* return new Response('Insufficient scopes', { status: 403 })
|
|
77
|
+
* }
|
|
78
|
+
* ```
|
|
79
|
+
*/
|
|
80
|
+
export declare function hasScopes(session: McpSession, requiredScopes: string[]): boolean;
|
|
81
|
+
/**
|
|
82
|
+
* Check if MCP session is expired
|
|
83
|
+
*/
|
|
84
|
+
export declare function isSessionExpired(session: McpSession): boolean;
|
|
85
|
+
/**
|
|
86
|
+
* Create OAuth discovery metadata handler
|
|
87
|
+
* Exposes OAuth authorization server metadata for MCP clients
|
|
88
|
+
*
|
|
89
|
+
* This should be placed at `/.well-known/oauth-authorization-server/route.ts`
|
|
90
|
+
* Better Auth already handles `/api/auth/.well-known/oauth-authorization-server`
|
|
91
|
+
* but some clients may fail to parse WWW-Authenticate headers
|
|
92
|
+
*/
|
|
93
|
+
export declare function createOAuthDiscoveryHandler(_auth: BetterAuthInstance): (req: Request) => Promise<Response>;
|
|
94
|
+
/**
|
|
95
|
+
* Create OAuth protected resource metadata handler
|
|
96
|
+
* Exposes OAuth protected resource metadata for MCP clients
|
|
97
|
+
*
|
|
98
|
+
* This should be placed at `/.well-known/oauth-protected-resource/route.ts`
|
|
99
|
+
*/
|
|
100
|
+
export declare function createOAuthProtectedResourceHandler(_auth: BetterAuthInstance): (req: Request) => Promise<Response>;
|
|
101
|
+
//# sourceMappingURL=better-auth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"better-auth.d.ts","sourceRoot":"","sources":["../../src/mcp/better-auth.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAA;AAE9E;;;GAGG;AACH,MAAM,MAAM,kBAAkB,GAAG;IAE/B,GAAG,EAAE,GAAG,CAAA;IAER,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;CACnB,CAAA;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,kBAAkB,GAAG,kBAAkB,CAIvF;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,WAAW,CACzB,IAAI,EAAE,kBAAkB,EACxB,OAAO,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,UAAU,KAAK,OAAO,CAAC,QAAQ,CAAC,GAAG,QAAQ,GAC3E,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAoBrC;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,0BAA0B,CAAC,UAAU,EAAE,UAAU,GAAG;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAIrF;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,EAAE,GAAG,OAAO,CAGhF;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAG7D;AAED;;;;;;;GAOG;AACH,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,kBAAkB,IACrD,KAAK,OAAO,uBAS3B;AAED;;;;;GAKG;AACH,wBAAgB,mCAAmC,CAAC,KAAK,EAAE,kBAAkB,IAC7D,KAAK,OAAO,uBAS3B"}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Better Auth integration for MCP OAuth authentication
|
|
3
|
+
* Provides session handling and authentication utilities
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Create Better Auth MCP session adapter
|
|
7
|
+
* Converts Better Auth instance into MCP session provider
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* import { createMcpHandlers } from '@opensaas/stack-core/mcp'
|
|
12
|
+
* import { createBetterAuthMcpAdapter } from '@opensaas/stack-auth/mcp'
|
|
13
|
+
* import config from '@/opensaas.config'
|
|
14
|
+
* import { auth } from '@/lib/auth'
|
|
15
|
+
* import { getContext } from '@/.opensaas/context'
|
|
16
|
+
*
|
|
17
|
+
* const { GET, POST, DELETE } = createMcpHandlers({
|
|
18
|
+
* config,
|
|
19
|
+
* getSession: createBetterAuthMcpAdapter(auth),
|
|
20
|
+
* getContext
|
|
21
|
+
* })
|
|
22
|
+
*
|
|
23
|
+
* export { GET, POST, DELETE }
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export function createBetterAuthMcpAdapter(auth) {
|
|
27
|
+
return async (headers) => {
|
|
28
|
+
return await auth.api.getMcpSession({ headers });
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Create MCP request handler with Better Auth OAuth authentication
|
|
33
|
+
* Wraps an MCP handler to automatically authenticate and inject session
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```typescript
|
|
37
|
+
* import { withMcpAuth } from '@opensaas/stack-auth/mcp'
|
|
38
|
+
* import { auth } from '@/lib/auth'
|
|
39
|
+
* import { createMcpServer } from '@/.opensaas/mcp/server'
|
|
40
|
+
*
|
|
41
|
+
* const handler = withMcpAuth(auth, async (req, session) => {
|
|
42
|
+
* const server = createMcpServer(session)
|
|
43
|
+
* return server.handleRequest(req)
|
|
44
|
+
* })
|
|
45
|
+
*
|
|
46
|
+
* export { handler as GET, handler as POST, handler as DELETE }
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
export function withMcpAuth(auth, handler) {
|
|
50
|
+
return async (req) => {
|
|
51
|
+
// Extract MCP session from Better Auth
|
|
52
|
+
const session = await auth.api.getMcpSession({
|
|
53
|
+
headers: req.headers,
|
|
54
|
+
});
|
|
55
|
+
if (!session) {
|
|
56
|
+
// Return 401 with WWW-Authenticate header for OAuth clients
|
|
57
|
+
return new Response(null, {
|
|
58
|
+
status: 401,
|
|
59
|
+
headers: {
|
|
60
|
+
'WWW-Authenticate': 'Bearer realm="MCP", error="invalid_token"',
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
// Call handler with authenticated session
|
|
65
|
+
return handler(req, session);
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Convert MCP session to OpenSaaS context session
|
|
70
|
+
* Allows using MCP session with OpenSaaS access control
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* ```typescript
|
|
74
|
+
* const mcpSession = await auth.api.getMcpSession({ headers: req.headers })
|
|
75
|
+
* const context = getContext(mcpSessionToContextSession(mcpSession))
|
|
76
|
+
* const posts = await context.db.post.findMany()
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
export function mcpSessionToContextSession(mcpSession) {
|
|
80
|
+
return {
|
|
81
|
+
userId: mcpSession.userId,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Validate MCP session scopes
|
|
86
|
+
* Check if session has required OAuth scopes
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* ```typescript
|
|
90
|
+
* if (!hasScopes(session, ['read:posts', 'write:posts'])) {
|
|
91
|
+
* return new Response('Insufficient scopes', { status: 403 })
|
|
92
|
+
* }
|
|
93
|
+
* ```
|
|
94
|
+
*/
|
|
95
|
+
export function hasScopes(session, requiredScopes) {
|
|
96
|
+
if (!session.scopes)
|
|
97
|
+
return false;
|
|
98
|
+
return requiredScopes.every((scope) => session.scopes.includes(scope));
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Check if MCP session is expired
|
|
102
|
+
*/
|
|
103
|
+
export function isSessionExpired(session) {
|
|
104
|
+
if (!session.expiresAt)
|
|
105
|
+
return false;
|
|
106
|
+
return new Date() > session.expiresAt;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Create OAuth discovery metadata handler
|
|
110
|
+
* Exposes OAuth authorization server metadata for MCP clients
|
|
111
|
+
*
|
|
112
|
+
* This should be placed at `/.well-known/oauth-authorization-server/route.ts`
|
|
113
|
+
* Better Auth already handles `/api/auth/.well-known/oauth-authorization-server`
|
|
114
|
+
* but some clients may fail to parse WWW-Authenticate headers
|
|
115
|
+
*/
|
|
116
|
+
export function createOAuthDiscoveryHandler(_auth) {
|
|
117
|
+
return async (req) => {
|
|
118
|
+
// Delegate to Better Auth's built-in handler
|
|
119
|
+
const authPath = '/api/auth/.well-known/oauth-authorization-server';
|
|
120
|
+
const authUrl = new URL(authPath, req.url);
|
|
121
|
+
return fetch(authUrl.toString(), {
|
|
122
|
+
headers: req.headers,
|
|
123
|
+
});
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Create OAuth protected resource metadata handler
|
|
128
|
+
* Exposes OAuth protected resource metadata for MCP clients
|
|
129
|
+
*
|
|
130
|
+
* This should be placed at `/.well-known/oauth-protected-resource/route.ts`
|
|
131
|
+
*/
|
|
132
|
+
export function createOAuthProtectedResourceHandler(_auth) {
|
|
133
|
+
return async (req) => {
|
|
134
|
+
// Delegate to Better Auth's built-in handler
|
|
135
|
+
const authPath = '/api/auth/.well-known/oauth-protected-resource';
|
|
136
|
+
const authUrl = new URL(authPath, req.url);
|
|
137
|
+
return fetch(authUrl.toString(), {
|
|
138
|
+
headers: req.headers,
|
|
139
|
+
});
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
//# sourceMappingURL=better-auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"better-auth.js","sourceRoot":"","sources":["../../src/mcp/better-auth.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAeH;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,UAAU,0BAA0B,CAAC,IAAwB;IACjE,OAAO,KAAK,EAAE,OAAgB,EAA8B,EAAE;QAC5D,OAAO,MAAM,IAAI,CAAC,GAAG,CAAC,aAAa,CAAC,EAAE,OAAO,EAAE,CAAC,CAAA;IAClD,CAAC,CAAA;AACH,CAAC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,WAAW,CACzB,IAAwB,EACxB,OAA4E;IAE5E,OAAO,KAAK,EAAE,GAAY,EAAE,EAAE;QAC5B,uCAAuC;QACvC,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,aAAa,CAAC;YAC3C,OAAO,EAAE,GAAG,CAAC,OAAO;SACrB,CAAC,CAAA;QAEF,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,4DAA4D;YAC5D,OAAO,IAAI,QAAQ,CAAC,IAAI,EAAE;gBACxB,MAAM,EAAE,GAAG;gBACX,OAAO,EAAE;oBACP,kBAAkB,EAAE,2CAA2C;iBAChE;aACF,CAAC,CAAA;QACJ,CAAC;QAED,0CAA0C;QAC1C,OAAO,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,CAAA;IAC9B,CAAC,CAAA;AACH,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,0BAA0B,CAAC,UAAsB;IAC/D,OAAO;QACL,MAAM,EAAE,UAAU,CAAC,MAAM;KAC1B,CAAA;AACH,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,SAAS,CAAC,OAAmB,EAAE,cAAwB;IACrE,IAAI,CAAC,OAAO,CAAC,MAAM;QAAE,OAAO,KAAK,CAAA;IACjC,OAAO,cAAc,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,CAAC,MAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAA;AACzE,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAAC,OAAmB;IAClD,IAAI,CAAC,OAAO,CAAC,SAAS;QAAE,OAAO,KAAK,CAAA;IACpC,OAAO,IAAI,IAAI,EAAE,GAAG,OAAO,CAAC,SAAS,CAAA;AACvC,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,2BAA2B,CAAC,KAAyB;IACnE,OAAO,KAAK,EAAE,GAAY,EAAE,EAAE;QAC5B,6CAA6C;QAC7C,MAAM,QAAQ,GAAG,kDAAkD,CAAA;QACnE,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,QAAQ,EAAE,GAAG,CAAC,GAAG,CAAC,CAAA;QAE1C,OAAO,KAAK,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE;YAC/B,OAAO,EAAE,GAAG,CAAC,OAAO;SACrB,CAAC,CAAA;IACJ,CAAC,CAAA;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,mCAAmC,CAAC,KAAyB;IAC3E,OAAO,KAAK,EAAE,GAAY,EAAE,EAAE;QAC5B,6CAA6C;QAC7C,MAAM,QAAQ,GAAG,gDAAgD,CAAA;QACjE,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,QAAQ,EAAE,GAAG,CAAC,GAAG,CAAC,CAAA;QAE1C,OAAO,KAAK,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE;YAC/B,OAAO,EAAE,GAAG,CAAC,OAAO;SACrB,CAAC,CAAA;IACJ,CAAC,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Better Auth MCP integration
|
|
3
|
+
* OAuth authentication adapter for MCP servers
|
|
4
|
+
*/
|
|
5
|
+
export { createBetterAuthMcpAdapter, withMcpAuth, mcpSessionToContextSession, hasScopes, isSessionExpired, createOAuthDiscoveryHandler, createOAuthProtectedResourceHandler, } from './better-auth.js';
|
|
6
|
+
export type { BetterAuthInstance } from './better-auth.js';
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/mcp/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EACL,0BAA0B,EAC1B,WAAW,EACX,0BAA0B,EAC1B,SAAS,EACT,gBAAgB,EAChB,2BAA2B,EAC3B,mCAAmC,GACpC,MAAM,kBAAkB,CAAA;AAEzB,YAAY,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Better Auth MCP integration
|
|
3
|
+
* OAuth authentication adapter for MCP servers
|
|
4
|
+
*/
|
|
5
|
+
export { createBetterAuthMcpAdapter, withMcpAuth, mcpSessionToContextSession, hasScopes, isSessionExpired, createOAuthDiscoveryHandler, createOAuthProtectedResourceHandler, } from './better-auth.js';
|
|
6
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/mcp/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EACL,0BAA0B,EAC1B,WAAW,EACX,0BAA0B,EAC1B,SAAS,EACT,gBAAgB,EAChB,2BAA2B,EAC3B,mCAAmC,GACpC,MAAM,kBAAkB,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opensaas/stack-auth",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Better-auth integration for OpenSaas Stack",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -25,6 +25,10 @@
|
|
|
25
25
|
"./plugins": {
|
|
26
26
|
"types": "./dist/plugins/index.d.ts",
|
|
27
27
|
"default": "./dist/plugins/index.js"
|
|
28
|
+
},
|
|
29
|
+
"./mcp": {
|
|
30
|
+
"types": "./dist/mcp/index.d.ts",
|
|
31
|
+
"default": "./dist/mcp/index.js"
|
|
28
32
|
}
|
|
29
33
|
},
|
|
30
34
|
"keywords": [
|
|
@@ -39,7 +43,7 @@
|
|
|
39
43
|
"better-auth": "^1.3.29",
|
|
40
44
|
"react": "^18.0.0 || ^19.0.0",
|
|
41
45
|
"next": "^15.0.0 || ^16.0.0",
|
|
42
|
-
"@opensaas/stack-core": "0.1.
|
|
46
|
+
"@opensaas/stack-core": "0.1.4"
|
|
43
47
|
},
|
|
44
48
|
"dependencies": {},
|
|
45
49
|
"devDependencies": {
|
|
@@ -52,7 +56,7 @@
|
|
|
52
56
|
"react": "^19.2.0",
|
|
53
57
|
"typescript": "^5.9.3",
|
|
54
58
|
"vitest": "^4.0.0",
|
|
55
|
-
"@opensaas/stack-core": "0.1.
|
|
59
|
+
"@opensaas/stack-core": "0.1.4"
|
|
56
60
|
},
|
|
57
61
|
"scripts": {
|
|
58
62
|
"build": "tsc",
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Better Auth integration for MCP OAuth authentication
|
|
3
|
+
* Provides session handling and authentication utilities
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { McpSession, McpSessionProvider } from '@opensaas/stack-core/mcp'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Better Auth instance type (flexible)
|
|
10
|
+
* Uses minimal typing to avoid tight coupling with better-auth package
|
|
11
|
+
*/
|
|
12
|
+
export type BetterAuthInstance = {
|
|
13
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Better Auth API types vary by plugins, must use any
|
|
14
|
+
api: any
|
|
15
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Allows additional Better Auth instance properties
|
|
16
|
+
[key: string]: any
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Create Better Auth MCP session adapter
|
|
21
|
+
* Converts Better Auth instance into MCP session provider
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```typescript
|
|
25
|
+
* import { createMcpHandlers } from '@opensaas/stack-core/mcp'
|
|
26
|
+
* import { createBetterAuthMcpAdapter } from '@opensaas/stack-auth/mcp'
|
|
27
|
+
* import config from '@/opensaas.config'
|
|
28
|
+
* import { auth } from '@/lib/auth'
|
|
29
|
+
* import { getContext } from '@/.opensaas/context'
|
|
30
|
+
*
|
|
31
|
+
* const { GET, POST, DELETE } = createMcpHandlers({
|
|
32
|
+
* config,
|
|
33
|
+
* getSession: createBetterAuthMcpAdapter(auth),
|
|
34
|
+
* getContext
|
|
35
|
+
* })
|
|
36
|
+
*
|
|
37
|
+
* export { GET, POST, DELETE }
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export function createBetterAuthMcpAdapter(auth: BetterAuthInstance): McpSessionProvider {
|
|
41
|
+
return async (headers: Headers): Promise<McpSession | null> => {
|
|
42
|
+
return await auth.api.getMcpSession({ headers })
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Create MCP request handler with Better Auth OAuth authentication
|
|
48
|
+
* Wraps an MCP handler to automatically authenticate and inject session
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```typescript
|
|
52
|
+
* import { withMcpAuth } from '@opensaas/stack-auth/mcp'
|
|
53
|
+
* import { auth } from '@/lib/auth'
|
|
54
|
+
* import { createMcpServer } from '@/.opensaas/mcp/server'
|
|
55
|
+
*
|
|
56
|
+
* const handler = withMcpAuth(auth, async (req, session) => {
|
|
57
|
+
* const server = createMcpServer(session)
|
|
58
|
+
* return server.handleRequest(req)
|
|
59
|
+
* })
|
|
60
|
+
*
|
|
61
|
+
* export { handler as GET, handler as POST, handler as DELETE }
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
export function withMcpAuth(
|
|
65
|
+
auth: BetterAuthInstance,
|
|
66
|
+
handler: (req: Request, session: McpSession) => Promise<Response> | Response,
|
|
67
|
+
): (req: Request) => Promise<Response> {
|
|
68
|
+
return async (req: Request) => {
|
|
69
|
+
// Extract MCP session from Better Auth
|
|
70
|
+
const session = await auth.api.getMcpSession({
|
|
71
|
+
headers: req.headers,
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
if (!session) {
|
|
75
|
+
// Return 401 with WWW-Authenticate header for OAuth clients
|
|
76
|
+
return new Response(null, {
|
|
77
|
+
status: 401,
|
|
78
|
+
headers: {
|
|
79
|
+
'WWW-Authenticate': 'Bearer realm="MCP", error="invalid_token"',
|
|
80
|
+
},
|
|
81
|
+
})
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Call handler with authenticated session
|
|
85
|
+
return handler(req, session)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Convert MCP session to OpenSaaS context session
|
|
91
|
+
* Allows using MCP session with OpenSaaS access control
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* ```typescript
|
|
95
|
+
* const mcpSession = await auth.api.getMcpSession({ headers: req.headers })
|
|
96
|
+
* const context = getContext(mcpSessionToContextSession(mcpSession))
|
|
97
|
+
* const posts = await context.db.post.findMany()
|
|
98
|
+
* ```
|
|
99
|
+
*/
|
|
100
|
+
export function mcpSessionToContextSession(mcpSession: McpSession): { userId: string } {
|
|
101
|
+
return {
|
|
102
|
+
userId: mcpSession.userId,
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Validate MCP session scopes
|
|
108
|
+
* Check if session has required OAuth scopes
|
|
109
|
+
*
|
|
110
|
+
* @example
|
|
111
|
+
* ```typescript
|
|
112
|
+
* if (!hasScopes(session, ['read:posts', 'write:posts'])) {
|
|
113
|
+
* return new Response('Insufficient scopes', { status: 403 })
|
|
114
|
+
* }
|
|
115
|
+
* ```
|
|
116
|
+
*/
|
|
117
|
+
export function hasScopes(session: McpSession, requiredScopes: string[]): boolean {
|
|
118
|
+
if (!session.scopes) return false
|
|
119
|
+
return requiredScopes.every((scope) => session.scopes!.includes(scope))
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Check if MCP session is expired
|
|
124
|
+
*/
|
|
125
|
+
export function isSessionExpired(session: McpSession): boolean {
|
|
126
|
+
if (!session.expiresAt) return false
|
|
127
|
+
return new Date() > session.expiresAt
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Create OAuth discovery metadata handler
|
|
132
|
+
* Exposes OAuth authorization server metadata for MCP clients
|
|
133
|
+
*
|
|
134
|
+
* This should be placed at `/.well-known/oauth-authorization-server/route.ts`
|
|
135
|
+
* Better Auth already handles `/api/auth/.well-known/oauth-authorization-server`
|
|
136
|
+
* but some clients may fail to parse WWW-Authenticate headers
|
|
137
|
+
*/
|
|
138
|
+
export function createOAuthDiscoveryHandler(_auth: BetterAuthInstance) {
|
|
139
|
+
return async (req: Request) => {
|
|
140
|
+
// Delegate to Better Auth's built-in handler
|
|
141
|
+
const authPath = '/api/auth/.well-known/oauth-authorization-server'
|
|
142
|
+
const authUrl = new URL(authPath, req.url)
|
|
143
|
+
|
|
144
|
+
return fetch(authUrl.toString(), {
|
|
145
|
+
headers: req.headers,
|
|
146
|
+
})
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Create OAuth protected resource metadata handler
|
|
152
|
+
* Exposes OAuth protected resource metadata for MCP clients
|
|
153
|
+
*
|
|
154
|
+
* This should be placed at `/.well-known/oauth-protected-resource/route.ts`
|
|
155
|
+
*/
|
|
156
|
+
export function createOAuthProtectedResourceHandler(_auth: BetterAuthInstance) {
|
|
157
|
+
return async (req: Request) => {
|
|
158
|
+
// Delegate to Better Auth's built-in handler
|
|
159
|
+
const authPath = '/api/auth/.well-known/oauth-protected-resource'
|
|
160
|
+
const authUrl = new URL(authPath, req.url)
|
|
161
|
+
|
|
162
|
+
return fetch(authUrl.toString(), {
|
|
163
|
+
headers: req.headers,
|
|
164
|
+
})
|
|
165
|
+
}
|
|
166
|
+
}
|
package/src/mcp/index.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Better Auth MCP integration
|
|
3
|
+
* OAuth authentication adapter for MCP servers
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export {
|
|
7
|
+
createBetterAuthMcpAdapter,
|
|
8
|
+
withMcpAuth,
|
|
9
|
+
mcpSessionToContextSession,
|
|
10
|
+
hasScopes,
|
|
11
|
+
isSessionExpired,
|
|
12
|
+
createOAuthDiscoveryHandler,
|
|
13
|
+
createOAuthProtectedResourceHandler,
|
|
14
|
+
} from './better-auth.js'
|
|
15
|
+
|
|
16
|
+
export type { BetterAuthInstance } from './better-auth.js'
|