11import { McpServer , ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js' ;
22import { Transport } from '@modelcontextprotocol/sdk/shared/transport.js' ;
33import type { CourseDBInterface } from '@vue-skuilder/db' ;
4+ import type { SkLogger } from '@vue-skuilder/common' ;
45import {
56 handleCourseConfigResource ,
67 handleCardsAllResource ,
@@ -43,16 +44,20 @@ export interface MCPServerOptions {
4344 maxCardsPerQuery ?: number ;
4445 allowedDataShapes ?: string [ ] ;
4546 eloCalibrationMode ?: 'strict' | 'adaptive' | 'manual' ;
47+ logger ?: SkLogger ;
4648}
4749
4850export class MCPServer {
4951 private mcpServer : McpServer ;
5052 private transport ?: Transport ;
53+ private logger : SkLogger ;
5154
5255 constructor (
5356 private courseDB : CourseDBInterface ,
5457 private readonly options : MCPServerOptions = { }
5558 ) {
59+ // Use provided logger or no-op logger as default
60+ this . logger = options . logger || { debug : ( ) => { } , info : ( ) => { } , warn : ( ) => { } , error : ( ) => { } } ;
5661 this . mcpServer = new McpServer ( {
5762 name : 'vue-skuilder-mcp' ,
5863 version : '0.1.0' ,
@@ -72,16 +77,23 @@ export class MCPServer {
7277 mimeType : 'application/json' ,
7378 } ,
7479 async ( uri ) => {
75- const result = await handleCourseConfigResource ( this . courseDB ) ;
76- return {
77- contents : [
78- {
79- uri : uri . href ,
80- text : JSON . stringify ( result , null , 2 ) ,
81- mimeType : 'application/json' ,
82- } ,
83- ] ,
84- } ;
80+ this . logger . debug ( 'MCP Server: Accessing course config resource' ) ;
81+ try {
82+ const result = await handleCourseConfigResource ( this . courseDB ) ;
83+ this . logger . info ( `MCP Server: Course config accessed - ${ result . config . name } ` ) ;
84+ return {
85+ contents : [
86+ {
87+ uri : uri . href ,
88+ text : JSON . stringify ( result , null , 2 ) ,
89+ mimeType : 'application/json' ,
90+ } ,
91+ ] ,
92+ } ;
93+ } catch ( error ) {
94+ this . logger . error ( 'MCP Server: Failed to access course config' , error ) ;
95+ throw error ;
96+ }
8597 }
8698 ) ;
8799
@@ -99,7 +111,9 @@ export class MCPServer {
99111 const limit = parseInt ( url . searchParams . get ( 'limit' ) || '50' , 10 ) ;
100112 const offset = parseInt ( url . searchParams . get ( 'offset' ) || '0' , 10 ) ;
101113
114+ this . logger . debug ( `MCP Server: Fetching cards, limit=${ limit } , offset=${ offset } ` ) ;
102115 const result = await handleCardsAllResource ( this . courseDB , limit , offset ) ;
116+ this . logger . info ( `MCP Server: Retrieved ${ result . cards . length } cards` ) ;
103117 return {
104118 contents : [
105119 {
@@ -126,12 +140,14 @@ export class MCPServer {
126140 const limit = parseInt ( url . searchParams . get ( 'limit' ) || '50' , 10 ) ;
127141 const offset = parseInt ( url . searchParams . get ( 'offset' ) || '0' , 10 ) ;
128142
143+ this . logger . debug ( `MCP Server: Fetching cards by tag '${ tagName } ', limit=${ limit } , offset=${ offset } ` ) ;
129144 const result = await handleCardsTagResource (
130145 this . courseDB ,
131146 tagName as string ,
132147 limit ,
133148 offset
134149 ) ;
150+ this . logger . info ( `MCP Server: Retrieved ${ result . cards . length } cards for tag '${ tagName } '` ) ;
135151 return {
136152 contents : [
137153 {
@@ -158,12 +174,14 @@ export class MCPServer {
158174 const limit = parseInt ( url . searchParams . get ( 'limit' ) || '50' , 10 ) ;
159175 const offset = parseInt ( url . searchParams . get ( 'offset' ) || '0' , 10 ) ;
160176
177+ this . logger . debug ( `MCP Server: Fetching cards by shape '${ shapeName } ', limit=${ limit } , offset=${ offset } ` ) ;
161178 const result = await handleCardsShapeResource (
162179 this . courseDB ,
163180 shapeName as string ,
164181 limit ,
165182 offset
166183 ) ;
184+ this . logger . info ( `MCP Server: Retrieved ${ result . cards . length } cards for shape '${ shapeName } '` ) ;
167185 return {
168186 contents : [
169187 {
@@ -190,12 +208,14 @@ export class MCPServer {
190208 const limit = parseInt ( url . searchParams . get ( 'limit' ) || '50' , 10 ) ;
191209 const offset = parseInt ( url . searchParams . get ( 'offset' ) || '0' , 10 ) ;
192210
211+ this . logger . debug ( `MCP Server: Fetching cards by ELO range '${ eloRange } ', limit=${ limit } , offset=${ offset } ` ) ;
193212 const result = await handleCardsEloResource (
194213 this . courseDB ,
195214 eloRange as string ,
196215 limit ,
197216 offset
198217 ) ;
218+ this . logger . info ( `MCP Server: Retrieved ${ result . cards . length } cards for ELO range '${ eloRange } '` ) ;
199219 return {
200220 contents : [
201221 {
@@ -218,7 +238,9 @@ export class MCPServer {
218238 mimeType : 'application/json' ,
219239 } ,
220240 async ( uri ) => {
241+ this . logger . debug ( 'MCP Server: Fetching all shapes' ) ;
221242 const result = await handleShapesAllResource ( this . courseDB ) ;
243+ this . logger . info ( `MCP Server: Retrieved ${ result . shapes . length } shapes` ) ;
222244 return {
223245 contents : [
224246 {
@@ -241,7 +263,9 @@ export class MCPServer {
241263 mimeType : 'application/json' ,
242264 } ,
243265 async ( uri , { shapeName } ) => {
266+ this . logger . debug ( `MCP Server: Fetching shape '${ shapeName } '` ) ;
244267 const result = await handleShapeSpecificResource ( this . courseDB , shapeName as string ) ;
268+ this . logger . info ( `MCP Server: Retrieved shape '${ shapeName } '` ) ;
245269 return {
246270 contents : [
247271 {
@@ -264,7 +288,9 @@ export class MCPServer {
264288 mimeType : 'application/json' ,
265289 } ,
266290 async ( uri ) => {
291+ this . logger . debug ( 'MCP Server: Fetching all tags' ) ;
267292 const result = await handleTagsAllResource ( this . courseDB ) ;
293+ this . logger . info ( `MCP Server: Retrieved ${ result . tags . length } tags` ) ;
268294 return {
269295 contents : [
270296 {
@@ -287,7 +313,9 @@ export class MCPServer {
287313 mimeType : 'application/json' ,
288314 } ,
289315 async ( uri ) => {
316+ this . logger . debug ( 'MCP Server: Fetching tag statistics' ) ;
290317 const result = await handleTagsStatsResource ( this . courseDB ) ;
318+ this . logger . info ( 'MCP Server: Retrieved tag statistics' ) ;
291319 return {
292320 contents : [
293321 {
@@ -310,7 +338,9 @@ export class MCPServer {
310338 mimeType : 'application/json' ,
311339 } ,
312340 async ( uri , { tagName } ) => {
341+ this . logger . debug ( `MCP Server: Fetching tag '${ tagName } '` ) ;
313342 const result = await handleTagSpecificResource ( this . courseDB , tagName as string ) ;
343+ this . logger . info ( `MCP Server: Retrieved tag '${ tagName } ' with ${ result . cardCount } cards` ) ;
314344 return {
315345 contents : [
316346 {
@@ -333,7 +363,9 @@ export class MCPServer {
333363 mimeType : 'application/json' ,
334364 } ,
335365 async ( uri , { tags } ) => {
366+ this . logger . debug ( `MCP Server: Fetching union of tags '${ tags } '` ) ;
336367 const result = await handleTagsUnionResource ( this . courseDB , tags as string ) ;
368+ this . logger . info ( `MCP Server: Retrieved ${ result . cardIds . length } cards for tag union '${ tags } '` ) ;
337369 return {
338370 contents : [
339371 {
@@ -356,7 +388,9 @@ export class MCPServer {
356388 mimeType : 'application/json' ,
357389 } ,
358390 async ( uri , { tags } ) => {
391+ this . logger . debug ( `MCP Server: Fetching intersection of tags '${ tags } '` ) ;
359392 const result = await handleTagsIntersectResource ( this . courseDB , tags as string ) ;
393+ this . logger . info ( `MCP Server: Retrieved ${ result . cardIds . length } cards for tag intersection '${ tags } '` ) ;
360394 return {
361395 contents : [
362396 {
@@ -379,7 +413,9 @@ export class MCPServer {
379413 mimeType : 'application/json' ,
380414 } ,
381415 async ( uri , { tags } ) => {
416+ this . logger . debug ( `MCP Server: Fetching exclusive tags '${ tags } '` ) ;
382417 const result = await handleTagsExclusiveResource ( this . courseDB , tags as string ) ;
418+ this . logger . info ( `MCP Server: Retrieved ${ result . cardIds . length } cards for tag exclusion '${ tags } '` ) ;
383419 return {
384420 contents : [
385421 {
@@ -402,7 +438,9 @@ export class MCPServer {
402438 mimeType : 'application/json' ,
403439 } ,
404440 async ( uri ) => {
441+ this . logger . debug ( 'MCP Server: Fetching tag distribution' ) ;
405442 const result = await handleTagsDistributionResource ( this . courseDB ) ;
443+ this . logger . info ( `MCP Server: Retrieved tag distribution with ${ result . distribution . length } entries` ) ;
406444 return {
407445 contents : [
408446 {
@@ -430,7 +468,10 @@ export class MCPServer {
430468 } ,
431469 } ,
432470 async ( input ) => {
433- const result = await handleCreateCard ( this . courseDB , input as CreateCardInput ) ;
471+ const createInput = input as CreateCardInput ;
472+ this . logger . info ( `MCP Server: Creating card with datashape '${ createInput . datashape } '` ) ;
473+ const result = await handleCreateCard ( this . courseDB , createInput ) ;
474+ this . logger . info ( `MCP Server: Created card ${ result . cardId } ` ) ;
434475 return {
435476 content : [
436477 {
@@ -457,7 +498,10 @@ export class MCPServer {
457498 } ,
458499 } ,
459500 async ( input ) => {
460- const result = await handleUpdateCard ( this . courseDB , input as UpdateCardInput ) ;
501+ const updateInput = input as UpdateCardInput ;
502+ this . logger . info ( `MCP Server: Updating card ${ updateInput . cardId } ` ) ;
503+ const result = await handleUpdateCard ( this . courseDB , updateInput ) ;
504+ this . logger . info ( `MCP Server: Updated card ${ updateInput . cardId } ` ) ;
461505 return {
462506 content : [
463507 {
@@ -483,7 +527,10 @@ export class MCPServer {
483527 } ,
484528 } ,
485529 async ( input ) => {
486- const result = await handleTagCard ( this . courseDB , input as TagCardInput ) ;
530+ const tagInput = input as TagCardInput ;
531+ this . logger . info ( `MCP Server: ${ tagInput . action === 'add' ? 'Adding' : 'Removing' } tags [${ tagInput . tags . join ( ', ' ) } ] ${ tagInput . action === 'add' ? 'to' : 'from' } card ${ tagInput . cardId } ` ) ;
532+ const result = await handleTagCard ( this . courseDB , tagInput ) ;
533+ this . logger . info ( `MCP Server: Tag operation completed for card ${ tagInput . cardId } ` ) ;
487534 return {
488535 content : [
489536 {
@@ -508,7 +555,10 @@ export class MCPServer {
508555 } ,
509556 } ,
510557 async ( input ) => {
511- const result = await handleDeleteCard ( this . courseDB , input as DeleteCardInput ) ;
558+ const deleteInput = input as DeleteCardInput ;
559+ this . logger . warn ( `MCP Server: Deleting card ${ deleteInput . cardId } ${ deleteInput . reason ? ` (reason: ${ deleteInput . reason } )` : '' } ` ) ;
560+ const result = await handleDeleteCard ( this . courseDB , deleteInput ) ;
561+ this . logger . info ( `MCP Server: Card ${ deleteInput . cardId } deletion completed` ) ;
512562 return {
513563 content : [
514564 {
@@ -573,14 +623,28 @@ export class MCPServer {
573623
574624 async start ( transport : Transport ) : Promise < void > {
575625 this . transport = transport ;
576- await this . mcpServer . connect ( transport ) ;
626+ this . logger . info ( 'MCP Server: Starting connection...' ) ;
627+ try {
628+ await this . mcpServer . connect ( transport ) ;
629+ this . logger . info ( 'MCP Server: Connected successfully' ) ;
630+ } catch ( error ) {
631+ this . logger . error ( 'MCP Server: Failed to start connection' , error ) ;
632+ throw error ;
633+ }
577634 }
578635
579636 async stop ( ) : Promise < void > {
580- if ( this . transport ) {
581- await this . transport . close ( ) ;
637+ this . logger . info ( 'MCP Server: Stopping connection...' ) ;
638+ try {
639+ if ( this . transport ) {
640+ await this . transport . close ( ) ;
641+ }
642+ await this . mcpServer . close ( ) ;
643+ this . logger . info ( 'MCP Server: Stopped successfully' ) ;
644+ } catch ( error ) {
645+ this . logger . error ( 'MCP Server: Error during shutdown' , error ) ;
646+ throw error ;
582647 }
583- await this . mcpServer . close ( ) ;
584648 }
585649
586650 // Getter for accessing the underlying MCP server (for testing)
0 commit comments