Files
EugeneTes ed026ac6c8 feat(mcp): process-wide tool catalogue discovery
McpProcess starts a server through the SDK stdio client under the startup
timeout and keeps a stderr tail for its errors. McpCatalogCache runs one shared
discovery per server config in a temp dir, with the environment from the process
spawn policy. It keeps successes for the process lifetime and failures for 60 s.

Deviation from the plan: the discovery process is disposed and its temp dir
deleted in the background, after the catalogue is returned. SDK 2.2 waits the
whole ShutdownTimeout (5 s) with stdin still open before it kills the process,
so an awaited dispose held every first activation for 5 s. A new test
(Discovery_returns_without_waiting_for_the_server_shutdown) covers this. Spec
section 3 gets the new GetAsync signature, the detached discovery and the
background cleanup. Section 5 now describes the SDK's shutdown order correctly.
2026-09-26 20:31:43 +02:00

25 lines
1.4 KiB
C#

using System.Text.Json;
using ModelContextProtocol.Protocol;
namespace Volcanic.AgenticCore.Extensions.Mcp;
/// <summary>One server tool: the name on the wire, the name the agent sees, and its schema verbatim.</summary>
internal sealed record McpCatalogTool(string McpName, string ExposedName, string? Description, string? Title, JsonElement InputSchema);
/// <summary>A server's <c>tools/list</c> result, discovered once per host process.</summary>
internal sealed record McpCatalog(string ServerName, string? ServerInfoName, string? ServerVersion, IReadOnlyList<McpCatalogTool> Tools)
{
public static McpCatalog From(string serverName, McpServerConfig config, Implementation? info, IReadOnlyList<Tool> tools)
{
var exposed = ToolNaming.Expose([.. tools.Select(t => t.Name)], config.ToolPrefix);
return new McpCatalog(serverName, info?.Name, info?.Version,
[.. tools.Select((t, i) => new McpCatalogTool(t.Name, exposed[i], t.Description, t.Annotations?.Title ?? t.Title, t.InputSchema.Clone()))]);
}
}
/// <summary>A server could not be started or didn't answer the handshake. The message includes its stderr tail.</summary>
internal sealed class McpServerStartException(string message, Exception? inner = null) : Exception(message, inner);
/// <summary>A running server exited; the next call starts a new process.</summary>
internal sealed class McpServerExitedException(string message) : Exception(message);