@daylight-labs/sharedb-mcp 0.1.1 → 0.1.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.
@@ -92,7 +92,94 @@ export interface LogQueryOptions {
92
92
  until?: string;
93
93
  limit?: number;
94
94
  }
95
+ export interface App {
96
+ id: string;
97
+ name: string;
98
+ display_name: string;
99
+ created_at: string;
100
+ updated_at: string;
101
+ databases: {
102
+ staging?: {
103
+ id: string;
104
+ name: string;
105
+ environment: string;
106
+ };
107
+ production?: {
108
+ id: string;
109
+ name: string;
110
+ environment: string;
111
+ };
112
+ };
113
+ }
114
+ export interface PromotionPreview {
115
+ staging: {
116
+ id: string;
117
+ name: string;
118
+ migrationVersion: number;
119
+ };
120
+ production: {
121
+ id: string;
122
+ name: string;
123
+ migrationVersion: number;
124
+ };
125
+ migrationsToApply: Array<{
126
+ id: string;
127
+ version: number;
128
+ description: string;
129
+ changeCount: number;
130
+ }>;
131
+ destructiveChanges: Array<{
132
+ changeType: string;
133
+ tableName: string;
134
+ columnName: string | null;
135
+ description: string;
136
+ }>;
137
+ warnings: string[];
138
+ totalMigrations: number;
139
+ totalChanges: number;
140
+ hasDestructiveChanges: boolean;
141
+ }
95
142
  export declare const api: {
143
+ apps: {
144
+ list: () => Promise<{
145
+ apps: Array<{
146
+ id: string;
147
+ name: string;
148
+ display_name: string;
149
+ databases: Array<{
150
+ id: string;
151
+ name: string;
152
+ environment: string;
153
+ postgrest_status: string | null;
154
+ postgrestUrl: string | null;
155
+ }>;
156
+ }>;
157
+ }>;
158
+ get: (id: string) => Promise<{
159
+ app: App;
160
+ }>;
161
+ create: (data: {
162
+ name: string;
163
+ displayName: string;
164
+ }) => Promise<{
165
+ app: App;
166
+ }>;
167
+ delete: (id: string) => Promise<{
168
+ deleted: {
169
+ app: App;
170
+ };
171
+ }>;
172
+ startPostgrest: (id: string) => Promise<{
173
+ results: Record<string, {
174
+ status: string;
175
+ postgrestUrl: string | null;
176
+ }>;
177
+ }>;
178
+ promotePreview: (id: string) => Promise<{
179
+ preview: PromotionPreview | null;
180
+ message?: string;
181
+ }>;
182
+ };
96
183
  databases: {
97
184
  list: () => Promise<{
98
185
  databases: Database[];
@@ -201,3 +288,8 @@ export declare const api: {
201
288
  }>;
202
289
  };
203
290
  };
291
+ export declare function resolveStagingDatabase(identifier: string): Promise<{
292
+ databaseId: string;
293
+ isApp: boolean;
294
+ appId?: string;
295
+ }>;
@@ -15,6 +15,19 @@ async function request(path, options) {
15
15
  return response.json();
16
16
  }
17
17
  export const api = {
18
+ apps: {
19
+ list: () => request('/admin/apps'),
20
+ get: (id) => request(`/admin/apps/${id}`),
21
+ create: (data) => request('/admin/apps', {
22
+ method: 'POST',
23
+ body: JSON.stringify(data),
24
+ }),
25
+ delete: (id) => request(`/admin/apps/${id}`, {
26
+ method: 'DELETE',
27
+ }),
28
+ startPostgrest: (id) => request(`/admin/apps/${id}/start-postgrest`, { method: 'POST', body: JSON.stringify({}) }),
29
+ promotePreview: (id) => request(`/admin/apps/${id}/promote/preview`),
30
+ },
18
31
  databases: {
19
32
  list: () => request('/admin/databases'),
20
33
  get: (id) => request(`/admin/databases/${id}`),
@@ -81,3 +94,22 @@ export const api = {
81
94
  get: (dbId, logId) => request(`/admin/databases/${dbId}/logs/${logId}`),
82
95
  },
83
96
  };
97
+ export async function resolveStagingDatabase(identifier) {
98
+ try {
99
+ const { app } = await api.apps.get(identifier);
100
+ if (app.databases.staging) {
101
+ return {
102
+ databaseId: app.databases.staging.id,
103
+ isApp: true,
104
+ appId: app.id,
105
+ };
106
+ }
107
+ throw new Error(`App "${identifier}" has no staging database`);
108
+ }
109
+ catch {
110
+ return {
111
+ databaseId: identifier,
112
+ isApp: false,
113
+ };
114
+ }
115
+ }
@@ -2,7 +2,7 @@ import { api } from '../api-client.js';
2
2
  export const migrationTools = [
3
3
  {
4
4
  name: 'stage_change',
5
- description: 'Stage a schema change to be applied later. Supports: CREATE_TABLE, DROP_TABLE, ADD_COLUMN, DROP_COLUMN, RENAME_COLUMN, ADD_INDEX, ADD_RLS_POLICY',
5
+ description: 'Stage a schema change to be applied later. Supports: CREATE_TABLE, DROP_TABLE, ADD_COLUMN, DROP_COLUMN, RENAME_COLUMN, ADD_INDEX, ADD_RLS_POLICY. NOTE: For apps with staging/production environments, this targets the STAGING database.',
6
6
  inputSchema: {
7
7
  type: 'object',
8
8
  properties: {
@@ -77,7 +77,7 @@ export const migrationTools = [
77
77
  },
78
78
  {
79
79
  name: 'apply_changes',
80
- description: 'Apply all staged changes as a migration. This executes the DDL on the database.',
80
+ description: 'Apply all staged changes as a migration. This executes the DDL on the database. NOTE: For apps with staging/production environments, this applies to STAGING only. Use preview_promotion and the Admin UI to promote to production.',
81
81
  inputSchema: {
82
82
  type: 'object',
83
83
  properties: {
@@ -17,6 +17,7 @@ export declare const schemaTools: ({
17
17
  enum: string[];
18
18
  description: string;
19
19
  };
20
+ app_id?: undefined;
20
21
  database_id?: undefined;
21
22
  table_name?: undefined;
22
23
  };
@@ -31,11 +32,30 @@ export declare const schemaTools: ({
31
32
  name?: undefined;
32
33
  display_name?: undefined;
33
34
  environment?: undefined;
35
+ app_id?: undefined;
34
36
  database_id?: undefined;
35
37
  table_name?: undefined;
36
38
  };
37
39
  required: never[];
38
40
  };
41
+ } | {
42
+ name: string;
43
+ description: string;
44
+ inputSchema: {
45
+ type: "object";
46
+ properties: {
47
+ app_id: {
48
+ type: string;
49
+ description: string;
50
+ };
51
+ name?: undefined;
52
+ display_name?: undefined;
53
+ environment?: undefined;
54
+ database_id?: undefined;
55
+ table_name?: undefined;
56
+ };
57
+ required: string[];
58
+ };
39
59
  } | {
40
60
  name: string;
41
61
  description: string;
@@ -49,6 +69,7 @@ export declare const schemaTools: ({
49
69
  name?: undefined;
50
70
  display_name?: undefined;
51
71
  environment?: undefined;
72
+ app_id?: undefined;
52
73
  table_name?: undefined;
53
74
  };
54
75
  required: string[];
@@ -70,6 +91,7 @@ export declare const schemaTools: ({
70
91
  name?: undefined;
71
92
  display_name?: undefined;
72
93
  environment?: undefined;
94
+ app_id?: undefined;
73
95
  };
74
96
  required: string[];
75
97
  };
@@ -33,6 +33,20 @@ export const schemaTools = [
33
33
  required: [],
34
34
  },
35
35
  },
36
+ {
37
+ name: 'preview_promotion',
38
+ description: 'Preview what schema changes would be promoted from staging to production for an app. Returns list of migrations and any destructive changes that require confirmation. Actual promotion must be done through the Admin UI.',
39
+ inputSchema: {
40
+ type: 'object',
41
+ properties: {
42
+ app_id: {
43
+ type: 'string',
44
+ description: 'The app ID or name',
45
+ },
46
+ },
47
+ required: ['app_id'],
48
+ },
49
+ },
36
50
  {
37
51
  name: 'get_database_schema',
38
52
  description: 'Get the full schema for a database, including all tables, columns, indexes, constraints, RLS policies, and functions',
@@ -114,43 +128,87 @@ export async function handleSchemaTool(name, args) {
114
128
  case 'create_database': {
115
129
  const dbName = args.name;
116
130
  const displayName = args.display_name;
117
- const environment = args.environment || 'development';
118
- const { database } = await api.databases.create({
131
+ const { app } = await api.apps.create({
119
132
  name: dbName,
120
133
  displayName,
121
- environment,
122
134
  });
135
+ const stagingDb = app.databases.staging;
123
136
  return {
124
137
  content: [
125
138
  {
126
139
  type: 'text',
127
- text: `Database "${database.display_name}" created successfully!
140
+ text: `App "${app.display_name}" created successfully with staging and production environments!
128
141
 
129
142
  ---
130
- **IMPORTANT: Store this database reference for future use in this project:**
131
- - Database ID: ${database.id}
132
- - Database Name: ${database.name}
133
- - Display Name: ${database.display_name}
134
- - Environment: ${database.environment}
143
+ **IMPORTANT: Store this reference for future use in this project:**
144
+ - App ID: ${app.id}
145
+ - App Name: ${app.name}
146
+ - Display Name: ${app.display_name}
147
+ - Staging Database ID: ${stagingDb?.id || 'N/A'}
148
+ - Production Database ID: ${app.databases.production?.id || 'N/A'}
135
149
  - Auth API: ${config.authApiUrl}
136
150
 
137
- Use the Database ID for all subsequent ShareDB operations (stage_change, apply_changes, insert_rows, etc.)
151
+ **NOTE: All MCP operations target the STAGING environment.**
152
+ Use the Staging Database ID for all subsequent ShareDB operations (stage_change, apply_changes, insert_rows, etc.)
153
+ When ready to go live, use the Admin UI to promote staging schema to production.
138
154
  ---
139
155
 
140
- ## API Endpoints for your app
156
+ ## Environment Configuration
157
+
158
+ **Set up separate build scripts for staging and production deployments.**
141
159
 
142
- **Auth API** (${config.authApiUrl}):
160
+ Each environment uses a database-specific REST API URL:
161
+ - **Staging REST API:** ${config.adminApiUrl}/admin/databases/${stagingDb?.id || '<staging_db_id>'}/rest
162
+ - **Production REST API:** ${config.adminApiUrl}/admin/databases/${app.databases.production?.id || '<production_db_id>'}/rest
163
+
164
+ **1. Add build scripts to package.json:**
165
+ \`\`\`json
166
+ {
167
+ "scripts": {
168
+ "dev": "VITE_API_URL=${config.adminApiUrl}/admin/databases/${stagingDb?.id || '<staging_db_id>'}/rest vite",
169
+ "build:staging": "VITE_API_URL=${config.adminApiUrl}/admin/databases/${stagingDb?.id || '<staging_db_id>'}/rest vite build",
170
+ "build:production": "VITE_API_URL=${config.adminApiUrl}/admin/databases/${app.databases.production?.id || '<production_db_id>'}/rest vite build"
171
+ }
172
+ }
173
+ \`\`\`
174
+
175
+ **2. Create a config file in your app:**
176
+ \`\`\`javascript
177
+ // src/config.js
178
+ export const API_URL = import.meta.env.VITE_API_URL;
179
+ export const AUTH_URL = '${config.authApiUrl}/auth';
180
+ \`\`\`
181
+
182
+ **3. Use the config throughout your app:**
183
+ \`\`\`javascript
184
+ import { API_URL, AUTH_URL } from './config';
185
+
186
+ // Auth (shared between environments)
187
+ fetch(\`\${AUTH_URL}/login\`, { method: 'POST', body: JSON.stringify({ email, password }) })
188
+
189
+ // REST API (environment-specific via VITE_API_URL)
190
+ fetch(\`\${API_URL}/todos\`, { headers: { Authorization: \`Bearer \${token}\` } })
191
+ \`\`\`
192
+
193
+ **For deployment:**
194
+ - \`npm run build:staging\` → Deploy to staging
195
+ - \`npm run build:production\` → Deploy to production
196
+
197
+ ---
198
+
199
+ ## API Endpoints
200
+
201
+ **Auth API** (${config.authApiUrl}/auth):
143
202
  - POST /auth/register - Register new user
144
203
  - POST /auth/login - Login, returns { accessToken, refreshToken, user }
145
204
  - POST /auth/refresh - Refresh access token
146
205
  - GET /auth/me - Get current user (requires auth header)
147
206
 
148
- **PostgREST API** (per-database, unique URL):
149
- - Use start_postgrest tool to start the API server
207
+ **REST API** (via VITE_API_URL set by build script):
150
208
  - GET /<table> - List rows
151
209
  - GET /<table>?id=eq.<uuid> - Get single row
152
- - POST /<table> - Create row (IMPORTANT: include user_id in body if table has user_id column!)
153
- - PATCH /<table>?id=eq.<uuid> - Update row
210
+ - POST /<table> - Create row (returns created record)
211
+ - PATCH /<table>?id=eq.<uuid> - Update row (returns updated record)
154
212
  - DELETE /<table>?id=eq.<uuid> - Delete row
155
213
  - Include header: Authorization: Bearer <accessToken>
156
214
 
@@ -158,18 +216,29 @@ Use the Database ID for all subsequent ShareDB operations (stage_change, apply_c
158
216
  - user_id is NOT automatically set by auth or RLS
159
217
  - After login, store the user object: { id, email, name }
160
218
  - When creating records, the client MUST send user_id in the request body
161
- - Example: POST /todos { "title": "Buy milk", "user_id": "<id-from-login>" }
219
+ - Example: POST \${API_URL}/todos { "title": "Buy milk", "user_id": "<id-from-login>" }
162
220
  - RLS policies filter BY user_id but don't SET it
163
221
 
222
+ ## Staging/Production Workflow
223
+ 1. Use stage_change to add tables (targets STAGING only)
224
+ 2. Use apply_changes to create them in staging
225
+ 3. Use start_postgrest to start the staging REST API
226
+ 4. Test your app against staging
227
+ 5. When ready, use the Admin UI to promote schema to production
228
+ 6. Use preview_promotion to see what will be promoted
229
+
164
230
  ## Next steps
165
231
  1. Use stage_change to add tables
166
232
  2. Use apply_changes to create them
167
233
  3. Use start_postgrest to start the REST API
168
234
  4. Implement auth in your app using the Auth API
169
- 5. Query data using the PostgREST API
235
+ 5. Query data using the REST API
170
236
 
171
237
  ---
172
238
  **NOTE FOR ASSISTANT:** ShareDB users are often non-technical builders who need guidance through technical steps. When helping them:
239
+ - ALWAYS set up build:staging and build:production scripts in package.json from the start
240
+ - ALWAYS create a src/config.js that exports API_URL from import.meta.env.VITE_API_URL
241
+ - Never hardcode API URLs directly in components - always import from config.js
173
242
  - Proactively offer to run commands for them (npm install, npm run dev, etc.)
174
243
  - Explain what each step does in simple terms
175
244
  - Don't assume they know how to start dev servers, install dependencies, or debug errors
@@ -180,19 +249,40 @@ Use the Database ID for all subsequent ShareDB operations (stage_change, apply_c
180
249
  };
181
250
  }
182
251
  case 'list_databases': {
183
- const { databases } = await api.databases.list();
184
- const dbList = databases.map(db => `- **${db.display_name}** (${db.environment})\n - ID: ${db.id}\n - Name: ${db.name}`).join('\n');
252
+ const [databasesResult, appsResult] = await Promise.all([
253
+ api.databases.list(),
254
+ api.apps.list(),
255
+ ]);
256
+ const { databases } = databasesResult;
257
+ const { apps } = appsResult;
258
+ const standaloneDbList = databases
259
+ .filter(db => !apps.some(app => app.databases?.some(d => d.id === db.id)))
260
+ .map(db => `- **${db.display_name}** (${db.environment})\n - ID: ${db.id}\n - Name: ${db.name}`).join('\n');
261
+ const appsList = apps.map(app => {
262
+ const staging = app.databases?.find(d => d.environment === 'staging');
263
+ const production = app.databases?.find(d => d.environment === 'production');
264
+ return `- **${app.display_name}**
265
+ - App ID: ${app.id}
266
+ - Staging DB: ${staging?.id || 'N/A'} (${staging?.postgrest_status || 'not started'})
267
+ - Production DB: ${production?.id || 'N/A'} (${production?.postgrest_status || 'not started'})`;
268
+ }).join('\n');
185
269
  return {
186
270
  content: [
187
271
  {
188
272
  type: 'text',
189
- text: `## ShareDB Databases
273
+ text: `## ShareDB Apps & Databases
274
+
275
+ ### Apps (Staging + Production)
276
+ ${apps.length === 0 ? '_No apps yet. Use create_database to create one._' : appsList}
190
277
 
191
- ${databases.length === 0 ? '_No databases yet. Use create_database to create one._' : dbList}
278
+ ${standaloneDbList ? `### Standalone Databases\n${standaloneDbList}` : ''}
192
279
 
193
280
  ---
194
- **IMPORTANT: When working with a specific database, store its ID and name for future operations.**
195
- Use the Database ID for all ShareDB tools (stage_change, apply_changes, insert_rows, query_rows, etc.)
281
+ **IMPORTANT: All MCP operations target STAGING environments.**
282
+ - When working with an app, use the Staging Database ID for all operations
283
+ - Schema changes are applied to staging first
284
+ - Use preview_promotion to see what will be promoted to production
285
+ - Actual promotion to production must be done through the Admin UI
196
286
  ---
197
287
 
198
288
  ## API Endpoints
@@ -208,6 +298,48 @@ Use get_api_endpoints with database_id for full endpoint documentation.
208
298
  ],
209
299
  };
210
300
  }
301
+ case 'preview_promotion': {
302
+ const appId = args.app_id;
303
+ const { preview, message } = await api.apps.promotePreview(appId);
304
+ if (!preview) {
305
+ return {
306
+ content: [
307
+ {
308
+ type: 'text',
309
+ text: message || 'Production is already up to date with staging. No changes to promote.',
310
+ },
311
+ ],
312
+ };
313
+ }
314
+ const migrationsList = preview.migrationsToApply
315
+ .map(m => `- v${m.version}: ${m.description || 'No description'} (${m.changeCount} changes)`)
316
+ .join('\n');
317
+ const destructiveList = preview.destructiveChanges.length > 0
318
+ ? `\n\n**WARNING - Destructive Changes:**\n${preview.destructiveChanges.map(d => `- ${d.description}`).join('\n')}`
319
+ : '';
320
+ return {
321
+ content: [
322
+ {
323
+ type: 'text',
324
+ text: `## Promotion Preview: Staging → Production
325
+
326
+ **Staging:** v${preview.staging.migrationVersion}
327
+ **Production:** v${preview.production.migrationVersion}
328
+
329
+ ### Migrations to Apply (${preview.totalMigrations})
330
+ ${migrationsList}
331
+
332
+ **Total Changes:** ${preview.totalChanges}
333
+ ${destructiveList}
334
+ ${preview.hasDestructiveChanges ? '\n**This promotion includes destructive changes that will delete data from production.**' : ''}
335
+
336
+ ---
337
+ **To promote these changes to production, use the Admin UI.**
338
+ The Admin UI will show a confirmation dialog for destructive changes.`,
339
+ },
340
+ ],
341
+ };
342
+ }
211
343
  case 'get_database_schema': {
212
344
  const databaseId = args.database_id;
213
345
  const schema = await api.databases.schema(databaseId);
@@ -255,21 +387,21 @@ Use get_api_endpoints with database_id for full endpoint documentation.
255
387
  text: `${result.message}
256
388
 
257
389
  ---
258
- **Store this PostgREST URL for this database:**
259
- - PostgREST URL: ${result.postgrestUrl}
260
- - Port: ${result.port}
390
+ **PostgREST is now running:**
391
+ - Direct URL (for MCP tools): ${result.postgrestUrl}
261
392
  - Status: ${result.status}
262
393
 
263
- This URL is used for direct REST API access to the database tables.
394
+ **In the app, use the API_URL from config.js** (which reads from VITE_API_URL set by the build script).
264
395
  ---
265
396
 
266
397
  You can now use insert_rows, query_rows, update_rows, and delete_rows with this database.
267
398
 
268
- **NOTE FOR ASSISTANT:** The backend is now ready. If the user has a frontend app, help them start it:
269
- - For React/Vite: \`npm install\` then \`npm run dev\`
270
- - For Next.js: \`npm install\` then \`npm run dev\`
271
- - Offer to run these commands for them and explain what's happening
272
- - If errors occur, help debug and check query_logs for backend issues`,
399
+ **NOTE FOR ASSISTANT:** The backend is now ready. If the user has a frontend app:
400
+ - Ensure package.json has build:staging and build:production scripts
401
+ - Ensure src/config.js exports API_URL from import.meta.env.VITE_API_URL
402
+ - Run \`npm install\` then \`npm run dev\`
403
+ - Offer to run these commands for them
404
+ - If errors occur, check query_logs for backend issues`,
273
405
  },
274
406
  ],
275
407
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@daylight-labs/sharedb-mcp",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "MCP server for ShareDB schema management and development",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",