@daylight-labs/sharedb-mcp 0.1.0 → 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.
@@ -75,7 +75,111 @@ export interface PostgRESTInfo {
75
75
  containerName?: string;
76
76
  startedAt?: string;
77
77
  }
78
+ export interface LogEntry {
79
+ id: string;
80
+ database_id: string;
81
+ level: 'error' | 'debug';
82
+ message: string;
83
+ metadata: Record<string, unknown>;
84
+ source: 'admin-api' | 'admin-ui' | 'user-app' | 'postgrest';
85
+ request_id: string | null;
86
+ created_at: string;
87
+ }
88
+ export interface LogQueryOptions {
89
+ level?: string;
90
+ source?: string;
91
+ since?: string;
92
+ until?: string;
93
+ limit?: number;
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
+ }
78
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
+ };
79
183
  databases: {
80
184
  list: () => Promise<{
81
185
  databases: Database[];
@@ -172,4 +276,20 @@ export declare const api: {
172
276
  helperFunction: string;
173
277
  }>;
174
278
  };
279
+ logs: {
280
+ query: (dbId: string, options?: LogQueryOptions) => Promise<{
281
+ logs: LogEntry[];
282
+ total: number;
283
+ limit: number;
284
+ offset: number;
285
+ }>;
286
+ get: (dbId: string, logId: string) => Promise<{
287
+ log: LogEntry;
288
+ }>;
289
+ };
175
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}`),
@@ -62,4 +75,41 @@ export const api = {
62
75
  auth: {
63
76
  config: () => request('/admin/auth/config'),
64
77
  },
78
+ logs: {
79
+ query: (dbId, options = {}) => {
80
+ const params = new URLSearchParams();
81
+ if (options.level)
82
+ params.append('level', options.level);
83
+ if (options.source)
84
+ params.append('source', options.source);
85
+ if (options.since)
86
+ params.append('since', options.since);
87
+ if (options.until)
88
+ params.append('until', options.until);
89
+ if (options.limit)
90
+ params.append('limit', options.limit.toString());
91
+ const query = params.toString();
92
+ return request(`/admin/databases/${dbId}/logs${query ? `?${query}` : ''}`);
93
+ },
94
+ get: (dbId, logId) => request(`/admin/databases/${dbId}/logs/${logId}`),
95
+ },
65
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
+ }
package/dist/config.js CHANGED
@@ -1,5 +1,6 @@
1
+ const domain = process.env.SHAREDB_DOMAIN;
1
2
  export const config = {
2
- adminApiUrl: process.env.SHAREDB_API_URL || 'http://localhost:3002',
3
- authApiUrl: process.env.SHAREDB_AUTH_URL || 'http://localhost:3000',
3
+ adminApiUrl: process.env.SHAREDB_API_URL || (domain ? `https://${domain}/api` : 'http://localhost:3002'),
4
+ authApiUrl: process.env.SHAREDB_AUTH_URL || (domain ? `https://${domain}/api` : 'http://localhost:3000'),
4
5
  token: process.env.SHAREDB_TOKEN || '',
5
6
  };
package/dist/index.js CHANGED
@@ -6,6 +6,7 @@ import { schemaTools, handleSchemaTool } from './tools/schema.js';
6
6
  import { migrationTools, handleMigrationTool } from './tools/migrations.js';
7
7
  import { authTools, handleAuthTool } from './tools/auth.js';
8
8
  import { dataTools, handleDataTool } from './tools/data.js';
9
+ import { logsTools, handleLogsTool } from './tools/logs.js';
9
10
  import { resources, readResource } from './resources/index.js';
