tiptap-apcore 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.
Files changed (70) hide show
  1. package/README.md +407 -0
  2. package/dist/builder/AnnotationCatalog.d.ts +22 -0
  3. package/dist/builder/AnnotationCatalog.d.ts.map +1 -0
  4. package/dist/builder/AnnotationCatalog.js +269 -0
  5. package/dist/builder/AnnotationCatalog.js.map +1 -0
  6. package/dist/builder/ModuleBuilder.d.ts +42 -0
  7. package/dist/builder/ModuleBuilder.d.ts.map +1 -0
  8. package/dist/builder/ModuleBuilder.js +106 -0
  9. package/dist/builder/ModuleBuilder.js.map +1 -0
  10. package/dist/builder/SchemaCatalog.d.ts +20 -0
  11. package/dist/builder/SchemaCatalog.d.ts.map +1 -0
  12. package/dist/builder/SchemaCatalog.js +352 -0
  13. package/dist/builder/SchemaCatalog.js.map +1 -0
  14. package/dist/builder/index.d.ts +4 -0
  15. package/dist/builder/index.d.ts.map +1 -0
  16. package/dist/builder/index.js +5 -0
  17. package/dist/builder/index.js.map +1 -0
  18. package/dist/discovery/ExtensionScanner.d.ts +25 -0
  19. package/dist/discovery/ExtensionScanner.d.ts.map +1 -0
  20. package/dist/discovery/ExtensionScanner.js +61 -0
  21. package/dist/discovery/ExtensionScanner.js.map +1 -0
  22. package/dist/discovery/index.d.ts +2 -0
  23. package/dist/discovery/index.d.ts.map +1 -0
  24. package/dist/discovery/index.js +3 -0
  25. package/dist/discovery/index.js.map +1 -0
  26. package/dist/errors/TiptapModuleError.d.ts +18 -0
  27. package/dist/errors/TiptapModuleError.d.ts.map +1 -0
  28. package/dist/errors/TiptapModuleError.js +23 -0
  29. package/dist/errors/TiptapModuleError.js.map +1 -0
  30. package/dist/errors/index.d.ts +2 -0
  31. package/dist/errors/index.d.ts.map +1 -0
  32. package/dist/errors/index.js +2 -0
  33. package/dist/errors/index.js.map +1 -0
  34. package/dist/index.d.ts +19 -0
  35. package/dist/index.d.ts.map +1 -0
  36. package/dist/index.js +17 -0
  37. package/dist/index.js.map +1 -0
  38. package/dist/runtime/TiptapExecutor.d.ts +20 -0
  39. package/dist/runtime/TiptapExecutor.d.ts.map +1 -0
  40. package/dist/runtime/TiptapExecutor.js +327 -0
  41. package/dist/runtime/TiptapExecutor.js.map +1 -0
  42. package/dist/runtime/TiptapRegistry.d.ts +35 -0
  43. package/dist/runtime/TiptapRegistry.d.ts.map +1 -0
  44. package/dist/runtime/TiptapRegistry.js +100 -0
  45. package/dist/runtime/TiptapRegistry.js.map +1 -0
  46. package/dist/runtime/index.d.ts +3 -0
  47. package/dist/runtime/index.d.ts.map +1 -0
  48. package/dist/runtime/index.js +4 -0
  49. package/dist/runtime/index.js.map +1 -0
  50. package/dist/security/AclGuard.d.ts +10 -0
  51. package/dist/security/AclGuard.d.ts.map +1 -0
  52. package/dist/security/AclGuard.js +96 -0
  53. package/dist/security/AclGuard.js.map +1 -0
  54. package/dist/security/index.d.ts +2 -0
  55. package/dist/security/index.d.ts.map +1 -0
  56. package/dist/security/index.js +2 -0
  57. package/dist/security/index.js.map +1 -0
  58. package/dist/server.d.ts +10 -0
  59. package/dist/server.d.ts.map +1 -0
  60. package/dist/server.js +9 -0
  61. package/dist/server.js.map +1 -0
  62. package/dist/types.d.ts +116 -0
  63. package/dist/types.d.ts.map +1 -0
  64. package/dist/types.js +8 -0
  65. package/dist/types.js.map +1 -0
  66. package/dist/withApcore.d.ts +14 -0
  67. package/dist/withApcore.d.ts.map +1 -0
  68. package/dist/withApcore.js +81 -0
  69. package/dist/withApcore.js.map +1 -0
  70. package/package.json +79 -0
