Services/ToolService.cs
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using SandboxModelContextProtocol.Server.Services.Interfaces;
using SandboxModelContextProtocol.Server.Services.Models;

namespace SandboxModelContextProtocol.Server.Services;

public class ToolService( ILogger<ToolService> logger, IServiceProvider serviceProvider ) : IToolService
{
	private readonly ILogger<ToolService> _logger = logger;
	private readonly ConcurrentDictionary<string, TaskCompletionSource<CallToolResponse>> _pendingCommands = new();
	private readonly IWebSocketService _webSocketService = serviceProvider.GetRequiredService<IWebSocketService>();

	public async Task<CallToolResponse> CallTool( CallToolRequest request )
	{
		_logger.LogInformation( "Executing tool call: {Name}", request.Name );

		// Find active connections
		var activeConnections = _webSocketService.GetWebSocketConnections()
			.Where( conn => conn.IsConnected )
			.ToList();

		// If no active connections, return an error
		if ( activeConnections.Count == 0 )
		{
			return new CallToolResponse()
			{
				Id = request.Id,
				Name = request.Name,
				Content = [JsonSerializer.SerializeToElement( "No active s&box connections available" )],
				IsError = true
			};
		}

		var requestJson = JsonSerializer.Serialize( request );

		// Create task completion source for this command
		var tcs = new TaskCompletionSource<CallToolResponse>();
		_pendingCommands[request.Id] = tcs;

		try
		{
			// Send command to all active s&box connections
			await _webSocketService.SendToAll( requestJson );

			_logger.LogInformation( "Tool call sent to s&box connections" );

			// Wait for response with timeout
			using var cts = new CancellationTokenSource( TimeSpan.FromSeconds( 30 ) );
			cts.Token.Register( () =>
			{
				if ( tcs.TrySetCanceled() )
				{
					_pendingCommands.TryRemove( request.Id, out _ );
					_logger.LogWarning( "Tool call {Id} timed out", request.Id );
				}
			} );

			return await tcs.Task;
		}
		catch ( OperationCanceledException )
		{
			_pendingCommands.TryRemove( request.Id, out _ );
			return new CallToolResponse()
			{
				Id = request.Id,
				Name = request.Name,
				Content = [JsonSerializer.SerializeToElement( "Tool call timed out after 30 seconds" )],
				IsError = true
			};
		}
		catch ( Exception ex )
		{
			_pendingCommands.TryRemove( request.Id, out _ );
			_logger.LogError( ex, "Failed to send tool call to s&box connections" );
			return new CallToolResponse()
			{
				Id = request.Id,
				Name = request.Name,
				Content = [JsonSerializer.SerializeToElement( $"Failed to send tool call: {ex.Message}" )],
				IsError = true
			};
		}
	}

	public void HandleResponse( string message )
	{
		_logger.LogInformation( "Handling response: {Message}", message );

		CallToolResponse? response = JsonSerializer.Deserialize<CallToolResponse>( message );
		if ( response == null )
		{
			_logger.LogWarning( "Failed to parse response JSON: {Message}", message );
			return;
		}

		if ( _pendingCommands.TryRemove( response.Id, out var tcs ) )
		{
			tcs.SetResult( response );
			_logger.LogInformation( "Tool call {Id} completed successfully", response.Id );
		}
	}
}