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-serverwith its shared HTTP server on port9090. 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
curlpath 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:nextPort 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:nextThe 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):
{
"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:
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:
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):
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:
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.closeuse 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
Configuration
McpConfig fields, the tool timeout, trusted origins, and the enable env var.
Tools overview
The 15-tool map — 11 core operations plus 4 agent-bridge tools.
Client setup
In-depth Claude Desktop config, generic JSON-RPC clients, and session headers.
Session management
The full handshake, MCP-Session-Id tracking, batching, and GET /mcp keepalive.
Was this page helpful?
MCP
Expose KubeMQ messaging and the A2A agent bridge as Model Context Protocol tools that Claude and other AI models can discover and invoke.
Configuration
Configure the KubeMQ MCP connector — enabled by default, tool timeout, trusted origins, and the disable env var, with TOML, environment variables, and Docker.