---
name: paper-save
version: 1.1.0
description: /paper-save, save this to paper, save this conversation, save to my vault, file this in paper, file to vault, log decision, save meeting notes
allowed-tools: Read Glob Grep
---

# paper-save

Save the current chat — an insight, decision, synthesis, or research summary — into a `paper-*` MCP vault on paper.farzai.com.

This skill is the bridge between an ad-hoc Claude Code conversation and a durable note in the user's second brain. It detects which `paper-*` MCP server is bound, asks once when ambiguous, and resolves wikilinks before writing so the user does not end up with orphan references.

## When to invoke

Any phrase containing one of:
- `/paper-save`
- "save this to paper" / "save to my vault" / "save this conversation"
- "file this in paper" / "file to vault"
- "log decision" / "save meeting notes" (route to the matching note type in step 3 — `decision`, or `reference` for meeting notes)

If the user says only "save this" without context, ask one clarifying question — vault name or topic — before proceeding.

## Server selection

Each `paper-*` MCP server represents one vault. The binding name itself (`mcp__paper-personal__*`, `mcp__paper-work__*`, etc.) is the only identifier you need.

Pattern-match exposed tools against `mcp__paper-[a-z0-9-]+__`. If more than one matches, ask:

> Which vault should I save to? I see: `paper-personal`, `paper-work`. (Reply with the name.)

Remember the answer for the rest of the session. Do not write a config file.

To distinguish personal vs global servers:
- Personal vault URLs contain `/api/mcp/v/<uuid>` and expose `create_note` / `update_note`.
- Global vault URLs contain `/api/mcp/global` and expose `propose_global_note` instead. `create_note` is NOT available there.

If the bound server is the global vault, see the **Global vault redirect** section below.

## Workflow

### 1. Health check

Call `get_vault` (the cheapest tool). Use it to confirm the binding works, and keep its `id` field — that is the **vaultId** you need to build the note URL in step 8.

- On `401 unauthorized`: check the `WWW-Authenticate` response header. If it carries `resource_metadata=...`, Claude Code will handle the OAuth refresh automatically — retry the call once. If the retry also 401s, stop and direct the user to `https://paper.farzai.com/app/connections` to reconnect.
- On `429 rate_limited`: wait the `Retry-After` seconds, retry once. If it fails again, stop and report.

### 2. Scan the chat for save-worthy content

Skim the recent conversation. Identify the substantive content:

- Insights you arrived at (not the mechanical Q&A around them)
- Decisions (with rationale)
- Syntheses (multi-source distillations)
- Research summaries (with sources cited)
- Code patterns or architecture notes

Skip:
- Pure tool-call echoes
- Boilerplate Claude scaffolding
- Conversation niceties

### 3. Determine note type and folder

| Type | Folder | Frontmatter additions |
|---|---|---|
| research | `notes/research/` | `confidence: low|medium|high` |
| how-to | `notes/how-to/` | — |
| decision | `notes/decisions/` | `status: proposed|accepted|rejected`, `decision_date: YYYY-MM-DD` |
| idea | `notes/ideas/` | — |
| reference | `notes/reference/` | — |

If unclear, ask the user once.

### 4. Resolve intended wikilinks

For every `[[Foo]]` you plan to write into the body, call `search_notes({ query: "Foo", limit: 5 })` first.

- Exact title match: keep `[[Foo]]` as-is. It will resolve.
- Fuzzy match: use the exact resolved title. `[[Foo Bar Baz]]` not `[[Foo]]`.
- No match: write `[[Foo]] _(new)_` so the user knows it's an unresolved link. Do NOT silently drop it — the absence of links is worse than orphans for a knowledge graph.

### 5. Build the frontmatter

The server validates frontmatter against a known-keys allowlist (`type`, `tags`, `status`, `aliases`, `description`, `canonical`, `created`, `updated`, `published`, `publishedAt`, `extra`). Unknown keys are NOT rejected — they are auto-bucketed into `extra`. The note **title is a top-level `create_note` argument, not a frontmatter key** — do not duplicate it in frontmatter.

