Skip to main content
Glama
nurawiguna
by nurawiguna

MCP Harbor

License: MIT TypeScript Node.js

This is a fork of nomagicln/mcp-harbor, the original MCP server for Harbor. It has been extended with additional configuration options and security hardening around the SSE transport (see What's Different From the Original). All credit for the original implementation goes to the upstream author; see LICENSE for the MIT license and copyright notice.

MCP Harbor is a Node.js application that provides a Model Context Protocol (MCP) server for interacting with Harbor container registry.

Table of Contents

Related MCP server: Harbor MCP Server

What's Different From the Original

This fork keeps all the original Harbor MCP tools and behavior, and adds:

  • TLS verification is secure by default: the original disabled TLS certificate validation for the whole process unconditionally. It is now opt-in via --insecure-tls / HARBOR_INSECURE_TLS, only relevant when HARBOR_URL uses https:// with a self-signed/internal certificate.

  • Configurable SSE bind address: the SSE server used to always bind 0.0.0.0 (all network interfaces). It now defaults to 127.0.0.1 and is configurable via --sse-host / HARBOR_SSE_HOST.

  • Bearer token authentication for SSE: /sse and /messages can now require an Authorization: Bearer <token> header via --sse-auth-token / HARBOR_SSE_AUTH_TOKEN, since the SSE transport otherwise has no authentication of its own.

  • Correct multi-client SSE sessions: SSE connections are now tracked per session instead of a single shared/global connection, so concurrent clients no longer risk having their messages cross-routed.

  • No more crash on a second SSE connection: the original reused a single MCP Server instance across every connection, which throws once a second client connects (the SDK only allows one transport per Server instance) — and since it was unhandled, it took the whole process down. Every connection now gets its own Server instance and errors are caught per-request instead of crashing the server.

  • Streamable HTTP support (/mcp): the original only implemented the deprecated HTTP+SSE transport (/sse + /messages). Most current MCP clients (2025-03-26+ spec) expect the newer Streamable HTTP transport instead and get a 404 against /sse-only servers. This fork adds a /mcp endpoint supporting both, following the SDK's own backwards-compatible server pattern.

Features

  • MCP Server: Exposes tools for interacting with Harbor through the Model Context Protocol

  • Harbor Operations: Supports operations for projects, repositories, tags, and Helm charts

  • TypeScript: Written in TypeScript for better type safety and developer experience

  • Automated Tests: Comprehensive test suite for reliable functionality

Prerequisites

Before installing MCP Harbor, ensure you have:

  • Node.js 18.x or higher

  • npm 8.x or higher

  • Access to a Harbor registry instance

  • Git (for cloning the repository)

Installation

  1. Clone the repository:

    git clone https://github.com/nurawiguna/mcp-harbor.git
  2. Navigate to the project directory:

    cd mcp-harbor
  3. Install dependencies:

    npm install
  4. Build the project:

    npm run build

Usage

Transport Modes

MCP Harbor supports two transport modes:

  • stdio (default): the MCP client (e.g. Claude Desktop, Cursor) spawns mcp-harbor directly as a subprocess and communicates over stdin/stdout. No network port is opened at all, so no SSE-related configuration is needed. Use this when the client and mcp-harbor run on the same machine.

  • HTTP (--sse, or HARBOR_SSE=true in .env — the flag name is --sse for historical reasons, but it now enables both transports below): runs an HTTP server so MCP clients on a different machine/process can connect over the network, exposing both /mcp (Streamable HTTP, current spec) and /sse (deprecated HTTP+SSE) — see Connecting to the SSE Endpoint for which one to use. This must be explicitly turned on — plain npm start / node dist/app.js with no flags always runs stdio mode, even if HARBOR_SSE_HOST/HARBOR_SSE_AUTH_TOKEN are set. Because enabling it opens a network port, see Securing the SSE Transport below before enabling it.

Command Line Arguments

The application accepts the following command line arguments:

Options:
  --url            Harbor API URL (the remote Harbor server mcp-harbor
                    connects to)                        [string] [required]
  --username       Harbor username                      [string] [required]
  --password       Harbor password                      [string] [required]
  --insecure-tls   Disable TLS certificate verification when connecting to
                    the Harbor URL over HTTPS. Only for trusted internal
                    networks with a self-signed certificate.
                                                 [boolean] [default: false]
  --debug          Enable debug mode                [boolean] [default: false]
  --sse            Enable SSE transport               [boolean] [default: false]
  --port           Port for the local SSE server to listen on
                                                      [number] [default: 3000]
  --sse-host       Host/interface the local SSE server binds to (this
                    machine, not the Harbor server)
                                                 [string] [default: "127.0.0.1"]
  --sse-auth-token Bearer token required to authenticate SSE connections to
                    this MCP server                                 [string]
  --help           Show help                                       [boolean]

Environment Variables

Instead of command line arguments, you can also use environment variables. Create a .env file in the root directory (see .env.example):

# Harbor API Configuration
# HARBOR_URL is the remote Harbor server this app connects OUT to.
# Works with either http:// or https:// (matches the Harbor server's own setup).
# Must point at the Harbor REST API base path, i.e. it needs a /api/v2.0
# suffix - just the bare host (e.g. https://harbor.example.com) usually hits
# Harbor's web UI instead of its JSON API and every tool call fails/returns
# empty results. If you forget it, mcp-harbor appends /api/v2.0 for you.
HARBOR_URL=https://harbor.example.com/api/v2.0
HARBOR_USERNAME=admin
HARBOR_PASSWORD=Harbor12345

# Only set this to true if HARBOR_URL is https:// with a self-signed/internal
# certificate. Leave it false (default) whenever the certificate is trusted,
# or when HARBOR_URL is http:// (in which case it has no effect anyway).
HARBOR_INSECURE_TLS=false

# Debug Mode (true/false)
DEBUG=false

# --- Local SSE server ---
# These configure mcp-harbor's OWN inbound server, i.e. where MCP clients
# (Claude, Cursor, etc.) connect TO this app. Unrelated to HARBOR_URL above.

# Enables SSE mode (equivalent to the --sse flag). Without this set to true
# (in either this file or the actual environment) and without --sse passed on
# the command line, mcp-harbor runs in stdio mode instead and never opens a
# port at all - "npm start" alone does NOT turn SSE on by itself.
HARBOR_SSE=true

# Host/interface this app listens on (only relevant when SSE is enabled above).
# Keep 127.0.0.1 unless this server sits behind a trusted reverse proxy/firewall
# that restricts who can reach it.
HARBOR_SSE_HOST=127.0.0.1
# Bearer token required on the Authorization header for /sse and /messages.
# Required in practice whenever HARBOR_SSE_HOST is anything other than 127.0.0.1.
HARBOR_SSE_AUTH_TOKEN=change-me-to-a-long-random-value

Securing the SSE Transport

The SSE transport has no authentication of its own, so treat these as required whenever mcp-harbor is reachable by more than just your own machine:

  1. Keep HARBOR_SSE_HOST at 127.0.0.1 unless a client genuinely needs to connect from another host.

  2. If it must be reachable from other hosts, set a long random HARBOR_SSE_AUTH_TOKEN (e.g. openssl rand -hex 32) and put a firewall rule in front of the port restricting which hosts can reach it.

  3. Prefer a trusted reverse proxy with TLS termination in front of the SSE port if it is exposed beyond localhost, since the SSE server itself speaks plain HTTP.

Connecting to the SSE Endpoint

When --sse is enabled, this server exposes two endpoints at the same time, for two different MCP protocol versions. Which one your MCP client actually uses depends on the client, not on you — most current-generation clients (2025+) speak Streamable HTTP; some older clients/SDKs only speak the deprecated SSE transport. Both work here; pick the URL that matches what your client expects:

Endpoint

Protocol

Methods

Use when

http://<host>:<port>/mcp

Streamable HTTP (current spec)

GET, POST, DELETE

Default choice — most clients today (2025-03-26+ spec)

http://<host>:<port>/sse

HTTP+SSE (deprecated, 2024-11-05 spec)

GET (+ internal POST /messages)

Only if your client specifically requires the older SSE transport

  • <host> / <port> are whatever HARBOR_SSE_HOST / --port are set to (default 127.0.0.1:3000).

  • For /sse, /messages is a separate, internal endpoint the server tells the client about after the connection is established (it includes a sessionId query parameter). You never configure /messages directly — the client library handles that handshake automatically.

  • If you point a client at /sse and it logs something like SSE connection established followed immediately by a 404/Not Found error, that almost always means the client actually speaks Streamable HTTP and tried to POST back to the same URL. Switch the client's URL to /mcp instead.

Examples for HARBOR_SSE_HOST=0.0.0.0, default port, using the recommended /mcp endpoint:

Where the client runs

URL to use

Same machine as mcp-harbor

http://127.0.0.1:3000/mcp

A different machine on the network

http://<mcp-harbor-host-ip>:3000/mcp

If HARBOR_SSE_AUTH_TOKEN is set, the client must send it as a bearer token on every request to whichever endpoint it uses. For an MCP client config that supports a remote/URL-based server entry, this typically looks like:

{
  "mcpServers": {
    "harbor": {
      "url": "http://<mcp-harbor-host-ip>:3000/mcp",
      "headers": {
        "Authorization": "Bearer <your HARBOR_SSE_AUTH_TOKEN>"
      }
    }
  }
}

The exact field names (url, headers, etc.) vary by MCP client — check that client's docs for how it configures a remote MCP server, and whether it lets you pick the transport/protocol explicitly.

Running in Production

Build once, then run the compiled output directly — no TypeScript tooling needed at runtime:

npm run build
npm start -- --url https://harbor.example.com --username admin --password ***
# or, with a .env file in place (see Environment Variables above):
npm start

npm start just runs node dist/app.js; any flags after -- are forwarded to it. You can also invoke node dist/app.js directly, or install it as a global command:

npm install -g .
mcp-harbor --url https://harbor.example.com --username admin --password ***

npm start on its own does NOT enable SSE mode. It runs in stdio mode by default (no port opened at all), regardless of HARBOR_SSE_HOST/HARBOR_SSE_AUTH_TOKEN being set — those only configure SSE, they don't turn it on. If you hit /sse and get a connection error or 404, this is almost always why. To actually enable SSE, either:

npm start -- --sse

or set HARBOR_SSE=true in your .env (see .env.example) and then plain npm start is enough. Either way, check the startup log for [MCP Server] Using SSE transport / SSE server running on ... to confirm it actually turned on before pointing a client at it.

For SSE mode in production, the process needs to keep running in the background (it doesn't daemonize itself). Use a process manager such as pm2 or a systemd unit, for example:

pm2 start dist/app.js --name mcp-harbor -- --sse --sse-host 127.0.0.1 --sse-auth-token "$HARBOR_SSE_AUTH_TOKEN"

See Securing the SSE Transport before exposing SSE mode beyond localhost.

Using It From an MCP Client (e.g. Claude Desktop)

The most common way to run this in "production" is not from a terminal at all — the MCP client spawns it for you via stdio (see Transport Modes). Point the client at the built binary and pass credentials as arguments or environment variables, e.g. in Claude Desktop's claude_desktop_config.json:

{
  "mcpServers": {
    "harbor": {
      "command": "node",
      "args": ["/absolute/path/to/mcp-harbor/dist/app.js"],
      "env": {
        "HARBOR_URL": "https://harbor.example.com",
        "HARBOR_USERNAME": "admin",
        "HARBOR_PASSWORD": "***"
      }
    }
  }
}

If installed globally (npm install -g .), you can use "command": "mcp-harbor" with "args": [] instead.

MCP Tools

The MCP server exposes the following tools:

Tool Name

Description

Parameters

list_projects

List all projects in Harbor

None

get_project

Get project details by ID

projectId: string

create_project

Create a new project

project_name: string, metadata?: object

delete_project

Delete a project

projectId: string

list_repositories

List repositories in a project

projectId: string

delete_repository

Delete a repository

projectId: string, repositoryName: string

list_tags

List tags in a repository

projectId: string, repositoryName: string

delete_tag

Delete a tag

projectId: string, repositoryName: string, tag: string

list_charts

List Helm charts

projectId: string

list_chart_versions

List chart versions

projectId: string, chartName: string

delete_chart

Delete chart version

projectId: string, chartName: string, version: string

Development

Running in Development Mode

Runs the TypeScript source directly (via ts-node's ESM loader), no npm run build needed:

npm run dev -- --url https://harbor.example.com --username admin --password ***
# or, with a .env file in place:
npm run dev

Running Tests

# Run all tests
npm test

# Run tests with coverage
npm run test:coverage

Project Structure

mcp-harbor
├── src
│   ├── app.ts                 # Main application entry point (MCP server)
│   ├── definitions
│   │   └── tool.definitions.ts # Tool definitions for MCP
│   ├── services
│   │   └── harbor.service.ts  # Harbor service implementation
│   └── types
│       └── index.ts           # TypeScript type definitions
├── test
│   └── harbor.test.ts         # Tests for Harbor service
├── .env.example              # Example environment variables
├── .gitignore               # Git ignore file
├── .eslintrc.json           # ESLint configuration
├── package.json            # Project dependencies
├── jest.config.js           # Jest configuration
├── tsconfig.test.json      # TypeScript configuration for tests
├── tsconfig.json          # TypeScript configuration
├── LICENSE                # Project license
└── README.md             # Project documentation

Troubleshooting

Common Issues

  1. Connection Failed

    Error: Unable to connect to Harbor instance
    • Verify HARBOR_URL is correct and accessible

    • Check network connectivity

    • Ensure Harbor instance is running

  2. Authentication Failed

    Error: Invalid credentials
    • Verify HARBOR_USERNAME and HARBOR_PASSWORD are correct

    • Check if user has required permissions

  3. Tool calls fail with X.map is not a function, or return an empty {} for a project/repo that exists

    This means requests are reaching Harbor's web UI instead of its REST API — almost always because HARBOR_URL is missing the /api/v2.0 path (e.g. https://harbor.example.com instead of https://harbor.example.com/api/v2.0). mcp-harbor auto-appends /api/v2.0 if it's missing, but double check HARBOR_URL doesn't already point somewhere else unexpected (e.g. a path-based reverse proxy) if this still happens after upgrading.

  4. Build Errors

    Error: TypeScript compilation failed
    • Run npm install to ensure all dependencies are installed

    • Check TypeScript version compatibility

    • Clear the dist directory and rebuild

  5. [MCP Error] SyntaxError: Unexpected end of JSON input right after npm start / npm run dev

    This is not a crash — the process keeps running. It happens because the default transport (stdio) expects every line on stdin to be a complete JSON-RPC message. If you run npm start/npm run dev directly in a terminal and press Enter (sending an empty line) or type plain text, it can't be parsed as JSON and this gets logged.

    stdio mode isn't meant to be typed into manually — it's meant to be spawned by an MCP client (see Using It From an MCP Client). To sanity-check it from a terminal instead, either:

    • Pipe in a real JSON-RPC message: echo '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}' | npm start

    • Or use --sse mode and test with curl/a browser against the SSE endpoint, which is easier to interact with manually.

  6. npm start alone doesn't open any port at all

    npm start just runs node dist/app.js with no flags, which defaults to stdio mode. Pass --sse (npm start -- --sse) or set HARBOR_SSE=true in .env to actually enable the HTTP server. Check the startup log for [MCP Server] Using SSE transport / SSE server running on ... to confirm it turned on before pointing a client at it.

  7. MCP client logs SSE connection established (or similar) and then immediately gets a 404 Not Found

    Your MCP client tried the deprecated /sse transport first, got connected, then attempted to POST a follow-up request back to the same URL — which only exists as a /mcp endpoint here. This means the client actually speaks the newer Streamable HTTP protocol. Point it at /mcp instead of /sse (see Connecting to the SSE Endpoint).

Debug Mode

Enable debug mode by using the --debug flag or setting:

DEBUG=true

Support

For additional help:

  1. Review the application logs

License

This project is licensed under the MIT License - see the LICENSE file for details.

Related MCP Connectors

Related MCP Servers