10
11
  const server = new Server({
11
12
  name: 'sharedb',
@@ -16,7 +17,7 @@ const server = new Server({
16
17
  resources: {},
17
18
  },
18
19
  });
19
- const allTools = [...schemaTools, ...migrationTools, ...authTools, ...dataTools];
20
+ const allTools = [...schemaTools, ...migrationTools, ...authTools, ...dataTools, ...logsTools];
20
21
  server.setRequestHandler(ListToolsRequestSchema, async () => {
21
22
  return { tools: allTools };
22
23
  });
@@ -38,6 +39,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
38
39
  if (dataTool) {
39
40
  return handleDataTool(name, args || {});
40
41
  }
42
+ const logsTool = logsTools.find((t) => t.name === name);
43
+ if (logsTool) {
44
+ return handleLogsTool(name, args || {});
45
+ }
41
46
  return {
42
47
  content: [{ type: 'text', text: `Unknown tool: ${name}` }],
43
48
  isError: true,
@@ -69,6 +69,11 @@ export async function handleAuthTool(name, args) {
69
69
  logout: `POST ${config.authApiUrl}/auth/logout`,
70
70
  me: `GET ${config.authApiUrl}/auth/me`,
71
71
  },
72
+ importantNotes: [
73
+ 'After login, store the accessToken and user object (contains user.id)',
74
+ 'Include accessToken in all API requests: Authorization: Bearer <accessToken>',
75
+ 'Use GET /auth/me to fetch current user if needed (returns { user: { id, email, name } })',
76
+ ],
72
77
  },
73
78
  postgrest: {
74
79
  baseUrl: postgrestUrl,
@@ -83,6 +88,36 @@ export async function handleAuthTool(name, args) {
83
88
  delete: `DELETE ${postgrestUrl}/<table>?id=eq.<uuid>`,
84
89
  },
85
90
  },
91
+ importantNotes: [
92
+ 'CRITICAL: user_id is NOT automatically set! When creating records with a user_id column, the client MUST include user_id in the request body.',
93
+ 'Get the user ID from login response (user.id) or GET /auth/me, then include it: POST /todos { "title": "...", "user_id": "<user-id-from-auth>" }',
94
+ 'RLS policies filter BY user_id but do not SET it - the client must always send user_id explicitly.',
95
+ ],
96
+ },
97
+ logging: {
98
+ description: 'DEVELOPER DEBUGGING ONLY - Error and debug logging for app development. Use this to catch and debug errors during development. Logs are stored for 7 days and viewable in the admin UI. NOT for end-user analytics or production user tracking.',
99
+ endpoint: `POST ${config.adminApiUrl}/logs`,
100
+ authentication: 'None required (public endpoint, rate limited to 100/min per database)',
101
+ requestBody: {
102
+ database_id: '<your-database-uuid> (required)',
103
+ level: '"error" | "debug" (required)',
104
+ message: 'string (required)',
105
+ metadata: 'object (optional) - additional context like stack traces, component names, etc.',
106
+ source: '"user-app" (required for client apps)',
107
+ request_id: 'string (optional) - for correlating related logs',
108
+ },
109
+ example: `fetch('${config.adminApiUrl}/logs', {
110
+ method: 'POST',
111
+ headers: { 'Content-Type': 'application/json' },
112
+ body: JSON.stringify({
113
+ database_id: '<your-database-uuid>',
114
+ level: 'error',
115
+ message: 'Failed to load user data',
116
+ metadata: { component: 'UserProfile', error: err.message, stack: err.stack },
117
+ source: 'user-app'
118
+ })
119
+ })`,
120
+ recommendation: 'Add to your global error handler (window.onerror, React error boundaries) during development to catch unhandled errors.',
86
121
  },
87
122
  };
