KubeMQ
AiwayMCP

Getting Started with MCP

Run KubeMQ, connect Claude Desktop, complete the MCP JSON-RPC handshake, and call your first tool in minutes.

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

  • A running kubemq-server with its shared HTTP server on port 9090. Docker is the quickest way to get one.
  • For Claude Desktop: a current Claude Desktop install.
  • 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.

docker run -d \  --name kubemq \  -p 9090:9090 \  -p 50000:50000 \  -e KUBEMQ_TOKEN=YOUR_LICENSE_KEY \  europe-docker.pkg.dev/kubemq/images/kubemq:next

Port 9090 is the shared HTTP server that hosts MCP alongside the REST, A2A, and CloudEvents connectors. Port 50000 is the gRPC port used by native SDKs and by the command/query subscribers behind some MCP tools.

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

docker run -d -p 9090:9090 -p 50000:50000 -e KUBEMQ_TOKEN=YOUR_LICENSE_KEY -e CONNECTORSMCP_ENABLE=false europe-docker.pkg.dev/kubemq/images/kubemq:next

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 Claude Desktop

Point Claude Desktop at KubeMQ's MCP endpoint by adding a server entry to claude_desktop_config.json (Claude Desktop → Settings → Developer → Edit Config):

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

Restart Claude Desktop. It performs the initialize handshake over the Streamable HTTP transport, discovers all 15 KubeMQ tools, and makes them available in the conversation. For an authenticated server, in-depth transport options, and a generic (non-Claude) JSON-RPC client walkthrough, see Client setup.

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.

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 returns 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 with the result text and isError: false:

{
  "jsonrpc": "2.0",
  "id": 3,
  "result": {
    "content": [{ "type": "text", "text": "Message sent successfully to queue 'my-queue'" }],
    "isError": false
  }
}

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.

What's next

Was this page helpful?

On this page