KubeMQ
AiwayMCP

Getting started with MCP

Connect Claude Desktop, Cursor or any MCP host to KubeMQ, list the tools, and call your first tool by hand or from an SDK.

The MCP connector is enabled by default on the shared HTTP server. Start KubeMQ and POST /mcp is live — there is no flag to turn it on. This guide takes you from a running server to your first tool call, with a Claude Desktop config, raw JSON-RPC over curl, and the official MCP SDK in nine languages.

Prerequisites

Any running KubeMQ works, including Try KubeMQ. MCP and A2A are on by default on port 9090. On Kubernetes, run kubectl --context YOUR_KUBE_CONTEXT -n kubemq port-forward svc/messaging-rest 9090:9090 first.

  • For Claude Desktop: a current Claude Desktop install, and Node.js, which provides npx.
  • For the SDK examples (optional): one of the nine supported runtimes — Python 3.10+, Node.js 18+, Java 21+, .NET 8+, Go 1.21+, Rust 1.75+, Ruby 3.1+, Kotlin 1.9+, or Swift 6.0+. The curl path needs no SDK or runtime — it speaks the MCP JSON-RPC protocol directly over HTTP.

Enable / disable

The MCP connector is enabled by default — start kubemq-server and POST /mcp is immediately available. No =true flag is needed.

To disable MCP, set its enable env var to false:

docker run -d --pull always --platform linux/amd64 --name kubemq --hostname kubemq -p 127.0.0.1:9090:9090 -p 127.0.0.1:50000:50000 -e STORE_ENGINE=next -e STORE_NEXT_ACK_POLICY=strict -e STORE_STORE_PATH=/kubemq/store -e API_BIND_ADDRESS=0.0.0.0 -e CONNECTORSMCP_ENABLE=false -v kubemq-data:/kubemq/store europe-docker.pkg.dev/kubemq/images/kubemq-next:latest

The enable var name CONNECTORSMCP_ENABLE is irregular by design, and older KubeMQ docs described MCP as off-by-default — both are explained on Shared HTTP server. Set it to false to disable; never to true to enable.

How it works

Every MCP interaction is a JSON-RPC 2.0 call to POST /mcp. A client first negotiates a session with initialize, acknowledges it with notifications/initialized, then discovers tools with tools/list and invokes them with tools/call. The connector resolves each tool to a KubeMQ operation and returns the result.

The MCP handshake establishes a session, then each tools/call maps to a KubeMQ operation.

Connect an MCP host

KubeMQ's MCP endpoint is http://localhost:9090/mcp. Pick your host below.

Claude Desktop

Claude Desktop's configuration file lists local servers that it starts itself, so KubeMQ joins through mcp-remote, a small bridge that npx downloads and runs. Open the file from Claude Desktop → Settings → Developer → Edit Config, and add:

claude_desktop_config.json
{
  "mcpServers": {
    "kubemq": {
      "command": "npx",
      "args": ["-y", "mcp-remote", "http://localhost:9090/mcp"]
    }
  }
}

Quit Claude Desktop fully and start it again. Closing the window is not enough.

Cursor

Add KubeMQ to ~/.cursor/mcp.json for all projects, or to .cursor/mcp.json for one project:

~/.cursor/mcp.json
{
  "mcpServers": {
    "kubemq": {
      "url": "http://localhost:9090/mcp"
    }
  }
}

Reload the Cursor window.

Other hosts

Add a Streamable HTTP server at http://localhost:9090/mcp. The /mcp path is required. For an authenticated server and the transport details, see Client setup.

Check the connection

Check the tools from the server

Terminal
curl -s -X POST http://localhost:9090/mcp -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | grep -o '"name":"[a-z_]*"'

You should see:

Output
"name":"queue_send"
"name":"queue_receive"
"name":"queue_peek"
"name":"events_publish"
"name":"events_store_publish"
"name":"events_store_read"
"name":"events_store_read_latest"
"name":"command_send"
"name":"query_send"
"name":"channel_list"
"name":"channel_info"
"name":"agent_list"
"name":"agent_info"
"name":"agent_send"
"name":"agent_query"

Check the tools from the host

Start a new conversation and ask the host: What KubeMQ tools do you have? You should see queue_send among the tools.

Then ask it to send hello to the mcp-check queue. You should see a result with a message_id.

Security: an LLM on an open gateway can send and consume real messages

With authentication off, anything that reaches port 9090 can call every MCP tool. Connect an LLM host only to a test server, or turn on authentication with a restricted role. To turn MCP off, see Enable / disable.

The handshake by hand

