using Telegram.Bot.Types; using Volcanic.AgenticCore.Extensions.Mcp.Remote; using Volcanic.AgenticCore.Features.Extensions; using Volcanic.AgenticCore.Features.Processes; namespace Volcanic.AgenticCore.Telegram; /// /// Remote MCP servers per chat (). Keys are sealed with MCP_ENCRYPTION_SECRET, or /// the bot token when that is unset; agent processes see neither (both match the default deny-list). /// public static class RemoteMcpSetup { public const string SecretVariable = "MCP_ENCRYPTION_SECRET"; public static readonly IReadOnlyList Menu = [ new() { Command = "mcp_list", Description = "Remote MCP servers of this chat" }, new() { Command = "mcp_add", Description = "Add an MCP server: /mcp_add " }, new() { Command = "mcp_remove", Description = "Remove an MCP server: /mcp_remove " }, new() { Command = "mcp_tools", Description = "Show or limit a server's tools" }, ]; public const string HelpLines = "\n/mcp_list, /mcp_add , /mcp_remove , /mcp_tools [a,b|all] – this chat's remote MCP servers"; public static IReadOnlyList MenuFor(IReadOnlyList menu) => [.. menu, .. Menu]; /// The dedicated secret, else the bot token. Adds the dedicated one to the redacted secret values. public static RemoteMcpOptions Options(TelegramOptions telegram, Func getEnv, ProcessEnvironmentOptions processEnvironment) { ArgumentNullException.ThrowIfNull(telegram); ArgumentNullException.ThrowIfNull(getEnv); ArgumentNullException.ThrowIfNull(processEnvironment); var dedicated = getEnv(SecretVariable) is { } value && !string.IsNullOrWhiteSpace(value) ? value.Trim() : null; if (dedicated is not null) processEnvironment.SecretValues.Add(dedicated); return new RemoteMcpOptions { Secret = dedicated ?? (string.IsNullOrWhiteSpace(telegram.BotToken) ? null : telegram.BotToken) }; } public static string Describe(Func getEnv) => string.IsNullOrWhiteSpace(getEnv(SecretVariable)) ? $"Remote MCP: keys sealed with the bot token (set {SecretVariable} to keep them across a token change)" : $"Remote MCP: keys sealed with {SecretVariable}"; /// A command whose message holds a key: the router deletes it from the chat after handing it over. public static bool CarriesKey(string command, string args) => command == "mcp_add" && (args.Contains("--bearer", StringComparison.Ordinal) || args.Contains("--header", StringComparison.Ordinal)); /// A new extension instance per chat session. public static ExtensionRegistration Registration(RemoteMcpOptions options) => new(RemoteMcpExtension.ExtensionName, _ => new RemoteMcpExtension(options), "telegram"); }