Class BowireMcpProtocol

Namespace
Kuestenlogik.Bowire.Protocol.Mcp
Assembly
Kuestenlogik.Bowire.Protocol.Mcp.dll

Bowire protocol plugin for the Model Context Protocol (MCP). Connects to a remote MCP server, discovers its tools / resources / prompts, and lets the user invoke them — analogous to the gRPC and REST plugins.

public sealed class BowireMcpProtocol : IBowireProtocol, IBowireDiscoveryDiagnostics
Inheritance
BowireMcpProtocol
Implements
Inherited Members

Remarks

Built on the official ModelContextProtocol C# SDK (ModelContextProtocol.Client.McpClient + ModelContextProtocol.Client.HttpClientTransport) — the previous hand-rolled JSON-RPC client predates the SDK's client surface. The SDK owns the entire negotiation: on MCP revision 2026-07-28 it probes server/discover first and only falls back to the legacy initialize handshake when that errors or the probe timeout elapses; it auto-detects Streamable HTTP vs SSE, stamps the SEP-2243 MCP-Protocol-Version / Mcp-Method / Mcp-Name headers onto every POST, and writes the JSON-RPC envelope.

There is no session left to resume: 2026-07-28 removed Mcp-Session-Id along with the initialize handshake, so per-request identity (protocol version, client info, client capabilities) travels inside params._meta and the SDK injects it. Bowire stores, echoes and transmits none of it — which is also why a fresh throwaway client per call costs nothing but the probe round trip.

The companion MapBowireMcpAdapter(IEndpointRouteBuilder, string) extension lives in the same assembly but is unrelated to discovery: it is an opt-in development feature that goes the other direction (Bowire's services exposed as MCP tools so AI agents can call them). It runs on the SDK's own server transport (AddBowireMcpAdapterWithHttpTransport); only the tool/resource/prompt contents are synthesised from Bowire's BowireProtocolRegistry.

Constructors

BowireMcpProtocol()

public BowireMcpProtocol()

Properties

Description

One-line description shown next to the plugin's display name in Settings → Plugins. Optional; defaults to empty so the row falls back to "DisplayName + version" without the description line. Keep it short (≤ 100 characters) — the row truncates anything wider than the manage panel.

public string Description { get; }

Property Value

string

IconSvg

SVG icon for the protocol tab.

public string IconSvg { get; }

Property Value

string

Id

Short identifier (e.g., "grpc", "signalr").

public string Id { get; }

Property Value

string

Name

Protocol name shown in UI tabs.

public string Name { get; }

Property Value

string

Methods

DiscoverAsync(string, bool, CancellationToken)

The lossy channel. Everything happens in DiscoverWithDiagnosticsAsync(string, bool, CancellationToken); this only has to decide what to do with a fault when the caller has no field to put it in.

public Task<List<BowireServiceInfo>> DiscoverAsync(string serverUrl, bool showInternalServices, CancellationToken ct = default)

Parameters

serverUrl string
showInternalServices bool
ct CancellationToken

Returns

Task<List<BowireServiceInfo>>

Remarks

This signature stays all-or-nothing: a fault throws, even when some surfaces answered. That looks like the opposite of #544, and it is deliberate — the lossy channel cannot carry "and by the way, part of this is missing", so the only two options are a silent truncation or a throw, and a silent truncation is the dangerous one. Bowire's own security scanner calls this signature directly (McpToolInjectionProbe, McpDiscoveryProbe, McpResourceTraversalProbe via OwaspApiSuite): handing it a half-list without a word would make it report a clean bill of health for an attack surface it never saw.

Callers that want the services AND the fault use IBowireDiscoveryDiagnostics, which is where #544 lives — BowireDiscoveryProbe takes that path, so the workbench and the CLI keep the working surfaces and show the diagnostic.

DiscoverWithDiagnosticsAsync(string, bool, CancellationToken)

Same probe as DiscoverAsync(string, bool, CancellationToken), but able to report a fault without swallowing the services it did find.

public Task<BowireDiscoveryReport> DiscoverWithDiagnosticsAsync(string serverUrl, bool showInternalServices, CancellationToken ct = default)

Parameters

serverUrl string

The bare target URL, as DiscoverAsync gets it.

showInternalServices bool

Forwarded verbatim from the probe.

ct CancellationToken

Caller cancellation, linked into the probe ceiling.

Returns

Task<BowireDiscoveryReport>

Initialize(IServiceProvider?)

Called after registration to inject the app's service provider (embedded mode).

public void Initialize(IServiceProvider? serviceProvider)

Parameters

serviceProvider IServiceProvider

InvokeAsync(string, string, string, List<string>, bool, Dictionary<string, string>?, CancellationToken)

Invoke a unary or client-streaming call.

public Task<InvokeResult> InvokeAsync(string serverUrl, string service, string method, List<string> jsonMessages, bool showInternalServices, Dictionary<string, string>? metadata = null, CancellationToken ct = default)

Parameters

serverUrl string
service string
method string
jsonMessages List<string>
showInternalServices bool
metadata Dictionary<string, string>
ct CancellationToken

Returns

Task<InvokeResult>

InvokeStreamAsync(string, string, string, List<string>, bool, Dictionary<string, string>?, CancellationToken)

Invoke a server-streaming or duplex call.

public IAsyncEnumerable<string> InvokeStreamAsync(string serverUrl, string service, string method, List<string> jsonMessages, bool showInternalServices, Dictionary<string, string>? metadata = null, CancellationToken ct = default)

Parameters

serverUrl string
service string
method string
jsonMessages List<string>
showInternalServices bool
metadata Dictionary<string, string>
ct CancellationToken

Returns

IAsyncEnumerable<string>

OpenChannelAsync(string, string, string, bool, Dictionary<string, string>?, CancellationToken)

Open an interactive channel (for duplex/client-streaming).

public Task<IBowireChannel?> OpenChannelAsync(string serverUrl, string service, string method, bool showInternalServices, Dictionary<string, string>? metadata = null, CancellationToken ct = default)

Parameters

serverUrl string
service string
method string
showInternalServices bool
metadata Dictionary<string, string>
ct CancellationToken

Returns

Task<IBowireChannel>