@daylight-labs/sharedb-mcp 0.1.2 → 0.1.3

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.
@@ -139,6 +139,43 @@ export interface PromotionPreview {
139
139
  totalChanges: number;
140
140
  hasDestructiveChanges: boolean;
141
141
  }
142
+ export interface DeploymentInfo {
143
+ id: string;
144
+ version: number;
145
+ fileCount: number;
146
+ totalSizeBytes: number;
147
+ }
148
+ export interface HostingStatus {
149
+ enabled: boolean;
150
+ url: string | null;
151
+ deployment: {
152
+ id: string;
153
+ version: number;
154
+ fileCount: number;
155
+ totalSizeBytes: number;
156
+ deployedAt: string;
157
+ } | null;
158
+ customDomains: Array<{
159
+ id: string;
160
+ domain: string;
161
+ status: string;
162
+ verified_at: string | null;
163
+ created_at: string;
164
+ }>;
165
+ }
166
+ export interface CustomDomainResult {
167
+ domain: {
168
+ id: string;
169
+ domain: string;
170
+ status: string;
171
+ };
172
+ dns: {
173
+ type: string;
174
+ name: string;
175
+ value: string;
176
+ instructions: string;
177
+ };
178
+ }
142
179
  export declare const api: {
143
180
  apps: {
144
181
  list: () => Promise<{
@@ -276,6 +313,22 @@ export declare const api: {
276
313
  helperFunction: string;
277
314
  }>;
278
315
  };
316
+ hosting: {
317
+ deploy: (appId: string, tarPath: string) => Promise<{
318
+ deployment: DeploymentInfo;
319
+ url: string | null;
320
+ }>;
321
+ status: (appId: string) => Promise<{
322
+ hosting: HostingStatus;
323
+ }>;
324
+ addDomain: (appId: string, domain: string) => Promise<CustomDomainResult>;
325
+ removeDomain: (appId: string, domainId: string) => Promise<{
326
+ deleted: {
327
+ id: string;
328
+ domain: string;
329
+ };
330
+ }>;
331
+ };
279
332
  logs: {
280
333
  query: (dbId: string, options?: LogQueryOptions) => Promise<{
281
334
  logs: LogEntry[];
@@ -1,3 +1,4 @@
1
+ import fs from 'node:fs';
1
2
  import { config } from './config.js';
2
3
  async function request(path, options) {
3
4
  const response = await fetch(`${config.adminApiUrl}${path}`, {
@@ -14,6 +15,24 @@ async function request(path, options) {
14
15
  }
15
16
  return response.json();
16
17
  }
18
+ async function uploadFile(path, filePath) {
19
+ const fileBuffer = fs.readFileSync(filePath);
20
+ const blob = new Blob([fileBuffer], { type: 'application/gzip' });
21
+ const formData = new FormData();
22
+ formData.append('file', blob, 'bundle.tar.gz');
23
+ const response = await fetch(`${config.adminApiUrl}${path}`, {
24
+ method: 'POST',
25
+ headers: {
26
+ Authorization: `Bearer ${config.token}`,
27
+ },
28
+ body: formData,
29
+ });
30
+ if (!response.ok) {
31
+ const error = await response.json().catch(() => ({ error: 'Upload failed' }));
32
+ throw new Error(error.error || `Upload failed: ${response.status}`);
33
+ }
34
+ return response.json();
35
+ }
17
36
  export const api = {
18
37
  apps: {
19
38
  list: () => request('/admin/apps'),
@@ -75,6 +94,15 @@ export const api = {
75
94
  auth: {
76
95
  config: () => request('/admin/auth/config'),
77
96
  },
97
+ hosting: {
98
+ deploy: (appId, tarPath) => uploadFile(`/admin/apps/${appId}/deploy`, tarPath),
99
+ status: (appId) => request(`/admin/apps/${appId}/hosting`),
100
+ addDomain: (appId, domain) => request(`/admin/apps/${appId}/domains`, {
101
+ method: 'POST',
102
+ body: JSON.stringify({ domain }),
103
+ }),
104
+ removeDomain: (appId, domainId) => request(`/admin/apps/${appId}/domains/${domainId}`, { method: 'DELETE' }),
105
+ },
78
106
  logs: {
79
107
  query: (dbId, options = {}) => {
80
108
  const params = new URLSearchParams();
package/dist/index.js CHANGED
@@ -7,6 +7,7 @@ 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
9
  import { logsTools, handleLogsTool } from './tools/logs.js';
10
+ import { hostingTools, handleHostingTool } from './tools/hosting.js';
10
11
  import { resources, readResource } from './resources/index.js';
11
12
  const server = new Server({
12
13
  name: 'sharedb',
@@ -17,7 +18,7 @@ const server = new Server({
17
18
  resources: {},
18
19
  },
19
20
  });
20
- const allTools = [...schemaTools, ...migrationTools, ...authTools, ...dataTools, ...logsTools];
21
+ const allTools = [...schemaTools, ...migrationTools, ...authTools, ...dataTools, ...logsTools, ...hostingTools];
21
22
  server.setRequestHandler(ListToolsRequestSchema, async () => {
22
23
  return { tools: allTools };
23
24
  });
@@ -43,6 +44,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
43
44
  if (logsTool) {
44
45
  return handleLogsTool(name, args || {});
45
46
  }
47
+ const hostingTool = hostingTools.find((t) => t.name === name);
48
+ if (hostingTool) {
49
+ return handleHostingTool(name, args || {});
50
+ }
46
51
  return {
47
52
  content: [{ type: 'text', text: `Unknown tool: ${name}` }],
48
53
  isError: true,
@@ -0,0 +1,81 @@
1
+ export declare const hostingTools: ({
2
+ name: string;
3
+ description: string;
4
+ inputSchema: {
5
+ type: "object";
6
+ properties: {
7
+ app_id: {
8
+ type: string;
9
+ description: string;
10
+ };
11
+ dist_path: {
12
+ type: string;
13
+ description: string;
14
+ };
15
+ domain?: undefined;
16
+ domain_id?: undefined;
17
+ };
18
+ required: string[];
19
+ };
20
+ } | {
21
+ name: string;
22
+ description: string;
23
+ inputSchema: {
24
+ type: "object";
25
+ properties: {
26
+ app_id: {
27
+ type: string;
28
+ description: string;
29
+ };
30
+ dist_path?: undefined;
31
+ domain?: undefined;
32
+ domain_id?: undefined;
33
+ };
34
+ required: string[];
35
+ };
36
+ } | {
37
+ name: string;
38
+ description: string;
39
+ inputSchema: {
40
+ type: "object";
41
+ properties: {
42
+ app_id: {
43
+ type: string;
44
+ description: string;
45
+ };
46
+ domain: {
47
+ type: string;
48
+ description: string;
49
+ };
50
+ dist_path?: undefined;
51
+ domain_id?: undefined;
52
+ };
53
+ required: string[];
54
+ };
55
+ } | {
56
+ name: string;
57
+ description: string;
58
+ inputSchema: {
59
+ type: "object";
60
+ properties: {
61
+ app_id: {
62
+ type: string;
63
+ description: string;
64
+ };
65
+ domain_id: {
66
+ type: string;
67
+ description: string;
68
+ };
69
+ dist_path?: undefined;
70
+ domain?: undefined;
71
+ };
72
+ required: string[];
73
+ };
74
+ })[];
75
+ export declare function handleHostingTool(name: string, args: Record<string, unknown>): Promise<{
76
+ content: Array<{
77
+ type: 'text';
78
+ text: string;
79
+ }>;
80
+ isError?: boolean;
81
+ }>;
@@ -0,0 +1,224 @@
1
+ import { execSync } from 'node:child_process';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { api } from '../api-client.js';
5
+ export const hostingTools = [
6
+ {
7
+ name: 'deploy_app',
8
+ description: 'Deploy a local directory (e.g., a Vite dist folder) as a static hosted app. Creates a tar.gz of the directory and uploads it. The app will be accessible at <app-name>.<tenant-domain>.',
9
+ inputSchema: {
10
+ type: 'object',
11
+ properties: {
12
+ app_id: {
13
+ type: 'string',
14
+ description: 'The app ID or name',
15
+ },
16
+ dist_path: {
17
+ type: 'string',
18
+ description: 'Absolute path to the local dist/build directory to deploy (e.g., /path/to/my-app/dist)',
19
+ },
20
+ },
21
+ required: ['app_id', 'dist_path'],
22
+ },
23
+ },
24
+ {
25
+ name: 'get_hosting_status',
26
+ description: 'Get the hosting status for an app, including the live URL, current deployment info, and custom domains.',
27
+ inputSchema: {
28
+ type: 'object',
29
+ properties: {
30
+ app_id: {
31
+ type: 'string',
32
+ description: 'The app ID or name',
33
+ },
34
+ },
35
+ required: ['app_id'],
36
+ },
37
+ },
38
+ {
39
+ name: 'add_custom_domain',
40
+ description: 'Add a custom domain to a hosted app. Returns DNS configuration instructions. SSL is provisioned automatically via on-demand TLS once DNS propagates.',
41
+ inputSchema: {
42
+ type: 'object',
43
+ properties: {
44
+ app_id: {
45
+ type: 'string',
46
+ description: 'The app ID or name',
47
+ },
48
+ domain: {
49
+ type: 'string',
50
+ description: 'The custom domain to add (e.g., "myapp.com" or "app.mycompany.com")',
51
+ },
52
+ },
53
+ required: ['app_id', 'domain'],
54
+ },
55
+ },
56
+ {
57
+ name: 'remove_custom_domain',
58
+ description: 'Remove a custom domain from a hosted app.',
59
+ inputSchema: {
60
+ type: 'object',
61
+ properties: {
62
+ app_id: {
63
+ type: 'string',
64
+ description: 'The app ID or name',
65
+ },
66
+ domain_id: {
67
+ type: 'string',
68
+ description: 'The domain ID to remove',
69
+ },
70
+ },
71
+ required: ['app_id', 'domain_id'],
72
+ },
73
+ },
74
+ ];
75
+ export async function handleHostingTool(name, args) {
76
+ try {
77
+ switch (name) {
78
+ case 'deploy_app': {
79
+ const appId = args.app_id;
80
+ const distPath = args.dist_path;
81
+ if (!fs.existsSync(distPath)) {
82
+ return {
83
+ content: [{ type: 'text', text: `Error: Directory not found: ${distPath}` }],
84
+ isError: true,
85
+ };
86
+ }
87
+ const stat = fs.statSync(distPath);
88
+ if (!stat.isDirectory()) {
89
+ return {
90
+ content: [{ type: 'text', text: `Error: Path is not a directory: ${distPath}` }],
91
+ isError: true,
92
+ };
93
+ }
94
+ const tarPath = path.join(path.dirname(distPath), `.deploy-${Date.now()}.tar.gz`);
95
+ try {
96
+ execSync(`tar -czf "${tarPath}" -C "${distPath}" .`, { stdio: 'pipe' });
97
+ const result = await api.hosting.deploy(appId, tarPath);
98
+ return {
99
+ content: [
100
+ {
101
+ type: 'text',
102
+ text: `App deployed successfully!
103
+
104
+ **Deployment Details:**
105
+ - Version: v${result.deployment.version}
106
+ - Files: ${result.deployment.fileCount}
107
+ - Size: ${formatBytes(result.deployment.totalSizeBytes)}
108
+ ${result.url ? `\n**Live URL:** ${result.url}` : ''}
109
+
110
+ The app is now live and serving static files.`,
111
+ },
112
+ ],
113
+ };
114
+ }
115
+ finally {
116
+ try {
117
+ fs.unlinkSync(tarPath);
118
+ }
119
+ catch { }
120
+ }
121
+ }
122
+ case 'get_hosting_status': {
123
+ const appId = args.app_id;
124
+ const { hosting } = await api.hosting.status(appId);
125
+ if (!hosting.enabled) {
126
+ return {
127
+ content: [
128
+ {
129
+ type: 'text',
130
+ text: `Hosting is not yet enabled for this app. Deploy a build using deploy_app to enable hosting.`,
131
+ },
132
+ ],
133
+ };
134
+ }
135
+ const domainsList = hosting.customDomains?.length
136
+ ? hosting.customDomains
137
+ .map((d) => `- ${d.domain} (${d.status})`)
138
+ .join('\n')
139
+ : '_No custom domains configured_';
140
+ return {
141
+ content: [
142
+ {
143
+ type: 'text',
144
+ text: `## Hosting Status
145
+
146
+ **URL:** ${hosting.url || 'N/A'}
147
+ **Enabled:** ${hosting.enabled}
148
+
149
+ ### Current Deployment
150
+ ${hosting.deployment
151
+ ? `- Version: v${hosting.deployment.version}
152
+ - Files: ${hosting.deployment.fileCount}
153
+ - Size: ${formatBytes(hosting.deployment.totalSizeBytes)}
154
+ - Deployed: ${hosting.deployment.deployedAt}`
155
+ : '_No active deployment_'}
156
+
157
+ ### Custom Domains
158
+ ${domainsList}`,
159
+ },
160
+ ],
161
+ };
162
+ }
163
+ case 'add_custom_domain': {
164
+ const appId = args.app_id;
165
+ const domain = args.domain;
166
+ const result = await api.hosting.addDomain(appId, domain);
167
+ return {
168
+ content: [
169
+ {
170
+ type: 'text',
171
+ text: `Custom domain "${domain}" added.
172
+
173
+ **DNS Configuration Required:**
174
+ - Record Type: ${result.dns.type}
175
+ - Name: ${result.dns.name}
176
+ - Value: ${result.dns.value}
177
+
178
+ ${result.dns.instructions}
179
+
180
+ Domain ID: ${result.domain.id}`,
181
+ },
182
+ ],
183
+ };
184
+ }
185
+ case 'remove_custom_domain': {
186
+ const appId = args.app_id;
187
+ const domainId = args.domain_id;
188
+ await api.hosting.removeDomain(appId, domainId);
189
+ return {
190
+ content: [
191
+ {
192
+ type: 'text',
193
+ text: `Custom domain removed successfully.`,
194
+ },
195
+ ],
196
+ };
197
+ }
198
+ default:
199
+ return {
200
+ content: [{ type: 'text', text: `Unknown hosting tool: ${name}` }],
201
+ isError: true,
202
+ };
203
+ }
204
+ }
205
+ catch (error) {
206
+ return {
207
+ content: [
208
+ {
209
+ type: 'text',
210
+ text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
211
+ },
212
+ ],
213
+ isError: true,
214
+ };
215
+ }
216
+ }
217
+ function formatBytes(bytes) {
218
+ if (bytes === 0)
219
+ return '0 B';
220
+ const k = 1024;
221
+ const sizes = ['B', 'KB', 'MB', 'GB'];
222
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
223
+ return `${parseFloat((bytes / Math.pow(k, i)).toFixed(1))} ${sizes[i]}`;
224
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@daylight-labs/sharedb-mcp",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "MCP server for ShareDB schema management and development",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",