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.
- package/README.md +407 -0
- package/dist/builder/AnnotationCatalog.d.ts +22 -0
- package/dist/builder/AnnotationCatalog.d.ts.map +1 -0
- package/dist/builder/AnnotationCatalog.js +269 -0
- package/dist/builder/AnnotationCatalog.js.map +1 -0
- package/dist/builder/ModuleBuilder.d.ts +42 -0
- package/dist/builder/ModuleBuilder.d.ts.map +1 -0
- package/dist/builder/ModuleBuilder.js +106 -0
- package/dist/builder/ModuleBuilder.js.map +1 -0
- package/dist/builder/SchemaCatalog.d.ts +20 -0
- package/dist/builder/SchemaCatalog.d.ts.map +1 -0
- package/dist/builder/SchemaCatalog.js +352 -0
- package/dist/builder/SchemaCatalog.js.map +1 -0
- package/dist/builder/index.d.ts +4 -0
- package/dist/builder/index.d.ts.map +1 -0
- package/dist/builder/index.js +5 -0
- package/dist/builder/index.js.map +1 -0
- package/dist/discovery/ExtensionScanner.d.ts +25 -0
- package/dist/discovery/ExtensionScanner.d.ts.map +1 -0
- package/dist/discovery/ExtensionScanner.js +61 -0
- package/dist/discovery/ExtensionScanner.js.map +1 -0
- package/dist/discovery/index.d.ts +2 -0
- package/dist/discovery/index.d.ts.map +1 -0
- package/dist/discovery/index.js +3 -0
- package/dist/discovery/index.js.map +1 -0
- package/dist/errors/TiptapModuleError.d.ts +18 -0
- package/dist/errors/TiptapModuleError.d.ts.map +1 -0
- package/dist/errors/TiptapModuleError.js +23 -0
- package/dist/errors/TiptapModuleError.js.map +1 -0
- package/dist/errors/index.d.ts +2 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +2 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/runtime/TiptapExecutor.d.ts +20 -0
- package/dist/runtime/TiptapExecutor.d.ts.map +1 -0
- package/dist/runtime/TiptapExecutor.js +327 -0
- package/dist/runtime/TiptapExecutor.js.map +1 -0
- package/dist/runtime/TiptapRegistry.d.ts +35 -0
- package/dist/runtime/TiptapRegistry.d.ts.map +1 -0
- package/dist/runtime/TiptapRegistry.js +100 -0
- package/dist/runtime/TiptapRegistry.js.map +1 -0
- package/dist/runtime/index.d.ts +3 -0
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/runtime/index.js +4 -0
- package/dist/runtime/index.js.map +1 -0
- package/dist/security/AclGuard.d.ts +10 -0
- package/dist/security/AclGuard.d.ts.map +1 -0
- package/dist/security/AclGuard.js +96 -0
- package/dist/security/AclGuard.js.map +1 -0
- package/dist/security/index.d.ts +2 -0
- package/dist/security/index.d.ts.map +1 -0
- package/dist/security/index.js +2 -0
- package/dist/security/index.js.map +1 -0
- package/dist/server.d.ts +10 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +9 -0
- package/dist/server.js.map +1 -0
- package/dist/types.d.ts +116 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +8 -0
- package/dist/types.js.map +1 -0
- package/dist/withApcore.d.ts +14 -0
- package/dist/withApcore.d.ts.map +1 -0
- package/dist/withApcore.js +81 -0
- package/dist/withApcore.js.map +1 -0
- 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"}
|