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 (AddBowireMcpAdapter →
WithHttpTransport); 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
IconSvg
SVG icon for the protocol tab.
public string IconSvg { get; }
Property Value
Id
Short identifier (e.g., "grpc", "signalr").
public string Id { get; }
Property Value
Name
Protocol name shown in UI tabs.
public string Name { get; }
Property Value
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
serverUrlstringshowInternalServicesboolctCancellationToken
Returns
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
serverUrlstringThe bare target URL, as DiscoverAsync gets it.
showInternalServicesboolForwarded verbatim from the probe.
ctCancellationTokenCaller cancellation, linked into the probe ceiling.
Returns
Initialize(IServiceProvider?)
Called after registration to inject the app's service provider (embedded mode).
public void Initialize(IServiceProvider? serviceProvider)
Parameters
serviceProviderIServiceProvider
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
serverUrlstringservicestringmethodstringjsonMessagesList<string>showInternalServicesboolmetadataDictionary<string, string>ctCancellationToken
Returns
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
serverUrlstringservicestringmethodstringjsonMessagesList<string>showInternalServicesboolmetadataDictionary<string, string>ctCancellationToken
Returns
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
serverUrlstringservicestringmethodstringshowInternalServicesboolmetadataDictionary<string, string>ctCancellationToken