# Channel Resolution (/aiway/mcp/guides/channel-resolution)



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 [#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 [#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.

<Mermaid
  chart="`
graph LR
AI[&#x22;AI model&#x22;]
MCP[&#x22;MCP connector&#x22;]
Q{{&#x22;queues<br/>orders&#x22;}}
E{{&#x22;events<br/>notifications&#x22;}}
ES{{&#x22;events_store<br/>audit-log&#x22;}}
C{{&#x22;commands<br/>provision&#x22;}}
QY{{&#x22;queries<br/>lookup&#x22;}}

AI -- &#x22;queue_*&#x22; --> MCP
AI -- &#x22;events_publish&#x22; --> MCP
AI -- &#x22;events_store_*&#x22; --> MCP
AI -- &#x22;command_send&#x22; --> MCP
AI -- &#x22;query_send&#x22; --> MCP
MCP --> Q
MCP --> E
MCP --> ES
MCP --> C
MCP --> QY

class AI external
class MCP aiway
class Q queue
class E events
class ES store
class C command
class QY query
`"
/>

*The tool family selects the channel type; the `channel` argument names the destination.*

## Channel naming [#channel-naming]

* Channels are arbitrary strings — for example `my-app.orders`, `notifications`, or
  `user-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-west` or `events.user.signup`.

Because channels are created on demand, a typo creates a new (empty) channel rather than
raising an error. Use [`channel_list`](#discovering-channels) to confirm a channel exists
and carries traffic before relying on it.

## Channel types [#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`.

<Callout type="info">
  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.
</Callout>

## Reserved prefix [#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.

<Callout type="warn">
  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](/aiway/mcp/tools/agent-bridge).
</Callout>

## Discovering channels [#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 [#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.

<Tabs groupId="language" items="['curl','Go','Python','TypeScript','Java','C#','Kotlin','Ruby','Rust','Swift']">
  <Tab value="curl">
    ```bash
    # 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-" }
    ```
  </Tab>

  <Tab value="Go">
    ```go
    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)
    ```
  </Tab>

  <Tab value="Python">
    ```python
    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-"}
    ```
  </Tab>

  <Tab value="TypeScript">
    ```typescript
    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();
    ```
  </Tab>

  <Tab value="Java">
    ```java
    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();
    ```
  </Tab>

  <Tab value="C#">
    ```csharp
    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}");
    ```
  </Tab>

  <Tab value="Kotlin">
    ```kotlin
    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()
    ```
  </Tab>

  <Tab value="Ruby">
    ```ruby
    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.close
    ```
  </Tab>

  <Tab value="Rust">
    ```rust
    let 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:#?}");
    ```
  </Tab>

  <Tab value="Swift">
    ```swift
    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)")
    ```
  </Tab>
</Tabs>

A successful `channel_list` returns a JSON array in the standard `content[]`/`isError`
envelope. An empty array (`[]`) is a normal result, not an error:

```json
{
  "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 [#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.

<Tabs groupId="language" items="['curl','Go','Python','TypeScript','Java','C#','Kotlin','Ruby','Rust','Swift']">
  <Tab value="curl">
    ```bash
    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"
          }
        }
      }'
    ```
  </Tab>

  <Tab value="Go">
    ```go
    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)
    ```
  </Tab>

  <Tab value="Python">
    ```python
    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}")
    ```
  </Tab>

  <Tab value="TypeScript">
    ```typescript
    const result = await client.callTool({
      name: "channel_info",
      arguments: {
        channel: "example-queue",
        type: "queues",
      },
    });
    console.log(JSON.stringify(result, null, 2));
    ```
  </Tab>

  <Tab value="Java">
    ```java
    var result = client.callTool(new CallToolRequest(
        "channel_info",
        Map.of(
            "channel", "example-queue",
            "type", "queues"
        )
    ));
    System.out.println(result);
    ```
  </Tab>

  <Tab value="C#">
    ```csharp
    var result = await client.CallToolAsync("channel_info", new Dictionary<string, object>
    {
        ["channel"] = "example-queue",
        ["type"] = "queues",
    });

    Console.WriteLine($"Result: {result}");
    ```
  </Tab>

  <Tab value="Kotlin">
    ```kotlin
    val result = client.callTool("channel_info", mapOf(
        "channel" to "example-queue",
        "type" to "queues"
    ))

    println("Result: $result")
    ```
  </Tab>

  <Tab value="Ruby">
    ```ruby
    result = client.call_tool("channel_info", {
      "channel" => "example-queue",
      "type" => "queues",
    })

    puts "Result: #{result}"
    ```
  </Tab>

  <Tab value="Rust">
    ```rust
    let result = client.call_tool("channel_info", json!({
        "channel": "example-queue",
        "type": "queues"
    })).await?;

    println!("Result: {result:#?}");
    ```
  </Tab>

  <Tab value="Swift">
    ```swift
    let result = try await client.callTool("channel_info", arguments: [
        "channel": "example-queue",
        "type": "queues",
    ])

    print("Result: \(result)")
    ```
  </Tab>
</Tabs>

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:

```json
{
  "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 [#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](/aiway/mcp/reference/error-codes).

## Related [#related]

<Cards>
  <Card title="Channel-management tools" href="/aiway/mcp/tools/channel-management" description="Full input/output schemas for channel_list and channel_info with examples in every language." />

  <Card title="Agent-bridge tools" href="/aiway/mcp/tools/agent-bridge" description="Reach agents over the reserved _AGENTS_ channels with agent_send and agent_query." />

  <Card title="Error handling" href="/aiway/mcp/guides/error-handling" description="Detect and recover from the three MCP failure layers, including reserved-channel rejections." />
</Cards>
