JSPM

@mcp-z/mcp-discovery

0.1.2
  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 20
  • Score
    100M100P100Q72867F
  • License MIT

Zero-configuration service discovery for MCP servers via mDNS. Supports HTTP and WebSocket transports.

Package Exports

  • @mcp-z/mcp-discovery
  • @mcp-z/mcp-discovery/package.json
  • @mcp-z/mcp-discovery/transports/websocket-server.js

Readme

@mcp-z/mcp-discovery

npm License: MIT Node npm downloads

Zero-configuration service discovery for MCP servers via mDNS. Supports HTTP and WebSocket transports.


Why This Exists

When running a cluster of MCP servers, you often need both public and private interfaces:

  • Public interface (/mcp) - External access, secured via OAuth/authentication
  • Private interface (/internal/mcp) - Internal cluster communication, discovered via mDNS

This library enables zero-configuration discovery of private MCP endpoints within your local network, optionally secured with PSK (pre-shared key) authentication.

Use Cases:

  • MCP server clusters that need to discover each other
  • Internal service mesh communication
  • Development/staging environments
  • Local network deployments where manual configuration is impractical

⚠️ Security Notice: This library is designed for trusted local networks (same LAN/VLAN). Do not expose discovery or PSK-authenticated endpoints to untrusted networks. If you're unsure about securing your MCP servers or what's appropriate for your situation, consult a security expert.


Features

  • 🔍 Service Discovery - Find MCP servers on your local network via mDNS/DNS-SD
  • 📢 Service Advertisement - Broadcast your MCP server's availability
  • 🌐 HTTP & WebSocket - Support for both transport types
  • 🔒 Optional PSK Authentication - Pre-shared key helpers for secure communication
  • 📦 Version Selection - Filter services by version compatibility
  • 🔌 MCP SDK Integration - Works seamlessly with official @modelcontextprotocol/sdk
  • 📘 TypeScript - Full type definitions included
  • 🎯 Zero Configuration - Works out-of-the-box on macOS, Linux (Avahi), Windows (Bonjour)

Install

# npm
npm install @mcp-z/mcp-discovery

# yarn
yarn add @mcp-z/mcp-discovery

# pnpm
pnpm add @mcp-z/mcp-discovery

Quick Start

Server: Advertise Your Service

import { advertiseService } from '@mcp-z/mcp-discovery';

const stopAdvertising = advertiseService({
  cluster: 'my-cluster',
  serviceName: 'gmail',
  transport: 'http',  // or 'ws'
  port: 8080,
  path: '/internal/mcp',  // Use /internal/mcp for private cluster communication
  version: '1.0.0',
  node: 'http:prod',  // Unique identifier for this server instance
  auth: 'psk'
});

// Stop advertising when shutting down
process.on('SIGINT', () => {
  stopAdvertising();
  process.exit(0);
});

Client: Discover and Connect

import { discoverServices, toBaseUrl } from '@mcp-z/mcp-discovery';
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';

// 1. Discover services
const services = await discoverServices({
  cluster: 'my-cluster',
  serviceName: 'gmail',
  timeoutMs: 1500
});

// 2. Select service (e.g., highest version)
const service = services
  .sort((a, b) => (b.version || '0').localeCompare(a.version || '0', undefined, { numeric: true }))
  [0];

// 3. Connect with MCP SDK
const url = toBaseUrl(service);
const transport = new StreamableHTTPClientTransport(new URL(url));
const client = new Client({ name: 'my-app', version: '1.0.0' }, { capabilities: {} });

await client.connect(transport);

// 4. Use the client
const tools = await client.listTools();
console.log('Available tools:', tools);

Quick Win (30 seconds)

Want to see mDNS discovery working immediately?

# Clone and install
git clone https://github.com/mcp-z/mcp-discovery.git
cd mcp-discovery
npm install

# Run the integration test - watch discovery happen in real-time!
npm test

✅ You'll see services being advertised and discovered via mDNS!


API Reference

advertiseService(opts)

Broadcast your MCP service on the local network.

Options:

  • cluster (string) - Cluster name (e.g., 'production', 'dev')
  • serviceName (string) - Service identifier (e.g., 'gmail', 'sheets')
  • transport ('http' | 'ws') - Transport protocol
  • port (number) - Port number
  • path (string, optional) - URL path (default: '/mcp')
  • version (string, optional) - Service version (default: '1.0.0')
  • node (string) - Unique node identifier (e.g., 'http:prod-01')
  • auth ('psk' | 'mtls', optional) - Authentication type (default: 'psk')

Returns: Function to stop advertising

discoverServices(opts)

Find MCP services on the local network.

