blockwerk-mcp 0.1.0 → 0.1.1

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/index.js CHANGED
@@ -18,18 +18,21 @@ const getArg = (flag) => {
18
18
  const i = args.indexOf(flag);
19
19
  return i !== -1 ? args[i + 1] : undefined;
20
20
  };
21
- const SESSION_ID = getArg('--session') ?? process.env.BLOCKWERK_SESSION;
22
- const RELAY_BASE = (getArg('--relay') ?? process.env.BLOCKWERK_RELAY ?? 'https://blockwerk.tech').replace(/\/$/, '');
21
+ let SESSION_ID = getArg('--session') ?? process.env.BLOCKWERK_SESSION;
22
+ const RELAY_BASE = (getArg('--relay') ??
23
+ process.env.BLOCKWERK_RELAY ??
24
+ 'https://blockwerk.tech').replace(/\/$/, '');
23
25
  const RELAY_URL = `${RELAY_BASE}/api/mcp-relay`;
24
26
  if (!SESSION_ID) {
25
- console.error('Error: session ID required.\n' +
26
- 'Usage: npx @blockwerk/mcp --session <session-id>\n\n' +
27
- 'Get your session ID from blockwerk.tech → AI panel → "Connect external AI".');
28
- process.exit(1);
27
+ console.error('[BlockWerk MCP] Warning: No session ID provided at startup.\n' +
28
+ 'Use the "configure_bridge" tool to set the session ID at runtime.');
29
29
  }
30
30
  // ── Relay communication ───────────────────────────────────────────────────────
31
31
  const MAX_WAIT_MS = 60000; // total wait before giving up
32
32
  async function callBlockWerk(name, input) {
33
+ if (!SESSION_ID) {
34
+ throw new Error('Session ID not configured. Please provide your BlockWerk Session ID first using the "configure_bridge" tool.');
35
+ }
33
36
  const requestId = crypto.randomUUID();
34
37
  // 1. Push command to relay
35
38
  const pushRes = await fetch(`${RELAY_URL}?session=${SESSION_ID}&type=command`, {
@@ -62,6 +65,19 @@ const server = new McpServer({
62
65
  version: '0.1.0',
63
66
  });
64
67
  // ── Tools ─────────────────────────────────────────────────────────────────────
68
+ server.tool('configure_bridge', 'Connects this AI assistant to a specific BlockWerk browser session.', {
69
+ sessionId: z.string().describe('The Session ID from the BlockWerk UI (e.g. bw-a1b2c3d4)'),
70
+ }, async ({ sessionId }) => {
71
+ SESSION_ID = sessionId;
72
+ return {
73
+ content: [
74
+ {
75
+ type: 'text',
76
+ text: `Bridge configured successfully! Now connected to session: ${SESSION_ID}`,
77
+ },
78
+ ],
79
+ };
80
+ });
65
81
  server.tool('describe_canvas', 'Returns a structured text description of all blocks and connections on the BlockWerk canvas.', {}, async () => ({
66
82
  content: [{ type: 'text', text: await callBlockWerk('describe_canvas', {}) }],
67
83
  }));
@@ -69,19 +85,37 @@ server.tool('get_available_blocks', 'Lists all available block types with their
69
85
  content: [{ type: 'text', text: await callBlockWerk('get_available_blocks', {}) }],
70
86
  }));
71
87
  server.tool('batch_commands', 'Executes multiple canvas operations in a single round-trip. Use handles (temporary IDs) to reference newly added blocks within the same batch. This is the preferred tool for building diagrams.', {
72
- commands: z.array(z.object({
73
- type: z.enum(['add_block', 'connect_blocks', 'update_params', 'delete_block', 'tidy_layout']),
88
+ commands: z
89
+ .array(z.object({
90
+ type: z.enum([
91
+ 'add_block',
92
+ 'connect_blocks',
93
+ 'update_params',
94
+ 'delete_block',
95
+ 'tidy_layout',
96
+ 'clear_canvas',
97
+ ]),
74
98
  blockType: z.string().optional().describe('Block type for add_block, e.g. PIDController'),
75
99
  x: z.number().optional().describe('X position in pixels'),
76
100
  y: z.number().optional().describe('Y position in pixels'),
77
- handle: z.string().optional().describe('Temporary ID for this block, usable in the same batch'),
101
+ handle: z
102
+ .string()
103
+ .optional()
104
+ .describe('Temporary ID for this block, usable in the same batch'),
78
105
  fromBlockId: z.string().optional().describe('Block ID or handle for connect_blocks'),
79
106
  fromPortId: z.string().optional().describe('Output port, e.g. "out"'),
80
107
  toBlockId: z.string().optional().describe('Block ID or handle for connect_blocks'),
81
108
  toPortId: z.string().optional().describe('Input port, e.g. "in"'),
82
- blockId: z.string().optional().describe('Block ID or handle for update_params / delete_block'),
83
- params: z.record(z.unknown()).optional().describe('Parameter key-value pairs for update_params'),
84
- })).describe('Ordered list of commands to execute'),
109
+ blockId: z
110
+ .string()
111
+ .optional()
112
+ .describe('Block ID or handle for update_params / delete_block'),
113
+ params: z
114
+ .record(z.unknown())
115
+ .optional()
116
+ .describe('Parameter key-value pairs for update_params'),
117
+ }))
118
+ .describe('Ordered list of commands to execute'),
85
119
  }, async ({ commands }) => ({
86
120
  content: [{ type: 'text', text: await callBlockWerk('batch_commands', { commands }) }],
87
121
  }));
@@ -104,7 +138,17 @@ server.tool('connect_blocks', 'Connects an output port of one block to an input
104
138
  toBlockId: z.string().describe('Target block ID'),
105
139
  toPortId: z.string().describe('Input port name, e.g. "in"'),
106
140
  }, async ({ fromBlockId, fromPortId, toBlockId, toPortId }) => ({
107
- content: [{ type: 'text', text: await callBlockWerk('connect_blocks', { fromBlockId, fromPortId, toBlockId, toPortId }) }],
141
+ content: [
142
+ {
143
+ type: 'text',
144
+ text: await callBlockWerk('connect_blocks', {
145
+ fromBlockId,
146
+ fromPortId,
147
+ toBlockId,
148
+ toPortId,
149
+ }),
150
+ },
151
+ ],
108
152
  }));
