@composer-app/mcp 0.0.1-beta.0 → 0.0.1-beta.2
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 → chunk-SZ67UYAY.js} +250 -64
- package/dist/cli.js +26 -2
- package/dist/mcp.js +3 -1
- package/package.json +23 -4
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// src/mcp.ts
|
|
2
2
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
3
3
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
4
5
|
import {
|
|
5
6
|
CallToolRequestSchema,
|
|
6
7
|
ListToolsRequestSchema
|
|
@@ -8,12 +9,119 @@ import {
|
|
|
8
9
|
import { nanoid as nanoid2 } from "nanoid";
|
|
9
10
|
import path3 from "path";
|
|
10
11
|
import os2 from "os";
|
|
12
|
+
import http from "http";
|
|
13
|
+
import { randomUUID } from "crypto";
|
|
11
14
|
|
|
12
15
|
// src/roomState.ts
|
|
13
16
|
import * as Y3 from "yjs";
|
|
14
17
|
import YProvider from "y-partyserver/provider";
|
|
15
18
|
import WebSocket from "ws";
|
|
16
19
|
|
|
20
|
+
// ../shared/src/editor-extensions.ts
|
|
21
|
+
import StarterKit from "@tiptap/starter-kit";
|
|
22
|
+
import { Code } from "@tiptap/extension-code";
|
|
23
|
+
import CodeBlock from "@tiptap/extension-code-block";
|
|
24
|
+
import Image from "@tiptap/extension-image";
|
|
25
|
+
import TaskList from "@tiptap/extension-task-list";
|
|
26
|
+
import TaskItem from "@tiptap/extension-task-item";
|
|
27
|
+
import Highlight from "@tiptap/extension-highlight";
|
|
28
|
+
import Subscript from "@tiptap/extension-subscript";
|
|
29
|
+
import Superscript from "@tiptap/extension-superscript";
|
|
30
|
+
import { Table } from "@tiptap/extension-table";
|
|
31
|
+
import { TableRow } from "@tiptap/extension-table-row";
|
|
32
|
+
import { TableCell } from "@tiptap/extension-table-cell";
|
|
33
|
+
import { TableHeader } from "@tiptap/extension-table-header";
|
|
34
|
+
var CodeWithCombinableMarks = Code.extend({ excludes: "" });
|
|
35
|
+
var FrontmatterSchema = CodeBlock.extend({
|
|
36
|
+
name: "frontmatter",
|
|
37
|
+
addInputRules() {
|
|
38
|
+
return [];
|
|
39
|
+
},
|
|
40
|
+
renderMarkdown: (node) => {
|
|
41
|
+
const text = (node.content ?? []).map((child) => child.text ?? "").join("").trim();
|
|
42
|
+
if (!text) return "";
|
|
43
|
+
return `---
|
|
44
|
+
${text}
|
|
45
|
+
---`;
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
function buildEditorExtensions(opts = {}) {
|
|
49
|
+
const table = opts.table ?? Table;
|
|
50
|
+
const frontmatter = opts.frontmatter ?? FrontmatterSchema;
|
|
51
|
+
return [
|
|
52
|
+
StarterKit.configure({
|
|
53
|
+
undoRedo: false,
|
|
54
|
+
code: false,
|
|
55
|
+
link: {
|
|
56
|
+
openOnClick: true,
|
|
57
|
+
HTMLAttributes: { target: "_blank", rel: "noopener noreferrer" },
|
|
58
|
+
shouldAutoLink: (url) => /^https?:\/\/\S+$/i.test(url)
|
|
59
|
+
}
|
|
60
|
+
}),
|
|
61
|
+
CodeWithCombinableMarks,
|
|
62
|
+
Image,
|
|
63
|
+
TaskList,
|
|
64
|
+
TaskItem.configure({ nested: true }),
|
|
65
|
+
Highlight,
|
|
66
|
+
Subscript,
|
|
67
|
+
Superscript,
|
|
68
|
+
table.configure({ resizable: false }),
|
|
69
|
+
TableRow,
|
|
70
|
+
TableCell,
|
|
71
|
+
TableHeader,
|
|
72
|
+
frontmatter
|
|
73
|
+
];
|
|
74
|
+
}
|
|
75
|
+
var editorExtensions = buildEditorExtensions();
|
|
76
|
+
|
|
77
|
+
// ../shared/src/frontmatter.ts
|
|
78
|
+
function extractFrontmatter(text) {
|
|
79
|
+
const match = text.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/);
|
|
80
|
+
if (!match) return null;
|
|
81
|
+
return { yaml: match[1].trim(), body: match[2] };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ../shared/src/markdown.ts
|
|
85
|
+
import { getSchema } from "@tiptap/core";
|
|
86
|
+
import { MarkdownManager } from "@tiptap/markdown";
|
|
87
|
+
import {
|
|
88
|
+
prosemirrorJSONToYXmlFragment,
|
|
89
|
+
yXmlFragmentToProsemirrorJSON
|
|
90
|
+
} from "@tiptap/y-tiptap";
|
|
91
|
+
function markdownToDocJSON(markdown, extensions = editorExtensions) {
|
|
92
|
+
const manager = new MarkdownManager({ extensions });
|
|
93
|
+
const parsed = manager.parse(markdown);
|
|
94
|
+
if (parsed && parsed.type === "doc") return parsed;
|
|
95
|
+
return { type: "doc", content: parsed?.content ?? [] };
|
|
96
|
+
}
|
|
97
|
+
function writeMarkdownToYFragment(fragment, markdown, extensions = editorExtensions) {
|
|
98
|
+
const doc = fragment.doc;
|
|
99
|
+
if (!doc) throw new Error("fragment must be attached to a Y.Doc");
|
|
100
|
+
const fm = extractFrontmatter(markdown);
|
|
101
|
+
const body = fm ? fm.body : markdown;
|
|
102
|
+
const schema = getSchema(extensions);
|
|
103
|
+
const bodyDoc = markdownToDocJSON(body, extensions);
|
|
104
|
+
const children = [];
|
|
105
|
+
if (fm) {
|
|
106
|
+
children.push({
|
|
107
|
+
type: "frontmatter",
|
|
108
|
+
attrs: { language: "yaml" },
|
|
109
|
+
content: fm.yaml ? [{ type: "text", text: fm.yaml }] : void 0
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
if (bodyDoc.content) children.push(...bodyDoc.content);
|
|
113
|
+
const fullDoc = { type: "doc", content: children };
|
|
114
|
+
doc.transact(() => {
|
|
115
|
+
prosemirrorJSONToYXmlFragment(schema, fullDoc, fragment);
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
function markdownFromYFragment(fragment, extensions = editorExtensions) {
|
|
119
|
+
const json = yXmlFragmentToProsemirrorJSON(fragment);
|
|
120
|
+
if (!json || !json.content || json.content.length === 0) return "";
|
|
121
|
+
const manager = new MarkdownManager({ extensions });
|
|
122
|
+
return manager.serialize(json);
|
|
123
|
+
}
|
|
124
|
+
|
|
17
125
|
// src/docReaders.ts
|
|
18
126
|
import * as Y from "yjs";
|
|
19
127
|
function isXmlElement(node) {
|
|
@@ -104,9 +212,7 @@ function renderBlockAsMarkdown(block) {
|
|
|
104
212
|
return text;
|
|
105
213
|
}
|
|
106
214
|
function serializeDocAsMarkdown(doc) {
|
|
107
|
-
|
|
108
|
-
if (blocks.length === 0) return "";
|
|
109
|
-
return blocks.map(renderBlockAsMarkdown).join("\n\n");
|
|
215
|
+
return markdownFromYFragment(doc.getXmlFragment("default"));
|
|
110
216
|
}
|
|
111
217
|
|
|
112
218
|
// src/anchors.ts
|
|
@@ -326,8 +432,40 @@ var RoomState = class {
|
|
|
326
432
|
this.provider.awareness.setLocalStateField("user", {
|
|
327
433
|
name: opts.actingAs,
|
|
328
434
|
color: opts.identity.color,
|
|
329
|
-
userId: opts.identity.userId
|
|
435
|
+
userId: opts.identity.userId,
|
|
436
|
+
isAgent: true
|
|
330
437
|
});
|
|
438
|
+
this.installAwarenessHeartbeat();
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* Re-broadcast the MCP's awareness every 15s.
|
|
442
|
+
*
|
|
443
|
+
* y-partyserver's provider disables the y-protocols awareness
|
|
444
|
+
* `_checkInterval` (see `clearInterval(awareness._checkInterval)` in
|
|
445
|
+
* `y-partyserver/dist/provider/index.js`), so the MCP sends its awareness
|
|
446
|
+
* exactly once — on connect — and never heartbeats after that. Combined
|
|
447
|
+
* with Cloudflare Durable Object hibernation (which evicts the server's
|
|
448
|
+
* in-memory `document.awareness` Map on wake), this means a browser that
|
|
449
|
+
* connects more than ~60s after the MCP sees an empty awareness dump in
|
|
450
|
+
* `onConnect` and never learns the agent is there. The user's own
|
|
451
|
+
* awareness flows the other direction fine (they send on connect, server
|
|
452
|
+
* broadcasts to the MCP), which is why the failure is asymmetric.
|
|
453
|
+
*
|
|
454
|
+
* y-partyserver's provider listens to `awareness.on("change", ...)`, and
|
|
455
|
+
* y-protocols only fires `change` when the new state is deep-unequal to
|
|
456
|
+
* the previous one. Re-setting an identical state emits `update` but NOT
|
|
457
|
+
* `change`, so the provider never sends a wire frame. We bump a throwaway
|
|
458
|
+
* `_hb` field each tick to guarantee deep-inequality, forcing the change
|
|
459
|
+
* event and a broadcast. 15s is well under any realistic hibernation gap.
|
|
460
|
+
*/
|
|
461
|
+
installAwarenessHeartbeat() {
|
|
462
|
+
const heartbeat = setInterval(() => {
|
|
463
|
+
const local = this.provider.awareness.getLocalState();
|
|
464
|
+
if (local !== null) {
|
|
465
|
+
this.provider.awareness.setLocalState({ ...local, _hb: Date.now() });
|
|
466
|
+
}
|
|
467
|
+
}, 15e3);
|
|
468
|
+
heartbeat.unref?.();
|
|
331
469
|
}
|
|
332
470
|
/**
|
|
333
471
|
* Resolves when the provider has completed its first sync handshake.
|
|
@@ -486,19 +624,24 @@ import * as fs from "fs/promises";
|
|
|
486
624
|
import * as path from "path";
|
|
487
625
|
import { nanoid } from "nanoid";
|
|
488
626
|
var PALETTE = [
|
|
489
|
-
"#
|
|
490
|
-
|
|
491
|
-
"#
|
|
492
|
-
|
|
493
|
-
"#
|
|
494
|
-
|
|
495
|
-
"#
|
|
496
|
-
|
|
497
|
-
"#
|
|
498
|
-
|
|
499
|
-
"#
|
|
500
|
-
|
|
627
|
+
"#9333ea",
|
|
628
|
+
// purple-600
|
|
629
|
+
"#4f46e5",
|
|
630
|
+
// indigo-600
|
|
631
|
+
"#dc2626",
|
|
632
|
+
// red-600
|
|
633
|
+
"#db2777",
|
|
634
|
+
// pink-600
|
|
635
|
+
"#0f766e",
|
|
636
|
+
// teal-700
|
|
637
|
+
"#b45309",
|
|
638
|
+
// amber-700
|
|
639
|
+
"#0e7490"
|
|
640
|
+
// cyan-700
|
|
501
641
|
];
|
|
642
|
+
function isPaletteColor(value) {
|
|
643
|
+
return PALETTE.includes(value);
|
|
644
|
+
}
|
|
502
645
|
var FILE_NAME = "user.json";
|
|
503
646
|
var FILE_MODE = 384;
|
|
504
647
|
function pickColor() {
|
|
@@ -515,7 +658,18 @@ async function loadOrCreateIdentity(dir) {
|
|
|
515
658
|
const raw = await fs.readFile(filePath, "utf8");
|
|
516
659
|
const parsed = JSON.parse(raw);
|
|
517
660
|
if (isValidIdentity(parsed)) {
|
|
518
|
-
|
|
661
|
+
if (isPaletteColor(parsed.color)) {
|
|
662
|
+
return { userId: parsed.userId, color: parsed.color };
|
|
663
|
+
}
|
|
664
|
+
const migrated = {
|
|
665
|
+
userId: parsed.userId,
|
|
666
|
+
color: pickColor()
|
|
667
|
+
};
|
|
668
|
+
await fs.mkdir(dir, { recursive: true });
|
|
669
|
+
await fs.writeFile(filePath, JSON.stringify(migrated, null, 2), {
|
|
670
|
+
mode: FILE_MODE
|
|
671
|
+
});
|
|
672
|
+
return migrated;
|
|
519
673
|
}
|
|
520
674
|
} catch (err) {
|
|
521
675
|
const code = err.code;
|
|
@@ -534,50 +688,8 @@ async function loadOrCreateIdentity(dir) {
|
|
|
534
688
|
}
|
|
535
689
|
|
|
536
690
|
// src/mdToFragment.ts
|
|
537
|
-
import { marked } from "marked";
|
|
538
|
-
import * as Y4 from "yjs";
|
|
539
691
|
function writeMarkdownToFragment(fragment, markdown) {
|
|
540
|
-
|
|
541
|
-
if (!doc) throw new Error("fragment must be attached to a Y.Doc");
|
|
542
|
-
const tokens = marked.lexer(markdown);
|
|
543
|
-
doc.transact(() => {
|
|
544
|
-
for (const token of tokens) {
|
|
545
|
-
const element = buildElementForToken(token);
|
|
546
|
-
if (element) fragment.push([element]);
|
|
547
|
-
}
|
|
548
|
-
});
|
|
549
|
-
}
|
|
550
|
-
function buildElementForToken(token) {
|
|
551
|
-
if (token.type === "heading") {
|
|
552
|
-
const heading = token;
|
|
553
|
-
return makeBlock("heading", heading.text ?? "", {
|
|
554
|
-
level: String(heading.depth ?? 1)
|
|
555
|
-
});
|
|
556
|
-
}
|
|
557
|
-
if (token.type === "paragraph") {
|
|
558
|
-
const paragraph = token;
|
|
559
|
-
return makeBlock("paragraph", paragraph.text ?? "");
|
|
560
|
-
}
|
|
561
|
-
if (token.type === "space") {
|
|
562
|
-
return null;
|
|
563
|
-
}
|
|
564
|
-
const raw = token.raw?.trim();
|
|
565
|
-
if (!raw) return null;
|
|
566
|
-
return makeBlock("paragraph", raw);
|
|
567
|
-
}
|
|
568
|
-
function makeBlock(nodeName, text, attrs) {
|
|
569
|
-
const element = new Y4.XmlElement(nodeName);
|
|
570
|
-
if (attrs) {
|
|
571
|
-
for (const [key, value] of Object.entries(attrs)) {
|
|
572
|
-
element.setAttribute(key, value);
|
|
573
|
-
}
|
|
574
|
-
}
|
|
575
|
-
if (text.length > 0) {
|
|
576
|
-
const xmlText = new Y4.XmlText();
|
|
577
|
-
xmlText.insert(0, text);
|
|
578
|
-
element.insert(0, [xmlText]);
|
|
579
|
-
}
|
|
580
|
-
return element;
|
|
692
|
+
writeMarkdownToYFragment(fragment, markdown);
|
|
581
693
|
}
|
|
582
694
|
|
|
583
695
|
// src/logger.ts
|
|
@@ -959,6 +1071,7 @@ ${resolved.currentSectionText}`
|
|
|
959
1071
|
authorName: state.actingAs,
|
|
960
1072
|
authorColor: state.identity.color,
|
|
961
1073
|
authorUserId: state.identity.userId,
|
|
1074
|
+
authorIsAgent: true,
|
|
962
1075
|
text,
|
|
963
1076
|
createdAt: Date.now(),
|
|
964
1077
|
resolved: false,
|
|
@@ -986,6 +1099,7 @@ function handleReplyComment(args) {
|
|
|
986
1099
|
authorName: state.actingAs,
|
|
987
1100
|
authorColor: state.identity.color,
|
|
988
1101
|
authorUserId: state.identity.userId,
|
|
1102
|
+
authorIsAgent: true,
|
|
989
1103
|
text,
|
|
990
1104
|
createdAt: Date.now()
|
|
991
1105
|
};
|
|
@@ -1044,6 +1158,7 @@ ${resolved.currentSectionText}`
|
|
|
1044
1158
|
authorName: state.actingAs,
|
|
1045
1159
|
authorColor: state.identity.color,
|
|
1046
1160
|
authorUserId: state.identity.userId,
|
|
1161
|
+
authorIsAgent: true,
|
|
1047
1162
|
createdAt: Date.now(),
|
|
1048
1163
|
status: "pending",
|
|
1049
1164
|
anchorFrom,
|
|
@@ -1073,6 +1188,7 @@ function handleReplySuggestion(args) {
|
|
|
1073
1188
|
authorName: state.actingAs,
|
|
1074
1189
|
authorColor: state.identity.color,
|
|
1075
1190
|
authorUserId: state.identity.userId,
|
|
1191
|
+
authorIsAgent: true,
|
|
1076
1192
|
text,
|
|
1077
1193
|
createdAt: Date.now()
|
|
1078
1194
|
};
|
|
@@ -1124,9 +1240,7 @@ async function dispatchTool(name, args) {
|
|
|
1124
1240
|
return errorResult(`unknown tool: ${name}`);
|
|
1125
1241
|
}
|
|
1126
1242
|
}
|
|
1127
|
-
|
|
1128
|
-
installCrashHandlers();
|
|
1129
|
-
log("mcp server starting", { pid: process.pid, node: process.version });
|
|
1243
|
+
function buildServer() {
|
|
1130
1244
|
const server = new Server(
|
|
1131
1245
|
{ name: "composer-mcp", version: "0.0.1" },
|
|
1132
1246
|
{ capabilities: { tools: {} } }
|
|
@@ -1160,18 +1274,90 @@ async function startMcpServer() {
|
|
|
1160
1274
|
return errorResult(message);
|
|
1161
1275
|
}
|
|
1162
1276
|
});
|
|
1277
|
+
return server;
|
|
1278
|
+
}
|
|
1279
|
+
async function startMcpServer() {
|
|
1280
|
+
installCrashHandlers();
|
|
1281
|
+
log("mcp server starting", {
|
|
1282
|
+
pid: process.pid,
|
|
1283
|
+
node: process.version,
|
|
1284
|
+
build: "awareness-heartbeat-v1"
|
|
1285
|
+
});
|
|
1286
|
+
const server = buildServer();
|
|
1163
1287
|
const transport = new StdioServerTransport();
|
|
1164
1288
|
await server.connect(transport);
|
|
1165
1289
|
log("mcp server connected", {
|
|
1290
|
+
transport: "stdio",
|
|
1166
1291
|
serverHost: SERVER_HOST,
|
|
1167
1292
|
appBase: APP_BASE,
|
|
1168
1293
|
logFile: LOG_FILE_PATH,
|
|
1169
1294
|
pid: process.pid
|
|
1170
1295
|
});
|
|
1171
1296
|
}
|
|
1297
|
+
async function startMcpHttpServer(opts) {
|
|
1298
|
+
installCrashHandlers();
|
|
1299
|
+
log("mcp http server starting", {
|
|
1300
|
+
port: opts.port,
|
|
1301
|
+
pid: process.pid,
|
|
1302
|
+
node: process.version,
|
|
1303
|
+
build: "awareness-heartbeat-v1"
|
|
1304
|
+
});
|
|
1305
|
+
const server = buildServer();
|
|
1306
|
+
const transport = new StreamableHTTPServerTransport({
|
|
1307
|
+
sessionIdGenerator: () => randomUUID()
|
|
1308
|
+
});
|
|
1309
|
+
await server.connect(transport);
|
|
1310
|
+
const httpServer = http.createServer(async (req, res) => {
|
|
1311
|
+
try {
|
|
1312
|
+
const url = req.url ?? "";
|
|
1313
|
+
if (url === "/mcp" || url.startsWith("/mcp?") || url.startsWith("/mcp/")) {
|
|
1314
|
+
await transport.handleRequest(req, res);
|
|
1315
|
+
return;
|
|
1316
|
+
}
|
|
1317
|
+
if (url === "/health") {
|
|
1318
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1319
|
+
res.end(JSON.stringify({ ok: true, serverHost: SERVER_HOST }));
|
|
1320
|
+
return;
|
|
1321
|
+
}
|
|
1322
|
+
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
1323
|
+
res.end("composer-mcp: POST /mcp");
|
|
1324
|
+
} catch (err) {
|
|
1325
|
+
logError("http handler error", err);
|
|
1326
|
+
if (!res.headersSent) {
|
|
1327
|
+
res.writeHead(500, { "Content-Type": "text/plain" });
|
|
1328
|
+
res.end("internal error");
|
|
1329
|
+
} else {
|
|
1330
|
+
res.end();
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
});
|
|
1334
|
+
httpServer.listen(opts.port, "127.0.0.1", () => {
|
|
1335
|
+
const url = `http://127.0.0.1:${opts.port}/mcp`;
|
|
1336
|
+
log("mcp http server listening", {
|
|
1337
|
+
url,
|
|
1338
|
+
serverHost: SERVER_HOST,
|
|
1339
|
+
appBase: APP_BASE,
|
|
1340
|
+
logFile: LOG_FILE_PATH,
|
|
1341
|
+
pid: process.pid
|
|
1342
|
+
});
|
|
1343
|
+
console.error(
|
|
1344
|
+
`composer-mcp http listening on ${url}
|
|
1345
|
+
serverHost=${SERVER_HOST}
|
|
1346
|
+
appBase=${APP_BASE}`
|
|
1347
|
+
);
|
|
1348
|
+
});
|
|
1349
|
+
const shutdown = () => {
|
|
1350
|
+
log("mcp http server shutting down");
|
|
1351
|
+
httpServer.close(() => process.exit(0));
|
|
1352
|
+
setTimeout(() => process.exit(0), 500).unref();
|
|
1353
|
+
};
|
|
1354
|
+
process.on("SIGTERM", shutdown);
|
|
1355
|
+
process.on("SIGINT", shutdown);
|
|
1356
|
+
}
|
|
1172
1357
|
|
|
1173
1358
|
export {
|
|
1174
1359
|
loadOrCreateIdentity,
|
|
1175
1360
|
logError,
|
|
1176
|
-
startMcpServer
|
|
1361
|
+
startMcpServer,
|
|
1362
|
+
startMcpHttpServer
|
|
1177
1363
|
};
|
package/dist/cli.js
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
import {
|
|
3
3
|
loadOrCreateIdentity,
|
|
4
4
|
logError,
|
|
5
|
+
startMcpHttpServer,
|
|
5
6
|
startMcpServer
|
|
6
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-SZ67UYAY.js";
|
|
7
8
|
|
|
8
9
|
// src/cli.ts
|
|
9
10
|
import { execFile } from "child_process";
|
|
@@ -13,13 +14,36 @@ import path from "path";
|
|
|
13
14
|
import os from "os";
|
|
14
15
|
import { fileURLToPath } from "url";
|
|
15
16
|
var exec = promisify(execFile);
|
|
17
|
+
var DEFAULT_HTTP_PORT = 3456;
|
|
18
|
+
function resolveHttpPort() {
|
|
19
|
+
const argv = process.argv.slice(3);
|
|
20
|
+
for (let i = 0; i < argv.length; i++) {
|
|
21
|
+
const arg = argv[i];
|
|
22
|
+
if (arg.startsWith("--port=")) {
|
|
23
|
+
const n = Number(arg.slice("--port=".length));
|
|
24
|
+
if (Number.isFinite(n) && n > 0) return n;
|
|
25
|
+
} else if (arg === "--port" && argv[i + 1]) {
|
|
26
|
+
const n = Number(argv[i + 1]);
|
|
27
|
+
if (Number.isFinite(n) && n > 0) return n;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
const envPort = process.env.COMPOSER_MCP_PORT;
|
|
31
|
+
if (envPort) {
|
|
32
|
+
const n = Number(envPort);
|
|
33
|
+
if (Number.isFinite(n) && n > 0) return n;
|
|
34
|
+
}
|
|
35
|
+
return DEFAULT_HTTP_PORT;
|
|
36
|
+
}
|
|
16
37
|
async function main() {
|
|
17
38
|
const cmd = process.argv[2];
|
|
18
39
|
if (cmd === "mcp") return startMcpServer();
|
|
40
|
+
if (cmd === "http") return startMcpHttpServer({ port: resolveHttpPort() });
|
|
19
41
|
if (cmd === "setup") return setup();
|
|
20
42
|
console.log(`composer-mcp
|
|
21
43
|
setup Register the MCP server with your agent
|
|
22
|
-
mcp Run as an MCP server (invoked by the host CLI)
|
|
44
|
+
mcp Run as an MCP server over stdio (invoked by the host CLI)
|
|
45
|
+
http Run as an MCP server over HTTP (for local dev + HMR)
|
|
46
|
+
flags: --port N (or COMPOSER_MCP_PORT env; default ${DEFAULT_HTTP_PORT})`);
|
|
23
47
|
}
|
|
24
48
|
async function setup() {
|
|
25
49
|
const dir = path.join(os.homedir(), ".composer-mcp");
|
package/dist/mcp.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@composer-app/mcp",
|
|
3
|
-
"version": "0.0.1-beta.
|
|
3
|
+
"version": "0.0.1-beta.2",
|
|
4
4
|
"description": "Composer MCP",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Josh Philpott",
|
|
@@ -27,13 +27,31 @@
|
|
|
27
27
|
"access": "public"
|
|
28
28
|
},
|
|
29
29
|
"scripts": {
|
|
30
|
-
"build": "tsup
|
|
31
|
-
"dev": "tsup
|
|
30
|
+
"build": "tsup",
|
|
31
|
+
"dev": "tsup --watch",
|
|
32
|
+
"start:local": "tsx --env-file=.env.local src/cli.ts mcp",
|
|
33
|
+
"start:prod": "tsx --env-file=.env.production src/cli.ts mcp",
|
|
34
|
+
"typecheck": "tsc --noEmit -p .",
|
|
32
35
|
"test": "vitest run"
|
|
33
36
|
},
|
|
34
37
|
"dependencies": {
|
|
35
38
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
36
|
-
"
|
|
39
|
+
"@tiptap/core": "^3.22.0",
|
|
40
|
+
"@tiptap/extension-code": "^3.22.0",
|
|
41
|
+
"@tiptap/extension-code-block": "^3.22.0",
|
|
42
|
+
"@tiptap/extension-highlight": "^3.22.0",
|
|
43
|
+
"@tiptap/extension-image": "^3.22.0",
|
|
44
|
+
"@tiptap/extension-subscript": "^3.22.0",
|
|
45
|
+
"@tiptap/extension-superscript": "^3.22.0",
|
|
46
|
+
"@tiptap/extension-table": "^3.22.0",
|
|
47
|
+
"@tiptap/extension-table-cell": "^3.22.0",
|
|
48
|
+
"@tiptap/extension-table-header": "^3.22.0",
|
|
49
|
+
"@tiptap/extension-table-row": "^3.22.0",
|
|
50
|
+
"@tiptap/extension-task-item": "^3.22.0",
|
|
51
|
+
"@tiptap/extension-task-list": "^3.22.0",
|
|
52
|
+
"@tiptap/markdown": "^3.22.1",
|
|
53
|
+
"@tiptap/starter-kit": "^3.22.0",
|
|
54
|
+
"@tiptap/y-tiptap": "^3.0.2",
|
|
37
55
|
"nanoid": "^5.1.7",
|
|
38
56
|
"partysocket": "^1.1.16",
|
|
39
57
|
"ws": "^8.20.0",
|
|
@@ -44,6 +62,7 @@
|
|
|
44
62
|
"@types/node": "^24.12.0",
|
|
45
63
|
"@types/ws": "^8.18.1",
|
|
46
64
|
"tsup": "^8.3.0",
|
|
65
|
+
"tsx": "^4.21.0",
|
|
47
66
|
"typescript": "~5.9.3",
|
|
48
67
|
"vitest": "^4.1.3"
|
|
49
68
|
}
|