Contributing to mcp-confluent

July 17, 2026 · View on GitHub

:+1::tada: First off, thanks for taking the time to contribute! :tada::+1:

The following is a set of guidelines for contributing to mcp-confluent which is hosted by Confluent Inc. on GitHub. This document lists rules, guidelines, and help getting started, so if you feel something is missing feel free to send a pull request.

Internal Confluent Contributors: Please inform #mcp-confluent before proposing new development to avoid conflicts with work already in progress.

What should I know before I get started?

Contributor Agreement

Required (please follow instructions after making any Pull Requests).

How can I contribute?

Reporting Bugs

Please use Github Issues to report bugs. When filling out an issue report, make sure to copy any related code and stack traces so we can properly debug. We need to be able to reproduce a failing test to be able to fix your issue most of the time, so a custom written failing test is very helpful.

Suggesting Enhancements

Please use Github Issues to suggest enhancements. We are happy to consider any extra functionality or features to the library, as long as they add real and related value to users. Describing your use case and why such an addition helps the user base can help guide the decision to implement it into the library's core.

Pull Requests

  • Include new test cases (either end-to-end or unit tests) with your change.
  • Follow our style guides.
  • Make sure all tests are still passing and the linter does not report any issues.
  • End files with a new line.
  • Make sure your branch is up to date and rebased.
  • Squash extraneous commits unless their history truly adds value to the library.

Developer Guide

Project Structure

/
├── src/                    # Source code
   ├── index.ts                # Main entry point
   ├── cli.ts                  # CLI argument parsing
   ├── env.ts                  # Environment initialization
   ├── env-schema.ts           # Environment variable schema (Zod)
   ├── logger.ts               # Logger configuration
   ├── confluent/              # Confluent integration
   ├── client-manager.ts       # API client management
   ├── schema-registry-helper.ts
   └── tools/
       ├── base-tools.ts        # Base handler class
       ├── tool-registry.ts     # Tool registry
       ├── tool-name.ts         # Tool name enum
       └── handlers/
           ├── billing/         # Billing tools
           ├── catalog/         # Catalog & tag tools
           ├── clusters/        # Cluster tools
           ├── connect/         # Connector tools
           ├── environments/    # Environment tools
           ├── flink/           # Flink SQL, catalog & diagnostics tools
           ├── kafka/           # Kafka topic & message tools
           ├── metrics/         # Telemetry API metrics tools
           ├── schema/          # Schema Registry tools
           ├── search/          # Search tools
           └── tableflow/       # Tableflow topic & catalog tools
   └── mcp/                    # MCP protocol and transport logic
       └── transports/
           ├── http.ts              # HTTP transport
           ├── sse.ts               # SSE transport
           ├── stdio.ts             # STDIO transport
           ├── auth.ts              # Authentication middleware
           └── manager.ts           # Transport manager
├── dist/                   # Compiled output
├── openapi.json            # OpenAPI specification for Confluent Cloud
├── .env.example            # Example environment variables
├── README.md               # This file
└── package.json            # Node.js project metadata and dependencies

Building and Running

This project uses pnpm (Node.js 22.19.0+). If you don't have pnpm yet, install it once -- brew install pnpm on macOS, or any other method on other platforms. The exact pnpm version is pinned in the packageManager field of package.json, and pnpm automatically runs that pinned version (via its built-in package-manager version management), so a recent pnpm install is all you need -- no separate Corepack setup required.

  1. Install Dependencies:

    pnpm install
    
  2. Development Mode (watch for changes):

    pnpm run dev
    

    This command compiles the TypeScript code to JavaScript and automatically rebuilds when changes are detected in the src/ directory.

  3. Production Build (one-time compilation):

    pnpm run build
    
  4. Start the Server:

    pnpm run start
    

Docker

Prerequisites

Before you begin, ensure you have the following installed on your system:

Docker Desktop (or Docker Engine and Docker Compose): https://www.docker.com/products/docker-desktop

Local credentials and config

The container needs a config.yaml (or, on the legacy path, a .env) describing which Confluent Cloud resources to talk to. Keep secrets in a .env and reference them from config.yaml via ${VAR} interpolation — see CONFIGURATION.md. Mount or bind in whichever file(s) you use; docker-compose.yml also accepts inline environment: entries for ad-hoc overrides.