109
153
  server.tool('update_params', 'Updates parameters of a block.', {
110
154
  blockId: z.string().describe('Block ID to update'),
@@ -125,4 +169,4 @@ server.tool('load_diagram', 'Loads a complete BlockWerk project from a JSON stri
125
169
  // ── Start ─────────────────────────────────────────────────────────────────────
126
170
  const transport = new StdioServerTransport();
127
171
  await server.connect(transport);
128
- console.error(`[BlockWerk MCP] Connected — session: ${SESSION_ID}, relay: ${RELAY_URL}`);
172
+ console.error(`[BlockWerk MCP] Started — session: ${SESSION_ID || 'PENDING'}, relay: ${RELAY_URL}`);
package/package.json CHANGED
@@ -1,26 +1,26 @@
1
- {
2
- "name": "blockwerk-mcp",
3
- "version": "0.1.0",
4
- "description": "MCP server for BlockWerk — lets Claude Desktop and Cursor control a live BlockWerk browser tab",
5
- "type": "module",
6
- "bin": {
7
- "blockwerk-mcp": "dist/index.js"
8
- },
9
- "scripts": {
10
- "build": "tsc",
11
- "dev": "tsx src/index.ts",
12
- "start": "node dist/index.js"
13
- },
14
- "dependencies": {
15
- "@modelcontextprotocol/sdk": "^1.15.0",
16
- "zod": "^3.25.0"
17
- },
18
- "devDependencies": {
19
- "@types/node": "^22.0.0",
20
- "tsx": "^4.19.0",
21
- "typescript": "~5.9.3"
22
- },
23
- "engines": {
24
- "node": ">=18"
25
- }
26
- }
1
+ {
2
+ "name": "blockwerk-mcp",
3
+ "version": "0.1.1",
4
+ "description": "MCP server for BlockWerk — lets Claude Desktop and Cursor control a live BlockWerk browser tab",
5
+ "type": "module",
6
+ "bin": {
7
+ "blockwerk-mcp": "dist/index.js"
8
+ },
9
+ "scripts": {
10
+ "build": "tsc",
11
+ "dev": "tsx src/index.ts",
12
+ "start": "node dist/index.js"
13
+ },
14
+ "dependencies": {
15
+ "@modelcontextprotocol/sdk": "^1.15.0",
16
+ "zod": "^3.25.0"
17
+ },
18
+ "devDependencies": {
19
+ "@types/node": "^22.0.0",
20
+ "tsx": "^4.19.0",
21
+ "typescript": "~5.9.3"
22
+ },
23
+ "engines": {
24
+ "node": ">=18"
25
+ }
26
+ }
package/src/index.ts CHANGED
@@ -22,17 +22,19 @@ const getArg = (flag: string) => {
22
22
  return i !== -1 ? args[i + 1] : undefined;
23
23
  };
24
24
 
25
- const SESSION_ID = getArg('--session') ?? process.env.BLOCKWERK_SESSION;
26
- const RELAY_BASE = (getArg('--relay') ?? process.env.BLOCKWERK_RELAY ?? 'https://blockwerk.tech').replace(/\/$/, '');
25
+ let SESSION_ID = getArg('--session') ?? process.env.BLOCKWERK_SESSION;
26
+ const RELAY_BASE = (
27
+ getArg('--relay') ??
28
+ process.env.BLOCKWERK_RELAY ??
29
+ 'https://blockwerk.tech'
30
+ ).replace(/\/$/, '');
27
31
  const RELAY_URL = `${RELAY_BASE}/api/mcp-relay`;
28
32
 
29
33
  if (!SESSION_ID) {
30
34
  console.error(
31
- 'Error: session ID required.\n' +
32
- 'Usage: npx @blockwerk/mcp --session <session-id>\n\n' +
33
- 'Get your session ID from blockwerk.tech → AI panel → "Connect external AI".',
35
+ '[BlockWerk MCP] Warning: No session ID provided at startup.\n' +
36
+ 'Use the "configure_bridge" tool to set the session ID at runtime.'
34
37
  );
35
- process.exit(1);
36
38
  }
37
39
 
38
40
  // ── Relay communication ───────────────────────────────────────────────────────
@@ -40,6 +42,11 @@ if (!SESSION_ID) {
40
42
  const MAX_WAIT_MS = 60000; // total wait before giving up
41
43
 
42
44
  async function callBlockWerk(name: string, input: Record<string, unknown>): Promise<string> {
45
+ if (!SESSION_ID) {
46
+ throw new Error(
47
+ 'Session ID not configured. Please provide your BlockWerk Session ID first using the "configure_bridge" tool.'
48
+ );
49
+ }
43
50
  const requestId = crypto.randomUUID();
44
51
 
45
52
  // 1. Push command to relay
@@ -59,7 +66,7 @@ async function callBlockWerk(name: string, input: Record<string, unknown>): Prom
59
66
 
60
67
  while (Date.now() < deadline) {
61
68
  const pollRes = await fetch(
62
- `${RELAY_URL}?session=${SESSION_ID}&type=result&requestId=${requestId}`,
69
+ `${RELAY_URL}?session=${SESSION_ID}&type=result&requestId=${requestId}`
63
70
  );
64
71
 
65
72
  if (!pollRes.ok) {
@@ -76,7 +83,7 @@ async function callBlockWerk(name: string, input: Record<string, unknown>): Prom
76
83
  }
77
84
 
78
85
  throw new Error(
79
- 'BlockWerk did not respond within 60 seconds. Is the browser tab open and the MCP bridge active?',
86
+ 'BlockWerk did not respond within 60 seconds. Is the browser tab open and the MCP bridge active?'
80
87
  );
81
88
  }
82
89
 
@@ -89,13 +96,32 @@ const server = new McpServer({
89
96
 
90
97
  // ── Tools ─────────────────────────────────────────────────────────────────────
91
98
 
99
+ server.tool(
100
+ 'configure_bridge',
101
+ 'Connects this AI assistant to a specific BlockWerk browser session.',
102
+ {
103
+ sessionId: z.string().describe('The Session ID from the BlockWerk UI (e.g. bw-a1b2c3d4)'),
104
+ },
105
+ async ({ sessionId }) => {
106
+ SESSION_ID = sessionId;
107
+ return {
108
+ content: [
109
+ {
110
+ type: 'text',
111
+ text: `Bridge configured successfully! Now connected to session: ${SESSION_ID}`,
112
+ },
113
+ ],
114
+ };
115
+ }
116
+ );
117
+
92
118
  server.tool(
93
119
  'describe_canvas',
94
120
  'Returns a structured text description of all blocks and connections on the BlockWerk canvas.',
95
121
  {},
96
122
  async () => ({
97
123
  content: [{ type: 'text', text: await callBlockWerk('describe_canvas', {}) }],
98
- }),
124
+ })
99
125
  );
100
126
 
101
127
  server.tool(
@@ -104,30 +130,50 @@ server.tool(
104
130
  {},
105
131
  async () => ({
106
132
  content: [{ type: 'text', text: await callBlockWerk('get_available_blocks', {}) }],
107
- }),
133
+ })
108
134
  );
109
135
 
110
136
  server.tool(
111
137
  'batch_commands',
112
138
  'Executes multiple canvas operations in a single round-trip. Use handles (temporary IDs) to reference newly added blocks within the same batch. This is the preferred tool for building diagrams.',
113
139
  {
114
- commands: z.array(z.object({
115
- type: z.enum(['add_block', 'connect_blocks', 'update_params', 'delete_block', 'tidy_layout']),
116
- blockType: z.string().optional().describe('Block type for add_block, e.g. PIDController'),
117
- x: z.number().optional().describe('X position in pixels'),
118
- y: z.number().optional().describe('Y position in pixels'),
119
- handle: z.string().optional().describe('Temporary ID for this block, usable in the same batch'),
120
- fromBlockId: z.string().optional().describe('Block ID or handle for connect_blocks'),
121
- fromPortId: z.string().optional().describe('Output port, e.g. "out"'),
122
- toBlockId: z.string().optional().describe('Block ID or handle for connect_blocks'),
123
- toPortId: z.string().optional().describe('Input port, e.g. "in"'),
124
- blockId: z.string().optional().describe('Block ID or handle for update_params / delete_block'),
125
- params: z.record(z.unknown()).optional().describe('Parameter key-value pairs for update_params'),
126
- })).describe('Ordered list of commands to execute'),
140
+ commands: z
141
+ .array(
142
+ z.object({
143
+ type: z.enum([
144
+ 'add_block',
145
+ 'connect_blocks',
146
+ 'update_params',
147
+ 'delete_block',
148
+ 'tidy_layout',
149
+ 'clear_canvas',
150
+ ]),
151
+ blockType: z.string().optional().describe('Block type for add_block, e.g. PIDController'),
152
+ x: z.number().optional().describe('X position in pixels'),
153
+ y: z.number().optional().describe('Y position in pixels'),
154
+ handle: z
155
+ .string()
156
+ .optional()
157
+ .describe('Temporary ID for this block, usable in the same batch'),
158
+ fromBlockId: z.string().optional().describe('Block ID or handle for connect_blocks'),
159
+ fromPortId: z.string().optional().describe('Output port, e.g. "out"'),
160
+ toBlockId: z.string().optional().describe('Block ID or handle for connect_blocks'),
161
+ toPortId: z.string().optional().describe('Input port, e.g. "in"'),
162
+ blockId: z
163
+ .string()
164
+ .optional()
165
+ .describe('Block ID or handle for update_params / delete_block'),
166
+ params: z
167
+ .record(z.unknown())
168
+ .optional()
169
+ .describe('Parameter key-value pairs for update_params'),
170
+ })
171
+ )
172
+ .describe('Ordered list of commands to execute'),
127
173
  },
128
174
  async ({ commands }) => ({
129
175
  content: [{ type: 'text', text: await callBlockWerk('batch_commands', { commands }) }],
130
- }),
176
+ })
131
177
  );
132
178
 
133
179
  server.tool(
@@ -136,7 +182,7 @@ server.tool(
136
182
  {},
137
183
  async () => ({
138
184
  content: [{ type: 'text', text: await callBlockWerk('tidy_layout', {}) }],
139
- }),
185
+ })
140
186
  );
141
187
 
142
188
  server.tool(
@@ -145,7 +191,7 @@ server.tool(
145
191
  {},
146
192
  async () => ({
147
193
  content: [{ type: 'text', text: await callBlockWerk('clear_canvas', {}) }],
148
- }),
194
+ })
149
195
  );
