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.
-
Install Dependencies:
pnpm install -
Development Mode (watch for changes):
pnpm run devThis command compiles the TypeScript code to JavaScript and automatically rebuilds when changes are detected in the
src/directory. -
Production Build (one-time compilation):
pnpm run build -
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.
-
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 -
Build the Docker image.
This command creates the
mcp-serverimage based on theDockerfilein the current directory.docker build -t mcp-server . -
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)
-tTransport Mode to enable http transport
docker run --rm -d -p 8080:8080 mcp-server -t http
Building and Running with Docker Compose
-
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 -
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 --buildThe --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.portinconfig.yaml, orHTTP_PORTif you're on the legacy env-var path). -
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 downThis 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.integrationfile with credentials for the tool groups you want to run.dist/is rebuilt automatically bytest,test:coverage,test:integration, andtest:integration:coverage. If you're iterating rapidly on handler code, keeppnpm run devrunning 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.tssets 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:
| File | Client |
|---|---|
.mcp.json | Claude Code |
.vscode/mcp.json | GitHub 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
/mcpto reconnect. Tool changes are picked up automatically. - GitHub Copilot: Open
.vscode/mcp.jsonand click Start or Restart in the code lens aboveconfluent-devto 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.
- Add a new entry to the
ToolNameenum insrc/confluent/tools/tool-name.ts. - 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 extendBaseToolHandlerdirectly. - 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, passinputSchema: {}, not an omitted field. Every input field needs a.describe()call.handle()— the runtime implementation.predicate— areadonly predicate = <namedExport>referencing one of the named predicates inconnection-predicates.ts. If no existing predicate fits, add one there first (with a test inconnection-predicates.test.tsand a row inEXPECTED_PREDICATESinsidetool-registry.test.ts), then reference it from the handler. Do not compose predicates inline at the handler use site — combinators likeallOfandwidenForOAuthare for defining new named exports, not handler-side glue.
- Register the handler in the
ToolHandlerRegistry.handlersmap insrc/confluent/tools/tool-registry.ts. - 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-unknownto work around openapi-typescript#1474 (a bug since v7.5.2 withallOf+required).