npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@leafslip/mcp

v0.4.0

Published

MCP server for .form - let an agent create and read forms.

Readme

@leafslip/mcp

A Model Context Protocol server. It gives an agent these tools:

| Tool | What it does | | --- | --- | | ask_human | Start here. You are blocked; ask a person and carry on with their answer. Writes the form, emails a named person, chases the next if they stay quiet, and hands back what they said — one call, no slug. Needs LEAFSLIP_TOKEN | | ask_status | Check a question you asked with wait: false, or one that had not been answered yet | | create_form | Publish a form, get a URL to send to a person | | read_responses | Read the answers that came back | | await_response | Block until somebody answers. Returns a cursor if nobody has yet, so an agent can stop and resume hours later without missing or repeating an answer | | update_response | Say what is happening to a response you picked up. The person who submitted it watches it on the page they were left holding, and is emailed when you finish; done and blocked close the thread and are final | | request_approval | Ask named people for a decision and chase them — the first is emailed a link of their own, the next is asked if they stay quiet, and it closes at the deadline. An expired request is not an approval. Needs LEAFSLIP_TOKEN | | cancel_approval | Withdraw a request you no longer need | | invite_to_form | Invite people to a @private form. Each gets a link of their own, and you never see the links | | list_invites | Who is invited, and who has answered — the answers themselves carry no identity | | revoke_invite | Withdraw one invitation | | update_form | Change a form without changing its URL. Pass base_source and a save made in the meantime is refused with the current source, not overwritten | | await_response | Block until somebody answers, instead of polling | | request_approval | Ask named people by email, chase them, and expire — never approve by silence | | cancel_approval | Withdraw a request that stopped mattering | | check_form | Validate a .form document without publishing | | list_forms | Find a form published in an earlier session | | list_channels | See where this account can send answers, and the line to write | | create_channel | Save a Slack channel, address or endpoint and get the line to paste | | remove_channel | Forget one; forms naming it stop delivering there |

ask_human is the one worth reading twice. An agent cannot send an email or interrupt anybody, so a URL it prints into a log is a decision that never arrives; this is the tool that makes the question reach a person. Reach for it the moment you would otherwise stop — a permission you do not have, a credential nobody gave you, a choice that is genuinely the user's. It covers "may I drop this table" and "red or blue" alike: pass options and the answer comes back as the one they picked, or leave them off for a yes/no and it comes back as a boolean.

create_form and request_approval are still there for callers that want to write the form themselves and ask separately. ask_human now uses the shared POST /api/ask endpoint instead of performing those two creations itself.

Structured questions and request-specific answers

Use questions instead of legacy question/options for typed human input:

{
  "questions": [
    {
      "id": "colour",
      "prompt": "Which colour?",
      "kind": "choice",
      "options": [{ "id": "blue", "label": "Blue" }, { "id": "red", "label": "Red" }],
      "required": true,
      "allowOther": true
    },
    { "id": "seats", "prompt": "How many seats?", "kind": "number", "minimum": 1, "integer": true }
  ],
  "subject": "Checkout settings",
  "approvers": ["[email protected]"],
  "visibility": "invite",
  "idempotency_key": "run-8842-checkout-settings",
  "wait": false
}

Kinds are text, choice, multi, boolean, number, and decision. Optional fields include required, help, default, numeric minimum, maximum, and integer, choice allowOther, and option description. Choice defaults are a single option ID; multi-select defaults are arrays of option IDs. Structured asks are invite-only. Use an explicit text kind for free text; omitting legacy options still means a decision, for compatibility.

An answered request carries an answers map, for example {"colour": {"selected": ["blue"]}, "seats": 3}. Keys are the question IDs and selections are option IDs, never display labels. Choice/multi values contain selected and optional other; text, number, and boolean values retain their types. The legacy answer record is also returned when available. Both come from the exact request's submission: no read_responses follow-up is needed, and another request's latest form response is not a substitute.