150
196
 
151
197
  server.tool(
@@ -158,7 +204,7 @@ server.tool(
158
204
  },
159
205
  async ({ type, x, y }) => ({
160
206
  content: [{ type: 'text', text: await callBlockWerk('add_block', { type, x, y }) }],
161
- }),
207
+ })
162
208
  );
163
209
 
164
210
  server.tool(
@@ -171,8 +217,18 @@ server.tool(
171
217
  toPortId: z.string().describe('Input port name, e.g. "in"'),
172
218
  },
173
219
  async ({ fromBlockId, fromPortId, toBlockId, toPortId }) => ({
174
- content: [{ type: 'text', text: await callBlockWerk('connect_blocks', { fromBlockId, fromPortId, toBlockId, toPortId }) }],
175
- }),
220
+ content: [
221
+ {
222
+ type: 'text',
223
+ text: await callBlockWerk('connect_blocks', {
224
+ fromBlockId,
225
+ fromPortId,
226
+ toBlockId,
227
+ toPortId,
228
+ }),
229
+ },
230
+ ],
231
+ })
176
232
  );
177
233
 
178
234
  server.tool(
@@ -184,7 +240,7 @@ server.tool(
184
240
  },
185
241
  async ({ blockId, params }) => ({
186
242
  content: [{ type: 'text', text: await callBlockWerk('update_params', { blockId, params }) }],
187
- }),
243
+ })
188
244
  );
