Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/workflows/ci-mcp.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,15 @@ jobs:
- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Generate SDK artifacts
run: pnpm run generate:all

- name: Build superdoc (dependency)
run: pnpm run build:superdoc

- name: Build SDK (dependency)
run: pnpm --prefix packages/sdk/langs/node run build

- name: Build
run: pnpm --prefix apps/mcp run build

Expand Down
6 changes: 6 additions & 0 deletions .github/workflows/release-mcp.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,15 @@ jobs:
- name: Install dependencies
run: pnpm install

- name: Generate SDK artifacts
run: pnpm run generate:all

- name: Build superdoc (dependency)
run: pnpm run build:superdoc

- name: Build SDK (dependency)
run: pnpm --prefix packages/sdk/langs/node run build

- name: Build MCP server
run: pnpm --prefix apps/mcp run build

Expand Down
314 changes: 4 additions & 310 deletions apps/cli/scripts/export-sdk-contract.ts

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion apps/docs/document-api/reference/_generated-manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -951,5 +951,5 @@
}
],
"marker": "{/* GENERATED FILE: DO NOT EDIT. Regenerate via `pnpm run docapi:sync`. */}",
"sourceHash": "b52520ee9b80ed98b8bb191aeec45df6488bdd1ee406009553247cfec58cfea1"
"sourceHash": "068dea933bf1591112019534c6bae48f811dc8d65c42f6cb94f365548028ea77"
}
14 changes: 12 additions & 2 deletions apps/docs/document-api/reference/insert.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ Returns an SDMutationReceipt with applied status; resolution reports a TextAddre

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `content` | object | yes | |
| `content` | object \\| object[] | yes | One of: object, object[] |
| `nestingPolicy` | object | no | |
| `nestingPolicy.tables` | enum | no | `"forbid"`, `"allow"` |
| `placement` | enum | no | `"before"`, `"after"`, `"insideStart"`, `"insideEnd"` |
Expand Down Expand Up @@ -201,7 +201,17 @@ Returns an SDMutationReceipt with applied status; resolution reports a TextAddre
"additionalProperties": false,
"properties": {
"content": {
"type": "object"
"oneOf": [
{
"type": "object"
},
{
"items": {
"type": "object"
},
"type": "array"
}
]
},
"nestingPolicy": {
"additionalProperties": false,
Expand Down
1 change: 0 additions & 1 deletion apps/docs/document-api/reference/ranges/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,3 @@ Deterministic range construction from explicit document anchors.
| Operation | Member path | Mutates | Idempotency | Tracked | Dry run |
| --- | --- | --- | --- | --- | --- |
| <span style={{ whiteSpace: 'nowrap', wordBreak: 'normal', overflowWrap: 'normal' }}><a href="/document-api/reference/ranges/resolve"><code>ranges.resolve</code></a></span> | `ranges.resolve` | No | `idempotent` | No | No |

28 changes: 24 additions & 4 deletions apps/docs/document-api/reference/replace.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ Returns an SDMutationReceipt with applied status; receipt reports NO_OP if the t

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `content` | object | yes | |
| `content` | object \\| object[] | yes | One of: object, object[] |
| `nestingPolicy` | object | no | |
| `nestingPolicy.tables` | enum | no | `"forbid"`, `"allow"` |
| `target` | BlockNodeAddress \\| SelectionTarget | yes | One of: BlockNodeAddress, SelectionTarget |
Expand All @@ -56,7 +56,7 @@ Returns an SDMutationReceipt with applied status; receipt reports NO_OP if the t

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `content` | object | yes | |
| `content` | object \\| object[] | yes | One of: object, object[] |
| `nestingPolicy` | object | no | |
| `nestingPolicy.tables` | enum | no | `"forbid"`, `"allow"` |
| `ref` | string | yes | |
Expand Down Expand Up @@ -225,7 +225,17 @@ Returns an SDMutationReceipt with applied status; receipt reports NO_OP if the t
"additionalProperties": false,
"properties": {
"content": {
"type": "object"
"oneOf": [
{
"type": "object"
},
{
"items": {
"type": "object"
},
"type": "array"
}
]
},
"nestingPolicy": {
"additionalProperties": false,
Expand Down Expand Up @@ -260,7 +270,17 @@ Returns an SDMutationReceipt with applied status; receipt reports NO_OP if the t
"additionalProperties": false,
"properties": {
"content": {
"type": "object"
"oneOf": [
{
"type": "object"
},
{
"items": {
"type": "object"
},
"type": "array"
}
]
},
"nestingPolicy": {
"additionalProperties": false,
Expand Down
165 changes: 45 additions & 120 deletions apps/docs/document-engine/ai-agents/llm-tools.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -119,10 +119,8 @@ Install the SDK, create a client, and wire up an agentic loop.
```typescript
import { chooseTools } from '@superdoc-dev/sdk';

const { tools, selected, meta } = await chooseTools({
const { tools, meta } = await chooseTools({
provider: 'openai', // 'openai' | 'anthropic' | 'vercel' | 'generic'
mode: 'essential', // 'essential' (default) | 'all'
groups: ['comments'], // optional β€” load additional groups alongside essential tools
});
```
</Tab>
Expand All @@ -132,49 +130,31 @@ Install the SDK, create a client, and wire up an agentic loop.

result = choose_tools(
provider="openai",
mode="essential",
groups=["comments"],
)
tools = result["tools"]
```
</Tab>
</Tabs>

### Essential mode (default)
The current SDK returns the full grouped intent tool set for the selected provider. Group filtering and meta-discovery are not part of the shipped public API here.

Returns 5 essential tools plus `discover_tools` β€” a meta-tool that lets the LLM load more groups on demand. This keeps the initial context small while giving the model access to the full toolkit when needed.
## Current tool set

The 5 essential tools:
The generated catalog currently contains 9 grouped intent tools:

| Tool | What it does |
| --- | --- |
| `get_document_text` | Returns the full plain-text content of the document |
| `query_match` | Searches by node type, text pattern, or both β€” returns matches with addresses |
| `apply_mutations` | Batch edit: rewrite, insert, delete text and apply formatting in one call |
| `get_node_by_id` | Get details about a specific node by its address |
| `undo` | Undo the last operation |

If you pass `groups`, those groups are loaded **in addition** to the essential set:

```typescript
// Essential tools + all comment tools
const { tools } = await chooseTools({
provider: 'openai',
groups: ['comments'],
});
```

### All mode

Returns every tool from the requested groups (or all groups if `groups` is omitted). The `core` group is always included.

```typescript
const { tools } = await chooseTools({
provider: 'openai',
mode: 'all',
groups: ['core', 'format', 'comments'],
});
```
| Tool | Actions | What it does |
| --- | --- | --- |
| `superdoc_get_content` | `text`, `markdown`, `html`, `info` | Read document content in different formats |
| `superdoc_search` | `match` | Find text or nodes and return handles or addresses for later edits |
| `superdoc_edit` | `insert`, `replace`, `delete`, `undo`, `redo` | Perform text edits and history actions |
| `superdoc_format` | `inline`, `set_style`, `set_alignment`, `set_indentation`, `set_spacing` | Apply inline or paragraph formatting |
| `superdoc_create` | `paragraph`, `heading` | Create structural block elements |
| `superdoc_list` | `insert`, `create`, `detach`, `indent`, `outdent`, `set_level`, `set_type` | Create and manipulate lists |
| `superdoc_comment` | `create`, `update`, `delete`, `get`, `list` | Manage comment threads |
| `superdoc_track_changes` | `list`, `decide` | Review and resolve tracked changes |
| `superdoc_mutations` | `preview`, `apply` | Execute multi-step atomic edits as a batch |

Multi-action tools use an `action` argument to select the underlying operation. Single-action tools like `superdoc_search` do not require `action`.

## Dispatching tool calls

Expand Down Expand Up @@ -206,51 +186,6 @@ const { tools } = await chooseTools({

The dispatcher validates required parameters, enforces mutual exclusivity constraints, and throws descriptive errors if arguments are invalid β€” so the LLM gets actionable feedback.

## Tool groups

Tools are organized into 11 groups. In essential mode, the LLM can load any group dynamically via `discover_tools`.

| Group | Description |
| --- | --- |
| `core` | Read nodes, get text, find/replace, insert, delete, batch mutations |
| `format` | Bold, italic, underline, strikethrough, alignment, spacing, borders, shading |
| `create` | Create headings, paragraphs, tables, sections, table of contents |
| `tables` | Row/column operations, cell merging, table formatting, borders |
| `sections` | Page layout, margins, columns, headers/footers, page numbering |
| `lists` | Bullet and numbered lists, indentation, list type conversion |
| `comments` | Create, edit, delete, resolve, and list comment threads |
| `trackChanges` | List, inspect, accept, and reject tracked changes |
| `toc` | Table of contents β€” create, configure, refresh |
| `history` | Undo and redo |
| `session` | Open, save, close, and manage document sessions |

## The discover_tools pattern

When the LLM needs tools beyond the essential set, it calls `discover_tools` with the groups it wants. Since `discover_tools` is a meta-tool (not a document operation), intercept it before `dispatchSuperDocTool` and handle it client-side via `chooseTools`:

```typescript
import { chooseTools } from '@superdoc-dev/sdk';

for (const call of message.tool_calls) {
let result;
const args = JSON.parse(call.function.arguments);

if (call.function.name === 'discover_tools') {
// Meta-tool β€” resolve client-side, then merge new tools
result = await chooseTools({ provider: 'openai', groups: args.groups });
tools.push(...result.tools);
} else {
result = await dispatchSuperDocTool(client, call.function.name, args);
}

messages.push({
role: 'tool',
tool_call_id: call.id,
content: JSON.stringify(result),
});
}
```

## Providers

Each provider gets tool definitions in its native format.
Expand Down Expand Up @@ -284,79 +219,69 @@ Each provider gets tool definitions in its native format.

## Best practices

### Start with essential mode
### Start with the full grouped set

Load only the 5 essential tools plus `discover_tools`. This keeps the context window small and gives the model room to reason. Let it call `discover_tools` when it needs more β€” don't front-load every group.
The current catalog is intentionally small. In most integrations you should load the full grouped set once and let the model choose among the 9 tools.

### Minimize tool calls

A typical edit should take 3–5 tool calls: query, mutate, done. Instruct the LLM to plan all edits before calling tools, and to batch multiple changes into a single `apply_mutations` call when possible.
A typical edit should take 3-5 tool calls: read, search, mutate, done. Instruct the LLM to plan all edits before calling tools, and to batch multiple changes only when atomic execution is genuinely helpful.

### Use `apply_mutations` for text edits
### Read first, then search before editing

`apply_mutations` can rewrite, insert, delete, and format text in one call. It supports multiple steps, so the LLM can edit several paragraphs at once. Use it for any operation on existing text.
Use `superdoc_get_content` to understand the document, then `superdoc_search` to obtain stable handles or addresses. Pass those targets into `superdoc_edit`, `superdoc_format`, `superdoc_create`, `superdoc_list`, or `superdoc_comment`.

### Prefer focused tools; use `superdoc_mutations` as an escape hatch

Use the focused intent tools for straightforward edits. Reach for `superdoc_mutations` when you need preview/apply semantics, atomic multi-step edits, or a batched workflow that would otherwise require several target refreshes.

### Feed errors back to the model

`dispatchSuperDocTool` throws descriptive errors with codes like `MATCH_NOT_FOUND` or `INVALID_ARGUMENT`. Pass these back as tool results β€” most models self-correct on the next turn.
`dispatchSuperDocTool` surfaces structured errors from the underlying SDK and Document API. Pass these back as tool results β€” most models self-correct on the next turn.

### Add tool call examples for repeatable actions

If your workflow involves the same kind of edit across many documents (e.g., always rewriting a specific clause, always adding a comment to a section), include a concrete tool call example in your system prompt. Models that see a working example of the exact tool invocation produce correct calls more reliably than models that only see the schema.

### Include a system prompt

Tell the model what it can do and how to approach edits. Here's an example:
Tell the model what it can do and how to approach edits. You can load the bundled prompt with `getSystemPrompt()`, or start with something like this:

````markdown
You edit `.docx` files using SuperDoc tools. Be efficient β€” minimize tool calls.
You edit `.docx` files using SuperDoc intent tools. Be efficient and minimize tool calls.

## Workflow

1. **Query** β€” Call `query_match` to find text you want to edit.
Use `require: "any"` to match broadly.
2. **Plan** β€” Decide what changes to make. Think through all
edits before making tool calls.
3. **Mutate** β€” Call `apply_mutations` to rewrite, insert, delete,
or format text.

Keep it to 3–5 tool calls total.

## Tools

You start with 5 essential tools plus `discover_tools`.
Call `discover_tools` to load additional categories when needed:
1. **Read** β€” Use `superdoc_get_content` to understand the document.
2. **Search** β€” Use `superdoc_search` to find stable handles or block addresses.
3. **Edit** β€” Use the focused tool that matches the job:
- `superdoc_edit` for insert, replace, delete, undo, redo
- `superdoc_format` for inline or paragraph formatting
- `superdoc_create` for paragraphs and headings
- `superdoc_comment` for comment threads
- `superdoc_track_changes` for review decisions
4. **Batch only when useful** β€” Use `superdoc_mutations` for preview/apply or atomic multi-step edits.

| Category | What it covers |
|----------|----------------|
| core | Read nodes, get text, query, mutations |
| format | Text formatting, alignment, spacing, borders |
| create | Headings, paragraphs, tables, sections |
| tables | Table manipulation, cell operations |
| comments | Comment threads |
| trackChanges | Accept/reject tracked changes |
Keep it to 3-5 tool calls total when possible.

## Rules

- Use `apply_mutations` for text edits on existing content.
You can batch multiple rewrites in one call.
- Use standalone tools (`create_heading`, `create_table`, etc.)
for structural changes β€” these are NOT step ops.
- Search before mutating so targets come from fresh results.
- Use focused intent tools for normal edits.
- Use `superdoc_mutations` when you need an atomic batch or preview/apply flow.
- Set `changeMode: "tracked"` when edits need human review.
- Don't rewrite and format in the same `apply_mutations` batch.
Rewrite first, query again for fresh refs, then format.
- Feed tool errors back to the model so it can recover.
````

## Utility functions

| Function | Description |
| --- | --- |
| `chooseTools(input)` | Select tools for a provider, filtered by mode and groups |
| `chooseTools(input)` | Load grouped tool definitions for a provider |
| `dispatchSuperDocTool(client, name, args)` | Execute a tool call against a connected client |
| `listTools(provider)` | List all tool definitions for a provider |
| `resolveToolOperation(toolName)` | Map a tool name to its operation ID |
| `getToolCatalog()` | Load the full tool catalog with metadata |
| `getAvailableGroups()` | List all available tool groups |
| `getSystemPrompt()` | Read the bundled system prompt for intent tools |

## Related

Expand Down
Loading
Loading