@access-mcp/shared 0.3.3 → 0.6.0

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.
@@ -1,21 +1,26 @@
1
1
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
2
2
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
+ import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
3
4
  import { CallToolRequestSchema, GetPromptRequestSchema, ListPromptsRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
4
5
  import axios from "axios";
5
6
  import express from "express";
7
+ import { createLogger } from "./logger.js";
6
8
  export class BaseAccessServer {
7
9
  serverName;
8
10
  version;
9
11
  baseURL;
10
12
  server;
11
13
  transport;
14
+ logger;
12
15
  _httpClient;
13
16
  _httpServer;
14
17
  _httpPort;
18
+ _sseTransports = new Map();
15
19
  constructor(serverName, version, baseURL = "https://support.access-ci.org/api") {
16
20
  this.serverName = serverName;
17
21
  this.version = version;
18
22
  this.baseURL = baseURL;
23
+ this.logger = createLogger(serverName);
19
24
  this.server = new Server({
20
25
  name: serverName,
21
26
  version: version,
@@ -73,13 +78,13 @@ export class BaseAccessServer {
73
78
  }
74
79
  catch (error) {
75
80
  const errorMessage = error instanceof Error ? error.message : String(error);
76
- console.error("Error handling tool call:", errorMessage);
81
+ this.logger.error("Error handling tool call", { error: errorMessage });
77
82
  return {
78
83
  content: [
79
84
  {
80
85
  type: "text",
81
86
  text: JSON.stringify({
82
- error: errorMessage
87
+ error: errorMessage,
83
88
  }),
84
89
  },
85
90
  ],
@@ -93,7 +98,7 @@ export class BaseAccessServer {
93
98
  }
94
99
  catch (error) {
95
100
  const errorMessage = error instanceof Error ? error.message : String(error);
96
- console.error("Error reading resource:", errorMessage);
101
+ this.logger.error("Error reading resource", { error: errorMessage });
97
102
  return {
98
103
  contents: [
99
104
  {
@@ -120,7 +125,7 @@ export class BaseAccessServer {
120
125
  }
121
126
  catch (error) {
122
127
  const errorMessage = error instanceof Error ? error.message : String(error);
123
- console.error("Error getting prompt:", errorMessage);
128
+ this.logger.error("Error getting prompt", { error: errorMessage });
124
129
  throw error;
125
130
  }
126
131
  });
@@ -135,13 +140,13 @@ export class BaseAccessServer {
135
140
  * Handle resource read requests - override in subclasses
136
141
  */
137
142
  async handleResourceRead(request) {
138
- throw new Error("Resource reading not supported by this server");
143
+ throw new Error(`Resource reading not supported by this server: ${request.params.uri}`);
139
144
  }
140
145
  /**
141
146
  * Handle get prompt requests - override in subclasses
142
147
  */
143
148
  async handleGetPrompt(request) {
144
- throw new Error("Prompt not found");
149
+ throw new Error(`Prompt not found: ${request.params.name}`);
145
150
  }
146
151
  /**
147
152
  * Helper method to create a standard error response (MCP 2025 compliant)
@@ -218,7 +223,7 @@ export class BaseAccessServer {
218
223
  if (options?.httpPort) {
219
224
  this._httpPort = options.httpPort;
220
225
  await this.startHttpService();
221
- console.log(`${this.serverName} HTTP server running on port ${this._httpPort}`);
226
+ this.logger.info("HTTP server running", { port: this._httpPort });
222
227
  }
223
228
  else {
224
229
  // Only connect stdio transport when NOT in HTTP mode
@@ -228,7 +233,7 @@ export class BaseAccessServer {
228
233
  }
229
234
  }
230
235
  /**
231
- * Start HTTP service layer for inter-server communication
236
+ * Start HTTP service layer with SSE support for remote MCP connections
232
237
  */
233
238
  async startHttpService() {
234
239
  if (!this._httpPort)
@@ -236,41 +241,79 @@ export class BaseAccessServer {
236
241
  this._httpServer = express();
237
242
  this._httpServer.use(express.json());
238
243
  // Health check endpoint
239
- this._httpServer.get('/health', (req, res) => {
244
+ this._httpServer.get("/health", (req, res) => {
240
245
  res.json({
241
246
  server: this.serverName,
242
247
  version: this.version,
243
- status: 'healthy',
244
- timestamp: new Date().toISOString()
248
+ status: "healthy",
249
+ timestamp: new Date().toISOString(),
245
250
  });
246
251
  });
247
- // List available tools endpoint
248
- this._httpServer.get('/tools', (req, res) => {
252
+ // SSE endpoint for MCP remote connections
253
+ this._httpServer.get("/sse", async (req, res) => {
254
+ this.logger.debug("New SSE connection");
255
+ const transport = new SSEServerTransport("/messages", res);
256
+ const sessionId = transport.sessionId;
257
+ this._sseTransports.set(sessionId, transport);
258
+ // Clean up on disconnect
259
+ res.on("close", () => {
260
+ this.logger.debug("SSE connection closed", { sessionId });
261
+ this._sseTransports.delete(sessionId);
262
+ });
263
+ // Create a new server instance for this SSE connection
264
+ const sseServer = new Server({
265
+ name: this.serverName,
266
+ version: this.version,
267
+ }, {
268
+ capabilities: {
269
+ resources: {},
270
+ tools: {},
271
+ prompts: {},
272
+ },
273
+ });
274
+ // Set up handlers for the SSE server (same as main server)
275
+ this.setupServerHandlers(sseServer);
276
+ await sseServer.connect(transport);
277
+ });
278
+ // Messages endpoint for SSE POST messages
279
+ this._httpServer.post("/messages", async (req, res) => {
280
+ const sessionId = req.query.sessionId;
281
+ const transport = this._sseTransports.get(sessionId);
282
+ if (!transport) {
283
+ res.status(404).json({ error: "Session not found" });
284
+ return;
285
+ }
286
+ await transport.handlePostMessage(req, res, req.body);
287
+ });
288
+ // List available tools endpoint (for inter-server communication)
289
+ this._httpServer.get("/tools", (req, res) => {
249
290
  try {
250
291
  const tools = this.getTools();
251
292
  res.json({ tools });
252
293
  }
253
294
  catch (error) {
254
- res.status(500).json({ error: 'Failed to list tools' });
295
+ res.status(500).json({ error: "Failed to list tools" });
255
296
  }
256
297
  });
257
- // Tool execution endpoint
258
- this._httpServer.post('/tools/:toolName', async (req, res) => {
298
+ // Tool execution endpoint (for inter-server communication)
299
+ this._httpServer.post("/tools/:toolName", async (req, res) => {
259
300
  try {
260
301
  const { toolName } = req.params;
261
302
  const { arguments: args = {} } = req.body;
262
303
  // Validate that the tool exists
263
304
  const tools = this.getTools();
264
- const tool = tools.find(t => t.name === toolName);
305
+ const tool = tools.find((t) => t.name === toolName);
265
306
  if (!tool) {
266
- return res.status(404).json({ error: `Tool '${toolName}' not found` });
307
+ res.status(404).json({ error: `Tool '${toolName}' not found` });
308
+ return;
267
309
  }
268
310
  // Execute the tool
269
311
  const request = {
312
+ method: "tools/call",
270
313
  params: {
271
314
  name: toolName,
272
- arguments: args
273
- }
315
+ arguments: args,
316
+ },
274
317
  };
275
318
  const result = await this.handleToolCall(request);
276
319
  res.json(result);
@@ -282,9 +325,86 @@ export class BaseAccessServer {
282
325
  });
283
326
  // Start HTTP server
284
327
  return new Promise((resolve, reject) => {
285
- this._httpServer.listen(this._httpPort, '0.0.0.0', () => {
328
+ this._httpServer.listen(this._httpPort, "0.0.0.0", () => {
286
329
  resolve();
287
- }).on('error', reject);
330
+ }).on("error", reject);
331
+ });
332
+ }
333
+ /**
334
+ * Set up MCP handlers on a server instance
335
+ */
336
+ setupServerHandlers(server) {
337
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
338
+ try {
339
+ return { tools: this.getTools() };
340
+ }
341
+ catch (error) {
342
+ return { tools: [] };
343
+ }
344
+ });
345
+ server.setRequestHandler(ListResourcesRequestSchema, async () => {
346
+ try {
347
+ return { resources: this.getResources() };
348
+ }
349
+ catch (error) {
350
+ return { resources: [] };
351
+ }
352
+ });
353
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
354
+ try {
355
+ return await this.handleToolCall(request);
356
+ }
357
+ catch (error) {
358
+ const errorMessage = error instanceof Error ? error.message : String(error);
359
+ this.logger.error("Error handling tool call", { error: errorMessage });
360
+ return {
361
+ content: [
362
+ {
363
+ type: "text",
364
+ text: JSON.stringify({
365
+ error: errorMessage,
366
+ }),
367
+ },
368
+ ],
369
+ isError: true,
370
+ };
371
+ }
372
+ });
373
+ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
374
+ try {
375
+ return await this.handleResourceRead(request);
376
+ }
377
+ catch (error) {
378
+ const errorMessage = error instanceof Error ? error.message : String(error);
379
+ this.logger.error("Error reading resource", { error: errorMessage });
380
+ return {
381
+ contents: [
382
+ {
383
+ uri: request.params.uri,
384
+ mimeType: "text/plain",
385
+ text: `Error: ${errorMessage}`,
386
+ },
387
+ ],
388
+ };
389
+ }
390
+ });
391
+ server.setRequestHandler(ListPromptsRequestSchema, async () => {
392
+ try {
393
+ return { prompts: this.getPrompts() };
394
+ }
395
+ catch (error) {
396
+ return { prompts: [] };
397
+ }
398
+ });
399
+ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
400
+ try {
401
+ return await this.handleGetPrompt(request);
402
+ }
403
+ catch (error) {
404
+ const errorMessage = error instanceof Error ? error.message : String(error);
405
+ this.logger.error("Error getting prompt", { error: errorMessage });
406
+ throw error;
407
+ }
288
408
  });
289
409
  }
290
410
  /**
@@ -296,10 +416,10 @@ export class BaseAccessServer {
296
416
  throw new Error(`Service '${serviceName}' not found. Check ACCESS_MCP_SERVICES environment variable.`);
297
417
  }
298
418
  const response = await axios.post(`${serviceUrl}/tools/${toolName}`, {
299
- arguments: args
419
+ arguments: args,
300
420
  }, {
301
421
  timeout: 30000,
302
- validateStatus: () => true
422
+ validateStatus: () => true,
303
423
  });
304
424
  if (response.status !== 200) {
305
425
  throw new Error(`Remote server call failed: ${response.status} ${response.data?.error || response.statusText}`);
@@ -315,8 +435,8 @@ export class BaseAccessServer {
315
435
  if (!services)
316
436
  return null;
317
437
  const serviceMap = {};
318
- services.split(',').forEach(service => {
319
- const [name, url] = service.split('=');
438
+ services.split(",").forEach((service) => {
439
+ const [name, url] = service.split("=");
320
440
  if (name && url) {
321
441
  serviceMap[name.trim()] = url.trim();
322
442
  }
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Authentication provider for Drupal JSON:API using cookie-based auth.
3
+ *
4
+ * This is a temporary implementation for development/testing.
5
+ * Production should use Key Auth with the access_mcp_author module.
6
+ *
7
+ * @see ../../../access-qa-planning/06-mcp-authentication.md
8
+ */
9
+ export declare class DrupalAuthProvider {
10
+ private baseUrl;
11
+ private username;
12
+ private password;
13
+ private sessionCookie?;
14
+ private csrfToken?;
15
+ private logoutToken?;
16
+ private userUuid?;
17
+ private httpClient;
18
+ private isAuthenticated;
19
+ constructor(baseUrl: string, username: string, password: string);
20
+ /**
21
+ * Ensure we have a valid session, logging in if necessary
22
+ */
23
+ ensureAuthenticated(): Promise<void>;
24
+ /**
25
+ * Login to Drupal and store session cookie + CSRF token
26
+ */
27
+ login(): Promise<void>;
28
+ /**
29
+ * Get headers required for authenticated JSON:API requests
30
+ */
31
+ getAuthHeaders(): Record<string, string>;
32
+ /**
33
+ * Get the authenticated user's UUID
34
+ */
35
+ getUserUuid(): string | undefined;
36
+ /**
37
+ * Invalidate the current session
38
+ */
39
+ invalidate(): void;
40
+ /**
41
+ * Make an authenticated GET request to JSON:API
42
+ */
43
+ get(path: string): Promise<any>;
44
+ /**
45
+ * Make an authenticated POST request to JSON:API
46
+ */
47
+ post(path: string, data: any): Promise<any>;
48
+ /**
49
+ * Make an authenticated PATCH request to JSON:API
50
+ */
51
+ patch(path: string, data: any): Promise<any>;
52
+ /**
53
+ * Make an authenticated DELETE request to JSON:API
54
+ */
55
+ delete(path: string): Promise<any>;
56
+ /**
57
+ * Handle JSON:API response, throwing on errors
58
+ */
59
+ private handleResponse;
60
+ }
@@ -0,0 +1,188 @@
1
+ import axios from "axios";
2
+ /**
3
+ * Authentication provider for Drupal JSON:API using cookie-based auth.
4
+ *
5
+ * This is a temporary implementation for development/testing.
6
+ * Production should use Key Auth with the access_mcp_author module.
7
+ *
8
+ * @see ../../../access-qa-planning/06-mcp-authentication.md
9
+ */
10
+ export class DrupalAuthProvider {
11
+ baseUrl;
12
+ username;
13
+ password;
14
+ sessionCookie;
15
+ csrfToken;
16
+ logoutToken;
17
+ userUuid;
18
+ httpClient;
19
+ isAuthenticated = false;
20
+ constructor(baseUrl, username, password) {
21
+ this.baseUrl = baseUrl;
22
+ this.username = username;
23
+ this.password = password;
24
+ this.httpClient = axios.create({
25
+ baseURL: this.baseUrl,
26
+ timeout: 30000,
27
+ validateStatus: () => true,
28
+ });
29
+ }
30
+ /**
31
+ * Ensure we have a valid session, logging in if necessary
32
+ */
33
+ async ensureAuthenticated() {
34
+ if (!this.isAuthenticated) {
35
+ await this.login();
36
+ }
37
+ }
38
+ /**
39
+ * Login to Drupal and store session cookie + CSRF token
40
+ */
41
+ async login() {
42
+ const response = await this.httpClient.post("/user/login?_format=json", {
43
+ name: this.username,
44
+ pass: this.password,
45
+ }, {
46
+ headers: {
47
+ "Content-Type": "application/json",
48
+ },
49
+ });
50
+ if (response.status !== 200) {
51
+ throw new Error(`Drupal login failed: ${response.status} ${response.statusText}`);
52
+ }
53
+ // Extract session cookie from Set-Cookie header
54
+ const setCookie = response.headers["set-cookie"];
55
+ if (setCookie && setCookie.length > 0) {
56
+ // Parse the session cookie (format: SESS...=value; path=/; ...)
57
+ const cookieParts = setCookie[0].split(";")[0];
58
+ this.sessionCookie = cookieParts;
59
+ }
60
+ // Store CSRF token and logout token from response
61
+ this.csrfToken = response.data.csrf_token;
62
+ this.logoutToken = response.data.logout_token;
63
+ this.userUuid = response.data.current_user?.uuid;
64
+ if (!this.sessionCookie || !this.csrfToken) {
65
+ throw new Error("Login succeeded but missing session cookie or CSRF token");
66
+ }
67
+ this.isAuthenticated = true;
68
+ }
69
+ /**
70
+ * Get headers required for authenticated JSON:API requests
71
+ */
72
+ getAuthHeaders() {
73
+ if (!this.isAuthenticated || !this.sessionCookie || !this.csrfToken) {
74
+ throw new Error("Not authenticated. Call ensureAuthenticated() first.");
75
+ }
76
+ return {
77
+ Cookie: this.sessionCookie,
78
+ "X-CSRF-Token": this.csrfToken,
79
+ "Content-Type": "application/vnd.api+json",
80
+ Accept: "application/vnd.api+json",
81
+ };
82
+ }
83
+ /**
84
+ * Get the authenticated user's UUID
85
+ */
86
+ getUserUuid() {
87
+ return this.userUuid;
88
+ }
89
+ /**
90
+ * Invalidate the current session
91
+ */
92
+ invalidate() {
93
+ this.sessionCookie = undefined;
94
+ this.csrfToken = undefined;
95
+ this.logoutToken = undefined;
96
+ this.userUuid = undefined;
97
+ this.isAuthenticated = false;
98
+ }
99
+ /**
100
+ * Make an authenticated GET request to JSON:API
101
+ */
102
+ async get(path) {
103
+ await this.ensureAuthenticated();
104
+ const response = await this.httpClient.get(path, {
105
+ headers: this.getAuthHeaders(),
106
+ });
107
+ if (response.status === 401 || response.status === 403) {
108
+ // Session may have expired, try re-authenticating
109
+ this.invalidate();
110
+ await this.ensureAuthenticated();
111
+ const retryResponse = await this.httpClient.get(path, {
112
+ headers: this.getAuthHeaders(),
113
+ });
114
+ return this.handleResponse(retryResponse);
115
+ }
116
+ return this.handleResponse(response);
117
+ }
118
+ /**
119
+ * Make an authenticated POST request to JSON:API
120
+ */
121
+ async post(path, data) {
122
+ await this.ensureAuthenticated();
123
+ const response = await this.httpClient.post(path, data, {
124
+ headers: this.getAuthHeaders(),
125
+ });
126
+ if (response.status === 401 || response.status === 403) {
127
+ this.invalidate();
128
+ await this.ensureAuthenticated();
129
+ const retryResponse = await this.httpClient.post(path, data, {
130
+ headers: this.getAuthHeaders(),
131
+ });
132
+ return this.handleResponse(retryResponse);
133
+ }
134
+ return this.handleResponse(response);
135
+ }
136
+ /**
137
+ * Make an authenticated PATCH request to JSON:API
138
+ */
139
+ async patch(path, data) {
140
+ await this.ensureAuthenticated();
141
+ const response = await this.httpClient.patch(path, data, {
142
+ headers: this.getAuthHeaders(),
143
+ });
144
+ if (response.status === 401 || response.status === 403) {
145
+ this.invalidate();
146
+ await this.ensureAuthenticated();
147
+ const retryResponse = await this.httpClient.patch(path, data, {
148
+ headers: this.getAuthHeaders(),
149
+ });
150
+ return this.handleResponse(retryResponse);
151
+ }
152
+ return this.handleResponse(response);
153
+ }
154
+ /**
155
+ * Make an authenticated DELETE request to JSON:API
156
+ */
157
+ async delete(path) {
158
+ await this.ensureAuthenticated();
159
+ const response = await this.httpClient.delete(path, {
160
+ headers: this.getAuthHeaders(),
161
+ });
162
+ if (response.status === 401 || response.status === 403) {
163
+ this.invalidate();
164
+ await this.ensureAuthenticated();
165
+ const retryResponse = await this.httpClient.delete(path, {
166
+ headers: this.getAuthHeaders(),
167
+ });
168
+ return this.handleResponse(retryResponse);
169
+ }
170
+ return this.handleResponse(response);
171
+ }
172
+ /**
173
+ * Handle JSON:API response, throwing on errors
174
+ */
175
+ handleResponse(response) {
176
+ if (response.status >= 200 && response.status < 300) {
177
+ return response.data;
178
+ }
179
+ // JSON:API error format
180
+ if (response.data?.errors) {
181
+ const errors = response.data.errors
182
+ .map((e) => e.detail || e.title || "Unknown error")
183
+ .join("; ");
184
+ throw new Error(`Drupal API error (${response.status}): ${errors}`);
185
+ }
186
+ throw new Error(`Drupal API error: ${response.status} ${response.statusText}`);
187
+ }
188
+ }
package/dist/index.d.ts CHANGED
@@ -2,3 +2,5 @@ export * from "./base-server.js";
2
2
  export * from "./types.js";
3
3
  export * from "./utils.js";
4
4
  export * from "./taxonomies.js";
5
+ export * from "./drupal-auth.js";
6
+ export * from "./logger.js";
package/dist/index.js CHANGED
@@ -2,3 +2,5 @@ export * from "./base-server.js";
2
2
  export * from "./types.js";
3
3
  export * from "./utils.js";
4
4
  export * from "./taxonomies.js";
5
+ export * from "./drupal-auth.js";
6
+ export * from "./logger.js";
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Structured logger for ACCESS-CI MCP servers.
3
+ *
4
+ * This logger writes to stderr to avoid interfering with MCP's JSON-RPC
5
+ * communication on stdout. It supports log levels that can be controlled
6
+ * via the LOG_LEVEL environment variable.
7
+ *
8
+ * Log levels (in order of severity):
9
+ * - error: Always shown, for critical errors
10
+ * - warn: Warnings that don't prevent operation
11
+ * - info: Important informational messages
12
+ * - debug: Detailed debugging information (disabled by default)
13
+ *
14
+ * Usage:
15
+ * import { createLogger } from "@access-mcp/shared";
16
+ * const logger = createLogger("my-server");
17
+ * logger.info("Server started");
18
+ * logger.error("Failed to connect", { url: "http://..." });
19
+ */
20
+ export type LogLevel = "error" | "warn" | "info" | "debug";
21
+ interface LogContext {
22
+ [key: string]: unknown;
23
+ }
24
+ export interface Logger {
25
+ error: (message: string, context?: LogContext) => void;
26
+ warn: (message: string, context?: LogContext) => void;
27
+ info: (message: string, context?: LogContext) => void;
28
+ debug: (message: string, context?: LogContext) => void;
29
+ }
30
+ /**
31
+ * Create a logger instance for a specific server.
32
+ *
33
+ * @param serverName - The name of the server (e.g., "access-mcp-events")
34
+ * @returns A logger instance with error, warn, info, and debug methods
35
+ */
36
+ export declare function createLogger(serverName: string): Logger;
37
+ /**
38
+ * A no-op logger that silently discards all log messages.
39
+ * Useful for testing or when logging should be completely disabled.
40
+ */
41
+ export declare const silentLogger: Logger;
42
+ export {};
package/dist/logger.js ADDED
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Structured logger for ACCESS-CI MCP servers.
3
+ *
4
+ * This logger writes to stderr to avoid interfering with MCP's JSON-RPC
5
+ * communication on stdout. It supports log levels that can be controlled
6
+ * via the LOG_LEVEL environment variable.
7
+ *
8
+ * Log levels (in order of severity):
9
+ * - error: Always shown, for critical errors
10
+ * - warn: Warnings that don't prevent operation
11
+ * - info: Important informational messages
12
+ * - debug: Detailed debugging information (disabled by default)
13
+ *
14
+ * Usage:
15
+ * import { createLogger } from "@access-mcp/shared";
16
+ * const logger = createLogger("my-server");
17
+ * logger.info("Server started");
18
+ * logger.error("Failed to connect", { url: "http://..." });
19
+ */
20
+ const LOG_LEVELS = {
21
+ error: 0,
22
+ warn: 1,
23
+ info: 2,
24
+ debug: 3,
25
+ };
26
+ function getLogLevel() {
27
+ const level = process.env.LOG_LEVEL?.toLowerCase();
28
+ if (level && level in LOG_LEVELS) {
29
+ return level;
30
+ }
31
+ // Default to 'warn' for production (errors and warnings only)
32
+ return "warn";
33
+ }
34
+ function shouldLog(level) {
35
+ const currentLevel = getLogLevel();
36
+ return LOG_LEVELS[level] <= LOG_LEVELS[currentLevel];
37
+ }
38
+ function formatMessage(serverName, level, message, context) {
39
+ const timestamp = new Date().toISOString();
40
+ const contextStr = context ? ` ${JSON.stringify(context)}` : "";
41
+ return `[${timestamp}] [${serverName}] [${level.toUpperCase()}] ${message}${contextStr}`;
42
+ }
43
+ /**
44
+ * Create a logger instance for a specific server.
45
+ *
46
+ * @param serverName - The name of the server (e.g., "access-mcp-events")
47
+ * @returns A logger instance with error, warn, info, and debug methods
48
+ */
49
+ export function createLogger(serverName) {
50
+ return {
51
+ error: (message, context) => {
52
+ if (shouldLog("error")) {
53
+ console.error(formatMessage(serverName, "error", message, context));
54
+ }
55
+ },
56
+ warn: (message, context) => {
57
+ if (shouldLog("warn")) {
58
+ console.error(formatMessage(serverName, "warn", message, context));
59
+ }
60
+ },
61
+ info: (message, context) => {
62
+ if (shouldLog("info")) {
63
+ console.error(formatMessage(serverName, "info", message, context));
64
+ }
65
+ },
66
+ debug: (message, context) => {
67
+ if (shouldLog("debug")) {
68
+ console.error(formatMessage(serverName, "debug", message, context));
69
+ }
70
+ },
71
+ };
72
+ }
73
+ /**
74
+ * A no-op logger that silently discards all log messages.
75
+ * Useful for testing or when logging should be completely disabled.
76
+ */
77
+ export const silentLogger = {
78
+ error: () => { },
79
+ warn: () => { },
80
+ info: () => { },
81
+ debug: () => { },
82
+ };