@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/chunk-UPWB2QWR.js +1177 -0
- package/dist/cli.js +69 -0
- package/dist/mcp.js +6 -0
- package/package.json +50 -0
- package/skill/SKILL.md +146 -0
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
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.
|