@composer-app/mcp 0.0.1-beta.2 → 0.0.1-beta.4

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/mcp.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  startMcpHttpServer,
3
3
  startMcpServer
4
- } from "./chunk-SZ67UYAY.js";
4
+ } from "./chunk-UVXQZ2TN.js";
5
5
  export {
6
6
  startMcpHttpServer,
7
7
  startMcpServer
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@composer-app/mcp",
3
- "version": "0.0.1-beta.2",
3
+ "version": "0.0.1-beta.4",
4
4
  "description": "Composer MCP",
5
5
  "license": "MIT",
6
6
  "author": "Josh Philpott",
@@ -0,0 +1,8 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "mcp__composer-mcp__composer_create_room",
5
+ "mcp__composer-mcp__composer_join_room"
6
+ ]
7
+ }
8
+ }
package/skill/SKILL.md CHANGED
@@ -12,20 +12,79 @@ to create, join, monitor, or act in a Composer doc.
12
12
 
13
13
  ### 1. Create
14
14
  Triggers: "send this markdown to Composer", "make a Composer doc with this".
15
- Action: call `composer_create_room({ name, seedMarkdown, actingAs: "<user's name>'s Agent" })`.
16
- Return the `browserUrl`. You are already attached; enter monitor mode.
15
+ Action: call `composer_create_room({ ... })`.
16
+
17
+ **First run only — ask the user what to call you.** If you have no saved
18
+ name on this machine, the MCP returns an error instructing you to stop
19
+ and ask. Offer one suggested default they can accept with a tap:
20
+
21
+ - If you know the user's first name, suggest `"<FirstName>'s Agent"`
22
+ (e.g. `"Josh's Agent"`).
23
+ - Otherwise suggest something playful that isn't a model family — `Monty`,
24
+ `Gerty`, `Rosie`, `Otto`, `Pip`. Do **not** suggest Claude, Gemini,
25
+ Sonnet, Opus, Haiku, GPT, or any other model name.
26
+
27
+ Phrase it like: *"I'll go by Monty in Composer docs — sound good, or pick
28
+ your own?"* Retry the tool call with their answer as `actingAs`. It
29
+ persists to `~/.composer/user.json` and is reused forever.
30
+
31
+ **On success** (first run or any subsequent run), the return gives you
32
+ two ordered steps — the field names encode the order:
33
+
34
+ 1. `step1_sayToUser` — output this FIRST. It always starts with the
35
+ `browserUrl` because the user needs the link to open the doc; it
36
+ also carries the `@<your-name>` tagging hint. Relay it; you can
37
+ paraphrase lightly but do not drop the URL or the mention syntax.
38
+ 2. `step2_callTool` — a structured `{ tool, args, why }` directive for
39
+ the follow-up `composer_next_event` call. Execute it AFTER step 1,
40
+ before ending your turn.
41
+
42
+ Skipping step 1 leaves the user without the URL — they have no way into
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.
46
+
47
+ **Seeding — prefer a file path when one exists.** Pick exactly one:
48
+
49
+ - `seedMarkdownPath: "<absolute path>"` — **preferred** whenever the markdown
50
+ already lives in a file on disk (a plan, a journal entry, any `.md` the
51
+ user pointed at). The MCP reads the file itself, so you don't stream the
52
+ whole document through the model. This is faster and avoids burning
53
+ tokens re-emitting content that already exists.
54
+ - `seedMarkdown: "<inline string>"` — only when the content was generated
55
+ in this turn and isn't on disk yet.
56
+
57
+ The seed file is read **once** at creation. Composer never writes back to
58
+ it — edits made in the room stay in the room. Do not modify the source
59
+ file while the user is working in Composer unless they explicitly ask you
60
+ to sync changes back.
17
61
 
18
62
  ### 2. Join
19
63
  Triggers: a share prompt with a Composer URL, "/composer join <url>".
20
- Action: extract the URL and the acting-as name from the prompt. Call
21
- `composer_join_room({ url, actingAs })`. Announce the doc outline and enter
22
- monitor mode.
64
+ Action: extract the URL from the prompt and call `composer_join_room({ url })`.
65
+ Same first-run rule as Create. On success, the return carries the same
66
+ ordered pair: output `step1_sayToUser` first (confirms the URL the user
67
+ just joined), then execute `step2_callTool`.
23
68
 
