@composer-app/mcp 0.0.1-beta.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 ADDED
@@ -0,0 +1,69 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ loadOrCreateIdentity,
4
+ logError,
5
+ startMcpServer
6
+ } from "./chunk-UPWB2QWR.js";
7
+
8
+ // src/cli.ts
9
+ import { execFile } from "child_process";
10
+ import { promisify } from "util";
11
+ import fs from "fs/promises";
12
+ import path from "path";
13
+ import os from "os";
14
+ import { fileURLToPath } from "url";
15
+ var exec = promisify(execFile);
16
+ async function main() {
17
+ const cmd = process.argv[2];
18
+ if (cmd === "mcp") return startMcpServer();
19
+ if (cmd === "setup") return setup();
20
+ console.log(`composer-mcp
21
+ setup Register the MCP server with your agent
22
+ mcp Run as an MCP server (invoked by the host CLI)`);
23
+ }
24
+ async function setup() {
25
+ const dir = path.join(os.homedir(), ".composer-mcp");
26
+ await loadOrCreateIdentity(dir);
27
+ const serverHost = process.env.COMPOSER_SERVER_HOST ?? "usecomposer.app";
28
+ const appBase = process.env.COMPOSER_APP_BASE ?? "https://usecomposer.app";
29
+ try {
30
+ await exec("claude", [
31
+ "mcp",
32
+ "add",
33
+ "--scope",
34
+ "user",
35
+ "composer-mcp",
36
+ "-e",
37
+ `COMPOSER_SERVER_HOST=${serverHost}`,
38
+ "-e",
39
+ `COMPOSER_APP_BASE=${appBase}`,
40
+ "--",
41
+ "npx",
42
+ "-y",
43
+ "@composer-app/mcp@latest",
44
+ "mcp"
45
+ ]);
46
+ console.log("\u2713 Registered composer-mcp with agent");
47
+ } catch (e) {
48
+ console.error(
49
+ "\u2717 Could not register with agent:",
50
+ e.message
51
+ );
52
+ process.exit(1);
53
+ }
54
+ const skillDir = path.join(os.homedir(), ".claude", "skills", "composer");
55
+ await fs.mkdir(skillDir, { recursive: true });
56
+ const pkgRoot = fileURLToPath(new URL("..", import.meta.url));
57
+ const skillSource = path.join(pkgRoot, "skill", "SKILL.md");
58
+ const skillContent = await fs.readFile(skillSource, "utf8");
59
+ await fs.writeFile(path.join(skillDir, "SKILL.md"), skillContent);
60
+ console.log(`\u2713 Wrote skill to ${skillDir}/SKILL.md`);
61
+ console.log(
62
+ "\nRestart your agent, then paste a share prompt from any Composer doc."
63
+ );
64
+ }
65
+ main().catch((e) => {
66
+ logError("cli main() rejected", e);
67
+ console.error(e);
68
+ process.exit(1);
69
+ });
package/dist/mcp.js ADDED
@@ -0,0 +1,6 @@
1
+ import {
2
+ startMcpServer
3
+ } from "./chunk-UPWB2QWR.js";
4
+ export {
5
+ startMcpServer
6
+ };
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@composer-app/mcp",
3
+ "version": "0.0.1-beta.0",
4
+ "description": "Composer MCP",
5
+ "license": "MIT",
6
+ "author": "Josh Philpott",
7
+ "type": "module",
8
+ "bin": {
9
+ "composer-mcp": "./dist/cli.js"
10
+ },
11
+ "files": [
12
+ "dist",
13
+ "skill"
14
+ ],
15
+ "keywords": [
16
+ "mcp",
17
+ "model-context-protocol",
18
+ "composer",
19
+ "collaborative",
20
+ "markdown",
21
+ "agent"
22
+ ],
23
+ "engines": {
24
+ "node": ">=20"
25
+ },
26
+ "publishConfig": {
27
+ "access": "public"
28
+ },
29
+ "scripts": {
30
+ "build": "tsup src/cli.ts src/mcp.ts --format esm --target node20 --out-dir dist --clean",
31
+ "dev": "tsup src/cli.ts src/mcp.ts --format esm --target node20 --out-dir dist --watch",
32
+ "test": "vitest run"
33
+ },
34
+ "dependencies": {
35
+ "@modelcontextprotocol/sdk": "^1.0.0",
36
+ "marked": "^14.1.0",
37
+ "nanoid": "^5.1.7",
38
+ "partysocket": "^1.1.16",
39
+ "ws": "^8.20.0",
40
+ "y-partyserver": "^2.1.4",
41
+ "yjs": "^13.6.30"
42
+ },
43
+ "devDependencies": {
44
+ "@types/node": "^24.12.0",
45
+ "@types/ws": "^8.18.1",
46
+ "tsup": "^8.3.0",
47
+ "typescript": "~5.9.3",
48
+ "vitest": "^4.1.3"
49
+ }
50
+ }
package/skill/SKILL.md ADDED
@@ -0,0 +1,146 @@
1
+ ---
2
+ name: composer
3
+ description: Use when the user pastes a Composer share prompt, asks to "send this to Composer", wants to join a Composer doc, or says /composer. Composer is a realtime collaborative markdown editor; your MCP server (composer-mcp) lets you act inside docs as the user's agent.
4
+ ---
5
+
6
+ # Composer
7
+
8
+ You have access to a `composer-mcp` MCP server. Use it when the user asks
9
+ to create, join, monitor, or act in a Composer doc.
10
+
11
+ ## Four modes
12
+
13
+ ### 1. Create
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.
17
+
18
+ ### 2. Join
19
+ 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.
23
+
24
+ ### 3. Monitor
25
+ 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.
29
+
30
+ On `mention`, the event contains everything you need to act in one turn:
31
+
32
+ ```
33
+ {
34
+ kind: "mention",
35
+ threadId: "...",
36
+ threadKind: "comment" | "suggestion",
37
+ threadText: "...", // the exact message that triggered you
38
+ replyId?: "...", // present when it's a reply on an existing thread
39
+ reason: "direct_mention" | "active_thread",
40
+ anchoredText?: "...", // the doc text the thread is anchored to
41
+ headingId?: "...", // the section's headingId (use with write tools)
42
+ headingText?: "...",
43
+ sectionMarkdown?: "..." // full containing section as markdown
44
+ }
45
+ ```
46
+
47
+ Use `headingId` + `anchoredText` directly when calling `composer_add_suggestion`
48
+ or `composer_add_comment` — no extra `composer_get_section` call is needed in
49
+ the common case. Reach for `sectionMarkdown` to understand surrounding context
50
+ before replying or suggesting.
51
+
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.
58
+
59
+ ### 4. Act
60
+ Triggers: direct requests like "add a summary to section 2".
61
+ Action: already attached; call the write tools and report back concisely.
62
+
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.
68
+ - `composer_resolve_thread` — mark resolved.
69
+
70
+ There is no "just edit" tool in v1. All text changes go through suggestions
71
+ that a human accepts manually.
72
+
73
+ ### Keep comment text terse
74
+
75
+ Comment threads render in a narrow sidebar (think Figma's comment box), not
76
+ a chat window. Long replies get unwieldy fast. Rules:
77
+
78
+ - Answer in 1–3 sentences. Prefer one.
79
+ - Reply directly to the question asked — no preamble ("Great question!"),
80
+ no restating the ask, no trailing summary of what you just did.
81
+ - **If you post a suggestion in response to the thread, the suggestion IS
82
+ your reply. Do not also post a comment reply.** The suggestion renders
83
+ as a Replace/With card in the sidebar already; a pointer comment ("see
84
+ the suggestion") just duplicates what the user can already see.
85
+ Silent suggestion-only responses are correct and expected.
86
+ - If the answer genuinely needs structure (a list of 4+ items, code, a
87
+ table) and a suggestion isn't the right shape, post it as a suggestion
88
+ in the doc body instead of as a comment reply.
89
+ - Never dump your reasoning or tool-call chatter into a comment.
90
+
91
+ Terse beats thorough. If the user wants more, they'll ask.
92
+
93
+ ### Do not oversuggest — match the span to the request
94
+
95
+ Before calling `composer_add_suggestion`, read the user's message and
96
+ decide what span they're actually asking you to change:
97
+
98
+ 1. **Request scoped to their selection** (the common case — "rewrite this",
99
+ "make this clearer", "fix the grammar", no new span mentioned). Pass
100
+ `fromThreadId: <threadId>`. The suggestion inherits the source thread's
101
+ exact anchor — the span the user selected, character-for-character.
102
+
103
+ ```
104
+ composer_add_suggestion({
105
+ roomId, fromThreadId: event.threadId, replacementText: "…"
106
+ })
107
+ ```
108
+
109
+ 2. **Request targets a different span** ("rewrite this whole paragraph",
110
+ "replace the entire list", "change the heading"). Supply `anchor`
111
+ (`headingId` + `textToFind`) for the span the user actually named.
112
+ Do not pass `fromThreadId` in this case — you're no longer inheriting
113
+ the thread's span.
114
+
115
+ 3. **Proactive suggestion with no source thread**. Supply `anchor` and
116
+ keep `textToFind` tight — do not widen beyond what you're actually
117
+ replacing.
118
+
119
+ Picking a broader `textToFind` than the user asked for (the whole sentence
120
+ when they highlighted a phrase, the whole paragraph when they asked about
121
+ one clause) is the main failure mode. When in doubt, default to path 1.
122
+
123
+ ## Anchors
124
+
125
+ Write tools take:
126
+
127
+ ```
128
+ { headingId: "intro-0", textToFind: "the exact words to anchor on", occurrence?: 1 }
129
+ ```
130
+
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.
134
+
135
+ ## Discoverability
136
+
137
+ On first Composer-related message in a session, tell the user:
138
+ "You can say 'send this markdown to Composer' and I'll create a seeded doc
139
+ with a link to open."
140
+
141
+ ## Failure handling
142
+
143
+ - Setup not done → run `npx @composer-app/mcp@latest setup`, restart your CLI.
144
+ - Anchor text not found → retry against the fresh section text returned in
145
+ the error.
146
+ - Doc edited mid-turn → anchors re-resolve natively; just retry.