# Getting Started with MCP (/aiway/mcp/getting-started)



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 [#prerequisites]

* A running `kubemq-server` with its [shared HTTP server](/connectors/concepts/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 [#enable--disable]

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

<RunKubeMQ ports="[9090, 50000]" />

Port `9090` is the [shared HTTP server](/connectors/concepts/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`:

<RunKubeMQ variant="disable" ports="[9090, 50000]" env="{ CONNECTORSMCP_ENABLE: 'false' }" />

<Callout type="info">
  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](/connectors/concepts/shared-http-server#enable-model-on-by-default). Set it to `false` to disable; never to `true` to enable.
</Callout>

## How it works [#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.

<Mermaid
  chart="`
sequenceDiagram
participant C as MCP client
participant M as MCP connector (:9090)
participant K as KubeMQ broker
C->>M: POST /mcp · initialize
M-->>C: capabilities + MCP-Session-Id
C->>M: POST /mcp · notifications/initialized
M-->>C: 200 OK · result: null
C->>M: POST /mcp · tools/list
M-->>C: 15 tool definitions
C->>M: POST /mcp · tools/call (queue_send)
M->>K: send to channel
K-->>M: ack
M-->>C: content[] + isError
`"
/>

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

## Connect Claude Desktop [#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):

```json title="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](/aiway/mcp/guides/client-setup).

## The handshake by hand [#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.

<Steps>
  <Step>
    ### Initialize a session [#initialize-a-session]

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

    ```bash title="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:

    ```json
    {
      "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.
  </Step>

  <Step>
    ### Acknowledge the handshake [#acknowledge-the-handshake]

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

    ```bash title="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.
  </Step>

  <Step>
    ### List the available tools [#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):

    ```bash title="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](/aiway/mcp/tools) maps every tool to its KubeMQ operation.
  </Step>

  <Step>
    ### Call your first tool [#call-your-first-tool]

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

    ```bash title="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`:

    ```json
    {
      "jsonrpc": "2.0",
      "id": 3,
      "result": {
        "content": [{ "type": "text", "text": "Message sent successfully to queue 'my-queue'" }],
        "isError": false
      }
    }
    ```
  </Step>
</Steps>

## The same call from an SDK [#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`.

<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 '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" }
          }
        }
      }'
    ```
  </Tab>

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

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

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

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

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

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

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

  <Tab value="Rust">
    ```rust
    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(())
    }
    ```
  </Tab>

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

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

## What's next [#whats-next]

<Cards>
  <Card title="Configuration" href="/aiway/mcp/configuration" description="McpConfig fields, the tool timeout, trusted origins, and the enable env var." />

  <Card title="Tools overview" href="/aiway/mcp/tools" description="The 15-tool map — 11 core operations plus 4 agent-bridge tools." />

  <Card title="Client setup" href="/aiway/mcp/guides/client-setup" description="In-depth Claude Desktop config, generic JSON-RPC clients, and session headers." />

  <Card title="Session management" href="/aiway/mcp/guides/session-management" description="The full handshake, MCP-Session-Id tracking, batching, and GET /mcp keepalive." />
</Cards>