24
69
  ### 3. Monitor
25
70
  Triggers: "watch this doc", or automatically after join/create.
26
- Action: call `composer_next_event({ roomId, timeoutSec: 300 })` in a loop.
27
- On `timeout`: loop again up to ~30 minutes, then ask the user if they want
28
- to keep watching.
71
+ Action: call `composer_next_event({ roomId })` in a loop (default timeout
72
+ is 10 minutes). **The loop is always-on.** Every return carries a
73
+ structured directive — follow it without waiting for user input.
74
+
75
+ On `mention`: handle the event (reply / suggestion / resolve as needed),
76
+ output any user-facing text, then execute `requiredNextToolCall` — which
77
+ is another `composer_next_event` call. Do not pause for the user to
78
+ acknowledge. The doc is the conversation.
79
+
80
+ On `timeout`: check `recentActivity`.
81
+ - `recentActivity: true` → the return includes `requiredNextToolCall`.
82
+ Execute it — the user is still working, just not tagging you.
83
+ - `recentActivity: false` → the return includes `userMessage` and
84
+ `instruction` but NO `requiredNextToolCall`. Say `userMessage`
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.
29
88
 
30
89
  On `mention`, the event contains everything you need to act in one turn:
31
90
 
@@ -49,22 +108,54 @@ or `composer_add_comment` — no extra `composer_get_section` call is needed in
49
108
  the common case. Reach for `sectionMarkdown` to understand surrounding context
50
109
  before replying or suggesting.
51
110
 
