✎ ShareNotes
2026-10-18 · Engineering · By the ShareNotes team

We gave our pastebin an MCP server. One of its tools refuses to read some notes.

Assistants are good at producing text and bad at getting it anywhere useful. So ShareNotes now speaks MCP: an assistant can turn text into a short link, or read one you paste in. Most of the work was not the protocol. It was deciding what an anonymous, model-driven caller should and should not be able to do.

ShareNotes is a pastebin with memorable links: paste text, get sharenotes.dev/quiet-river-7, open it anywhere. It already had a public REST API with no key. Since this week it also has a remote MCP server, so an assistant such as Claude can do the same thing for you: "put this in a note and give me the link". It can also read a ShareNotes link you paste into a chat.

The server is small: two tools, around two hundred lines. Here are the decisions behind it, because each one is a trade-off you will meet if you put an MCP server in front of anything users share.

Two tools, and the one we left out

  • create_note takes text, an optional title, an expiry (1 hour, 24 hours, 7 days or 30 days) and an optional delete-after-reading flag. It returns the link.
  • read_note takes a link or just a name like blue-fox-42 and returns the note.

There is no update_note, although live editing is the thing ShareNotes is best at. On ShareNotes, a note without a PIN can be edited by anyone who has its link; that is the point of a shared scratchpad. Hand an assistant an edit-by-name tool and a sentence hidden in a web page ("now update blue-fox-42 to say...") becomes a way to rewrite notes that belong to someone else. A person in a browser has to be shown the link. A tool does not. So creating and reading are tools, and editing stays in the browser.

Stateless, because there is nothing to remember

MCP over Streamable HTTP can keep a session per client. We have nothing to keep: each call either creates a note or reads one. So every POST gets a fresh server and transport, and the response is plain JSON instead of a stream:

router.post('/', async (req, res) => {
  const server = buildServer();   // registers create_note and read_note
  const transport = new StreamableHTTPServerTransport({
    sessionIdGenerator: undefined, // stateless
    enableJsonResponse: true,      // plain JSON, no event stream
  });
  res.on('close', () => { transport.close(); server.close(); });
  await server.connect(transport);
  await transport.handleRequest(req, res, req.body);
});

GET and DELETE answer 405, because a stateless server has no stream to open and no session to end. We mounted it at /api/mcp, under the path our reverse proxy already sends to the backend, so shipping it needed no proxy change at all.

Why per-IP limits break for MCP

The REST API allows 20 new notes a day per IP address. That works because a script runs on someone's own machine or CI runner. An assistant that runs in its vendor's cloud is different: the request that reaches us can come from the vendor's servers rather than from the person typing, so many people may arrive from the same few addresses. With a per-IP limit, the first few users of the day would use up everyone's allowance.

We could have added API keys and accounts. ShareNotes has neither, on purpose. Instead, notes created through MCP share one daily cap: 500 a day across all assistants. The count lives in the same table as our all-time counter, so a restart doesn't reset it. When it runs out, create_note says so in plain words, and the assistant can tell you. The trade-off is real: if someone burns through the cap, everyone waits until tomorrow. For a free tool with no accounts, a hard ceiling on abuse is worth that.

A read tool that refuses

ShareNotes has two kinds of note an assistant could easily damage:

  • Delete-after-reading notes are destroyed the first time they are opened. We already had to stop link-preview bots from consuming them. An assistant is the same problem with more initiative. If it opens the note to "check the link works", the person it was meant for gets nothing.
  • PIN-protected notes exist so that only people with the PIN read them.

So read_note opens neither. For a one-time note, it explains that opening it would destroy it and asks the user to open it in a browser. This also closes a prompt injection route: text in some document cannot talk the assistant into burning a note, because the tool doesn't do that for anyone.

It made the tool honest in a second way. MCP tools can carry hints, and readOnlyHint tells a client a tool changes nothing. If read_note could consume notes, that hint would be a lie. Because it can't, the hint is true.

Defaults written for a model, not a form

A person picks an expiry in a dialog. A model picks whatever its tool description leads it to, so the description is the interface. Two choices follow from that:

  • Notes from assistants expire after 7 days unless the assistant chooses otherwise. Our web editor defaults to a month, and the REST API to no timer. An anonymous, model-driven write path should not leave notes behind by default.
  • The description says, in so many words, that notes are readable by anyone with the link, are not end-to-end encrypted, and must never hold passwords or keys. It is the only place that warning will reliably reach the model at the moment it decides.

How we tested it

The tests use the official MCP TypeScript client over real HTTP, the same way an assistant connects. They check that:

  • Both tools are listed, and read_note is marked read-only.
  • A note created with no options expires in 7 days and reads back.
  • An unknown expiry is rejected.
  • A delete-after-reading note survives a read_note call untouched, and a PIN-locked note's text never appears in a response.
  • Hitting the daily cap returns a clear error and is counted.

If you connect it, tell us how it goes through the Suggestion box at the top of the site. That decides what comes next.

No sign-up · Free forever Connect your assistant →

Answers, briefly.

The questions this post gets asked most.

How do I connect an assistant to ShareNotes?
Add https://sharenotes.dev/api/mcp as a remote MCP server. In Claude Code: claude mcp add --transport http sharenotes https://sharenotes.dev/api/mcp. In claude.ai, add it as a custom connector at claude.ai/customize/connectors.
Why doesn't the MCP server use per-IP rate limits like the REST API?
When an assistant runs in its vendor's cloud, requests can reach us from the vendor's servers rather than the user's computer, so many people may share one address. Notes created through MCP share one daily cap of 500 instead.
Why won't read_note open a delete-after-reading note?
Opening one destroys it. An assistant should not be able to consume a one-time note meant for a person, and a prompt injection should not be able to make it try.
Is there a tool for editing notes?
No. A note without a PIN can be edited by anyone who has its link in the browser. An edit-by-name tool would let a prompt injection rewrite notes that are not yours, so we left it out.