Options:

  • cluster (string) - Cluster to search in
  • serviceName (string) - Service to find
  • timeoutMs (number, optional) - Discovery timeout (default: 1500ms)

Returns: Promise<ServiceInfo[]>

ServiceInfo:

{
  cluster: string;
  serviceName: string;
  transport: 'http' | 'ws';
  host: string;
  port: number;
  path: string;
  version?: string;
  node: string;
  auth: 'psk' | 'mtls';
}

toBaseUrl(service)

Convert a discovered service to a connection URL.

const url = toBaseUrl(service);
// 'http://hostname.local:8080/mcp' or 'ws://hostname.local:8080/mcp'

Transport Support

HTTP

Use with official SDK's StreamableHTTPClientTransport:

import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';

const transport = new StreamableHTTPClientTransport(new URL(toBaseUrl(service)));

WebSocket

Client: Use SDK's WebSocketClientTransport (SDK 1.18+)

import { WebSocketClientTransport } from '@modelcontextprotocol/sdk/client/websocket.js';

const transport = new WebSocketClientTransport(new URL(toBaseUrl(service)));

Server: Use included WebSocketServerTransport

import { WebSocketServerTransport } from '@mcp-z/mcp-discovery/transports/websocket-server.js';
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { WebSocketServer } from 'ws';

const wss = new WebSocketServer({ port: 8080 });
wss.on('connection', async (ws) => {
  const transport = new WebSocketServerTransport(ws);
  const server = new Server({ name: 'my-server', version: '1.0.0' }, { capabilities: {} });
  await server.connect(transport);
});

Service Selection

Filter by Transport

const httpServices = services.filter(s => s.transport === 'http');
const wsServices = services.filter(s => s.transport === 'ws');

Select by Version

// Highest version
const latest = services
  .sort((a, b) => (b.version || '0').localeCompare(a.version || '0', undefined, { numeric: true }))
  [0];

// Semantic version range (using semver package)
import semver from 'semver';
const compatible = services.filter(s => s.version && semver.satisfies(s.version, '^1.0.0'));

Multiple Criteria

const best = services
  .filter(s => s.transport === 'ws')
  .filter(s => s.version?.startsWith('2.'))
  .sort((a, b) => (b.version || '0').localeCompare(a.version || '0', undefined, { numeric: true }))
  [0];

Authentication

PSK (Pre-Shared Key)

Optional HMAC-based authentication helpers for inter-server communication on trusted networks.

⚠️ Security Warning: PSK authentication is suitable for internal cluster communication on trusted local networks. It is NOT a replacement for proper authentication (OAuth, mTLS) on public endpoints. Use at your own risk. If unsure, consult a security expert.

Recommended Pattern:

  • Public endpoint (/mcp) - Use OAuth, API keys, or other robust authentication
  • Internal endpoint (/internal/mcp) - Use PSK + discovery for cluster communication

Build auth header (client):

import { buildAuthHeader } from '@mcp-z/mcp-discovery';

const authHeader = buildAuthHeader(secret, 'POST', '/mcp', requestBody);

Verify auth (server):

import { verifyPskHmac, expressPskHmac } from '@mcp-z/mcp-discovery';

// Express middleware
app.use(expressPskHmac(secret));

// Manual verification
const valid = verifyPskHmac({ secret, method, path, body, authHeader });

See test/psk-auth.test.ts and src/psk.ts for complete examples.


Understanding serviceName, node, and version

  • serviceName - What the service does (e.g., 'gmail'). Same across all instances.
  • node - Which server instance (e.g., 'http:prod-01'). Must be unique per instance.
  • version - Which API version (e.g., '2.0.0'). Used for client filtering.

Why all three? serviceName + node form the unique mDNS name. version is metadata for compatibility filtering.

Example: Running two servers of the same service:

// Server 1
advertiseService({ serviceName: 'gmail', node: 'http:prod-01', version: '2.0.0', port: 8080 });

// Server 2 (load balancing)
advertiseService({ serviceName: 'gmail', node: 'http:prod-02', version: '2.0.0', port: 8081 });

Clients discover both and can select either.


Examples & Tests

Complete Working Examples

See the test/ directory for comprehensive examples:

Basic Discovery:

Version Selection:

WebSocket Integration:

PSK Authentication:

Run all tests:

npm test  # 17 tests, all passing

Run specific test:

npm test -- test/websocket-integration.test.ts

How It Works

This library uses mDNS (Multicast DNS) / Bonjour for zero-configuration service discovery on local networks.

Platform Support:

  • macOS: Built-in Bonjour support
  • Linux: Requires Avahi daemon (apt install avahi-daemon)
  • Windows: Requires Bonjour for Windows

Contributing

See CONTRIBUTING.md for development guidelines.


Resources


License

MIT © Kevin Malakoff