52
- **Important:** `reason: "active_thread"` means the user replied on a thread
53
- the agent has already participated in no explicit `@agent` was required.
54
- Decide whether to respond based on content, not just the trigger; if the
55
- reply is plainly addressed to another person, or is a thank-you that doesn't
56
- need an answer, it's fine to leave it alone (don't emit an empty reply just
57
- to acknowledge). If it clearly asks you something, answer it.
111
+ **The event only carries the triggering message.** If the thread already has
112
+ replies (from the user, or from another agent), call `composer_get_thread({
113
+ roomId, threadId })` before replying. The return has every reply with author
114
+ and timestamp essential when the user tagged you mid-conversation and you
115
+ need to catch up on what's already been said.
116
+
117
+ **`reason` is your main filter:**
118
+
119
+ - `"direct_mention"` — sidecar or text explicitly tagged you. Always
120
+ reply (unless the content is purely a thank-you that doesn't need an
121
+ answer — never emit empty acknowledgements).
122
+ - `"active_thread"` — a plain reply on a thread you're already in. Reply
123
+ if the content invites one; skip if it's plainly addressed to another
124
+ person, is a thank-you, or is otherwise a conversational dead-end.
125
+ - `"solo_room"` — you're alone with one human who didn't tag anyone.
126
+ **Default to a helpful reply** — they almost certainly want your
127
+ input. Skip only when the text reads like:
128
+ - a **note-to-self** ("TODO: fix this later", "remember to check
129
+ the date"),
130
+ - a bare **acknowledgement** ("k", "got it", "done"),
131
+ - a stage direction / aside ("ugh", "hmm"),
132
+ - or anything that visibly isn't pointed at you (quoted text,
133
+ drafts they're jotting down).
134
+ When in doubt, reply — the user can always ignore you.
58
135
 
59
136
  ### 4. Act
60
137
  Triggers: direct requests like "add a summary to section 2".
61
138
  Action: already attached; call the write tools and report back concisely.
62
139
 
63
- ## Write tools
64
-
65
- - `composer_add_comment` — comment anchored to text.
66
- - `composer_add_suggestion` — propose a text replacement (lands as pending).
67
- - `composer_reply_comment` / `composer_reply_suggestion` reply on a thread.
140
+ ## Tools
141
+
142
+ Read tools:
143
+ - `composer_get_full_doc` — entire doc as markdown.
144
+ - `composer_get_section` — one section by `headingId`.
145
+ - `composer_get_thread` — full state of a thread (all replies, anchor,
146
+ containing section). Call this when `composer_next_event` surfaces a
147
+ mention on a thread that already has history — the event gives you
148
+ only the triggering message.
149
+
150
+ Write tools:
151
+ - `composer_add_comment` — NEW comment on any span in the doc. Use when
152
+ raising something outside the current thread's anchor.
153
+ - `composer_add_suggestion` — propose a text replacement (lands as
154
+ pending). Can target any span — `fromThreadId` inherits the source
155
+ thread's anchor; `anchor` specifies a span elsewhere. Call it multiple
156
+ times in a turn to suggest in several spots.
157
+ - `composer_reply_comment` / `composer_reply_suggestion` — reply on an
158
+ existing thread.
68
159
  - `composer_resolve_thread` — mark resolved.
69
160
 
70
161
  There is no "just edit" tool in v1. All text changes go through suggestions
@@ -120,6 +211,104 @@ Picking a broader `textToFind` than the user asked for (the whole sentence
120
211
  when they highlighted a phrase, the whole paragraph when they asked about
121
212
  one clause) is the main failure mode. When in doubt, default to path 1.
122
213
 
214
+ ### Cross-span: reply and suggest anywhere in the doc
215
+
216
+ A comment/reply thread is anchored to *one* span, but your response is
217
+ not confined to that span. When the user's question (or your own
218
+ judgment) points elsewhere:
219
+
220
+ - **Suggest a change to different text.** Call `composer_add_suggestion`
221
+ with `anchor: { headingId, textToFind }` pointing at the target. You
222
+ can post multiple suggestions in one turn — e.g., the user says "the
223
+ flour amount is off and so is the bake time" → two suggestions, each
224
+ anchored to its own span.
225
+ - **Open a new thread elsewhere.** Call `composer_add_comment` with
226
+ its own anchor. Useful for cross-references ("see also the
227
+ conclusion") or raising something the user didn't ask about but
228
+ should see.
229
+ - **Still reply on the original thread too** if the user's question
230
+ deserves a direct answer — but only when the reply says something
231
+ the suggestion/new-comment doesn't already convey. Don't post
232
+ "see my suggestion"; the card IS the answer.
233
+
234
+ Order of operations for a multi-span response: post the suggestion(s)
235
+ / new comment(s) first, then (optionally) a reply on the originating
236
+ thread pointing out the bigger picture. That way the originating
237
+ thread's reply can reference what you just did.
238
+
239
+ ### Suggest completely — accepting must leave the doc correct
240
+
241
+ Goal: the user clicks Accept and is done. They should never have to
242
+ hunt down downstream edits you forgot.
243
+
244
+ **Load enough context before you suggest.** The event gives you
245
+ `sectionMarkdown` for the containing section — usually enough for
246
+ wording changes. For anything that might appear elsewhere in the doc
247
+ (numbers, names, product/feature references, versions, dates,
248
+ terminology, heading text), call `composer_get_full_doc` first.
249
+ One extra read is much cheaper than shipping a broken doc.
250
+
251
+ **Scan for ripples before posting.** Common ones:
252
+
253
+ - **Counts and enumerations.** "The three examples below" / "three
254
+ things to remember" — if you add or remove an item, update the
255
+ count and any ordinal words ("first", "finally").
256
+ - **Cross-references.** "As in section 2", "see the conclusion",
257
+ "per step 3 above". If your edit moves or renames the target,
258
+ update the reference too.
259
+ - **Restated facts.** Recipes reference an ingredient twice; release
260
+ notes cite a version in both intro and body; specs quote a number
261
+ in a heading and a paragraph. One fact, multiple spans — cover
262
+ all of them.
263
+ - **Subject/verb and pronoun agreement.** "X and Y are" → trim to
264
+ just X → "X is". Changing from plural to singular ripples.
265
+ - **Neighboring flow.** Rewriting sentence 2 can break sentence 3
266
+ ("This is why..."). Fix the continuation.
267
+ - **Heading changes.** If you change heading text, any prose that
268
+ says "see the Intro section" may need updating.
269
+
270
+ **Post every ripple as its own suggestion, in the same turn.** Don't
271
+ leave the user to hunt for companion edits. The tool accepts one
272
+ anchor per call — call it multiple times. Each suggestion stays
273
+ tight to its own span (this is NOT oversuggesting — it's covering
274
+ the actual surface of the change).
275
+
276
+ If a ripple is too structural for a clean suggestion (reorder a list,
277
+ split a paragraph), post the ones you can AND a short reply flagging
278
+ what's still open. The user shouldn't be surprised.
279
+
280
+ **When in doubt about the scope of a ripple, fetch the full doc.**
281
+ Don't guess.
282
+
283
+ ### Auto-suggest when the user confirms a concrete proposal
284
+
285
+ When a user flags something qualitative ("this is too much flour", "this
286
+ sentence is clunky", "this number feels off"), lead with a **concrete
287
+ counter-proposal framed as a question** — then, if they confirm, post
288
+ the suggestion immediately without waiting for a second "yes, go ahead".
289
+
290
+ Two turns, not three:
291
+
292
+ 1. **Turn 1 (propose).** Reply on the thread with one specific
293
+ alternative phrased as a check: "Does 200g seem right?", "How about
294
+ 'gently fold' instead of 'stir'?", "Would 45 minutes read better than
295
+ 90?". Pick a real number / phrase — not "would you like me to
296
+ suggest a different amount?" (that's a question about your behavior,
297
+ not a proposal).
298
+ 2. **Turn 2 (commit on confirmation).** When the user replies with any
299
+ variant of yes ("yes", "sure", "go for it", "perfect", a thumbs-up
300
+ emoji), call `composer_add_suggestion` with `fromThreadId: event.threadId`
301
+ and the concrete replacement. Do NOT also post a comment reply — the
302
+ suggestion card IS your reply (see "Keep comment text terse" above).
303
+
304
+ If the user says no / picks a different value / redirects, follow their
305
+ lead — do not post the original proposal anyway.
306
+
307
+ If you can't name a concrete alternative (e.g. the thread is too
308
+ abstract to guess a number), ask a clarifying question instead. Don't
309
+ propose something generic just to fill the slot — "Would you like me
310
+ to shorten this?" is worthless without a target length.
311
+
123
312
  ## Anchors
