@composer-app/mcp 0.0.1-beta.5 → 0.0.1-beta.7

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
@@ -4,7 +4,7 @@ import {
4
4
  logError,
5
5
  startMcpHttpServer,
6
6
  startMcpServer
7
- } from "./chunk-A5KBJAJW.js";
7
+ } from "./chunk-EJUJPBX2.js";
8
8
 
9
9
  // src/setup.ts
10
10
  import * as fs from "fs/promises";
package/dist/mcp.js CHANGED
@@ -2,14 +2,34 @@ import {
2
2
  __test_clearRooms,
3
3
  __test_dispatch,
4
4
  __test_setRoom,
5
+ _clearRoomsForTest,
6
+ _registerRoomForTest,
7
+ dispatchTool,
8
+ performAddComment,
9
+ performAddSuggestion,
10
+ performAgentStatus,
11
+ performDone,
12
+ performReplyComment,
13
+ performReplySuggestion,
14
+ performResolveThread,
5
15
  startMcpHttpServer,
6
16
  startMcpServer,
7
17
  teardownAllRooms
8
- } from "./chunk-A5KBJAJW.js";
18
+ } from "./chunk-EJUJPBX2.js";
9
19
  export {
10
20
  __test_clearRooms,
11
21
  __test_dispatch,
12
22
  __test_setRoom,
23
+ _clearRoomsForTest,
24
+ _registerRoomForTest,
25
+ dispatchTool,
26
+ performAddComment,
27
+ performAddSuggestion,
28
+ performAgentStatus,
29
+ performDone,
30
+ performReplyComment,
31
+ performReplySuggestion,
32
+ performResolveThread,
13
33
  startMcpHttpServer,
14
34
  startMcpServer,
15
35
  teardownAllRooms
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@composer-app/mcp",
3
- "version": "0.0.1-beta.5",
3
+ "version": "0.0.1-beta.7",
4
4
  "description": "Composer MCP",
5
5
  "license": "MIT",
6
6
  "author": "Josh Philpott",
@@ -10,7 +10,8 @@
10
10
  },
11
11
  "files": [
12
12
  "dist",
13
- "skill"
13
+ "skill",
14
+ "CHANGELOG.md"
14
15
  ],