Building and Running with Docker

Here's how to build your Docker image and run it in different modes.

  1. Navigate to your project directory. Open your terminal or command prompt and change to the directory containing the Dockerfile.

    cd /path/to/repo/mcp-confluent
    
  2. Build the Docker image.

    This command creates the mcp-server image based on the Dockerfile in the current directory.

    docker build -t mcp-server .
    
  3. Run the container

    • --rm: Automatically removes the container when it exits. This helps keep your system clean.
    • -i: Keeps STDIN open (runs the server using stdio transport by default).
    • -d: Runs the container in detached mode (in the background).
    • -p 8080:8080: Maps port 8080 on your host machine to port 8080 inside the container. The default HTTP_PORT is 8080; adjust if you've configured a different port.
    docker run --rm -i -d -p 8080:8080 mcp-server
    

    (Optional)

    • -t Transport Mode to enable http transport
    docker run --rm -d -p 8080:8080 mcp-server -t http
    

Building and Running with Docker Compose

  1. Navigate to the project root: Open your terminal or command prompt and change to the directory containing Dockerfile and docker-compose.yml.

    cd /path/to/repo/mcp-confluent
    
  2. Build and run the service: Docker Compose will build the Docker image (if not already built) and start the mcp-server service.

    docker compose up --build
    

    The --build flag ensures that Docker Compose rebuilds the image before starting the container. You can omit this flag on subsequent runs if you haven't changed the Dockerfile or source code.

    The server will be accessible on http://localhost:8080 (or the port specified by server.http.port in config.yaml, or HTTP_PORT if you're on the legacy env-var path).

  3. Stopping the Server To stop the running MCP server and remove the containers, press Ctrl+C in the terminal where docker compose up is running.

    Alternatively, in a new terminal from the project root, you can run:

    docker compose down
    

    This command stops and removes the containers, networks, and volumes created by docker compose up.

Testing

Unit Tests

pnpm run test:unit           # single run (fast, no build)
pnpm run test:unit:watch     # watch mode
pnpm run test:unit:coverage  # with coverage report

(pnpm run test runs unit and integration — use it for the full sweep. pnpm run test:coverage does the same with coverage.)

Unit tests are co-located with source files as *.test.ts. Conventions (naming, stubbing, assertion style, node-deps.ts indirection pattern) live in .claude/rules/unit-tests.md.

Integration Tests

Integration tests exercise the real MCP server as a child process against a real Confluent Cloud account, over both stdio and streamable HTTP transports. They live alongside handlers as *.integration.test.ts and run in a separate Vitest project from unit tests.

Prerequisites
  • A .env.integration file with credentials for the tool groups you want to run. dist/ is rebuilt automatically by test, test:coverage, test:integration, and test:integration:coverage. If you're iterating rapidly on handler code, keep pnpm run dev running in a separate terminal so the build prefix becomes a no-op incremental check.
Option A: Vault-backed setup (team workflow)

If you have Vault CLI access to the team's secrets path:

make setup-test-env                     # fetches secrets from Vault into .env.integration (chmod 600)
pnpm run test:integration -- --tags-filter=@kafka