124
313
 
125
314
  Write tools take:
@@ -128,9 +317,40 @@ Write tools take:
128
317
  { headingId: "intro-0", textToFind: "the exact words to anchor on", occurrence?: 1 }
129
318
  ```
130
319
 
131
- If you get `text_not_found`, the error message includes the current section
132
- text. Re-plan against the fresh text and retry. Never retry with stale
133
- content.
320
+ ### Pick the right span anchor = what gets deleted
321
+
322
+ Your `textToFind` is literally cut out when the user accepts; your
323
+ `replacementText` is inserted in its place. So:
324
+
325
+ - **Anchor the whole unit you're changing.** Replacing a sentence →
326
+ include the terminal punctuation (`.`, `?`, `!`). Replacing a bullet
327
+ item → anchor the item's text (not the `- ` marker; that's block
328
+ structure). Replacing a paragraph → anchor the whole paragraph.
329
+ - **Include any trailing punctuation you're changing.** Converting a
330
+ statement to a question? End the anchor at the `.` and end the
331
+ replacement with `?`. Don't anchor "the statement" alone and
332
+ replace with "the question?" — you'll end up with `the question?.`.
333
+ - **Match your `replacementText`'s shape to the anchor's shape.** Inline
334
+ replacement inside a paragraph → replacement is inline (no leading
335
+ `- `, `#`, or blank line). Replacing a full list → replacement is a
336
+ full markdown list. Single-paragraph markdown is unwrapped to inline
337
+ on accept; multi-block markdown is inserted as blocks.
338
+ - **Formatting is part of your replacement, not the anchor.** If the
339
+ original had `**bold**` or a link, the anchor's formatting is gone
340
+ on accept — your replacement must include the markdown syntax for
341
+ any formatting you want preserved.
342
+ - **Anchor at token boundaries, not mid-word.** `textToFind: "istrat"`
343
+ to hit the middle of "administration" is fragile. Use whole words
344
+ or sentence boundaries. Use `occurrence` when the same phrase
345
+ appears multiple times.
346
+ - **Mind the whitespace.** By default, do not include leading or
347
+ trailing whitespace in the anchor, and end `replacementText` at the
348
+ same boundary. If you include a trailing space in the anchor,
349
+ include one in the replacement too; otherwise words smash together.
350
+
351
+ If you get `text_not_found`, the error message includes the current
352
+ section text. Re-plan against the fresh text and retry. Never retry
353
+ with stale content.
134
354
 
135
355
  ## Discoverability
136
356