@everystack/mcp 0.2.0
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/README.md +100 -0
- package/package.json +39 -0
- package/src/index.ts +58 -0
- package/src/prompts/add-feature.ts +163 -0
- package/src/prompts/debug.ts +136 -0
- package/src/prompts/deploy.ts +131 -0
- package/src/prompts/design-schema.ts +104 -0
- package/src/prompts/index.ts +16 -0
- package/src/prompts/new-app.ts +211 -0
- package/src/prompts/secure.ts +231 -0
- package/src/resources/adding-database.md +169 -0
- package/src/resources/admin.md +81 -0
- package/src/resources/auth.md +115 -0
- package/src/resources/aws-setup.md +173 -0
- package/src/resources/cli.md +108 -0
- package/src/resources/client-api.md +145 -0
- package/src/resources/core.md +196 -0
- package/src/resources/deployment.md +146 -0
- package/src/resources/events.md +87 -0
- package/src/resources/first-run.md +100 -0
- package/src/resources/getting-started.md +75 -0
- package/src/resources/handler-options.md +114 -0
- package/src/resources/images.md +73 -0
- package/src/resources/index.ts +224 -0
- package/src/resources/jobs.md +97 -0
- package/src/resources/logging.md +91 -0
- package/src/resources/plugins.md +68 -0
- package/src/resources/project-claude-md.md +127 -0
- package/src/resources/query-protocol.md +129 -0
- package/src/resources/schema-patterns.md +167 -0
- package/src/resources/security-device.md +99 -0
- package/src/resources/security.md +270 -0
- package/src/resources/ssr.md +82 -0
- package/src/resources/storage.md +63 -0
- package/src/resources/testing.md +118 -0
- package/src/tools/check-environment.ts +319 -0
- package/src/tools/index.ts +58 -0
- package/src/tools/project-status.ts +183 -0
- package/src/tools/project-validate.ts +369 -0
- package/src/tools/schema-analyze.ts +410 -0
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
|
|
4
|
+
export function registerDesignSchemaPrompt(server: McpServer): void {
|
|
5
|
+
server.prompt(
|
|
6
|
+
'design-schema',
|
|
7
|
+
'Interactive schema design: plain English description → Drizzle schema + migrations + RLS policies + handler config + testing checklist.',
|
|
8
|
+
{
|
|
9
|
+
description: z.string().describe('Plain English description of the data model (e.g., "blog with posts, comments, and tags")'),
|
|
10
|
+
projectPath: z.string().optional().describe('Absolute path to project root (to analyze existing schema)'),
|
|
11
|
+
},
|
|
12
|
+
async ({ description, projectPath }) => {
|
|
13
|
+
return {
|
|
14
|
+
messages: [
|
|
15
|
+
{
|
|
16
|
+
role: 'user' as const,
|
|
17
|
+
content: {
|
|
18
|
+
type: 'text' as const,
|
|
19
|
+
text: [
|
|
20
|
+
`Design a database schema for: ${description}`,
|
|
21
|
+
'',
|
|
22
|
+
'## Instructions',
|
|
23
|
+
'',
|
|
24
|
+
'1. Read everystack://schema-patterns for Drizzle schema conventions.',
|
|
25
|
+
'2. Read everystack://security for RLS policy patterns.',
|
|
26
|
+
'3. Read everystack://handler-options for handler configuration.',
|
|
27
|
+
projectPath ? `4. Run schema_analyze with projectPath="${projectPath}" to understand the existing schema.` : '',
|
|
28
|
+
'',
|
|
29
|
+
'## Deliverables',
|
|
30
|
+
'',
|
|
31
|
+
'Generate ALL of the following:',
|
|
32
|
+
'',
|
|
33
|
+
'### 1. Drizzle Schema (db/schema.ts)',
|
|
34
|
+
'',
|
|
35
|
+
'Follow these conventions:',
|
|
36
|
+
'- UUID primary keys: `uuid(\'id\').primaryKey().defaultRandom()`',
|
|
37
|
+
'- Timestamps: `timestamp(\'created_at\', { withTimezone: true }).defaultNow().notNull()`',
|
|
38
|
+
'- Soft delete columns: `deletedAt` + `deletedBy` on user-facing tables',
|
|
39
|
+
'- Foreign keys with `.references(() => table.column)`',
|
|
40
|
+
'- Export all tables as named exports',
|
|
41
|
+
'- Add Drizzle `relations()` for SSR query building and API embedding',
|
|
42
|
+
'',
|
|
43
|
+
'### 2. SQL Migration with RLS',
|
|
44
|
+
'',
|
|
45
|
+
'Generate a custom SQL migration that includes:',
|
|
46
|
+
'',
|
|
47
|
+
'```sql',
|
|
48
|
+
'-- For each table:',
|
|
49
|
+
'ALTER TABLE table_name ENABLE ROW LEVEL SECURITY;',
|
|
50
|
+
'',
|
|
51
|
+
'-- Grant minimum required access per role:',
|
|
52
|
+
'GRANT SELECT ON table_name TO anon;',
|
|
53
|
+
'GRANT SELECT, INSERT, UPDATE, DELETE ON table_name TO authenticated;',
|
|
54
|
+
'GRANT ALL ON table_name TO admin;',
|
|
55
|
+
'',
|
|
56
|
+
'-- Policies per access pattern:',
|
|
57
|
+
'-- Public read:',
|
|
58
|
+
'CREATE POLICY "anon_select" ON table_name FOR SELECT TO anon USING (deleted_at IS NULL);',
|
|
59
|
+
'',
|
|
60
|
+
'-- Own rows only:',
|
|
61
|
+
'CREATE POLICY "own_rows" ON table_name FOR ALL TO authenticated',
|
|
62
|
+
' USING (user_id = current_setting(\'request.jwt.claims\', true)::json->>\'sub\');',
|
|
63
|
+
'',
|
|
64
|
+
'-- Admin full access:',
|
|
65
|
+
'CREATE POLICY "admin_all" ON table_name FOR ALL TO admin USING (true);',
|
|
66
|
+
'```',
|
|
67
|
+
'',
|
|
68
|
+
'Adapt the policies to the specific access patterns for each table.',
|
|
69
|
+
'',
|
|
70
|
+
'### 3. Handler Configuration',
|
|
71
|
+
'',
|
|
72
|
+
'Generate the createHandler() config snippet:',
|
|
73
|
+
'- `exposedTables`: only tables that should be API-accessible',
|
|
74
|
+
'- `relations`: for embedding related data in API queries',
|
|
75
|
+
'- `rowOwnership`: for user-scoped tables',
|
|
76
|
+
'- `softDelete`: for tables with deletedAt columns',
|
|
77
|
+
'- `protectedFields`: fields users cannot set directly (role, deletedAt)',
|
|
78
|
+
'- `hiddenColumns`: sensitive columns not returned in API responses',
|
|
79
|
+
'',
|
|
80
|
+
'### 4. Testing Checklist',
|
|
81
|
+
'',
|
|
82
|
+
'Generate a testing checklist:',
|
|
83
|
+
'- [ ] Anonymous users can read public data',
|
|
84
|
+
'- [ ] Anonymous users cannot write any data',
|
|
85
|
+
'- [ ] Authenticated users can only read/write their own data',
|
|
86
|
+
'- [ ] Soft-deleted rows are hidden from normal queries',
|
|
87
|
+
'- [ ] Admin users can access all data',
|
|
88
|
+
'- [ ] Foreign key constraints prevent orphaned rows',
|
|
89
|
+
'- [ ] RLS policies work with pgSettings claim injection',
|
|
90
|
+
'',
|
|
91
|
+
'Test each RLS policy with:',
|
|
92
|
+
'```sql',
|
|
93
|
+
'SET LOCAL ROLE authenticated;',
|
|
94
|
+
"SELECT set_config('request.jwt.claims', '{\"sub\": \"user-uuid\", \"role\": \"authenticated\"}', true);",
|
|
95
|
+
'SELECT * FROM table_name; -- should only return own rows',
|
|
96
|
+
'```',
|
|
97
|
+
].filter(Boolean).join('\n'),
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
],
|
|
101
|
+
};
|
|
102
|
+
},
|
|
103
|
+
);
|
|
104
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { registerNewAppPrompt } from './new-app.js';
|
|
3
|
+
import { registerAddFeaturePrompt } from './add-feature.js';
|
|
4
|
+
import { registerDesignSchemaPrompt } from './design-schema.js';
|
|
5
|
+
import { registerDeployPrompt } from './deploy.js';
|
|
6
|
+
import { registerDebugPrompt } from './debug.js';
|
|
7
|
+
import { registerSecurePrompt } from './secure.js';
|
|
8
|
+
|
|
9
|
+
export function registerPrompts(server: McpServer): void {
|
|
10
|
+
registerNewAppPrompt(server);
|
|
11
|
+
registerAddFeaturePrompt(server);
|
|
12
|
+
registerDesignSchemaPrompt(server);
|
|
13
|
+
registerDeployPrompt(server);
|
|
14
|
+
registerDebugPrompt(server);
|
|
15
|
+
registerSecurePrompt(server);
|
|
16
|
+
}
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
|
|
4
|
+
export function registerNewAppPrompt(server: McpServer): void {
|
|
5
|
+
server.prompt(
|
|
6
|
+
'new-app',
|
|
7
|
+
'Scaffold a new everystack app. Describe what you want to build and Claude will recommend the right tier, check prerequisites, and walk you through setup.',
|
|
8
|
+
{
|
|
9
|
+
description: z.string().describe('Describe what you want to build (e.g., "a recipe sharing app where people can post and save recipes")'),
|
|
10
|
+
tier: z.enum(['V1', 'V2', 'V3']).optional().describe('Target tier: V1 (static), V2 (dynamic with DB), V3 (full platform). If omitted, recommended based on description.'),
|
|
11
|
+
name: z.string().optional().describe('App name (e.g., my-app). If omitted, suggested from description.'),
|
|
12
|
+
},
|
|
13
|
+
async ({ description, tier, name }) => {
|
|
14
|
+
const hasTier = tier !== undefined;
|
|
15
|
+
const hasName = name !== undefined;
|
|
16
|
+
|
|
17
|
+
const tierPackages: Record<string, string[]> = {
|
|
18
|
+
V1: ['@everystack/server', '@everystack/cli', '@everystack/ui'],
|
|
19
|
+
V2: [
|
|
20
|
+
'@everystack/server', '@everystack/cli', '@everystack/ui',
|
|
21
|
+
'@everystack/api', '@everystack/auth', '@everystack/admin',
|
|
22
|
+
'@everystack/logging', '@everystack/security', '@everystack/query',
|
|
23
|
+
],
|
|
24
|
+
V3: [
|
|
25
|
+
'@everystack/server', '@everystack/cli', '@everystack/ui',
|
|
26
|
+
'@everystack/api', '@everystack/auth', '@everystack/admin',
|
|
27
|
+
'@everystack/logging', '@everystack/security', '@everystack/query',
|
|
28
|
+
'@everystack/jobs', '@everystack/storage', '@everystack/images',
|
|
29
|
+
],
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// When tier is omitted, build an intake phase for Claude to work through
|
|
33
|
+
const intakeSection = !hasTier ? [
|
|
34
|
+
'## Step 1: Understand the Idea',
|
|
35
|
+
'',
|
|
36
|
+
`The user wants to build: "${description}"`,
|
|
37
|
+
'',
|
|
38
|
+
'Read the everystack://getting-started resource for how to explain things to beginners.',
|
|
39
|
+
'Listen to what they want to build. Do NOT categorize into tiers or ask them to choose.',
|
|
40
|
+
'Every app starts the same way: a running Expo app. Build the UI first, add complexity when features demand it.',
|
|
41
|
+
'',
|
|
42
|
+
'## Step 2: Choose a Name',
|
|
43
|
+
'',
|
|
44
|
+
hasName
|
|
45
|
+
? `Use the name: "${name}"`
|
|
46
|
+
: 'Suggest a short, lowercase, hyphenated name based on the description (e.g., "recipe-share", "my-recipes"). Ask the user if they like it or want something different.',
|
|
47
|
+
'',
|
|
48
|
+
'## Step 3: Check Prerequisites',
|
|
49
|
+
'',
|
|
50
|
+
'Run the check_environment tool with phase "local".',
|
|
51
|
+
'Read everystack://getting-started for how to explain each prerequisite to a beginner.',
|
|
52
|
+
'Walk through installing missing tools ONE AT A TIME. Confirm each works before moving to the next.',
|
|
53
|
+
'Only V1 prerequisites are needed now — Node.js, git, pnpm. PostgreSQL is NOT needed yet.',
|
|
54
|
+
'AWS tools (AWS CLI, credentials, SST) are NOT needed now. The user will build and run locally first.',
|
|
55
|
+
'',
|
|
56
|
+
'## Step 4: Scaffold the Project',
|
|
57
|
+
'',
|
|
58
|
+
'Once prerequisites are ready and the user has confirmed the name, proceed with the scaffolding steps below.',
|
|
59
|
+
'The app starts as a static Expo app. Database, auth, and other features are added later when needed.',
|
|
60
|
+
'Explain each step in plain language alongside the technical commands.',
|
|
61
|
+
'',
|
|
62
|
+
].join('\n') : '';
|
|
63
|
+
|
|
64
|
+
const effectiveTier = tier || 'V1';
|
|
65
|
+
const effectiveName = name || 'my-app';
|
|
66
|
+
const packages = tierPackages[effectiveTier];
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
messages: [
|
|
70
|
+
{
|
|
71
|
+
role: 'user' as const,
|
|
72
|
+
content: {
|
|
73
|
+
type: 'text' as const,
|
|
74
|
+
text: [
|
|
75
|
+
intakeSection,
|
|
76
|
+
hasTier
|
|
77
|
+
? `Create a new everystack ${effectiveTier} app called "${effectiveName}".`
|
|
78
|
+
: '## Scaffolding Instructions',
|
|
79
|
+
`Description: ${description}`,
|
|
80
|
+
'',
|
|
81
|
+
hasTier ? [
|
|
82
|
+
'## Prerequisites',
|
|
83
|
+
'',
|
|
84
|
+
'Run the check_environment tool with phase "local" to verify local development prerequisites.',
|
|
85
|
+
'If any required tools are missing, help the user install them before proceeding.',
|
|
86
|
+
'AWS tools are not needed yet — the user will build and run locally first.',
|
|
87
|
+
].join('\n') : '',
|
|
88
|
+
'',
|
|
89
|
+
'## Instructions',
|
|
90
|
+
'',
|
|
91
|
+
'1. Read the everystack://core resource for architecture and conventions.',
|
|
92
|
+
effectiveTier !== 'V1' ? '2. Read everystack://security for the security model.' : '',
|
|
93
|
+
effectiveTier !== 'V1' ? '3. Read everystack://schema-patterns for Drizzle schema design.' : '',
|
|
94
|
+
effectiveTier !== 'V1' ? '4. Read everystack://auth for authentication setup.' : '',
|
|
95
|
+
'',
|
|
96
|
+
'## Steps to Execute',
|
|
97
|
+
'',
|
|
98
|
+
'### 1. Project Setup',
|
|
99
|
+
`- Create the project directory: ${effectiveName}/`,
|
|
100
|
+
'- Initialize with: `npx create-expo-app@latest`',
|
|
101
|
+
`- Install packages: \`pnpm add ${packages.join(' ')}\``,
|
|
102
|
+
effectiveTier !== 'V1' ? '- Install peer deps: `pnpm add drizzle-orm && pnpm add -D drizzle-kit`' : '',
|
|
103
|
+
'- Install SST: `pnpm add -D sst`',
|
|
104
|
+
'',
|
|
105
|
+
'### 2. Project Structure',
|
|
106
|
+
'```',
|
|
107
|
+
`${effectiveName}/`,
|
|
108
|
+
'├── app/ # Expo Router pages',
|
|
109
|
+
effectiveTier !== 'V1' ? '├── db/ # Schema, migrations, seed' : '',
|
|
110
|
+
'├── server/ # Lambda handlers',
|
|
111
|
+
effectiveTier !== 'V1' ? '│ └── api.ts # PostgREST handler' : '',
|
|
112
|
+
effectiveTier === 'V3' ? '│ ├── worker.ts # SQS worker handler' : '',
|
|
113
|
+
effectiveTier === 'V3' ? '│ └── image.ts # Image processing handler' : '',
|
|
114
|
+
'├── lib/ # Shared code (auth context, API client)',
|
|
115
|
+
'├── sst.config.ts # Infrastructure',
|
|
116
|
+
'└── package.json',
|
|
117
|
+
'```',
|
|
118
|
+
'',
|
|
119
|
+
effectiveTier !== 'V1' ? [
|
|
120
|
+
'### 3. Database Schema',
|
|
121
|
+
`Create db/schema.ts with tables for the ${description}.`,
|
|
122
|
+
'Follow the patterns from everystack://schema-patterns:',
|
|
123
|
+
'- UUID primary keys with defaultRandom()',
|
|
124
|
+
'- created_at timestamps on all tables',
|
|
125
|
+
'- Foreign key references with proper cascading',
|
|
126
|
+
'- Relations for both SSR and API query embedding',
|
|
127
|
+
'',
|
|
128
|
+
'### 4. Migrations',
|
|
129
|
+
'- Generate: `npx drizzle-kit generate`',
|
|
130
|
+
'- Add RLS policies in a custom SQL migration',
|
|
131
|
+
'- Reference the RLS templates from everystack://security',
|
|
132
|
+
'',
|
|
133
|
+
'### 5. Handler Configuration',
|
|
134
|
+
'Create server/api.ts using createPluginLambdaHandler or createLambdaHandler.',
|
|
135
|
+
'Include:',
|
|
136
|
+
'- auth.verifyToken for JWT verification',
|
|
137
|
+
'- pgSettings for RLS context injection',
|
|
138
|
+
'- exposedTables to limit API surface',
|
|
139
|
+
'- rowOwnership for user-scoped mutation control',
|
|
140
|
+
'- softDelete for reversible deletes',
|
|
141
|
+
'',
|
|
142
|
+
'### 6. Auth Setup',
|
|
143
|
+
'Read everystack://auth for the full auth flow.',
|
|
144
|
+
'- Set up createAuthHandlers with the JWT secret',
|
|
145
|
+
'- Create lib/auth-context.tsx with AuthProvider',
|
|
146
|
+
'- Add signup/signin screens',
|
|
147
|
+
'',
|
|
148
|
+
].join('\n') : '',
|
|
149
|
+
'## Validation',
|
|
150
|
+
'',
|
|
151
|
+
'After scaffolding, run the project_validate tool to check for common mistakes.',
|
|
152
|
+
'',
|
|
153
|
+
'## Run Locally',
|
|
154
|
+
'',
|
|
155
|
+
'Read everystack://first-run for a walkthrough of what to expect.',
|
|
156
|
+
'- Run `npx expo start` (or `pnpm dev`)',
|
|
157
|
+
'- Press `w` to open in a browser',
|
|
158
|
+
'- Verify the app loads and displays correctly',
|
|
159
|
+
'- Try changing text in app/index.tsx to confirm hot reload works',
|
|
160
|
+
'',
|
|
161
|
+
'This is the milestone: the user sees their app running on their own computer.',
|
|
162
|
+
'Celebrate this. Then continue building features.',
|
|
163
|
+
'',
|
|
164
|
+
!hasTier ? [
|
|
165
|
+
'## Growing Your App',
|
|
166
|
+
'',
|
|
167
|
+
'Build the UI together first — screens, navigation, components, styling. Everything is static and visual.',
|
|
168
|
+
'',
|
|
169
|
+
'When a feature needs user accounts, login, or saved data:',
|
|
170
|
+
'- Read everystack://adding-database for the step-by-step guide',
|
|
171
|
+
'- Run check_environment("local") again — PostgreSQL will be needed',
|
|
172
|
+
'',
|
|
173
|
+
'When a feature needs file uploads or background processing:',
|
|
174
|
+
'- Read everystack://storage for file uploads',
|
|
175
|
+
'- Read everystack://jobs for background tasks',
|
|
176
|
+
'- Read everystack://images for image processing',
|
|
177
|
+
].join('\n') : '',
|
|
178
|
+
'',
|
|
179
|
+
'## Project Documentation',
|
|
180
|
+
'Read the everystack://project-claude-md resource and write a CLAUDE.md file to the project root.',
|
|
181
|
+
`Replace {PROJECT_NAME} with "${effectiveName}", {ONE_LINE_DESCRIPTION} with "${description}",`,
|
|
182
|
+
'and {ANNOTATED_DIRECTORY_TREE} with the actual project structure created above.',
|
|
183
|
+
'',
|
|
184
|
+
'## Deployment (Later)',
|
|
185
|
+
'',
|
|
186
|
+
'When the user is ready to put the app on the internet:',
|
|
187
|
+
'1. Run check_environment with phase "deploy" to verify AWS prerequisites',
|
|
188
|
+
'2. Set up AWS credentials (read everystack://aws-setup for beginners, everystack://security for IAM profiles)',
|
|
189
|
+
'### SST Configuration',
|
|
190
|
+
'Create sst.config.ts with:',
|
|
191
|
+
'- S3 bucket for static assets',
|
|
192
|
+
'- CloudFront distribution',
|
|
193
|
+
'- Lambda for SSR',
|
|
194
|
+
effectiveTier !== 'V1' ? '- RDS Aurora Serverless PostgreSQL' : '',
|
|
195
|
+
effectiveTier !== 'V1' ? '- VPC configuration' : '',
|
|
196
|
+
effectiveTier === 'V3' ? '- SQS queue for background jobs' : '',
|
|
197
|
+
effectiveTier === 'V3' ? '- S3 bucket for file uploads' : '',
|
|
198
|
+
'',
|
|
199
|
+
'### Deploy',
|
|
200
|
+
'Read everystack://deployment for the full walkthrough.',
|
|
201
|
+
'1. `pnpm sst deploy --stage dev`',
|
|
202
|
+
effectiveTier !== 'V1' ? '2. `everystack db:migrate`' : '',
|
|
203
|
+
effectiveTier !== 'V1' ? '3. `everystack db:seed` (dev only)' : '',
|
|
204
|
+
].filter(Boolean).join('\n'),
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
],
|
|
208
|
+
};
|
|
209
|
+
},
|
|
210
|
+
);
|
|
211
|
+
}
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
|
|
4
|
+
export function registerSecurePrompt(server: McpServer): void {
|
|
5
|
+
server.prompt(
|
|
6
|
+
'secure',
|
|
7
|
+
'Set up security for an everystack project: AWS IAM profiles, JWT auth, RLS policies, and edge verification.',
|
|
8
|
+
{
|
|
9
|
+
features: z.string().optional().describe('Comma-separated security features to set up (e.g., "aws,rls,auth"). Defaults to all.'),
|
|
10
|
+
projectPath: z.string().optional().describe('Absolute path to project root'),
|
|
11
|
+
},
|
|
12
|
+
async ({ features, projectPath }) => {
|
|
13
|
+
const allFeatures = ['aws', 'rls', 'auth', 'edge'];
|
|
14
|
+
const requested = features
|
|
15
|
+
? features.split(',').map((f) => f.trim().toLowerCase())
|
|
16
|
+
: allFeatures;
|
|
17
|
+
|
|
18
|
+
return {
|
|
19
|
+
messages: [
|
|
20
|
+
{
|
|
21
|
+
role: 'user' as const,
|
|
22
|
+
content: {
|
|
23
|
+
type: 'text' as const,
|
|
24
|
+
text: [
|
|
25
|
+
`Set up security for ${projectPath ? 'the project at ' + projectPath : 'my everystack project'}.`,
|
|
26
|
+
`Features: ${requested.join(', ')}`,
|
|
27
|
+
'',
|
|
28
|
+
'## Instructions',
|
|
29
|
+
'',
|
|
30
|
+
'1. Read everystack://security for the complete security model.',
|
|
31
|
+
'2. Read everystack://auth for JWT authentication details.',
|
|
32
|
+
projectPath ? `3. Run project_validate with projectPath="${projectPath}" to find existing security gaps.` : '',
|
|
33
|
+
projectPath ? `4. Run schema_analyze with projectPath="${projectPath}" to check schema security.` : '',
|
|
34
|
+
'',
|
|
35
|
+
requested.includes('aws') ? [
|
|
36
|
+
'## AWS IAM Profiles',
|
|
37
|
+
'',
|
|
38
|
+
'Set up three named AWS profiles with least-privilege permissions:',
|
|
39
|
+
'',
|
|
40
|
+
'### 1. everystack-create (Infrastructure Creation)',
|
|
41
|
+
'',
|
|
42
|
+
'**When:** First deploy only. Disable after infrastructure exists.',
|
|
43
|
+
'',
|
|
44
|
+
'```ini',
|
|
45
|
+
'# ~/.aws/credentials',
|
|
46
|
+
'[everystack-create]',
|
|
47
|
+
'aws_access_key_id = ...',
|
|
48
|
+
'aws_secret_access_key = ...',
|
|
49
|
+
'```',
|
|
50
|
+
'',
|
|
51
|
+
'Permissions needed:',
|
|
52
|
+
'- CloudFormation (create stacks)',
|
|
53
|
+
'- S3 (create buckets)',
|
|
54
|
+
'- CloudFront (create distributions)',
|
|
55
|
+
'- Lambda (create functions)',
|
|
56
|
+
'- RDS (create clusters)',
|
|
57
|
+
'- IAM (create roles — scoped to everystack-* prefix)',
|
|
58
|
+
'- VPC (create VPC, subnets, security groups)',
|
|
59
|
+
'- SQS (create queues)',
|
|
60
|
+
'',
|
|
61
|
+
'**After first deploy:** Delete or deactivate these access keys.',
|
|
62
|
+
'',
|
|
63
|
+
'### 2. everystack-manage (Day-to-Day CLI)',
|
|
64
|
+
'',
|
|
65
|
+
'**When:** Development, running CLI commands, migrations.',
|
|
66
|
+
'',
|
|
67
|
+
'Permissions needed:',
|
|
68
|
+
'- Lambda:InvokeFunction (for CLI → Lambda operations)',
|
|
69
|
+
'- S3:PutObject/GetObject (for OTA updates)',
|
|
70
|
+
'- CloudWatch:GetLogEvents (for log tailing)',
|
|
71
|
+
'- SSM:GetParameter (for secret access)',
|
|
72
|
+
'',
|
|
73
|
+
'**Cannot:** Create or delete infrastructure, modify IAM, access RDS directly.',
|
|
74
|
+
'',
|
|
75
|
+
'### 3. everystack-deploy (CI/CD)',
|
|
76
|
+
'',
|
|
77
|
+
'**When:** Automated deployments from CI/CD pipelines.',
|
|
78
|
+
'',
|
|
79
|
+
'Permissions needed:',
|
|
80
|
+
'- CloudFormation:UpdateStack (update existing, not create new)',
|
|
81
|
+
'- Lambda:UpdateFunctionCode',
|
|
82
|
+
'- S3:PutObject (deploy assets)',
|
|
83
|
+
'- CloudFront:CreateInvalidation',
|
|
84
|
+
'',
|
|
85
|
+
'**Cannot:** Create new infrastructure, modify IAM, access secrets.',
|
|
86
|
+
'',
|
|
87
|
+
].join('\n') : '',
|
|
88
|
+
|
|
89
|
+
requested.includes('auth') ? [
|
|
90
|
+
'## JWT Authentication',
|
|
91
|
+
'',
|
|
92
|
+
'Read everystack://auth for full details.',
|
|
93
|
+
'',
|
|
94
|
+
'### Setup',
|
|
95
|
+
'',
|
|
96
|
+
'1. Generate a strong JWT secret:',
|
|
97
|
+
'```bash',
|
|
98
|
+
'pnpm sst secret set JwtSecret "$(openssl rand -base64 32)" --stage dev',
|
|
99
|
+
'```',
|
|
100
|
+
'',
|
|
101
|
+
'2. Create auth handlers:',
|
|
102
|
+
'```typescript',
|
|
103
|
+
"import { createAuthHandlers } from '@everystack/auth';",
|
|
104
|
+
'',
|
|
105
|
+
'const auth = createAuthHandlers(db, schema, jwtSecret, {',
|
|
106
|
+
" mode: 'postgres',",
|
|
107
|
+
' sql,',
|
|
108
|
+
' cookies: true, // HttpOnly cookies for web',
|
|
109
|
+
'});',
|
|
110
|
+
'```',
|
|
111
|
+
'',
|
|
112
|
+
'3. Wire into Lambda handler with auth routes.',
|
|
113
|
+
'',
|
|
114
|
+
'4. Create AuthProvider in lib/auth-context.tsx.',
|
|
115
|
+
'',
|
|
116
|
+
'### Token Lifecycle',
|
|
117
|
+
'',
|
|
118
|
+
'- Access tokens: short-lived (15 min default)',
|
|
119
|
+
'- Refresh tokens: long-lived (7 days default)',
|
|
120
|
+
'- Auto-refresh on 401 via client interceptor',
|
|
121
|
+
'',
|
|
122
|
+
].join('\n') : '',
|
|
123
|
+
|
|
124
|
+
requested.includes('rls') ? [
|
|
125
|
+
'## Row-Level Security (RLS)',
|
|
126
|
+
'',
|
|
127
|
+
'Read everystack://security for the mental model and templates.',
|
|
128
|
+
'',
|
|
129
|
+
'### How RLS Works in everystack',
|
|
130
|
+
'',
|
|
131
|
+
'1. Client sends JWT in Authorization header',
|
|
132
|
+
'2. Handler verifies JWT, extracts claims',
|
|
133
|
+
'3. `pgSettings` sets PostgreSQL session variables:',
|
|
134
|
+
' - `SET LOCAL ROLE authenticated`',
|
|
135
|
+
' - `set_config(\'request.jwt.claims\', \'{"sub":"...","role":"..."}\', true)`',
|
|
136
|
+
'4. Query runs inside a transaction with those settings',
|
|
137
|
+
'5. RLS policies read the claims and filter rows',
|
|
138
|
+
'',
|
|
139
|
+
'### Step-by-Step',
|
|
140
|
+
'',
|
|
141
|
+
'1. **Create roles** (one-time migration):',
|
|
142
|
+
'```sql',
|
|
143
|
+
'CREATE ROLE anon NOLOGIN;',
|
|
144
|
+
'CREATE ROLE authenticated NOLOGIN;',
|
|
145
|
+
'CREATE ROLE admin NOLOGIN;',
|
|
146
|
+
'GRANT anon, authenticated, admin TO authenticator;',
|
|
147
|
+
'```',
|
|
148
|
+
'',
|
|
149
|
+
'2. **Enable RLS on every table:**',
|
|
150
|
+
'```sql',
|
|
151
|
+
'ALTER TABLE table_name ENABLE ROW LEVEL SECURITY;',
|
|
152
|
+
'```',
|
|
153
|
+
'',
|
|
154
|
+
'3. **Grant minimum permissions:**',
|
|
155
|
+
'```sql',
|
|
156
|
+
'GRANT SELECT ON table_name TO anon;',
|
|
157
|
+
'GRANT SELECT, INSERT, UPDATE, DELETE ON table_name TO authenticated;',
|
|
158
|
+
'GRANT ALL ON table_name TO admin;',
|
|
159
|
+
'```',
|
|
160
|
+
'',
|
|
161
|
+
'4. **Create policies per access pattern:**',
|
|
162
|
+
'',
|
|
163
|
+
'**Public read (soft-delete aware):**',
|
|
164
|
+
'```sql',
|
|
165
|
+
"CREATE POLICY \"anon_select\" ON table_name FOR SELECT TO anon",
|
|
166
|
+
' USING (deleted_at IS NULL);',
|
|
167
|
+
'```',
|
|
168
|
+
'',
|
|
169
|
+
'**Own rows only:**',
|
|
170
|
+
'```sql',
|
|
171
|
+
"CREATE POLICY \"own_rows_select\" ON table_name FOR SELECT TO authenticated",
|
|
172
|
+
" USING (user_id = (current_setting('request.jwt.claims', true)::json->>'sub')::uuid);",
|
|
173
|
+
'```',
|
|
174
|
+
'',
|
|
175
|
+
'**Admin full access:**',
|
|
176
|
+
'```sql',
|
|
177
|
+
"CREATE POLICY \"admin_all\" ON table_name FOR ALL TO admin USING (true);",
|
|
178
|
+
'```',
|
|
179
|
+
'',
|
|
180
|
+
'5. **Configure pgSettings in handler:**',
|
|
181
|
+
'```typescript',
|
|
182
|
+
'pgSettings: (user) => ({',
|
|
183
|
+
" role: user?.role === 'admin' ? 'admin' : user ? 'authenticated' : 'anon',",
|
|
184
|
+
" 'request.jwt.claims': JSON.stringify(user || { role: 'anon' }),",
|
|
185
|
+
'}),',
|
|
186
|
+
'```',
|
|
187
|
+
'',
|
|
188
|
+
'### Testing RLS',
|
|
189
|
+
'',
|
|
190
|
+
'```sql',
|
|
191
|
+
'-- Test as authenticated user',
|
|
192
|
+
'BEGIN;',
|
|
193
|
+
'SET LOCAL ROLE authenticated;',
|
|
194
|
+
"SELECT set_config('request.jwt.claims',",
|
|
195
|
+
" '{\"sub\": \"user-uuid\", \"role\": \"authenticated\"}', true);",
|
|
196
|
+
'SELECT * FROM posts; -- should only return own posts',
|
|
197
|
+
'ROLLBACK;',
|
|
198
|
+
'```',
|
|
199
|
+
'',
|
|
200
|
+
].join('\n') : '',
|
|
201
|
+
|
|
202
|
+
requested.includes('edge') ? [
|
|
203
|
+
'## Edge JWT Verification',
|
|
204
|
+
'',
|
|
205
|
+
'Verify JWTs at the CDN edge before requests reach Lambda:',
|
|
206
|
+
'',
|
|
207
|
+
'- CloudFront function validates JWT signature (HS256)',
|
|
208
|
+
'- Rejects expired or malformed tokens at the edge',
|
|
209
|
+
'- Reduces Lambda invocations for unauthorized requests',
|
|
210
|
+
'- See everystack://deployment for CloudFront function setup',
|
|
211
|
+
'',
|
|
212
|
+
].join('\n') : '',
|
|
213
|
+
|
|
214
|
+
'## Verification',
|
|
215
|
+
'',
|
|
216
|
+
'After setup, verify security:',
|
|
217
|
+
'',
|
|
218
|
+
'1. Run project_validate to check for gaps',
|
|
219
|
+
'2. Test unauthenticated access returns 401',
|
|
220
|
+
'3. Test cross-user data isolation with RLS',
|
|
221
|
+
'4. Verify JWT claims propagate through pgSettings',
|
|
222
|
+
'5. Check `.env` files are not committed',
|
|
223
|
+
'6. Confirm `everystack-create` profile is disabled',
|
|
224
|
+
].filter(Boolean).join('\n'),
|
|
225
|
+
},
|
|
226
|
+
},
|
|
227
|
+
],
|
|
228
|
+
};
|
|
229
|
+
},
|
|
230
|
+
);
|
|
231
|
+
}
|