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