make setup-test-env fails fast if the Vault CLI isn't on PATH or you're not authed. If an individual Vault field is missing or unreadable, the line is skipped rather than written with an empty value (writing empty would fail the spawned server's env validation); tests that need that credential skip themselves via their predicate gate. See .env.integration.example for the full expected shape.

Option B: Bring your own cluster (no Vault required)
cp .env.integration.example .env.integration
# edit .env.integration — fill in the vars for the tool group(s) you want
pnpm run test:integration -- --tags-filter=@kafka

Each tool group needs a specific credential subset; .env.integration.example annotates which var feeds which tests. Tests whose credentials aren't populated skip themselves with a clear reason — you don't have to fill in every var to run a subset.

Minimum for @kafka tests: KAFKA_API_KEY, KAFKA_API_SECRET. Non-secret config (bootstrap servers, REST endpoint, cluster id) lives in test-fixtures/yaml_configs/integration.yaml and doesn't need to be set in the env file.

OAuth integration tests (one-time setup)

@oauth-tagged tests drive the CCloud OAuth flow (Auth0 sign-in) through a real headless browser via playwright-core. The project depends on playwright-core (API only, no browser auto-download) rather than @playwright/test to keep regular pnpm install lightweight for contributors who never run OAuth tests. Before running OAuth integration tests locally for the first time, install Chromium via the locally-installed playwright-core CLI:

npx playwright-core install chromium

Using playwright-core's own CLI keeps the chromium install version-aligned with the playwright-core devDep, with no fresh registry fetch. This is a one-time step per machine; the binary is cached at ~/Library/Caches/ms-playwright/ (macOS) or ~/.cache/ms-playwright/ (Linux) and is shared across playwright versions. CI runs this automatically in the integration pipeline's prologue, so the CI matrix doesn't need any additional setup.

In addition to a Confluent Cloud API key/secret, @oauth tests need a real Confluent Cloud user account (username/password) for the playwright-driven Auth0 sign-in. With Vault-backed setup (Option A above), these come from confluent-cloud-user. With BYO setup (Option B), set CONFLUENT_CLOUD_USERNAME and CONFLUENT_CLOUD_PASSWORD in .env.integration.

To filter to OAuth-capable tests only:

pnpm run test:integration -- --tags-filter=@oauth

To debug the playwright flow visually (browser window opens, you watch the sign-in happen), set INTEGRATION_TEST_PLAYWRIGHT_HEADLESS=false for that run:

INTEGRATION_TEST_PLAYWRIGHT_HEADLESS=false pnpm run test:integration -- --tags-filter=@oauth

This toggle is scoped to playwright's launch options only; it doesn't touch INTEGRATION_TEST (the general "we are in integration tests" flag), so unrelated test-mode behavior (like skipping the system-browser open() in node-deps.ts) stays intact.

Timing expectations
  • Cold start per test file: ~5-10s (server spawn + MCP handshake + first CCloud round-trip).
  • Per-test CCloud round-trip: 1-30s depending on call and region.
  • vitest.config.ts sets a 60s testTimeout for this project; don't lower it without measuring first.
Other useful commands
  • Filter to one test file: pnpm run test:integration -- --tags-filter=@kafka path/to/my.integration.test.ts.
  • Just the tool-group tag: pnpm run test:integration -- --tags-filter=@kafka.
  • Run both unit and integration in one shot: pnpm run test.
  • Clean up the secrets file: make remove-test-env.

For patterns, conventions, and write-path test lifecycle rules, see .claude/rules/integration-tests.md.

MCP Inspector

For testing MCP servers, you can use MCP Inspector which is an interactive developer tool for testing and debugging MCP servers.

# make sure you've already built the project either in dev mode or by running pnpm run build
npx @modelcontextprotocol/inspector node  $PATH_TO_PROJECT/dist/index.js --env-file $PATH_TO_PROJECT/.env

Local Development with an MCP Client

While the MCP Inspector is useful for ad-hoc tool testing, this setup lets you develop against a real MCP client (Claude Code, Cursor, etc.) with full server log visibility. By default, MCP clients spawn the server as a child process using stdio transport, which makes server logs difficult to observe. Running the server in HTTP mode gives you direct access to logs while the client interacts with it normally.

After building the project (see Building and Running):

1. Start the server in HTTP mode

pnpm run start:http -- --disable-auth

Warning

Never disable authentication in production or when the server is network-accessible.

This starts the server on http://127.0.0.1:8080/mcp (the defaults for HTTP_HOST, HTTP_PORT, and HTTP_MCP_ENDPOINT_PATH) with authentication disabled for local development.

2. Point your MCP client at the running server

Instead of the default stdio configuration, configure your assistant's MCP settings to connect via HTTP. For example, in .mcp.json:

{
  "mcpServers": {
    "confluent-dev": {
      "type": "http",
      "url": "http://127.0.0.1:8080/mcp"
    }
  }
}

This replaces the typical command/args config that spawns a stdio child process.

Important

After restarting the MCP server, you may also need to restart or reconnect your MCP client so it picks up the new server process. For example, in Claude Code use the /mcp command to reconnect.

3. Observe server logs

All server logs are written to stderr via pino and will appear directly in the terminal where you started the server. Set LOG_LEVEL=debug for more verbose output. To capture logs to a file instead:

pnpm run start:http -- --disable-auth 2>server.log

Then tail in a separate terminal:

tail -f server.log

IDE Configuration for Development

The repository includes checked-in configs for debugging the MCP server and connecting MCP clients (Claude Code, GitHub Copilot) to a local dev instance.

Prerequisites: pnpm install, plus a config.yaml (preferred — see CONFIGURATION.md) or a legacy .env populated from .env.example.

Starting the Dev Server

Press F5 in VS Code to build and start the server in HTTP mode with auth disabled. The launch config (.vscode/launch.json) automatically runs pnpm run build, enables pretty logging (LOG_PRETTY=true, LOG_LEVEL=debug), and attaches the debugger. The server starts on http://127.0.0.1:18080/mcp (port 18080 is used to avoid conflicts with the default 8080).

Connecting MCP Clients

Two project-level MCP configs register a confluent-dev server (distinct from the published confluent package that end users configure). Both connect to the locally running HTTP server started by F5:

FileClient
.mcp.jsonClaude Code
.vscode/mcp.jsonGitHub Copilot

After pressing F5, your MCP client should automatically discover the confluent-dev server. If you change .env or source code, restart the debug session (Ctrl+Shift+F5 or stop + F5) and reconnect the MCP client:

  • Claude Code: Restart the session or use /mcp to reconnect. Tool changes are picked up automatically.
  • GitHub Copilot: Open .vscode/mcp.json and click Start or Restart in the code lens above confluent-dev to reconnect Copilot to the server. The code lens shows Running once Copilot is connected and has fetched the current tool list. (Copilot does not automatically detect when an HTTP server restarts.)

Adding a New Tool

Tools are auto-enabled at startup based on which service blocks are present in each connection's resolved ConnectionConfig (whether from YAML or the legacy env-var synthesizer). Each handler declares its requirement via a predicate property pointing at a named export from src/confluent/tools/connection-predicates.ts, and BaseToolHandler derives enabledConnectionIds() and connectionVerdicts() from it. There is no manual enabled-tools list to maintain.

  1. Add a new entry to the ToolName enum in src/confluent/tools/tool-name.ts.
  2. Create a handler class under src/confluent/tools/handlers/<domain>/ (pick the existing domain directory that matches the Confluent service the tool wraps, or add a new one). Extend the domain base class if one exists (e.g., FlinkToolHandler, TableflowToolHandler); otherwise extend BaseToolHandler directly.
  3. On your handler, implement:
    • getToolConfig() — returns the tool name, description, Zod input schema, and annotations (READ_ONLY, CREATE_UPDATE, DESTRUCTIVE). For tools with no parameters, pass inputSchema: {}, not an omitted field. Every input field needs a .describe() call.
    • handle() — the runtime implementation.
    • predicate — a readonly predicate = <namedExport> referencing one of the named predicates in connection-predicates.ts. If no existing predicate fits, add one there first (with a test in connection-predicates.test.ts and a row in EXPECTED_PREDICATES inside tool-registry.test.ts), then reference it from the handler. Do not compose predicates inline at the handler use site — combinators like allOf and widenForOAuth are for defining new named exports, not handler-side glue.
  4. Register the handler in the ToolHandlerRegistry.handlers map in src/confluent/tools/tool-registry.ts.
  5. If the tool calls a Confluent Cloud REST endpoint whose path or response shape isn't already in openapi.json, add it and regenerate the typed client — see Generating Types below.

Project-specific conventions for handler structure, input-schema rules, and the predicate-selection checklist live in .claude/rules/tool-handlers.md.

Generating Types

openapi.json is a vendored snapshot of the public Confluent Cloud API spec. It drives the typed openapi-fetch clients in src/confluent/client-manager.ts, and only needs changes when a new handler calls a Confluent Cloud REST endpoint whose path or response shape isn't already described there (for example, a preview API the snapshot predates). Handlers that wrap existing typed endpoints, or that go through the Kafka / Schema Registry SDKs, don't require a spec edit.

If you edit openapi.json, regenerate src/confluent/openapi-schema.d.ts:

pnpm run generate:openapi-types

Commit both files together so downstream contributors see the same types the vendored spec describes.

The script passes --empty-objects-unknown to work around openapi-typescript#1474 (a bug since v7.5.2 with allOf + required).