approved is present only for an answered single decision. A boolean fact, choice, or multi-question bundle does not imply permission. An absent approval boolean is neither approval nor a person's rejection.

Creation does not wait on HTTP: ask_human gets the request id first, then waits through GET /api/approvals/:id unless you passed wait: false. Reuse the same idempotency_key for a retry of identical question/policy input; conflicting content is refused. A replay may already be answered, even with wait: false.

The status is open, answered, expired, or cancelled. Only open means keep waiting. An incomplete wait returns answered: false and the request id; call ask_status again if you are blocked. Expiry/cancellation carry no approval boolean and are terminal, not reasons to keep polling. An HTTP failure is an unknown outcome, not a “no”: the tool returns an error with the known request id for recovery rather than implying the human's answer was lost.

A wait has a thirty-minute elapsed budget, including HTTP and response-body time. Each network call is bounded. Progress notifications keep a waiting client informed only when its call supplied a progress token; they continue during HTTP waits. If your process will exit before a person can answer, persist the request id or use resume_url/resume_state rather than creating the question again.

Installing MCP exposes tools for the model to choose; it does not itself replace an agent's native question UI or guarantee the model will call ask_human.

Why

A program can do most of a job and then hit something it cannot decide: an approval, a preference, a fact nobody wrote down. The usual answers are a Slack message nobody parses or a hand-rolled endpoint. This makes the question a form: the agent describes what it needs, hands a person a link, and reads the answer.

Installing it

In Claude Code, as a plugin — which ships the built server, so nothing is fetched from a registry:

/plugin marketplace add na5co/theform
/plugin install leafslip@leafslip

In Codex, Cursor, Claude Desktop or Gemini CLI, which have no plugin format but launch a server the same way:

npx @leafslip/cli install

It writes only the clients you actually have, shows the change first, and backs up anything it touches. See plugin/README.md.

Running it by hand

Point your client at the published server:

{
  "mcpServers": {
    "leafslip": {
      "command": "npx",
      "args": ["-y", "@leafslip/mcp"],
      "env": { "LEAFSLIP_TOKEN": "tok_…" }
    }
  }
}

To run it from a checkout instead:

npm install && npm run build --workspace @leafslip/mcp

and use "command": "node", "args": ["/absolute/path/to/packages/mcp/dist/server.js"].

LEAFSLIP_URL defaults to https://leafslip.app, so the server works with no configuration. Point it at a local worker during development. Set LEAFSLIP_TOKEN to an API token (leafslip login, then leafslip token) so the forms an agent creates belong to your account. Without one they are anonymous and stop accepting responses after seven days — create_form says so in its result, since an agent cannot see a form stop working.

Edit keys

create_form returns a slug and a URL, never the form's edit key. The key stays inside the server process and is looked up by slug when a tool needs it, so it cannot reach a transcript. It is sent as a header, never in a URL, so it does not reach an access log or a Referer.

A key lives only as long as the process. With LEAFSLIP_TOKEN that does not matter: forms belong to the account, so read_responses, update_form and list_forms work on anything it owns, whenever. Without a token an agent can only reach forms it created in the same session — which is usually not enough, because the whole point is to ask a person something and come back later for the answer.

Asking, then coming back

The shape this is for:

create_form  ->  hand the URL to a person  ->  ... time passes ...
list_forms   ->  find it again             ->  read_responses

A form can also carry what it was created for. Declare a @hidden field and put the value in the link, and it comes back with the answer:

Deploy approval
Approve the release to production?

Approve?
- Yes
- No
@required

run_id
@hidden

create_form returns the URL; append ?run_id=8842 before handing it over, and every response names the run it belongs to. read_responses reports those values under context as well as in data. See Context in SPEC.md.

For a form that should wake the agent instead of being polled, add @webhook https://... to the source: the same context arrives in the body.

No dependencies

The protocol is implemented directly. The surface needed here is a handshake and three methods, and the reference SDK would be this project's only runtime dependency, for more bytes than the whole package.