The steps below drive the protocol directly with curl so you can see exactly what each MCP client does for you. These commands are for bash on macOS and Linux; on Windows, run them in WSL (Windows Subsystem for Linux).

Initialize a session

Send an initialize request to negotiate the protocol version (2025-11-25) and open a session:

terminal
curl -X POST http://localhost:9090/mcp \
  -H 'Content-Type: application/json' \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "initialize",
    "params": {
      "protocolVersion": "2025-11-25",
      "capabilities": {},
      "clientInfo": { "name": "test", "version": "1.0.0" }
    }
  }'

The server replies with its capabilities and a session ID under result._meta.sessionId. The same value is also returned in the MCP-Session-Id response header:

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "protocolVersion": "2025-11-25",
    "capabilities": { "tools": {} },
    "serverInfo": { "name": "kubemq", "version": "..." },
    "_meta": { "sessionId": "<session-id>" }
  }
}

Save the sessionId — every subsequent request carries it in the MCP-Session-Id header.

Acknowledge the handshake

Tell the server the handshake is complete with the notifications/initialized notification, passing the session ID:

terminal
curl -X POST http://localhost:9090/mcp \
  -H 'Content-Type: application/json' \
  -H 'MCP-Session-Id: <session-id>' \
  -d '{
    "jsonrpc": "2.0",
    "method": "notifications/initialized"
  }'

This is a notification, so it has no id field. The server returns HTTP 200 OK with body {"jsonrpc":"2.0","result":null,"id":null} — there is no meaningful JSON-RPC result to parse.

List the available tools

Discover what you can call with tools/list. KubeMQ has 11 core tools, plus 4 agent-bridge tools when the agent registry is available (15 total):

terminal
curl -X POST http://localhost:9090/mcp \
  -H 'Content-Type: application/json' \
  -H 'MCP-Session-Id: <session-id>' \
  -d '{
    "jsonrpc": "2.0",
    "id": 2,
    "method": "tools/list"
  }'

Each entry carries a name, description, and inputSchema. The Tools overview maps every tool to its KubeMQ operation.

Call your first tool

Invoke queue_send to put a message on a queue channel, passing the session ID:

terminal
curl -X POST http://localhost:9090/mcp \
  -H 'Content-Type: application/json' \
  -H 'MCP-Session-Id: <session-id>' \
  -d '{
    "jsonrpc": "2.0",
    "id": 3,
    "method": "tools/call",
    "params": {
      "name": "queue_send",
      "arguments": {
        "channel": "my-queue",
        "body": "Hello from MCP"
      }
    }
  }'

A successful call returns a content array whose text is the send result, including the new message's ID:

{
  "jsonrpc": "2.0",
  "id": 3,
  "result": {
    "content": [{ "type": "text", "text": "{\"is_error\":false,\"message_id\":\"<message-id>\",\"sent_at\":\"<sent-at>\"}" }]
  }
}

A failed call returns "isError": true with text that starts error:.

The same call from an SDK

Each official MCP SDK performs the initialize handshake and tracks the session ID for you over the Streamable HTTP transport — you only write the tools/call. All examples read KUBEMQ_MCP_URL (default http://localhost:9090) and append /mcp.

curl -X POST http://localhost:9090/mcp \
  -H 'Content-Type: application/json' \
  -H 'MCP-Session-Id: <session-id>' \
  -d '{
    "jsonrpc": "2.0",
    "id": 3,
    "method": "tools/call",
    "params": {
      "name": "queue_send",
      "arguments": {
        "channel": "example-queue",
        "body": "Hello from MCP",
        "metadata": "example-metadata",
        "tags": { "env": "dev", "source": "mcp-example" }
      }
    }
  }'
package main

import (
	"context"
	"fmt"
	"log"
	"os"

	"github.com/mark3labs/mcp-go/client"
	"github.com/mark3labs/mcp-go/mcp"
)

func main() {
	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: "queue_send",
			Arguments: map[string]any{
				"channel":  "example-queue",
				"body":     "Hello from Go MCP",
				"metadata": "example-metadata",
				"tags":     map[string]any{"env": "dev", "source": "mcp-example"},
			},
		},
	})
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println("Tool: queue_send")
	fmt.Printf("Result: %+v\n", result)
}
import asyncio
import os

from mcp.client.streamable_http import streamablehttp_client
from mcp import ClientSession

KUBEMQ_MCP_URL = os.environ.get("KUBEMQ_MCP_URL", "http://localhost:9090")


