shared-list
Link lookup support: when an Amazon product link lands in a link column, the server reads the page (via Amazon's own markup) and fills in the row's price, photo and name for empty cells, so agents can build and complete shopping lists automatically.
Lists' per-list CSS can import fonts from Google Fonts, letting agents restyle a list's typography alongside its colors and layout.
Link lookup support: Shopify store product pages are parsed by the server to fill in an item's name, image and price when a product link is added to a link column.
Shared List
Lists you share as a link. Each one is locked with a password, works the same on a phone and a laptop, and can carry its own CSS. An MCP server is built in, so an AI agent can make the list, fill it in and restyle it for you.
Think of a small spreadsheet that is pleasant to look at: typed columns (text, numbers, money, links, pictures, checkboxes, choices, dates), rows you tick off, totals, grouping, search. It began as a furniture list for two roommates and is built for anything you'd otherwise drop in a shared sheet.
Live at https://reallyitsandi.com/shared-list/.
What it does
No accounts. Press Create list, give it a title and a password, and send the link. Anyone with both can open it, edit it, change its password or delete it. The lock screen shows nothing about the list, not even its title. Sessions last 30 days per list and per device, and a new password signs everyone else out.
Edit together. Tick rows, edit cells in place (click on a computer, tap on a phone), add rows, add and retype columns. Open tabs pick up each other's changes within a few seconds.
Typed columns. Money columns total up, along with "how much is left" for rows that aren't ticked yet. Choice columns become colored tags and can group the rows. Links show their site; image links show a thumbnail. Columns can be narrow or wide, hidden from the table, and the list can have a default sort.
Paste a link, get the price. A shop link fills in the row's price, photo and name from the page. Paste one straight into Add item… and that's the whole row. See Link lookups.
Per-list CSS. Every list has a stylesheet that restyles its page for everyone. The default look is built on
--sl-*variables and stable class names, so most themes are a few lines. See Styling.MCP. Eighteen tools over Streamable HTTP. Agents can create, read, edit and restyle lists, lay out their columns, and look links up. Tokens can only be made by the server's operator. See For agents.
Small. One Node process and one SQLite file. No build step on the server, two runtime dependencies, about 50 MB of memory.
Related MCP server: vnsh-mcp
For agents
The server speaks MCP at <base>/mcp,
statelessly. Each agent connects with its own token, sent as a bearer token.
Tokens
Tokens are made on the server and nowhere else. No page and no API route can mint one, so handing one out takes a shell on the box:
node server/cli.ts token create "Roommate" # prints the token once
node server/cli.ts token list
node server/cli.ts token revoke Roommate # by name or idIn Docker, prefix each one with docker exec <container>. Only a hash of each
token is stored.
A token sees the lists it created, plus any list it has opened with
open_list and that list's password. A token you give someone shows them none
of your other lists. Access through open_list ends when the list's password
changes, and only the token that created a list can make it view-only.
Connecting
Claude Code
claude mcp add --transport http shared-list https://your.host/mcp \
--header "Authorization: Bearer <token>"Any other client that takes a JSON config:
{
"mcpServers": {
"shared-list": {
"type": "http",
"url": "https://your.host/mcp",
"headers": { "Authorization": "Bearer <token>" }
}
}
}A client that can only be given a URL can put the token in the path instead:
https://your.host/mcp/<token>. That URL is a credential, so keep it
private, and remember that it ends up in the proxy's access log.
Tool | What it does |
| The token's lists: id, title, link, item count |
| Get access to someone else's list with its password |
| Columns, items (with ids) and view settings |
| A new list with a password, from columns or a preset, optionally with items and CSS |
| Title, description, grouping, default sort, password, edit access |
| Delete a list |
| Change the columns: name, type, options, order, width, hidden, and what a link column fills. Retyping keeps the values that still fit |
| Change the rows |
| Read a shop page's name, photo and price without saving anything |
| Look the links in some rows up again and fill what is empty |
| The class names and variables a list's CSS can use |
| Read or replace a list's CSS |
Agents refer to columns by name or id and send values the way people type them:
"$1,299" for money, "yes" for a checkbox, ikea.com/p/123 for a link. A new
label in a choice column becomes a new option. A batch with one bad value adds
nothing, and the error says which column refused what.
Try asking: "Make a shared list for our apartment furniture with item, room, price, link and who's buying it, password couch123. Add a sofa, a bed frame and a dining table. Then make it look warm and cozy."
Link lookups
When a link lands in a link column, the server reads the page in the background. It fills the row's price, photo and name, but only cells that are empty. Nothing anyone typed is overwritten, and a price in a different currency from the column's is kept with the row rather than filled in. The row shows Looking up… while it works, and the item's form says what was found, or why nothing was.
It reads, in order of trust:
schema.org
Productdata (JSON-LD), which most shop platforms publish,Open Graph and
product:pricemeta tags,microdata (
itemprop="price"),
and Amazon's own markup, which has none of those for its price. Temu, Amazon
and Shopify stores work. Some big retailers (IKEA, Walmart, Home Depot, Etsy,
eBay, and some furniture brands) refuse to answer any server at all, and those
lookups fail with that reason. The fetcher first identifies itself as
SharedListBot, which gets some shops to serve their product data. If that
leaves gaps, it asks again as a browser would.
Each link column has its own switch for this, and a choice of which columns it
fills (fill in update_column). Lookups are capped per list and per hour.
The fetcher only ever connects to the public internet. The check happens in
the connection's own DNS lookup and again after every redirect. A link to
localhost, a private network, a cloud metadata address, or a port other than
80 and 443 is refused. Bodies are cut off at 2.5 MB, and a page gets ten
seconds.
Styling
A list's CSS loads after the built-in stylesheet and applies only to that list.
Edit it under Settings → Style, which previews as you type, or with the
set_css tool.
:root {
--sl-paper: #fff8f0; /* page */
--sl-rule: #f0dcc8; /* lines between rows */
--sl-margin: #c2410c; /* the heavy line under the headings */
--sl-accent: #c2410c; /* ticks, strike-through, progress */
}
/* A whole row, by the value of one of its columns */
.sl-row[data-col-who="Split"] .sl-cell[data-primary] { font-style: italic; }The full reference is shared/styleguide.ts. It is the
same text agents get from get_style_guide and people see in the Style tab.
Fonts can be imported from Google Fonts. The page's Content-Security-Policy
blocks other font hosts and every script.
Run your own
Docker
git clone https://github.com/realAndi/shared-list && cd shared-list
docker build -t shared-list .
docker run -d --name shared-list -p 127.0.0.1:8787:8787 -v "$PWD/data:/data" shared-list
docker exec shared-list node server/cli.ts token create "Me" # only if you want MCPOpen http://localhost:8787/ and press Create list.
deploy/compose.example.yml and
deploy/nginx.example.conf put it behind a reverse
proxy, at a whole domain or under a sub-path.
Without Docker
Node 24 or newer runs the TypeScript directly, so there is nothing to compile on the server.
npm ci && npm run build # builds the web client into dist/web
npm start # http://localhost:8787/Configuration
All optional.
Variable | Default | |
|
| A port, or a path to listen on as a Unix socket |
|
| Where the app is mounted, e.g. |
| from the request | Origin for links the server hands out, e.g. |
|
| The SQLite file, backups, and generated secrets |
| generated | Signs session cookies. Written to |
| on for a socket | Believe |
|
| Daily copies of the database to keep in |
|
| |
|
| New lists one address can make from the web |
|
| Per list |
|
| Per list |
|
| |
|
| Per list |
|
| Creating lists and adding rows stop when the database reaches this |
Images are links, never uploads, so the database stays small. A list with a thousand rows is a few hundred KB.
How it is put together
server/
index.ts start, listen on a port or a socket, shut down cleanly
app.ts request context (base path, proxy headers) and dispatch
api.ts the JSON API the web client uses
mcp.ts the MCP tools
lookups.ts looking links up and filling rows from them
extract.ts a page's name, photo and price, from its HTML
fetcher.ts fetching a page without letting a link reach a private address
tokens.ts MCP tokens: made, checked, revoked
cli.ts the only place tokens are made
service.ts each operation as one transaction
model.ts the list document: columns, values, items — pure functions
store.ts SQLite via node:sqlite
auth.ts scrypt passwords, signed session cookies, rate limits
static.ts the built client, served from memory with a strict CSP
backup.ts a daily VACUUM INTO
shared/ types, presets and the style guide, used by both sides
web/ the client: plain TypeScript and DOM, built by Vite
test/ node:test, including an MCP client end to endA list is one row in SQLite holding a JSON document of columns and items. Every
change reads it, edits it and writes it back inside a transaction, and bumps a
version number. Clients poll with that version and get 204 No Content until
something changes.
Security
Passwords are hashed with scrypt. The session cookie is HMAC-signed,
HttpOnly,SameSite=Lax, and scoped to the app's path. It names one list and the password generation it was issued under.Ten wrong passwords for a list from one address in 15 minutes, or forty across all lists, and that address waits. Wrong MCP tokens, and wrong passwords given to
open_list, have limits of their own.Writes accept only
application/jsonand are refused whenOriginorSec-Fetch-Sitesays they came from another site.Pages are built with DOM calls rather than HTML strings, so list content can't turn into markup. The CSP allows scripts only from the app itself.
Custom CSS is served as its own stylesheet, behind the same session as the list.
Anyone can create a list, so storage is capped in three places: new lists per address per hour, lists on the server, and the size of the database.
There is no admin account, page or master key. MCP tokens are made with the CLI, which needs a shell on the server, and only their SHA-256 is stored.
Development
npm install
npm run dev # API on :8787 (restarts on change), client on :5173
npm run check # typecheck, test, buildLicense
MIT
This server cannot be deployed
Maintenance
Related MCP Connectors
Directory of APIs, merchants, and tools AI agents can actually use.
Shared tables and Markdown docs for teams and AI-built apps, with scoped access and audited changes.
Personal lists (groceries, trips, gifts, books) that your AI assistant can read and update.
1Persistent docs and memory for AI agents — read, write, organize & search a shared workspace.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceEnables AI assistants to interact with Slack Lists through comprehensive tools for creating, retrieving, filtering, and managing list items. Supports bulk operations, data export, subtask creation, and all Slack List field types with robust error handling and production-ready implementation.8MIT
- AlicenseNot gradedqualityAmaintenanceEnables AI agents to create, list, read, update, and restore encrypted shared workspaces so multiple agents and people can collaborate on the same living document through one link. Supports version-checked writes that prevent concurrent agents from silently overwriting each other, plus one-shot share and read tools for quick content exchange.14 npmMIT
- AlicenseNot gradedqualityAmaintenanceEnables AI agents to create, read, update, list, restore and renew encrypted shared workspaces and one-shot links, so multiple agents and people can collaborate on the same versioned document through one URL. Content is encrypted client-side before upload, supports view-only sharing, and is deleted 24 hours after the last edit.14 npmMIT
- AlicenseNot gradedqualityAmaintenanceEnables AI agents to create, read, update, list, and restore shared encrypted workspaces and one-shot links that both people and other agents can open and edit through a single URL. Content is encrypted client-side, version-checked on write to prevent clobbering, and expires 24 hours after the last edit.14 npmMIT