@msgmesh/mcp-server
Advanced tools
+237
-43
| #!/usr/bin/env node | ||
| // src/index.ts | ||
| import { createRequire } from "module"; | ||
| import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; | ||
@@ -10,2 +11,70 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; | ||
| // src/handlers.ts | ||
| import { MsgMeshError } from "@msgmesh/sdk"; | ||
| // src/config.ts | ||
| var HOSTED_API_URL = "https://msgmesh-api.alderflux.com"; | ||
| var PANEL_URL = "https://msgmesh-panel.alderflux.com"; | ||
| var ENV = { | ||
| apiKey: "MQ_API_KEY", | ||
| controlPlaneUrl: "MQ_CONTROL_PLANE_URL", | ||
| gatewayUrl: "MQ_GATEWAY_URL", | ||
| realtimeUrl: "MQ_REALTIME_URL" | ||
| }; | ||
| var KEY_PLACEHOLDER = "<\u4F60\u7684 API Key>"; | ||
| var PLACEHOLDER_PATTERNS = [ | ||
| /^<[\s\S]*>$/, | ||
| // <你的 API Key>(packages/shared-go/snippet 產生的接入片段用這個) | ||
| /^\{\{[\s\S]*\}\}$/, | ||
| // {{API_KEY}} 之類的模板語法沒被渲染 | ||
| /^\$\{?[A-Za-z_][A-Za-z0-9_]*\}?$/, | ||
| // $MQ_API_KEY / ${MQ_API_KEY}:client 不做 shell 展開時會原樣傳入 | ||
| /^mk_x+$/i, | ||
| // mk_xxxxxxxx(真 key 的 hex 段不含 x → 不會誤擋) | ||
| /^x+$/i, | ||
| // xxxxxxxx | ||
| /your[\s_-]*api[\s_-]*key/i, | ||
| // YOUR_API_KEY / your-api-key / Your API Key | ||
| /(你的|換成|請填|填入|替換|貼上)/ | ||
| // 中文範本語句,如「把這裡換成你的 API Key」 | ||
| ]; | ||
| function isPlaceholder(v) { | ||
| return PLACEHOLDER_PATTERNS.some((re) => re.test(v)); | ||
| } | ||
| function envValue(env, name) { | ||
| return (env[name] ?? "").trim(); | ||
| } | ||
| var HOW_TO_GET_KEY = `\u8ACB\u5230\u9762\u677F ${PANEL_URL} \u7684 Keys \u9801\u7C3D\u767C\u4E00\u628A API Key(MCP \u591A\u6578 tool \u5C6C\u6CBB\u7406\u985E\u3001\u9700 admin scope)`; | ||
| var CONFIG_EXAMPLE = `\u8A2D\u5B9A\u7BC4\u4F8B:"env": { "${ENV.apiKey}": "${KEY_PLACEHOLDER}" }(\u89D2\u62EC\u865F\u9023\u540C\u88E1\u9762\u7684\u5B57\u4E00\u8D77\u63DB\u6210\u9762\u677F\u7C3D\u51FA\u7684\u5B8C\u6574 key)\u2014\u2014${ENV.controlPlaneUrl} / ${ENV.gatewayUrl} / ${ENV.realtimeUrl} \u672A\u8A2D\u6642\u9810\u8A2D ${HOSTED_API_URL},\u81EA\u67B6\u624D\u9700\u8986\u5BEB\u3002`; | ||
| function resolveConfig(env) { | ||
| const apiKey = envValue(env, ENV.apiKey); | ||
| if (apiKey === "") { | ||
| return { | ||
| ok: false, | ||
| lines: [ | ||
| `MsgMesh MCP server \u7121\u6CD5\u555F\u52D5:\u74B0\u5883\u8B8A\u6578 ${ENV.apiKey} \u672A\u8A2D\u5B9A\u3002${HOW_TO_GET_KEY},\u586B\u9032 MCP \u8A2D\u5B9A\u7684 env.${ENV.apiKey} \u5F8C\u91CD\u555F\u3002`, | ||
| CONFIG_EXAMPLE | ||
| ] | ||
| }; | ||
| } | ||
| if (isPlaceholder(apiKey)) { | ||
| return { | ||
| ok: false, | ||
| lines: [ | ||
| `MsgMesh MCP server \u7121\u6CD5\u555F\u52D5:\u74B0\u5883\u8B8A\u6578 ${ENV.apiKey} \u9084\u662F\u8A2D\u5B9A\u7BC4\u672C\u7684\u4F54\u4F4D\u5B57\u4E32\u300C${apiKey}\u300D,\u5C1A\u672A\u63DB\u6210\u771F\u7684 key\u3002${HOW_TO_GET_KEY},\u586B\u9032 MCP \u8A2D\u5B9A\u7684 env.${ENV.apiKey} \u5F8C\u91CD\u555F\u3002`, | ||
| CONFIG_EXAMPLE | ||
| ] | ||
| }; | ||
| } | ||
| return { | ||
| ok: true, | ||
| config: { | ||
| apiKey, | ||
| controlPlaneUrl: envValue(env, ENV.controlPlaneUrl) || HOSTED_API_URL, | ||
| gatewayUrl: envValue(env, ENV.gatewayUrl) || HOSTED_API_URL, | ||
| realtimeUrl: envValue(env, ENV.realtimeUrl) || HOSTED_API_URL | ||
| } | ||
| }; | ||
| } | ||
| // src/handlers.ts | ||
| function buildHandlers(mq2) { | ||
@@ -133,16 +202,101 @@ return { | ||
| } | ||
| var HINT_401 = `\u2192 \u4E0B\u4E00\u6B65:${ENV.apiKey} \u7121\u6548\u6216\u5DF2\u64A4\u92B7\u3002\u5230\u9762\u677F ${PANEL_URL} \u7684 Keys \u9801\u91CD\u7C3D\u4E00\u628A,\u66F4\u65B0 MCP \u8A2D\u5B9A\u88E1\u7684 ${ENV.apiKey} \u5F8C\u91CD\u555F\u9019\u500B MCP server\u3002`; | ||
| var HINT_403 = `\u2192 \u4E0B\u4E00\u6B65:\u9019\u628A ${ENV.apiKey} \u6B0A\u9650\u4E0D\u8DB3\u3002MCP \u7684\u6CBB\u7406\u985E tool(topic / key / schema / webhook / function / \u65B9\u6848 / \u7A3D\u6838)\u9700 **admin** scope,\u8CC7\u6599\u9762(publish / consume)\u5247\u9700\u5C0D\u61C9 topic \u7684\u80FD\u529B\u3002\u8ACB\u5230\u9762\u677F ${PANEL_URL} \u7684 Keys \u9801\u7C3D\u4E00\u628A admin key \u63DB\u6389 ${ENV.apiKey}\u2014\u2014\u4E0D\u8981\u6539\u7528 create_key \u81EA\u52A9\u958B\u901A,\u5B83\u672C\u8EAB\u4E5F\u8981 admin,\u53EA\u6703\u518D\u88AB\u64CB\u4E00\u6B21\u3002`; | ||
| function httpStatusOf(err) { | ||
| if (err instanceof MsgMeshError) return err.status; | ||
| if (typeof err === "object" && err !== null) { | ||
| const e = err; | ||
| const shaped = typeof e.status === "number" && typeof e.code === "string" && typeof e.path === "string" && typeof e.name === "string" && e.name.endsWith("Error"); | ||
| if (shaped) return e.status; | ||
| } | ||
| return void 0; | ||
| } | ||
| function withHint(err, hint) { | ||
| const copy = Object.create(Object.getPrototypeOf(err)); | ||
| Object.assign(copy, err); | ||
| copy.message = `${err.message} | ||
| ${hint}`; | ||
| copy.name = err.name; | ||
| if (typeof err.stack === "string") copy.stack = err.stack.replace(err.message, () => copy.message); | ||
| copy.cause = err; | ||
| return copy; | ||
| } | ||
| function mapError(err) { | ||
| const status = httpStatusOf(err); | ||
| const hint = status === 401 ? HINT_401 : status === 403 ? HINT_403 : void 0; | ||
| if (!hint || !(err instanceof Error)) return err; | ||
| return withHint(err, hint); | ||
| } | ||
| function withMappedError(h2) { | ||
| const src = h2; | ||
| const out = {}; | ||
| for (const name of Object.keys(src)) { | ||
| const fn = src[name]; | ||
| out[name] = async (...args) => { | ||
| try { | ||
| return await fn.apply(src, args); | ||
| } catch (err) { | ||
| throw mapError(err); | ||
| } | ||
| }; | ||
| } | ||
| return out; | ||
| } | ||
| // src/index.ts | ||
| var mq = new MsgMesh({ | ||
| apiKey: process.env.MQ_API_KEY ?? "", | ||
| controlPlaneUrl: process.env.MQ_CONTROL_PLANE_URL, | ||
| gatewayUrl: process.env.MQ_GATEWAY_URL, | ||
| realtimeUrl: process.env.MQ_REALTIME_URL | ||
| }); | ||
| var h = buildHandlers(mq); | ||
| var requireFromHere = createRequire(import.meta.url); | ||
| var { version: SERVER_VERSION } = requireFromHere("../package.json"); | ||
| var resolved = resolveConfig(process.env); | ||
| if (!resolved.ok) { | ||
| for (const line of resolved.lines) console.error(line); | ||
| process.exit(1); | ||
| } | ||
| var mq; | ||
| try { | ||
| mq = new MsgMesh({ | ||
| apiKey: resolved.config.apiKey, | ||
| controlPlaneUrl: resolved.config.controlPlaneUrl, | ||
| gatewayUrl: resolved.config.gatewayUrl, | ||
| realtimeUrl: resolved.config.realtimeUrl | ||
| }); | ||
| } catch (err) { | ||
| console.error( | ||
| `MsgMesh MCP server \u7121\u6CD5\u555F\u52D5:\u5EFA\u7ACB SDK \u9023\u7DDA\u8A2D\u5B9A\u5931\u6557(${err instanceof Error ? err.message : String(err)})\u3002\u8ACB\u6AA2\u67E5 MCP \u8A2D\u5B9A\u88E1\u7684 ${ENV.apiKey} \u8207 ${ENV.controlPlaneUrl} / ${ENV.gatewayUrl} / ${ENV.realtimeUrl}\u3002` | ||
| ); | ||
| process.exit(1); | ||
| } | ||
| var h = withMappedError(buildHandlers(mq)); | ||
| var text = (s) => ({ content: [{ type: "text", text: s }] }); | ||
| var server = new McpServer({ name: "msgmesh", version: "0.1.0" }); | ||
| var READ_ONLY = { readOnlyHint: true, openWorldHint: false }; | ||
| var ADDITIVE = { readOnlyHint: false, destructiveHint: false, openWorldHint: false }; | ||
| var MUTATING = { readOnlyHint: false, destructiveHint: true, openWorldHint: false }; | ||
| var INSTRUCTIONS = [ | ||
| "MsgMesh \u662F\u67B6\u5728 Kafka \u4E4B\u4E0A\u7684\u591A\u79DF\u6236\u4E8B\u4EF6\u7E3D\u7DDA(topic / \u8A0A\u606F / schema / webhook / DLQ)\u3002", | ||
| "\u9019\u500B server \u8B93\u4F60\u76F4\u63A5\u6536\u767C\u4E8B\u4EF6\u4E26\u76E3\u770B\u4E8B\u4EF6\u6D41,\u4E0D\u5FC5\u81EA\u5DF1\u5BEB\u4E00\u5C64\u4E8B\u4EF6\u6A4B\u3002", | ||
| "", | ||
| "\u5EFA\u8B70\u6D41\u7A0B:", | ||
| "1. \u5148\u547C\u53EB get_plan \u9A57\u8B49\u6191\u8B49\u2014\u2014\u6210\u529F\u4EE3\u8868 MQ_API_KEY \u6709\u6548\u4E14\u5177 admin scope;401=key \u7121\u6548\u3001403=scope \u4E0D\u8DB3\u3002", | ||
| "2. create_topic \u5EFA\u7ACB topic,\u518D\u7528 publish_message \u767C\u4E8B\u4EF6(message \u53EF\u70BA\u4EFB\u610F JSON)\u3002", | ||
| "3. \u76E3\u770B\u4E8B\u4EF6\u7528 watch_topic:\u9577\u8F2A\u8A62\u7B49\u4E0B\u4E00\u6279,\u6536\u5230\u5C31\u8655\u7406\u3001\u518D\u4EE5**\u540C\u4E00\u500B group** \u547C\u53EB\u7E7C\u7E8C\u7B49\u4E0B\u4E00\u6279\u3002", | ||
| " consume_messages \u5247\u662F\u300C\u6709\u5C31\u56DE\u3001\u6C92\u6709\u5373\u56DE\u7A7A\u300D,\u9069\u5408\u4E00\u6B21\u6027\u53D6\u7528\u3002", | ||
| "", | ||
| "\u6CE8\u610F:", | ||
| "- \u6CBB\u7406\u985E tool(topic / key / schema / webhook / function / \u65B9\u6848 / \u7A3D\u6838 / \u7528\u91CF)\u9700 **admin** scope \u7684 key;", | ||
| " \u8CC7\u6599\u9762(publish / consume / watch / presence / DLQ)\u53E6\u9700\u8A72 topic \u7684\u5C0D\u61C9\u80FD\u529B\u3002", | ||
| "- consume_messages / watch_topic / dlq_peek \u6703\u63A8\u9032 consumer group \u4F4D\u79FB(at-most-once):", | ||
| " \u540C\u4E00\u6279\u8A0A\u606F\u4E0D\u6703\u518D\u9001\u7B2C\u4E8C\u6B21,\u5225\u7576\u6210\u7121\u526F\u4F5C\u7528\u7684\u67E5\u8A62\u53CD\u8986\u547C\u53EB\u3002", | ||
| `- \u9047\u5230 401 / 403 \u4E0D\u8981\u5617\u8A66\u7528 create_key \u81EA\u52A9\u958B\u901A(\u5B83\u672C\u8EAB\u4E5F\u9700 admin,\u53EA\u6703\u518D\u88AB\u64CB);`, | ||
| ` \u8ACB\u544A\u8A34\u4F7F\u7528\u8005\u5230\u9762\u677F ${PANEL_URL} \u7684 Keys \u9801\u91CD\u7C3D,\u4E26\u66F4\u65B0 MCP \u8A2D\u5B9A\u88E1\u7684 ${ENV.apiKey} \u5F8C\u91CD\u555F\u3002` | ||
| ].join("\n"); | ||
| var server = new McpServer( | ||
| { name: "msgmesh", version: SERVER_VERSION }, | ||
| { instructions: INSTRUCTIONS } | ||
| ); | ||
| server.registerTool( | ||
| "create_topic", | ||
| { description: "\u5EFA\u7ACB\u4E00\u500B\u4E8B\u4EF6 topic", inputSchema: { name: z.string(), partitions: z.number().optional() } }, | ||
| { | ||
| description: "\u5EFA\u7ACB\u4E00\u500B\u4E8B\u4EF6 topic", | ||
| inputSchema: { name: z.string(), partitions: z.number().optional() }, | ||
| annotations: ADDITIVE | ||
| }, | ||
| async (args) => text(await h.createTopic(args)) | ||
@@ -152,3 +306,3 @@ ); | ||
| "list_topics", | ||
| { description: "\u5217\u51FA\u76EE\u524D\u6240\u6709 topic", inputSchema: {} }, | ||
| { description: "\u5217\u51FA\u76EE\u524D\u6240\u6709 topic", inputSchema: {}, annotations: READ_ONLY }, | ||
| async () => text(await h.listTopics()) | ||
@@ -158,3 +312,7 @@ ); | ||
| "delete_topic", | ||
| { description: "\u522A\u9664\u6307\u5B9A topic", inputSchema: { name: z.string() } }, | ||
| { | ||
| description: "\u522A\u9664\u6307\u5B9A topic\u3002**\u4E0D\u53EF\u9006**:\u8A72 topic \u7684\u6240\u6709\u8A0A\u606F\u3001\u6B7B\u4FE1\u4F47\u5217\u3001schema\u3001\u51FD\u6578\u8207 webhook \u7D81\u5B9A\u6703\u7ACB\u5373\u88AB\u6E05\u9664,\u540C\u540D\u91CD\u5EFA\u53EA\u6703\u5F97\u5230\u4E00\u500B\u5168\u65B0\u7684\u7A7A topic\u3002\u522A\u9664\u524D\u8ACB\u5148\u5411\u4F7F\u7528\u8005\u78BA\u8A8D\u3002", | ||
| inputSchema: { name: z.string() }, | ||
| annotations: MUTATING | ||
| }, | ||
| async (args) => text(await h.deleteTopic(args)) | ||
@@ -166,3 +324,4 @@ ); | ||
| description: "\u767C\u9001\u4E00\u5247\u8A0A\u606F\u5230\u6307\u5B9A topic(\u53EF\u9078 key \u505A\u5206\u5340\u8DEF\u7531)", | ||
| inputSchema: { topic: z.string(), message: z.unknown(), key: z.string().optional() } | ||
| inputSchema: { topic: z.string(), message: z.unknown(), key: z.string().optional() }, | ||
| annotations: ADDITIVE | ||
| }, | ||
@@ -174,4 +333,5 @@ async (args) => text(await h.publishMessage(args)) | ||
| { | ||
| description: "\u5F9E\u6307\u5B9A topic \u62C9\u53D6\u8A0A\u606F(\u53EF\u6307\u5B9A consumer group \u8207\u6700\u5927\u7B46\u6578)", | ||
| inputSchema: { topic: z.string(), group: z.string().optional(), max: z.number().optional() } | ||
| description: "\u5F9E\u6307\u5B9A topic \u62C9\u53D6\u8A0A\u606F(\u53EF\u6307\u5B9A consumer group \u8207\u6700\u5927\u7B46\u6578)\u3002\u26A0\uFE0F \u6703\u63D0\u4EA4 consumer group \u4F4D\u79FB(at-most-once):\u53D6\u56DE\u7684\u8A0A\u606F\u4E0D\u6703\u518D\u9001\u7B2C\u4E8C\u6B21,\u4E0D\u662F\u552F\u8B80\u67E5\u8A62\u3002", | ||
| inputSchema: { topic: z.string(), group: z.string().optional(), max: z.number().optional() }, | ||
| annotations: MUTATING | ||
| }, | ||
@@ -183,4 +343,5 @@ async (args) => text(await h.consumeMessages(args)) | ||
| { | ||
| description: "\u8A02\u95B1\u4E26\u300E\u7B49\u5F85\u300F\u67D0 topic \u7684\u4E0B\u4E00\u6279\u4E8B\u4EF6:\u9577\u8F2A\u8A62\u5230\u6709\u4E8B\u4EF6\u624D\u56DE(\u6216\u7B49\u5F85\u8996\u7A97\u903E\u6642\u56DE\u7A7A+\u63D0\u793A\u518D\u547C\u53EB)\u3002\u9069\u5408 AI agent \u76E3\u770B\u4E8B\u4EF6\u6D41\u2014\u2014\u6536\u5230\u5F8C\u8655\u7406\u3001\u518D\u6B21\u4EE5\u540C\u4E00 group \u547C\u53EB\u5373\u53EF\u7E7C\u7E8C\u7B49\u4E0B\u4E00\u6279\u3002\u8207 consume_messages(\u6709\u5C31\u56DE\u3001\u6C92\u6709\u5373\u56DE\u7A7A)\u4E0D\u540C\u3002maxWaitSec \u9810\u8A2D 25\u3001\u4E0A\u9650 55\u3002", | ||
| inputSchema: { topic: z.string(), group: z.string().optional(), maxWaitSec: z.number().optional() } | ||
| description: "\u8A02\u95B1\u4E26\u300E\u7B49\u5F85\u300F\u67D0 topic \u7684\u4E0B\u4E00\u6279\u4E8B\u4EF6:\u9577\u8F2A\u8A62\u5230\u6709\u4E8B\u4EF6\u624D\u56DE(\u6216\u7B49\u5F85\u8996\u7A97\u903E\u6642\u56DE\u7A7A+\u63D0\u793A\u518D\u547C\u53EB)\u3002\u9069\u5408 AI agent \u76E3\u770B\u4E8B\u4EF6\u6D41\u2014\u2014\u6536\u5230\u5F8C\u8655\u7406\u3001\u518D\u6B21\u4EE5\u540C\u4E00 group \u547C\u53EB\u5373\u53EF\u7E7C\u7E8C\u7B49\u4E0B\u4E00\u6279\u3002\u8207 consume_messages(\u6709\u5C31\u56DE\u3001\u6C92\u6709\u5373\u56DE\u7A7A)\u4E0D\u540C\u3002maxWaitSec \u9810\u8A2D 25\u3001\u4E0A\u9650 55\u3002\u26A0\uFE0F \u540C\u6A23\u6703\u63D0\u4EA4 consumer group \u4F4D\u79FB(at-most-once),\u4E0D\u662F\u552F\u8B80\u67E5\u8A62\u3002", | ||
| inputSchema: { topic: z.string(), group: z.string().optional(), maxWaitSec: z.number().optional() }, | ||
| annotations: MUTATING | ||
| }, | ||
@@ -192,4 +353,5 @@ async (args) => text(await h.watchEvents(args)) | ||
| { | ||
| description: "\u70BA topic \u8A3B\u518A JSON Schema(compatibility:none/backward)", | ||
| inputSchema: { topic: z.string(), schema: z.unknown(), compatibility: z.string().optional() } | ||
| description: "\u70BA topic \u8A3B\u518A JSON Schema(compatibility:none/backward)\u3002\u6BCF\u6B21\u547C\u53EB\u90FD\u6703\u65B0\u589E\u4E00\u500B\u7248\u672C(\u975E\u51AA\u7B49)", | ||
| inputSchema: { topic: z.string(), schema: z.unknown(), compatibility: z.string().optional() }, | ||
| annotations: ADDITIVE | ||
| }, | ||
@@ -200,3 +362,3 @@ async (args) => text(await h.registerSchema(args)) | ||
| "get_schema", | ||
| { description: "\u53D6\u5F97 topic \u6700\u65B0\u7248\u672C\u7684 schema", inputSchema: { topic: z.string() } }, | ||
| { description: "\u53D6\u5F97 topic \u6700\u65B0\u7248\u672C\u7684 schema", inputSchema: { topic: z.string() }, annotations: READ_ONLY }, | ||
| async (args) => text(await h.getSchema(args)) | ||
@@ -206,3 +368,3 @@ ); | ||
| "list_schemas", | ||
| { description: "\u5217\u51FA topic \u6240\u6709 schema \u7248\u672C", inputSchema: { topic: z.string() } }, | ||
| { description: "\u5217\u51FA topic \u6240\u6709 schema \u7248\u672C", inputSchema: { topic: z.string() }, annotations: READ_ONLY }, | ||
| async (args) => text(await h.listSchemas(args)) | ||
@@ -214,3 +376,4 @@ ); | ||
| description: "\u522A\u9664 topic \u7684\u6307\u5B9A schema \u7248\u672C(\u4E0D\u53EF\u522A\u6700\u65B0\u7248,\u5426\u5247\u56DE 409)", | ||
| inputSchema: { topic: z.string(), version: z.number() } | ||
| inputSchema: { topic: z.string(), version: z.number() }, | ||
| annotations: MUTATING | ||
| }, | ||
@@ -223,3 +386,4 @@ async (args) => text(await h.deleteSchema(args)) | ||
| description: "\u5EFA\u7ACB webhook:topic \u6709\u65B0\u8A0A\u606F\u6642 POST \u5230\u6307\u5B9A URL(\u53EF\u9078 secret \u505A HMAC \u7C3D\u7AE0)", | ||
| inputSchema: { topic: z.string(), url: z.string(), secret: z.string().optional() } | ||
| inputSchema: { topic: z.string(), url: z.string(), secret: z.string().optional() }, | ||
| annotations: ADDITIVE | ||
| }, | ||
@@ -230,3 +394,3 @@ async (args) => text(await h.createWebhook(args)) | ||
| "list_webhooks", | ||
| { description: "\u5217\u51FA\u76EE\u524D\u6240\u6709 webhook", inputSchema: {} }, | ||
| { description: "\u5217\u51FA\u76EE\u524D\u6240\u6709 webhook", inputSchema: {}, annotations: READ_ONLY }, | ||
| async () => text(await h.listWebhooks()) | ||
@@ -236,3 +400,3 @@ ); | ||
| "delete_webhook", | ||
| { description: "\u522A\u9664\u6307\u5B9A webhook(\u4EE5 id)", inputSchema: { id: z.string() } }, | ||
| { description: "\u522A\u9664\u6307\u5B9A webhook(\u4EE5 id)", inputSchema: { id: z.string() }, annotations: MUTATING }, | ||
| async (args) => text(await h.deleteWebhook(args)) | ||
@@ -243,4 +407,7 @@ ); | ||
| { | ||
| description: "\u70BA topic \u8A3B\u518A\u8A0A\u606F\u8F49\u63DB\u51FD\u6578(\u8A0A\u606F\u9032\u5165\u6642\u57F7\u884C)\u3002language \u9810\u8A2D javascript(JS \u7A0B\u5F0F\u78BC);\u50B3 wasm \u5247 code \u9808\u70BA base64 \u7DE8\u78BC\u7684 WASI \u6A21\u7D44(\u8B80 stdin JSON\u3001\u5BEB stdout JSON)", | ||
| inputSchema: { topic: z.string(), code: z.string(), language: z.string().optional() } | ||
| description: "\u70BA topic \u8A3B\u518A\u8A0A\u606F\u8F49\u63DB\u51FD\u6578(\u8A0A\u606F\u9032\u5165\u6642\u57F7\u884C)\u3002language \u9810\u8A2D javascript(JS \u7A0B\u5F0F\u78BC);\u50B3 wasm \u5247 code \u9808\u70BA base64 \u7DE8\u78BC\u7684 WASI \u6A21\u7D44(\u8B80 stdin JSON\u3001\u5BEB stdout JSON)\u3002\u26A0\uFE0F \u540C\u4E00 topic \u53EA\u6709\u4E00\u500B\u51FD\u6578:\u91CD\u8907\u8A3B\u518A\u6703**\u8986\u5BEB**\u65E2\u6709\u7684,\u820A\u7A0B\u5F0F\u78BC\u4E0D\u4FDD\u7559\u3002", | ||
| inputSchema: { topic: z.string(), code: z.string(), language: z.string().optional() }, | ||
| // 與其他 register_* 不同:store.UpsertFunction 是 ON CONFLICT DO UPDATE(覆寫既有函數), | ||
| // 不是新增一個版本,故標 MUTATING 而非 ADDITIVE。 | ||
| annotations: MUTATING | ||
| }, | ||
@@ -251,3 +418,3 @@ async (args) => text(await h.registerFunction(args)) | ||
| "get_function", | ||
| { description: "\u53D6\u5F97 topic \u7D81\u5B9A\u7684\u8A0A\u606F\u8F49\u63DB\u51FD\u6578", inputSchema: { topic: z.string() } }, | ||
| { description: "\u53D6\u5F97 topic \u7D81\u5B9A\u7684\u8A0A\u606F\u8F49\u63DB\u51FD\u6578", inputSchema: { topic: z.string() }, annotations: READ_ONLY }, | ||
| async (args) => text(await h.getFunction(args)) | ||
@@ -257,3 +424,3 @@ ); | ||
| "delete_function", | ||
| { description: "\u522A\u9664 topic \u7D81\u5B9A\u7684\u8A0A\u606F\u8F49\u63DB\u51FD\u6578", inputSchema: { topic: z.string() } }, | ||
| { description: "\u522A\u9664 topic \u7D81\u5B9A\u7684\u8A0A\u606F\u8F49\u63DB\u51FD\u6578", inputSchema: { topic: z.string() }, annotations: MUTATING }, | ||
| async (args) => text(await h.deleteFunction(args)) | ||
@@ -263,3 +430,7 @@ ); | ||
| "list_keys", | ||
| { description: "\u5217\u51FA\u76EE\u524D\u6240\u6709 API key(\u50C5 prefix,\u4E0D\u542B\u5B8C\u6574 key)", inputSchema: {} }, | ||
| { | ||
| description: "\u5217\u51FA\u76EE\u524D\u6240\u6709 API key(\u50C5 prefix,\u4E0D\u542B\u5B8C\u6574 key)", | ||
| inputSchema: {}, | ||
| annotations: READ_ONLY | ||
| }, | ||
| async () => text(await h.listKeys()) | ||
@@ -270,3 +441,3 @@ ); | ||
| { | ||
| description: '\u5EFA\u7ACB\u65B0\u7684 API key\u3002scope:admin|producer|consumer\u3002\u53EF\u9078 capabilities \u505A\u7D30\u7C92\u5EA6\u6388\u6B0A(scope \u9808 producer/consumer):[{ops:["publish"|"subscribe"],topics:["orders","support*","*"]}]', | ||
| description: '\u5EFA\u7ACB\u65B0\u7684 API key(\u672C\u8EAB\u9700 admin scope,\u6B0A\u9650\u4E0D\u8DB3\u6642\u4E0D\u8981\u7528\u5B83\u81EA\u52A9\u958B\u901A)\u3002scope:admin|producer|consumer\u3002\u53EF\u9078 capabilities \u505A\u7D30\u7C92\u5EA6\u6388\u6B0A(scope \u9808 producer/consumer):[{ops:["publish"|"subscribe"],topics:["orders","support*","*"]}]', | ||
| inputSchema: { | ||
@@ -276,3 +447,4 @@ scope: z.string(), | ||
| capabilities: z.array(z.object({ ops: z.array(z.string()), topics: z.array(z.string()) })).optional() | ||
| } | ||
| }, | ||
| annotations: ADDITIVE | ||
| }, | ||
@@ -283,3 +455,3 @@ async (args) => text(await h.createKey(args)) | ||
| "delete_key", | ||
| { description: "\u522A\u9664\u6307\u5B9A API key(\u4EE5 id)", inputSchema: { id: z.string() } }, | ||
| { description: "\u522A\u9664\u6307\u5B9A API key(\u4EE5 id)", inputSchema: { id: z.string() }, annotations: MUTATING }, | ||
| async (args) => text(await h.deleteKey(args)) | ||
@@ -289,3 +461,7 @@ ); | ||
| "get_plan", | ||
| { description: "\u67E5\u8A62\u76EE\u524D\u65B9\u6848\u8207\u914D\u984D\u9650\u5236", inputSchema: {} }, | ||
| { | ||
| description: "\u67E5\u8A62\u76EE\u524D\u65B9\u6848\u8207\u914D\u984D\u9650\u5236(\u4E5F\u662F\u6700\u8F15\u91CF\u7684\u6191\u8B49\u6AA2\u67E5:\u6210\u529F=key \u6709\u6548\u4E14\u5177 admin scope)", | ||
| inputSchema: {}, | ||
| annotations: READ_ONLY | ||
| }, | ||
| async () => text(await h.getPlan()) | ||
@@ -295,3 +471,3 @@ ); | ||
| "set_plan", | ||
| { description: "\u5207\u63DB\u65B9\u6848(free/pro/team)", inputSchema: { plan: z.string() } }, | ||
| { description: "\u5207\u63DB\u65B9\u6848(free/pro/team)", inputSchema: { plan: z.string() }, annotations: MUTATING }, | ||
| async (args) => text(await h.setPlan(args)) | ||
@@ -301,3 +477,7 @@ ); | ||
| "get_billing", | ||
| { description: "\u67E5\u8A62\u9918\u984D/\u8A08\u8CBB\u72C0\u614B/\u7E8C\u822A\u5929\u6578(PAYG \u9810\u4ED8\u5E33\u672C)", inputSchema: {} }, | ||
| { | ||
| description: "\u67E5\u8A62\u9918\u984D/\u8A08\u8CBB\u72C0\u614B/\u7E8C\u822A\u5929\u6578(PAYG \u9810\u4ED8\u5E33\u672C)", | ||
| inputSchema: {}, | ||
| annotations: READ_ONLY | ||
| }, | ||
| async () => text(await h.getBilling()) | ||
@@ -307,3 +487,7 @@ ); | ||
| "get_audit", | ||
| { description: "\u67E5\u8A62\u64CD\u4F5C\u5BE9\u8A08\u7D00\u9304(\u53EF\u6307\u5B9A\u7B46\u6578\u4E0A\u9650)", inputSchema: { limit: z.number().optional() } }, | ||
| { | ||
| description: "\u67E5\u8A62\u64CD\u4F5C\u5BE9\u8A08\u7D00\u9304(\u53EF\u6307\u5B9A\u7B46\u6578\u4E0A\u9650)", | ||
| inputSchema: { limit: z.number().optional() }, | ||
| annotations: READ_ONLY | ||
| }, | ||
| async (args) => text(await h.getAudit(args)) | ||
@@ -314,4 +498,5 @@ ); | ||
| { | ||
| description: "\u67E5\u770B topic \u7684\u6B7B\u4FE1\u4F47\u5217(DLQ)\u8A0A\u606F,\u4E0D\u5F71\u97FF\u5F8C\u7E8C\u91CD\u653E", | ||
| inputSchema: { topic: z.string(), group: z.string().optional(), max: z.number().optional() } | ||
| description: "\u67E5\u770B topic \u7684\u6B7B\u4FE1\u4F47\u5217(DLQ)\u8A0A\u606F\u3002\u26A0\uFE0F \u5E95\u5C64\u8D70 committing poll:\u6703\u63D0\u4EA4 DLQ consumer group \u4F4D\u79FB,\u770B\u904E\u7684\u8A0A\u606F\u4E0D\u6703\u518D\u56DE\u4E00\u6B21,\u4E0D\u662F\u552F\u8B80\u67E5\u8A62(\u8981\u628A\u6B7B\u4FE1\u653E\u56DE\u4E3B topic \u8ACB\u7528 dlq_replay)", | ||
| inputSchema: { topic: z.string(), group: z.string().optional(), max: z.number().optional() }, | ||
| annotations: MUTATING | ||
| }, | ||
@@ -324,3 +509,4 @@ async (args) => text(await h.dlqPeek(args)) | ||
| description: "\u5C07 topic \u6B7B\u4FE1\u4F47\u5217(DLQ)\u8A0A\u606F\u91CD\u653E\u56DE\u539F topic(\u53EF\u9650\u5236\u7B46\u6578)", | ||
| inputSchema: { topic: z.string(), max: z.number().optional() } | ||
| inputSchema: { topic: z.string(), max: z.number().optional() }, | ||
| annotations: MUTATING | ||
| }, | ||
@@ -331,3 +517,7 @@ async (args) => text(await h.dlqReplay(args)) | ||
| "get_presence", | ||
| { description: "\u67E5\u8A62 topic \u76EE\u524D\u7DDA\u4E0A\u9023\u7DDA\u6578(realtime \u670D\u52D9)", inputSchema: { topic: z.string() } }, | ||
| { | ||
| description: "\u67E5\u8A62 topic \u76EE\u524D\u7DDA\u4E0A\u9023\u7DDA\u6578(realtime \u670D\u52D9)", | ||
| inputSchema: { topic: z.string() }, | ||
| annotations: READ_ONLY | ||
| }, | ||
| async (args) => text(await h.getPresence(args)) | ||
@@ -337,3 +527,3 @@ ); | ||
| "get_docs", | ||
| { description: "\u53D6\u5F97\u7531\u79DF\u6236 topics+schema \u751F\u6210\u7684 Markdown \u4F7F\u7528\u6587\u4EF6", inputSchema: {} }, | ||
| { description: "\u53D6\u5F97\u7531\u79DF\u6236 topics+schema \u751F\u6210\u7684 Markdown \u4F7F\u7528\u6587\u4EF6", inputSchema: {}, annotations: READ_ONLY }, | ||
| async () => text(await h.getDocs()) | ||
@@ -343,3 +533,3 @@ ); | ||
| "get_connection_snippet", | ||
| { description: "\u53D6\u5F97\u53EF\u8CBC\u7D66 AI \u7684\u63A5\u5165\u63D0\u793A", inputSchema: {} }, | ||
| { description: "\u53D6\u5F97\u53EF\u8CBC\u7D66 AI \u7684\u63A5\u5165\u63D0\u793A", inputSchema: {}, annotations: READ_ONLY }, | ||
| async () => text(await h.getConnectionSnippet()) | ||
@@ -349,3 +539,7 @@ ); | ||
| "get_usage", | ||
| { description: "\u67E5\u8A62\u79DF\u6236\u7528\u91CF(\u53EF\u6307\u5B9A\u65E5\u671F period=YYYY-MM-DD)", inputSchema: { period: z.string().optional() } }, | ||
| { | ||
| description: "\u67E5\u8A62\u79DF\u6236\u7528\u91CF(\u53EF\u6307\u5B9A\u65E5\u671F period=YYYY-MM-DD)", | ||
| inputSchema: { period: z.string().optional() }, | ||
| annotations: READ_ONLY | ||
| }, | ||
| async (args) => text(await h.getUsage(args)) | ||
@@ -352,0 +546,0 @@ ); |
+3
-3
| { | ||
| "name": "@msgmesh/mcp-server", | ||
| "version": "0.1.3", | ||
| "description": "MsgMesh MCP server — 讓 Claude / Cursor 等 AI agent 原生訂閱事件流(watch_topic)並自助開通(create_key / create_topic)。", | ||
| "version": "0.1.4", | ||
| "description": "MsgMesh MCP server — 讓 Claude / Cursor 等 AI agent 原生訂閱事件流(watch_topic)並管理 topic / schema / webhook。", | ||
| "license": "MIT", | ||
| "author": "LukeLogix", | ||
| "homepage": "https://msg.alderflux.com", | ||
| "homepage": "https://msgmesh.alderflux.com", | ||
| "repository": { | ||
@@ -9,0 +9,0 @@ "type": "git", |
+41
-15
@@ -5,4 +5,4 @@ # @msgmesh/mcp-server | ||
| [MsgMesh](https://msg.alderflux.com) 是架在 Kafka 之上的多租戶事件總線。把這個 MCP server 加進 | ||
| Claude Desktop / Cursor,agent 就能用自然語言收發事件、監看 topic、甚至自己開通金鑰—— | ||
| [MsgMesh](https://msgmesh.alderflux.com) 是架在 Kafka 之上的多租戶事件總線。把這個 MCP server 加進 | ||
| Claude Desktop / Cursor,agent 就能用自然語言收發事件、監看 topic、管理 topic 與 schema—— | ||
| 你不必為它寫一層事件橋。 | ||
@@ -27,5 +27,3 @@ | ||
| "env": { | ||
| "MQ_API_KEY": "<你的 API Key>", | ||
| "MQ_CONTROL_PLANE_URL": "https://msgapi.alderflux.com", | ||
| "MQ_GATEWAY_URL": "https://msgapi.alderflux.com" | ||
| "MQ_API_KEY": "<你的 API Key>" | ||
| } | ||
@@ -37,23 +35,47 @@ } | ||
| 只需要一把 key:服務位址預設指向官方託管的 `https://msgmesh-api.alderflux.com`,自架才需覆寫(見下表)。 | ||
| 不需要事先安裝:`npx -y` 會在首次啟動時自動取得。 | ||
| 把 `<你的 API Key>` 連同角括號一起換成面板簽出的 key。沒填、或還留著佔位字串(`<...>`、 | ||
| `YOUR_API_KEY`、`mk_xxxxxxxx`、`$MQ_API_KEY` 這類)時,server 會拒絕啟動並在 stderr 印出該 | ||
| 去哪拿 key——不會裝出「已連線」卻在第一次呼叫才吐 401。 | ||
| ### 還沒有 API Key? | ||
| [面板](https://msgpanel.alderflux.com)註冊後可簽發一把。或者——直接讓 agent 自己開通: | ||
| 到[面板](https://msgmesh-panel.alderflux.com)註冊後,在 Keys 頁簽發一把。 | ||
| **建議簽 `admin` scope**:29 個 tool 裡多數是治理類(topic / key / schema / webhook / | ||
| function / 方案 / 稽核 / 用量),後端要求 admin;`producer` / `consumer` key 只能用資料面 | ||
| (`publish_message` / `consume_messages` / `watch_topic` / `get_presence` / DLQ)。 | ||
| 權限不足時後端回 403 —— 這時**不能**叫 agent 用 `create_key` 自己開通(簽發金鑰本身也需要 | ||
| admin,只會再被擋一次),請到面板換一把。 | ||
| 拿到 key 之後,可以直接這樣用: | ||
| ``` | ||
| 「用 create_key 建一把能 publish + subscribe 的 key,然後監看 support.tickets。」 | ||
| 「建一個 support.tickets topic,然後幫我監看它,有新工單就告訴我。」 | ||
| ``` | ||
| agent 有 `create_key` / `create_topic` / `watch_topic`,會自我 bootstrap 後開始監看。 | ||
| (自助開通需要一把有 `admin` scope 的 key;完全沒有 key 時請先到面板簽發。) | ||
| agent 會呼叫 `create_topic` → `watch_topic`,長輪詢等下一批事件。 | ||
| ## 環境變數 | ||
| | 變數 | 說明 | | ||
| |---|---| | ||
| | `MQ_API_KEY` | API Key(必要)。 | | ||
| | `MQ_CONTROL_PLANE_URL` | 管理 API(建 topic / 簽 key / 查用量)。 | | ||
| | `MQ_GATEWAY_URL` | 收發 API(publish / consume)。 | | ||
| | 變數 | 說明 | 預設 | | ||
| |---|---|---| | ||
| | `MQ_API_KEY` | API Key(**必要**;缺少或仍是佔位字串則不啟動)。 | — | | ||
| | `MQ_CONTROL_PLANE_URL` | 管理 API(建 topic / 簽 key / 查用量)。 | `https://msgmesh-api.alderflux.com` | | ||
| | `MQ_GATEWAY_URL` | 收發 API(publish / consume / DLQ)。 | `https://msgmesh-api.alderflux.com` | | ||
| | `MQ_REALTIME_URL` | 即時服務(`get_presence` 用)。 | `https://msgmesh-api.alderflux.com` | | ||
| 自架時三個 URL 各自指向你的服務,例如本機開發: | ||
| ```json | ||
| "env": { | ||
| "MQ_API_KEY": "<你的 API Key>", | ||
| "MQ_CONTROL_PLANE_URL": "http://localhost:8080", | ||
| "MQ_GATEWAY_URL": "http://localhost:8081", | ||
| "MQ_REALTIME_URL": "http://localhost:8082" | ||
| } | ||
| ``` | ||
| ## 工具 | ||
@@ -74,5 +96,9 @@ | ||
| 每個 tool 都帶 MCP `annotations`,client 可據此自動放行查詢、對會改動狀態的操作要求確認。 | ||
| 注意 `consume_messages` / `watch_topic` / `dlq_peek` **不是唯讀**:它們會提交 consumer group | ||
| 位移(at-most-once),取回的訊息不會再送第二次。 | ||
| ## 相關 | ||
| - API 參考(可線上試打):<https://msgapi.alderflux.com/docs> | ||
| - API 參考(可線上試打):<https://msgmesh-api.alderflux.com/docs> | ||
| - TypeScript SDK:[`@msgmesh/sdk`](https://www.npmjs.com/package/@msgmesh/sdk) | ||
@@ -79,0 +105,0 @@ |
Debug access
Supply chain riskUses debug, reflection and dynamic code execution features.
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 4 instances
29898
66.11%525
62.54%105
32.91%3
-25%