15
16
  "keywords": [
16
17
  "mcp",
package/skill/SKILL.md CHANGED
@@ -36,14 +36,13 @@ two ordered steps — the field names encode the order:
36
36
  also carries the `@<your-name>` tagging hint. Relay it; you can
37
37
  paraphrase lightly but do not drop the URL or the mention syntax.
38
38
  2. `step2_callTool` — a structured `{ tool, args, why }` directive for
39
- the `composer_next_event` loop. **Do not run it inline.** Hand the
40
- loop to a background subagent (see "Monitor — runs in a subagent"
41
- below). End your turn once the subagent is spawned.
39
+ the follow-up `composer_next_event` call. Execute it AFTER step 1,
40
+ before ending your turn.
42
41
 
43
42
  Skipping step 1 leaves the user without the URL — they have no way into
44
- the doc. Skipping the subagent spawn leaves the room attached but
45
- silent; saying "I'm monitoring" without spawning the loop is a lie,
46
- every mention gets missed.
43
+ the doc. Skipping step 2 leaves the room attached but silent; saying
44
+ "I'm monitoring" without making the call is a lie, the agent will miss
45
+ every mention.
47
46
 
48
47
  **Seeding — prefer a file path when one exists.** Pick exactly one:
49
48
 
@@ -65,63 +64,16 @@ Triggers: a share prompt with a Composer URL, "/composer join <url>".
65
64
  Action: extract the URL from the prompt and call `composer_join_room({ url })`.
66
65
  Same first-run rule as Create. On success, the return carries the same
67
66
  ordered pair: output `step1_sayToUser` first (confirms the URL the user
68
- just joined), then spawn the monitor subagent — same flow as Create
69
- (see "Monitor — runs in a subagent" below).
70
-
71
- ### 3. Monitor runs in a subagent
72
-
73
- Triggers: "watch this doc", or automatically after create/join.
74
-
75
- The monitor loop is delegated to a background subagent. Polling
76
- `composer_next_event` from the main thread fills the conversation
77
- context with idle-tick chatter and mention-handling that belongs in
78
- the doc, not the terminal. Spawn the subagent, end your turn, let the
79
- doc be the conversation.
80
-
81
- **How to spawn (Claude Code).** Use the `Agent` tool with:
82
-
83
- - `subagent_type: "general-purpose"`
84
- - `run_in_background: true`
85
- - `description: "Composer monitor: <roomId>"` — short, identifies the room
86
- - `prompt`: the template below, with `{roomId}` and `{actingAs}` filled in
87
-
88
- Prompt template:
89
-
90
- > Invoke the composer skill, then run the monitor loop for room
91
- > `{roomId}` as `{actingAs}`. Your only job is the in-doc conversation:
92
- > call `composer_next_event({ roomId: "{roomId}" })` and follow each
93
- > return's `requiredNextToolCall` directive verbatim, looping until the
94
- > goodbye branch fires.
95
- >
96
- > All write tools (`composer_add_comment`, `composer_add_suggestion`,
97
- > `composer_reply_comment`, `composer_reply_suggestion`,
98
- > `composer_resolve_thread`) are yours to use as the skill describes.
99
- > The doc IS your conversation — do not narrate to the parent between
100
- > ticks.
101
- >
102
- > Exit and return when ANY of these happen:
103
- > 1. `composer_next_event` returns `kind: "timeout"` with
104
- > `recentActivity: false` — say `userMessage` EXACTLY in the doc
105
- > (it's the goodbye line), then exit.
106
- > 2. A request inside the doc clearly needs the parent terminal
107
- > (a code change, a shell command, an external action the parent
108
- > would do). Post a short reply: "I'll get on this in the terminal,"
109
- > then exit with a one-sentence summary of the ask.
110
- > 3. An unrecoverable error (auth lost, room destroyed). Exit with
111
- > the error.
112
-
113
- **Main thread after spawning.** Turn ends. Do **not** also call
114
- `composer_next_event` — two listeners on the same room means duplicated
115
- replies. If the user asks "what's happening in Composer?", check the
116
- subagent's status (`TaskList` / `TaskOutput`) rather than re-entering
117
- the loop yourself.
118
-
119
- **Inside the loop (what the subagent does).** Default timeout is 30
120
- seconds. Every return carries a structured directive — follow it
121
- without waiting for user input.
67
+ just joined), then execute `step2_callTool`.
68
+
69
+ ### 3. Monitor
70
+ Triggers: "watch this doc", or automatically after join/create.
71
+ Action: call `composer_next_event({ roomId })` in a loop (default timeout
72
+ is 30 seconds). **The loop is always-on.** Every return carries a
73
+ structured directive — follow it without waiting for user input.
122
74
 
123
75
  On `mention`: handle the event (reply / suggestion / resolve as needed),
124
- output any in-doc action, then execute `requiredNextToolCall` — which
76
+ output any user-facing text, then execute `requiredNextToolCall` — which
125
77
  is another `composer_next_event` call. Do not pause for the user to
126
78
  acknowledge. The doc is the conversation.
127
79
 
@@ -130,8 +82,9 @@ On `timeout`: check `recentActivity`.
130
82
  Execute it — the user is still working, just not tagging you.
131
83
  - `recentActivity: false` → the return includes `userMessage` and
132
84
  `instruction` but NO `requiredNextToolCall`. Say `userMessage`
133
- EXACTLY ("I've left the document…") and exit (per exit rule 1
134
- above). Do not paraphrase users recognize the line across sessions.
85
+ EXACTLY ("I've left the document…") and stop calling
86
+ `composer_next_event` until the user asks you to rejoin. Do not
87
+ paraphrase — users recognize the line across sessions.
135
88
 
136
89
  On `mention`, the event contains everything you need to act in one turn:
137
90
 
@@ -142,11 +95,13 @@ On `mention`, the event contains everything you need to act in one turn:
142
95
  threadKind: "comment" | "suggestion",
143
96
  threadText: "...", // the exact message that triggered you
144
97
  replyId?: "...", // present when it's a reply on an existing thread
145
- reason: "direct_mention" | "active_thread",
98
+ reason: "direct_mention" | "active_thread" | "solo_room",
146
99
  anchoredText?: "...", // the doc text the thread is anchored to
147
100
  headingId?: "...", // the section's headingId (use with write tools)
148
101
  headingText?: "...",
149
- sectionMarkdown?: "..." // full containing section as markdown
102
+ sectionMarkdown?: "...", // full containing section as markdown
103
+ invokerUserId?: "...", // userId of whoever triggered you (use in mentions[])
104
+ invokerName?: "..." // their display name (use in @mention literal)
150
105
  }