Allowlist fields:
```yaml
type: research|how-to|decision|idea|reference
tags: [array, of, kebab-case, tags]
description: "<one-line description>"
created: <ISO 8601 of "now">
updated: <same as created>
```

Metadata that lands in `extra` (no special syntax needed — just add the keys):
```yaml
source: claude-code
related: [related-note-slug-1, related-note-slug-2]   # plain strings; put the actual [[wikilinks]] in the body
```

Type-specific fields per the table above (`confidence` for research, `status` + `decision_date` for decision) also bucket into `extra`, except `status`, which is an allowlist key.

### 6. Preview before writing

Show the user:
- Path (e.g. `notes/research/cache-invalidation` — no `.md` extension; the server strips a trailing `.md` if you add one)
- Frontmatter block
- First 200 characters of the body
- Resolved wikilinks count

Ask for confirmation. Do not silently write.

### 7. Duplicate detection

Before `create_note`, call `list_notes({ folder })` and check whether a note with the same title already exists.

If yes, offer two options:
- **Append**: read the existing note via `read_note`, append the new content under a `## <date>` heading, call `update_note` with the `expectedRevisionId` from the read.
  - On `revision_conflict`: re-read, re-merge, retry once. If still conflicting, ask the user to resolve manually.
- **Create with suffix**: `<title> (continued)` or similar.

### 8. Write the note

Call `create_note` (or `update_note` for append).

On success:
- Output the note URL: `https://paper.farzai.com/app/vaults/<vaultId>/notes/<id>` — use the `vaultId` you kept from `get_vault` in step 1 and the `id` returned by `create_note`. (There is no `/app/notes/<id>` route; the vault segment is required.)
- Suggest 1-2 follow-ups: "Want me to publish this?", "Should I link it from `[[Related]]`?"

### 9. Optional: add to a collection

If the user mentioned project context (e.g. "save this from my MCP investigation"), call `list_collections` and offer `add_to_collection`. Skip if no obvious match.

## Global vault redirect

When the bound server is `paper-global` (URL contains `/api/mcp/global`):

> The MCP server you're connected to (`paper-global`) accepts contributions, not direct writes.
> Did you mean to (a) save this to your personal vault instead, or (b) propose this as a PR to the global vault?
> If (a), connect to `paper-personal` and re-run. If (b), I'll invoke /paper-contribute now.

Wait for explicit confirmation. Do not silently redirect.

## Error matrix

| Error | Action |
|---|---|
| `401 unauthorized` | Check the `WWW-Authenticate` header. If `resource_metadata=...` is present, Claude Code handles the OAuth refresh — retry once. If the retry also 401s, direct the user to `/app/connections` to reconnect. |
| `429 rate_limited` | Wait `Retry-After`, retry once. If still 429, stop. |
| `revision_conflict` | Re-read the target note, re-merge, retry once. If still conflicting, stop and ask the user. |
| `note_not_found` (on update) | Fall back to `create_note`. |
| `path_conflict` | Append `-2`, `-3`, … to the slug until unique. |
| Missing MCP tools (no `mcp__paper-*` server bound) | Point to https://paper.farzai.com/docs/mcp-setup. |

## Examples

User: "save this analysis of cache invalidation strategies"

You:
1. Probe `get_vault` → ok.
2. Scan chat — the substantive content is the four-strategy comparison.
3. Folder: `notes/research/`. Type: `research`. Confidence: `medium` (analysis based on 3 sources).
4. Resolve `[[Cache stampede]]` via `search_notes` → not found → write `[[Cache stampede]] _(new)_`.
5. Build frontmatter (no `title` key — it's a `create_note` arg), show preview.
6. User confirms → `create_note({ path: "notes/research/cache-invalidation", title: "Cache invalidation strategies", body, frontmatter })`.
7. Output `https://paper.farzai.com/app/vaults/<vaultId>/notes/<id>` + offer to publish.
