Skip to content

Commit fc93d9c

Browse files
committed
accept logger w/ ctor, log requests
1 parent d89c62d commit fc93d9c

File tree

1 file changed

+82
-18
lines changed

1 file changed

+82
-18
lines changed

packages/mcp/src/server.ts

Lines changed: 82 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
22
import { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
33
import type { CourseDBInterface } from '@vue-skuilder/db';
4+
import type { SkLogger } from '@vue-skuilder/common';
45
import {
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

4850
export 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

Comments
 (0)