Skip to content

chore: rename to @doist/comms-mcp on @doist/comms-sdk#1

Merged
amix merged 6 commits into
mainfrom
amix/comms-mcp-rename
May 22, 2026
Merged

chore: rename to @doist/comms-mcp on @doist/comms-sdk#1
amix merged 6 commits into
mainfrom
amix/comms-mcp-rename

Conversation

@amix
Copy link
Copy Markdown
Member

@amix amix commented May 22, 2026

Context

Fork-and-rename of the MCP server from Twist to Comms. Drops @doist/twist-sdk@2.6.0 for @doist/comms-sdk@^0.2.0 (same surface, new product name) and resets the version to 0.1.0.

What was changed

  • Package: @doist/twist-ai@doist/comms-mcp; bincomms-mcp; mcpNamecom.doist/comms-mcp.
  • SDK: TwistApiCommsApi, TwistToolCommsTool, getFullTwistURLgetFullCommsURL; twist-tool.tscomms-tool.ts.
  • Env: TWIST_API_KEYCOMMS_API_KEY, TWIST_BASE_URLCOMMS_BASE_URL.
  • IDs: thread/channel/comment/conversation/message/group IDs are now opaque base58 UUIDv7 strings across Zod schemas, fixtures, and URL helpers. workspaceId/userId stay numeric.
  • Tools: all 18 adapted to the Comms SDK signatures. client.batch(...)Promise.all, with limitedAll (p-limit@^6) gating fan-outs in list-channels, fetch-inbox, and mark-done.
  • mark-done data-loss fix: archive + channelId + !workspaceId is rejected up front; workspaceId + channelId now scopes to that channel instead of archiving the whole workspace.
  • away: unregistered from the MCP server (Comms SDK has no away endpoint); set/clear throw an explicit unsupported-by-SDK error. Symbol still exported for parity.
  • URLs: twist.comcomms.todoist.com.
  • Docs: README, AGENTS, CHANGELOG, .env.example rewritten for Comms.
  • CI: dropped twist-post-action announce + twist-ai-integrations dispatch (not relevant here).

Validated

  • npm run type-check clean
  • npm test — 194 passing / 19 suites
  • npm run build clean
  • Smoke-tested user-info, list-channels, fetch-inbox against staging

Out of scope

  • Restoring away (needs a Comms SDK endpoint first).

amix added 3 commits May 21, 2026 20:28
Renames every identifier, import, env var, and prose mention from
Twist/twist-sdk to Comms/comms-sdk:

- package.json: @doist/twist-ai → @doist/comms-mcp; depend on
  @doist/comms-sdk@^0.2.0; reset version to 0.1.0; bin → comms-mcp;
  mcpName → com.doist/comms-mcp
- README, AGENTS, .env.example, CHANGELOG: rewritten for Comms
- env: TWIST_API_KEY → COMMS_API_KEY, TWIST_BASE_URL → COMMS_BASE_URL
- types: TwistApi → CommsApi, TwistTool → CommsTool, TwistObjectType
  → CommsObjectType; twist-tool.ts → comms-tool.ts
- URL helpers: getFullTwistURL → getFullCommsURL, twist.com →
  comms.todoist.com
- MCP server name: comms-mcp-server; tool title prefix "Comms: …"
- release.yml: drop twist-post-action announcement + twist-ai-integrations
  dispatch (not relevant to this repo)

Type-check is intentionally still broken. The Comms SDK surface differs
from Twist's in three load-bearing ways and each tool needs targeted
adaptation in follow-ups:

1. CommsApi has no `batch()` primitive — every batching tool
   (load-thread, load-conversation, mark-done, search-content,
   get-mentions, fetch-inbox, get-groups, get-workspaces, list-channels,
   get-users) needs to switch to Promise.all over individual client calls
   (or we add a batch primitive to comms-sdk).
2. Object IDs are base58 UUIDv7 strings, not numbers. Zod schemas,
   structured output types, test fixtures, and url-helpers all assume
   numeric IDs.
3. User shape differs: `name`/`bot`/`defaultWorkspace`/`awayMode` are
   gone; `fullName`/`shortName` replace `name`. user-info needs a
   field-by-field remap.

Once these are addressed per-tool, jest snapshots also need regenerating.
Brings the type-check, jest suite, and build to a clean state on top of
the prior mechanical rename. Per-tool highlights:

- away: stubbed `get` to honestly report `isAway: false` (no SDK
  endpoint exposes away state); `set` and `clear` now throw with a
  clear unsupported-by-SDK error instead of silently returning a fake
  success. Also documented in mcp-server.ts instructions.
- list-channels, get-users, get-workspaces, get-groups, fetch-inbox,
  load-thread, load-conversation, search-content, get-mentions,
  mark-done: replaced `client.batch(...)` with `Promise.all([...])`;
  switched to the new positional/args-object SDK signatures; per-call
  error tolerance preserved where the original code had a fallback.
- user-info: maps `User.fullName`/`shortName`/`lang`; drops `bot`/
  `defaultWorkspace`/`awayMode` (gone from the Comms user payload).
- build-link / create-thread / update-object / delete-object / react /
  reply: switched ID Zod schemas and types from `z.number()` to
  `z.string()` for thread/comment/conversation/message/channel/group IDs
  (workspaceId and userId remain numeric).
- mark-done: fix data-loss bug — passing `workspaceId + channelId`
  previously called `archiveAll({ workspaceId })`, archiving the whole
  workspace. Now passes `{ workspaceId, channelIds: [channelId] }` and
  similarly scopes `markAllRead`. Channel-only archive (no workspaceId)
  is still unsupported by the SDK and is documented as a skip.
