Thanks for building something for SurfSense users. This page takes you from an MCP server to a plugin anyone can connect in the app. It is written for people and for coding agents alike; if you are an agent helping someone list a plugin, also read the checklist for agents.
The short version: a SurfSense plugin is an MCP server. You run the server; this folder only holds a short JSON entry pointing at it. Listing a plugin is one pull request that adds that entry and an icon.
MCP, the Model Context Protocol, is an open standard for giving AI apps tools. An MCP server offers tools, each with a name, a description, an input schema and a few hints about what it does. An MCP client connects to the server, lists those tools and calls them.
In SurfSense, the agent is the client:
Settings → Plugins your MCP server
┌──────────────────┐ lists your tools ┌──────────────────┐
│ user clicks │ ────────────────────► │ search_docs │
│ Connect │ │ read_page │
└──────────────────┘ └──────────────────┘
│ ▲
▼ │ calls a tool,
an Agentic chat: "what do Acme's docs say │ with approval
about rate limits?" ────────────────────────────────┘ when needed
- A user turns on Allow third-party plugins in Settings → Plugins → Browse, finds your plugin there, reads its page, and presses Connect, signing in if your server needs it. Third-party plugins stay hidden and unused until the user allows them.
- In an Agentic chat, the agent sees your tools beside SurfSense's own and calls them when they help. The user can also type
@your-pluginto point the agent at it. - Your server does the work and answers. SurfSense shows the result as a step in the reply.
So there is nothing SurfSense-specific to build. If your server works in other MCP clients, it works here unchanged, and the server you write for SurfSense works in them too. Your code never enters this repository and never runs on the user's computer.
-
Remote, over Streamable HTTP, on HTTPS, reachable from the public internet. SurfSense does not start servers from a command (
npx,uvx) and does not speak the older SSE transport. Every official MCP SDK serves Streamable HTTP. -
Protocol
2025-03-26or newer, the first with Streamable HTTP;2025-11-25or later is best. -
Stateless if you can. A server that keeps no session between requests survives restarts and scaling, and loses nothing when SurfSense reconnects.
-
Annotate every tool.
readOnlyHintanddestructiveHintdecide when SurfSense asks the user before a call:Your tool says SurfSense readOnlyHint: true, ordestructiveHint: falseAsks until the user picks Always allow Neither, or destructiveHint: trueAsks before every call: MCP assumes a tool that says nothing may destroy -
Describe tools for a model. Say what the tool does and when to use it, briefly: every description reaches the model on every turn. Text that tells the model what to do, such as "always call this first", is turned away in review.
-
Keep the tool list short. A chat offers the agent at most 24 plugin tools across all of a user's plugins, so a few well-chosen tools beat many narrow ones.
-
Pick how users sign in:
authThe user Your server receives noneDoes nothing No credentials tokenPastes a key you give them Authorization: Bearer <key>oauthSigns in on your page in their browser Authorization: Bearer <access token>OAuth follows MCP's authorization spec: serve protected resource metadata, and accept Client ID Metadata Documents or dynamic client registration. SurfSense signs in as
https://plugins.surfsense.com/oauth/client.jsonand comes back tohttps://plugins.surfsense.com/oauth/callback. If you only accept clients registered in advance, register SurfSense with those values and put its client id in your entry'sauth.client_id. SurfSense never holds a client secret. -
Have a privacy policy. The user's requests and sign-in reach your server, so the policy must be yours, not your API provider's.
No listing is needed to try your server:
- In the desktop app, open Settings → Plugins and turn on Allow third-party plugins.
- Choose Add custom plugin, with a name, your server's URL and, if it needs one, a key. A server that asks for OAuth starts its sign-in on its own.
- Open an Agentic chat and ask for something your tools do.
A server added this way is marked Custom, stays on that one computer, and is a good way to polish your tool descriptions before anyone reviews them.
Add one object to the plugins array in plugins.json:
{
"id": "acme-docs",
"kind": "remote",
"verification": "community",
"name": "Acme Docs",
"description": "Search and read Acme's public documentation.",
"author": "Acme Inc.",
"url": "https://mcp.acme.example/mcp",
"auth": { "type": "none" },
"hosts": ["mcp.acme.example"],
"access": "free",
"privacy_policy": "https://acme.example/privacy",
"homepage": "https://acme.example",
"icon": "acme-docs.svg",
"about": "Search Acme's public documentation and read any page of it in full, so the agent answers from the current docs rather than from memory.",
"tools": ["search_docs", "read_page"],
"added": "2026-10"
}DeepWiki's entry is a real one to compare with.
| Field | What to put |
|---|---|
id |
Lowercase letters, digits and hyphens, starting with a letter. Unique, and never reused once listed |
kind |
remote |
verification |
community. Only a maintainer sets verified (below) |
name, author |
Up to 80 characters. author is the company or project that runs the server, not a person |
description |
Up to 200 characters: the one line on your card |
url |
Your server's MCP endpoint, https, with no credentials in it |
auth |
{"type": "none"}; {"type": "token", "title": "...", "description": "..."}, the words shown when asking for the key; or {"type": "oauth"}, with an optional client_id and scopes. Never a secret |
hosts |
Every host SurfSense contacts for your plugin: your server's, and for OAuth your authorization server's. The user agrees to each before anything is sent |
access |
free, or external with an access_note (up to 200 characters) saying what the user must buy or have, such as an Acme subscription |
privacy_policy |
Required, https |
homepage |
Optional, https |
icon |
A file you add to icons/: SVG or PNG, square, at most 32 KB, a PNG at least 64×64, an SVG with no scripts, event handlers or outside references. Without one, SurfSense shows your name's first letter |
about |
Up to 600 characters for your plugin's page: what it lets someone do, and when it helps |
tools |
The name of every tool your server lists, as your page shows them before anyone connects |
added |
This month, like 2026-10 |
min_app_version |
Optional: the oldest SurfSense version your plugin works with, like 2.3.0 |
The check lets about, tools and added out, but review asks for all three, since they make your plugin's page.
From check/, with uv installed:
uv sync
uv run python -m registry_check static # the entry rules and your icon
uv run python -m registry_check live # connects to your server, as the app wouldIt prints static check: passed, or one line per problem naming your entry and the field, such as plugins[1] "acme-docs": hosts: must include the url's host, mcp.acme.example. live connects to your server with the app's own MCP client and fails if the app could not use it: unreachable, not MCP over Streamable HTTP, asking for sign-in when auth is none, listing other tools than tools, or, for OAuth, no authorization server, a sign-in host missing from hosts, or no way for SurfSense to register. What MCP leaves optional, such as tool hints, only warns. It never signs in, so a server behind a key or an account is checked up to its sign-in. CI runs both on your pull request.
Fork the repository, branch from dev, and open the pull request into dev. Change only plugins.json and your icon. published/ is written by CI, and a pull request that touches it fails.
Fill in the template's headings, What, Why and How to test, and include:
- A prompt to try, such as "What do Acme's docs say about rate limits?", and the tools it should call.
- How a reviewer signs in, if your plugin needs a key or an account. Never put a key or password in the pull request: say that one is needed, and a maintainer will arrange it with you, or reach the maintainers on Discord.
- Why you should be Verified, if you think you should be: a link from your own docs to the server, for example (below).
A title in Conventional Commits form helps, such as feat(registry): list Acme Docs.
A maintainer reviews your pull request, and once it is merged into dev, connects your plugin, signing in if it needs it, before it reaches a release. They check once:
- who runs the server, and that
authornames them; - what each tool does, and that its annotations are honest;
- that no description carries instructions aimed at the model;
- that
hostsis complete, and the privacy policy is real and yours.
Once merged, CI signs the list. It reaches every app that allows third-party plugins with SurfSense's next release from dev to main, with no app update.
Every plugin starts as Community: the app shows a small badge and, before connecting, reminds the user who runs the server. Verified shows a checkmark instead, and means a maintainer is satisfied that author runs the server.
A maintainer decides it, on whatever convinces them that author runs the server: your own docs linking it, the pull request coming from your company's account, or someone they can ask. Nothing in the entry proves it, and no check decides it, so never set it yourself.
- Updating your server needs no pull request. Deploy whenever you like. A tool you add, or one whose description changes or whose annotations become less safe, is switched off in every app until the user turns it on, so tell your users when you add or reword something.
- Changing your entry (name, URL, hosts, sign-in) is another pull request and another review.
- Removing your plugin moves its entry to
removed, as{"id": "acme-docs", "reason": "..."}. Apps stop offering its tools and show the reason; users keep their connection in case it returns. An id is never reused. - Reports. Anyone can report a listed plugin that is broken, misleading or unsafe with the Report a plugin form. A maintainer may then remove it, with the reason users see.
- Can I charge for my plugin? Yes: bill on your own service, set
access: externalwith anaccess_note, and sign users in withtokenoroauth. The SurfSense license is only for SurfSense's own plugins, and your server never sees it. - Can my plugin run on the user's computer? Not yet. Local plugins are planned as bundles.
- Where are SurfSense's own plugins? In
../remote/, run by SurfSense's plugin host and listed in the app itself, not here (plugin host). Only maintainers change the paid ones, inremote/proprietary/. - How does the app handle all this? Plugins and the plugin registry describe it in full.
If you are helping someone list a plugin:
- Confirm the server is remote, Streamable HTTP, on HTTPS. If it only runs from a command or over SSE, stop and say so; it cannot be listed yet.
- Read each tool's name from the server, or ask for them; never invent
tools. Check every tool hasreadOnlyHintordestructiveHint. - Append one entry to
pluginsinplugins.json, withverification: "community". Never setverified: only a maintainer does. - Put no key, token, password or client secret anywhere: not in the entry, the pull request or a commit message.
- Touch only
plugins.jsonandicons/<file>. Never editpublished/,check/or another entry. - Run
uv run python -m registry_check staticanduv run python -m registry_check liveincheck/, and fix every problem they print. - Open the pull request into
devwith What, Why and How to test, a prompt to try, and whether the reviewer needs sign-in details, without including them.