@shadow-garden/bapbong-mcp 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Le Phuoc Minh
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/dist/host.cjs ADDED
@@ -0,0 +1,548 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // packages/mcp/src/host.ts
21
+ var host_exports = {};
22
+ __export(host_exports, {
23
+ AnchorError: () => AnchorError,
24
+ NoDocumentError: () => NoDocumentError,
25
+ PmDocSession: () => PmDocSession,
26
+ RemoteSession: () => RemoteSession,
27
+ VersionConflictError: () => VersionConflictError,
28
+ createMcpServer: () => createMcpServer,
29
+ executeOp: () => executeOp,
30
+ reviveError: () => reviveError
31
+ });
32
+ module.exports = __toCommonJS(host_exports);
33
+
34
+ // packages/mcp/src/lib/contract.ts
35
+ var VersionConflictError = class extends Error {
36
+ constructor(current, expected) {
37
+ super(
38
+ `Document changed (version is now ${current}, you expected ${expected}). Call get_document again, re-locate your anchor, then retry.`
39
+ );
40
+ this.name = "VersionConflictError";
41
+ }
42
+ };
43
+ var AnchorError = class extends Error {
44
+ constructor(message) {
45
+ super(message);
46
+ this.name = "AnchorError";
47
+ }
48
+ };
49
+ var NoDocumentError = class extends Error {
50
+ constructor(message = "No document is open. Ask the user to open a document first.") {
51
+ super(message);
52
+ this.name = "NoDocumentError";
53
+ }
54
+ };
55
+
56
+ // packages/mcp/src/lib/pm-session.ts
57
+ var MARK_BY_FLAG = { bold: "strong", italic: "em", underline: "underline", strike: "strike" };
58
+ var PmDocSession = class {
59
+ constructor(host) {
60
+ this.host = host;
61
+ this.capabilities = { selection: typeof host.selection === "function" };
62
+ }
63
+ host;
64
+ capabilities;
65
+ // ── reads ────────────────────────────────────────────────────────────
66
+ async snapshot() {
67
+ const blocks = this.textblocks().map(({ node }, index) => {
68
+ const block = { index, type: blockType(node), text: node.textContent };
69
+ const images = blockImages(node).map(({ node: img }, i) => ({
70
+ index: i,
71
+ alt: String(img.attrs["alt"] ?? ""),
72
+ width: Number(img.attrs["width"]) || 0,
73
+ height: Number(img.attrs["height"]) || 0,
74
+ rotation: Number(img.attrs["rotation"]) || 0,
75
+ kind: img.attrs["shape"] ? "shape" : "bitmap"
76
+ }));
77
+ if (images.length > 0) block.images = images;
78
+ return block;
79
+ });
80
+ return { docVersion: this.host.getVersion(), blocks, meta: this.host.meta() };
81
+ }
82
+ async find(query) {
83
+ return this.hits(query).map((h, i) => ({ blockIndex: h.blockIndex, occurrence: i + 1, context: h.context }));
84
+ }
85
+ async getSelection() {
86
+ const sel = this.host.selection?.();
87
+ if (!sel || sel.from === sel.to) return null;
88
+ const state = this.host.getState();
89
+ const text = state.doc.textBetween(sel.from, sel.to, "\n");
90
+ const blockIndex = this.textblocks().findIndex(
91
+ ({ node, pos }) => sel.from >= pos && sel.from <= pos + node.nodeSize
92
+ );
93
+ return { text, blockIndex };
94
+ }
95
+ // ── mutations ────────────────────────────────────────────────────────
96
+ async replaceText(oldText, newText, opts = {}) {
97
+ this.checkVersion(opts.expectedVersion);
98
+ const hit = this.uniqueHit(oldText, opts.occurrence);
99
+ this.host.apply(this.host.getState().tr.insertText(newText, hit.from, hit.to));
100
+ return { docVersion: this.host.getVersion(), range: { from: hit.from, to: hit.from + newText.length } };
101
+ }
102
+ async insertContent(content, anchor, opts = {}) {
103
+ this.checkVersion(opts.expectedVersion);
104
+ const state = this.host.getState();
105
+ const { schema } = state;
106
+ const paragraphs = content.split("\n").map((line) => schema.node("paragraph", null, line.length > 0 ? [schema.text(line)] : []));
107
+ let insertAt;
108
+ if (anchor.position === "document_end") {
109
+ insertAt = state.doc.content.size;
110
+ } else {
111
+ const hit = this.uniqueHit(anchor.text, anchor.occurrence);
112
+ const block = this.textblocks()[hit.blockIndex];
113
+ insertAt = anchor.position === "before" ? block.pos : block.pos + block.node.nodeSize;
114
+ }
115
+ const inserted = paragraphs.reduce((size, node) => size + node.nodeSize, 0);
116
+ this.host.apply(state.tr.insert(insertAt, paragraphs));
117
+ return { docVersion: this.host.getVersion(), range: { from: insertAt, to: insertAt + inserted } };
118
+ }
119
+ async applyFormatting(target, format, opts = {}) {
120
+ this.checkVersion(opts.expectedVersion);
121
+ const hit = this.uniqueHit(target, opts.occurrence);
122
+ const state = this.host.getState();
123
+ const { schema } = state;
124
+ let tr = state.tr;
125
+ for (const [flag, markName] of Object.entries(MARK_BY_FLAG)) {
126
+ const want = format[flag];
127
+ if (want === void 0) continue;
128
+ const mark = schema.marks[markName];
129
+ if (!mark) continue;
130
+ tr = want ? tr.addMark(hit.from, hit.to, mark.create()) : tr.removeMark(hit.from, hit.to, mark);
131
+ }
132
+ if (format.align) {
133
+ const block = this.textblocks()[hit.blockIndex];
134
+ tr = tr.setNodeMarkup(block.pos, void 0, { ...block.node.attrs, align: format.align });
135
+ }
136
+ if (tr.steps.length === 0) {
137
+ return { docVersion: this.host.getVersion() };
138
+ }
139
+ this.host.apply(tr);
140
+ return { docVersion: this.host.getVersion(), range: { from: hit.from, to: hit.to } };
141
+ }
142
+ async updateImage(blockIndex, imageIndex, changes, opts = {}) {
143
+ this.checkVersion(opts.expectedVersion);
144
+ const blocks = this.textblocks();
145
+ const block = blocks[blockIndex];
146
+ if (!block) {
147
+ throw new AnchorError(
148
+ `blockIndex ${blockIndex} is out of range \u2014 the document has ${blocks.length} block(s). Block indexes change with every edit; call get_document again.`
149
+ );
150
+ }
151
+ const images = blockImages(block.node).map((img2) => ({ ...img2, pos: block.pos + 1 + img2.offset }));
152
+ if (images.length === 0) {
153
+ throw new AnchorError(`Block ${blockIndex} has no images. get_document lists each block's images.`);
154
+ }
155
+ const img = images[imageIndex];
156
+ if (!img) {
157
+ throw new AnchorError(
158
+ `imageIndex ${imageIndex} is out of range \u2014 block ${blockIndex} has ${images.length} image(s) (0-${images.length - 1}).`
159
+ );
160
+ }
161
+ let tr = this.host.getState().tr;
162
+ if (changes.width !== void 0) tr = tr.setNodeAttribute(img.pos, "width", Math.max(1, Math.round(changes.width)));
163
+ if (changes.height !== void 0) tr = tr.setNodeAttribute(img.pos, "height", Math.max(1, Math.round(changes.height)));
164
+ if (changes.rotation !== void 0) {
165
+ tr = tr.setNodeAttribute(img.pos, "rotation", (changes.rotation % 360 + 360) % 360);
166
+ }
167
+ if (tr.steps.length === 0) return { docVersion: this.host.getVersion() };
168
+ this.host.apply(tr);
169
+ return { docVersion: this.host.getVersion(), range: { from: img.pos, to: img.pos + 1 } };
170
+ }
171
+ async save() {
172
+ await this.host.save();
173
+ }
174
+ async close() {
175
+ }
176
+ // ── internals ────────────────────────────────────────────────────────
177
+ checkVersion(expected) {
178
+ const current = this.host.getVersion();
179
+ if (expected !== void 0 && expected !== current) {
180
+ throw new VersionConflictError(current, expected);
181
+ }
182
+ }
183
+ /** All textblocks (paragraphs, incl. inside table cells) in reading order. */
184
+ textblocks() {
185
+ const out = [];
186
+ this.host.getState().doc.descendants((node, pos) => {
187
+ if (node.isTextblock) {
188
+ out.push({ node, pos });
189
+ return false;
190
+ }
191
+ return true;
192
+ });
193
+ return out;
194
+ }
195
+ /** Every occurrence of `query`, atom-safe (matches never span images/fields
196
+ * or block boundaries), in document order with absolute PM positions. */
197
+ hits(query) {
198
+ if (query.length === 0) return [];
199
+ const out = [];
200
+ this.textblocks().forEach(({ node, pos }, blockIndex) => {
201
+ const segments = [];
202
+ let joined = "";
203
+ node.forEach((child, offset) => {
204
+ if (child.isText && child.text) {
205
+ segments.push({ joinedStart: joined.length, length: child.text.length, startPos: pos + 1 + offset });
206
+ joined += child.text;
207
+ } else {
208
+ joined += "\uFFFF";
209
+ }
210
+ });
211
+ let at = joined.indexOf(query);
212
+ while (at !== -1) {
213
+ const seg = segments.find((s) => at >= s.joinedStart && at < s.joinedStart + s.length);
214
+ if (seg) {
215
+ const from = seg.startPos + (at - seg.joinedStart);
216
+ out.push({
217
+ from,
218
+ to: from + query.length,
219
+ blockIndex,
220
+ context: contextAround(joined.replace(/￿/g, " "), at, query.length)
221
+ });
222
+ }
223
+ at = joined.indexOf(query, at + 1);
224
+ }
225
+ });
226
+ return out;
227
+ }
228
+ uniqueHit(text, occurrence2) {
229
+ const all = this.hits(text);
230
+ if (all.length === 0) {
231
+ throw new AnchorError(
232
+ `Text not found in the document: ${JSON.stringify(clip(text))}. Anchors match within one paragraph \u2014 check get_document for the exact text.`
233
+ );
234
+ }
235
+ if (occurrence2 !== void 0) {
236
+ const hit = all[occurrence2 - 1];
237
+ if (!hit) {
238
+ throw new AnchorError(`occurrence ${occurrence2} is out of range \u2014 the text matches ${all.length} time(s).`);
239
+ }
240
+ return hit;
241
+ }
242
+ if (all.length > 1) {
243
+ throw new AnchorError(
244
+ `The text matches ${all.length} times \u2014 pass occurrence (1-${all.length}) to pick one, or use a longer, unique anchor.`
245
+ );
246
+ }
247
+ return all[0];
248
+ }
249
+ };
250
+ function blockImages(block) {
251
+ const out = [];
252
+ block.forEach((child, offset) => {
253
+ if (child.type.name === "image") out.push({ node: child, offset });
254
+ });
255
+ return out;
256
+ }
257
+ function blockType(node) {
258
+ const heading = node.attrs["heading"];
259
+ if (typeof heading === "number" && heading >= 1) return `heading${heading}`;
260
+ return node.type.name;
261
+ }
262
+ function contextAround(text, at, len) {
263
+ const before = text.slice(Math.max(0, at - 30), at);
264
+ const after = text.slice(at + len, at + len + 30);
265
+ return `${before}\xAB${text.slice(at, at + len)}\xBB${after}`;
266
+ }
267
+ function clip(s) {
268
+ return s.length > 60 ? `${s.slice(0, 57)}\u2026` : s;
269
+ }
270
+
271
+ // packages/mcp/src/lib/wire.ts
272
+ async function executeOp(session, request) {
273
+ try {
274
+ if (!session) throw new NoDocumentError();
275
+ const value = await run(session, request);
276
+ return { id: request.id, ok: true, value };
277
+ } catch (err) {
278
+ const e = err instanceof Error ? err : new Error(String(err));
279
+ return { id: request.id, ok: false, error: { name: e.name, message: e.message } };
280
+ }
281
+ }
282
+ function run(session, { op, args }) {
283
+ switch (op) {
284
+ case "snapshot":
285
+ return session.snapshot();
286
+ case "find":
287
+ return session.find(args[0]);
288
+ case "replaceText":
289
+ return session.replaceText(args[0], args[1], args[2]);
290
+ case "insertContent":
291
+ return session.insertContent(args[0], args[1], args[2]);
292
+ case "applyFormatting":
293
+ return session.applyFormatting(args[0], args[1], args[2]);
294
+ case "updateImage":
295
+ return session.updateImage(
296
+ args[0],
297
+ args[1],
298
+ args[2],
299
+ args[3]
300
+ );
301
+ case "getSelection":
302
+ return session.getSelection?.() ?? Promise.resolve(null);
303
+ case "save":
304
+ return session.save();
305
+ default:
306
+ return Promise.reject(new Error(`unknown session op: ${op}`));
307
+ }
308
+ }
309
+ function reviveError(error) {
310
+ switch (error.name) {
311
+ case "AnchorError":
312
+ return new AnchorError(error.message);
313
+ case "NoDocumentError":
314
+ return new NoDocumentError(error.message);
315
+ case "VersionConflictError": {
316
+ const revived = new VersionConflictError("?", "?");
317
+ revived.message = error.message;
318
+ return revived;
319
+ }
320
+ default: {
321
+ const generic = new Error(error.message);
322
+ generic.name = error.name;
323
+ return generic;
324
+ }
325
+ }
326
+ }
327
+ var RemoteSession = class {
328
+ constructor(send, capabilities) {
329
+ this.send = send;
330
+ this.capabilities = capabilities;
331
+ }
332
+ send;
333
+ capabilities;
334
+ async call(op, args = []) {
335
+ return await this.send(op, args);
336
+ }
337
+ snapshot() {
338
+ return this.call("snapshot");
339
+ }
340
+ find(query) {
341
+ return this.call("find", [query]);
342
+ }
343
+ replaceText(oldText, newText, opts) {
344
+ return this.call("replaceText", [oldText, newText, opts]);
345
+ }
346
+ insertContent(content, anchor, opts) {
347
+ return this.call("insertContent", [content, anchor, opts]);
348
+ }
349
+ applyFormatting(target, format, opts) {
350
+ return this.call("applyFormatting", [target, format, opts]);
351
+ }
352
+ updateImage(blockIndex, imageIndex, changes, opts) {
353
+ return this.call("updateImage", [blockIndex, imageIndex, changes, opts]);
354
+ }
355
+ async getSelection() {
356
+ return this.call("getSelection");
357
+ }
358
+ async save() {
359
+ await this.call("save");
360
+ }
361
+ async close() {
362
+ }
363
+ };
364
+
365
+ // packages/mcp/src/lib/server.ts
366
+ var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
367
+ var import_zod = require("zod");
368
+ var documentId = import_zod.z.string().optional().describe("Target document id. Omit for the currently open document (desktop).");
369
+ var expectedVersion = import_zod.z.string().optional().describe("docVersion you last read. If the document changed since, the call fails and you must re-read.");
370
+ var occurrence = import_zod.z.number().int().min(1).optional().describe("1-based pick when the anchor text matches more than once (document order).");
371
+ function json(value) {
372
+ return { content: [{ type: "text", text: JSON.stringify(value, null, 1) }] };
373
+ }
374
+ function errorText(message) {
375
+ return { content: [{ type: "text", text: message }], isError: true };
376
+ }
377
+ function createMcpServer(provider, opts = {}) {
378
+ const server = new import_mcp.McpServer({
379
+ name: opts.name ?? "bapbong",
380
+ version: opts.version ?? "0.0.1"
381
+ });
382
+ const withSession = async (id, fn) => {
383
+ const session = await provider.get(id);
384
+ if (!session) {
385
+ return errorText(
386
+ id === void 0 ? "No document is open. Ask the user to open a document first." : `No document with id ${JSON.stringify(id)}.`
387
+ );
388
+ }
389
+ try {
390
+ return await fn(session);
391
+ } catch (err) {
392
+ if (err instanceof AnchorError || err instanceof VersionConflictError || err instanceof NoDocumentError) {
393
+ return errorText(err.message);
394
+ }
395
+ throw err;
396
+ }
397
+ };
398
+ server.registerTool(
399
+ "get_document",
400
+ {
401
+ title: "Read the document",
402
+ description: "Read the whole document as numbered blocks (paragraphs, headings \u2014 table-cell paragraphs included, in reading order). Returns docVersion: pass it as expectedVersion to mutation tools so concurrent edits are detected. Block indexes are only stable within one docVersion.",
403
+ inputSchema: { documentId }
404
+ },
405
+ async ({ documentId: id }) => withSession(id, async (s) => json(await s.snapshot()))
406
+ );
407
+ server.registerTool(
408
+ "find_text",
409
+ {
410
+ title: "Find text",
411
+ description: "Find every occurrence of a text in the document. Matches are within one paragraph (they never span paragraphs or inline objects). Returns each match with its block index, 1-based occurrence number, and surrounding context.",
412
+ inputSchema: { documentId, query: import_zod.z.string().min(1).describe("Exact text to find (case-sensitive).") }
413
+ },
414
+ async ({ documentId: id, query }) => withSession(id, async (s) => json({ matches: await s.find(query) }))
415
+ );
416
+ server.registerTool(
417
+ "replace_text",
418
+ {
419
+ title: "Replace text",
420
+ description: "Replace one occurrence of exact text. old_text must match exactly once in the document \u2014 if it matches more, either pass occurrence or use a longer, unique anchor (include surrounding words). Formatting of the replaced range is kept.",
421
+ inputSchema: {
422
+ documentId,
423
+ old_text: import_zod.z.string().min(1).describe("Exact existing text (must match uniquely, or pass occurrence)."),
424
+ new_text: import_zod.z.string().describe("Replacement text (empty string deletes)."),
425
+ occurrence,
426
+ expectedVersion
427
+ }
428
+ },
429
+ async ({ documentId: id, old_text, new_text, occurrence: occ, expectedVersion: ver }) => withSession(id, async (s) => json(await s.replaceText(old_text, new_text, { occurrence: occ, expectedVersion: ver })))
430
+ );
431
+ server.registerTool(
432
+ "insert_content",
433
+ {
434
+ title: "Insert content",
435
+ description: "Insert new paragraphs. Each line of `content` becomes one paragraph. Anchor by exact text with position before/after (the paragraph containing the anchor), or position document_end to append.",
436
+ inputSchema: {
437
+ documentId,
438
+ content: import_zod.z.string().min(1).describe("Plain text; every line becomes a paragraph."),
439
+ position: import_zod.z.enum(["before", "after", "document_end"]),
440
+ anchor_text: import_zod.z.string().optional().describe("Required for before/after: exact text inside the anchor paragraph."),
441
+ occurrence,
442
+ expectedVersion
443
+ }
444
+ },
445
+ async ({ documentId: id, content, position, anchor_text, occurrence: occ, expectedVersion: ver }) => withSession(id, async (s) => {
446
+ if (position === "document_end") {
447
+ return json(await s.insertContent(content, { position: "document_end" }, { expectedVersion: ver }));
448
+ }
449
+ if (!anchor_text) return errorText("anchor_text is required when position is before/after.");
450
+ return json(
451
+ await s.insertContent(content, { position, text: anchor_text, occurrence: occ }, { expectedVersion: ver })
452
+ );
453
+ })
454
+ );
455
+ server.registerTool(
456
+ "apply_formatting",
457
+ {
458
+ title: "Apply formatting",
459
+ description: "Format one occurrence of exact text: character marks (bold/italic/underline/strike \u2014 true applies, false removes) and/or paragraph alignment of the containing paragraph. Same anchoring rules as replace_text.",
460
+ inputSchema: {
461
+ documentId,
462
+ target_text: import_zod.z.string().min(1).describe("Exact text to format (must match uniquely, or pass occurrence)."),
463
+ bold: import_zod.z.boolean().optional(),
464
+ italic: import_zod.z.boolean().optional(),
465
+ underline: import_zod.z.boolean().optional(),
466
+ strike: import_zod.z.boolean().optional(),
467
+ align: import_zod.z.enum(["left", "center", "right", "justify"]).optional(),
468
+ occurrence,
469
+ expectedVersion
470
+ }
471
+ },
472
+ async ({ documentId: id, target_text, occurrence: occ, expectedVersion: ver, ...format }) => withSession(id, async (s) => json(await s.applyFormatting(target_text, format, { occurrence: occ, expectedVersion: ver })))
473
+ );
474
+ server.registerTool(
475
+ "update_image",
476
+ {
477
+ title: "Resize / rotate an image",
478
+ description: "Resize and/or rotate one image. Read get_document first: blocks list their images, and (block_index, image_index) addresses one. Omitted fields keep their current value; width/height are CSS px, rotation is clockwise degrees around the image center (normalized to 0-360; 0 = upright). Block indexes change with every edit \u2014 pass expectedVersion from your last read.",
479
+ inputSchema: {
480
+ documentId,
481
+ block_index: import_zod.z.number().int().min(0).describe("Index of the block holding the image (from get_document)."),
482
+ image_index: import_zod.z.number().int().min(0).optional().describe("0-based image within the block (default 0, the first)."),
483
+ width: import_zod.z.number().positive().optional().describe("New width in CSS px."),
484
+ height: import_zod.z.number().positive().optional().describe("New height in CSS px."),
485
+ rotation: import_zod.z.number().optional().describe("Clockwise degrees around the center."),
486
+ expectedVersion
487
+ }
488
+ },
489
+ async ({ documentId: id, block_index, image_index, width, height, rotation, expectedVersion: ver }) => withSession(id, async (s) => {
490
+ if (width === void 0 && height === void 0 && rotation === void 0) {
491
+ return errorText("Pass at least one of width, height, rotation.");
492
+ }
493
+ return json(
494
+ await s.updateImage(block_index, image_index ?? 0, { width, height, rotation }, { expectedVersion: ver })
495
+ );
496
+ })
497
+ );
498
+ server.registerTool(
499
+ "save_document",
500
+ {
501
+ title: "Save the document",
502
+ description: "Persist the document to its backing store (file on desktop). Usually optional \u2014 the desktop autosaves.",
503
+ inputSchema: { documentId }
504
+ },
505
+ async ({ documentId: id }) => withSession(id, async (s) => {
506
+ await s.save();
507
+ return json({ saved: true });
508
+ })
509
+ );
510
+ if (opts.selection) {
511
+ server.registerTool(
512
+ "get_selection",
513
+ {
514
+ title: "Read the user's selection",
515
+ description: "The text the user currently has selected in the editor, or null when nothing is selected.",
516
+ inputSchema: { documentId }
517
+ },
518
+ async ({ documentId: id }) => withSession(id, async (s) => json(await s.getSelection?.() ?? { selection: null }))
519
+ );
520
+ }
521
+ server.registerResource(
522
+ "document",
523
+ "bapbong://document",
524
+ {
525
+ title: "Open document",
526
+ description: "The currently open document as plain text (one line per block).",
527
+ mimeType: "text/plain"
528
+ },
529
+ async (uri) => {
530
+ const session = await provider.get(void 0);
531
+ if (!session) return { contents: [{ uri: uri.href, text: "(no document open)" }] };
532
+ const snap = await session.snapshot();
533
+ return { contents: [{ uri: uri.href, text: snap.blocks.map((b) => b.text).join("\n") }] };
534
+ }
535
+ );
536
+ return server;
537
+ }
538
+ // Annotate the CommonJS export names for ESM import in node:
539
+ 0 && (module.exports = {
540
+ AnchorError,
541
+ NoDocumentError,
542
+ PmDocSession,
543
+ RemoteSession,
544
+ VersionConflictError,
545
+ createMcpServer,
546
+ executeOp,
547
+ reviveError
548
+ });
package/dist/host.d.ts ADDED
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Host-process surface: everything in ./session plus the MCP server factory
3
+ * (which brings the @modelcontextprotocol/sdk + zod runtime deps). The
4
+ * desktop Bun process imports THIS; it deliberately excludes HeadlessSession
5
+ * so a host that proxies to a live editor doesn't drag the docx pipeline in.
6
+ */
7
+ export * from './session.js';
8
+ export { createMcpServer, type CreateMcpServerOptions } from './lib/server.js';
9
+ //# sourceMappingURL=host.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"host.d.ts","sourceRoot":"","sources":["../src/host.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,cAAc,cAAc,CAAC;AAC7B,OAAO,EAAE,eAAe,EAAE,KAAK,sBAAsB,EAAE,MAAM,iBAAiB,CAAC"}