async def main():
    async with streamablehttp_client(f"{KUBEMQ_MCP_URL}/mcp") as (read, write, _):
        async with ClientSession(read, write) as session:
            await session.initialize()

            result = await session.call_tool("queue_send", {
                "channel": "example-queue",
                "body": "Hello from Python MCP",
                "metadata": "example-metadata",
                "tags": {"env": "dev", "source": "mcp-example"},
            })

            print(f"Tool: queue_send")
            print(f"IsError: {result.isError}")
            for content in result.content:
                print(f"Result: {content.text}")


if __name__ == "__main__":
    asyncio.run(main())
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";

const KUBEMQ_MCP_URL = process.env.KUBEMQ_MCP_URL || "http://localhost:9090";

async function main() {
  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);

  const result = await client.callTool({
    name: "queue_send",
    arguments: {
      channel: "example-queue",
      body: "Hello from TypeScript MCP",
      metadata: "example-metadata",
      tags: { env: "dev", source: "mcp-example" },
    },
  });
  console.log(JSON.stringify(result, null, 2));

  await client.close();
}

main().catch(console.error);
import io.modelcontextprotocol.sdk.McpClient;
import io.modelcontextprotocol.sdk.client.transport.HttpClientStreamableHttpTransport;
import io.modelcontextprotocol.spec.McpSchema.CallToolRequest;

import java.util.Map;

public class QueueSend {
    public static void main(String[] args) {
        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();

        var result = client.callTool(new CallToolRequest(
            "queue_send",
            Map.of(
                "channel", "example-queue",
                "body", "Hello from Java MCP",
                "metadata", "example-metadata",
                "tags", Map.of("env", "dev", "source", "mcp-example")
            )
        ));
        System.out.println(result);

        client.closeGracefully();
    }
}
using ModelContextProtocol.Client;

class QueueSend
{
    static async Task Main(string[] args)
    {
        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("queue_send", new Dictionary<string, object>
        {
            ["channel"] = "example-queue",
            ["body"] = "Hello from C# MCP",
            ["metadata"] = "example-metadata",
            ["tags"] = new Dictionary<string, string> { ["env"] = "dev", ["source"] = "mcp-example" },
        });

        Console.WriteLine($"Tool: queue_send");
        Console.WriteLine($"Result: {result}");
    }
}
import io.modelcontextprotocol.kotlin.sdk.Implementation
import io.modelcontextprotocol.kotlin.sdk.client.Client
import io.modelcontextprotocol.kotlin.sdk.client.StreamableHttpClientTransport
import io.ktor.client.*
import io.ktor.client.plugins.sse.*
import kotlinx.coroutines.runBlocking

fun main() = runBlocking {
    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("queue_send", mapOf(
        "channel" to "example-queue",
        "body" to "Hello from Kotlin MCP",
        "metadata" to "example-metadata",
        "tags" to mapOf("env" to "dev", "source" to "mcp-example")
    ))

    println("Tool: queue_send")
    println("Result: $result")

    client.close()
    httpClient.close()
}
require "mcp"

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("queue_send", {
  "channel" => "example-queue",
  "body" => "Hello from Ruby MCP",
  "metadata" => "example-metadata",
  "tags" => { "env" => "dev", "source" => "mcp-example" },
})

puts "Tool: queue_send"
puts "Result: #{result}"

client.close
use rmcp::transport::streamable_http::StreamableHttpClientTransport;
use rmcp::service::RunService;
use serde_json::json;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    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("queue_send", json!({
        "channel": "example-queue",
        "body": "Hello from Rust MCP",
        "metadata": "example-metadata",
        "tags": {"env": "dev", "source": "mcp-example"}
    })).await?;

    println!("Tool: queue_send");
    println!("Result: {result:#?}");
    Ok(())
}
import Foundation
import MCP

@main
struct QueueSend {
    static func main() async throws {
        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("queue_send", arguments: [
            "channel": "example-queue",
            "body": "Hello from Swift MCP",
            "metadata": "example-metadata",
            "tags": ["env": "dev", "source": "mcp-example"],
        ])

        print("Tool: queue_send")
        print("Result: \(result)")
    }
}

queue_send and the event-publishing tools work on their own, but command_send and query_send need an active subscriber on the target channel, and the agent_* bridge tools need agents registered with the server. Without them, those calls time out.

Troubleshooting

  • No KubeMQ tools in the host: quit and restart the host fully, not a reload. Claude Desktop writes connection errors to mcp.log in its logs folder.
  • Connection refused: confirm port 9090 is published (docker ps) or the port-forward is running.
  • npx not found: install Node.js, then restart Claude Desktop.
  • Calls fail with -32010: authentication is on; see Authenticated servers.

What's next

Was this page helpful?

On this page