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

@formstr/mcp

v0.5.0

Published

Model Context Protocol server for the Formstr super-app — drive Nostr forms (and more) from any MCP host, with secure keychain/NIP-46 login.

Downloads

1,115

Readme

@formstr/mcp

A standalone Model Context Protocol (MCP) server that exposes the Formstr super-app (forms, calendar, pages, drive, polls) to any MCP host — Claude Code/Desktop, Cursor, and others. It builds on @formstr/core and the super-app's service layer and talks to Nostr relays directly. Transport: stdio.

Identity is powered by @formstr/signer — the same login engine the Formstr web app uses. Local keys are stored NIP-49 encrypted (ncryptsec) inside an OS-keychain (or encrypted-file) keystore; a raw nsec is never persisted. Remote keys stay in your NIP-46 signer.

Quick start

# 1. Sign in once (terminal-interactive; key is stored encrypted in your keystore)
npx -y @formstr/mcp login

# 2. Point your MCP host at the server (see "Host configuration")
#    No key in the config — the account is unlocked from the keystore at startup.

Subcommands: formstr-mcp login · formstr-mcp whoami · formstr-mcp accounts · formstr-mcp switch <npub> · formstr-mcp logout · formstr-mcp version · formstr-mcp help · formstr-mcp (run the stdio server, the default). Run formstr-mcp help (or -h) for the full usage.

Version & updates

formstr-mcp version (or -v / --version) prints the installed version and checks the npm registry for a newer release:

$ formstr-mcp version
@formstr/mcp 0.5.0
Update available: 0.6.0 (you have 0.5.0).
Upgrade: npm install -g @formstr/mcp@latest
Or just re-run via: npx -y @formstr/mcp@latest

The update check is best-effort — if you're offline or the registry is unreachable it prints the installed version and a note, never an error. If you run the server via npx -y @formstr/mcp you already get the latest published version on each launch; pin a version (@formstr/[email protected]) in your host config if you'd rather control upgrades.

Sign-in

formstr-mcp login is fully terminal-interactive (no browser, no localhost server) and offers four methods:

| Method | What happens | | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | | Create (new key) | Generates a key, encrypts it with a passphrase you choose, and prints the ncryptsec once — back it up, it is the only recovery path. | | Import (nsec/hex/ncryptsec) | Paste an nsec…, hex secret, or existing ncryptsec1…. Non-encrypted input is encrypted with your passphrase before storage. | | Bunker URI (NIP-46) | Paste a bunker://… URI. Your key stays in the remote signer; only the session is stored. | | QR (NIP-46 nostrconnect) | A nostrconnect:// URI is rendered as a terminal QR; scan it in Amber / nsec.app / nsecbunker. |

Where the key lives: the OS keychain (macOS Keychain / Windows Credential Manager / Linux Secret Service via @napi-rs/keyring). On hosts without a keychain (e.g. headless Linux), set FORMSTR_MCP_PASSPHRASE to use an AES-256-GCM encrypted file at ~/.config/formstr-mcp/keystore.enc (mode 0600). Multiple identities are supported (formstr-mcp accounts lists them). Change the persisted active account with formstr-mcp switch <npub>, or pick one for a single boot with --account <npub>; the server follows the active account when neither is given, so switching accounts just works. Both switch and --account accept either the npub (as shown by accounts) or the hex pubkey.

Defense in depth: even on the encrypted-file fallback the stored key is also NIP-49 encrypted, so recovering it needs both the keystore and the unlock passphrase.

The agent never sees your key. No tool returns key material, and login happens out-of-band, so secrets never enter the chat transcript.

Host configuration

After login, no key belongs in the config:

{
  "mcpServers": {
    "formstr": {
      "command": "npx",
      "args": ["-y", "@formstr/mcp"]
    }
  }
}

Add "--allow-writes" to args to enable the gated (destructive/outward) tools, and "--relays", "wss://a,wss://b" to override relays.

Note: the default (no --allow-writes) is not read-only. Create/import tools (create_form, create_calendar_event, create_calendar, create_poll, create_page, import_form_from_naddr) are always enabled and publish events on your identity. The flag gates the tools that modify or delete existing data or act toward other people (update / delete / share / submit / RSVP).

Passing the ncryptsec passphrase

If your active account is an ncryptsec key (Create / Import login), the server needs its passphrase to unlock at boot. An MCP host spawns the server with stdin wired to the JSON-RPC channel, so it can't prompt — supply the passphrase through an "env" block in the server entry of your MCP config (mcp_config.json, claude_desktop_config.json, Cursor's ~/.cursor/mcp.json, etc.):

{
  "mcpServers": {
    "formstr": {
      "command": "npx",
      "args": ["-y", "@formstr/mcp"],
      "env": {
        "FORMSTR_MCP_NCRYPTSEC_PASSPHRASE": "your-passphrase-here"
      }
    }
  }
}

The host hands that value to the server as an environment variable at startup — it never enters the chat transcript. Each account has its own passphrase, so this unlocks whichever one is active (set it with formstr-mcp switch <npub>).

Tip: prefer not to keep a passphrase in a config file? Use a NIP-46 (bunker) account instead — it reconnects from its stored session and needs no passphrase, so the config can stay secret-free. Run formstr-mcp switch <npub> to a bunker account.

Using with Ollama (local models)