88
123
  return {
@@ -0,0 +1,66 @@
1
+ export declare const logsTools: ({
2
+ name: string;
3
+ description: string;
4
+ inputSchema: {
5
+ type: "object";
6
+ properties: {
7
+ database_id: {
8
+ type: string;
9
+ description: string;
10
+ };
11
+ level: {
12
+ type: string;
13
+ enum: string[];
14
+ description: string;
15
+ };
16
+ source: {
17
+ type: string;
18
+ enum: string[];
19
+ description: string;
20
+ };
21
+ since: {
22
+ type: string;
23
+ description: string;
24
+ };
25
+ until: {
26
+ type: string;
27
+ description: string;
28
+ };
29
+ limit: {
30
+ type: string;
31
+ description: string;
32
+ };
33
+ log_id?: undefined;
34
+ };
35
+ required: string[];
36
+ };
37
+ } | {
38
+ name: string;
39
+ description: string;
40
+ inputSchema: {
41
+ type: "object";
42
+ properties: {
43
+ database_id: {
44
+ type: string;
45
+ description: string;
46
+ };
47
+ log_id: {
48
+ type: string;
49
+ description: string;
50
+ };
51
+ level?: undefined;
52
+ source?: undefined;
53
+ since?: undefined;
54
+ until?: undefined;
55
+ limit?: undefined;
56
+ };
57
+ required: string[];
58
+ };
59
+ })[];
60
+ export declare function handleLogsTool(name: string, args: Record<string, unknown>): Promise<{
61
+ content: Array<{
62
+ type: 'text';
63
+ text: string;
64
+ }>;
65
+ isError?: boolean;
66
+ }>;
@@ -0,0 +1,132 @@
1
+ import { api } from '../api-client.js';
2
+ export const logsTools = [
3
+ {
4
+ name: 'query_logs',
5
+ description: 'Query error and debug logs for a database. FOR DEVELOPER DEBUGGING ONLY - helps you debug issues during app development. View client-side errors, PostgREST API errors, or backend failures. Logs are retained for 7 days. Filter by source: "user-app" for client app errors, "postgrest" for API errors, "admin-api" for backend errors.',
6
+ inputSchema: {
7
+ type: 'object',
8
+ properties: {
9
+ database_id: {
10
+ type: 'string',
11
+ description: 'The database ID or name to query logs for',
12
+ },
13
+ level: {
14
+ type: 'string',
15
+ enum: ['error', 'debug'],
16
+ description: 'Filter by log level',
17
+ },
18
+ source: {
19
+ type: 'string',
20
+ enum: ['admin-api', 'admin-ui', 'user-app', 'postgrest'],
21
+ description: 'Filter by log source: user-app (client apps), postgrest (API errors), admin-api (backend), admin-ui (admin dashboard)',
22
+ },
23
+ since: {
24
+ type: 'string',
25
+ description: 'ISO 8601 datetime to filter logs after (e.g., "2024-01-01T00:00:00Z")',
26
+ },
27
+ until: {
28
+ type: 'string',
29
+ description: 'ISO 8601 datetime to filter logs before',
30
+ },
31
+ limit: {
32
+ type: 'number',
33
+ description: 'Maximum number of logs to return (default: 100, max: 1000)',
34
+ },
35
+ },
36
+ required: ['database_id'],
37
+ },
38
+ },
39
+ {
40
+ name: 'get_log_details',
41
+ description: 'Get full details of a specific log entry including all metadata (stack traces, request context, etc.).',
42
+ inputSchema: {
43
+ type: 'object',
44
+ properties: {
45
+ database_id: {
46
+ type: 'string',
47
+ description: 'The database ID or name',
48
+ },
49
+ log_id: {
50
+ type: 'string',
51
+ description: 'The log entry ID',
52
+ },
53
+ },
54
+ required: ['database_id', 'log_id'],
55
+ },
56
+ },
57
+ ];
58
+ export async function handleLogsTool(name, args) {
59
+ try {
60
+ switch (name) {
61
+ case 'query_logs': {
62
+ const databaseId = args.database_id;
63
+ const level = args.level;
64
+ const source = args.source;
65
+ const since = args.since;
66
+ const until = args.until;
67
+ const limit = args.limit;
68
+ const result = await api.logs.query(databaseId, {
69
+ level,
70
+ source,
71
+ since,
72
+ until,
73
+ limit,
74
+ });
75
+ if (result.logs.length === 0) {
76
+ return {
77
+ content: [
78
+ {
79
+ type: 'text',
80
+ text: `No logs found for database "${databaseId}" with the specified filters.`,
81
+ },
82
+ ],
83
+ };
84
+ }
85
+ const summary = result.logs.map((log) => ({
86
+ id: log.id,
87
+ level: log.level,
88
+ source: log.source,
89
+ message: log.message.substring(0, 200) + (log.message.length > 200 ? '...' : ''),
90
+ created_at: log.created_at,
91
+ }));
92
+ return {
93
+ content: [
94
+ {
95
+ type: 'text',
96
+ text: `Found ${result.total} logs (showing ${result.logs.length}):\n\n${JSON.stringify(summary, null, 2)}`,
97
+ },
98
+ ],
99
+ };
100
+ }
101
+ case 'get_log_details': {
102
+ const databaseId = args.database_id;
103
+ const logId = args.log_id;
104
+ const result = await api.logs.get(databaseId, logId);
105
+ return {
106
+ content: [
107
+ {
108
+ type: 'text',
109
+ text: `Log details:\n\n${JSON.stringify(result.log, null, 2)}`,
110
+ },
111
+ ],
112
+ };
113
+ }
114
+ default:
115
+ return {
116
+ content: [{ type: 'text', text: `Unknown logs tool: ${name}` }],
117
+ isError: true,
118
+ };
119
+ }
120
+ }
121
+ catch (error) {
122
+ return {
123
+ content: [
124
+ {
125
+ type: 'text',
126
+ text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
127
+ },
128
+ ],
129
+ isError: true,
130
+ };
131
+ }
132
+ }
@@ -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,70 +128,161 @@ 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.
154
+ ---
155
+
156
+ ## Environment Configuration
157
+
158
+ **Set up separate build scripts for staging and production deployments.**
159
+
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
+
138
197
  ---
139
198
 
140
- ## API Endpoints for your app
199
+ ## API Endpoints
141
200
 
142
- **Auth API** (${config.authApiUrl}):
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
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
 
215
+ **CRITICAL - user_id handling:**
216
+ - user_id is NOT automatically set by auth or RLS
217
+ - After login, store the user object: { id, email, name }
218
+ - When creating records, the client MUST send user_id in the request body
219
+ - Example: POST \${API_URL}/todos { "title": "Buy milk", "user_id": "<id-from-login>" }
220
+ - RLS policies filter BY user_id but don't SET it
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
+
157
230
  ## Next steps
158
231
  1. Use stage_change to add tables
159
232
  2. Use apply_changes to create them
160
233
  3. Use start_postgrest to start the REST API
161
234
  4. Implement auth in your app using the Auth API
162
- 5. Query data using the PostgREST API`,
235
+ 5. Query data using the REST API
236
+
237
+ ---
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
242
+ - Proactively offer to run commands for them (npm install, npm run dev, etc.)
243
+ - Explain what each step does in simple terms
244
+ - Don't assume they know how to start dev servers, install dependencies, or debug errors
245
+ - Guide them step-by-step through running their app locally
246
+ - If something fails, check logs with query_logs and explain the issue clearly`,
163
247
  },