151
106
  ```
152
107
 
@@ -155,6 +110,38 @@ or `composer_add_comment` — no extra `composer_get_section` call is needed in
155
110
  the common case. Reach for `sectionMarkdown` to understand surrounding context
156
111
  before replying or suggesting.
157
112
 
113
+ **Ack first, then do the work.** On every `mention` event you intend to act
114
+ on, post a brief ack reply FIRST — before reading the full doc, before
115
+ drafting a suggestion. This is the "I heard you, I'm on it" signal; without
116
+ it the user stares at a silent sidebar while you think.
117
+
118
+ - Call `composer_reply_comment` (or `composer_reply_suggestion`) with
119
+ `state: "thinking"` and `mentions: [event.invokerUserId]`. For a brand-new
120
+ thread response use `composer_add_comment` / `composer_add_suggestion`
121
+ with the same `state` + `mentions` shape.
122
+ - Body: `@<invokerName> — on it` or equivalent terse phrasing (≤ 24 chars).
123
+ "On it.", "Looking.", "Checking.". No preamble, no promise of structure.
124
+ - Read `invokerUserId` and `invokerName` directly from the event payload
125
+ above — do NOT try to reconstruct them from `threadText` or from an
126
+ awareness lookup. They're the authoritative values the server resolved.
127
+ - **Skip the ack** for empty thank-yous or conversational dead-ends you
128
+ wouldn't reply to at all (see `reason` gates below) — the ack is a
129
+ signal of intent to act, not a reflex.
130
+ - **Skip the ack** when this mention is a yes-variant confirmation inside
131
+ an ask-then-auto-suggest round-trip (see §"Auto-suggest when the user
132
+ confirms a concrete proposal"): if the mention fired as `active_thread`
133
+ AND your own prior reply on this thread was a concrete yes/no
134
+ counter-proposal AND the human's message is a yes-variant, drop the
135
+ suggestion directly via `composer_add_suggestion` with
136
+ `fromThreadId: event.threadId`. The "I heard you" signal was already
137
+ delivered in the prior ack; a second ack here is noise. Only the
138
+ initial substantive-work mention gets an ack.
139
+
140
+ Once the ack is posted, drive state with `composer_agent_status` as you
141
+ work (see §"Progress status" below). On completion, rewrite the ack to
142
+ its final form in the same call that transitions to `ready` — do not
143
+ post a duplicate pointer reply.
144
+
158
145
  **The event only carries the triggering message.** If the thread already has
159
146
  replies (from the user, or from another agent), call `composer_get_thread({
160
147
  roomId, threadId })` before replying. The return has every reply with author
@@ -180,13 +167,19 @@ need to catch up on what's already been said.
180
167
  drafts they're jotting down).
181
168
  When in doubt, reply — the user can always ignore you.
182
169
 
170
+ **When you skip a mention, call `composer_done({ roomId, threadId })`.**
171
+ The instant a mention is dequeued via `composer_next_event`, the server
172
+ publishes a `thinking…` indicator on that thread so the user sees you
173
+ picked it up — there's no flicker waiting for your first
174
+ `composer_agent_status` call. If you reply, the `state: "ready"`
175
+ transition clears the indicator on its own. If you skip without
176
+ replying, nothing else clears it and the user's avatar pulses forever.
177
+ `composer_done` is idempotent — safe to call even if the indicator was
178
+ already cleared.
179
+
183
180
  ### 4. Act
184
- Triggers: direct requests in the terminal like "add a summary to section 2".
185
- Action: the main thread is also attached to the room call the write
186
- tools from here and report back concisely. Don't hand terminal directives
187
- to the monitor subagent; the subagent handles in-doc mentions, the main
188
- thread handles in-terminal asks. They share the same MCP, so writes from
189
- either show up in the doc.
181
+ Triggers: direct requests like "add a summary to section 2".
182
+ Action: already attached; call the write tools and report back concisely.
190
183
 
191
184
  ## Tools
192
185
 
@@ -200,13 +193,25 @@ Read tools:
200
193
 
201
194
  Write tools:
202
195
  - `composer_add_comment` — NEW comment on any span in the doc. Use when
203
- raising something outside the current thread's anchor.
196
+ raising something outside the current thread's anchor. Accepts an
197
+ optional `state` ("thinking" on an ack) so the first-ever reply on a
198
+ thread can be the ack.
204
199
  - `composer_add_suggestion` — propose a text replacement (lands as
205
200
  pending). Can target any span — `fromThreadId` inherits the source
206
201
  thread's anchor; `anchor` specifies a span elsewhere. Call it multiple
207
202
  times in a turn to suggest in several spots.
208
203
  - `composer_reply_comment` / `composer_reply_suggestion` — reply on an
209
- existing thread.
204
+ existing thread. Accept an optional `state` field for the ack-first
205
+ flow; post with `state: "thinking"` and the invoker in `mentions[]`.
206
+ - `composer_agent_status` — drive state transitions (`thinking →
207
+ working → replying → ready`) and rewrite the final ack body on the
208
+ reply/comment/suggestion you own. See §"Progress status".
209
+ - `composer_done` — clear the live indicator on a thread you decided
210
+ NOT to reply to. Required when you skip a mention (off-topic chatter,
211
+ self-mention, conversational dead-end), because the indicator is
212
+ published optimistically the moment a mention is dequeued. Replying
213
+ with `state: "ready"` clears it on its own — only call `composer_done`
214
+ for the skip case.
210
215
  - `composer_resolve_thread` — mark resolved.
211
216
 
212
217
  There is no "just edit" tool in v1. All text changes go through suggestions
@@ -220,11 +225,18 @@ a chat window. Long replies get unwieldy fast. Rules:
220
225
  - Answer in 1–3 sentences. Prefer one.
221
226
  - Reply directly to the question asked — no preamble ("Great question!"),
222
227
  no restating the ask, no trailing summary of what you just did.
223
- - **If you post a suggestion in response to the thread, the suggestion IS
224
- your reply. Do not also post a comment reply.** The suggestion renders
225
- as a Replace/With card in the sidebar already; a pointer comment ("see
226
- the suggestion") just duplicates what the user can already see.
227
- Silent suggestion-only responses are correct and expected.
228
+ - **When the substantive answer is a standalone artifact, that artifact
229
+ IS the reply.** A "standalone artifact" means a suggestion, a cross-span
230
+ comment on a different anchor, or a separate document link. In that
231
+ case do NOT post a duplicate pointer comment instead, rewrite your
232
+ existing ack in place to a thin pointer (`"Posted a suggestion below."`,
233
+ `"Added a comment in Section 3."`, `"See doc <link>."`) and set
234
+ `state: "ready"` on the same call. The suggestion / cross-span comment
235
+ renders as its own card; a pointer reply just duplicates what the user
236
+ can already see.
237
+ - **When the substantive answer IS a reply with text**, rewrite the ack
238
+ to that text and clear the state (or set `state: "ready"`) in the same
239
+ `composer_agent_status` call. Do not post a separate follow-up reply.
228
240
  - If the answer genuinely needs structure (a list of 4+ items, code, a
229
241
  table) and a suggestion isn't the right shape, post it as a suggestion
230
242
  in the doc body instead of as a comment reply.
@@ -349,8 +361,10 @@ Two turns, not three:
349
361
  2. **Turn 2 (commit on confirmation).** When the user replies with any
350
362
  variant of yes ("yes", "sure", "go for it", "perfect", a thumbs-up
351
363
  emoji), call `composer_add_suggestion` with `fromThreadId: event.threadId`
352
- and the concrete replacement. Do NOT also post a comment reply — the
353
- suggestion card IS your reply (see "Keep comment text terse" above).
364
+ and the concrete replacement. **Skip the ack in this case** — the
365
+ prior reply already delivered the "I heard you" signal, and the
366
+ suggestion card IS your answer. Do NOT also post a comment reply
367
+ (see §"Keep comment text terse" and the ack-skip rule in §"Monitor").
354
368
 
355
369
  If the user says no / picks a different value / redirects, follow their
356
370
  lead — do not post the original proposal anyway.
@@ -360,6 +374,108 @@ abstract to guess a number), ask a clarifying question instead. Don't
360
374
  propose something generic just to fill the slot — "Would you like me
361
375
  to shorten this?" is worthless without a target length.
362
376
 
377
+ ### Progress status
378
+
379
+ Once the ack is posted, drive the owning reply through a small state
380
+ machine using `composer_agent_status`. The UI animates state changes
381
+ on the reply card; the user sees progress instead of a silent pause.
382
+
383
+ ```
384
+ composer_agent_status({
385
+ roomId,
386
+ threadId,
387
+ replyId?, // identifies which reply you own; omit for thread-head
388
+ state, // "thinking" | "working" | "replying" | "ready"
389
+ text?, // rewrite the reply body (only meaningful on "ready")
390
+ note?, // short human-readable progress line
391
+ kind? // "comment" | "suggestion" — disambiguates head records
392
+ })
393
+ ```
394
+
395
+ State machine: **`thinking → working → replying → ready`**.
396
+
397
+ - `thinking` — initial ack, set by the reply/add tool that posted it.
398
+ - `working` — you're doing substantive work (reading the doc, computing,
399
+ drafting). Set this whenever you expect a gap.
400
+ - `replying` — you're about to write the final text. Optional, brief.
401
+ - `ready` — done. Call with `text: "<final ack body>"` to rewrite the
402
+ ack in place atomically; the awareness heartbeat for this entry is
403
+ pruned in the same call.
404
+
405
+ **Minimum-visible rule.** The UI collapses state changes that happen
406
+ faster than 400 ms, so don't worry about being too fast. DO worry about
407
+ being SILENT for more than ~2 seconds without calling
408
+ `composer_agent_status({ state: "working" })` — silence is the failure
409
+ mode. If you're about to do something slow (fetch the full doc, compute
410
+ a non-trivial diff, call another tool), transition to `working` first.
411
+
412
+ Use `note` to surface human-readable progress where it helps — e.g.
413
+ `note: "Reading section 3…"`, `note: "Drafting suggestion…"`,
414
+ `note: "Checking cross-references…"`. Short sentence fragments; the
415
+ user reads them at a glance.
416
+
417
+ On completion, one call does everything:
418
+
419
+ ```
420
+ composer_agent_status({
421
+ roomId, threadId, replyId,
422
+ state: "ready",
423
+ text: "Posted a suggestion below."
424
+ })
425
+ ```
426
+
427
+ This rewrites the ack body and prunes the awareness heartbeat
428
+ atomically. Do NOT post a separate reply to say "done" — the rewrite
429
+ IS the final reply.
430
+
431
+ ### Worked example — ack-then-suggestion flow
432
+
433
+ A mention arrives; the user asked you to tighten a sentence in Section 3.
434
+ The full round-trip is four tool calls:
435
+
436
+ ```
437
+ // 1. Mention arrives via composer_next_event:
438
+ // { kind: "mention", threadId: "t_abc", invokerUserId: "u_jess",
439
+ // invokerName: "Jess", reason: "direct_mention", ... }
440
+
441
+ // 2. Ack first — posts the "on it" reply with a thinking indicator.
442
+ const { replyId } = composer_reply_comment({
443
+ roomId,
444
+ threadId: "t_abc",
445
+ text: "@Jess — on it",
446
+ mentions: ["u_jess"],
447
+ state: "thinking",
448
+ });
449
+
450
+ // 3. Transition to working before any slow step.
451
+ composer_agent_status({
452
+ roomId,
453
+ threadId: "t_abc",
454
+ replyId,
455
+ state: "working",
456
+ note: "Reading section 3…",
457
+ });
458
+
459
+ // 4. Do the work and post the substantive artifact.
460
+ composer_add_suggestion({
461
+ roomId,
462
+ fromThreadId: "t_abc",
463
+ replacementText: "…",
464
+ });
465
+
466
+ // 5. Rewrite the ack to a thin pointer and mark ready atomically.
467
+ composer_agent_status({
468
+ roomId,
469
+ threadId: "t_abc",
470
+ replyId,
471
+ state: "ready",
472
+ text: "Posted a suggestion below.",
473
+ });
474
+ ```
475
+
476
+ No extra pointer reply. No "done" message. The rewrite + suggestion
477
+ card together are the complete response.
478
+
363
479
  ## Anchors
364
480
 
365
481
  Write tools take: