@paean-ai/wechat-mcp 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -48,7 +48,7 @@ var init_constants = __esm({
48
48
  "src/constants.ts"() {
49
49
  "use strict";
50
50
  PACKAGE_NAME = "@paean-ai/wechat-mcp";
51
- PACKAGE_VERSION = "0.1.0";
51
+ PACKAGE_VERSION = "0.2.0";
52
52
  DEFAULT_BASE_URL = "https://ilinkai.weixin.qq.com";
53
53
  BOT_TYPE = "3";
54
54
  CHANNEL_VERSION = "0.1.0";
@@ -522,22 +522,30 @@ var init_client = __esm({
522
522
  async status() {
523
523
  return this.request("GET", "/status");
524
524
  }
525
- async registerAgent(agentId, name) {
526
- return this.request("POST", "/agents", { agentId, name });
525
+ async registerAgent(agentId, name, mode, gatewayUrl, gatewayType) {
526
+ return this.request("POST", "/agents", {
527
+ agentId,
528
+ name,
529
+ mode,
530
+ gatewayUrl,
531
+ gatewayType
532
+ });
527
533
  }
528
534
  async unregisterAgent(agentId) {
529
535
  return this.request("DELETE", `/agents/${encodeURIComponent(agentId)}`);
530
536
  }
531
537
  /**
532
538
  * Long-poll for messages intended for this agent.
533
- * Will block up to ~30s on the daemon side.
539
+ * Will block up to ~30s on the daemon side by default.
540
+ * Pass a custom timeoutMs for shorter waits (e.g. 500 for non-blocking read).
534
541
  */
535
- async pollMessages(agentId) {
542
+ async pollMessages(agentId, clientTimeoutMs) {
543
+ const path3 = clientTimeoutMs !== void 0 ? `/messages/${encodeURIComponent(agentId)}?timeout=${clientTimeoutMs}` : `/messages/${encodeURIComponent(agentId)}`;
536
544
  const resp = await this.request(
537
545
  "GET",
538
- `/messages/${encodeURIComponent(agentId)}`,
546
+ path3,
539
547
  void 0,
540
- 35e3
548
+ Math.max(clientTimeoutMs ?? 35e3, 35e3)
541
549
  );
542
550
  return resp.messages;
543
551
  }
@@ -567,8 +575,9 @@ function log(msg) {
567
575
  process.stderr.write(`[wechat-mcp:mcp] ${msg}
568
576
  `);
569
577
  }
570
- async function startMcpServer(agentId) {
571
- log(`Starting MCP server for agent "${agentId}"...`);
578
+ async function startMcpServer(opts) {
579
+ const { agentId, mode, gatewayUrl, gatewayType } = opts;
580
+ log(`Starting MCP server for agent "${agentId}" (mode=${mode})...`);
572
581
  let daemonInfo;
573
582
  try {
574
583
  daemonInfo = await ensureDaemon();
@@ -579,30 +588,46 @@ async function startMcpServer(agentId) {
579
588
  }
580
589
  const client = new DaemonClient(daemonInfo.port);
581
590
  try {
582
- await client.registerAgent(agentId, agentId);
583
- log(`Registered as agent "${agentId}"`);
591
+ await client.registerAgent(agentId, agentId, mode, gatewayUrl, gatewayType);
592
+ log(`Registered as agent "${agentId}" mode=${mode}`);
584
593
  } catch (err) {
585
594
  log(`Failed to register agent: ${err instanceof Error ? err.message : String(err)}`);
586
595
  process.exit(1);
587
596
  }
597
+ const instructions = [
598
+ `You are connected to WeChat via the wechat-mcp middleware (agent: "${agentId}", mode: ${mode}).`
599
+ ];
600
+ if (mode === "channel") {
601
+ instructions.push(
602
+ "Incoming WeChat messages arrive as channel notifications from this server.",
603
+ "Use the wechat_send tool to reply. Pass the sender_id from the incoming message."
604
+ );
605
+ } else if (mode === "poll") {
606
+ instructions.push(
607
+ "Use the wechat_read_messages tool to check for new incoming WeChat messages.",
608
+ "Use the wechat_send tool to reply. Pass the sender_id from the message.",
609
+ "Check for new messages periodically \u2014 they queue up until you read them."
610
+ );
611
+ } else if (mode === "gateway") {
612
+ instructions.push(
613
+ "The daemon automatically bridges incoming WeChat messages to your HTTP endpoint.",
614
+ "Use wechat_send to send proactive messages (not replies to incoming ones)."
615
+ );
616
+ }
617
+ instructions.push(
618
+ "Messages are from real WeChat users. Respond in Chinese unless the user writes in another language.",
619
+ "Keep replies concise \u2014 WeChat is a chat app. Strip markdown formatting (WeChat doesn't render it)."
620
+ );
621
+ const capabilities = { tools: {} };
622
+ if (mode === "channel") {
623
+ capabilities.experimental = { "claude/channel": {} };
624
+ }
588
625
  const mcp = new Server(
589
626
  { name: PACKAGE_NAME, version: PACKAGE_VERSION },
590
- {
591
- capabilities: {
592
- tools: {}
593
- },
594
- instructions: [
595
- `You are connected to WeChat via the wechat-mcp middleware (agent: "${agentId}").`,
596
- "Incoming WeChat messages arrive as notifications from this server.",
597
- "Use the wechat_send tool to reply. You MUST pass the sender_id from the incoming message.",
598
- "Messages are from real WeChat users. Respond in Chinese unless the user writes in another language.",
599
- "Keep replies concise \u2014 WeChat is a chat app. Strip markdown formatting (WeChat doesn't render it).",
600
- "Use wechat_get_contacts to see who has messaged before."
601
- ].join("\n")
602
- }
627
+ { capabilities, instructions: instructions.join("\n") }
603
628
  );
604
- mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
605
- tools: [
629
+ mcp.setRequestHandler(ListToolsRequestSchema, async () => {
630
+ const tools = [
606
631
  {
607
632
  name: "wechat_send",
608
633
  description: "Send a text message to a WeChat user via the shared WeChat connection",
@@ -637,8 +662,19 @@ async function startMcpServer(agentId) {
637
662
  properties: {}
638
663
  }
639
664
  }
640
- ]
641
- }));
665
+ ];
666
+ if (mode === "poll") {
667
+ tools.push({
668
+ name: "wechat_read_messages",
669
+ description: "Read pending WeChat messages addressed to this agent. Returns immediately with any queued messages (does not block).",
670
+ inputSchema: {
671
+ type: "object",
672
+ properties: {}
673
+ }
674
+ });
675
+ }
676
+ return { tools };
677
+ });
642
678
  mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
643
679
  const { name, arguments: args } = req.params;
644
680
  if (name === "wechat_send") {
@@ -673,7 +709,7 @@ async function startMcpServer(agentId) {
673
709
  if (name === "wechat_get_status") {
674
710
  try {
675
711
  const status = await client.status();
676
- const agentList = status.agents.map((a) => ` - ${a.name} (${a.agentId})`).join("\n");
712
+ const agentList = status.agents.map((a) => ` - ${a.name} (${a.agentId}) [${a.mode}]${a.gatewayUrl ? ` \u2192 ${a.gatewayUrl}` : ""}`).join("\n");
677
713
  const text = [
678
714
  `WeChat connected: ${status.wechatConnected ? "yes" : "no"}`,
679
715
  `Account: ${status.accountId || "N/A"}`,
@@ -686,48 +722,60 @@ ${agentList || " (none)"}`
686
722
  return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }] };
687
723
  }
688
724
  }
725
+ if (name === "wechat_read_messages") {
726
+ try {
727
+ const messages = await client.pollMessages(agentId, 500);
728
+ if (messages.length === 0) {
729
+ return { content: [{ type: "text", text: "No new messages." }] };
730
+ }
731
+ const formatted = messages.map(
732
+ (m) => `[${new Date(m.timestamp).toLocaleTimeString()}] ${m.senderName} (${m.senderId}): ${m.text}`
733
+ ).join("\n\n");
734
+ return { content: [{ type: "text", text: formatted }] };
735
+ } catch (err) {
736
+ return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }] };
737
+ }
738
+ }
689
739
  throw new Error(`Unknown tool: ${name}`);
690
740
  });
691
741
  const transport = new StdioServerTransport();
692
742
  await mcp.connect(transport);
693
743
  log("MCP connection ready");
694
- const pollLoop = async () => {
695
- while (true) {
696
- try {
697
- const messages = await client.pollMessages(agentId);
698
- for (const msg of messages) {
699
- log(`Forwarding message: from=${msg.senderId} text="${msg.text.slice(0, 50)}..."`);
700
- try {
701
- await mcp.notification({
702
- method: "notifications/message",
703
- params: {
704
- level: "info",
705
- data: {
706
- type: "wechat_message",
707
- sender: msg.senderName,
708
- sender_id: msg.senderId,
709
- text: msg.text,
710
- raw_text: msg.rawText,
711
- timestamp: msg.timestamp
744
+ if (mode === "channel") {
745
+ const pollLoop = async () => {
746
+ while (true) {
747
+ try {
748
+ const messages = await client.pollMessages(agentId);
749
+ for (const msg of messages) {
750
+ log(`Channel push: from=${msg.senderId} text="${msg.text.slice(0, 50)}..."`);
751
+ try {
752
+ await mcp.notification({
753
+ method: "notifications/claude/channel",
754
+ params: {
755
+ content: msg.text,
756
+ meta: {
757
+ sender: msg.senderName,
758
+ sender_id: msg.senderId
759
+ }
712
760
  }
713
- }
714
- });
715
- } catch {
716
- log(`Notification delivery failed for message ${msg.id}`);
761
+ });
762
+ } catch {
763
+ log(`Channel notification delivery failed for message ${msg.id}`);
764
+ }
717
765
  }
766
+ } catch (err) {
767
+ const errMsg = err instanceof Error ? err.message : String(err);
768
+ if (!errMsg.includes("ECONNREFUSED")) {
769
+ log(`Poll error: ${errMsg}`);
770
+ }
771
+ await new Promise((r) => setTimeout(r, 5e3));
718
772
  }
719
- } catch (err) {
720
- const errMsg = err instanceof Error ? err.message : String(err);
721
- if (!errMsg.includes("ECONNREFUSED")) {
722
- log(`Poll error: ${errMsg}`);
723
- }
724
- await new Promise((r) => setTimeout(r, 5e3));
725
773
  }
726
- }
727
- };
728
- pollLoop().catch((err) => {
729
- log(`Fatal poll error: ${err instanceof Error ? err.message : String(err)}`);
730
- });
774
+ };
775
+ pollLoop().catch((err) => {
776
+ log(`Fatal poll error: ${err instanceof Error ? err.message : String(err)}`);
777
+ });
778
+ }
731
779
  const cleanup = async () => {
732
780
  try {
733
781
  await client.unregisterAgent(agentId);
@@ -757,18 +805,22 @@ var init_store = __esm({
757
805
  agents = /* @__PURE__ */ new Map();
758
806
  defaultAgentId = null;
759
807
  startedAt = Date.now();
760
- registerAgent(agentId, name) {
808
+ registerAgent(agentId, name, mode = "poll", gatewayUrl, gatewayType) {
761
809
  const existing = this.agents.get(agentId);
762
810
  if (existing) return existing.registration;
763
811
  const registration = {
764
812
  agentId,
765
813
  name: name || agentId,
814
+ mode,
815
+ gatewayUrl,
816
+ gatewayType,
766
817
  registeredAt: (/* @__PURE__ */ new Date()).toISOString()
767
818
  };
768
819
  this.agents.set(agentId, {
769
820
  registration,
770
821
  messages: [],
771
- waiters: []
822
+ waiters: [],
823
+ conversationIds: /* @__PURE__ */ new Map()
772
824
  });
773
825
  if (!this.defaultAgentId) {
774
826
  this.defaultAgentId = agentId;
@@ -792,6 +844,9 @@ var init_store = __esm({
792
844
  getAgents() {
793
845
  return Array.from(this.agents.values()).map((s) => s.registration);
794
846
  }
847
+ getAgent(agentId) {
848
+ return this.agents.get(agentId)?.registration ?? null;
849
+ }
795
850
  getDefaultAgentId() {
796
851
  return this.defaultAgentId;
797
852
  }
@@ -801,6 +856,12 @@ var init_store = __esm({
801
856
  getUptime() {
802
857
  return Date.now() - this.startedAt;
803
858
  }
859
+ getConversationId(agentId, senderId) {
860
+ return this.agents.get(agentId)?.conversationIds.get(senderId);
861
+ }
862
+ setConversationId(agentId, senderId, convId) {
863
+ this.agents.get(agentId)?.conversationIds.set(senderId, convId);
864
+ }
804
865
  /**
805
866
  * Push a message to a specific agent's queue.
806
867
  * If an agent has a waiting long-poll, resolve it immediately.
@@ -854,6 +915,18 @@ function parseMention(text) {
854
915
  }
855
916
  return { agent: match[1].toLowerCase(), rest: match[2] || "" };
856
917
  }
918
+ function buildMessage(senderId, text, rawText, contextToken, targetAgent) {
919
+ return {
920
+ id: crypto2.randomBytes(8).toString("hex"),
921
+ senderId,
922
+ senderName: senderId.split("@")[0] || senderId,
923
+ text,
924
+ rawText,
925
+ contextToken,
926
+ targetAgent,
927
+ timestamp: Date.now()
928
+ };
929
+ }
857
930
  function routeMessage(store2, senderId, text, contextToken) {
858
931
  const { agent, rest } = parseMention(text);
859
932
  let targetAgent = null;
@@ -866,16 +939,11 @@ function routeMessage(store2, senderId, text, contextToken) {
866
939
  strippedText = text;
867
940
  }
868
941
  if (!targetAgent) return null;
869
- const message = {
870
- id: crypto2.randomBytes(8).toString("hex"),
871
- senderId,
872
- senderName: senderId.split("@")[0] || senderId,
873
- text: strippedText,
874
- rawText: text,
875
- contextToken,
876
- targetAgent,
877
- timestamp: Date.now()
878
- };
942
+ const message = buildMessage(senderId, strippedText, text, contextToken, targetAgent);
943
+ const reg = store2.getAgent(targetAgent);
944
+ if (reg?.mode === "gateway") {
945
+ return message;
946
+ }
879
947
  store2.pushMessage(targetAgent, message);
880
948
  return message;
881
949
  }
@@ -885,6 +953,109 @@ var init_router = __esm({
885
953
  }
886
954
  });
887
955
 
956
+ // src/daemon/adapters.ts
957
+ async function readSSEStream(body, handler) {
958
+ const reader = body.getReader();
959
+ const decoder = new TextDecoder();
960
+ let buffer = "";
961
+ while (true) {
962
+ const { done, value } = await reader.read();
963
+ if (done) break;
964
+ buffer += decoder.decode(value, { stream: true });
965
+ const lines = buffer.split("\n");
966
+ buffer = lines.pop() || "";
967
+ for (const line of lines) {
968
+ handler(line);
969
+ }
970
+ }
971
+ }
972
+ function getAdapter(type) {
973
+ switch (type) {
974
+ case "openai":
975
+ return openaiAdapter;
976
+ case "claw":
977
+ default:
978
+ return clawAdapter;
979
+ }
980
+ }
981
+ var clawAdapter, openaiAdapter;
982
+ var init_adapters = __esm({
983
+ "src/daemon/adapters.ts"() {
984
+ "use strict";
985
+ clawAdapter = {
986
+ name: "claw",
987
+ async send(gatewayUrl, message, signal, conversationId) {
988
+ const url = `${gatewayUrl.replace(/\/$/, "")}/api/chat`;
989
+ const body = { message };
990
+ if (conversationId) body.conversationId = conversationId;
991
+ const res = await fetch(url, {
992
+ method: "POST",
993
+ headers: { "Content-Type": "application/json" },
994
+ body: JSON.stringify(body),
995
+ signal
996
+ });
997
+ if (!res.ok || !res.body) {
998
+ throw new Error(`Gateway error: HTTP ${res.status}`);
999
+ }
1000
+ let fullContent = "";
1001
+ let returnedConversationId;
1002
+ await readSSEStream(res.body, (line) => {
1003
+ if (!line.startsWith("data: ")) return;
1004
+ try {
1005
+ const event = JSON.parse(line.slice(6));
1006
+ switch (event.type) {
1007
+ case "start":
1008
+ if (event.conversationId) returnedConversationId = event.conversationId;
1009
+ break;
1010
+ case "content":
1011
+ fullContent += event.text || "";
1012
+ break;
1013
+ case "done":
1014
+ if (event.content && !fullContent) fullContent = event.content;
1015
+ break;
1016
+ }
1017
+ } catch {
1018
+ }
1019
+ });
1020
+ return { content: fullContent, conversationId: returnedConversationId };
1021
+ }
1022
+ };
1023
+ openaiAdapter = {
1024
+ name: "openai",
1025
+ async send(gatewayUrl, message, signal) {
1026
+ const url = `${gatewayUrl.replace(/\/$/, "")}/v1/chat/completions`;
1027
+ const res = await fetch(url, {
1028
+ method: "POST",
1029
+ headers: { "Content-Type": "application/json" },
1030
+ body: JSON.stringify({
1031
+ model: "default",
1032
+ messages: [{ role: "user", content: message }],
1033
+ stream: true
1034
+ }),
1035
+ signal
1036
+ });
1037
+ if (!res.ok || !res.body) {
1038
+ throw new Error(`Gateway error: HTTP ${res.status}`);
1039
+ }
1040
+ let fullContent = "";
1041
+ await readSSEStream(res.body, (line) => {
1042
+ const trimmed = line.trim();
1043
+ if (!trimmed.startsWith("data: ") || trimmed === "data: [DONE]") return;
1044
+ try {
1045
+ const chunk = JSON.parse(trimmed.slice(6));
1046
+ const delta = chunk.choices?.[0]?.delta;
1047
+ if (delta?.content) {
1048
+ fullContent += delta.content;
1049
+ }
1050
+ } catch {
1051
+ }
1052
+ });
1053
+ return { content: fullContent };
1054
+ }
1055
+ };
1056
+ }
1057
+ });
1058
+
888
1059
  // src/daemon/server.ts
889
1060
  var server_exports2 = {};
890
1061
  __export(server_exports2, {
@@ -936,8 +1107,19 @@ async function handleRequest(req, res) {
936
1107
  json(res, 400, { error: "agentId required" });
937
1108
  return;
938
1109
  }
939
- const reg = store.registerAgent(body.agentId, body.name);
940
- log2(`Agent registered: ${reg.agentId} (${reg.name})`);
1110
+ const mode = body.mode || "poll";
1111
+ if (mode === "gateway" && !body.gatewayUrl) {
1112
+ json(res, 400, { error: "gatewayUrl required for gateway mode" });
1113
+ return;
1114
+ }
1115
+ const reg = store.registerAgent(
1116
+ body.agentId,
1117
+ body.name,
1118
+ mode,
1119
+ body.gatewayUrl,
1120
+ body.gatewayType
1121
+ );
1122
+ log2(`Agent registered: ${reg.agentId} (${reg.name}) mode=${reg.mode}${reg.gatewayUrl ? ` gateway=${reg.gatewayUrl}` : ""}`);
941
1123
  json(res, 200, reg);
942
1124
  return;
943
1125
  }
@@ -1006,6 +1188,58 @@ async function handleRequest(req, res) {
1006
1188
  json(res, 500, { error: "internal error" });
1007
1189
  }
1008
1190
  }
1191
+ async function bridgeToGateway(agentId, senderId, text, contextToken) {
1192
+ const reg = store.getAgent(agentId);
1193
+ if (!reg?.gatewayUrl) {
1194
+ log2(`Gateway bridge failed: no gatewayUrl for ${agentId}`);
1195
+ return;
1196
+ }
1197
+ const adapter = getAdapter(reg.gatewayType || "claw");
1198
+ const conversationId = store.getConversationId(agentId, senderId);
1199
+ log2(`Gateway bridge: ${agentId} \u2192 ${reg.gatewayUrl} (${adapter.name})`);
1200
+ try {
1201
+ const controller = new AbortController();
1202
+ const timeout = setTimeout(() => controller.abort(), 12e4);
1203
+ const result = await adapter.send(
1204
+ reg.gatewayUrl,
1205
+ text,
1206
+ controller.signal,
1207
+ conversationId
1208
+ );
1209
+ clearTimeout(timeout);
1210
+ if (result.conversationId) {
1211
+ store.setConversationId(agentId, senderId, result.conversationId);
1212
+ }
1213
+ if (result.content && activeAccount && contextToken) {
1214
+ const preview = result.content.length > 80 ? result.content.slice(0, 80) + "..." : result.content;
1215
+ log2(`Gateway response: ${agentId} \u2192 ${senderId}: "${preview}"`);
1216
+ for (let i = 0; i < result.content.length; i += MAX_WECHAT_MSG_LENGTH) {
1217
+ await sendTextMessage(
1218
+ activeAccount.baseUrl,
1219
+ activeAccount.token,
1220
+ senderId,
1221
+ result.content.slice(i, i + MAX_WECHAT_MSG_LENGTH),
1222
+ contextToken
1223
+ );
1224
+ }
1225
+ }
1226
+ } catch (err) {
1227
+ const errMsg = err instanceof Error ? err.message : String(err);
1228
+ log2(`Gateway bridge error for ${agentId}: ${errMsg}`);
1229
+ if (activeAccount && contextToken) {
1230
+ try {
1231
+ await sendTextMessage(
1232
+ activeAccount.baseUrl,
1233
+ activeAccount.token,
1234
+ senderId,
1235
+ `[${agentId}] Error: ${errMsg}`,
1236
+ contextToken
1237
+ );
1238
+ } catch {
1239
+ }
1240
+ }
1241
+ }
1242
+ }
1009
1243
  async function startPolling(account) {
1010
1244
  let buf = loadSyncBuf();
1011
1245
  let failures = 0;
@@ -1046,6 +1280,12 @@ async function startPolling(account) {
1046
1280
  const routed = routeMessage(store, senderId, text, contextToken);
1047
1281
  if (routed) {
1048
1282
  log2(`Message routed: from=${senderId} agent=${routed.targetAgent} text="${text.slice(0, 50)}..."`);
1283
+ const reg = routed.targetAgent ? store.getAgent(routed.targetAgent) : null;
1284
+ if (reg?.mode === "gateway") {
1285
+ bridgeToGateway(routed.targetAgent, senderId, routed.text, contextToken).catch((err) => {
1286
+ log2(`Gateway bridge async error: ${err instanceof Error ? err.message : String(err)}`);
1287
+ });
1288
+ }
1049
1289
  } else {
1050
1290
  log2(`Message dropped (no agents): from=${senderId} text="${text.slice(0, 50)}..."`);
1051
1291
  }
@@ -1111,6 +1351,7 @@ var init_server2 = __esm({
1111
1351
  init_wechat_api();
1112
1352
  init_store();
1113
1353
  init_router();
1354
+ init_adapters();
1114
1355
  store = new DaemonStore();
1115
1356
  activeAccount = null;
1116
1357
  wechatConnected = false;
@@ -1199,7 +1440,28 @@ WeChat connected successfully!`);
1199
1440
  console.log(` Account ID: ${account.accountId}`);
1200
1441
  console.log(` User ID: ${account.userId ?? "N/A"}
1201
1442
  `);
1202
- console.log("Add this to any agent's MCP config to connect:");
1443
+ console.log("Add this to any agent's MCP config to connect:\n");
1444
+ console.log(` Claude Code (channel mode \u2014 messages arrive automatically):`);
1445
+ console.log(` {`);
1446
+ console.log(` "mcpServers": {`);
1447
+ console.log(` "wechat": {`);
1448
+ console.log(` "command": "npx",`);
1449
+ console.log(` "args": ["@paean-ai/wechat-mcp", "serve", "--agent", "claude", "--mode", "channel"]`);
1450
+ console.log(` }`);
1451
+ console.log(` }`);
1452
+ console.log(` }
1453
+ `);
1454
+ console.log(` OpenClaw / other agents (gateway mode \u2014 auto-bridges to HTTP endpoint):`);
1455
+ console.log(` {`);
1456
+ console.log(` "mcpServers": {`);
1457
+ console.log(` "wechat": {`);
1458
+ console.log(` "command": "npx",`);
1459
+ console.log(` "args": ["@paean-ai/wechat-mcp", "serve", "--agent", "openclaw", "--mode", "gateway", "--gateway-url", "http://localhost:3000"]`);
1460
+ console.log(` }`);
1461
+ console.log(` }`);
1462
+ console.log(` }
1463
+ `);
1464
+ console.log(` Generic agent (poll mode \u2014 agent calls wechat_read_messages tool):`);
1203
1465
  console.log(` {`);
1204
1466
  console.log(` "mcpServers": {`);
1205
1467
  console.log(` "wechat": {`);
@@ -1216,14 +1478,29 @@ WeChat connected successfully!`);
1216
1478
  console.error("\nLogin timed out. Please try again.");
1217
1479
  process.exit(1);
1218
1480
  });
1219
- program.command("serve").description("Start MCP stdio server for an agent (used in MCP config)").requiredOption("-a, --agent <id>", "Agent identifier (e.g. claude, openclaw, paean)").action(async (opts) => {
1481
+ program.command("serve").description("Start MCP stdio server for an agent (used in MCP config)").requiredOption("-a, --agent <id>", "Agent identifier (e.g. claude, openclaw, paean)").option("-m, --mode <mode>", "Message delivery mode: channel, gateway, or poll (default: poll)", "poll").option("--gateway-url <url>", "Agent's HTTP endpoint URL (required for gateway mode)").option("--gateway-type <type>", "Gateway protocol: claw or openai (default: claw)", "claw").action(async (opts) => {
1220
1482
  const { loadCredentials: loadCredentials2 } = await Promise.resolve().then(() => (init_credentials(), credentials_exports));
1221
1483
  if (!loadCredentials2()) {
1222
1484
  process.stderr.write("No WeChat credentials. Run `wechat-mcp setup` first.\n");
1223
1485
  process.exit(1);
1224
1486
  }
1487
+ const mode = opts.mode;
1488
+ if (!["channel", "gateway", "poll"].includes(mode)) {
1489
+ process.stderr.write(`Invalid mode "${opts.mode}". Must be: channel, gateway, or poll
1490
+ `);
1491
+ process.exit(1);
1492
+ }
1493
+ if (mode === "gateway" && !opts.gatewayUrl) {
1494
+ process.stderr.write("--gateway-url is required when --mode=gateway\n");
1495
+ process.exit(1);
1496
+ }
1225
1497
  const { startMcpServer: startMcpServer2 } = await Promise.resolve().then(() => (init_server(), server_exports));
1226
- await startMcpServer2(opts.agent);
1498
+ await startMcpServer2({
1499
+ agentId: opts.agent,
1500
+ mode,
1501
+ gatewayUrl: opts.gatewayUrl,
1502
+ gatewayType: opts.gatewayType
1503
+ });
1227
1504
  });
1228
1505
  program.command("daemon").description("Start the daemon process in foreground (usually auto-started)").action(async () => {
1229
1506
  const { startDaemon: startDaemon2 } = await Promise.resolve().then(() => (init_server2(), server_exports2));
@@ -1267,7 +1544,9 @@ program.command("status").description("Show daemon and WeChat connection status"
1267
1544
  if (status.agents.length > 0) {
1268
1545
  console.log(` Agents:`);
1269
1546
  for (const a of status.agents) {
1270
- console.log(` - ${a.name} (${a.agentId})`);
1547
+ const modeStr = a.mode ? ` [${a.mode}]` : "";
1548
+ const gwStr = a.gatewayUrl ? ` \u2192 ${a.gatewayUrl}` : "";
1549
+ console.log(` - ${a.name} (${a.agentId})${modeStr}${gwStr}`);
1271
1550
  }
1272
1551
  } else {
1273
1552
  console.log(` Agents: (none)`);