---
name: claude-saved-ledger
description: "Connect Claude Code to Saved Ledger. Rescan and refresh LinkedIn saved posts and X bookmarks, sort/search archived posts, resume interrupted imports, or clear source saves after verified archiving with durable per-item receipts and exhaustive reconciliation. Use for Saved Ledger setup, bookmark sorting, rescan, refresh, archive-first cleanup, leave none behind, categorisation, and drafting replies from category links."
---

# Saved Ledger for Claude Code

Saved Ledger keeps LinkedIn saved posts and X bookmarks, sorts them into their
categories, and answers questions about them. All of it runs on the hosted
Saved Ledger MCP server: parsing, dedupe, categorisation and storage happen
there, and this skill only decides which tools to call and what to tell the
person running it (the runner).

## Modes

| Mode | The runner says something like | Read |
|---|---|---|
| connect | "set up Saved Ledger", or any mode below with the server missing | [references/connect.md](references/connect.md) |
| sync | "sync my saved posts" | [references/sync.md](references/sync.md), [references/browser.md](references/browser.md) for the browser route |
| refresh | "rescan LinkedIn and X", "refresh my archive" | [references/sync.md](references/sync.md), [references/browser.md](references/browser.md) |
| cleanup / resume | "clear saves after archiving", "resume bookmark cleanup", "leave none behind" | [references/cleanup.md](references/cleanup.md), [references/sync.md](references/sync.md) |
| categorise | "sort my saved posts", "set up categories" | [references/categorise.md](references/categorise.md) |
| ask | "what did I save about Rust?" | the **Ask** section below |
| reply | a category link plus "reply in my voice" | [references/reply.md](references/reply.md) |
| engage / archive | "work through this category", "reply, follow and archive completed posts" | [references/engagement.md](references/engagement.md) |

Read the reference for a mode before its first tool call. Tool calls are
written as tool(argument, argument), naming the arguments the server's schema
takes.

The server answers in TOON, which spends fewer tokens than JSON: `key: value`
lines nested by indent, and a list of like records as one header naming the
columns, such as `items[2]{id,text,url}:`, then one row per record in that
column order. A column written `name{a,b}` is a nested object whose fields
take the next cells of the row in that order: under
`drafts[1]{item_id,reply{id,status},url}:` a row's cells are `item_id`,
`reply.id`, `reply.status` and `url`, so `reply.id` is the second cell.
Outside a table, a field named like `categorisation.state` is the `state` line
under `categorisation:`. A refusal is a plain sentence.

## First, check the connection

The server's tools are named `mcp__saved-ledger__<tool>`, for example
`mcp__saved-ledger__stats`. Claude Code may list them as deferred: search the
tools for `saved-ledger` before concluding they are missing.

- No such tools: run **connect**.
- Tools present: call `stats()`. Counts mean you are connected. A refusal that
  says to sign in, or an `unauthorized` error, means the sign-in lapsed: run
  the sign-in step of **connect**.

## This harness

| Need | In Claude Code |
|---|---|
| Add the server | `claude mcp add --transport http --scope user saved-ledger <mcp-url>` |
| Sign in | `/mcp`, choose saved-ledger, Authenticate (or `claude mcp login saved-ledger` in a terminal) |
| The runner's own browser | Claude in Chrome, the `mcp__claude-in-chrome__*` tools; start Claude Code with `claude --chrome` if they are absent |
| Upload a file | the Bash tool, with `curl` |

## Ask

1. Call `search_items(query, limit)` with one or two words the answer must
   contain; every word must begin a word in the post, author or headline
   (`rust` finds "RustConf", not "trust"). Try a
   synonym as a second search rather than a longer query. Narrow with
   `search_items(query, category, source, status)` when the runner names one.
2. For a post whose snippet is cut, call `get_item(id)`.
3. Answer only from the results: per post, the author, one line on what it
   says, and its link (`url`). If `total` is above `returned`, say how many
   more there are and offer the next page with `search_items(query, offset)`.
4. Counts ("how many posts about hiring?") come from `stats()` or a search's
   `total`. Zero results is an answer: say so, and suggest a synonym or a sync.
5. Offer `dashboard_url()` for browsing.

## Data flow and privacy

Tell the runner this the first time the skill runs for them, before connect.

- Saved Ledger is a hosted service tied to their Google account (name and
  email). It stores each saved post's link, text, author name, headline and
  profile link, and the saved and posted dates; their categories; the reply
  drafts they save; their voice brief; and this connection's grant, kept as
  hashes of its tokens.
- A direct upload sends posts from their browser or export file to the service.
  Browser-tool capture with push_items passes post text through this conversation.
  Scan inventories and cleanup receipts retain source account and evidence summaries.
  Asking questions and
  drafting replies do bring post text into this conversation, and so to the
  model provider behind this harness.
- Post text goes to TypeSafe (Jev) for category suggestions and reply picks
  only if the runner turned on "Send post text to TypeSafe (Jev) for category
  suggestions and reply picks" on their dashboard's items page. Reply picks
  then also send their voice brief. Without it, keywords are used and no post
  text leaves the service.
- Nothing is ever posted to LinkedIn or X. Requested cleanup unsaves only verified
  archived posts, keeps the archive, and records each browser outcome.
- "Delete my data" on the Account page, or `delete_my_data(confirm)`, erases
  every item, category, consent choice, reply draft, voice brief, sync record
  and connected-app grant. The service's store is an append-only event log:
  erased records stop being readable by any page or tool, and the earlier
  entries stay in that log behind the erasure marker.

## Rules

- **Never post, comment, like or reply on LinkedIn or X**, and never drive a
  browser toward a comment box. LinkedIn's User Agreement §8.2 bans "bots or
  other unauthorized automated methods to … comment on … posts". The runner
  posts every reply by hand.
- Read a tool's refusal to the runner as it is written: each one is a sentence
  meant for them, and one caused by a limit says which limit and when it
  resets.
- Category suggestions are drafts. Confirm them only when the runner asks, at
  the threshold they choose.
- Call `delete_my_data(confirm)` with `confirm: true` only after the runner
  says, in this conversation, to delete everything. It cannot be undone and it
  disconnects this harness.
- Never pass a whole capture through tool arguments. `push_items(sync_id, items)`
  takes at most 50 items and is only for when no upload is possible.
- Invent nothing: no numbers, clients, stories or experience the runner has not
  stated.
- Never type the runner's passwords or sign in on their behalf. If a page asks
  them to sign in, they do it.
