@opensaas/stack-auth 0.1.2 → 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.
@@ -1,4 +1,4 @@
1
1
 
2
- > @opensaas/stack-auth@0.1.2 build /home/runner/work/stack/stack/packages/auth
2
+ > @opensaas/stack-auth@0.1.4 build /home/runner/work/stack/stack/packages/auth
3
3
  > tsc
4
4
 
package/CHANGELOG.md CHANGED
@@ -1,5 +1,53 @@
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
+
45
+ ## 0.1.3
46
+
47
+ ### Patch Changes
48
+
49
+ - @opensaas/stack-core@0.1.3
50
+
3
51
  ## 0.1.2
4
52
 
5
53
  ### 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 @opensaas/stack-mcp
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.2",
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.2"
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.2"
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
+ }
@@ -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'