@florentleveque/mural-mcp-serveur 0.5.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/LICENSE +21 -0
- package/README.md +301 -0
- package/build/index.d.ts +3 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +1472 -0
- package/build/index.js.map +1 -0
- package/build/mural-client.d.ts +66 -0
- package/build/mural-client.d.ts.map +1 -0
- package/build/mural-client.js +706 -0
- package/build/mural-client.js.map +1 -0
- package/build/oauth.d.ts +22 -0
- package/build/oauth.d.ts.map +1 -0
- package/build/oauth.js +278 -0
- package/build/oauth.js.map +1 -0
- package/build/rate-limiter.d.ts +23 -0
- package/build/rate-limiter.d.ts.map +1 -0
- package/build/rate-limiter.js +163 -0
- package/build/rate-limiter.js.map +1 -0
- package/build/types.d.ts +245 -0
- package/build/types.d.ts.map +1 -0
- package/build/types.js +2 -0
- package/build/types.js.map +1 -0
- package/package.json +68 -0
package/build/index.js
ADDED
|
@@ -0,0 +1,1472 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import 'dotenv/config';
|
|
3
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
4
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
5
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
import { MuralClient } from './mural-client.js';
|
|
8
|
+
const REQUIRED_ENV_VARS = ['MURAL_CLIENT_ID', 'MURAL_CLIENT_SECRET'];
|
|
9
|
+
function validateEnvironment() {
|
|
10
|
+
const clientId = process.env.MURAL_CLIENT_ID;
|
|
11
|
+
if (!clientId) {
|
|
12
|
+
throw new Error('Missing required environment variable: MURAL_CLIENT_ID. ' +
|
|
13
|
+
'Please set this in your environment or .env file.');
|
|
14
|
+
}
|
|
15
|
+
const clientSecret = process.env.MURAL_CLIENT_SECRET;
|
|
16
|
+
if (!clientSecret) {
|
|
17
|
+
throw new Error('Missing required environment variable: MURAL_CLIENT_SECRET. ' +
|
|
18
|
+
'Mural requires client authentication (the client secret) for the OAuth token exchange. ' +
|
|
19
|
+
'Copy it from your Mural app (Basic Information page) and set it in your environment or .env file.');
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
clientId,
|
|
23
|
+
clientSecret,
|
|
24
|
+
redirectUri: process.env.MURAL_REDIRECT_URI
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
async function main() {
|
|
28
|
+
const { clientId, clientSecret, redirectUri } = validateEnvironment();
|
|
29
|
+
const muralClient = new MuralClient(clientId, clientSecret, redirectUri);
|
|
30
|
+
const server = new Server({
|
|
31
|
+
name: 'mural-mcp-serveur',
|
|
32
|
+
version: '1.0.0',
|
|
33
|
+
}, {
|
|
34
|
+
capabilities: {
|
|
35
|
+
tools: {},
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
// List available tools
|
|
39
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
40
|
+
return {
|
|
41
|
+
tools: [
|
|
42
|
+
{
|
|
43
|
+
name: 'list-workspaces',
|
|
44
|
+
description: 'List all workspaces the authenticated user has access to',
|
|
45
|
+
inputSchema: {
|
|
46
|
+
type: 'object',
|
|
47
|
+
properties: {
|
|
48
|
+
limit: {
|
|
49
|
+
type: 'number',
|
|
50
|
+
description: 'Maximum number of workspaces to return (optional)',
|
|
51
|
+
minimum: 1,
|
|
52
|
+
maximum: 100
|
|
53
|
+
},
|
|
54
|
+
offset: {
|
|
55
|
+
type: 'number',
|
|
56
|
+
description: 'Number of workspaces to skip for pagination (optional)',
|
|
57
|
+
minimum: 0
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
additionalProperties: false
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
name: 'get-workspace',
|
|
65
|
+
description: 'Get detailed information about a specific workspace',
|
|
66
|
+
inputSchema: {
|
|
67
|
+
type: 'object',
|
|
68
|
+
properties: {
|
|
69
|
+
workspaceId: {
|
|
70
|
+
type: 'string',
|
|
71
|
+
description: 'The unique identifier of the workspace'
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
required: ['workspaceId'],
|
|
75
|
+
additionalProperties: false
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
name: 'test-connection',
|
|
80
|
+
description: 'Test the connection to Mural API and verify authentication',
|
|
81
|
+
inputSchema: {
|
|
82
|
+
type: 'object',
|
|
83
|
+
properties: {},
|
|
84
|
+
additionalProperties: false
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
name: 'clear-auth',
|
|
89
|
+
description: 'Clear stored authentication tokens (requires re-authentication)',
|
|
90
|
+
inputSchema: {
|
|
91
|
+
type: 'object',
|
|
92
|
+
properties: {},
|
|
93
|
+
additionalProperties: false
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
name: 'debug-api-response',
|
|
98
|
+
description: 'Debug tool: Show raw API response from workspaces endpoint for troubleshooting',
|
|
99
|
+
inputSchema: {
|
|
100
|
+
type: 'object',
|
|
101
|
+
properties: {},
|
|
102
|
+
additionalProperties: false
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
name: 'get-rate-limit-status',
|
|
107
|
+
description: 'Get current rate limiting status including remaining tokens and refresh times',
|
|
108
|
+
inputSchema: {
|
|
109
|
+
type: 'object',
|
|
110
|
+
properties: {},
|
|
111
|
+
additionalProperties: false
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
name: 'list-workspace-boards',
|
|
116
|
+
description: 'List all boards (murals) within a specific workspace',
|
|
117
|
+
inputSchema: {
|
|
118
|
+
type: 'object',
|
|
119
|
+
properties: {
|
|
120
|
+
workspaceId: {
|
|
121
|
+
type: 'string',
|
|
122
|
+
description: 'The unique identifier of the workspace'
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
required: ['workspaceId'],
|
|
126
|
+
additionalProperties: false
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
name: 'list-room-boards',
|
|
131
|
+
description: 'List all boards (murals) within a specific room',
|
|
132
|
+
inputSchema: {
|
|
133
|
+
type: 'object',
|
|
134
|
+
properties: {
|
|
135
|
+
roomId: {
|
|
136
|
+
type: 'string',
|
|
137
|
+
description: 'The unique identifier of the room'
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
required: ['roomId'],
|
|
141
|
+
additionalProperties: false
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
name: 'list-workspace-rooms',
|
|
146
|
+
description: 'List all rooms within a specific workspace (use a room id with list-room-boards). Returns all pages.',
|
|
147
|
+
inputSchema: {
|
|
148
|
+
type: 'object',
|
|
149
|
+
properties: {
|
|
150
|
+
workspaceId: {
|
|
151
|
+
type: 'string',
|
|
152
|
+
description: 'The unique identifier of the workspace'
|
|
153
|
+
},
|
|
154
|
+
openOnly: {
|
|
155
|
+
type: 'boolean',
|
|
156
|
+
description: 'If true, list only open (discoverable) rooms instead of all rooms (optional, defaults to false)'
|
|
157
|
+
}
|
|
158
|
+
},
|
|
159
|
+
required: ['workspaceId'],
|
|
160
|
+
additionalProperties: false
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
name: 'list-workspace-templates',
|
|
165
|
+
description: 'List a workspace\'s templates (default + custom), or search them by name. Returns all pages.',
|
|
166
|
+
inputSchema: {
|
|
167
|
+
type: 'object',
|
|
168
|
+
properties: {
|
|
169
|
+
workspaceId: {
|
|
170
|
+
type: 'string',
|
|
171
|
+
description: 'The unique identifier of the workspace'
|
|
172
|
+
},
|
|
173
|
+
searchQuery: {
|
|
174
|
+
type: 'string',
|
|
175
|
+
description: 'Optional. If provided, search templates by name instead of listing all'
|
|
176
|
+
},
|
|
177
|
+
withoutDefault: {
|
|
178
|
+
type: 'boolean',
|
|
179
|
+
description: 'If true, exclude Mural default templates and return only custom ones (optional, ignored when searchQuery is set)'
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
required: ['workspaceId'],
|
|
183
|
+
additionalProperties: false
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
name: 'create-mural-from-template',
|
|
188
|
+
description: 'Create a new mural in a room from a template',
|
|
189
|
+
inputSchema: {
|
|
190
|
+
type: 'object',
|
|
191
|
+
properties: {
|
|
192
|
+
templateId: {
|
|
193
|
+
type: 'string',
|
|
194
|
+
description: 'The unique identifier of the template to instantiate'
|
|
195
|
+
},
|
|
196
|
+
title: {
|
|
197
|
+
type: 'string',
|
|
198
|
+
description: 'Title of the new mural'
|
|
199
|
+
},
|
|
200
|
+
roomId: {
|
|
201
|
+
type: 'number',
|
|
202
|
+
description: 'The numeric identifier of the destination room'
|
|
203
|
+
},
|
|
204
|
+
folderId: {
|
|
205
|
+
type: 'string',
|
|
206
|
+
description: 'Optional destination folder id within the room'
|
|
207
|
+
}
|
|
208
|
+
},
|
|
209
|
+
required: ['templateId', 'title', 'roomId'],
|
|
210
|
+
additionalProperties: false
|
|
211
|
+
},
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
name: 'create-room',
|
|
215
|
+
description: 'Create a new room in a workspace',
|
|
216
|
+
inputSchema: {
|
|
217
|
+
type: 'object',
|
|
218
|
+
properties: {
|
|
219
|
+
workspaceId: {
|
|
220
|
+
type: 'string',
|
|
221
|
+
description: 'The unique identifier of the workspace'
|
|
222
|
+
},
|
|
223
|
+
name: {
|
|
224
|
+
type: 'string',
|
|
225
|
+
description: 'Name of the new room'
|
|
226
|
+
},
|
|
227
|
+
type: {
|
|
228
|
+
type: 'string',
|
|
229
|
+
enum: ['open', 'private'],
|
|
230
|
+
description: 'Room visibility: "open" (discoverable by workspace members) or "private"'
|
|
231
|
+
},
|
|
232
|
+
description: {
|
|
233
|
+
type: 'string',
|
|
234
|
+
description: 'Optional description of the room'
|
|
235
|
+
},
|
|
236
|
+
confidential: {
|
|
237
|
+
type: 'boolean',
|
|
238
|
+
description: 'Optional. Mark the room as confidential (defaults to false)'
|
|
239
|
+
}
|
|
240
|
+
},
|
|
241
|
+
required: ['workspaceId', 'name', 'type'],
|
|
242
|
+
additionalProperties: false
|
|
243
|
+
},
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
name: 'create-mural',
|
|
247
|
+
description: 'Create a new blank mural in a room (requires murals:write)',
|
|
248
|
+
inputSchema: {
|
|
249
|
+
type: 'object',
|
|
250
|
+
properties: {
|
|
251
|
+
roomId: { type: 'number', description: 'The numeric identifier of the destination room' },
|
|
252
|
+
title: { type: 'string', description: 'Optional title of the new mural' },
|
|
253
|
+
backgroundColor: { type: 'string', description: 'Optional background color (hex, e.g. #FFFFFFFF)' },
|
|
254
|
+
width: { type: 'number', description: 'Optional canvas width in pixels' },
|
|
255
|
+
height: { type: 'number', description: 'Optional canvas height in pixels' },
|
|
256
|
+
infinite: { type: 'boolean', description: 'Optional. Whether the canvas is infinite' },
|
|
257
|
+
folderId: { type: 'string', description: 'Optional destination folder id within the room' }
|
|
258
|
+
},
|
|
259
|
+
required: ['roomId'],
|
|
260
|
+
additionalProperties: false
|
|
261
|
+
},
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
name: 'update-mural',
|
|
265
|
+
description: 'Update a mural\'s properties by id (title, status, dimensions, sharing permissions...). Provide at least one field (requires murals:write)',
|
|
266
|
+
inputSchema: {
|
|
267
|
+
type: 'object',
|
|
268
|
+
properties: {
|
|
269
|
+
muralId: { type: 'string', description: 'The unique identifier of the mural to update' },
|
|
270
|
+
title: { type: 'string' },
|
|
271
|
+
backgroundColor: { type: 'string', description: 'Hex background color' },
|
|
272
|
+
favorite: { type: 'boolean' },
|
|
273
|
+
status: { type: 'string', enum: ['active', 'archived'], description: 'Set to "archived" to archive the mural (non-destructive alternative to delete)' },
|
|
274
|
+
width: { type: 'number', description: 'Canvas width (3000-60000)' },
|
|
275
|
+
height: { type: 'number', description: 'Canvas height (3000-60000)' },
|
|
276
|
+
infinite: { type: 'boolean' },
|
|
277
|
+
visitorsPermission: { type: 'string', enum: ['read', 'write', 'none'] },
|
|
278
|
+
workspaceMembersPermission: { type: 'string', enum: ['read', 'write', 'none'] },
|
|
279
|
+
folderId: { type: 'string' }
|
|
280
|
+
},
|
|
281
|
+
required: ['muralId'],
|
|
282
|
+
additionalProperties: false
|
|
283
|
+
},
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
name: 'delete-mural',
|
|
287
|
+
description: 'Permanently delete a mural by its id (irreversible; requires murals:write). For a non-destructive alternative, use update-mural with status "archived"',
|
|
288
|
+
inputSchema: {
|
|
289
|
+
type: 'object',
|
|
290
|
+
properties: {
|
|
291
|
+
muralId: { type: 'string', description: 'The unique identifier of the mural to delete' }
|
|
292
|
+
},
|
|
293
|
+
required: ['muralId'],
|
|
294
|
+
additionalProperties: false
|
|
295
|
+
},
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
name: 'duplicate-mural',
|
|
299
|
+
description: 'Duplicate an existing mural into a room (requires murals:write)',
|
|
300
|
+
inputSchema: {
|
|
301
|
+
type: 'object',
|
|
302
|
+
properties: {
|
|
303
|
+
muralId: { type: 'string', description: 'The unique identifier of the mural to duplicate' },
|
|
304
|
+
roomId: { type: 'number', description: 'The numeric identifier of the destination room' },
|
|
305
|
+
title: { type: 'string', description: 'Title of the duplicated mural' },
|
|
306
|
+
folderId: { type: 'string', description: 'Optional destination folder id' },
|
|
307
|
+
infinite: { type: 'boolean', description: 'Optional. Whether the canvas is infinite' }
|
|
308
|
+
},
|
|
309
|
+
required: ['muralId', 'roomId', 'title'],
|
|
310
|
+
additionalProperties: false
|
|
311
|
+
},
|
|
312
|
+
},
|
|
313
|
+
{
|
|
314
|
+
name: 'export-mural',
|
|
315
|
+
description: 'Export a mural in a given format (requires murals:read). Accepted downloadFormat values are defined by the Mural API',
|
|
316
|
+
inputSchema: {
|
|
317
|
+
type: 'object',
|
|
318
|
+
properties: {
|
|
319
|
+
muralId: { type: 'string', description: 'The unique identifier of the mural to export' },
|
|
320
|
+
downloadFormat: { type: 'string', description: 'The export format (e.g. pdf, png, zip — values defined by the Mural API)' }
|
|
321
|
+
},
|
|
322
|
+
required: ['muralId', 'downloadFormat'],
|
|
323
|
+
additionalProperties: false
|
|
324
|
+
},
|
|
325
|
+
},
|
|
326
|
+
{
|
|
327
|
+
name: 'get-board',
|
|
328
|
+
description: 'Get detailed information about a specific board (mural)',
|
|
329
|
+
inputSchema: {
|
|
330
|
+
type: 'object',
|
|
331
|
+
properties: {
|
|
332
|
+
boardId: {
|
|
333
|
+
type: 'string',
|
|
334
|
+
description: 'The unique identifier of the board/mural'
|
|
335
|
+
}
|
|
336
|
+
},
|
|
337
|
+
required: ['boardId'],
|
|
338
|
+
additionalProperties: false
|
|
339
|
+
},
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
name: 'check-user-scopes',
|
|
343
|
+
description: 'Check the current user\'s OAuth scopes and permissions',
|
|
344
|
+
inputSchema: {
|
|
345
|
+
type: 'object',
|
|
346
|
+
properties: {},
|
|
347
|
+
additionalProperties: false
|
|
348
|
+
},
|
|
349
|
+
},
|
|
350
|
+
// Content reading tools
|
|
351
|
+
{
|
|
352
|
+
name: 'get-mural-widgets',
|
|
353
|
+
description: 'Get all widgets from a mural',
|
|
354
|
+
inputSchema: {
|
|
355
|
+
type: 'object',
|
|
356
|
+
properties: {
|
|
357
|
+
muralId: {
|
|
358
|
+
type: 'string',
|
|
359
|
+
description: 'The unique identifier of the mural'
|
|
360
|
+
}
|
|
361
|
+
},
|
|
362
|
+
required: ['muralId'],
|
|
363
|
+
additionalProperties: false
|
|
364
|
+
},
|
|
365
|
+
},
|
|
366
|
+
{
|
|
367
|
+
name: 'get-mural-widget',
|
|
368
|
+
description: 'Get details of a specific widget by its ID (requires both the mural id and the widget id)',
|
|
369
|
+
inputSchema: {
|
|
370
|
+
type: 'object',
|
|
371
|
+
properties: {
|
|
372
|
+
muralId: {
|
|
373
|
+
type: 'string',
|
|
374
|
+
description: 'The unique identifier of the mural'
|
|
375
|
+
},
|
|
376
|
+
widgetId: {
|
|
377
|
+
type: 'string',
|
|
378
|
+
description: 'The unique identifier of the widget'
|
|
379
|
+
}
|
|
380
|
+
},
|
|
381
|
+
required: ['muralId', 'widgetId'],
|
|
382
|
+
additionalProperties: false
|
|
383
|
+
},
|
|
384
|
+
},
|
|
385
|
+
{
|
|
386
|
+
name: 'delete-widget',
|
|
387
|
+
description: 'Permanently delete a widget from a mural by its ID (irreversible)',
|
|
388
|
+
inputSchema: {
|
|
389
|
+
type: 'object',
|
|
390
|
+
properties: {
|
|
391
|
+
muralId: {
|
|
392
|
+
type: 'string',
|
|
393
|
+
description: 'The unique identifier of the mural'
|
|
394
|
+
},
|
|
395
|
+
widgetId: {
|
|
396
|
+
type: 'string',
|
|
397
|
+
description: 'The unique identifier of the widget to delete'
|
|
398
|
+
}
|
|
399
|
+
},
|
|
400
|
+
required: ['muralId', 'widgetId'],
|
|
401
|
+
additionalProperties: false
|
|
402
|
+
},
|
|
403
|
+
},
|
|
404
|
+
// Widget creation tools
|
|
405
|
+
{
|
|
406
|
+
name: 'create-sticky-notes',
|
|
407
|
+
description: 'Create sticky notes on a mural (max 1000 per request)',
|
|
408
|
+
inputSchema: {
|
|
409
|
+
type: 'object',
|
|
410
|
+
properties: {
|
|
411
|
+
muralId: {
|
|
412
|
+
type: 'string',
|
|
413
|
+
description: 'The unique identifier of the mural'
|
|
414
|
+
},
|
|
415
|
+
stickyNotes: {
|
|
416
|
+
type: 'array',
|
|
417
|
+
description: 'Array of sticky notes to create',
|
|
418
|
+
items: {
|
|
419
|
+
type: 'object',
|
|
420
|
+
properties: {
|
|
421
|
+
x: { type: 'number', description: 'X coordinate position' },
|
|
422
|
+
y: { type: 'number', description: 'Y coordinate position' },
|
|
423
|
+
text: { type: 'string', description: 'Text content of the sticky note' },
|
|
424
|
+
width: { type: 'number', description: 'Width in pixels (optional)' },
|
|
425
|
+
height: { type: 'number', description: 'Height in pixels (optional)' },
|
|
426
|
+
style: {
|
|
427
|
+
type: 'object',
|
|
428
|
+
description: 'Visual styling properties (optional)',
|
|
429
|
+
properties: {
|
|
430
|
+
backgroundColor: { type: 'string', description: 'Background color' },
|
|
431
|
+
textColor: { type: 'string', description: 'Text color' },
|
|
432
|
+
fontSize: { type: 'number', description: 'Font size' }
|
|
433
|
+
},
|
|
434
|
+
additionalProperties: false
|
|
435
|
+
}
|
|
436
|
+
},
|
|
437
|
+
required: ['x', 'y', 'text'],
|
|
438
|
+
additionalProperties: false
|
|
439
|
+
},
|
|
440
|
+
maxItems: 1000,
|
|
441
|
+
minItems: 1
|
|
442
|
+
}
|
|
443
|
+
},
|
|
444
|
+
required: ['muralId', 'stickyNotes'],
|
|
445
|
+
additionalProperties: false
|
|
446
|
+
},
|
|
447
|
+
},
|
|
448
|
+
// Shape widget
|
|
449
|
+
{
|
|
450
|
+
name: 'create-shapes',
|
|
451
|
+
description: 'Create shape widgets (rectangle, circle, triangle, diamond) on a mural. Each shape supports fill, border, and optional text.',
|
|
452
|
+
inputSchema: {
|
|
453
|
+
type: 'object',
|
|
454
|
+
properties: {
|
|
455
|
+
muralId: { type: 'string', description: 'The unique identifier of the mural' },
|
|
456
|
+
shapes: {
|
|
457
|
+
type: 'array',
|
|
458
|
+
description: 'Array of shape widgets to create',
|
|
459
|
+
items: {
|
|
460
|
+
type: 'object',
|
|
461
|
+
properties: {
|
|
462
|
+
x: { type: 'number' },
|
|
463
|
+
y: { type: 'number' },
|
|
464
|
+
width: { type: 'number' },
|
|
465
|
+
height: { type: 'number' },
|
|
466
|
+
shape: { type: 'string', enum: ['rectangle', 'circle', 'triangle', 'diamond'], description: 'Shape geometry' },
|
|
467
|
+
text: { type: 'string', description: 'Optional text content rendered inside the shape' },
|
|
468
|
+
rotation: { type: 'number' },
|
|
469
|
+
style: {
|
|
470
|
+
type: 'object',
|
|
471
|
+
properties: {
|
|
472
|
+
backgroundColor: { type: 'string' },
|
|
473
|
+
borderColor: { type: 'string' },
|
|
474
|
+
borderWidth: { type: 'number' },
|
|
475
|
+
borderStyle: { type: 'string', enum: ['solid', 'dashed', 'dotted'] },
|
|
476
|
+
fontColor: { type: 'string' },
|
|
477
|
+
fontSize: { type: 'number' },
|
|
478
|
+
fontFamily: { type: 'string' },
|
|
479
|
+
bold: { type: 'boolean' },
|
|
480
|
+
italic: { type: 'boolean' },
|
|
481
|
+
textAlign: { type: 'string', enum: ['left', 'center', 'right'] }
|
|
482
|
+
},
|
|
483
|
+
additionalProperties: true
|
|
484
|
+
}
|
|
485
|
+
},
|
|
486
|
+
required: ['x', 'y', 'width', 'height', 'shape'],
|
|
487
|
+
additionalProperties: true
|
|
488
|
+
},
|
|
489
|
+
minItems: 1
|
|
490
|
+
}
|
|
491
|
+
},
|
|
492
|
+
required: ['muralId', 'shapes'],
|
|
493
|
+
additionalProperties: false
|
|
494
|
+
}
|
|
495
|
+
},
|
|
496
|
+
// Arrow / connector widget
|
|
497
|
+
{
|
|
498
|
+
name: 'create-arrows',
|
|
499
|
+
description: 'Create arrow (connector) widgets on a mural. Arrows can anchor to other widgets via startRefId/endRefId or use absolute start/end points in the points array.',
|
|
500
|
+
inputSchema: {
|
|
501
|
+
type: 'object',
|
|
502
|
+
properties: {
|
|
503
|
+
muralId: { type: 'string', description: 'The unique identifier of the mural' },
|
|
504
|
+
arrows: {
|
|
505
|
+
type: 'array',
|
|
506
|
+
description: 'Array of arrow widgets to create',
|
|
507
|
+
items: {
|
|
508
|
+
type: 'object',
|
|
509
|
+
properties: {
|
|
510
|
+
x: { type: 'number' },
|
|
511
|
+
y: { type: 'number' },
|
|
512
|
+
width: { type: 'number' },
|
|
513
|
+
height: { type: 'number' },
|
|
514
|
+
points: {
|
|
515
|
+
type: 'array',
|
|
516
|
+
description: 'Two or more {x,y} points defining the arrow path. Coordinates are relative to x/y or absolute depending on Mural API version.',
|
|
517
|
+
items: { type: 'object', properties: { x: { type: 'number' }, y: { type: 'number' } }, required: ['x', 'y'], additionalProperties: false },
|
|
518
|
+
minItems: 2
|
|
519
|
+
},
|
|
520
|
+
arrowType: { type: 'string', enum: ['straight', 'curved', 'orthogonal'] },
|
|
521
|
+
tip: { type: 'string', enum: ['no tip', 'single', 'double'] },
|
|
522
|
+
startRefId: { type: 'string', description: 'Widget ID that the arrow starts at (anchors to widget)' },
|
|
523
|
+
endRefId: { type: 'string', description: 'Widget ID that the arrow ends at (anchors to widget)' },
|
|
524
|
+
label: { type: 'object', description: 'Optional label attached to the arrow' },
|
|
525
|
+
style: {
|
|
526
|
+
type: 'object',
|
|
527
|
+
properties: {
|
|
528
|
+
color: { type: 'string' },
|
|
529
|
+
width: { type: 'number' },
|
|
530
|
+
arrowheadType: { type: 'string' },
|
|
531
|
+
strokeStyle: { type: 'string', enum: ['solid', 'dashed', 'dotted'] }
|
|
532
|
+
},
|
|
533
|
+
additionalProperties: true
|
|
534
|
+
}
|
|
535
|
+
},
|
|
536
|
+
required: ['x', 'y', 'width', 'height', 'points'],
|
|
537
|
+
additionalProperties: true
|
|
538
|
+
},
|
|
539
|
+
minItems: 1
|
|
540
|
+
}
|
|
541
|
+
},
|
|
542
|
+
required: ['muralId', 'arrows'],
|
|
543
|
+
additionalProperties: false
|
|
544
|
+
}
|
|
545
|
+
},
|
|
546
|
+
// Text box widget
|
|
547
|
+
{
|
|
548
|
+
name: 'create-text-boxes',
|
|
549
|
+
description: 'Create text box widgets on a mural. Unlike sticky notes, text boxes support full font color, font size, and alignment.',
|
|
550
|
+
inputSchema: {
|
|
551
|
+
type: 'object',
|
|
552
|
+
properties: {
|
|
553
|
+
muralId: { type: 'string' },
|
|
554
|
+
textBoxes: {
|
|
555
|
+
type: 'array',
|
|
556
|
+
items: {
|
|
557
|
+
type: 'object',
|
|
558
|
+
properties: {
|
|
559
|
+
x: { type: 'number' },
|
|
560
|
+
y: { type: 'number' },
|
|
561
|
+
width: { type: 'number' },
|
|
562
|
+
height: { type: 'number' },
|
|
563
|
+
text: { type: 'string' },
|
|
564
|
+
rotation: { type: 'number' },
|
|
565
|
+
style: {
|
|
566
|
+
type: 'object',
|
|
567
|
+
properties: {
|
|
568
|
+
backgroundColor: { type: 'string' },
|
|
569
|
+
fontColor: { type: 'string' },
|
|
570
|
+
fontSize: { type: 'number' },
|
|
571
|
+
fontFamily: { type: 'string' },
|
|
572
|
+
bold: { type: 'boolean' },
|
|
573
|
+
italic: { type: 'boolean' },
|
|
574
|
+
textAlign: { type: 'string', enum: ['left', 'center', 'right'] },
|
|
575
|
+
border: { type: 'boolean' },
|
|
576
|
+
borderColor: { type: 'string' },
|
|
577
|
+
borderWidth: { type: 'number' }
|
|
578
|
+
},
|
|
579
|
+
additionalProperties: true
|
|
580
|
+
}
|
|
581
|
+
},
|
|
582
|
+
required: ['x', 'y', 'width', 'height', 'text'],
|
|
583
|
+
additionalProperties: true
|
|
584
|
+
},
|
|
585
|
+
minItems: 1
|
|
586
|
+
}
|
|
587
|
+
},
|
|
588
|
+
required: ['muralId', 'textBoxes'],
|
|
589
|
+
additionalProperties: false
|
|
590
|
+
}
|
|
591
|
+
},
|
|
592
|
+
// Title widget
|
|
593
|
+
{
|
|
594
|
+
name: 'create-titles',
|
|
595
|
+
description: 'Create title widgets (large heading text) on a mural.',
|
|
596
|
+
inputSchema: {
|
|
597
|
+
type: 'object',
|
|
598
|
+
properties: {
|
|
599
|
+
muralId: { type: 'string' },
|
|
600
|
+
titles: {
|
|
601
|
+
type: 'array',
|
|
602
|
+
items: {
|
|
603
|
+
type: 'object',
|
|
604
|
+
properties: {
|
|
605
|
+
x: { type: 'number' },
|
|
606
|
+
y: { type: 'number' },
|
|
607
|
+
width: { type: 'number' },
|
|
608
|
+
height: { type: 'number' },
|
|
609
|
+
text: { type: 'string' },
|
|
610
|
+
style: {
|
|
611
|
+
type: 'object',
|
|
612
|
+
properties: {
|
|
613
|
+
fontColor: { type: 'string' },
|
|
614
|
+
fontSize: { type: 'number' },
|
|
615
|
+
fontFamily: { type: 'string' },
|
|
616
|
+
bold: { type: 'boolean' },
|
|
617
|
+
italic: { type: 'boolean' },
|
|
618
|
+
textAlign: { type: 'string', enum: ['left', 'center', 'right'] }
|
|
619
|
+
},
|
|
620
|
+
additionalProperties: true
|
|
621
|
+
}
|
|
622
|
+
},
|
|
623
|
+
required: ['x', 'y', 'text'],
|
|
624
|
+
additionalProperties: true
|
|
625
|
+
},
|
|
626
|
+
minItems: 1
|
|
627
|
+
}
|
|
628
|
+
},
|
|
629
|
+
required: ['muralId', 'titles'],
|
|
630
|
+
additionalProperties: false
|
|
631
|
+
}
|
|
632
|
+
},
|
|
633
|
+
// Area widget (grouping container)
|
|
634
|
+
{
|
|
635
|
+
name: 'create-areas',
|
|
636
|
+
description: 'Create area widgets (grouping containers) on a mural.',
|
|
637
|
+
inputSchema: {
|
|
638
|
+
type: 'object',
|
|
639
|
+
properties: {
|
|
640
|
+
muralId: { type: 'string' },
|
|
641
|
+
areas: {
|
|
642
|
+
type: 'array',
|
|
643
|
+
items: {
|
|
644
|
+
type: 'object',
|
|
645
|
+
properties: {
|
|
646
|
+
x: { type: 'number' },
|
|
647
|
+
y: { type: 'number' },
|
|
648
|
+
width: { type: 'number' },
|
|
649
|
+
height: { type: 'number' },
|
|
650
|
+
title: { type: 'string' },
|
|
651
|
+
style: {
|
|
652
|
+
type: 'object',
|
|
653
|
+
properties: {
|
|
654
|
+
backgroundColor: { type: 'string' },
|
|
655
|
+
borderColor: { type: 'string' },
|
|
656
|
+
borderWidth: { type: 'number' },
|
|
657
|
+
fontColor: { type: 'string' },
|
|
658
|
+
fontSize: { type: 'number' }
|
|
659
|
+
},
|
|
660
|
+
additionalProperties: true
|
|
661
|
+
}
|
|
662
|
+
},
|
|
663
|
+
required: ['x', 'y', 'width', 'height'],
|
|
664
|
+
additionalProperties: true
|
|
665
|
+
},
|
|
666
|
+
minItems: 1
|
|
667
|
+
}
|
|
668
|
+
},
|
|
669
|
+
required: ['muralId', 'areas'],
|
|
670
|
+
additionalProperties: false
|
|
671
|
+
}
|
|
672
|
+
},
|
|
673
|
+
// Generic update tool — works for any widget kind
|
|
674
|
+
{
|
|
675
|
+
name: 'update-widget',
|
|
676
|
+
description: 'Update any widget by kind and ID (sticky-note, shape, arrow, text-box, title, area). Accepts arbitrary field updates.',
|
|
677
|
+
inputSchema: {
|
|
678
|
+
type: 'object',
|
|
679
|
+
properties: {
|
|
680
|
+
muralId: { type: 'string' },
|
|
681
|
+
kind: { type: 'string', enum: ['sticky-note', 'shape', 'arrow', 'text-box', 'title', 'area'] },
|
|
682
|
+
widgetId: { type: 'string' },
|
|
683
|
+
updates: { type: 'object', additionalProperties: true }
|
|
684
|
+
},
|
|
685
|
+
required: ['muralId', 'kind', 'widgetId', 'updates'],
|
|
686
|
+
additionalProperties: false
|
|
687
|
+
}
|
|
688
|
+
},
|
|
689
|
+
// PATCH/Update tools
|
|
690
|
+
{
|
|
691
|
+
name: 'update-sticky-note',
|
|
692
|
+
description: 'Update a sticky note widget in a mural',
|
|
693
|
+
inputSchema: {
|
|
694
|
+
type: 'object',
|
|
695
|
+
properties: {
|
|
696
|
+
muralId: {
|
|
697
|
+
type: 'string',
|
|
698
|
+
description: 'The unique identifier of the mural'
|
|
699
|
+
},
|
|
700
|
+
widgetId: {
|
|
701
|
+
type: 'string',
|
|
702
|
+
description: 'The unique identifier of the sticky note widget to update'
|
|
703
|
+
},
|
|
704
|
+
updates: {
|
|
705
|
+
type: 'object',
|
|
706
|
+
description: 'The properties to update',
|
|
707
|
+
properties: {
|
|
708
|
+
x: { type: 'number', description: 'X coordinate position' },
|
|
709
|
+
y: { type: 'number', description: 'Y coordinate position' },
|
|
710
|
+
text: { type: 'string', description: 'Text content of the sticky note' },
|
|
711
|
+
width: { type: 'number', description: 'Width in pixels' },
|
|
712
|
+
height: { type: 'number', description: 'Height in pixels' },
|
|
713
|
+
style: {
|
|
714
|
+
type: 'object',
|
|
715
|
+
description: 'Visual styling properties',
|
|
716
|
+
properties: {
|
|
717
|
+
backgroundColor: { type: 'string', description: 'Background color' },
|
|
718
|
+
textColor: { type: 'string', description: 'Text color' },
|
|
719
|
+
fontSize: { type: 'number', description: 'Font size' }
|
|
720
|
+
},
|
|
721
|
+
additionalProperties: false
|
|
722
|
+
}
|
|
723
|
+
},
|
|
724
|
+
additionalProperties: false
|
|
725
|
+
}
|
|
726
|
+
},
|
|
727
|
+
required: ['muralId', 'widgetId', 'updates'],
|
|
728
|
+
additionalProperties: false
|
|
729
|
+
},
|
|
730
|
+
},
|
|
731
|
+
],
|
|
732
|
+
};
|
|
733
|
+
});
|
|
734
|
+
// Handle tool calls
|
|
735
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
736
|
+
const { name, arguments: args } = request.params;
|
|
737
|
+
try {
|
|
738
|
+
switch (name) {
|
|
739
|
+
case 'list-workspaces': {
|
|
740
|
+
const schema = z.object({
|
|
741
|
+
limit: z.number().min(1).max(100).optional(),
|
|
742
|
+
offset: z.number().min(0).optional()
|
|
743
|
+
});
|
|
744
|
+
const { limit, offset } = schema.parse(args || {});
|
|
745
|
+
const workspaces = await muralClient.getWorkspaces(limit, offset);
|
|
746
|
+
return {
|
|
747
|
+
content: [
|
|
748
|
+
{
|
|
749
|
+
type: 'text',
|
|
750
|
+
text: JSON.stringify({
|
|
751
|
+
workspaces,
|
|
752
|
+
count: workspaces.length,
|
|
753
|
+
message: workspaces.length === 0
|
|
754
|
+
? 'No workspaces found'
|
|
755
|
+
: `Found ${workspaces.length} workspace${workspaces.length === 1 ? '' : 's'}`
|
|
756
|
+
}, null, 2)
|
|
757
|
+
}
|
|
758
|
+
],
|
|
759
|
+
};
|
|
760
|
+
}
|
|
761
|
+
case 'get-workspace': {
|
|
762
|
+
const schema = z.object({
|
|
763
|
+
workspaceId: z.string().min(1)
|
|
764
|
+
});
|
|
765
|
+
const { workspaceId } = schema.parse(args);
|
|
766
|
+
const workspace = await muralClient.getWorkspace(workspaceId);
|
|
767
|
+
return {
|
|
768
|
+
content: [
|
|
769
|
+
{
|
|
770
|
+
type: 'text',
|
|
771
|
+
text: JSON.stringify(workspace, null, 2)
|
|
772
|
+
}
|
|
773
|
+
],
|
|
774
|
+
};
|
|
775
|
+
}
|
|
776
|
+
case 'test-connection': {
|
|
777
|
+
const isConnected = await muralClient.testConnection();
|
|
778
|
+
return {
|
|
779
|
+
content: [
|
|
780
|
+
{
|
|
781
|
+
type: 'text',
|
|
782
|
+
text: JSON.stringify({
|
|
783
|
+
connected: isConnected,
|
|
784
|
+
message: isConnected
|
|
785
|
+
? 'Successfully connected to Mural API'
|
|
786
|
+
: 'Failed to connect to Mural API'
|
|
787
|
+
}, null, 2)
|
|
788
|
+
}
|
|
789
|
+
],
|
|
790
|
+
};
|
|
791
|
+
}
|
|
792
|
+
case 'clear-auth': {
|
|
793
|
+
await muralClient.clearAuthentication();
|
|
794
|
+
return {
|
|
795
|
+
content: [
|
|
796
|
+
{
|
|
797
|
+
type: 'text',
|
|
798
|
+
text: JSON.stringify({
|
|
799
|
+
message: 'Authentication tokens cleared. You will need to re-authenticate on the next API call.'
|
|
800
|
+
}, null, 2)
|
|
801
|
+
}
|
|
802
|
+
],
|
|
803
|
+
};
|
|
804
|
+
}
|
|
805
|
+
case 'debug-api-response': {
|
|
806
|
+
const debugInfo = await muralClient.debugWorkspacesAPI();
|
|
807
|
+
return {
|
|
808
|
+
content: [
|
|
809
|
+
{
|
|
810
|
+
type: 'text',
|
|
811
|
+
text: JSON.stringify({
|
|
812
|
+
debug: debugInfo,
|
|
813
|
+
message: 'Raw API response data for troubleshooting'
|
|
814
|
+
}, null, 2)
|
|
815
|
+
}
|
|
816
|
+
],
|
|
817
|
+
};
|
|
818
|
+
}
|
|
819
|
+
case 'get-rate-limit-status': {
|
|
820
|
+
const rateLimitStatus = await muralClient.getRateLimitStatus();
|
|
821
|
+
return {
|
|
822
|
+
content: [
|
|
823
|
+
{
|
|
824
|
+
type: 'text',
|
|
825
|
+
text: JSON.stringify({
|
|
826
|
+
rateLimits: rateLimitStatus,
|
|
827
|
+
message: 'Current rate limiting status',
|
|
828
|
+
explanation: {
|
|
829
|
+
user: `${rateLimitStatus.user.tokensRemaining}/${rateLimitStatus.user.capacity} requests available (${rateLimitStatus.user.refillRate}/second)`,
|
|
830
|
+
app: `${rateLimitStatus.app.tokensRemaining}/${rateLimitStatus.app.capacity} requests available (${rateLimitStatus.app.refillRate}/minute)`
|
|
831
|
+
}
|
|
832
|
+
}, null, 2)
|
|
833
|
+
}
|
|
834
|
+
],
|
|
835
|
+
};
|
|
836
|
+
}
|
|
837
|
+
case 'list-workspace-boards': {
|
|
838
|
+
const schema = z.object({
|
|
839
|
+
workspaceId: z.string().min(1)
|
|
840
|
+
});
|
|
841
|
+
const { workspaceId } = schema.parse(args);
|
|
842
|
+
const boards = await muralClient.getWorkspaceMurals(workspaceId);
|
|
843
|
+
return {
|
|
844
|
+
content: [
|
|
845
|
+
{
|
|
846
|
+
type: 'text',
|
|
847
|
+
text: JSON.stringify({
|
|
848
|
+
boards,
|
|
849
|
+
count: boards.length,
|
|
850
|
+
workspaceId,
|
|
851
|
+
message: boards.length === 0
|
|
852
|
+
? `No boards found in workspace ${workspaceId}`
|
|
853
|
+
: `Found ${boards.length} board${boards.length === 1 ? '' : 's'} in workspace`
|
|
854
|
+
}, null, 2)
|
|
855
|
+
}
|
|
856
|
+
],
|
|
857
|
+
};
|
|
858
|
+
}
|
|
859
|
+
case 'list-room-boards': {
|
|
860
|
+
const schema = z.object({
|
|
861
|
+
roomId: z.string().min(1)
|
|
862
|
+
});
|
|
863
|
+
const { roomId } = schema.parse(args);
|
|
864
|
+
const boards = await muralClient.getRoomMurals(roomId);
|
|
865
|
+
return {
|
|
866
|
+
content: [
|
|
867
|
+
{
|
|
868
|
+
type: 'text',
|
|
869
|
+
text: JSON.stringify({
|
|
870
|
+
boards,
|
|
871
|
+
count: boards.length,
|
|
872
|
+
roomId,
|
|
873
|
+
message: boards.length === 0
|
|
874
|
+
? `No boards found in room ${roomId}`
|
|
875
|
+
: `Found ${boards.length} board${boards.length === 1 ? '' : 's'} in room`
|
|
876
|
+
}, null, 2)
|
|
877
|
+
}
|
|
878
|
+
],
|
|
879
|
+
};
|
|
880
|
+
}
|
|
881
|
+
case 'list-workspace-rooms': {
|
|
882
|
+
const schema = z.object({
|
|
883
|
+
workspaceId: z.string().min(1),
|
|
884
|
+
openOnly: z.boolean().optional().default(false)
|
|
885
|
+
});
|
|
886
|
+
const { workspaceId, openOnly } = schema.parse(args);
|
|
887
|
+
const rooms = await muralClient.getWorkspaceRooms(workspaceId, openOnly);
|
|
888
|
+
return {
|
|
889
|
+
content: [
|
|
890
|
+
{
|
|
891
|
+
type: 'text',
|
|
892
|
+
text: JSON.stringify({
|
|
893
|
+
rooms,
|
|
894
|
+
count: rooms.length,
|
|
895
|
+
workspaceId,
|
|
896
|
+
openOnly,
|
|
897
|
+
message: rooms.length === 0
|
|
898
|
+
? `No rooms found in workspace ${workspaceId}`
|
|
899
|
+
: `Found ${rooms.length} room${rooms.length === 1 ? '' : 's'} in workspace`
|
|
900
|
+
}, null, 2)
|
|
901
|
+
}
|
|
902
|
+
],
|
|
903
|
+
};
|
|
904
|
+
}
|
|
905
|
+
case 'list-workspace-templates': {
|
|
906
|
+
const schema = z.object({
|
|
907
|
+
workspaceId: z.string().min(1),
|
|
908
|
+
searchQuery: z.string().optional(),
|
|
909
|
+
withoutDefault: z.boolean().optional().default(false)
|
|
910
|
+
});
|
|
911
|
+
const { workspaceId, searchQuery, withoutDefault } = schema.parse(args);
|
|
912
|
+
const templates = await muralClient.getWorkspaceTemplates(workspaceId, searchQuery, withoutDefault);
|
|
913
|
+
return {
|
|
914
|
+
content: [
|
|
915
|
+
{
|
|
916
|
+
type: 'text',
|
|
917
|
+
text: JSON.stringify({
|
|
918
|
+
templates,
|
|
919
|
+
count: templates.length,
|
|
920
|
+
workspaceId,
|
|
921
|
+
searchQuery: searchQuery ?? null,
|
|
922
|
+
message: templates.length === 0
|
|
923
|
+
? `No templates found in workspace ${workspaceId}${searchQuery ? ` matching "${searchQuery}"` : ''}`
|
|
924
|
+
: `Found ${templates.length} template${templates.length === 1 ? '' : 's'}`
|
|
925
|
+
}, null, 2)
|
|
926
|
+
}
|
|
927
|
+
],
|
|
928
|
+
};
|
|
929
|
+
}
|
|
930
|
+
case 'create-mural-from-template': {
|
|
931
|
+
const schema = z.object({
|
|
932
|
+
templateId: z.string().min(1),
|
|
933
|
+
title: z.string().min(1),
|
|
934
|
+
roomId: z.number(),
|
|
935
|
+
folderId: z.string().optional()
|
|
936
|
+
});
|
|
937
|
+
const { templateId, title, roomId, folderId } = schema.parse(args);
|
|
938
|
+
const mural = await muralClient.createMuralFromTemplate(templateId, title, roomId, folderId);
|
|
939
|
+
return {
|
|
940
|
+
content: [
|
|
941
|
+
{
|
|
942
|
+
type: 'text',
|
|
943
|
+
text: JSON.stringify({
|
|
944
|
+
mural,
|
|
945
|
+
message: `Created mural "${title}" from template ${templateId} in room ${roomId}`
|
|
946
|
+
}, null, 2)
|
|
947
|
+
}
|
|
948
|
+
],
|
|
949
|
+
};
|
|
950
|
+
}
|
|
951
|
+
case 'create-room': {
|
|
952
|
+
const schema = z.object({
|
|
953
|
+
workspaceId: z.string().min(1),
|
|
954
|
+
name: z.string().min(1),
|
|
955
|
+
type: z.enum(['open', 'private']),
|
|
956
|
+
description: z.string().optional(),
|
|
957
|
+
confidential: z.boolean().optional()
|
|
958
|
+
});
|
|
959
|
+
const { workspaceId, name, type, description, confidential } = schema.parse(args);
|
|
960
|
+
const room = await muralClient.createRoom(workspaceId, name, type, description, confidential);
|
|
961
|
+
return {
|
|
962
|
+
content: [
|
|
963
|
+
{
|
|
964
|
+
type: 'text',
|
|
965
|
+
text: JSON.stringify({
|
|
966
|
+
room,
|
|
967
|
+
message: `Created ${type} room "${name}" in workspace ${workspaceId}`
|
|
968
|
+
}, null, 2)
|
|
969
|
+
}
|
|
970
|
+
],
|
|
971
|
+
};
|
|
972
|
+
}
|
|
973
|
+
case 'create-mural': {
|
|
974
|
+
const schema = z.object({
|
|
975
|
+
roomId: z.number(),
|
|
976
|
+
title: z.string().optional(),
|
|
977
|
+
backgroundColor: z.string().optional(),
|
|
978
|
+
width: z.number().optional(),
|
|
979
|
+
height: z.number().optional(),
|
|
980
|
+
infinite: z.boolean().optional(),
|
|
981
|
+
folderId: z.string().optional()
|
|
982
|
+
});
|
|
983
|
+
const { roomId, ...options } = schema.parse(args);
|
|
984
|
+
const mural = await muralClient.createMural(roomId, options);
|
|
985
|
+
return {
|
|
986
|
+
content: [
|
|
987
|
+
{
|
|
988
|
+
type: 'text',
|
|
989
|
+
text: JSON.stringify({ mural, message: `Created mural in room ${roomId}` }, null, 2)
|
|
990
|
+
}
|
|
991
|
+
],
|
|
992
|
+
};
|
|
993
|
+
}
|
|
994
|
+
case 'update-mural': {
|
|
995
|
+
const schema = z.object({
|
|
996
|
+
muralId: z.string().min(1),
|
|
997
|
+
title: z.string().optional(),
|
|
998
|
+
backgroundColor: z.string().optional(),
|
|
999
|
+
favorite: z.boolean().optional(),
|
|
1000
|
+
status: z.enum(['active', 'archived']).optional(),
|
|
1001
|
+
width: z.number().optional(),
|
|
1002
|
+
height: z.number().optional(),
|
|
1003
|
+
infinite: z.boolean().optional(),
|
|
1004
|
+
visitorsPermission: z.enum(['read', 'write', 'none']).optional(),
|
|
1005
|
+
workspaceMembersPermission: z.enum(['read', 'write', 'none']).optional(),
|
|
1006
|
+
folderId: z.string().optional()
|
|
1007
|
+
});
|
|
1008
|
+
const { muralId, ...updates } = schema.parse(args);
|
|
1009
|
+
if (Object.keys(updates).length === 0) {
|
|
1010
|
+
throw new Error('update-mural requires at least one field to update');
|
|
1011
|
+
}
|
|
1012
|
+
const mural = await muralClient.updateMural(muralId, updates);
|
|
1013
|
+
return {
|
|
1014
|
+
content: [
|
|
1015
|
+
{
|
|
1016
|
+
type: 'text',
|
|
1017
|
+
text: JSON.stringify({ mural, message: `Updated mural ${muralId}` }, null, 2)
|
|
1018
|
+
}
|
|
1019
|
+
],
|
|
1020
|
+
};
|
|
1021
|
+
}
|
|
1022
|
+
case 'delete-mural': {
|
|
1023
|
+
const schema = z.object({
|
|
1024
|
+
muralId: z.string().min(1)
|
|
1025
|
+
});
|
|
1026
|
+
const { muralId } = schema.parse(args);
|
|
1027
|
+
await muralClient.deleteMural(muralId);
|
|
1028
|
+
return {
|
|
1029
|
+
content: [
|
|
1030
|
+
{
|
|
1031
|
+
type: 'text',
|
|
1032
|
+
text: JSON.stringify({ message: `Deleted mural ${muralId}` }, null, 2)
|
|
1033
|
+
}
|
|
1034
|
+
],
|
|
1035
|
+
};
|
|
1036
|
+
}
|
|
1037
|
+
case 'duplicate-mural': {
|
|
1038
|
+
const schema = z.object({
|
|
1039
|
+
muralId: z.string().min(1),
|
|
1040
|
+
roomId: z.number(),
|
|
1041
|
+
title: z.string().min(1),
|
|
1042
|
+
folderId: z.string().optional(),
|
|
1043
|
+
infinite: z.boolean().optional()
|
|
1044
|
+
});
|
|
1045
|
+
const { muralId, roomId, title, ...options } = schema.parse(args);
|
|
1046
|
+
const mural = await muralClient.duplicateMural(muralId, roomId, title, options);
|
|
1047
|
+
return {
|
|
1048
|
+
content: [
|
|
1049
|
+
{
|
|
1050
|
+
type: 'text',
|
|
1051
|
+
text: JSON.stringify({ mural, message: `Duplicated mural ${muralId} into room ${roomId}` }, null, 2)
|
|
1052
|
+
}
|
|
1053
|
+
],
|
|
1054
|
+
};
|
|
1055
|
+
}
|
|
1056
|
+
case 'export-mural': {
|
|
1057
|
+
const schema = z.object({
|
|
1058
|
+
muralId: z.string().min(1),
|
|
1059
|
+
downloadFormat: z.string().min(1)
|
|
1060
|
+
});
|
|
1061
|
+
const { muralId, downloadFormat } = schema.parse(args);
|
|
1062
|
+
const result = await muralClient.exportMural(muralId, downloadFormat);
|
|
1063
|
+
return {
|
|
1064
|
+
content: [
|
|
1065
|
+
{
|
|
1066
|
+
type: 'text',
|
|
1067
|
+
text: JSON.stringify({ export: result, message: `Exported mural ${muralId} as ${downloadFormat}` }, null, 2)
|
|
1068
|
+
}
|
|
1069
|
+
],
|
|
1070
|
+
};
|
|
1071
|
+
}
|
|
1072
|
+
case 'get-board': {
|
|
1073
|
+
const schema = z.object({
|
|
1074
|
+
boardId: z.string().min(1)
|
|
1075
|
+
});
|
|
1076
|
+
const { boardId } = schema.parse(args);
|
|
1077
|
+
const board = await muralClient.getMural(boardId);
|
|
1078
|
+
return {
|
|
1079
|
+
content: [
|
|
1080
|
+
{
|
|
1081
|
+
type: 'text',
|
|
1082
|
+
text: JSON.stringify(board, null, 2)
|
|
1083
|
+
}
|
|
1084
|
+
],
|
|
1085
|
+
};
|
|
1086
|
+
}
|
|
1087
|
+
case 'check-user-scopes': {
|
|
1088
|
+
const scopes = await muralClient.getUserScopes();
|
|
1089
|
+
// Only try to get user info if we have identity:read scope
|
|
1090
|
+
let user = null;
|
|
1091
|
+
if (scopes.includes('identity:read')) {
|
|
1092
|
+
user = await muralClient.getCurrentUser().catch(() => null);
|
|
1093
|
+
}
|
|
1094
|
+
return {
|
|
1095
|
+
content: [
|
|
1096
|
+
{
|
|
1097
|
+
type: 'text',
|
|
1098
|
+
text: JSON.stringify({
|
|
1099
|
+
user: user ? { id: user.id, firstName: user.firstName, lastName: user.lastName, email: user.email } : 'User info unavailable (requires identity:read scope)',
|
|
1100
|
+
scopes,
|
|
1101
|
+
scopeCount: scopes.length,
|
|
1102
|
+
message: scopes.length === 0
|
|
1103
|
+
? 'No OAuth scopes available. Please re-authenticate or check your Mural app configuration.'
|
|
1104
|
+
: `User has ${scopes.length} OAuth scope${scopes.length === 1 ? '' : 's'}`,
|
|
1105
|
+
recommendations: {
|
|
1106
|
+
'workspaces:read': scopes.includes('workspaces:read') ? 'Required for listing workspaces (✓ available)' : 'Required for listing workspaces (✗ missing)',
|
|
1107
|
+
'rooms:read': scopes.includes('rooms:read') ? 'Required for listing rooms (✓ available)' : 'Required for listing rooms (✗ missing)',
|
|
1108
|
+
'rooms:write': scopes.includes('rooms:write') ? 'Required for creating/modifying rooms (✓ available)' : 'Required for creating/modifying rooms (✗ missing)',
|
|
1109
|
+
'murals:read': scopes.includes('murals:read') ? 'Required for reading boards/murals (✓ available)' : 'Required for reading boards/murals (✗ missing)',
|
|
1110
|
+
'murals:write': scopes.includes('murals:write') ? 'Required for creating/modifying boards/murals (✓ available)' : 'Required for creating/modifying boards/murals (✗ missing)',
|
|
1111
|
+
'templates:read': scopes.includes('templates:read') ? 'Required for reading templates (✓ available)' : 'Required for reading templates (✗ missing)',
|
|
1112
|
+
'templates:write': scopes.includes('templates:write') ? 'Required for creating/modifying templates (✓ available)' : 'Required for creating/modifying templates (✗ missing)',
|
|
1113
|
+
'identity:read': scopes.includes('identity:read') ? 'Required for user info (✓ available)' : 'Required for user info (✗ missing)'
|
|
1114
|
+
},
|
|
1115
|
+
nextSteps: scopes.length === 0
|
|
1116
|
+
? ['Run clear-auth tool', 'Update your Mural app to include all required scopes', 'Re-authenticate when prompted']
|
|
1117
|
+
: (scopes.includes('murals:read') && scopes.includes('murals:write') && scopes.includes('workspaces:read') && scopes.includes('rooms:read') && scopes.includes('rooms:write') && scopes.includes('templates:read'))
|
|
1118
|
+
? ['You have comprehensive scopes for full workspace/room/board/template operations']
|
|
1119
|
+
: ['Add missing scopes to your Mural app: workspaces:read, rooms:read, rooms:write, murals:read, murals:write, templates:read, templates:write, identity:read', 'Run clear-auth tool', 'Re-authenticate to get new scopes']
|
|
1120
|
+
}, null, 2)
|
|
1121
|
+
}
|
|
1122
|
+
],
|
|
1123
|
+
};
|
|
1124
|
+
}
|
|
1125
|
+
// Content reading tools
|
|
1126
|
+
case 'get-mural-widgets': {
|
|
1127
|
+
const schema = z.object({
|
|
1128
|
+
muralId: z.string().min(1)
|
|
1129
|
+
});
|
|
1130
|
+
const { muralId } = schema.parse(args);
|
|
1131
|
+
const widgets = await muralClient.getMuralWidgets(muralId);
|
|
1132
|
+
return {
|
|
1133
|
+
content: [
|
|
1134
|
+
{
|
|
1135
|
+
type: 'text',
|
|
1136
|
+
text: JSON.stringify({
|
|
1137
|
+
widgets,
|
|
1138
|
+
count: widgets.length,
|
|
1139
|
+
muralId,
|
|
1140
|
+
message: widgets.length === 0
|
|
1141
|
+
? `No widgets found in mural ${muralId}`
|
|
1142
|
+
: `Found ${widgets.length} widget${widgets.length === 1 ? '' : 's'} in mural`
|
|
1143
|
+
}, null, 2)
|
|
1144
|
+
}
|
|
1145
|
+
],
|
|
1146
|
+
};
|
|
1147
|
+
}
|
|
1148
|
+
case 'get-mural-widget': {
|
|
1149
|
+
const schema = z.object({
|
|
1150
|
+
muralId: z.string().min(1),
|
|
1151
|
+
widgetId: z.string().min(1)
|
|
1152
|
+
});
|
|
1153
|
+
const { muralId, widgetId } = schema.parse(args);
|
|
1154
|
+
const widget = await muralClient.getMuralWidget(muralId, widgetId);
|
|
1155
|
+
return {
|
|
1156
|
+
content: [
|
|
1157
|
+
{
|
|
1158
|
+
type: 'text',
|
|
1159
|
+
text: JSON.stringify({
|
|
1160
|
+
widget,
|
|
1161
|
+
muralId,
|
|
1162
|
+
widgetId,
|
|
1163
|
+
message: `Widget details retrieved successfully`
|
|
1164
|
+
}, null, 2)
|
|
1165
|
+
}
|
|
1166
|
+
],
|
|
1167
|
+
};
|
|
1168
|
+
}
|
|
1169
|
+
// Widget creation tools
|
|
1170
|
+
case 'create-sticky-notes': {
|
|
1171
|
+
const schema = z.object({
|
|
1172
|
+
muralId: z.string().min(1),
|
|
1173
|
+
stickyNotes: z.array(z.object({
|
|
1174
|
+
x: z.number(),
|
|
1175
|
+
y: z.number(),
|
|
1176
|
+
text: z.string().min(1),
|
|
1177
|
+
width: z.number().optional(),
|
|
1178
|
+
height: z.number().optional(),
|
|
1179
|
+
style: z.object({
|
|
1180
|
+
backgroundColor: z.string().optional(),
|
|
1181
|
+
textColor: z.string().optional(),
|
|
1182
|
+
fontSize: z.number().optional()
|
|
1183
|
+
}).optional()
|
|
1184
|
+
})).min(1).max(1000)
|
|
1185
|
+
});
|
|
1186
|
+
const { muralId, stickyNotes } = schema.parse(args);
|
|
1187
|
+
// Helper function to calculate text-based dimensions
|
|
1188
|
+
function calculateTextDimensions(text, fontSize = 14) {
|
|
1189
|
+
const charWidth = fontSize * 0.6; // Approximate character width
|
|
1190
|
+
const lineHeight = fontSize * 1.4; // Standard line height
|
|
1191
|
+
const padding = 20; // Padding for sticky note
|
|
1192
|
+
const minWidth = 120; // Minimum sticky note width
|
|
1193
|
+
const maxWidth = 400; // Maximum sticky note width
|
|
1194
|
+
// Estimate text width and wrap to calculate height
|
|
1195
|
+
const words = text.split(' ');
|
|
1196
|
+
let currentLineWidth = 0;
|
|
1197
|
+
let lines = 1;
|
|
1198
|
+
for (const word of words) {
|
|
1199
|
+
const wordWidth = (word.length + 1) * charWidth; // +1 for space
|
|
1200
|
+
if (currentLineWidth + wordWidth > maxWidth - padding) {
|
|
1201
|
+
// Word doesn't fit, start new line
|
|
1202
|
+
lines++;
|
|
1203
|
+
currentLineWidth = word.length * charWidth;
|
|
1204
|
+
}
|
|
1205
|
+
else {
|
|
1206
|
+
currentLineWidth += wordWidth;
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
const calculatedWidth = Math.min(Math.max(currentLineWidth + padding, minWidth), maxWidth);
|
|
1210
|
+
const calculatedHeight = Math.max(lines * lineHeight + padding, 60); // Minimum height of 60
|
|
1211
|
+
return { width: calculatedWidth, height: calculatedHeight };
|
|
1212
|
+
}
|
|
1213
|
+
// Add required shape field and calculate dimensions for each sticky note
|
|
1214
|
+
const stickyNotesWithShape = stickyNotes.map(note => {
|
|
1215
|
+
const fontSize = note.style?.fontSize || 14;
|
|
1216
|
+
const dimensions = calculateTextDimensions(note.text, fontSize);
|
|
1217
|
+
return {
|
|
1218
|
+
...note,
|
|
1219
|
+
shape: 'rectangle',
|
|
1220
|
+
// Use provided dimensions if available, otherwise use calculated ones
|
|
1221
|
+
width: note.width || dimensions.width,
|
|
1222
|
+
height: note.height || dimensions.height
|
|
1223
|
+
};
|
|
1224
|
+
});
|
|
1225
|
+
const createdWidgets = await muralClient.createStickyNotes(muralId, stickyNotesWithShape);
|
|
1226
|
+
return {
|
|
1227
|
+
content: [
|
|
1228
|
+
{
|
|
1229
|
+
type: 'text',
|
|
1230
|
+
text: JSON.stringify({
|
|
1231
|
+
widgets: createdWidgets,
|
|
1232
|
+
count: createdWidgets.length,
|
|
1233
|
+
muralId,
|
|
1234
|
+
message: `Successfully created ${createdWidgets.length} sticky note${createdWidgets.length === 1 ? '' : 's'} in mural ${muralId}`
|
|
1235
|
+
}, null, 2)
|
|
1236
|
+
}
|
|
1237
|
+
],
|
|
1238
|
+
};
|
|
1239
|
+
}
|
|
1240
|
+
// PATCH/Update tool handlers
|
|
1241
|
+
case 'update-sticky-note': {
|
|
1242
|
+
const schema = z.object({
|
|
1243
|
+
muralId: z.string().min(1),
|
|
1244
|
+
widgetId: z.string().min(1),
|
|
1245
|
+
updates: z.object({
|
|
1246
|
+
x: z.number().optional(),
|
|
1247
|
+
y: z.number().optional(),
|
|
1248
|
+
text: z.string().min(1).optional(),
|
|
1249
|
+
width: z.number().optional(),
|
|
1250
|
+
height: z.number().optional(),
|
|
1251
|
+
style: z.object({
|
|
1252
|
+
backgroundColor: z.string().optional(),
|
|
1253
|
+
textColor: z.string().optional(),
|
|
1254
|
+
fontSize: z.number().optional()
|
|
1255
|
+
}).optional()
|
|
1256
|
+
})
|
|
1257
|
+
});
|
|
1258
|
+
const { muralId, widgetId, updates } = schema.parse(args);
|
|
1259
|
+
const updatedWidget = await muralClient.updateStickyNote(muralId, widgetId, updates);
|
|
1260
|
+
return {
|
|
1261
|
+
content: [
|
|
1262
|
+
{
|
|
1263
|
+
type: 'text',
|
|
1264
|
+
text: JSON.stringify({
|
|
1265
|
+
widget: updatedWidget,
|
|
1266
|
+
muralId,
|
|
1267
|
+
widgetId,
|
|
1268
|
+
message: `Successfully updated sticky note ${widgetId} in mural ${muralId}`
|
|
1269
|
+
}, null, 2)
|
|
1270
|
+
}
|
|
1271
|
+
],
|
|
1272
|
+
};
|
|
1273
|
+
}
|
|
1274
|
+
case 'delete-widget': {
|
|
1275
|
+
const schema = z.object({
|
|
1276
|
+
muralId: z.string().min(1),
|
|
1277
|
+
widgetId: z.string().min(1)
|
|
1278
|
+
});
|
|
1279
|
+
const { muralId, widgetId } = schema.parse(args);
|
|
1280
|
+
await muralClient.deleteWidget(muralId, widgetId);
|
|
1281
|
+
return {
|
|
1282
|
+
content: [{
|
|
1283
|
+
type: 'text',
|
|
1284
|
+
text: JSON.stringify({
|
|
1285
|
+
muralId,
|
|
1286
|
+
widgetId,
|
|
1287
|
+
deleted: true,
|
|
1288
|
+
message: `Successfully deleted widget ${widgetId} from mural ${muralId}`
|
|
1289
|
+
}, null, 2)
|
|
1290
|
+
}]
|
|
1291
|
+
};
|
|
1292
|
+
}
|
|
1293
|
+
case 'create-shapes': {
|
|
1294
|
+
const schema = z.object({
|
|
1295
|
+
muralId: z.string().min(1),
|
|
1296
|
+
shapes: z.array(z.record(z.string(), z.unknown())).min(1)
|
|
1297
|
+
});
|
|
1298
|
+
const { muralId, shapes } = schema.parse(args);
|
|
1299
|
+
const createdWidgets = await muralClient.createShapes(muralId, shapes);
|
|
1300
|
+
return {
|
|
1301
|
+
content: [{
|
|
1302
|
+
type: 'text',
|
|
1303
|
+
text: JSON.stringify({
|
|
1304
|
+
widgets: createdWidgets,
|
|
1305
|
+
count: Array.isArray(createdWidgets) ? createdWidgets.length : 0,
|
|
1306
|
+
muralId,
|
|
1307
|
+
message: `Created ${Array.isArray(createdWidgets) ? createdWidgets.length : 0} shape widget(s)`
|
|
1308
|
+
}, null, 2)
|
|
1309
|
+
}]
|
|
1310
|
+
};
|
|
1311
|
+
}
|
|
1312
|
+
case 'create-arrows': {
|
|
1313
|
+
const schema = z.object({
|
|
1314
|
+
muralId: z.string().min(1),
|
|
1315
|
+
arrows: z.array(z.record(z.string(), z.unknown())).min(1)
|
|
1316
|
+
});
|
|
1317
|
+
const { muralId, arrows } = schema.parse(args);
|
|
1318
|
+
const createdWidgets = await muralClient.createArrows(muralId, arrows);
|
|
1319
|
+
return {
|
|
1320
|
+
content: [{
|
|
1321
|
+
type: 'text',
|
|
1322
|
+
text: JSON.stringify({
|
|
1323
|
+
widgets: createdWidgets,
|
|
1324
|
+
count: Array.isArray(createdWidgets) ? createdWidgets.length : 0,
|
|
1325
|
+
muralId,
|
|
1326
|
+
message: `Created ${Array.isArray(createdWidgets) ? createdWidgets.length : 0} arrow widget(s)`
|
|
1327
|
+
}, null, 2)
|
|
1328
|
+
}]
|
|
1329
|
+
};
|
|
1330
|
+
}
|
|
1331
|
+
case 'create-text-boxes': {
|
|
1332
|
+
const schema = z.object({
|
|
1333
|
+
muralId: z.string().min(1),
|
|
1334
|
+
textBoxes: z.array(z.record(z.string(), z.unknown())).min(1)
|
|
1335
|
+
});
|
|
1336
|
+
const { muralId, textBoxes } = schema.parse(args);
|
|
1337
|
+
const createdWidgets = await muralClient.createTextBoxes(muralId, textBoxes);
|
|
1338
|
+
return {
|
|
1339
|
+
content: [{
|
|
1340
|
+
type: 'text',
|
|
1341
|
+
text: JSON.stringify({
|
|
1342
|
+
widgets: createdWidgets,
|
|
1343
|
+
count: Array.isArray(createdWidgets) ? createdWidgets.length : 0,
|
|
1344
|
+
muralId,
|
|
1345
|
+
message: `Created ${Array.isArray(createdWidgets) ? createdWidgets.length : 0} text-box widget(s)`
|
|
1346
|
+
}, null, 2)
|
|
1347
|
+
}]
|
|
1348
|
+
};
|
|
1349
|
+
}
|
|
1350
|
+
case 'create-titles': {
|
|
1351
|
+
const schema = z.object({
|
|
1352
|
+
muralId: z.string().min(1),
|
|
1353
|
+
titles: z.array(z.record(z.string(), z.unknown())).min(1)
|
|
1354
|
+
});
|
|
1355
|
+
const { muralId, titles } = schema.parse(args);
|
|
1356
|
+
const createdWidgets = await muralClient.createTitles(muralId, titles);
|
|
1357
|
+
return {
|
|
1358
|
+
content: [{
|
|
1359
|
+
type: 'text',
|
|
1360
|
+
text: JSON.stringify({
|
|
1361
|
+
widgets: createdWidgets,
|
|
1362
|
+
count: Array.isArray(createdWidgets) ? createdWidgets.length : 0,
|
|
1363
|
+
muralId,
|
|
1364
|
+
message: `Created ${Array.isArray(createdWidgets) ? createdWidgets.length : 0} title widget(s)`
|
|
1365
|
+
}, null, 2)
|
|
1366
|
+
}]
|
|
1367
|
+
};
|
|
1368
|
+
}
|
|
1369
|
+
case 'create-areas': {
|
|
1370
|
+
const schema = z.object({
|
|
1371
|
+
muralId: z.string().min(1),
|
|
1372
|
+
areas: z.array(z.record(z.string(), z.unknown())).min(1)
|
|
1373
|
+
});
|
|
1374
|
+
const { muralId, areas } = schema.parse(args);
|
|
1375
|
+
const createdWidgets = await muralClient.createAreas(muralId, areas);
|
|
1376
|
+
return {
|
|
1377
|
+
content: [{
|
|
1378
|
+
type: 'text',
|
|
1379
|
+
text: JSON.stringify({
|
|
1380
|
+
widgets: createdWidgets,
|
|
1381
|
+
count: Array.isArray(createdWidgets) ? createdWidgets.length : 0,
|
|
1382
|
+
muralId,
|
|
1383
|
+
message: `Created ${Array.isArray(createdWidgets) ? createdWidgets.length : 0} area widget(s)`
|
|
1384
|
+
}, null, 2)
|
|
1385
|
+
}]
|
|
1386
|
+
};
|
|
1387
|
+
}
|
|
1388
|
+
case 'update-widget': {
|
|
1389
|
+
const schema = z.object({
|
|
1390
|
+
muralId: z.string().min(1),
|
|
1391
|
+
kind: z.enum(['sticky-note', 'shape', 'arrow', 'text-box', 'title', 'area']),
|
|
1392
|
+
widgetId: z.string().min(1),
|
|
1393
|
+
updates: z.record(z.string(), z.unknown())
|
|
1394
|
+
});
|
|
1395
|
+
const { muralId, kind, widgetId, updates } = schema.parse(args);
|
|
1396
|
+
let updated;
|
|
1397
|
+
switch (kind) {
|
|
1398
|
+
case 'sticky-note':
|
|
1399
|
+
updated = await muralClient.updateStickyNote(muralId, widgetId, updates);
|
|
1400
|
+
break;
|
|
1401
|
+
case 'shape':
|
|
1402
|
+
updated = await muralClient.updateShape(muralId, widgetId, updates);
|
|
1403
|
+
break;
|
|
1404
|
+
case 'arrow':
|
|
1405
|
+
updated = await muralClient.updateArrow(muralId, widgetId, updates);
|
|
1406
|
+
break;
|
|
1407
|
+
case 'text-box':
|
|
1408
|
+
updated = await muralClient.updateTextBox(muralId, widgetId, updates);
|
|
1409
|
+
break;
|
|
1410
|
+
case 'title':
|
|
1411
|
+
updated = await muralClient.updateTitle(muralId, widgetId, updates);
|
|
1412
|
+
break;
|
|
1413
|
+
case 'area':
|
|
1414
|
+
updated = await muralClient.updateArea(muralId, widgetId, updates);
|
|
1415
|
+
break;
|
|
1416
|
+
}
|
|
1417
|
+
return {
|
|
1418
|
+
content: [{
|
|
1419
|
+
type: 'text',
|
|
1420
|
+
text: JSON.stringify({
|
|
1421
|
+
widget: updated,
|
|
1422
|
+
muralId,
|
|
1423
|
+
widgetId,
|
|
1424
|
+
kind,
|
|
1425
|
+
message: `Updated ${kind} ${widgetId}`
|
|
1426
|
+
}, null, 2)
|
|
1427
|
+
}]
|
|
1428
|
+
};
|
|
1429
|
+
}
|
|
1430
|
+
default:
|
|
1431
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
catch (error) {
|
|
1435
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
|
|
1436
|
+
return {
|
|
1437
|
+
content: [
|
|
1438
|
+
{
|
|
1439
|
+
type: 'text',
|
|
1440
|
+
text: JSON.stringify({
|
|
1441
|
+
error: true,
|
|
1442
|
+
message: errorMessage,
|
|
1443
|
+
tool: name
|
|
1444
|
+
}, null, 2)
|
|
1445
|
+
}
|
|
1446
|
+
],
|
|
1447
|
+
isError: true,
|
|
1448
|
+
};
|
|
1449
|
+
}
|
|
1450
|
+
});
|
|
1451
|
+
// Start the server
|
|
1452
|
+
const transport = new StdioServerTransport();
|
|
1453
|
+
await server.connect(transport);
|
|
1454
|
+
console.error('Mural MCP Server running on stdio');
|
|
1455
|
+
console.error(`Required environment variables: ${REQUIRED_ENV_VARS.join(', ')}`);
|
|
1456
|
+
console.error('Server ready to accept requests...');
|
|
1457
|
+
}
|
|
1458
|
+
// Handle graceful shutdown
|
|
1459
|
+
process.on('SIGINT', () => {
|
|
1460
|
+
console.error('Received SIGINT, shutting down gracefully...');
|
|
1461
|
+
process.exit(0);
|
|
1462
|
+
});
|
|
1463
|
+
process.on('SIGTERM', () => {
|
|
1464
|
+
console.error('Received SIGTERM, shutting down gracefully...');
|
|
1465
|
+
process.exit(0);
|
|
1466
|
+
});
|
|
1467
|
+
// Start the server
|
|
1468
|
+
main().catch((error) => {
|
|
1469
|
+
console.error('Fatal error in main():', error);
|
|
1470
|
+
process.exit(1);
|
|
1471
|
+
});
|
|
1472
|
+
//# sourceMappingURL=index.js.map
|