@gakim-digital/dexter-bridge 0.5.21 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,240 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
4
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
5
+ import { z } from 'zod';
6
+ import { BRIDGE_VERSION } from './config.js';
7
+
8
+ const gatewayUrl = String(process.env.INSTAWEB_HARNESS_GATEWAY_URL || '');
9
+ const gatewayToken = String(process.env.INSTAWEB_HARNESS_GATEWAY_TOKEN || '');
10
+ const configuredAllowedTools = (() => {
11
+ const value = String(process.env.INSTAWEB_HARNESS_ALLOWED_TOOLS || '').trim();
12
+ if (!value) return null;
13
+ try {
14
+ const parsed = JSON.parse(value);
15
+ return Array.isArray(parsed)
16
+ ? new Set(parsed.filter((name) => typeof name === 'string'))
17
+ : null;
18
+ } catch {
19
+ return null;
20
+ }
21
+ })();
22
+ const toolAllowed = (name) =>
23
+ !configuredAllowedTools || configuredAllowedTools.has(name);
24
+
25
+ if (!gatewayUrl || !gatewayToken) {
26
+ throw new Error('The InstaWebAI harness gateway is not configured.');
27
+ }
28
+
29
+ async function callGateway(name, args) {
30
+ const response = await fetch(gatewayUrl, {
31
+ method: 'POST',
32
+ headers: {
33
+ authorization: `Bearer ${gatewayToken}`,
34
+ 'content-type': 'application/json',
35
+ },
36
+ body: JSON.stringify({ name, arguments: args }),
37
+ });
38
+ const payload = await response.json().catch(() => null);
39
+ if (!response.ok || payload?.ok !== true) {
40
+ throw Object.assign(new Error(payload?.error?.message || `${name} failed.`), {
41
+ code: payload?.error?.code || 'APP_HARNESS_TOOL_FAILED',
42
+ });
43
+ }
44
+ return payload.result;
45
+ }
46
+
47
+ function resultContent(result) {
48
+ return {
49
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
50
+ structuredContent: result && typeof result === 'object' && !Array.isArray(result) ? result : { result },
51
+ };
52
+ }
53
+
54
+ const server = new McpServer({
55
+ name: 'instawebai-harness',
56
+ version: BRIDGE_VERSION,
57
+ });
58
+
59
+ if (toolAllowed('workspace_inspect')) server.registerTool(
60
+ 'workspace_inspect',
61
+ {
62
+ description: 'Inspect workspace changes relative to the last synchronized InstaWebAI revision.',
63
+ inputSchema: {},
64
+ },
65
+ async () => resultContent(await callGateway('workspace_inspect', {})),
66
+ );
67
+
68
+ if (toolAllowed('workspace_sync')) server.registerTool(
69
+ 'workspace_sync',
70
+ {
71
+ description: 'Synchronize all current workspace changes to the protected InstaWebAI build workspace.',
72
+ inputSchema: {},
73
+ },
74
+ async () => resultContent(await callGateway('workspace_sync', {})),
75
+ );
76
+
77
+ if (toolAllowed('shell_run')) server.registerTool(
78
+ 'shell_run',
79
+ {
80
+ description:
81
+ 'Run a project command in InstaWebAI’s isolated build workspace. Use it for package installation, code generation, tests, and builds.',
82
+ inputSchema: {
83
+ command: z.string().trim().min(1).max(8_192),
84
+ cwd: z
85
+ .string()
86
+ .regex(/^(?:\.|[A-Za-z0-9][A-Za-z0-9._-]*(?:\/[A-Za-z0-9][A-Za-z0-9._-]*)*)$/)
87
+ .max(500)
88
+ .default('.'),
89
+ timeoutMs: z.number().int().min(1_000).max(600_000).default(300_000),
90
+ },
91
+ },
92
+ async (args) => resultContent(await callGateway('shell_run', args)),
93
+ );
94
+
95
+ if (toolAllowed('preview_control')) server.registerTool(
96
+ 'preview_control',
97
+ {
98
+ description: 'Start, restart, or inspect the server-managed preview for the current synchronized workspace.',
99
+ inputSchema: {
100
+ action: z.enum(['start', 'restart', 'status']),
101
+ },
102
+ },
103
+ async (args) => resultContent(await callGateway('preview_control', args)),
104
+ );
105
+
106
+ if (toolAllowed('browser_control')) server.registerTool(
107
+ 'browser_control',
108
+ {
109
+ description:
110
+ 'Drive an isolated browser against the current preview. Prefer batch for consecutive interactions and receive one compact final snapshot.',
111
+ inputSchema: {
112
+ action: z.enum(['open', 'snapshot', 'click', 'fill', 'select', 'press', 'batch', 'close']),
113
+ path: z.string().max(1_000).optional(),
114
+ selector: z.string().max(1_000).optional(),
115
+ value: z.string().max(10_000).optional(),
116
+ key: z.string().max(80).optional(),
117
+ actions: z.array(z.object({
118
+ action: z.enum(['open', 'snapshot', 'click', 'fill', 'select', 'press']),
119
+ path: z.string().max(1_000).optional(),
120
+ selector: z.string().max(1_000).optional(),
121
+ value: z.string().max(10_000).optional(),
122
+ key: z.string().max(80).optional(),
123
+ }).strict()).min(1).max(25).optional(),
124
+ },
125
+ },
126
+ async (args) => resultContent(await callGateway('browser_control', args)),
127
+ );
128
+
129
+ if (toolAllowed('data_inspect')) server.registerTool(
130
+ 'data_inspect',
131
+ {
132
+ description:
133
+ 'Read the application data contract or redacted development records without exposing database credentials.',
134
+ inputSchema: {
135
+ action: z.enum(['overview', 'list_records']),
136
+ entityId: z.string().max(180).optional(),
137
+ limit: z.number().int().min(1).max(100).default(50),
138
+ offset: z.number().int().min(0).max(100_000).default(0),
139
+ search: z.string().max(500).default(''),
140
+ },
141
+ },
142
+ async (args) => resultContent(await callGateway('data_inspect', args)),
143
+ );
144
+
145
+ if (toolAllowed('verification_run')) server.registerTool(
146
+ 'verification_run',
147
+ {
148
+ description: 'Synchronize the workspace and run InstaWebAI deterministic and browser verification.',
149
+ inputSchema: {
150
+ level: z.enum(['fast', 'visual']),
151
+ workflowIds: z.array(z.string().max(180)).max(20).default([]),
152
+ },
153
+ },
154
+ async (args) => resultContent(await callGateway('verification_run', args)),
155
+ );
156
+
157
+ if (toolAllowed('progress_update')) server.registerTool(
158
+ 'progress_update',
159
+ {
160
+ description:
161
+ 'Send a brief user-facing progress update before the first inspection or edit and at meaningful phase changes.',
162
+ inputSchema: {
163
+ message: z.string().trim().min(1).max(500),
164
+ },
165
+ },
166
+ async (args) => resultContent(await callGateway('progress_update', args)),
167
+ );
168
+
169
+ if (toolAllowed('framer_instructions')) server.registerTool(
170
+ 'framer_instructions',
171
+ {
172
+ description:
173
+ 'Load Framer’s official agent command reference before making canvas changes.',
174
+ inputSchema: {},
175
+ },
176
+ async () => resultContent(await callGateway('framer_instructions', {})),
177
+ );
178
+
179
+ if (toolAllowed('framer_context')) server.registerTool(
180
+ 'framer_context',
181
+ {
182
+ description:
183
+ 'Read the connected Framer project context, including pages, components, styles, CMS, and the active branch.',
184
+ inputSchema: {},
185
+ },
186
+ async () => resultContent(await callGateway('framer_context', {})),
187
+ );
188
+
189
+ if (toolAllowed('framer_read_project')) server.registerTool(
190
+ 'framer_read_project',
191
+ {
192
+ description:
193
+ 'Run focused Framer Agent readProject queries against the connected project.',
194
+ inputSchema: {
195
+ queries: z.array(z.record(z.string(), z.unknown())).min(1).max(50),
196
+ pagePath: z.string().max(500).default('/'),
197
+ },
198
+ },
199
+ async (args) => resultContent(await callGateway('framer_read_project', args)),
200
+ );
201
+
202
+ if (toolAllowed('framer_apply_changes')) server.registerTool(
203
+ 'framer_apply_changes',
204
+ {
205
+ description:
206
+ 'Apply Framer Agent DSL changes directly to the connected project.',
207
+ inputSchema: {
208
+ changes: z.string().trim().min(1).max(250_000),
209
+ pagePath: z.string().max(500).default('/'),
210
+ },
211
+ },
212
+ async (args) => resultContent(await callGateway('framer_apply_changes', args)),
213
+ );
214
+
215
+ if (toolAllowed('framer_read')) server.registerTool(
216
+ 'framer_read',
217
+ {
218
+ description:
219
+ 'Execute read-only JavaScript with the official Framer Agent connection.',
220
+ inputSchema: {
221
+ code: z.string().trim().min(1).max(200_000),
222
+ },
223
+ },
224
+ async (args) => resultContent(await callGateway('framer_read', args)),
225
+ );
226
+
227
+ if (toolAllowed('framer_write')) server.registerTool(
228
+ 'framer_write',
229
+ {
230
+ description:
231
+ 'Execute project-scoped JavaScript with the official Framer Agent connection for mutations not covered by applyChanges.',
232
+ inputSchema: {
233
+ code: z.string().trim().min(1).max(200_000),
234
+ },
235
+ },
236
+ async (args) => resultContent(await callGateway('framer_write', args)),
237
+ );
238
+
239
+ const transport = new StdioServerTransport();
240
+ await server.connect(transport);