KubeMQ
AiwayMCPGuides

Client Setup

Connect any MCP client to KubeMQ — Claude Desktop config, generic JSON-RPC over HTTP, session headers, and the official SDK in nine languages.

The MCP connector speaks the Model Context Protocol (version 2025-11-25) as JSON-RPC 2.0 over the Streamable HTTP transport at POST /mcp. Any compliant MCP client — Claude Desktop, an official MCP SDK, or a hand-rolled JSON-RPC caller — connects the same way: point it at the endpoint, run the initialize handshake, and start calling tools. This guide covers each path in depth. For the 5-minute quick start, see Getting started.

Overview

Every MCP client connects to a single URL and goes through the same three-step lifecycle before any tool call:

  1. initialize — negotiate the protocol version and open a session.
  2. notifications/initialized — acknowledge the handshake.
  3. tools/list / tools/call — discover and invoke tools.

The official SDKs and Claude Desktop perform steps 1 and 2 automatically and track the session ID for you. With raw curl you drive each step yourself.

How it works

The Streamable HTTP transport carries every JSON-RPC message as a standalone POST /mcp. The server returns an MCP-Session-Id on initialize, and the client echoes it on all subsequent requests. A separate GET /mcp opens a stateless keepalive stream that emits a : keepalive comment every 30 seconds.

One session, established once, carries every later request via the MCP-Session-Id header.

Claude Desktop

Claude Desktop connects to KubeMQ over the HTTP transport. Add a server entry to claude_desktop_config.json (Claude Desktop → Settings → Developer → Edit Config), then restart Claude Desktop.

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

On restart, Claude Desktop runs the initialize handshake, discovers all 15 KubeMQ tools, and exposes them in the conversation. The url must include the /mcp path — it is the only required field for an unauthenticated server.

Authenticated servers

If the shared HTTP server has JWT auth enabled, add an Authorization header so Claude Desktop sends a Bearer token with every request:

claude_desktop_config.json
{
  "mcpServers": {
    "kubemq": {
      "url": "https://kubemq.example.com/mcp",
      "headers": {
        "Authorization": "Bearer <jwt-token>"
      }
    }
  }
}

Use https:// for any remote endpoint. See Authentication for how the connector validates the token and returns -32010 on failure.

The MCP connector also validates the request Origin against McpConfig.TrustedOrigins (default ["auto"], which matches localhost, 127.0.0.1, ::1, [::1], 0.0.0.0, and the server's bind address). When connecting from a remote host or a custom origin, add it to the trusted-origins list in Configuration or origin validation will reject the connection.

Generic JSON-RPC client

Any HTTP client can speak MCP directly — no SDK required. You manage the handshake, the MCP-Session-Id header, and request IDs yourself. This is the lowest-level path and mirrors exactly what an SDK does internally.

Open a session with initialize

POST an initialize request announcing the protocol version (2025-11-25) and your client identity:

terminal
curl -i -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": "my-agent", "version": "1.0.0" }
    }
  }'

Capture the session ID from result._meta.sessionId in the body — it is also returned in the MCP-Session-Id response header (use -i to see headers):

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

Acknowledge with notifications/initialized

Send the notifications/initialized notification, carrying the session ID. It has no id, so the server replies with HTTP 200 OK and body {"jsonrpc":"2.0","result":null,"id":null}:

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"
  }'

Call tools with the session header

Every later request — tools/list, tools/call, ping — includes the MCP-Session-Id header:

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/call",
    "params": {
      "name": "queue_send",
      "arguments": { "channel": "my-queue", "body": "Hello from MCP" }
    }
  }'

Required headers

HeaderDirectionWhenDescription
Content-TypeRequestAlwaysMust be application/json.
MCP-Session-IdRequestAfter initializeThe session ID returned by the handshake.
MCP-Session-IdResponseAlwaysEchoed back by the server.
MCP-Protocol-VersionResponseAlwaysThe protocol version, 2025-11-25.
AuthorizationRequestWhen auth is onBearer <jwt-token> for authenticated servers.

A stateless GET /mcp opens an SSE keepalive stream that emits a : keepalive comment every 30 seconds and carries no MCP messages — it only keeps an idle connection alive and closes when the client disconnects. See Session management for the full lifecycle and batching.

Official MCP SDKs

Each official MCP SDK wraps the Streamable HTTP transport: it performs the initialize handshake, sends notifications/initialized, and tracks the session ID automatically — you construct the transport with the endpoint URL and call tools. All examples read KUBEMQ_MCP_URL (default http://localhost:9090) and append /mcp.

# Raw JSON-RPC: initialize, then call a tool with the returned session ID.
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/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)")
    }
}

For an authenticated server, pass the JWT through the SDK's transport options (an Authorization: Bearer <token> header) — the same mechanism Claude Desktop uses. The standard environment variables are KUBEMQ_MCP_URL, KUBEMQ_MCP_TIMEOUT (client-side HTTP timeout, default 30s), and KUBEMQ_MCP_AUTH_TOKEN.

Was this page helpful?

On this page