@omnisocials/mcp-server 1.51.0 → 1.51.1

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/build/client.d.ts CHANGED
@@ -8,6 +8,8 @@ export declare function formatBytes(bytes: number): string;
8
8
  export declare function formatDate(iso: string | null): string;
9
9
  export declare function formatDateTime(iso: string | null): string;
10
10
  export declare function truncate(value: unknown, len: number): string;
11
+ export declare function cutText(value: unknown, max: number): string;
12
+ export declare function wellFormed<T>(value: T): T;
11
13
  export declare function capitalize(str: string): string;
12
14
  /** Rates, averages and lengths: shown per platform, never added up across
13
15
  * platforms (two 60% hold rates are not 120%). Mirrors
package/build/client.js CHANGED
@@ -84,7 +84,37 @@ export function truncate(value, len) {
84
84
  if (!str)
85
85
  return "";
86
86
  const clean = str.replace(/\n/g, " ");
87
- return clean.length > len ? clean.slice(0, len - 1) + "…" : clean;
87
+ // Count and cut on code points: a UTF-16 slice can split an emoji.
88
+ const chars = Array.from(clean);
89
+ return chars.length > len ? chars.slice(0, len - 1).join("") + "…" : clean;
90
+ }
91
+ // Text this server sends must be well-formed UTF-16. Half an emoji (a lone
92
+ // surrogate) is legal in a JS string and JSON.stringify writes it as
93
+ // "\ud83c", but strict JSON readers (Rust serde_json, Python clients that
94
+ // validate with pydantic) refuse it and drop the connection: "Transport
95
+ // closed" instead of an answer (2026-11-03, a drafts list cut a preview
96
+ // through an emoji). cutText cuts on code points; wellFormed turns any lone
97
+ // surrogate left in a tool result into U+FFFD (a regex: Node 18 has no
98
+ // String.prototype.toWellFormed).
99
+ const LONE_SURROGATE = /[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g;
100
+ export function cutText(value, max) {
101
+ if (typeof value !== "string")
102
+ return "";
103
+ const chars = Array.from(value);
104
+ return chars.length > max ? chars.slice(0, max).join("") : value;
105
+ }
106
+ export function wellFormed(value) {
107
+ if (typeof value === "string")
108
+ return value.replace(LONE_SURROGATE, "\uFFFD");
109
+ if (Array.isArray(value))
110
+ return value.map(wellFormed);
111
+ if (value && typeof value === "object" && Object.getPrototypeOf(value) === Object.prototype) {
112
+ const out = {};
113
+ for (const [key, item] of Object.entries(value))
114
+ out[key] = wellFormed(item);
115
+ return out;
116
+ }
117
+ return value;
88
118
  }
89
119
  export function capitalize(str) {
90
120
  return str.charAt(0).toUpperCase() + str.slice(1);
package/build/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
3
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
4
  import { z } from "zod";
5
- import { OmniSocialsClient } from "./client.js";
5
+ import { OmniSocialsClient, wellFormed } from "./client.js";
6
6
  import { registerPostTools } from "./tools/posts.js";
7
7
  import { registerMediaTools } from "./tools/media.js";
8
8
  import { registerAccountTools } from "./tools/accounts.js";
@@ -31,8 +31,16 @@ const sessionState = { activeIndex: 0 };
31
31
  const getActiveClient = () => workspaceClients[sessionState.activeIndex].client;
32
32
  const server = new McpServer({
33
33
  name: "OmniSocials",
34
- version: "1.51.0",
34
+ version: "1.51.1",
35
35
  });
36
+ const rawTool = server.tool.bind(server);
37
+ server.tool = (...args) => {
38
+ const cb = args[args.length - 1];
39
+ if (typeof cb === "function") {
40
+ args[args.length - 1] = async (...a) => wellFormed(await cb(...a));
41
+ }
42
+ return rawTool(...args);
43
+ };
36
44
  // Register all tools - pass getter function so tools always use the active workspace's client
37
45
  registerPostTools(server, getActiveClient);
38
46
  registerMediaTools(server, getActiveClient);
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import { cutText } from "../client.js";
2
3
  export function registerHashtagSetTools(server, getClient) {
3
4
  server.tool("list_hashtag_sets", "List the saved hashtag sets in this workspace. Apply one to a new post by passing its name via create_post (hashtag_set) — the tags are appended to the captions or, with hashtag_placement='first_comment', posted as the auto first comment.", {}, { title: "List Hashtag Sets", readOnlyHint: true, destructiveHint: false, openWorldHint: false }, async () => {
4
5
  const result = await getClient().listHashtagSets();
@@ -17,7 +18,7 @@ export function registerHashtagSetTools(server, getClient) {
17
18
  md += `| Set ID | Name | Tags | Preview |\n`;
18
19
  md += `|--------|------|------|---------|\n`;
19
20
  for (const s of sets) {
20
- const preview = (s.preview || "").length > 80 ? `${s.preview.slice(0, 77)}…` : s.preview || "";
21
+ const preview = Array.from(s.preview || "").length > 80 ? `${cutText(s.preview, 77)}…` : s.preview || "";
21
22
  md += `| \`${s.id}\` | ${s.name} | ${s.hashtag_count} | ${preview} |\n`;
22
23
  }
23
24
  md += `\nApply a set with create_post (hashtag_set="<name>"). Edit with update_hashtag_set.`;
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- import { formatDateTime } from "../client.js";
2
+ import { cutText, formatDateTime } from "../client.js";
3
3
  const PLATFORM_EMOJI = {
4
4
  instagram: "📸",
5
5
  facebook: "📘",
@@ -41,7 +41,7 @@ function renderInboxPost(post) {
41
41
  }
42
42
  md += `\n`;
43
43
  if (post.caption) {
44
- md += `> ${post.caption.replace(/\s*\n+\s*/g, " ").slice(0, 600)}\n`;
44
+ md += `> ${cutText(post.caption.replace(/\s*\n+\s*/g, " "), 600)}\n`;
45
45
  }
46
46
  if (post.url)
47
47
  md += `🔗 ${post.url}\n`;
@@ -148,7 +148,7 @@ export function registerInboxTools(server, getClient) {
148
148
  md += `|----------|------|------|--------|----------------|----------------|\n`;
149
149
  for (const c of convos) {
150
150
  const who = c.participant?.name || c.participant?.username || "Unknown";
151
- const text = (c.last_message?.text || "").replace(/\n/g, " ").slice(0, 40);
151
+ const text = cutText((c.last_message?.text || "").replace(/\n/g, " "), 40);
152
152
  md += `| ${PLATFORM_EMOJI[c.platform] || ""} ${c.platform} | ${c.type} | ${who} | ${c.unread_count || 0} | ${text} | \`${c.conversation_id}\` |\n`;
153
153
  }
154
154
  if (result.pagination?.next_cursor) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@omnisocials/mcp-server",
3
- "version": "1.51.0",
3
+ "version": "1.51.1",
4
4
  "mcpName": "io.github.omnisocials/mcp-server",
5
5
  "description": "MCP server for OmniSocials API - manage social media posts, media, accounts, analytics, and webhooks",
6
6
  "type": "module",