164
248
  ],
165
249
  };
166
250
  }
167
251
  case 'list_databases': {
168
- const { databases } = await api.databases.list();
169
- 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');
170
269
  return {
171
270
  content: [
172
271
  {
173
272
  type: 'text',
174
- 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}
175
277
 
176
- ${databases.length === 0 ? '_No databases yet. Use create_database to create one._' : dbList}
278
+ ${standaloneDbList ? `### Standalone Databases\n${standaloneDbList}` : ''}
177
279
 
178
280
  ---
179
- **IMPORTANT: When working with a specific database, store its ID and name for future operations.**
180
- 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
181
286
  ---
182
287
 
183
288
  ## API Endpoints
@@ -185,7 +290,52 @@ Use the Database ID for all ShareDB tools (stage_change, apply_changes, insert_r
185
290
  **Auth API**: ${config.authApiUrl}
186
291
  **PostgREST API**: Each database has its own PostgREST instance (use start_postgrest to get the URL).
187
292
 
188
- Use get_api_endpoints with database_id for full endpoint documentation.`,
293
+ Use get_api_endpoints with database_id for full endpoint documentation.
294
+
295
+ ---
296
+ **NOTE FOR ASSISTANT:** ShareDB users are often non-technical builders. Proactively help them with technical tasks like running commands, starting dev servers, and debugging. Explain things in simple terms.`,
297
+ },
298
+ ],
299
+ };
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.`,
189
339
  },
190
340
  ],
191
341
  };
@@ -237,15 +387,21 @@ Use get_api_endpoints with database_id for full endpoint documentation.`,
237
387
  text: `${result.message}
238
388
 
239
389
  ---
240
- **Store this PostgREST URL for this database:**
241
- - PostgREST URL: ${result.postgrestUrl}
242
- - Port: ${result.port}
390
+ **PostgREST is now running:**
391
+ - Direct URL (for MCP tools): ${result.postgrestUrl}
243
392
  - Status: ${result.status}
244
393
 
245
- 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).
246
395
  ---
247
396
 
248
- You can now use insert_rows, query_rows, update_rows, and delete_rows with this database.`,
397
+ You can now use insert_rows, query_rows, update_rows, and delete_rows with this database.
398
+
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`,
249
405
  },
250
406
  ],
251
407
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@daylight-labs/sharedb-mcp",
3
- "version": "0.1.0",
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",