189
245
 
190
246
  server.tool(
@@ -195,7 +251,7 @@ server.tool(
195
251
  },
196
252
  async ({ blockId }) => ({
197
253
  content: [{ type: 'text', text: await callBlockWerk('delete_block', { blockId }) }],
198
- }),
254
+ })
199
255
  );
200
256
 
201
257
  server.tool(
@@ -206,11 +262,11 @@ server.tool(
206
262
  },
207
263
  async ({ json }) => ({
208
264
  content: [{ type: 'text', text: await callBlockWerk('load_diagram', { json }) }],
209
- }),
265
+ })
210
266
  );
211
267
 
212
268
  // ── Start ─────────────────────────────────────────────────────────────────────
213
269
 
214
270
  const transport = new StdioServerTransport();
215
271
  await server.connect(transport);
216
- console.error(`[BlockWerk MCP] Connected — session: ${SESSION_ID}, relay: ${RELAY_URL}`);
272
+ console.error(`[BlockWerk MCP] Started — session: ${SESSION_ID || 'PENDING'}, relay: ${RELAY_URL}`);
package/tsconfig.json CHANGED
@@ -1,13 +1,13 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2022",
4
- "module": "NodeNext",
5
- "moduleResolution": "NodeNext",
6
- "outDir": "dist",
7
- "rootDir": "src",
8
- "strict": true,
9
- "esModuleInterop": true,
10
- "skipLibCheck": true
11
- },
12
- "include": ["src"]
13
- }
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "outDir": "dist",
7
+ "rootDir": "src",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true
11
+ },
12
+ "include": ["src"]
13
+ }