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 +58 -14
- package/package.json +26 -26
- package/src/index.ts +89 -33
- package/tsconfig.json +13 -13
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
|
-
|
|
22
|
-
const RELAY_BASE = (getArg('--relay') ??
|
|
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('
|
|
26
|
-
'
|
|
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
|
|
73
|
-
|
|
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
|
|
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
|
|
83
|
-
|
|
84
|
-
|
|
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: [
|
|
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]
|
|
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.
|
|
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
|
-
|
|
26
|
-
const RELAY_BASE = (
|
|
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
|
-
'
|
|
32
|
-
|
|
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
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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: [
|
|
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]
|
|
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
|
+
}
|