- fetch-inbox: dedupe channel IDs before calling `channels.getChannel`,
  so an inbox dominated by threads from the same channel hits the
  endpoint once per channel rather than once per thread.

Foundations:
- src/utils/output-schemas.ts: ID fields switched to z.string() where
  the API uses opaque base58 UUIDv7s; UserInfo / GetWorkspaces / GetUsers
  schemas re-aligned to the new User/WorkspaceUser/Workspace shapes.
- src/utils/url-helpers.ts: thin wrappers around getFullCommsURL with
  string IDs.
- src/utils/test-helpers.ts: TEST_IDS now uses string fixtures for
  channel/thread/comment/conversation/message/group IDs; createMockUser
  / createMockWorkspace match the new schemas.

Validated:
- npm run type-check — clean
- npm test — 194 passed / 19 suites
- npm run build — clean
- smoke-tested user-info, list-channels, and fetch-inbox against
  staging (workspace 48121); all return real data with string IDs.
Direction review surfaced two "lie about state" failure modes and an
absent latency hedge. Doistbot rounds 2 and 3 surfaced one data-loss
bug (workspaceId + channelId silently archiving the whole workspace),
one Node-version mismatch (p-limit@7 requires Node 20), and a few test
quality nits worth taking.

away:
  - Unregistered from the MCP server (was advertising a broken tool to
    the model). Symbol stays exported from src/index.ts so the
    importable-tools surface still gets the loud failure.
  - All three actions now throw an explicit "unsupported by Comms SDK"
    error. Previously `get` returned `isAway: false`, which is a silent
    lie when the user is genuinely away — the SDK simply can't tell us.
  - mcp-server.ts instructions updated (away note removed; tool gone).
  - tool-annotations.test no longer expects away in the registry.

mark-done:
  - Reject `archive && channelId && !workspaceId` up front instead of
    silently dropping the archive step while reporting success.
  - Already-fixed (in a prior commit): pass `{ workspaceId, channelIds:
    [channelId] }` when both selectors are present so the call stays
    scoped to that channel.
  - Added two regression tests: the channel-only rejection, and the
    scoped-archive path, including assertions on the user-visible
    selectors payload (not just the SDK call shape).

Concurrency:
  - Added `limitedAll(items, fn)` helper in src/utils/concurrency.ts
    that wraps `Promise.all` with `p-limit(8)`. One implementation,
    used by `list-channels` (creator lookup), `fetch-inbox` (channel
    hydration), and `mark-done` (individual IDs). Keeps inbox/mark-done
    fan-outs inside the undici socket pool / rate limiter on large
    workspaces; today the burst is invisible.
  - Pinned `p-limit@^6` (Node 18 compatible) instead of `@^7` (Node 20+)
    to match the README's "Node 18+" requirement.
  - jest transformIgnorePatterns extended for p-limit's ESM-only chain
    (p-limit, yocto-queue, yoctocolors).

Decisions intentionally not taken:
  - Doistbot suggested auto-disabling archive for channel-only bulk
    operations rather than throwing. Rejected — the explicit throw was
    the expert's prescription precisely because silent skipping is the
    failure mode that broke the previous version.
  - Doistbot suggested removing the `away` export entirely from
    src/index.ts. Rejected — the importable-tools surface intentionally
    keeps the symbol so consumers get the same loud-failure contract
    they get from the MCP server (which is now: it's not there at all).

Validated:
  - npm run type-check — clean
  - npm test — 194 / 19 suites
  - npm run build — clean
  - Smoke-tested list-channels against staging — still returns string
    channel IDs end-to-end.
@amix amix requested a review from scottlovegrove May 22, 2026 05:06
@amix amix added the 👀 Show PR PR must be reviewed before or after merging label May 22, 2026
@amix amix marked this pull request as ready for review May 22, 2026 05:08
@amix amix requested a review from doistbot May 22, 2026 05:08
@amix
Copy link
Copy Markdown
Member Author

amix commented May 22, 2026

@doistbot /review

Comment thread src/tools/away.ts Outdated
Comment thread src/tools/away.ts Outdated
Comment thread jest.config.js Outdated
amix added 2 commits May 22, 2026 11:17
Scott flagged the away stub as user-hostile (LLM might echo "SDK" jargon
at users) and the yoctocolors entry in transformIgnorePatterns as
suspicious. This is a hard fork — no point keeping a tool that exists
only to throw, or pretending we need to transform a package only
semantic-release pulls in.

- Delete src/tools/away.ts + its test
- Remove away from ToolNames, src/index.ts (both `tools` map and named
  exports), scripts/run-tool.ts, mcp-server.ts comment, and the
  tool-annotations test commentary
- Drop AWAY_ACTIONS / AwayAction / AwayOutputSchema / AwayOutput from
  src/utils/output-schemas.ts (also removed from StructuredOutputSchema
  union)
- Drop the "away" bullet from README's tool list
- jest.config.js: remove yoctocolors from transformIgnorePatterns
  (only semantic-release > execa pulls it in, never touched at test
  time); yocto-queue stays — it's p-limit@6's only runtime dep
Hard fork — the schema should describe what's in the Comms user payload,
not what Twist's payload "no longer carries." Kept the one-liner
mapping `name` → `User.fullName` since that's the only non-obvious bit.
@amix amix merged commit f0b7e12 into main May 22, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

👀 Show PR PR must be reviewed before or after merging

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants