forked from admin/VolcanicAgenticCore
Streamable HTTP/SSE servers from the workspace's .volcanic/mcp/servers.json. Headers are sealed with AES-GCM; the chat and the visible fields are the associated data, so only /mcp_add|remove|tools change servers. Tools <server>__<tool> go live in the session. An unreachable server is skipped and reported in the system prompt. Keys are redacted from tool results, and Telegram deletes messages that carry one.
45 lines
2.2 KiB
C#
45 lines
2.2 KiB
C#
using System.Text.Json;
|
|
using ModelContextProtocol.Protocol;
|
|
using Volcanic.AgenticCore.Abstractions.Messages;
|
|
using Volcanic.AgenticCore.Abstractions.Tools;
|
|
|
|
namespace Volcanic.AgenticCore.Extensions.Mcp.Remote;
|
|
|
|
/// <summary>
|
|
/// A remote server tool as an agent tool. The registry can't drop or replace a tool, so the same instance is re-bound when
|
|
/// its server is added again or reconnects with a new schema, and unbound (and deactivated) when the server is removed.
|
|
/// </summary>
|
|
internal sealed class RemoteMcpTool(string exposedName) : ITool
|
|
{
|
|
private volatile Binding? _binding;
|
|
|
|
public string Name { get; } = exposedName;
|
|
public string Description => _binding?.Description ?? "Unavailable MCP tool.";
|
|
public JsonElement Parameters => _binding?.Parameters ?? McpTool.NormalizeSchema(default);
|
|
public string? PromptSnippet => null;
|
|
public ToolExecutionMode ExecutionMode => ToolExecutionMode.Sequential;
|
|
|
|
/// <summary>The server this tool currently calls, or null once the server is gone.</summary>
|
|
public string? Server => _binding?.Connection.Name;
|
|
|
|
public void Bind(Tool tool, RemoteServerConnection connection)
|
|
{
|
|
var description = !string.IsNullOrWhiteSpace(tool.Description) ? tool.Description!
|
|
: !string.IsNullOrWhiteSpace(tool.Annotations?.Title ?? tool.Title) ? (tool.Annotations?.Title ?? tool.Title)!
|
|
: tool.Name;
|
|
_binding = new Binding(tool.Name, $"[{connection.Name} MCP] {description}", McpTool.NormalizeSchema(tool.InputSchema), connection);
|
|
}
|
|
|
|
public void Unbind() => _binding = null;
|
|
|
|
public async Task<ToolResult> ExecuteAsync(ToolCallContent call, ToolInvocation invocation, CancellationToken cancellationToken)
|
|
{
|
|
if (_binding is not { } binding)
|
|
throw new InvalidOperationException($"{Name}: its MCP server was removed from this chat.");
|
|
var result = await binding.Connection.CallAsync(binding.McpName, call.Arguments, cancellationToken).ConfigureAwait(false);
|
|
return McpResultMapper.Map(binding.Connection.Name, binding.McpName, result);
|
|
}
|
|
|
|
private sealed record Binding(string McpName, string Description, JsonElement Parameters, RemoteServerConnection Connection);
|
|
}
|