1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js' ;
2
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js' ;
3
+ import { z } from 'zod' ;
4
+ import { HubConnectionBuilder } from '@microsoft/signalr' ;
5
+ import dotenv from 'dotenv' ;
6
+
7
+ dotenv . config ( ) ;
8
+
9
+ const logger = new class {
10
+ log = ( level , message ) => level > 1 && console . error ( `[${ level } ] ${ message } ` ) ;
11
+ } ;
12
+
13
+ const connection = new HubConnectionBuilder ( ) . withUrl ( `${ process . env [ 'WHITEBOARD_ENDPOINT' ] || 'http://localhost:5000' } /draw` ) . withAutomaticReconnect ( ) . configureLogging ( logger ) . build ( ) ;
14
+
15
+ const server = new McpServer ( {
16
+ name : 'Whiteboard' ,
17
+ version : '1.0.0'
18
+ } ) ;
19
+
20
+ let color = z . string ( ) . describe ( 'color of the shape, valid values are: black, grey, darkred, red, orange, yellow, green, deepskyblue, indigo, purple' ) ;
21
+ let width = z . number ( ) . describe ( 'width of the shape, valid values are: 1, 2, 4, 8' ) ;
22
+ let point = z . object ( {
23
+ x : z . number ( ) . describe ( 'x coordinate of the point, 0 denotes the left edge of the whiteboard' ) ,
24
+ y : z . number ( ) . describe ( 'y coordinate of the point, 0 denotes the top edge of the whiteboard' )
25
+ } ) ;
26
+ let id = z . string ( ) . describe ( 'unique identifier of the shape, if it does not exist, it will be created, if it exists, it will be updated' ) ;
27
+
28
+ server . tool ( 'send_message' , 'post a message on whiteboard' , { name : z . string ( ) , message : z . string ( ) } , async ( { name, message } ) => {
29
+ await connection . send ( 'sendMessage' , name , message ) ;
30
+ return { content : [ { type : 'text' , text : 'Message sent' } ] }
31
+ } ) ;
32
+
33
+ server . tool (
34
+ 'add_or_update_polyline' , 'add or update a polyline on whiteboard' ,
35
+ {
36
+ id, polyline : z . object ( {
37
+ color, width,
38
+ points : z . array ( point ) . describe ( 'array of points that define the polyline' )
39
+ } )
40
+ } ,
41
+ async ( { id, polyline } ) => {
42
+ await connection . send ( 'addOrUpdatePolyline' , id , polyline ) ;
43
+ return { content : [ { type : 'text' , text : 'Polyline added or updated' } ] } ;
44
+ } ) ;
45
+
46
+ server . tool (
47
+ 'add_or_update_line' , 'add or update a line on whiteboard' ,
48
+ {
49
+ id, line : z . object ( {
50
+ color, width,
51
+ start : point . describe ( 'start point of the line' ) ,
52
+ end : point . describe ( 'end point of the line' )
53
+ } )
54
+ } ,
55
+ async ( { id, line } ) => {
56
+ await connection . send ( 'addOrUpdateLine' , id , line ) ;
57
+ return { content : [ { type : 'text' , text : 'Line added or updated' } ] } ;
58
+ } ) ;
59
+
60
+ server . tool (
61
+ 'add_or_update_circle' , 'add or update a circle on whiteboard' ,
62
+ {
63
+ id, circle : z . object ( {
64
+ color, width,
65
+ center : point . describe ( 'center point of the circle' ) ,
66
+ radius : z . number ( ) . describe ( 'radius of the circle' )
67
+ } )
68
+ } ,
69
+ async ( { id, circle } ) => {
70
+ await connection . send ( 'addOrUpdateCircle' , id , circle ) ;
71
+ return { content : [ { type : 'text' , text : 'Circle added or updated' } ] } ;
72
+ } ) ;
73
+
74
+ server . tool (
75
+ 'add_or_update_rect' , 'add or update a rectangle on whiteboard' ,
76
+ {
77
+ id, rect : z . object ( {
78
+ color, width,
79
+ topLeft : point . describe ( 'top left corner of the rectangle' ) ,
80
+ bottomRight : point . describe ( 'bottom right of the rectangle' )
81
+ } )
82
+ } ,
83
+ async ( { id, rect } ) => {
84
+ await connection . send ( 'addOrUpdateRect' , id , rect ) ;
85
+ return { content : [ { type : 'text' , text : 'Rectangle added or updated' } ] } ;
86
+ } ) ;
87
+
88
+ server . tool (
89
+ 'add_or_update_ellipse' , 'add or update an ellipse on whiteboard' ,
90
+ {
91
+ id, ellipse : z . object ( {
92
+ color, width,
93
+ topLeft : point . describe ( 'top left corner of the bounding rectangle of the ellipse' ) ,
94
+ bottomRight : point . describe ( 'bottom right of the bounding rectangle of the ellipse' )
95
+ } )
96
+ } ,
97
+ async ( { id, ellipse } ) => {
98
+ await connection . send ( 'addOrUpdateEllipse' , id , ellipse ) ;
99
+ return { content : [ { type : 'text' , text : 'Ellipse added or updated' } ] } ;
100
+ } ) ;
101
+
102
+ server . tool (
103
+ 'remove_shape' , 'remove a shape from whiteboard' ,
104
+ { id } ,
105
+ async ( { id } ) => {
106
+ await connection . send ( 'removeShape' , id ) ;
107
+ return { content : [ { type : 'text' , text : 'Shape removed' } ] } ;
108
+ } ) ;
109
+
110
+ server . tool (
111
+ 'clear' , 'clear the whiteboard' ,
112
+ { } ,
113
+ async ( ) => {
114
+ await connection . send ( 'clear' ) ;
115
+ return { content : [ { type : 'text' , text : 'Whiteboard cleared' } ] } ;
116
+ } ) ;
117
+
118
+ const transport = new StdioServerTransport ( ) ;
119
+
120
+ await server . connect ( transport ) ;
121
+
122
+ const sleep = ms => new Promise ( resolve => setTimeout ( resolve , ms ) ) ;
123
+ for ( ; ; ) {
124
+ try {
125
+ await connection . start ( ) ;
126
+ break ;
127
+ } catch ( e ) {
128
+ console . error ( 'Failed to start SignalR connection: ' + e . message ) ;
129
+ await sleep ( 5000 ) ;
130
+ }
131
+ }
0 commit comments