{"id":31044798,"url":"https://github.com/emqx/mcp-typescript-sdk","last_synced_at":"2026-02-25T03:02:58.672Z","repository":{"id":314406651,"uuid":"1054710253","full_name":"emqx/mcp-typescript-sdk","owner":"emqx","description":"A TypeScript SDK for implementing Model Context Protocol (MCP) over MQTT, supporting both browser and Node.js environments.","archived":false,"fork":false,"pushed_at":"2025-09-19T02:25:10.000Z","size":418,"stargazers_count":96,"open_issues_count":0,"forks_count":3,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-10-20T06:26:04.402Z","etag":null,"topics":["ai","ai-agent","llm","mcp","mcp-client","mcp-server","mqtt","nodejs","sdk","typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/emqx.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-09-11T08:18:10.000Z","updated_at":"2025-10-13T08:49:13.000Z","dependencies_parsed_at":"2025-09-12T09:35:28.435Z","dependency_job_id":"4d193a3c-0947-40a7-aafc-d6ee060c7525","html_url":"https://github.com/emqx/mcp-typescript-sdk","commit_stats":null,"previous_names":["emqx/mcp-typescript-sdk"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/emqx/mcp-typescript-sdk","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/emqx%2Fmcp-typescript-sdk","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/emqx%2Fmcp-typescript-sdk/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/emqx%2Fmcp-typescript-sdk/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/emqx%2Fmcp-typescript-sdk/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/emqx","download_url":"https://codeload.github.com/emqx/mcp-typescript-sdk/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/emqx%2Fmcp-typescript-sdk/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28541068,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-18T14:59:57.589Z","status":"ssl_error","status_checked_at":"2026-01-18T14:59:46.540Z","response_time":98,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ai","ai-agent","llm","mcp","mcp-client","mcp-server","mqtt","nodejs","sdk","typescript"],"created_at":"2025-09-14T16:51:32.954Z","updated_at":"2026-02-25T03:02:58.658Z","avatar_url":"https://github.com/emqx.png","language":"TypeScript","funding_links":[],"categories":["📚 Projects (1974 total)"],"sub_categories":["MCP Servers"],"readme":"# @emqx-ai/mcp-mqtt-sdk\n\nA TypeScript SDK for implementing Model Context Protocol (MCP) over MQTT, supporting both browser and Node.js environments with full type safety and automatic environment detection.\n\n## 🎯 Live Demo\n\nSee this SDK in action! Check out our **[MCP AI Companion Demo](https://github.com/emqx/mcp-ai-companion-demo)** - a real-world implementation using both Python and TypeScript with this SDK to create an AI agent that can:\n\n- 🎤 Control browser audio/video functionality\n- 😊 Switch facial expressions and emotions\n- 💬 Power interactive conversational experiences\n- 🌐 Demonstrate cross-platform MCP communication\n\nThis demo showcases how to build sophisticated AI agents using MCP over MQTT for seamless browser control and interaction.\n\n## Features\n\n- 🚀 **Universal**: Works seamlessly in browser (WebSocket) and Node.js (TCP) environments\n- 🔒 **Type Safe**: Full TypeScript support with Zod schema validation for MCP protocol\n- 🌐 **MQTT Transport**: Uses MQTT as the transport layer for reliable MCP communication\n- 🏗️ **Constructor-Based**: Clean object-oriented API with proper TypeScript classes\n- 📋 **Standards Compliant**: Follows MCP specification v2024-11-05\n- 🔧 **Tool \u0026 Resource Support**: Complete support for MCP tools and resources\n- 🔍 **Auto Discovery**: Automatic server discovery over MQTT topics\n- 🌍 **Environment Detection**: Automatic browser/Node.js detection with appropriate defaults\n\n## Requirements\n\n- Node.js \u003e= 18\n\n## Installation\n\n```bash\nnpm install @emqx-ai/mcp-mqtt-sdk\n```\n\n## Quick Start\n\n### Creating an MCP Server\n\n```typescript\nimport { McpMqttServer } from '@emqx-ai/mcp-mqtt-sdk'\n\n// Create server instance\nconst server = new McpMqttServer({\n  // MQTT connection\n  host: 'mqtt://localhost:1883',\n\n  // Server identification\n  serverId: 'unique-server-id-123',\n  serverName: 'myapp/greeting-server',  // Hierarchical naming\n\n  // Server information\n  name: 'My MCP Server',\n  version: '1.0.0',\n\n  // Optional configuration\n  description: 'A sample MCP server providing greeting tools',\n  capabilities: {\n    tools: { listChanged: true },\n    resources: { listChanged: true, subscribe: false },\n  },\n})\n\n// Add a tool\nserver.tool(\n  'greet',\n  'Greet someone with a personalized message',\n  {\n    type: 'object',\n    properties: {\n      name: {\n        type: 'string',\n        description: 'Name of the person to greet'\n      },\n      language: {\n        type: 'string',\n        enum: ['en', 'es', 'fr'],\n        description: 'Language for greeting'\n      }\n    },\n    required: ['name'],\n  },\n  async ({ name, language = 'en' }) =\u003e {\n    const greetings = {\n      en: `Hello, ${name}!`,\n      es: `¡Hola, ${name}!`,\n      fr: `Bonjour, ${name}!`\n    }\n\n    return {\n      content: [{\n        type: 'text',\n        text: greetings[language] || greetings.en,\n      }],\n    }\n  }\n)\n\n// Add a resource\nserver.resource(\n  'config://app-settings',\n  'Application Settings',\n  async () =\u003e ({\n    contents: [{\n      uri: 'config://app-settings',\n      mimeType: 'application/json',\n      text: JSON.stringify({\n        theme: 'dark',\n        language: 'en',\n        notifications: true\n      }, null, 2),\n    }],\n  }),\n  {\n    description: 'Current application configuration',\n    mimeType: 'application/json',\n  }\n)\n\n// Event handlers\nserver.on('ready', () =\u003e {\n  console.log('Server is ready!')\n  console.log('Topics:', server.getTopics())\n})\n\nserver.on('error', (error) =\u003e {\n  console.error('Server error:', error)\n})\n\n// Start the server\nawait server.start()\n```\n\n### Creating an MCP Client\n\n```typescript\nimport { McpMqttClient } from '@emqx-ai/mcp-mqtt-sdk'\n\n// Create client instance\nconst client = new McpMqttClient({\n  // MQTT connection\n  host: 'mqtt://localhost:1883',\n\n  // Client information\n  name: 'My MCP Client',\n  version: '1.0.0',\n})\n\n// Set up server discovery handler\nclient.on('serverDiscovered', async (server) =\u003e {\n  console.log('📡 Discovered server:', server.name, '(ID:', server.serverId, ')')\n\n  try {\n    // Connect to the discovered server using serverId\n    await client.initializeServer(server.serverId)\n    console.log('✅ Connected to:', server.name)\n\n    // List available tools using serverId\n    const tools = await client.listTools(server.serverId)\n    console.log('🔧 Available tools:', tools.map(t =\u003e t.name))\n\n    // Call a tool using serverId\n    if (tools.some(t =\u003e t.name === 'greet')) {\n      const result = await client.callTool(server.serverId, 'greet', {\n        name: 'World',\n        language: 'es'\n      })\n      console.log('🎉 Tool result:', result.content[0]?.text)\n    }\n\n    // List and read resources using serverId\n    const resources = await client.listResources(server.serverId)\n    console.log('📚 Available resources:', resources.map(r =\u003e r.uri))\n\n    if (resources.some(r =\u003e r.uri === 'config://app-settings')) {\n      const config = await client.readResource(server.serverId, 'config://app-settings')\n      console.log('📄 Config:', config.contents[0]?.text)\n    }\n  } catch (error) {\n    console.error('❌ Error with server:', server.name, error)\n  }\n})\n\n// Connect and start discovery\nawait client.connect()\n\n// Graceful shutdown\nprocess.on('SIGINT', async () =\u003e {\n  await client.disconnect()\n  process.exit(0)\n})\n```\n\n## API Reference\n\n### McpMqttServer\n\n#### Constructor\n\n```typescript\nnew McpMqttServer(config: McpMqttServerConfig)\n```\n\n**Configuration:**\n\n```typescript\ninterface McpMqttServerConfig {\n  // MQTT connection settings\n  host: string\n  username?: string\n  password?: string\n\n  // Server identification (required)\n  serverId: string      // Unique server ID (MQTT client ID)\n  serverName: string    // Hierarchical server name (e.g., \"app/feature/server\")\n\n  // Server information (required)\n  name: string\n  version: string\n\n  // Optional configuration\n  description?: string     // Server description\n  capabilities?: {\n    prompts?: { listChanged?: boolean }\n    resources?: { subscribe?: boolean; listChanged?: boolean }\n    tools?: { listChanged?: boolean }\n  }\n  rbac?: {               // Optional role-based access control\n    roles: Array\u003c{\n      name: string\n      description: string\n      allowed_methods: string[]\n      allowed_tools: string[] | \"all\"\n      allowed_resources: string[] | \"all\"\n    }\u003e\n  }\n}\n```\n\n#### Methods\n\n##### `tool(name, description, inputSchema, handler)`\n\nRegister a tool that clients can call.\n\n```typescript\nserver.tool(\n  'calculate',\n  'Perform mathematical calculations',\n  {\n    type: 'object',\n    properties: {\n      expression: {\n        type: 'string',\n        description: 'Mathematical expression to evaluate'\n      },\n    },\n    required: ['expression'],\n  },\n  async ({ expression }) =\u003e {\n    try {\n      // Safe evaluation - implement your own parser\n      const result = evaluateExpression(expression)\n      return {\n        content: [{\n          type: 'text',\n          text: `${expression} = ${result}`,\n        }],\n      }\n    } catch (error) {\n      return {\n        content: [{\n          type: 'text',\n          text: `Error: ${error.message}`,\n        }],\n        isError: true,\n      }\n    }\n  }\n)\n```\n\n##### `resource(uri, name, handler, options?)`\n\nRegister a resource that clients can read.\n\n```typescript\nserver.resource(\n  'file://logs/app.log',\n  'Application Logs',\n  async () =\u003e {\n    const logs = await readLogFile()\n    return {\n      contents: [{\n        uri: 'file://logs/app.log',\n        mimeType: 'text/plain',\n        text: logs,\n      }],\n    }\n  },\n  {\n    description: 'Current application log entries',\n    mimeType: 'text/plain',\n  }\n)\n```\n\n##### `start()` / `stop()`\n\nControl server lifecycle.\n\n```typescript\nawait server.start()  // Start listening for requests\nawait server.stop()   // Gracefully shutdown\n```\n\n##### `getTopics()`\n\nGet MQTT topics used by this server.\n\n```typescript\nconst { request, response } = server.getTopics()\n```\n\n#### Events\n\n```typescript\nserver.on('ready', () =\u003e console.log('Server ready'))\nserver.on('error', (error) =\u003e console.error('Server error:', error))\nserver.on('closed', () =\u003e console.log('Server closed'))\n```\n\n### McpMqttClient\n\n#### Client Constructor\n\n```typescript\nnew McpMqttClient(config: McpMqttClientConfig)\n```\n\n**Configuration:**\n\n```typescript\ninterface McpMqttClientConfig {\n  // MQTT connection settings\n  host: string\n  clientId?: string\n  username?: string\n  password?: string\n  clean?: boolean\n  keepalive?: number\n  connectTimeout?: number\n  reconnectPeriod?: number\n\n  // Client information (required)\n  name: string\n  version: string\n\n  // Optional configuration\n  capabilities?: {\n    roots?: { listChanged?: boolean }\n    sampling?: Record\u003cstring, any\u003e\n  }\n\n  // Advanced MQTT settings (optional)\n  will?: {\n    topic: string\n    payload: string | Buffer\n    qos?: 0 | 1 | 2\n    retain?: boolean\n  }\n  properties?: Record\u003cstring, any\u003e\n}\n```\n\n#### Client Methods\n\n##### Connection Management\n\n```typescript\nawait client.connect()           // Connect to MQTT broker and start discovery\nawait client.disconnect()        // Disconnect from broker\nawait client.initializeServer(serverId)  // Initialize connection to a specific server\n```\n\n##### Tool Operations\n\n```typescript\nconst tools = await client.listTools(serverId)\nconst result = await client.callTool(serverId, toolName, args)\n```\n\n##### Resource Operations\n\n```typescript\nconst resources = await client.listResources(serverId)\nconst data = await client.readResource(serverId, uri)\n```\n\n##### Discovery\n\n```typescript\nconst discovered = client.getDiscoveredServers()\nconst connected = client.getConnectedServers()\n```\n\n#### Client Events\n\n```typescript\nclient.on('serverDiscovered', (server) =\u003e {\n  console.log('Found server:', server.name, 'ID:', server.serverId)\n})\n\nclient.on('serverInitialized', (server) =\u003e {\n  console.log('Connected to:', server.name)\n})\n\nclient.on('serverDisconnected', (serverId) =\u003e {\n  console.log('Server disconnected:', serverId)\n})\n\nclient.on('connected', () =\u003e console.log('Client connected'))\nclient.on('disconnected', () =\u003e console.log('Client disconnected'))\nclient.on('error', (error) =\u003e console.error('Client error:', error))\n```\n\n## MQTT Configuration\n\nThe SDK supports comprehensive MQTT connection options:\n\n```typescript\ninterface MqttConnectionOptions {\n  host: string               // Complete connection URL (e.g., ws://localhost:8083, wss://broker.emqx.io:8084)\n  clientId?: string          // Auto-generated if not provided\n  username?: string\n  password?: string\n  clean?: boolean            // Clean session (default: true)\n  keepalive?: number         // Keep-alive interval in seconds\n  connectTimeout?: number    // Connection timeout in milliseconds\n  reconnectPeriod?: number   // Reconnection period in milliseconds\n  will?: {                   // Last Will Testament\n    topic: string\n    payload: string | Buffer\n    qos?: 0 | 1 | 2\n    retain?: boolean\n  };\n}\n```\n\n### Connection Examples\n\nThe `host` parameter should be a complete connection URL:\n\n```typescript\n// WebSocket connections (browser)\nhost: 'ws://localhost:8083'\nhost: 'wss://broker.emqx.io:8084'\n\n// TCP connections (Node.js)\nhost: 'mqtt://localhost:1883'\nhost: 'mqtts://broker.emqx.io:8883'\n```\n\n## Advanced Usage\n\n### Accessing Underlying MQTT Client\n\nBoth server and client provide `getMqttClient()` method to access the underlying MQTT client for custom pub/sub operations:\n\n```typescript\nconst mqttClient = server.getMqttClient() // or client.getMqttClient()\nif (mqttClient) {\n  mqttClient.subscribe('custom/topic', { qos: 1 })\n  mqttClient.publish('custom/topic', 'Hello World')\n}\n```\n\n### Custom Tool Validation\n\n```typescript\nimport { z } from 'zod'\n\n// Define schema for tool parameters\nconst CalculateSchema = z.object({\n  operation: z.enum(['add', 'subtract', 'multiply', 'divide']),\n  a: z.number(),\n  b: z.number(),\n})\n\nserver.tool(\n  'calculate',\n  'Perform arithmetic operations',\n  {\n    type: 'object',\n    properties: {\n      operation: { type: 'string', enum: ['add', 'subtract', 'multiply', 'divide'] },\n      a: { type: 'number' },\n      b: { type: 'number' },\n    },\n    required: ['operation', 'a', 'b'],\n  },\n  async (params) =\u003e {\n    // Validate with Zod\n    const { operation, a, b } = CalculateSchema.parse(params)\n\n    let result: number\n    switch (operation) {\n      case 'add': result = a + b; break\n      case 'subtract': result = a - b; break\n      case 'multiply': result = a * b; break\n      case 'divide':\n        if (b === 0) throw new Error('Division by zero')\n        result = a / b\n        break\n    }\n\n    return {\n      content: [{\n        type: 'text',\n        text: `${a} ${operation} ${b} = ${result}`,\n      }],\n    }\n  }\n)\n```\n\n### Error Handling\n\n```typescript\n// Server-side error handling\nserver.tool('risky-operation', 'An operation that might fail', schema, async (params) =\u003e {\n  try {\n    const result = await performRiskyOperation(params)\n    return {\n      content: [{ type: 'text', text: `Success: ${result}` }],\n    }\n  } catch (error) {\n    return {\n      content: [{ type: 'text', text: `Operation failed: ${error.message}` }],\n      isError: true, // Mark as error response\n    }\n  }\n})\n\n// Client-side error handling\ntry {\n  const result = await client.callTool(serverName, 'risky-operation', params)\n  if (result.isError) {\n    console.error('Tool returned error:', result.content[0]?.text)\n  } else {\n    console.log('Success:', result.content[0]?.text)\n  }\n} catch (error) {\n  console.error('Tool call failed:', error.message)\n}\n```\n\n### Resource Streaming\n\n```typescript\n// Server: Streaming resource\nserver.resource(\n  'stream://live-data',\n  'Live Data Stream',\n  async () =\u003e {\n    const chunks = await getLiveDataChunks()\n    return {\n      contents: chunks.map((chunk, index) =\u003e ({\n        uri: `stream://live-data#${index}`,\n        mimeType: 'application/json',\n        text: JSON.stringify(chunk),\n      })),\n    }\n  }\n)\n\n// Client: Read streaming resource\nconst stream = await client.readResource(serverName, 'stream://live-data')\nfor (const content of stream.contents) {\n  const data = JSON.parse(content.text!)\n  processStreamChunk(data)\n}\n```\n\n## Development\n\n```bash\n# Install dependencies\nnpm install\n\n# Build\nnpm run build\n\n# Build in watch mode\nnpm run build:watch\n\n# Run tests\nnpm run test\n\n# Type checking\nnpm run typecheck\n\n# Linting\nnpm run lint\nnpm run lint:fix\n```\n\n## Protocol Details\n\n### MQTT Topic Structure\n\nThe SDK follows the official MCP over MQTT specification topic hierarchy:\n\n```text\nMCP over MQTT Topic Structure:\n\n🗂️ Server Topics:\n├── $mcp-server/{server-id}/{server-name}              # Control topic (initialization)\n├── $mcp-server/capability/{server-id}/{server-name}   # Capability change notifications\n└── $mcp-server/presence/{server-id}/{server-name}     # Server presence (online/offline)\n\n🗂️ Client Topics:\n├── $mcp-client/capability/{mcp-client-id}             # Client capability changes\n└── $mcp-client/presence/{mcp-client-id}               # Client presence\n\n🗂️ RPC Communication:\n└── $mcp-rpc/{mcp-client-id}/{server-id}/{server-name} # Bidirectional RPC communication\n\nExample Topics:\n- Control: $mcp-server/server-123/myapp/greeting-server\n- Capability: $mcp-server/capability/server-123/myapp/greeting-server\n- Presence: $mcp-server/presence/server-123/myapp/greeting-server\n- RPC: $mcp-rpc/client-456/server-123/myapp/greeting-server\n```\n\n### Message Flow\n\n1. **Service Discovery**: Client subscribes to `$mcp-server/presence/+/#`\n2. **Server Registration**: Server publishes presence to `$mcp-server/presence/{server-id}/{server-name}`\n3. **Initialization**: Client sends `initialize` request to `$mcp-server/{server-id}/{server-name}`\n4. **RPC Communication**: Bidirectional communication via `$mcp-rpc/{client-id}/{server-id}/{server-name}`\n\n### Error Codes\n\nThe SDK follows JSON-RPC 2.0 error codes:\n\n- `-32700`: Parse error\n- `-32600`: Invalid request\n- `-32601`: Method not found\n- `-32602`: Invalid params\n- `-32603`: Internal error\n- `-32000` to `-32099`: Implementation-defined server errors\n\n## Other Language SDKs\n\nLooking for MCP over MQTT support in other languages?\n\n- **[Python SDK](https://github.com/emqx/mcp-python-sdk)** - MCP over MQTT implementation for Python\n- **[Erlang SDK](https://github.com/emqx/mcp-mqtt-erl)** - MCP over MQTT implementation for Erlang\n- **[Paho MCP over MQTT](https://github.com/mqtt-ai/paho-mcp-over-mqtt)** - MCP over MQTT implementation in C\n- **[ESP MCP over MQTT](https://github.com/mqtt-ai/esp-mcp-over-mqtt)** - MCP over MQTT for ESP32/embedded devices in C\n\n## Contributing\n\n1. Fork the repository\n2. Create a feature branch (`git checkout -b feature/amazing-feature`)\n3. Make your changes with tests\n4. Run the test suite (`npm test`)\n5. Submit a pull request\n\n## License\n\nApache License 2.0. See [LICENSE](LICENSE) file for details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Femqx%2Fmcp-typescript-sdk","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Femqx%2Fmcp-typescript-sdk","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Femqx%2Fmcp-typescript-sdk/lists"}