Channel Resolution
How MCP tools map to KubeMQ channels — naming, the five channel types, the reserved _AGENTS_ prefix, and discovery via channel_list and channel_info.
Every KubeMQ MCP tool acts on a channel — the addressable destination a message is sent to or read from. This guide explains how channels are named, the five channel types each tool family targets, the single reserved prefix you must avoid, and how a model discovers channels at runtime before it acts.
Overview
A channel is just a string. There is no registry to provision and no create step: a
channel comes into existence the first time a tool references it, and disappears from
discovery when it no longer carries traffic. This makes the MCP surface
self-describing — a model can name a channel on the fly (orders.us-west), and the same
name resolves consistently across every tool that uses it.
Each tool argument named channel is resolved against one of the five KubeMQ messaging
patterns. The tool you call determines the pattern; the channel string determines the
destination within it.
How tools map to channels
There is no separate "channel resolution" step in the protocol — the tool name selects
the channel type, and the channel argument names the destination. The model never has
to declare a type alongside a send; calling queue_send is the declaration that the
channel is a queue.
The tool family selects the channel type; the channel argument names the destination.
Channel naming
- Channels are arbitrary strings — for example
my-app.orders,notifications, oruser-events. - Channels are created implicitly on first use. There is no explicit creation step.
- Convention: use dot-separated hierarchical names to organize a namespace, such as
orders.us-westorevents.user.signup.
Because channels are created on demand, a typo creates a new (empty) channel rather than
raising an error. Use channel_list to confirm a channel exists
and carries traffic before relying on it.
Channel types
Each channel belongs to exactly one of five types — one per messaging pattern — and each type is served by a specific set of tools:
| Type | Description | Associated tools |
|---|---|---|
queues | Point-to-point durable queue channels | queue_send, queue_receive, queue_peek |
events | Ephemeral fire-and-forget pub/sub channels | events_publish |
events_store | Persistent pub/sub channels with replay | events_store_publish, events_store_read, events_store_read_latest |
commands | Request/reply command channels | command_send |
queries | Request/reply query channels | query_send |
The same string can name distinct channels under different types — orders as a queue and
orders as an events channel are unrelated. That is why channel_info requires both the
channel name and the type.
Only queues and events_store channels are backed by broker monitoring, so
channel_list and channel_info report live message statistics for them. The ephemeral
types (events, commands, queries) exist only while subscribers are connected and are
returned as type descriptions rather than per-channel stats.
Reserved prefix
One prefix is reserved by the broker for the agent bridge and cannot be targeted by the direct messaging tools.
| Prefix | Purpose | Restriction |
|---|---|---|
_AGENTS_. | Agent-bridge internal communication | Rejected by all direct messaging tools |
A channel is reserved when its name begins with the literal prefix _AGENTS_. (the
trailing dot is part of the prefix). Passing such a channel to queue_send,
events_publish, events_store_publish, command_send, or query_send returns a tool
error (isError: true) at the tool layer — the message is never published.
To reach an agent, use the agent-bridge tools agent_send and agent_query instead of
addressing _AGENTS_.* channels directly. The bridge manages the reserved channels on
your behalf — see Agent-bridge tools.
Discovering channels
Two read-only tools let a model explore the namespace at runtime instead of hard-coding
channel names: channel_list enumerates channels (optionally filtered), and
channel_info returns metadata for one channel.
Listing and filtering
channel_list returns every known channel, or a subset filtered by type, by a name
pattern, or both. The pattern filter is a prefix match — "example-" matches
example-queue and example-events but not my-example. With no arguments it returns
all channels.
# List all channels (no filter)
curl -X POST http://localhost:9090/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-H 'MCP-Protocol-Version: 2025-11-25' \
-d '{
"jsonrpc": "2.0",
"id": 11,
"method": "tools/call",
"params": {
"name": "channel_list",
"arguments": {}
}
}'
# Filter by type: "arguments": { "type": "queues" }
# Filter by prefix: "arguments": { "pattern": "example-" }url := os.Getenv("KUBEMQ_MCP_URL")
if url == "" {
url = "http://localhost:9090"
}
c, err := client.NewStreamableHttpClient(url + "/mcp")
if err != nil {
log.Fatal(err)
}
defer c.Close()
ctx := context.Background()
if err := c.Start(ctx); err != nil {
log.Fatal(err)
}
result, err := c.CallTool(ctx, mcp.CallToolRequest{
Params: mcp.CallToolParams{
Name: "channel_list",
Arguments: map[string]any{},
},
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("Result: %+v\n", result)async with streamablehttp_client(f"{KUBEMQ_MCP_URL}/mcp") as (read, write, _):
async with ClientSession(read, write) as session:
await session.initialize()
# List all channels (no filter)
result = await session.call_tool("channel_list", {})
print(f"IsError: {result.isError}")
for content in result.content:
print(f"Result: {content.text}")
# To filter by type, use: {"type": "queues"}
# To filter by pattern, use: {"pattern": "example-"}const transport = new StreamableHTTPClientTransport(
new URL(`${KUBEMQ_MCP_URL}/mcp`)
);
const client = new Client({ name: "kubemq-mcp-ts-example", version: "1.0.0" });
await client.connect(transport);
// List all channels (no filter)
const result = await client.callTool({
name: "channel_list",
arguments: {},
});
console.log(JSON.stringify(result, null, 2));
// To filter by type: { type: "queues" }
// To filter by pattern: { pattern: "example-" }
await client.close();String url = System.getenv().getOrDefault("KUBEMQ_MCP_URL", "http://localhost:9090");
var transport = HttpClientStreamableHttpTransport.builder(url).endpoint("/mcp").build();
var client = McpClient.sync(transport).build();
client.initialize();
// List all channels (no filter)
var result = client.callTool(new CallToolRequest(
"channel_list",
Map.of()
));
System.out.println(result);
// To filter by type: Map.of("type", "queues")
// To filter by pattern: Map.of("pattern", "example-")
client.closeGracefully();var url = Environment.GetEnvironmentVariable("KUBEMQ_MCP_URL") ?? "http://localhost:9090";
var transport = new HttpClientTransport(new HttpClientTransportOptions { Endpoint = new Uri($"{url}/mcp") });
await using var client = await McpClientFactory.CreateAsync(transport);
var result = await client.CallToolAsync("channel_list", new Dictionary<string, object>());
Console.WriteLine($"Result: {result}");val url = System.getenv("KUBEMQ_MCP_URL") ?: "http://localhost:9090"
val httpClient = HttpClient { install(SSE) }
val transport = StreamableHttpClientTransport(client = httpClient, url = "$url/mcp")
val client = Client(clientInfo = Implementation(name = "kubemq-mcp-kotlin-example", version = "1.0.0"))
client.connect(transport)
val result = client.callTool("channel_list", emptyMap())
println("Result: $result")
client.close()
httpClient.close()url = ENV.fetch("KUBEMQ_MCP_URL", "http://localhost:9090")
client = MCP::Client.new(
transport: MCP::Transport::StreamableHTTP.new("#{url}/mcp"),
name: "kubemq-mcp-ruby-example",
version: "1.0.0"
)
client.initialize_handshake
result = client.call_tool("channel_list", {})
puts "Result: #{result}"
client.closelet url = std::env::var("KUBEMQ_MCP_URL")
.unwrap_or_else(|_| "http://localhost:9090".to_string());
let transport = StreamableHttpClientTransport::from_uri(format!("{url}/mcp"))?;
let client = ().serve(transport).await?;
let result = client.call_tool("channel_list", json!({})).await?;
println!("Result: {result:#?}");let url = ProcessInfo.processInfo.environment["KUBEMQ_MCP_URL"] ?? "http://localhost:9090"
let transport = HTTPClientTransport(endpoint: URL(string: "\(url)/mcp")!, streaming: true)
let client = Client(name: "kubemq-mcp-swift-example", version: "1.0.0")
try await client.connect(transport: transport)
let result = try await client.callTool("channel_list", arguments: [:])
print("Result: \(result)")A successful channel_list returns a JSON array in the standard content[]/isError
envelope. An empty array ([]) is a normal result, not an error:
{
"jsonrpc": "2.0",
"id": 11,
"result": {
"content": [
{
"type": "text",
"text": "[{\"name\":\"example-queue\",\"type\":\"queues\",\"is_active\":true},{\"name\":\"example-events\",\"type\":\"events\",\"is_active\":true}]"
}
],
"isError": false
}
}Inspecting one channel
channel_info confirms a single channel's type and live state. Both the channel name and
its type are required — the type scopes the lookup to the correct messaging pattern.
curl -X POST http://localhost:9090/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-H 'MCP-Protocol-Version: 2025-11-25' \
-d '{
"jsonrpc": "2.0",
"id": 12,
"method": "tools/call",
"params": {
"name": "channel_info",
"arguments": {
"channel": "example-queue",
"type": "queues"
}
}
}'result, err := c.CallTool(ctx, mcp.CallToolRequest{
Params: mcp.CallToolParams{
Name: "channel_info",
Arguments: map[string]any{
"channel": "example-queue",
"type": "queues",
},
},
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("Result: %+v\n", result)result = await session.call_tool("channel_info", {
"channel": "example-queue",
"type": "queues",
})
print(f"IsError: {result.isError}")
for content in result.content:
print(f"Result: {content.text}")const result = await client.callTool({
name: "channel_info",
arguments: {
channel: "example-queue",
type: "queues",
},
});
console.log(JSON.stringify(result, null, 2));var result = client.callTool(new CallToolRequest(
"channel_info",
Map.of(
"channel", "example-queue",
"type", "queues"
)
));
System.out.println(result);var result = await client.CallToolAsync("channel_info", new Dictionary<string, object>
{
["channel"] = "example-queue",
["type"] = "queues",
});
Console.WriteLine($"Result: {result}");val result = client.callTool("channel_info", mapOf(
"channel" to "example-queue",
"type" to "queues"
))
println("Result: $result")result = client.call_tool("channel_info", {
"channel" => "example-queue",
"type" => "queues",
})
puts "Result: #{result}"let result = client.call_tool("channel_info", json!({
"channel": "example-queue",
"type": "queues"
})).await?;
println!("Result: {result:#?}");let result = try await client.callTool("channel_info", arguments: [
"channel": "example-queue",
"type": "queues",
])
print("Result: \(result)")For queues and events_store channels the result includes live incoming/outgoing
message counts; for the ephemeral types it returns the channel's type and active state:
{
"jsonrpc": "2.0",
"id": 12,
"result": {
"content": [
{
"type": "text",
"text": "{\"name\":\"example-queue\",\"type\":\"queues\",\"is_active\":true,\"incoming\":5,\"outgoing\":3}"
}
],
"isError": false
}
}Resolution failures
| Condition | Result |
|---|---|
Channel name begins with _AGENTS_. on a direct messaging tool | Tool error — isError: true |
Invalid type value (not one of the five) | JSON-RPC -32602 Invalid Params |
channel_info missing channel or type | JSON-RPC -32602 Invalid Params |
channel_list with a pattern that matches nothing | Successful result with an empty array [] |
For the full JSON-RPC error catalog, see Error codes.
Related
Channel-management tools
Full input/output schemas for channel_list and channel_info with examples in every language.
Agent-bridge tools
Reach agents over the reserved _AGENTS_ channels with agent_send and agent_query.
Error handling
Detect and recover from the three MCP failure layers, including reserved-channel rejections.
Was this page helpful?
Authentication
Secure the MCP connector with JWT Bearer tokens — attach the Authorization header to your MCP client, handle -32010 errors, and configure origin validation.
Session Management
Establish and reuse an MCP session with the KubeMQ connector — initialize handshake, MCP-Session-Id, batch requests, and the GET /mcp keepalive stream.