@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.
- package/dist/api-client.d.ts +120 -0
- package/dist/api-client.js +50 -0
- package/dist/config.js +3 -2
- package/dist/index.js +6 -1
- package/dist/tools/auth.js +35 -0
- package/dist/tools/logs.d.ts +66 -0
- package/dist/tools/logs.js +132 -0
- package/dist/tools/migrations.js +2 -2
- package/dist/tools/schema.d.ts +22 -0
- package/dist/tools/schema.js +185 -29
- package/package.json +1 -1
package/dist/api-client.d.ts
CHANGED
|
@@ -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
|
+
}>;
|
package/dist/api-client.js
CHANGED
|
@@ -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,
|
package/dist/tools/auth.js
CHANGED
|
@@ -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
|
+
}
|
package/dist/tools/migrations.js
CHANGED
|
@@ -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: {
|
package/dist/tools/schema.d.ts
CHANGED
|
@@ -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
|
};
|
package/dist/tools/schema.js
CHANGED
|
@@ -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
|
|
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: `
|
|
140
|
+
text: `App "${app.display_name}" created successfully with staging and production environments!
|
|
128
141
|
|
|
129
142
|
---
|
|
130
|
-
**IMPORTANT: Store this
|
|
131
|
-
-
|
|
132
|
-
-
|
|
133
|
-
- Display Name: ${
|
|
134
|
-
-
|
|
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
|
-
|
|
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
|
|
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
|
-
**
|
|
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
|
|
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
|
|
169
|
-
|
|
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
|
-
${
|
|
278
|
+
${standaloneDbList ? `### Standalone Databases\n${standaloneDbList}` : ''}
|
|
177
279
|
|
|
178
280
|
---
|
|
179
|
-
**IMPORTANT:
|
|
180
|
-
|
|
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
|
-
**
|
|
241
|
-
-
|
|
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
|
-
|
|
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
|
};
|