Ollama isn't an MCP client — it just runs the model. To drive this server with a local model you need an MCP host that uses Ollama as its backend. A good, actively-maintained option is Goose (open-source agent by Block; CLI + desktop), which has both first-class Ollama support and native stdio MCP extensions.

1. Pull a tool-calling-capable model and start Ollama:

ollama pull qwen2.5     # llama3.1 / 3.2, mistral, … also work — the model MUST support tools
ollama serve            # serves the API on http://localhost:11434

A model without tool/function-calling support can chat but can't invoke this server's tools (list_forms, create_form, …), so don't pick one of those.

2. Sign in to formstr once (stores your key in the keystore):

npx -y @formstr/mcp login

3. Point Goose at Ollamagoose configureConfigure ProvidersOllama, then enter the host (http://localhost:11434) and pick your model.

4. Add this server as an extensiongoose configureAdd ExtensionCommand-line Extension, then answer the prompts:

| Prompt | Value | | --------------------- | ------------------------------------------------------------- | | Name | formstr | | Command | npx -y @formstr/mcp (add --allow-writes to enable writes) | | Timeout (secs) | 300 | | Environment variables | FORMSTR_MCP_NCRYPTSEC_PASSPHRASE = your passphrase |

Goose passes that env var to the server when it spawns it (so it unlocks headlessly), and saves the extension to ~/.config/goose/config.yaml. Then just run goose (or goose session) and ask it to work with your forms.

Bunker accounts need no passphrase — skip the env-variable step and formstr-mcp switch <npub> to a NIP-46 account (see the tip above); the extension then stores no secret.

The same approach works with any other Ollama-backed MCP host: point it at npx -y @formstr/mcp and supply the passphrase through that host's env mechanism.

Headless / unattended

Run formstr-mcp login once interactively to populate the keystore, then run the server unattended. At boot the active account is unlocked headlessly:

  • ncryptsec accounts decrypt using FORMSTR_MCP_NCRYPTSEC_PASSPHRASE (the passphrase you set during login). On an interactive terminal the server instead prompts for it (and re-prompts up to 3× on a typo) — so the env var is only required when an MCP host spawns the server, since then stdin is the JSON-RPC channel and there's nobody to prompt. Each account has its own passphrase.
  • NIP-46 accounts reconnect from their stored session — no passphrase needed. This is the simplest setup for a host: formstr-mcp switch <npub> to a bunker account and the config needs no secret at all.

| Variable | Meaning | | ---------------------------------- | ----------------------------------------------------------------- | | FORMSTR_MCP_NCRYPTSEC_PASSPHRASE | unlock the active ncryptsec account at boot | | FORMSTR_MCP_PASSPHRASE | encrypts the at-rest keystore file (keychain-less hosts only) | | FORMSTR_MCP_KEYSTORE | force file or keychain backend (optional) | | FORMSTR_MCP_CONFIG_DIR | keystore directory (default ~/.config/formstr-mcp) | | FORMSTR_RELAYS | comma-separated relay override (optional) | | FORMSTR_MCP_DEBUG | print full stack traces on fatal errors (set to 1) |

CLI flags: --relays <wss://a,wss://b>, --allow-writes, --account <npub|hex>. There is no plaintext-nsec path — a raw key is never read from env, flags, or a config file.

Forms tools

The forms module is fully implemented — the MCP can create every field type the service supports (more than the super-app builder UI currently exposes).

Read (always on)

  • list_forms — your forms with ids, encryption status, and naddr coordinates.
  • get_form — a form's fields, settings, and encryption status (pass viewKey for encrypted forms).
  • fetch_form_responses — submissions with responder npub and per-field answers.

Create / import (always on)

  • create_form — name, description, fields (short/paragraph/choice/dropdown/number/date/ time/grid/file/signature/section), per-field validation, title/cover images, thank-you text, publicForm, encrypted, allowedResponders, collaborators, notifyNpubs.
  • import_form_from_naddr — add a form by naddr1… / pubkey:formId to your forms list.

Gated (require --allow-writes + confirm: true)

  • update_form — republish a form's name/fields/description.
  • share_form — gift-wrap (NIP-59) an encrypted form's view key to collaborators.
  • delete_form — publish a NIP-09 deletion.
  • submit_form_response — submit a response on your identity.

Other modules (calendar, pages, polls, drive) expose the v1 read/create tools and gated actions; see the source under src/tools/.

Safety model

Destructive / outward tools are not registered unless --allow-writes (or FORMSTR_ALLOW_WRITES=true) is set, AND each such call additionally requires "confirm": true. Without confirm, the tool returns a structured "confirmation required" message naming the irreversible effect instead of executing.

Create tools are always on. --allow-writes gates updates, deletions, shares, submissions, and RSVPs — not creation. Creating a new form/event/calendar/poll/page (and importing a form) publishes to relays on your identity even without the flag, because a fresh entity can't clobber existing data. If you need a strictly read-only server, don't connect an identity that matters.

share_form distributes only the view key (read access) — never the signing key. Logging goes to stderr (stdout is the MCP transport).

Tests

pnpm --filter @formstr/mcp test       # unit + stdio smoke test
pnpm --filter @formstr/mcp typecheck
pnpm --filter @formstr/mcp build      # single-file CJS bundle (keychain dep stays external)