diff --git a/lib/mcp_dart.dart b/lib/mcp_dart.dart index b366e58..62d0672 100644 --- a/lib/mcp_dart.dart +++ b/lib/mcp_dart.dart @@ -11,6 +11,7 @@ library; // Common exports for all platforms export 'src/types.dart'; // Exports shared types used across the MCP protocol. export 'src/shared/uuid.dart'; // Exports UUID generation utilities. +export 'src/shared/logging.dart'; // Exports logging for customization // Platform-specific exports export 'src/exports.dart' // Stub export for other platforms diff --git a/lib/src/client/client.dart b/lib/src/client/client.dart index b383df4..9ff15ff 100644 --- a/lib/src/client/client.dart +++ b/lib/src/client/client.dart @@ -1,8 +1,11 @@ import 'dart:async'; +import 'package:mcp_dart/src/shared/logging.dart'; import 'package:mcp_dart/src/shared/protocol.dart'; import 'package:mcp_dart/src/shared/transport.dart'; import 'package:mcp_dart/src/types.dart'; +final _logger = Logger("mcp_dart.client"); + /// Options for configuring the MCP [Client]. class ClientOptions extends ProtocolOptions { /// Capabilities to advertise as being supported by this client. @@ -88,11 +91,11 @@ class Client extends Protocol { const initializedNotification = JsonRpcInitializedNotification(); await notification(initializedNotification); - print( + _logger.debug( "MCP Client Initialized. Server: ${result.serverInfo.name} ${result.serverInfo.version}, Protocol: ${result.protocolVersion}", ); } catch (error) { - print("MCP Client Initialization Failed: $error"); + _logger.error("MCP Client Initialization Failed: $error"); await close(); rethrow; } @@ -150,8 +153,8 @@ class Client extends Protocol { requiredCapability = 'prompts or resources'; break; default: - print( - "Warning: assertCapabilityForMethod called for potentially custom client request: $method", + _logger.warn( + "assertCapabilityForMethod called for potentially custom client request: $method", ); supported = true; } @@ -175,8 +178,8 @@ class Client extends Protocol { } break; default: - print( - "Warning: assertNotificationCapability called for potentially custom client notification: $method", + _logger.warn( + "assertNotificationCapability called for potentially custom client notification: $method", ); } } @@ -199,8 +202,8 @@ class Client extends Protocol { } break; default: - print( - "Info: Setting request handler for potentially custom method '$method'. Ensure client capabilities match.", + _logger.info( + "Setting request handler for potentially custom method '$method'. Ensure client capabilities match.", ); } } diff --git a/lib/src/client/stdio.dart b/lib/src/client/stdio.dart index 9a8a276..6af7f3a 100644 --- a/lib/src/client/stdio.dart +++ b/lib/src/client/stdio.dart @@ -9,6 +9,9 @@ import 'package:mcp_dart/src/shared/stdio.dart'; // Adjust import path as needed import 'package:mcp_dart/src/shared/transport.dart'; // Adjust import path as needed // Assume types are defined in types.dart import 'package:mcp_dart/src/types.dart'; // Adjust import path as needed +import 'package:mcp_dart/src/shared/logging.dart'; + +final _logger = Logger("mcp_dart.client.stdio"); /// Configuration parameters for launching the stdio server process. class StdioServerParameters { @@ -127,7 +130,9 @@ class StdioClientTransport implements Transport { mode: mode, // Handles stdin/stdout/stderr piping/inheritance ); - print("StdioClientTransport: Process started (PID: ${_process?.pid})"); + _logger.debug( + "StdioClientTransport: Process started (PID: ${_process?.pid})", + ); // --- Setup stream listeners --- @@ -144,7 +149,7 @@ class StdioClientTransport implements Transport { // Expose stderr via getter, let user handle it. // Optionally add logging here: _stderrSubscription = _process!.stderr.listen( - (data) => print( + (data) => _logger.debug( "Server stderr: ${utf8.decode(data, allowMalformed: true)}", ), onError: _onStreamError, // Report stderr stream errors too @@ -158,7 +163,7 @@ class StdioClientTransport implements Transport { return Future.value(); } catch (error, stackTrace) { // Handle errors during Process.start() - print("StdioClientTransport: Failed to start process: $error"); + _logger.error("StdioClientTransport: Failed to start process: $error"); _started = false; // Reset state final startError = StateError( "Failed to start server process: $error\n$stackTrace", @@ -166,7 +171,7 @@ class StdioClientTransport implements Transport { try { onerror?.call(startError); } catch (e) { - print("Error in onerror handler: $e"); + _logger.warn("Error in onerror handler: $e"); } throw startError; // Rethrow to signal failure } @@ -193,7 +198,7 @@ class StdioClientTransport implements Transport { /// Internal handler for when the process's stdout stream closes. void _onStdoutDone() { - print("StdioClientTransport: Process stdout closed."); + _logger.debug("StdioClientTransport: Process stdout closed."); // Consider if this should trigger close() - depends if server exiting is expected. // Maybe only close if the process has also exited? // close(); // Optionally close transport when stdout ends @@ -207,7 +212,7 @@ class StdioClientTransport implements Transport { try { onerror?.call(streamError); } catch (e) { - print("Error in onerror handler: $e"); + _logger.warn("Error in onerror handler: $e"); } // Consider if stream errors should trigger close() // close(); @@ -222,7 +227,7 @@ class StdioClientTransport implements Transport { try { onmessage?.call(message); } catch (e) { - print("Error in onmessage handler: $e"); + _logger.warn("Error in onmessage handler: $e"); onerror?.call(StateError("Error in onmessage handler: $e")); } } catch (error) { @@ -232,9 +237,9 @@ class StdioClientTransport implements Transport { try { onerror?.call(parseError); } catch (e) { - print("Error in onerror handler: $e"); + _logger.warn("Error in onerror handler: $e"); } - print( + _logger.error( "StdioClientTransport: Error processing read buffer: $parseError. Skipping data.", ); // Consider clearing buffer or attempting recovery depending on error type. @@ -247,7 +252,7 @@ class StdioClientTransport implements Transport { /// Internal handler for when the process exits. void _onProcessExit(int exitCode) { - print("StdioClientTransport: Process exited with code $exitCode."); + _logger.debug("StdioClientTransport: Process exited with code $exitCode."); if (!_exitCompleter.isCompleted) { _exitCompleter.complete(); // Signal exit if not already closing } @@ -256,14 +261,16 @@ class StdioClientTransport implements Transport { /// Internal handler for errors retrieving the process exit code. void _onProcessExitError(dynamic error, StackTrace stackTrace) { - print("StdioClientTransport: Error waiting for process exit: $error"); + _logger.debug( + "StdioClientTransport: Error waiting for process exit: $error", + ); final Error exitError = (error is Error) ? error : StateError("Process exit error: $error\n$stackTrace"); try { onerror?.call(exitError); } catch (e) { - print("Error in onerror handler: $e"); + _logger.warn("Error in onerror handler: $e"); } if (!_exitCompleter.isCompleted) { _exitCompleter.completeError(exitError); @@ -279,7 +286,7 @@ class StdioClientTransport implements Transport { Future close() async { if (!_started) return; // Already closed or never started - print("StdioClientTransport: Closing transport..."); + _logger.debug("StdioClientTransport: Closing transport..."); // Mark as closing immediately to prevent further sends/starts _started = false; @@ -297,13 +304,13 @@ class StdioClientTransport implements Transport { _process = null; // Clear reference if (processToKill != null) { - print( + _logger.debug( "StdioClientTransport: Terminating process (PID: ${processToKill.pid})...", ); // Attempt graceful termination first bool killed = processToKill.kill(io.ProcessSignal.sigterm); if (!killed) { - print( + _logger.debug( "StdioClientTransport: Failed to send SIGTERM or process already exited.", ); // If SIGTERM fails or wasn't sent (e.g., process already dead), @@ -312,15 +319,15 @@ class StdioClientTransport implements Transport { // Give a short grace period for SIGTERM before SIGKILL try { await _exitCompleter.future.timeout(const Duration(seconds: 2)); - print("StdioClientTransport: Process terminated gracefully."); + _logger.debug("StdioClientTransport: Process terminated gracefully."); } on TimeoutException { - print( + _logger.warn( "StdioClientTransport: Process did not exit after SIGTERM, sending SIGKILL.", ); processToKill.kill(io.ProcessSignal.sigkill); // Force kill } catch (e) { // Error waiting for exit after SIGTERM (might have exited quickly) - print( + _logger.error( "StdioClientTransport: Error waiting for process exit after SIGTERM: $e", ); } @@ -336,9 +343,9 @@ class StdioClientTransport implements Transport { try { onclose?.call(); } catch (e) { - print("Error in onclose handler: $e"); + _logger.warn("Error in onclose handler: $e"); } - print("StdioClientTransport: Transport closed."); + _logger.debug("StdioClientTransport: Transport closed."); } /// Sends a [JsonRpcMessage] to the server process via its stdin. @@ -361,14 +368,16 @@ class StdioClientTransport implements Transport { // Flushing stdin might be necessary depending on the server's reading behavior. await currentProcess.stdin.flush(); } catch (error, stackTrace) { - print("StdioClientTransport: Error writing to process stdin: $error"); + _logger.warn( + "StdioClientTransport: Error writing to process stdin: $error", + ); final Error sendError = (error is Error) ? error : StateError("Process stdin write error: $error\n$stackTrace"); try { onerror?.call(sendError); } catch (e) { - print("Error in onerror handler: $e"); + _logger.warn("Error in onerror handler: $e"); } // Consider closing the transport on stdin write failure close(); diff --git a/lib/src/server/mcp.dart b/lib/src/server/mcp.dart index 1e2e47e..2e3bb47 100644 --- a/lib/src/server/mcp.dart +++ b/lib/src/server/mcp.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:mcp_dart/src/shared/logging.dart'; import 'package:mcp_dart/src/shared/protocol.dart'; import 'package:mcp_dart/src/shared/transport.dart'; import 'package:mcp_dart/src/shared/uri_template.dart'; @@ -7,6 +8,8 @@ import 'package:mcp_dart/src/types.dart'; import 'server.dart'; +final _logger = Logger("mcp_dart.server.mcp"); + typedef CompleteCallback = Future> Function(String value); class CompletableDef { @@ -244,7 +247,7 @@ class McpServer { registeredTool.callback(args: toolArgs, extra: extra), ); } catch (error) { - print("Error executing tool '$toolName': $error"); + _logger.warn("Error executing tool '$toolName': $error"); return CallToolResult.fromContent( content: [TextContent(text: error.toString())], isError: true, @@ -295,7 +298,7 @@ class McpServer { try { return _createCompletionResult(await completer(argInfo.value)); } catch (e) { - print( + _logger.warn( "Error during prompt argument completion for '${ref.name}.${argInfo.name}': $e", ); throw McpError(ErrorCode.internalError.value, "Completion failed"); @@ -319,7 +322,7 @@ class McpServer { try { return _createCompletionResult(await completer(argInfo.value)); } catch (e) { - print( + _logger.warn( "Error during resource template completion for '${ref.uri}' variable '${argInfo.name}': $e", ); throw McpError(ErrorCode.internalError.value, "Completion failed"); @@ -359,7 +362,7 @@ class McpServer { ) .toList(); } catch (e) { - print("Error listing resources for template: $e"); + _logger.warn("Error listing resources for template: $e"); return []; } }); @@ -475,7 +478,7 @@ class McpServer { throw StateError("No callback found"); } } catch (error) { - print("Error executing prompt '$name': $error"); + _logger.warn("Error executing prompt '$name': $error"); if (error is McpError) rethrow; throw McpError( ErrorCode.internalError.value, diff --git a/lib/src/server/server.dart b/lib/src/server/server.dart index c695443..095a898 100644 --- a/lib/src/server/server.dart +++ b/lib/src/server/server.dart @@ -1,8 +1,11 @@ import 'dart:async'; +import 'package:mcp_dart/src/shared/logging.dart'; import 'package:mcp_dart/src/shared/protocol.dart'; import 'package:mcp_dart/src/types.dart'; +final _logger = Logger("mcp_dart.server"); + /// Options for configuring the MCP [Server]. class ServerOptions extends ProtocolOptions { /// Capabilities to advertise as being supported by this server. @@ -129,8 +132,8 @@ class Server extends Protocol { break; default: - print( - "Warning: assertCapabilityForMethod called for unknown server-sent request method: $method", + _logger.warn( + "assertCapabilityForMethod called for unknown server-sent request method: $method", ); } } @@ -183,8 +186,8 @@ class Server extends Protocol { break; default: - print( - "Warning: assertNotificationCapability called for unknown server-sent notification method: $method", + _logger.warn( + "assertNotificationCapability called for unknown server-sent notification method: $method", ); } } @@ -243,8 +246,8 @@ class Server extends Protocol { break; default: - print( - "Info: Setting request handler for potentially custom method '$method'. Ensure server capabilities match.", + _logger.info( + "Setting request handler for potentially custom method '$method'. Ensure server capabilities match.", ); } } diff --git a/lib/src/server/sse.dart b/lib/src/server/sse.dart index d5cf9f4..c0eb274 100644 --- a/lib/src/server/sse.dart +++ b/lib/src/server/sse.dart @@ -3,10 +3,13 @@ import 'dart:convert'; import 'dart:io'; import 'dart:typed_data'; +import 'package:mcp_dart/src/shared/logging.dart'; import 'package:mcp_dart/src/shared/transport.dart'; import 'package:mcp_dart/src/shared/uuid.dart'; import 'package:mcp_dart/src/types.dart'; +final _logger = Logger("mcp_dart.server.sse"); + /// Maximum size for incoming POST message bodies. const int _maximumMessageSize = 4 * 1024 * 1024; // 4MB in bytes @@ -93,18 +96,18 @@ class SseServerTransport implements Transport { socket.listen( (_) {}, onDone: () { - print('Client disconnected'); + _logger.debug('Client disconnected'); close(); }, onError: (error) { - print('Socket error: $error'); + _logger.warn('Socket error: $error'); onerror?.call( error is Error ? error : StateError("Socket error: $error"), ); }, ); } catch (error) { - print('Error starting SSE transport: $error'); + _logger.error('Error starting SSE transport: $error'); } } @@ -215,14 +218,14 @@ class SseServerTransport implements Transport { try { parsedMessage = JsonRpcMessage.fromJson(messageJson); } catch (error) { - print("Failed to parse JsonRpcMessage from JSON: $messageJson"); + _logger.warn("Failed to parse JsonRpcMessage from JSON: $messageJson"); rethrow; } try { onmessage?.call(parsedMessage); } catch (e) { - print("Error within onmessage handler: $e"); + _logger.warn("Error within onmessage handler: $e"); onerror?.call(StateError("Error in onmessage handler: $e")); } } @@ -273,7 +276,7 @@ class SseServerTransport implements Transport { try { _sink?.close(); } catch (e) { - print("Error closing SSE response: $e"); + _logger.warn("Error closing SSE response: $e"); } _sink = null; @@ -281,7 +284,7 @@ class SseServerTransport implements Transport { try { onclose?.call(); } catch (e) { - print("Error within onclose handler: $e"); + _logger.warn("Error within onclose handler: $e"); onerror?.call(StateError("Error in onclose handler: $e")); } } diff --git a/lib/src/server/sse_server_manager.dart b/lib/src/server/sse_server_manager.dart index 9cee17b..27b01aa 100644 --- a/lib/src/server/sse_server_manager.dart +++ b/lib/src/server/sse_server_manager.dart @@ -1,8 +1,12 @@ import 'dart:io'; +import 'package:mcp_dart/src/shared/logging.dart'; + import 'mcp.dart'; import 'sse.dart'; +final _logger = Logger("mcp_dart.server.sse.manager"); + /// Manages Server-Sent Events (SSE) connections and routes HTTP requests. class SseServerManager { /// Map to store active SSE transports, keyed by session ID. @@ -25,7 +29,7 @@ class SseServerManager { /// Routes incoming HTTP requests to appropriate handlers. Future handleRequest(HttpRequest request) async { - print("Received request: ${request.method} ${request.uri.path}"); + _logger.debug("Received request: ${request.method} ${request.uri.path}"); if (request.uri.path == ssePath) { if (request.method == 'GET') { @@ -46,7 +50,7 @@ class SseServerManager { /// Handles the initial GET request to establish an SSE connection. Future handleSseConnection(HttpRequest request) async { - print("Client connecting for SSE at /sse..."); + _logger.debug("Client connecting for SSE at /sse..."); SseServerTransport? transport; try { @@ -57,23 +61,23 @@ class SseServerManager { final sessionId = transport.sessionId; activeSseTransports[sessionId] = transport; - print("Stored new SSE transport for session: $sessionId"); + _logger.debug("Stored new SSE transport for session: $sessionId"); transport.onclose = () { - print( + _logger.debug( "SSE transport closed (Session: $sessionId). Removing from active list.", ); activeSseTransports.remove(sessionId); }; transport.onerror = (error) { - print("Error on SSE transport (Session: $sessionId): $error"); + _logger.warn("Error on SSE transport (Session: $sessionId): $error"); }; await mcpServer.connect(transport); - print("SSE transport connected, session ID: $sessionId"); + _logger.debug("SSE transport connected, session ID: $sessionId"); } catch (e) { - print("Error setting up SSE connection: $e"); + _logger.warn("Error setting up SSE connection: $e"); if (transport != null) { activeSseTransports.remove(transport.sessionId); } @@ -90,7 +94,7 @@ class SseServerManager { /// Handles POST requests containing client messages. Future _handlePostMessage(HttpRequest request) async { final sessionId = request.uri.queryParameters['sessionId']; - print("Received POST to $messagePath (Session ID: $sessionId)"); + _logger.debug("Received POST to $messagePath (Session ID: $sessionId)"); if (sessionId == null || sessionId.isEmpty) { request.response @@ -104,7 +108,7 @@ class SseServerManager { if (transportToUse != null) { await transportToUse.handlePostMessage(request); } else { - print("No active SSE transport found for session ID: $sessionId"); + _logger.debug("No active SSE transport found for session ID: $sessionId"); request.response ..statusCode = HttpStatus.notFound ..write("No active SSE session found for ID: $sessionId"); diff --git a/lib/src/server/stdio.dart b/lib/src/server/stdio.dart index 7de39b8..375cc13 100644 --- a/lib/src/server/stdio.dart +++ b/lib/src/server/stdio.dart @@ -2,10 +2,13 @@ import 'dart:async'; import 'dart:io' as io; import 'dart:typed_data'; +import 'package:mcp_dart/src/shared/logging.dart'; import 'package:mcp_dart/src/shared/stdio.dart'; import 'package:mcp_dart/src/shared/transport.dart'; import 'package:mcp_dart/src/types.dart'; +final _logger = Logger("mcp_dart.server.stdio"); + /// Server transport for stdio: communicates with a MCP client by reading /// from the current process's standard input ([io.stdin]) and writing to /// standard output ([io.stdout]). @@ -90,13 +93,13 @@ class StdioServerTransport implements Transport { try { onerror?.call(dartError); } catch (e) { - print("Error within onerror handler: $e"); + _logger.warn("Error within onerror handler: $e"); } } /// Internal callback for when the stdin stream is closed. void _onStdinDone() { - print("Stdin closed."); + _logger.debug("Stdin closed."); close(); } @@ -111,7 +114,7 @@ class StdioServerTransport implements Transport { try { onmessage?.call(message); } catch (e) { - print("Error within onmessage handler: $e"); + _logger.warn("Error within onmessage handler: $e"); onerror?.call(StateError("Error in onmessage handler: $e")); } } catch (error) { @@ -121,9 +124,9 @@ class StdioServerTransport implements Transport { try { onerror?.call(dartError); } catch (e) { - print("Error within onerror handler during parsing: $e"); + _logger.warn("Error within onerror handler during parsing: $e"); } - print( + _logger.warn( "StdioServerTransport: Error processing read buffer: $dartError. Attempting to continue.", ); } @@ -150,7 +153,7 @@ class StdioServerTransport implements Transport { try { onclose?.call(); } catch (e) { - print("Error within onclose handler: $e"); + _logger.warn("Error within onclose handler: $e"); } } @@ -163,8 +166,8 @@ class StdioServerTransport implements Transport { @override Future send(JsonRpcMessage message) { if (!_started) { - print( - "Warning: Attempted to send message on stopped StdioServerTransport.", + _logger.warn( + "Attempted to send message on stopped StdioServerTransport.", ); return Future.value(); } @@ -179,7 +182,7 @@ class StdioServerTransport implements Transport { try { onerror?.call(dartError); } catch (e) { - print("Error within onerror handler during send: $e"); + _logger.warn("Error within onerror handler during send: $e"); } return Future.error(dartError); } diff --git a/lib/src/shared/iostream.dart b/lib/src/shared/iostream.dart index 8159cc6..b63a20a 100644 --- a/lib/src/shared/iostream.dart +++ b/lib/src/shared/iostream.dart @@ -2,10 +2,13 @@ import 'dart:async'; import 'dart:convert'; import 'dart:typed_data'; +import 'package:mcp_dart/src/shared/logging.dart'; import 'package:mcp_dart/src/shared/stdio.dart'; import 'package:mcp_dart/src/shared/transport.dart'; import 'package:mcp_dart/src/types.dart'; +final _logger = Logger("mcp_dart.shared.iostream"); + /// Transport implementation that uses standard I/O for communication. class IOStreamTransport implements Transport { /// The input stream to read from. @@ -83,7 +86,7 @@ class IOStreamTransport implements Transport { try { onerror?.call(startError); } catch (e) { - print("Error in onerror handler: $e"); + _logger.warn("Error in onerror handler: $e"); } throw startError; // Rethrow to signal failure } @@ -98,7 +101,7 @@ class IOStreamTransport implements Transport { /// Internal handler for when the input stream closes void _onStreamDone() { - print("IOStreamTransport: Input stream closed."); + _logger.debug("IOStreamTransport: Input stream closed."); close(); // Close transport when input ends } @@ -110,7 +113,7 @@ class IOStreamTransport implements Transport { try { onerror?.call(streamError); } catch (e) { - print("Error in onerror handler: $e"); + _logger.warn("Error in onerror handler: $e"); } close(); } @@ -124,7 +127,7 @@ class IOStreamTransport implements Transport { try { onmessage?.call(message); } catch (e) { - print("Error in onmessage handler: $e"); + _logger.warn("Error in onmessage handler: $e"); onerror?.call(StateError("Error in onmessage handler: $e")); } } catch (error) { @@ -134,9 +137,9 @@ class IOStreamTransport implements Transport { try { onerror?.call(parseError); } catch (e) { - print("Error in onerror handler: $e"); + _logger.warn("Error in onerror handler: $e"); } - print( + _logger.warn( "IOStreamTransport: Error processing read buffer: $parseError. Skipping data.", ); break; // Stop processing buffer on error @@ -149,7 +152,7 @@ class IOStreamTransport implements Transport { Future close() async { if (_closed || !_started) return; // Already closed or never started - print("IOStreamTransport: Closing transport..."); + _logger.debug("IOStreamTransport: Closing transport..."); // Mark as closing immediately to prevent further sends/starts _started = false; @@ -165,9 +168,9 @@ class IOStreamTransport implements Transport { try { onclose?.call(); } catch (e) { - print("Error in onclose handler: $e"); + _logger.warn("Error in onclose handler: $e"); } - print("IOStreamTransport: Transport closed."); + _logger.debug("IOStreamTransport: Transport closed."); } /// Sends a message to the output stream. @@ -186,14 +189,16 @@ class IOStreamTransport implements Transport { sink.add(utf8.encode(jsonString)); // No need to flush as StreamSink should handle this } catch (error, stackTrace) { - print("IOStreamTransport: Error writing to output stream: $error"); + _logger.warn( + "IOStreamTransport: Error writing to output stream: $error", + ); final Error sendError = (error is Error) ? error : StateError("Output stream write error: $error\n$stackTrace"); try { onerror?.call(sendError); } catch (e) { - print("Error in onerror handler: $e"); + _logger.warn("Error in onerror handler: $e"); } close(); throw sendError; // Rethrow after cleanup attempt diff --git a/lib/src/shared/logging.dart b/lib/src/shared/logging.dart new file mode 100644 index 0000000..b84bfb1 --- /dev/null +++ b/lib/src/shared/logging.dart @@ -0,0 +1,35 @@ +enum LogLevel { debug, info, warn, error } + +typedef LogHandler = void Function( + String loggerName, + LogLevel level, + String message, +); + +final class Logger { + static LogHandler _handler = _defaultLogHandler; + final String name; + + Logger(this.name); + + static void setHandler(LogHandler handler) { + _handler = handler; + } + + static void _defaultLogHandler( + String loggerName, + LogLevel level, + String message, + ) { + print("[${level.name.toUpperCase()}][$loggerName] $message"); + } + + void log(LogLevel level, String message) { + _handler(name, level, message); + } + + void debug(String message) => log(LogLevel.debug, message); + void info(String message) => log(LogLevel.info, message); + void warn(String message) => log(LogLevel.warn, message); + void error(String message) => log(LogLevel.error, message); +} diff --git a/lib/src/shared/protocol.dart b/lib/src/shared/protocol.dart index 76edfaf..04cca83 100644 --- a/lib/src/shared/protocol.dart +++ b/lib/src/shared/protocol.dart @@ -1,9 +1,12 @@ import 'dart:async'; +import 'package:mcp_dart/src/shared/logging.dart'; import 'package:mcp_dart/src/types.dart'; import 'transport.dart'; +final _logger = Logger("mcp_dart.shared.protocol"); + /// Callback for progress notifications. typedef ProgressCallback = void Function(Progress progress); @@ -366,8 +369,8 @@ abstract class Protocol { try { onerror?.call(error); } catch (e) { - print("Error occurred in user onerror handler: $e"); - print("Original error was: $error"); + _logger.warn("Error occurred in user onerror handler: $e"); + _logger.warn("Original error was: $error"); } } diff --git a/lib/src/shared/stdio.dart b/lib/src/shared/stdio.dart index 4f7827f..8997030 100644 --- a/lib/src/shared/stdio.dart +++ b/lib/src/shared/stdio.dart @@ -1,8 +1,11 @@ import 'dart:convert'; import 'dart:typed_data'; +import 'package:mcp_dart/src/shared/logging.dart'; import 'package:mcp_dart/src/types.dart'; +final _logger = Logger("mcp_dart.shared.stdio"); + /// Buffers a continuous stdio stream (like stdin) and parses discrete, /// newline-terminated JSON-RPC messages. class ReadBuffer { @@ -41,7 +44,7 @@ class ReadBuffer { try { line = utf8.decode(lineBytes); } catch (e) { - print("Error decoding UTF-8 line: $e"); + _logger.warn("Error decoding UTF-8 line: $e"); _updateBufferAfterRead(newlineIndex); return null; } @@ -78,10 +81,10 @@ JsonRpcMessage deserializeMessage(String line) { final jsonMap = jsonDecode(line) as Map; return JsonRpcMessage.fromJson(jsonMap); } on FormatException catch (e) { - print("Failed to decode JSON line: $line"); + _logger.warn("Failed to decode JSON line: $line"); throw FormatException("Invalid JSON received: ${e.message}", line); } catch (e) { - print("Failed to parse JsonRpcMessage from line: $line"); + _logger.warn("Failed to parse JsonRpcMessage from line: $line"); rethrow; } } @@ -93,7 +96,7 @@ String serializeMessage(JsonRpcMessage message) { try { return '${jsonEncode(message.toJson())}\n'; } catch (e) { - print("Failed to serialize JsonRpcMessage: $message"); + _logger.warn("Failed to serialize JsonRpcMessage: $message"); rethrow; } } diff --git a/test/shared/logging_test.dart b/test/shared/logging_test.dart new file mode 100644 index 0000000..f9b8146 --- /dev/null +++ b/test/shared/logging_test.dart @@ -0,0 +1,24 @@ +import 'dart:async'; + +import 'package:mcp_dart/mcp_dart.dart'; +import 'package:test/test.dart'; + +void main() { + test('it uses the configured LogHandler', () async { + final completer = Completer>(); + Logger.setHandler((loggerName, level, message) { + completer.complete([loggerName, level, message]); + }); + + final loggerName = "test.logger"; + final logger = Logger(loggerName); + final message = "Test log message"; + logger.info(message); + + final captured = await completer.future; + expect( + captured, + equals([loggerName, LogLevel.info, message]), + ); + }); +}