package/README.md ADDED
@@ -0,0 +1,407 @@
1
+ # tiptap-apcore
2
+
3
+ Let AI safely control your TipTap editor via the [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) and OpenAI Function Calling.
4
+
5
+ **tiptap-apcore** wraps every TipTap editor command as a schema-driven [APCore](https://github.com/aipartnerup) module — complete with JSON Schema validation, safety annotations, and fine-grained access control. Any MCP-compatible AI agent can then discover and invoke these modules to read, format, insert, or restructure rich-text content.
6
+
7
+ ## Features
8
+
9
+ - **79 built-in commands** across 7 categories (query, format, content, destructive, selection, history, unknown)
10
+ - **Automatic extension discovery** — scans TipTap extensions at runtime, no manual wiring
11
+ - **MCP Server** in one line — `serve(executor)` exposes all commands via stdio / HTTP / SSE
12
+ - **OpenAI Function Calling** — `toOpenaiTools(executor)` exports tool definitions for GPT
13
+ - **Role-based ACL** — `readonly`, `editor`, `admin` roles with tag-level and module-level overrides
14
+ - **Safety annotations** — every command tagged `readonly`, `destructive`, `idempotent`, `requiresApproval`, `openWorld`, `streaming`
15
+ - **Strict JSON Schemas** — `inputSchema` + `outputSchema` with `additionalProperties: false` for all known commands
16
+ - **Dynamic re-discovery** — call `registry.discover()` to pick up extensions added at runtime
17
+ - **925 tests**, 99.7% statement coverage
18
+
19
+ ## Installation
20
+
21
+ ```bash
22
+ npm install tiptap-apcore apcore-js apcore-mcp @tiptap/core
23
+ ```
24
+
25
+ `apcore-js`, `apcore-mcp`, and `@tiptap/core` are peer dependencies.
26
+
27
+ ## Quick Start
28
+
29
+ ```typescript
30
+ import { Editor } from "@tiptap/core";
31
+ import StarterKit from "@tiptap/starter-kit";
32
+ import { withApcore, serve, toOpenaiTools } from "tiptap-apcore";
33
+
34
+ // 1. Create a TipTap editor
35
+ const editor = new Editor({
36
+ extensions: [StarterKit],
37
+ content: "<p>Hello world</p>",
38
+ });
39
+
40
+ // 2. Create APCore registry + executor
41
+ const { registry, executor } = withApcore(editor, {
42
+ acl: { role: "editor" }, // no destructive ops
43
+ });
44
+
45
+ // 3a. Launch an MCP Server (stdio)
46
+ await serve(executor);
47
+
48
+ // 3b. Or export OpenAI tool definitions
49
+ const tools = toOpenaiTools(executor);
50
+
51
+ // 3c. Or call commands directly
52
+ await executor.call("tiptap.format.toggleBold", {});
53
+ const { html } = await executor.call("tiptap.query.getHTML", {});
54
+ ```
55
+
56
+ ## Commands
57
+
58
+ All commands follow the module ID pattern `{prefix}.{category}.{commandName}`.
59
+
60
+ ### Query (10 commands) — `readonly`, `idempotent`
61
+
62
+ | Command | Input | Output |
63
+ |---------|-------|--------|
64
+ | `getHTML` | — | `{ html: string }` |
65
+ | `getJSON` | — | `{ json: object }` |
66
+ | `getText` | `{ blockSeparator?: string }` | `{ text: string }` |
67
+ | `isActive` | `{ name: string, attrs?: object }` | `{ active: boolean }` |
68
+ | `getAttributes` | `{ typeOrName: string }` | `{ attributes: object }` |
69
+ | `isEmpty` | — | `{ value: boolean }` |
70
+ | `isEditable` | — | `{ value: boolean }` |
71
+ | `isFocused` | — | `{ value: boolean }` |
72
+ | `getCharacterCount` | — | `{ count: number }` |
73
+ | `getWordCount` | — | `{ count: number }` |
74
+
75
+ ### Format (36 commands) — non-destructive
76
+
77
+ `toggleBold`, `toggleItalic`, `toggleStrike`, `toggleCode`, `toggleUnderline`, `toggleSubscript`, `toggleSuperscript`, `toggleHighlight`, `toggleHeading`, `toggleBulletList`, `toggleOrderedList`, `toggleTaskList`, `toggleCodeBlock`, `toggleBlockquote`, `setTextAlign`, `setMark`, `unsetMark`, `unsetAllMarks`, `clearNodes`, `updateAttributes`, `setLink`, `unsetLink`, `setHardBreak`, `setHorizontalRule`, `setBold`, `setItalic`, `setStrike`, `setCode`, `unsetBold`, `unsetItalic`, `unsetStrike`, `unsetCode`, `setBlockquote`, `unsetBlockquote`, `setHeading`, `setParagraph`
78
+
79
+ ### Content (15 commands)
80
+
81
+ `insertContent`, `insertContentAt`, `setNode`, `splitBlock`, `liftListItem`, `sinkListItem`, `wrapIn`, `joinBackward`, `joinForward`, `lift`, `splitListItem`, `wrapInList`, `toggleList`, `exitCode`, `deleteNode`
82
+
83
+ ### Destructive (6 commands) — `requiresApproval`
84
+
85
+ `clearContent`, `setContent`, `deleteSelection`, `deleteRange`, `deleteCurrentNode`, `cut`
86
+
87
+ ### Selection (10 commands) — `idempotent`
88
+
89
+ `setTextSelection`, `setNodeSelection`, `selectAll`, `selectParentNode`, `selectTextblockStart`, `selectTextblockEnd`, `selectText`, `focus`, `blur`, `scrollIntoView`
90
+
91
+ ### History (2 commands)
92
+
93
+ `undo`, `redo`
94
+
95
+ ### Unknown
96
+
97
+ Commands discovered from extensions but not in the built-in catalog. Excluded by default (`includeUnsafe: false`). Set `includeUnsafe: true` to include them with permissive schemas.
98
+
99
+ ## Access Control (ACL)
100
+
101
+ ```typescript
102
+ // Read-only: only query commands
103
+ withApcore(editor, { acl: { role: "readonly" } });
104
+
105
+ // Editor: query + format + content + history + selection
106
+ withApcore(editor, { acl: { role: "editor" } });
107
+
108
+ // Admin: everything including destructive
109
+ withApcore(editor, { acl: { role: "admin" } });
110
+
111
+ // Custom: readonly base + allow format tag
112
+ withApcore(editor, { acl: { role: "readonly", allowTags: ["format"] } });
113
+
114
+ // Custom: admin but deny destructive tag
115
+ withApcore(editor, { acl: { role: "admin", denyTags: ["destructive"] } });
116
+
117
+ // Module-level: deny specific commands
118
+ withApcore(editor, {
119
+ acl: { role: "admin", denyModules: ["tiptap.destructive.clearContent"] },
120
+ });
121
+ ```
122
+
123
+ **Precedence:** `denyModules` > `allowModules` > `denyTags` > `allowTags` > role
124
+
125
+ > **Note:** `allowModules` is additive — it grants access to listed modules but does not deny unlisted ones. Combine with a role to restrict the baseline.
126
+
127
+ ## MCP Server
128
+
129
+ ```typescript
130
+ import { withApcore, serve } from "tiptap-apcore";
131
+
132
+ const { executor } = withApcore(editor);
133
+
134
+ // stdio (default)
135
+ await serve(executor);
136
+
137
+ // HTTP streaming
138
+ await serve(executor, {
139
+ transport: "streamable-http",
140
+ host: "127.0.0.1",
141
+ port: 8000,
142
+ });
143
+
144
+ // Server-Sent Events
145
+ await serve(executor, { transport: "sse", port: 3000 });
146
+ ```
147
+
148
+ ## OpenAI Function Calling
149
+
150
+ ```typescript
151
+ import { withApcore, toOpenaiTools } from "tiptap-apcore";
152
+
153
+ const { executor } = withApcore(editor);
154
+ const tools = toOpenaiTools(executor);
155
+
156
+ // Use with OpenAI API
157
+ const response = await openai.chat.completions.create({
158
+ model: "gpt-4o",
159
+ messages: [...],
160
+ tools,
161
+ });
162
+ ```
163
+
164
+ ## Vercel AI SDK
165
+
166
+ APCore's JSON schemas work directly with AI SDK's `jsonSchema()` — no Zod conversion needed. Combined with `generateText({ maxSteps })`, the tool-use loop is fully automatic.
167
+
168
+ ```typescript
169
+ import { generateText, tool, jsonSchema } from "ai";
170
+ import { openai } from "@ai-sdk/openai";
171
+ import { withApcore } from "tiptap-apcore";
172
+
173
+ const { registry, executor } = withApcore(editor, { acl: { role: "editor" } });
174
+
175
+ // Convert APCore modules to AI SDK tools
176
+ const tools: Record<string, CoreTool> = {};
177
+ for (const id of registry.list()) {
178
+ const def = registry.getDefinition(id)!;
179
+ tools[id.replaceAll(".", "-")] = tool({
180
+ description: def.description,
181
+ parameters: jsonSchema(def.inputSchema),
182
+ execute: (args) => executor.call(id, args),
183
+ });
184
+ }
185
+
186
+ const { text, steps } = await generateText({
187
+ model: openai("gpt-4o"),
188
+ system: "You are an editor assistant...",
189
+ messages,
190
+ tools,
191
+ maxSteps: 10,
192
+ });
193
+ ```
194
+
195
+ ## API Reference
196
+
197
+ ### `withApcore(editor, options?)`
198
+
199
+ Creates an APCore `{ registry, executor }` pair from a TipTap editor.
200
+
201
+ | Option | Type | Default | Description |
202
+ |--------|------|---------|-------------|
203
+ | `prefix` | `string` | `"tiptap"` | Module ID prefix (lowercase alphanumeric) |
204
+ | `acl` | `AclConfig` | `undefined` | Access control configuration (permissive if omitted) |
205
+ | `includeUnsafe` | `boolean` | `false` | Include commands not in the built-in catalog |
206
+
207
+ ### Registry Methods
208
+
209
+ | Method | Description |
210
+ |--------|-------------|
211
+ | `list(options?)` | List module IDs, optionally filtered by `tags` (OR) and/or `prefix` |
212
+ | `getDefinition(moduleId)` | Get full `ModuleDescriptor` or `null` |
213
+ | `has(moduleId)` | Check if a module exists |
214
+ | `iter()` | Iterate `[moduleId, descriptor]` pairs |
215
+ | `count` | Number of registered modules |
216
+ | `moduleIds` | Array of all module IDs |
217
+ | `on(event, callback)` | Listen for `"register"` / `"unregister"` events |
218
+ | `discover()` | Re-scan extensions and update registry |
219
+
220
+ ### Executor Methods
221
+
222
+ | Method | Description |
223
+ |--------|-------------|
224
+ | `call(moduleId, inputs)` | Execute a module (async) |
225
+ | `callAsync(moduleId, inputs)` | Alias for `call()` |
226
+
227
+ ### Error Codes
228
+
229
+ | Code | Description |
230
+ |------|-------------|
231
+ | `MODULE_NOT_FOUND` | Module ID not registered |
232
+ | `COMMAND_NOT_FOUND` | Command not available on editor |
233
+ | `ACL_DENIED` | Access denied by ACL policy |
234
+ | `EDITOR_NOT_READY` | Editor is destroyed |
235
+ | `COMMAND_FAILED` | TipTap command returned false |
236
+ | `SCHEMA_VALIDATION_ERROR` | Invalid options (bad prefix, bad role) |
237
+ | `INTERNAL_ERROR` | Unexpected error |
238
+
239
+ ## Comparison with TipTap AI Toolkit
240
+
241
+ TipTap's official AI solution is the **[AI Toolkit](https://tiptap.dev/docs/ai-toolkit/getting-started/overview)** (`@tiptap-pro/ai-toolkit`), a paid extension for client-side AI-powered editing. The two projects serve different use cases and are complementary.
242
+
243
+ ### Architecture
244
+
245
+ | | TipTap AI Toolkit | tiptap-apcore |
246
+ |---|---|---|
247
+ | **Type** | Client-side TipTap extension | Server-side / headless adapter |
248
+ | **License** | Proprietary (TipTap Pro subscription) | Apache-2.0 (open source) |
249
+ | **Runtime** | Browser only | Browser + Node.js + headless |
250
+ | **Protocol** | Provider-specific adapters | MCP standard + OpenAI Function Calling |
251
+ | **Approach** | AI generates content, streams into editor | AI invokes structured commands on editor |
252
+
253
+ ### Command Granularity
254
+
255
+ | | TipTap AI Toolkit | tiptap-apcore |
256
+ |---|---|---|
257
+ | **Tools exposed** | 5 coarse tools | 79+ fine-grained commands |
258
+ | **Read** | `tiptapRead`, `tiptapReadSelection` | `getHTML`, `getJSON`, `getText`, `isActive`, `getAttributes`, `isEmpty`, `isEditable`, `isFocused`, `getCharacterCount`, `getWordCount` |
259
+ | **Write** | `tiptapEdit` (accepts operations array) | Individual commands: `toggleBold`, `insertContent`, `setNode`, `wrapIn`, ... |
260
+ | **Comments** | `getThreads`, `editThreads` | Not supported |
261
+ | **Schemas** | Tool parameters with descriptions | Strict JSON Schema per command (`additionalProperties: false`) |
262
+
263
+ The AI Toolkit bundles all editing into a single `tiptapEdit` tool that accepts an array of operations. tiptap-apcore exposes each operation as a standalone tool with its own schema — this gives the LLM more precise tool selection and lower token usage per call.
264
+
265
+ ### Security
266
+
267
+ | | TipTap AI Toolkit | tiptap-apcore |
268
+ |---|---|---|
269
+ | **Access control** | None built-in | 3 roles + tag/module allow/deny lists |
270
+ | **Safety annotations** | None | `readonly`, `destructive`, `idempotent`, `requiresApproval`, `openWorld`, `streaming` per command |
271
+ | **Approval workflow** | Review mode (accept/reject UI) | `requiresApproval` annotation for MCP clients |
272
+ | **Input validation** | Basic parameter types | Strict JSON Schema with `additionalProperties: false` |
273
+
274
+ ### Protocol Support
275
+
276
+ | | TipTap AI Toolkit | tiptap-apcore |
277
+ |---|---|---|
278
+ | **MCP** | Not supported | stdio, streamable-http, SSE |
279
+ | **OpenAI** | Via adapter (`@tiptap-pro/ai-adapter-openai`) | `toOpenaiTools()` one-liner |
280
+ | **Anthropic** | Via adapter (`@tiptap-pro/ai-adapter-anthropic`) | Via MCP (any MCP client) |
281
+ | **Vercel AI SDK** | Via adapter | Direct (`generateText` + `tool` + `jsonSchema`) or via MCP |
282
+ | **Custom agents** | Adapter required per provider | Any MCP-compatible agent works |
283
+
284
+ ### AI Content Generation
285
+
286
+ | | TipTap AI Toolkit | tiptap-apcore |
287
+ |---|---|---|
288
+ | **Streaming output** | `streamText()`, `streamHtml()` | Not yet supported |
289
+ | **Review mode** | Accept / Reject UI | Not supported (planned) |
290
+ | **Content generation** | Built-in (prompts → editor) | Delegated to LLM (tool use → commands) |
291
+
292
+ The AI Toolkit streams LLM-generated content directly into the editor with a review UI. tiptap-apcore takes a different approach: the LLM decides *which commands* to call, and the executor applies them. Content generation is the LLM's responsibility, not the editor's.
293
+
294
+ ### Server-Side & Headless
295
+
296
+ | | TipTap AI Toolkit | tiptap-apcore |
297
+ |---|---|---|
298
+ | **Headless mode** | Not supported | Full support |
299
+ | **Batch processing** | Not possible | Process multiple documents programmatically |
300
+ | **CI/CD pipelines** | Not applicable | Can validate, transform, or test content |
301
+ | **Multi-tenant** | One editor per user | One executor per editor, server-side isolation |
302
+
303
+ ### When to Use Which
304
+
305
+ **Use TipTap AI Toolkit when:**
306
+ - You need real-time streaming of AI-generated content into the editor
307
+ - You want a built-in accept/reject review UI
308
+ - You're building a client-side-only application
309
+ - You need comment thread management with AI
310
+
311
+ **Use tiptap-apcore when:**
312
+ - You want any MCP-compatible agent to control the editor
313
+ - You need fine-grained access control (roles, tag/module blocking)
314
+ - You're running headless / server-side (batch processing, CI/CD)
315
+ - You want strict schema validation and safety annotations
316
+ - You need to support multiple AI providers without per-provider adapters
317
+ - You want open-source with no licensing fees
318
+
319
+ **Use both when:**
320
+ - You want streaming AI content generation (AI Toolkit) AND structured command control (tiptap-apcore) in the same application
321
+ - You want client-side AI chat + server-side AI automation on the same editor
322
+
323
+ ## AI Capabilities
324
+
325
+ ### Supported (79 commands)
326
+
327
+ tiptap-apcore exposes 79 built-in commands that an AI agent can invoke:
328
+
329
+ | Category | Count | Commands |
330
+ |----------|-------|----------|
331
+ | **Query** | 10 | `getHTML`, `getJSON`, `getText`, `isActive`, `getAttributes`, `isEmpty`, `isEditable`, `isFocused`, `getCharacterCount`, `getWordCount` |
332
+ | **Format** | 36 | Toggle: `toggleBold`, `toggleItalic`, `toggleStrike`, `toggleCode`, `toggleUnderline`, `toggleSubscript`, `toggleSuperscript`, `toggleHighlight`, `toggleHeading`, `toggleBulletList`, `toggleOrderedList`, `toggleTaskList`, `toggleCodeBlock`, `toggleBlockquote`. Set/Unset: `setBold`, `setItalic`, `setStrike`, `setCode`, `unsetBold`, `unsetItalic`, `unsetStrike`, `unsetCode`, `setBlockquote`, `unsetBlockquote`, `setHeading`, `setParagraph`. Other: `setTextAlign`, `setMark`, `unsetMark`, `unsetAllMarks`, `clearNodes`, `updateAttributes`, `setLink`, `unsetLink`, `setHardBreak`, `setHorizontalRule` |
333
+ | **Content** | 15 | `insertContent`, `insertContentAt`, `setNode`, `splitBlock`, `liftListItem`, `sinkListItem`, `wrapIn`, `joinBackward`, `joinForward`, `lift`, `splitListItem`, `wrapInList`, `toggleList`, `exitCode`, `deleteNode` |
334
+ | **Destructive** | 6 | `clearContent`, `setContent`, `deleteSelection`, `deleteRange`, `deleteCurrentNode`, `cut` |
335
+ | **Selection** | 10 | `setTextSelection`, `setNodeSelection`, `selectAll`, `selectParentNode`, `selectTextblockStart`, `selectTextblockEnd`, `selectText`, `focus`, `blur`, `scrollIntoView` |
336
+ | **History** | 2 | `undo`, `redo` |
337
+
338
+ The `selectText` command enables semantic text selection — the AI can select text by content rather than by position, which is more natural for LLM-driven editing.
339
+
340
+ ### Not Supported
341
+
342
+ | Feature | Reason |
343
+ |---------|--------|
344
+ | Clipboard operations (`copy`, `paste`) | Requires browser Clipboard API — not available in headless / server-side |
345
+ | Drag and drop | Requires browser DOM events |
346
+ | IME / composition events | Requires browser input events |
347
+ | Real-time collaboration (Yjs/Hocuspocus) | Collaboration is handled at the transport layer, not the command layer |
348
+ | Streaming content generation | Content generation is delegated to the LLM; the executor applies discrete commands |
349
+ | Comment threads | Not part of core TipTap — requires `@tiptap-pro` extensions |
350
+
351
+ ## Architecture
352
+
353
+ ### tiptap-apcore vs apcore-mcp
354
+
355
+ **tiptap-apcore** is the TipTap adapter that wraps editor commands as APCore modules. **apcore-mcp** is the protocol layer that exposes those modules to AI agents via MCP or OpenAI Function Calling.
356
+
357
+ ```
358
+ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────┐
359
+ │ TipTap Editor │────▶│ tiptap-apcore │────▶│ apcore-mcp │
360
+ │ (@tiptap/core) │ │ (this package) │ │ (protocol) │
361
+ └──────────────────┘ └──────────────────┘ └──────────────┘
362
+ Registry + Executor MCP / OpenAI
363
+ ```
364
+
365
+ **tiptap-apcore provides:**
366
+ - Extension discovery (`ExtensionScanner`)
367
+ - Module building (`ModuleBuilder` + `AnnotationCatalog` + `SchemaCatalog`)
368
+ - Command execution (`TiptapExecutor`)
369
+ - Access control (`AclGuard`)
370
+
371
+ **apcore-mcp provides:**
372
+ - `serve(executor)` — Launch an MCP server (stdio / HTTP / SSE)
373
+ - `toOpenaiTools(executor)` — Export OpenAI Function Calling tool definitions
374
+ - `resolveRegistry(executor)` — Access the registry from an executor
375
+ - `resolveExecutor(registry)` — Create an executor from a registry
376
+ - Types and constants for the APCore protocol
377
+
378
+ ## Demo
379
+
380
+ The `demo/` directory contains a full-stack example: a React + Vite frontend with a TipTap editor, and an Express backend that uses the [Vercel AI SDK](https://ai-sdk.dev/) to let any LLM edit the document via APCore tools.
381
+
382
+ ```bash
383
+ cd demo/server && npm install && npm run dev # Terminal 1
384
+ cd demo && npm install && npm run dev # Terminal 2
385
+ ```
386
+
387
+ Set `LLM_MODEL` (e.g. `openai:gpt-4o`, `anthropic:claude-sonnet-4-5`) in `demo/.env`. See [`demo/README.md`](demo/README.md) for details.
388
+
389
+ ## Development
390
+
391
+ ```bash
392
+ # Install dependencies
393
+ npm install
394
+
395
+ # Run tests
396
+ npm test
397
+
398
+ # Type check
399
+ npm run typecheck
400
+
401
+ # Build
402
+ npm run build
403
+ ```
404
+
405
+ ## License
406
+
407
+ Apache-2.0
@@ -0,0 +1,22 @@
1
+ /**
2
+ * AnnotationCatalog: Static mapping of TipTap command names to safety annotations.
3
+ *
4
+ * Maps all 79 known TipTap commands across 6 categories (query, format, content,
5
+ * destructive, selection, history) to their APCore annotation metadata.
6
+ */
7
+ import type { AnnotationEntry } from "../types.js";
8
+ export declare class AnnotationCatalog {
9
+ private catalog;
10
+ constructor();
11
+ /**
12
+ * Look up the annotation entry for a command name.
13
+ * Returns `null` when the command is not in the catalog.
14
+ */
15
+ get(commandName: string): AnnotationEntry | null;
16
+ /**
17
+ * Convenience helper that returns the category string for a command,
18
+ * or `"unknown"` when the command is not in the catalog.
19
+ */
20
+ getCategory(commandName: string): string;
21
+ }
22
+ //# sourceMappingURL=AnnotationCatalog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AnnotationCatalog.d.ts","sourceRoot":"","sources":["../../src/builder/AnnotationCatalog.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAsC,MAAM,aAAa,CAAC;AA+RvF,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,OAAO,CAA+B;;IAM9C;;;OAGG;IACH,GAAG,CAAC,WAAW,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI;IAIhD;;;OAGG;IACH,WAAW,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM;CAGzC"}