@abraca/mcp 1.8.1 → 1.9.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abraca/mcp",
3
- "version": "1.8.1",
3
+ "version": "1.9.1",
4
4
  "description": "MCP server for Abracadabra — AI agent collaboration on CRDT documents",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -164,6 +164,10 @@ export function parseFrontmatter(markdown: string): FrontmatterResult {
164
164
  interface InlineToken {
165
165
  text: string
166
166
  attrs?: Record<string, unknown>
167
+ /** If set, emit an inline Y.XmlElement with this nodeName instead of text+marks. */
168
+ node?: string
169
+ /** Attributes for the inline node (when node is set). */
170
+ nodeAttrs?: Record<string, string>
167
171
  }
168
172
 
169
173
  function parseInline(text: string): InlineToken[] {
@@ -190,10 +194,9 @@ function parseInline(text: string): InlineToken[] {
190
194
  const kbdProps = parseMdcProps(`{${match[4]}}`)
191
195
  tokens.push({ text: kbdProps['value'] || '', attrs: { kbd: { value: kbdProps['value'] || '' } } })
192
196
  } else if (match[5] !== undefined) {
193
- // Inline wikilink [[docId]] or [[docId|label]] → link to /doc/docId
194
- const docId = match[5]
195
- const displayText = match[6] ?? docId
196
- tokens.push({ text: displayText!, attrs: { link: { href: `/doc/${docId}` } } })
197
+ // Inline wikilink [[docId]] or [[docId|label]] → inline docLink node
198
+ // (the label is derived from the tree at render time; legacy label is dropped)
199
+ tokens.push({ text: '', node: 'docLink', nodeAttrs: { docId: match[5]! } })
197
200
  } else if (match[7] !== undefined) {
198
201
  tokens.push({ text: match[7], attrs: { strike: true } })
199
202
  } else if (match[8] !== undefined) {
@@ -213,7 +216,7 @@ function parseInline(text: string): InlineToken[] {
213
216
  if (lastIndex < stripped.length) {
214
217
  tokens.push({ text: stripped.slice(lastIndex) })
215
218
  }
216
- return tokens.filter(t => t.text.length > 0)
219
+ return tokens.filter(t => t.node || t.text.length > 0)
217
220
  }
218
221
 
219
222
  // ── Block-level parser ───────────────────────────────────────────────────────
@@ -247,7 +250,7 @@ type Block =
247
250
  | { type: 'field'; name: string; fieldType: string; required: boolean; innerBlocks: Block[] }
248
251
  | { type: 'fieldGroup'; fields: Block[] }
249
252
  | { type: 'image'; src: string; alt: string; width?: string; height?: string }
250
- | { type: 'docEmbed'; docId: string }
253
+ | { type: 'docEmbed'; docId: string; seamless?: boolean }
251
254
  | { type: 'svgEmbed'; svg: string; title: string }
252
255
 
253
256
  function parseTableRow(line: string): string[] {
@@ -382,9 +385,11 @@ function parseBlocks(markdown: string): Block[] {
382
385
  continue
383
386
  }
384
387
 
385
- const docEmbedMatch = line.match(/^!\[\[([^\]|]+?)(?:\|[^\]]*?)?\]\]\s*$/)
388
+ const docEmbedMatch = line.match(/^!\[\[([^\]|]+?)(?:\|[^\]]*?)?\]\](\{[^}]*\})?\s*$/)
386
389
  if (docEmbedMatch) {
387
- blocks.push({ type: 'docEmbed', docId: docEmbedMatch[1]! })
390
+ const props = parseMdcProps(docEmbedMatch[2])
391
+ const seamless = 'seamless' in props || props['seamless'] === 'true' || /\{[^}]*\bseamless\b[^}]*\}/.test(docEmbedMatch[2] ?? '')
392
+ blocks.push({ type: 'docEmbed', docId: docEmbedMatch[1]!, seamless: seamless || undefined })
388
393
  i++
389
394
  continue
390
395
  }
@@ -600,17 +605,28 @@ function parseBlocks(markdown: string): Block[] {
600
605
  // ── Y.js content population ──────────────────────────────────────────────────
601
606
 
602
607
  function fillTextInto(el: Y.XmlElement, tokens: InlineToken[]): void {
603
- const filtered = tokens.filter(t => t.text.length > 0)
608
+ const filtered = tokens.filter(t => t.node || t.text.length > 0)
604
609
  if (!filtered.length) return
605
610
 
606
- const xtNodes = filtered.map(() => new Y.XmlText())
607
- el.insert(0, xtNodes)
611
+ const children: (Y.XmlText | Y.XmlElement)[] = filtered.map((tok) => {
612
+ if (tok.node) {
613
+ const xe = new Y.XmlElement(tok.node)
614
+ if (tok.nodeAttrs) {
615
+ for (const [k, v] of Object.entries(tok.nodeAttrs)) xe.setAttribute(k, v)
616
+ }
617
+ return xe
618
+ }
619
+ return new Y.XmlText()
620
+ })
621
+ el.insert(0, children)
608
622
 
609
623
  filtered.forEach((tok, i) => {
624
+ if (tok.node) return
625
+ const xt = children[i] as Y.XmlText
610
626
  if (tok.attrs) {
611
- xtNodes[i]!.insert(0, tok.text, tok.attrs as Record<string, boolean | object>)
627
+ xt.insert(0, tok.text, tok.attrs as Record<string, boolean | object>)
612
628
  } else {
613
- xtNodes[i]!.insert(0, tok.text)
629
+ xt.insert(0, tok.text)
614
630
  }
615
631
  })
616
632
  }
@@ -837,6 +853,7 @@ function fillBlock(el: Y.XmlElement, block: Block): void {
837
853
  }
838
854
  case 'docEmbed': {
839
855
  el.setAttribute('docId', block.docId)
856
+ if (block.seamless) el.setAttribute('seamless', 'true')
840
857
  break
841
858
  }
842
859
  case 'svgEmbed': {
@@ -38,6 +38,12 @@ function elementTextContent(el: Y.XmlElement | Y.XmlFragment): string {
38
38
  if (child instanceof Y.XmlText) {
39
39
  parts.push(xmlTextToMarkdown(child))
40
40
  } else if (child instanceof Y.XmlElement) {
41
+ // Inline nodes that carry meaning at the inline level.
42
+ if (child.nodeName === 'docLink') {
43
+ const docId = child.getAttribute('docId')
44
+ if (docId) parts.push(`[[${docId}]]`)
45
+ continue
46
+ }
41
47
  parts.push(elementTextContent(child))
42
48
  }
43
49
  }
@@ -97,7 +103,10 @@ function serializeElement(el: Y.XmlElement, indent = ''): string {
97
103
 
98
104
  case 'docEmbed': {
99
105
  const docId = el.getAttribute('docId')
100
- return docId ? `![[${docId}]]` : ''
106
+ if (!docId) return ''
107
+ const seamlessAttr = el.getAttribute('seamless')
108
+ const seamless = seamlessAttr === true || seamlessAttr === 'true'
109
+ return seamless ? `![[${docId}]]{seamless}` : `![[${docId}]]`
101
110
  }
102
111
 
103
112
  case 'svgEmbed': {