create-claude-webapp 1.0.0 → 1.0.2
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/.claude/agents/acceptance-test-generator.md +256 -0
- package/.claude/agents/auth-flow-designer.md +93 -0
- package/.claude/agents/code-reviewer.md +193 -0
- package/.claude/agents/code-verifier.md +194 -0
- package/.claude/agents/deployment-executor.md +90 -0
- package/.claude/agents/design-sync.md +226 -0
- package/.claude/agents/document-reviewer.md +304 -0
- package/.claude/agents/environment-validator.md +100 -0
- package/.claude/agents/integration-test-reviewer.md +196 -0
- package/.claude/agents/investigator.md +162 -0
- package/.claude/agents/prd-creator.md +220 -0
- package/.claude/agents/quality-fixer-frontend.md +323 -0
- package/.claude/agents/quality-fixer.md +280 -0
- package/.claude/agents/requirement-analyzer.md +149 -0
- package/.claude/agents/rls-policy-designer.md +86 -0
- package/.claude/agents/rule-advisor.md +123 -0
- package/.claude/agents/scope-discoverer.md +231 -0
- package/.claude/agents/solver.md +173 -0
- package/.claude/agents/supabase-migration-generator.md +85 -0
- package/.claude/agents/task-decomposer.md +246 -0
- package/.claude/agents/task-executor-frontend.md +264 -0
- package/.claude/agents/task-executor.md +261 -0
- package/.claude/agents/technical-designer-frontend.md +444 -0
- package/.claude/agents/technical-designer.md +370 -0
- package/.claude/agents/verifier.md +193 -0
- package/.claude/agents/work-planner.md +211 -0
- package/.claude/commands/add-integration-tests.md +116 -0
- package/.claude/commands/build.md +77 -0
- package/.claude/commands/db-migrate.md +96 -0
- package/.claude/commands/deploy.md +95 -0
- package/.claude/commands/design.md +75 -0
- package/.claude/commands/diagnose.md +202 -0
- package/.claude/commands/front-build.md +116 -0
- package/.claude/commands/front-design.md +61 -0
- package/.claude/commands/front-plan.md +53 -0
- package/.claude/commands/front-reverse-design.md +183 -0
- package/.claude/commands/front-review.md +89 -0
- package/.claude/commands/implement.md +80 -0
- package/.claude/commands/local-dev.md +94 -0
- package/.claude/commands/plan.md +61 -0
- package/.claude/commands/project-inject.md +76 -0
- package/.claude/commands/refine-skill.md +207 -0
- package/.claude/commands/reverse-engineer.md +301 -0
- package/.claude/commands/review.md +88 -0
- package/.claude/commands/setup-auth.md +68 -0
- package/.claude/commands/setup-supabase.md +66 -0
- package/.claude/commands/setup-vercel.md +71 -0
- package/.claude/commands/sync-skills.md +116 -0
- package/.claude/commands/task.md +13 -0
- package/.claude/skills/coding-standards/SKILL.md +246 -0
- package/.claude/skills/documentation-criteria/SKILL.md +184 -0
- package/.claude/skills/documentation-criteria/references/adr-template.md +64 -0
- package/.claude/skills/documentation-criteria/references/design-template.md +263 -0
- package/.claude/skills/documentation-criteria/references/plan-template.md +130 -0
- package/.claude/skills/documentation-criteria/references/prd-template.md +109 -0
- package/.claude/skills/documentation-criteria/references/task-template.md +38 -0
- package/.claude/skills/frontend/technical-spec/SKILL.md +147 -0
- package/.claude/skills/frontend/typescript-rules/SKILL.md +136 -0
- package/.claude/skills/frontend/typescript-testing/SKILL.md +129 -0
- package/.claude/skills/fullstack-integration/SKILL.md +466 -0
- package/.claude/skills/implementation-approach/SKILL.md +141 -0
- package/.claude/skills/integration-e2e-testing/SKILL.md +146 -0
- package/.claude/skills/interview/SKILL.md +345 -0
- package/.claude/skills/project-context/SKILL.md +53 -0
- package/.claude/skills/stack-auth/SKILL.md +519 -0
- package/.claude/skills/subagents-orchestration-guide/SKILL.md +218 -0
- package/.claude/skills/supabase/SKILL.md +289 -0
- package/.claude/skills/supabase-edge-functions/SKILL.md +386 -0
- package/.claude/skills/supabase-local/SKILL.md +328 -0
- package/.claude/skills/supabase-testing/SKILL.md +513 -0
- package/.claude/skills/task-analyzer/SKILL.md +131 -0
- package/.claude/skills/task-analyzer/references/skills-index.yaml +375 -0
- package/.claude/skills/technical-spec/SKILL.md +86 -0
- package/.claude/skills/typescript-rules/SKILL.md +121 -0
- package/.claude/skills/typescript-testing/SKILL.md +155 -0
- package/.claude/skills/vercel-deployment/SKILL.md +355 -0
- package/.claude/skills/vercel-edge/SKILL.md +407 -0
- package/README.md +4 -17
- package/package.json +1 -1
|
@@ -0,0 +1,519 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: stack-auth
|
|
3
|
+
description: Stack-auth integration, MCP server usage, and authentication flows. Use when implementing authentication with Stack-auth.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Stack-auth Integration
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
Stack-auth is a modern authentication solution with an MCP server for AI-assisted development.
|
|
11
|
+
|
|
12
|
+
**MCP Server**: `https://mcp.stack-auth.com/`
|
|
13
|
+
|
|
14
|
+
## Setup
|
|
15
|
+
|
|
16
|
+
### Installation
|
|
17
|
+
```bash
|
|
18
|
+
npm install @stackframe/stack
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### Environment Variables
|
|
22
|
+
```env
|
|
23
|
+
# Public (browser-accessible)
|
|
24
|
+
NEXT_PUBLIC_STACK_PROJECT_ID=your-project-id
|
|
25
|
+
NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY=pk_...
|
|
26
|
+
|
|
27
|
+
# Server-only
|
|
28
|
+
STACK_SECRET_SERVER_KEY=sk_...
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Provider Setup
|
|
32
|
+
```typescript
|
|
33
|
+
// app/layout.tsx
|
|
34
|
+
import { StackProvider, StackTheme } from '@stackframe/stack'
|
|
35
|
+
import { stackServerApp } from '@/lib/stack'
|
|
36
|
+
|
|
37
|
+
export default function RootLayout({
|
|
38
|
+
children,
|
|
39
|
+
}: {
|
|
40
|
+
children: React.ReactNode
|
|
41
|
+
}) {
|
|
42
|
+
return (
|
|
43
|
+
<html lang="en">
|
|
44
|
+
<body>
|
|
45
|
+
<StackProvider app={stackServerApp}>
|
|
46
|
+
<StackTheme>
|
|
47
|
+
{children}
|
|
48
|
+
</StackTheme>
|
|
49
|
+
</StackProvider>
|
|
50
|
+
</body>
|
|
51
|
+
</html>
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Stack Client Configuration
|
|
57
|
+
```typescript
|
|
58
|
+
// lib/stack.ts
|
|
59
|
+
import { StackServerApp } from '@stackframe/stack'
|
|
60
|
+
|
|
61
|
+
export const stackServerApp = new StackServerApp({
|
|
62
|
+
projectId: process.env.NEXT_PUBLIC_STACK_PROJECT_ID!,
|
|
63
|
+
publishableClientKey: process.env.NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY!,
|
|
64
|
+
secretServerKey: process.env.STACK_SECRET_SERVER_KEY!,
|
|
65
|
+
})
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Authentication Flows
|
|
69
|
+
|
|
70
|
+
### Email/Password Authentication
|
|
71
|
+
```typescript
|
|
72
|
+
// components/auth/sign-in-form.tsx
|
|
73
|
+
'use client'
|
|
74
|
+
|
|
75
|
+
import { useStackApp } from '@stackframe/stack'
|
|
76
|
+
import { useState } from 'react'
|
|
77
|
+
|
|
78
|
+
export function SignInForm() {
|
|
79
|
+
const app = useStackApp()
|
|
80
|
+
const [email, setEmail] = useState('')
|
|
81
|
+
const [password, setPassword] = useState('')
|
|
82
|
+
const [error, setError] = useState<string | null>(null)
|
|
83
|
+
|
|
84
|
+
const handleSubmit = async (e: React.FormEvent) => {
|
|
85
|
+
e.preventDefault()
|
|
86
|
+
setError(null)
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
await app.signInWithCredential({
|
|
90
|
+
email,
|
|
91
|
+
password,
|
|
92
|
+
})
|
|
93
|
+
// Redirect handled automatically
|
|
94
|
+
} catch (err) {
|
|
95
|
+
setError(err instanceof Error ? err.message : 'Sign in failed')
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return (
|
|
100
|
+
<form onSubmit={handleSubmit}>
|
|
101
|
+
<input
|
|
102
|
+
type="email"
|
|
103
|
+
value={email}
|
|
104
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
105
|
+
placeholder="Email"
|
|
106
|
+
required
|
|
107
|
+
/>
|
|
108
|
+
<input
|
|
109
|
+
type="password"
|
|
110
|
+
value={password}
|
|
111
|
+
onChange={(e) => setPassword(e.target.value)}
|
|
112
|
+
placeholder="Password"
|
|
113
|
+
required
|
|
114
|
+
/>
|
|
115
|
+
{error && <p className="error">{error}</p>}
|
|
116
|
+
<button type="submit">Sign In</button>
|
|
117
|
+
</form>
|
|
118
|
+
)
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### OAuth Authentication
|
|
123
|
+
```typescript
|
|
124
|
+
// components/auth/oauth-buttons.tsx
|
|
125
|
+
'use client'
|
|
126
|
+
|
|
127
|
+
import { useStackApp } from '@stackframe/stack'
|
|
128
|
+
|
|
129
|
+
export function OAuthButtons() {
|
|
130
|
+
const app = useStackApp()
|
|
131
|
+
|
|
132
|
+
return (
|
|
133
|
+
<div className="oauth-buttons">
|
|
134
|
+
<button onClick={() => app.signInWithOAuth('google')}>
|
|
135
|
+
Continue with Google
|
|
136
|
+
</button>
|
|
137
|
+
<button onClick={() => app.signInWithOAuth('github')}>
|
|
138
|
+
Continue with GitHub
|
|
139
|
+
</button>
|
|
140
|
+
</div>
|
|
141
|
+
)
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Magic Link Authentication
|
|
146
|
+
```typescript
|
|
147
|
+
// components/auth/magic-link-form.tsx
|
|
148
|
+
'use client'
|
|
149
|
+
|
|
150
|
+
import { useStackApp } from '@stackframe/stack'
|
|
151
|
+
import { useState } from 'react'
|
|
152
|
+
|
|
153
|
+
export function MagicLinkForm() {
|
|
154
|
+
const app = useStackApp()
|
|
155
|
+
const [email, setEmail] = useState('')
|
|
156
|
+
const [sent, setSent] = useState(false)
|
|
157
|
+
|
|
158
|
+
const handleSubmit = async (e: React.FormEvent) => {
|
|
159
|
+
e.preventDefault()
|
|
160
|
+
|
|
161
|
+
await app.sendMagicLinkEmail(email)
|
|
162
|
+
setSent(true)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (sent) {
|
|
166
|
+
return <p>Check your email for a sign-in link!</p>
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return (
|
|
170
|
+
<form onSubmit={handleSubmit}>
|
|
171
|
+
<input
|
|
172
|
+
type="email"
|
|
173
|
+
value={email}
|
|
174
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
175
|
+
placeholder="Email"
|
|
176
|
+
required
|
|
177
|
+
/>
|
|
178
|
+
<button type="submit">Send Magic Link</button>
|
|
179
|
+
</form>
|
|
180
|
+
)
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## User Management
|
|
185
|
+
|
|
186
|
+
### Getting Current User
|
|
187
|
+
```typescript
|
|
188
|
+
// Client component
|
|
189
|
+
'use client'
|
|
190
|
+
|
|
191
|
+
import { useUser } from '@stackframe/stack'
|
|
192
|
+
|
|
193
|
+
export function UserProfile() {
|
|
194
|
+
const user = useUser()
|
|
195
|
+
|
|
196
|
+
if (!user) {
|
|
197
|
+
return <p>Not signed in</p>
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return (
|
|
201
|
+
<div>
|
|
202
|
+
<p>Email: {user.primaryEmail}</p>
|
|
203
|
+
<p>Name: {user.displayName}</p>
|
|
204
|
+
<img src={user.profileImageUrl} alt="Profile" />
|
|
205
|
+
</div>
|
|
206
|
+
)
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
```typescript
|
|
211
|
+
// Server component
|
|
212
|
+
import { stackServerApp } from '@/lib/stack'
|
|
213
|
+
|
|
214
|
+
export default async function ProfilePage() {
|
|
215
|
+
const user = await stackServerApp.getUser()
|
|
216
|
+
|
|
217
|
+
if (!user) {
|
|
218
|
+
redirect('/sign-in')
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return (
|
|
222
|
+
<div>
|
|
223
|
+
<h1>Welcome, {user.displayName}</h1>
|
|
224
|
+
</div>
|
|
225
|
+
)
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### Updating User Profile
|
|
230
|
+
```typescript
|
|
231
|
+
'use client'
|
|
232
|
+
|
|
233
|
+
import { useUser } from '@stackframe/stack'
|
|
234
|
+
|
|
235
|
+
export function UpdateProfileForm() {
|
|
236
|
+
const user = useUser({ or: 'redirect' })
|
|
237
|
+
|
|
238
|
+
const handleUpdate = async (formData: FormData) => {
|
|
239
|
+
await user.update({
|
|
240
|
+
displayName: formData.get('name') as string,
|
|
241
|
+
})
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return (
|
|
245
|
+
<form action={handleUpdate}>
|
|
246
|
+
<input
|
|
247
|
+
name="name"
|
|
248
|
+
defaultValue={user.displayName ?? ''}
|
|
249
|
+
placeholder="Display Name"
|
|
250
|
+
/>
|
|
251
|
+
<button type="submit">Update</button>
|
|
252
|
+
</form>
|
|
253
|
+
)
|
|
254
|
+
}
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
## Security Best Practices
|
|
258
|
+
|
|
259
|
+
### Session Configuration
|
|
260
|
+
```typescript
|
|
261
|
+
// lib/stack.ts
|
|
262
|
+
export const stackServerApp = new StackServerApp({
|
|
263
|
+
// ... other config
|
|
264
|
+
tokenStore: 'cookie', // Use httpOnly cookies
|
|
265
|
+
})
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### Protected Routes
|
|
269
|
+
```typescript
|
|
270
|
+
// app/dashboard/page.tsx
|
|
271
|
+
import { stackServerApp } from '@/lib/stack'
|
|
272
|
+
import { redirect } from 'next/navigation'
|
|
273
|
+
|
|
274
|
+
export default async function DashboardPage() {
|
|
275
|
+
const user = await stackServerApp.getUser()
|
|
276
|
+
|
|
277
|
+
if (!user) {
|
|
278
|
+
redirect('/sign-in?redirect=/dashboard')
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return <Dashboard user={user} />
|
|
282
|
+
}
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
### API Route Protection
|
|
286
|
+
```typescript
|
|
287
|
+
// app/api/protected/route.ts
|
|
288
|
+
import { stackServerApp } from '@/lib/stack'
|
|
289
|
+
import { NextResponse } from 'next/server'
|
|
290
|
+
|
|
291
|
+
export async function GET() {
|
|
292
|
+
const user = await stackServerApp.getUser()
|
|
293
|
+
|
|
294
|
+
if (!user) {
|
|
295
|
+
return NextResponse.json(
|
|
296
|
+
{ error: 'Unauthorized' },
|
|
297
|
+
{ status: 401 }
|
|
298
|
+
)
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return NextResponse.json({ user })
|
|
302
|
+
}
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
### Middleware Protection
|
|
306
|
+
```typescript
|
|
307
|
+
// middleware.ts
|
|
308
|
+
import { NextResponse } from 'next/server'
|
|
309
|
+
import type { NextRequest } from 'next/server'
|
|
310
|
+
|
|
311
|
+
const protectedPaths = ['/dashboard', '/settings', '/api/protected']
|
|
312
|
+
|
|
313
|
+
export function middleware(request: NextRequest) {
|
|
314
|
+
const { pathname } = request.nextUrl
|
|
315
|
+
|
|
316
|
+
// Check if path needs protection
|
|
317
|
+
const isProtected = protectedPaths.some(path => pathname.startsWith(path))
|
|
318
|
+
|
|
319
|
+
if (!isProtected) {
|
|
320
|
+
return NextResponse.next()
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Check for Stack-auth session cookie
|
|
324
|
+
const sessionCookie = request.cookies.get('stack-session')
|
|
325
|
+
|
|
326
|
+
if (!sessionCookie) {
|
|
327
|
+
const signInUrl = new URL('/sign-in', request.url)
|
|
328
|
+
signInUrl.searchParams.set('redirect', pathname)
|
|
329
|
+
return NextResponse.redirect(signInUrl)
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
return NextResponse.next()
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
export const config = {
|
|
336
|
+
matcher: ['/dashboard/:path*', '/settings/:path*', '/api/protected/:path*'],
|
|
337
|
+
}
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
## Integration with Supabase
|
|
341
|
+
|
|
342
|
+
### Syncing User ID
|
|
343
|
+
```typescript
|
|
344
|
+
// After Stack-auth sign-in, sync with Supabase
|
|
345
|
+
import { stackServerApp } from '@/lib/stack'
|
|
346
|
+
import { supabaseAdmin } from '@/lib/supabase-admin'
|
|
347
|
+
|
|
348
|
+
export async function syncUserToSupabase() {
|
|
349
|
+
const stackUser = await stackServerApp.getUser()
|
|
350
|
+
|
|
351
|
+
if (!stackUser) return null
|
|
352
|
+
|
|
353
|
+
// Check if user exists in Supabase
|
|
354
|
+
const { data: existingUser } = await supabaseAdmin
|
|
355
|
+
.from('users')
|
|
356
|
+
.select('id')
|
|
357
|
+
.eq('stack_auth_id', stackUser.id)
|
|
358
|
+
.single()
|
|
359
|
+
|
|
360
|
+
if (!existingUser) {
|
|
361
|
+
// Create user in Supabase
|
|
362
|
+
const { data: newUser, error } = await supabaseAdmin
|
|
363
|
+
.from('users')
|
|
364
|
+
.insert({
|
|
365
|
+
stack_auth_id: stackUser.id,
|
|
366
|
+
email: stackUser.primaryEmail,
|
|
367
|
+
name: stackUser.displayName,
|
|
368
|
+
})
|
|
369
|
+
.select()
|
|
370
|
+
.single()
|
|
371
|
+
|
|
372
|
+
if (error) throw error
|
|
373
|
+
return newUser
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
return existingUser
|
|
377
|
+
}
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
### RLS with Stack-auth ID
|
|
381
|
+
```sql
|
|
382
|
+
-- Migration: Add stack_auth_id to users table
|
|
383
|
+
ALTER TABLE users ADD COLUMN stack_auth_id TEXT UNIQUE;
|
|
384
|
+
|
|
385
|
+
-- Create index for lookups
|
|
386
|
+
CREATE INDEX idx_users_stack_auth_id ON users(stack_auth_id);
|
|
387
|
+
|
|
388
|
+
-- RLS policy using custom claim
|
|
389
|
+
CREATE POLICY "Users can access own data"
|
|
390
|
+
ON user_data FOR ALL
|
|
391
|
+
TO authenticated
|
|
392
|
+
USING (
|
|
393
|
+
user_id IN (
|
|
394
|
+
SELECT id FROM users
|
|
395
|
+
WHERE stack_auth_id = current_setting('app.stack_user_id', true)
|
|
396
|
+
)
|
|
397
|
+
);
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
### API Route with Both Auth Systems
|
|
401
|
+
```typescript
|
|
402
|
+
// app/api/user-data/route.ts
|
|
403
|
+
import { stackServerApp } from '@/lib/stack'
|
|
404
|
+
import { createClient } from '@supabase/supabase-js'
|
|
405
|
+
import { NextResponse } from 'next/server'
|
|
406
|
+
|
|
407
|
+
export async function GET() {
|
|
408
|
+
// Get Stack-auth user
|
|
409
|
+
const stackUser = await stackServerApp.getUser()
|
|
410
|
+
|
|
411
|
+
if (!stackUser) {
|
|
412
|
+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// Create Supabase client with user context
|
|
416
|
+
const supabase = createClient(
|
|
417
|
+
process.env.NEXT_PUBLIC_SUPABASE_URL!,
|
|
418
|
+
process.env.SUPABASE_SERVICE_ROLE_KEY!
|
|
419
|
+
)
|
|
420
|
+
|
|
421
|
+
// Set user context for RLS
|
|
422
|
+
await supabase.rpc('set_config', {
|
|
423
|
+
setting: 'app.stack_user_id',
|
|
424
|
+
value: stackUser.id,
|
|
425
|
+
})
|
|
426
|
+
|
|
427
|
+
// Query with RLS
|
|
428
|
+
const { data, error } = await supabase
|
|
429
|
+
.from('user_data')
|
|
430
|
+
.select('*')
|
|
431
|
+
|
|
432
|
+
if (error) {
|
|
433
|
+
return NextResponse.json({ error: error.message }, { status: 500 })
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
return NextResponse.json({ data })
|
|
437
|
+
}
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
## MCP Server Usage
|
|
441
|
+
|
|
442
|
+
### Configuring Claude Code MCP
|
|
443
|
+
Add to your Claude Code MCP configuration:
|
|
444
|
+
|
|
445
|
+
```json
|
|
446
|
+
{
|
|
447
|
+
"mcpServers": {
|
|
448
|
+
"stack-auth": {
|
|
449
|
+
"url": "https://mcp.stack-auth.com/",
|
|
450
|
+
"transport": "sse"
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
### Available MCP Tools
|
|
457
|
+
The Stack-auth MCP server provides tools for:
|
|
458
|
+
- Creating and managing projects
|
|
459
|
+
- Configuring OAuth providers
|
|
460
|
+
- Managing users
|
|
461
|
+
- Generating authentication code
|
|
462
|
+
|
|
463
|
+
## Sign Out
|
|
464
|
+
|
|
465
|
+
```typescript
|
|
466
|
+
'use client'
|
|
467
|
+
|
|
468
|
+
import { useStackApp } from '@stackframe/stack'
|
|
469
|
+
|
|
470
|
+
export function SignOutButton() {
|
|
471
|
+
const app = useStackApp()
|
|
472
|
+
|
|
473
|
+
return (
|
|
474
|
+
<button onClick={() => app.signOut()}>
|
|
475
|
+
Sign Out
|
|
476
|
+
</button>
|
|
477
|
+
)
|
|
478
|
+
}
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
## Error Handling
|
|
482
|
+
|
|
483
|
+
```typescript
|
|
484
|
+
'use client'
|
|
485
|
+
|
|
486
|
+
import { useStackApp } from '@stackframe/stack'
|
|
487
|
+
import { useState } from 'react'
|
|
488
|
+
|
|
489
|
+
export function AuthForm() {
|
|
490
|
+
const app = useStackApp()
|
|
491
|
+
const [error, setError] = useState<string | null>(null)
|
|
492
|
+
|
|
493
|
+
const handleAuth = async () => {
|
|
494
|
+
try {
|
|
495
|
+
await app.signInWithCredential({ email, password })
|
|
496
|
+
} catch (err) {
|
|
497
|
+
if (err instanceof Error) {
|
|
498
|
+
// Handle specific error types
|
|
499
|
+
if (err.message.includes('invalid_credentials')) {
|
|
500
|
+
setError('Invalid email or password')
|
|
501
|
+
} else if (err.message.includes('user_not_found')) {
|
|
502
|
+
setError('No account found with this email')
|
|
503
|
+
} else if (err.message.includes('email_not_verified')) {
|
|
504
|
+
setError('Please verify your email first')
|
|
505
|
+
} else {
|
|
506
|
+
setError('An error occurred. Please try again.')
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
return (
|
|
513
|
+
<div>
|
|
514
|
+
{error && <div className="error-banner">{error}</div>}
|
|
515
|
+
{/* Form fields */}
|
|
516
|
+
</div>
|
|
517
|
+
)
|
|
518
|
+
}
|
|
519
|
+
```
|