Skip to content

feat(plugins): remote MCP plugins, SurfSense's scraper plugins and the third-party registry - #2234

Merged
AnishSarkar22 merged 72 commits into
MODSetter:devfrom
AnishSarkar22:feat/remote-plugins
Oct 12, 2026
Merged

AnishSarkar22 merged 72 commits into
MODSetter:devfrom
AnishSarkar22:feat/remote-plugins

Conversation

@AnishSarkar22

@AnishSarkar22 AnishSarkar22 commented Oct 12, 2026 •

Copy link
Copy Markdown
Collaborator

What

Plugins, built from the plugins proposal (docs/proposals/plugins/README.md): a plugin is a remote MCP server whose tools the Agentic thread's agent can call. This PR adds the app side, SurfSense's own plugins, and the third-party registry.

In the desktop app (surfsense_local)

  • Connecting plugins. Settings → Plugins connects any remote MCP server by URL, with no sign-in, a key, or OAuth. Each tool has its own switch and an Ask / Always allow setting. A whole plugin can be paused, and a refused key replaced without reconnecting. A tool the server adds later, or one whose description or annotations change, starts off until the user allows it.
  • The tool gateway (modules/plugins/gateway/) is the one answer to which tools are ready and which ask first. It follows MCP annotations, as Claude's connectors do.
  • Agent turns. Each turn gives opencode the ready plugin servers, their sign-in headers and a rule per tool. opencode reaches plugin hosts only through SurfSense's proxy. Plugin steps name their plugin and tool, show the whole result and offer Save to Sources. Approvals are asked on their step in the reply. @ in the Agentic composer mentions a plugin. A turn says which plugins it could not use and why.
  • SurfSense's own plugins. A first-party list packaged into the app shows them with no network. Connecting one sends the license key only to plugins.surfsense.com. Refusals read as "Needs a license", "License expired" and the like.
  • Third-party plugins. They stay off until the user allows them. The app then fetches the signed registry, verifies it against compiled Ed25519 keys, refuses an older list or entries claiming to be SurfSense, and keeps the last good copy and its icons offline. A delisted plugin stops reaching the agent and says why.
  • OAuth sign-in, for registry plugins and servers added by URL. It uses the MCP SDK's client: consent for the sign-in hosts, the browser, PKCE, a callback on the API's port, and tokens stored encrypted. A token is refreshed as a turn starts, and opencode gets the new header. A refused refresh shows "Signed out" with Sign in again.
  • Egress: each plugin's hosts need consent, and plugins.surfsense.com is a built-in destination, off by default.
  • Migration 0028 adds installed_plugins, plugin_tools and the check failures.

SurfSense's plugins (plugins/remote/)

  • One plugin host, an ASGI app, serves every SurfSense plugin, each as its own MCP server at /<id>/mcp:
    • MCP is served stateless, so any copy answers any request;
    • the app's Stop ends a call through POST /cancel, passed between copies by Redis;
    • a host copy keeps serving while Redis restarts;
    • one image (Chromium, Xvfb and fonts), with host/.env.example.
  • Ten scraper plugins, copied from surfsense_backend, each behind MCP tools with progress: Google Search, Web Crawl, Reddit, YouTube, Instagram, TikTok, Google Maps, Amazon, Walmart and Indeed. What they share is in proprietary/scraping/:
    • residential proxy providers, with DataImpulse sticky sessions held per port;
    • the async captcha solvers;
    • the browser loop.
  • The license check (proprietary/license_check/) uses Keygen's validate-key, with a five-minute cache and a 24-hour grace that a refusal ends.
  • plugins/remote/proprietary/ is Business Source License 1.1 and changed by maintainers only, enforced in CI.

The registry (plugins/registry/)

  • The list. Publishers add an entry and an icon by pull request. It starts with DeepWiki.
  • registry_check checks every entry and icon rule.
  • Signing. On dev, CI signs the list into published/, and the release pull request carries it to main, so nothing pushes to main.
  • A small Caddy container serves the signed list, its icons, and SurfSense's OAuth client document and callback page. It is beside the plugin host, so a listing never restarts the scrapers.
  • plugins/README.md is the guide for publishers.

Docs:

  • architecture docs for plugins.md, plugin-host.md and the new plugin-registry.md;
  • updates to agent.md and egress.md;
  • ADRs 0051 to 0054;
  • the proposal brought in line with what was built.

Why

The product direction is local + API. Plugins let the agent use outside services, SurfSense's paid scrapers first, without building each one into the app. They are standard MCP servers, so any existing MCP server works unchanged. Remote plugins come first, hosted by their publishers; bundles that run on the user's machine come later. The hosted scraper API is being shut down, so the scrapers move out of surfsense_backend into their own servers, licensed through the SurfSense license.

Fixes #

How to test

Automated

cd surfsense_local/backend && uv sync && uv run pytest -m unit && uv run pytest tests/integration/plugins tests/integration/agent
cd surfsense_local/frontend && pnpm test && pnpm typecheck
cd surfsense_local/electron && pnpm test
cd plugins/remote/host && uv sync && uv run pytest
cd plugins/registry/check && uv sync && uv run pytest && uv run python -m registry_check static

In the app

  1. Start a plugin host: cd plugins/remote && uv run --project host --env-file host/.env python -m host. Copy host/.env.example first; the scrapers need a residential proxy, and Google Search a captcha key.
  2. cd surfsense_local/electron && SURFSENSE_LOCAL_PLUGIN_HOST_URL=http://127.0.0.1:8000 pnpm dev, then import a license.
  3. Settings → Plugins → By SurfSense: connect Google Search and Web Crawl. In an Agentic thread, ask the agent to find and read a page.
  4. Turn on Allow third-party plugins: DeepWiki is listed with its icon. Connect it and ask a question about a GitHub repo.
  5. Add MCP server with an OAuth server, such as https://mcp.linear.app/mcp: the browser opens the sign-in, the row waits, then shows Connected with its tools.
  6. Press Stop during a long scrape: the call ends on the plugin host.

Before production

  • add the REGISTRY_SIGNING_KEY repository secret;
  • let GitHub Actions push to dev if dev is protected;
  • deploy the plugin host, Redis, SearXNG and the registry container behind plugins.surfsense.com, with the registry's paths routed to its container;
  • set SURFSENSE_ACCOUNT_ID in plugins/remote/proprietary/license_check/keygen.py.

🤖 Generated with Claude Code

AnishSarkar22 and others added 30 commits October 10, 2026 22:21
A read-only tool runs without asking only on a plugin SurfSense runs, since
annotations are the server's own claim; a third party's read-only tool asks
until Always allow. Settings groups each plugin's tools as read-only and the
rest, with one setting per group.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The client is the SDK's, pinned to one minor version and used only inside
modules/plugins/remote/. It adds about 5 MB and brings OAuth and new protocol
revisions; SurfSense's rules (deadlines, retries, never resending a call) sit
around it. Each operation opens its own connection, since the SDK's tasks must
close where they opened.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
mcp is pinned to 2.3.x for the plugin client and its test servers. jsonschema
was already shipped through docling-core; the gateway now checks arguments
with it, so it is declared directly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Migration 0028 adds installed_plugins, plugin_credentials (encrypted),
plugin_tools (with the server's order), plugin_calls (no foreign key to the
plugin, so past calls survive a disconnect) and plugin_settings, one row for
Restricted mode.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… through

list_tools offers only ready tools under `<plugin>__<tool>` names: switched on,
hosts allowed, other publishers' only once the user turns them on, colliding
names kept distinct. call_tool refuses unknown tools and bad arguments with a
sentence, asks for approval by the trust rules, records every call whole, and
trims the model's copy at 20 KB. A later listing applies the rule for tools
added later.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
McpSession lists every page of tools and calls them with progress and a
deadline, through the SDK's client. Around it: the failing HTTP status kept
(the SDK hides it), listing retried twice, a call never retried, and a 2025
server's forgotten session resent once. RemoteMcpSource puts it behind the
gateway. Tested against the SDK's own server.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…later

Plugins reach Agentic threads through opencode first; the chat engine's
router step is kept as a design for later, and ADR 0052 is deferred with it.
`@` mentions stay for agent threads: the mention offers the tool that turn and
the agent fills its inputs, so approval is as for any call. A user on a
chat-only model has no plugins until the router step, and the screen says so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The SDK-built test server and its wrappers move to tests/mcp_test_server.py,
beside office_stand_in.py, so route tests use it too; renamed so it is not
mistaken for plugins/remote/mcp_server/. It gains the notes server and a
wrapper that answers 401 without a token. The gateway's failing-source test
raises a plain error, so the gateway's tests no longer import the remote client.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…le the tables

SurfSense's plugins are first-party and listed in the first-party list; every
other publisher's are third-party and wait for "Allow third-party plugins",
which replaces "Restricted mode" (`third_party_on`, source `first_party`).

Migration 0028 settles on four tables: the sign-in folds into
installed_plugins as an encrypted column, as provider_connections keeps a key;
one plugin per server, its URL unique and stored in one spelling;
plugin_settings is a one-row table like license_state. Tools stay rows, so
each carries its switch and approval and tool search can index them later.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
POST /plugins/custom checks the server's host before anything is sent, lists
its tools and stores the plugin with its token encrypted; a server already
connected answers 409, one needing a key 422, one out of reach 502. GET
/plugins lists plugins and tools without credentials, PUT switches a tool or
allows third-party plugins, DELETE disconnects and keeps past calls.

An egress refusal now names every host still to allow (`hosts`), and a refused
plugin host is recorded off so Settings > Network lists it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ists them

The agent's opencode calls plugin servers with its own MCP client and keeps
each call in its session, so SurfSense's call path and the plugin_calls table
are removed. They return with the chat engine.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ectly

SurfSense becomes the controller, not a hop: each turn it gives opencode the
ready servers and a rule per tool, and reads opencode's view of each server
to explain a plugin it could not reach. The call path and plugin_calls move
to the chat engine's later section.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Off pauses a plugin: its sign-in and tool settings stay, and none of its
tools reach a caller until it is switched back on.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Servers are given only when opencode does not already hold them, rules are
set on the thread's session, and denied tools are hidden from the model, so
the open question is closed. Adds the plugin switch and, for later, taking
access away mid-turn.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Before a turn, SurfSense re-lists ready plugins at most every five minutes,
gives the thread's opencode their servers (keeping a held session), drops
those no longer ready, and sets the session's allow/ask/deny rules. A plugin
opencode cannot connect is named in an agent-plugins-unavailable frame.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
opencode's traffic leaves through a CONNECT proxy the API runs, which
tunnels only to a ready plugin's host, checked on each connection, and
refuses the rest. TLS stays end to end. A remote plugin must use HTTPS.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Electron picks the proxy's port beside opencode's, gives it to the API,
and sets opencode's proxy variables to it instead of the discard port.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The reply shows "Couldn't reach Notion" or "Sign in to Notion again" from
the agent-plugins-unavailable frame, in every language.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every refusal to connect a server carries a code the screen translates,
GET /plugins/third-party reads the switch back, and a plugin's egress
refusal says it is for a plugin, so the consent prompt words it as one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add an MCP server by URL with an optional key, pause or disconnect it,
switch each tool and choose whether it asks, and allow third-party
plugins behind a consent. The network prompt words a plugin's host as
one. Translated into every language.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…vers

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ard test

The mode menu lists both modes for an open chat, the other marked "New
chat"; the test still looked for the "Start a new chat in" item it replaced.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d checks

A step's whole result is read from its opencode session, and a plugin
step's result is saved to Sources as a note the same call updates. Every
listing of a plugin's server is a recorded check, with Check now on
request, and a tool switched off on its own is marked added or changed
until allowed. The check before a turn reads the stored check time.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A plugin step reads "Notes: Search notes", opens to its whole result and
offers Save to Sources. Each plugin card shows its last check with Check
now, and marks tools that are new or changed. Translated into every
language.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…0028

0028 has not shipped, so the plugin tables are created once with their
check and tool-change columns, and 0029's raw ALTER TABLE goes. A database
that ran the earlier 0028 is downgraded to 0027 once and upgraded again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An @<plugin>_<tool> in the message that names a ready tool gives it to the
turn past the limit, with a note telling the agent the user asked for it.
Notes go as separate synthetic parts, and reading a thread back drops
every synthetic part from the user's words.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Through assistant-ui's composer trigger popover; a pick writes the tool's
opencode name, @notes_count, into the message. Translated into every
language.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
AnishSarkar22 and others added 21 commits October 12, 2026 03:37
proprietary/youtube/ is the backend's YouTube scraper behind two tools at
/youtube/mcp, scrape and comments, licensed like Reddit, with progress per
item from an on_item callback. youtube-transcript-api, fake-useragent and
requests join the host for its subtitles. The backend's tests came with it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
proprietary/amazon/ is the backend's Amazon scraper behind one tool, scrape,
at /amazon/mcp, licensed like Reddit, plain HTTP with the exit country
following the domain. A block is an error item per input, as the backend's.
The backend's tests came with it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
proprietary/walmart/ is the backend's Walmart scraper behind two tools at
/walmart/mcp, scrape and reviews, licensed like Reddit, plain HTTP on US
exits. A block is an error item per input, as the backend's. The backend's
tests came with it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
proprietary/google_maps/ is the backend's Google Maps scraper behind two
tools at /google-maps/mcp, scrape and reviews, licensed like Reddit; a
sign-in wall is the tool's error. Its summary shows a review count only when
known. The backend's tests came with it, those reading uncaptured responses
skipped as there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… host

The plugin host doc gains the four plugins, how each reaches its site and
how a block reads; Instagram follows Google Search.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An expired key that was valid earlier in the day passed while Keygen was
unreachable. Any refusal now ends the 24-hour grace.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
proprietary/google_search/ is the backend's Google Search scraper behind one
tool, scrape, at /google-search/mcp, licensed like Reddit. Google walling every
address, with no SearXNG page to fall back on, is the tool's error; a SearXNG
page says it is not Google's ranking.

The captcha solvers move to proprietary/scraping/captcha/, async on httpx2,
with 2captcha over HTTPS, set by SCRAPING_CAPTCHA_PROVIDER and
SCRAPING_CAPTCHA_API_KEY. The shared warm-IP pool uses async Redis at
REDIS_URL, and sticky IPs come from the proxy provider, so NodeMaven holds
them too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Redis runs as a container that restarts with the platform. A copy of the host
now starts without it and resubscribes when it returns, instead of failing to
start; a Stop sent while Redis is down still ends the calls on the copy that
took it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… container

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
DataImpulse ignores a session id in the username and rotates the exit IP per
request, measured live: five requests on one session went out on five IPs.
Each port from 10000 to 20000 holds one IP, with or without a country, so a
session now picks its port from its id. Reddit's warm-up, Amazon, Walmart and
Google Search were all asking for sticky IPs and not getting them; Google
Search paid a captcha solve on every page for it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An agent wanting an article's text passed its URL as a query, and Google
Search paid a render and a captcha solve to search for the URL. Another site's
URL is now the tool's error, which its description warns of; Google Search
URLs are still scraped as they are.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
proprietary/instagram/ is the backend's Instagram scraper behind two tools,
scrape and details, at /instagram/mcp, licensed like Reddit. A keyword finds
profiles through the Google Search plugin's scraper; Instagram refusing every
address is the tool's error. Instagram's anonymous profile API now answers 401
on every address, so both tools fail on public profiles until they read the
profile page instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
proprietary/tiktok/ is the backend's TikTok scraper behind four tools, scrape,
comments, trending and user_search, at /tiktok/mcp, licensed like Reddit.
TikTok withholds profile feeds from a headless browser, so the image installs
Xvfb and host/entrypoint.sh starts it when CRAWL_HEADED_XVFB_ENABLED is true,
then execs the host so it still gets the container's signals.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
proprietary/indeed/ is the backend's Indeed scraper behind one tool, scrape, at
/indeed/mcp, licensed like Reddit. Its Cloudflare-solving session is Scrapling's
stealth Chromium, so the image needs no Camoufox, and each session now holds
one sticky IP, since its clearance belongs to that IP.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
proprietary/web_crawl/ is the backend's web crawler behind one tool, crawl, at
/web-crawl/mcp, licensed like Reddit: pages read as markdown, or a site
followed by its links. It refuses private addresses before any fetch, and its
browser tiers await the async captcha solver. The first-party list renames the
plugin from web-crawler to web-crawl, and Google Search's refusal of a page to
read now points the agent at it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…host

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SurfSense signs in itself, at Connect, through the MCP SDK's OAuth client, and
gives opencode only the bearer header: its own client lists tools with the same
token, tokens stay encrypted, and the sign-in hosts get consent first. One fixed
HTTPS redirect on the plugin host forwards to the app's loopback port, and an
entry may carry a pre-registered client_id. A token is refreshed only as a turn
starts, and a plugin is given to opencode again only when its token changed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…container

The docs no longer name a platform: the plugin host needs a container, Redis,
object storage and a proxy in front that routes by path, and where it runs is
the deployment's choice. The signed registry, its icons and the OAuth files are
served by a small static container beside the plugin host, on the same
hostname, so updating the list never restarts the plugins. CI commits the
signature back to main, and the app refuses a list older than its cached copy.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d container

plugins/registry/ holds the list publishers add to by pull request, starting
with DeepWiki, and its icons. registry_check checks every entry and icon rule;
on dev, CI signs the list with Ed25519 into published/, which the release pull
request carries to main, so nothing pushes to main and the source list is
never rewritten. A small Caddy container serves the signed list, its icons and
SurfSense's OAuth client document and callback page on plugins.surfsense.com,
apart from the plugin host so a listing never restarts the scrapers. CI does
not contact listed servers: most need a sign-in, so a maintainer connects each
listing on dev. plugins/README.md is the guide for publishers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…m with OAuth

Once third-party plugins are allowed, the app fetches the signed registry at
most every five minutes, verifies it against compiled keys, refuses an older
list and entries claiming SurfSense or the license, and keeps the last good
copy and its icons for offline use; an unpackaged app reads the repository.
Settings > Plugins lists them under SurfSense's own, with Connect, and a
removed plugin stops reaching the agent and says why.

OAuth plugins, listed or added by URL, sign in at Connect through the MCP SDK's
client: consent for the sign-in hosts, the browser, the callback, and tokens
stored encrypted. A token is refreshed as a turn starts, giving opencode the
new header; a refused refresh signs the plugin out, with Sign in again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@vercel

vercel Bot commented Oct 12, 2026

Copy link
Copy Markdown

@AnishSarkar22 is attempting to deploy a commit to the Rohan Verma's projects Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Oct 12, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Too many files!

This PR contains 519 files, which is 219 over the limit of 300.

To get a review, reduce the PR to 300 files or fewer by splitting it into smaller PRs or changing its base branch.

Usage-priced reviews support at most 300 files.

⚙️ Run configuration
  • Configuration used: Repository: MODSetter/SurfSense/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 94e520ab-3c42-47c5-add7-fbc7b75a1381

📥 Commits

Reviewing files that changed from the base of the PR and between 4782896 and b14d9f9.


⛔ Files ignored due to path filters (4)
  • plugins/registry/check/uv.lock is excluded by !**/*.lock
  • plugins/registry/icons/deepwiki.svg is excluded by !**/*.svg
  • plugins/remote/host/uv.lock is excluded by !**/*.lock
  • surfsense_local/backend/uv.lock is excluded by !**/*.lock

📒 Files selected for processing (519)
  • .agents/skills/translate/glossary.md
  • .github/CODEOWNERS
  • .github/workflows/code-quality.yml
  • .github/workflows/plugins-pull-request-checks.yml
  • .github/workflows/plugins-registry.yml
  • AGENTS.md
  • CONTRIBUTING.md
  • LICENSE
  • docs/ROADMAP.md
  • docs/adr/0047-premium-plugins-are-source-available.md
  • docs/adr/0051-plugins-are-mcp-servers-behind-one-tool-gateway.md
  • docs/adr/0052-the-chat-model-never-calls-tools.md
  • docs/adr/0053-plugins-are-listed-in-a-registry-and-third-party-ones-start-off.md
  • docs/adr/0054-surfsenses-plugins-run-on-one-plugin-host.md
  • docs/adr/README.md
  • docs/architecture/agent.md
  • docs/architecture/chat.md
  • docs/architecture/egress.md
  • docs/architecture/overview.md
  • docs/architecture/plugin-host.md
  • docs/architecture/plugin-registry.md
  • docs/architecture/plugins.md
  • docs/proposals/plugins/README.md
  • docs/proposals/plugins/bundles/README.md
  • docs/proposals/plugins/core/01-architecture.md
  • docs/proposals/plugins/core/02-registry.md
  • docs/proposals/plugins/core/03-engines.md
  • docs/proposals/plugins/core/04-trust.md
  • docs/proposals/plugins/core/05-paid.md
  • docs/proposals/plugins/core/06-later.md
  • docs/proposals/plugins/remote/01-mcp-client.md
  • docs/proposals/plugins/remote/02-surfsense-servers.md
  • docs/proposals/plugins/remote/README.md
  • plugins/README.md
  • plugins/registry/.dockerignore
  • plugins/registry/.gitignore
  • plugins/registry/Caddyfile
  • plugins/registry/Dockerfile
  • plugins/registry/check/pyproject.toml
  • plugins/registry/check/registry_check/__init__.py
  • plugins/registry/check/registry_check/__main__.py
  • plugins/registry/check/registry_check/signing/__init__.py
  • plugins/registry/check/registry_check/signing/sign.py
  • plugins/registry/check/registry_check/signing/signed_bytes.py
  • plugins/registry/check/registry_check/signing/verify.py
  • plugins/registry/check/registry_check/static/__init__.py
  • plugins/registry/check/registry_check/static/checked_list.py
  • plugins/registry/check/registry_check/static/entry_rules.py
  • plugins/registry/check/registry_check/static/icon_rules.py
  • plugins/registry/check/registry_check/static/list_rules.py
  • plugins/registry/check/tests/__init__.py
  • plugins/registry/check/tests/registry_files.py
  • plugins/registry/check/tests/test_commands.py
  • plugins/registry/check/tests/test_icon_check.py
  • plugins/registry/check/tests/test_signing.py
  • plugins/registry/check/tests/test_static_check.py
  • plugins/registry/oauth/callback.html
  • plugins/registry/oauth/client.json
  • plugins/registry/oauth/tests/callback.test.mjs
  • plugins/registry/plugins.json
  • plugins/registry/published/README.md
  • plugins/remote/.gitignore
  • plugins/remote/host/.env.example
  • plugins/remote/host/Dockerfile
  • plugins/remote/host/Dockerfile.dockerignore
  • plugins/remote/host/__init__.py
  • plugins/remote/host/__main__.py
  • plugins/remote/host/app.py
  • plugins/remote/host/calls_in_flight.py
  • plugins/remote/host/cancel_broadcast.py
  • plugins/remote/host/cancel_route.py
  • plugins/remote/host/cancellable_calls.py
  • plugins/remote/host/entrypoint.sh
  • plugins/remote/host/mounted_plugins.py
  • plugins/remote/host/pyproject.toml
  • plugins/remote/host/refused_unless_allowed.py
  • plugins/remote/host/tests/__init__.py
  • plugins/remote/host/tests/mcp_calls.py
  • plugins/remote/host/tests/redis_server.py
  • plugins/remote/host/tests/sample_plugins/failing/server.py
  • plugins/remote/host/tests/sample_plugins/notes/server.py
  • plugins/remote/host/tests/sample_plugins/proprietary/paid/server.py
  • plugins/remote/host/tests/sample_plugins/slow/server.py
  • plugins/remote/host/tests/serving.py
  • plugins/remote/host/tests/test_host.py
  • plugins/remote/host/tests/test_scaling.py
  • plugins/remote/mcp_server/__init__.py
  • plugins/remote/mcp_server/access.py
  • plugins/remote/proprietary/LICENSE
  • plugins/remote/proprietary/__init__.py
  • plugins/remote/proprietary/amazon/__init__.py
  • plugins/remote/proprietary/amazon/fetch.py
  • plugins/remote/proprietary/amazon/locale.py
  • plugins/remote/proprietary/amazon/parsers.py
  • plugins/remote/proprietary/amazon/schemas.py
  • plugins/remote/proprietary/amazon/scraper.py
  • plugins/remote/proprietary/amazon/server.py
  • plugins/remote/proprietary/amazon/tests/__init__.py
  • plugins/remote/proprietary/amazon/tests/fixtures/bestsellers.html
  • plugins/remote/proprietary/amazon/tests/fixtures/blocked.html
  • plugins/remote/proprietary/amazon/tests/fixtures/offers.html
  • plugins/remote/proprietary/amazon/tests/fixtures/product.html
  • plugins/remote/proprietary/amazon/tests/fixtures/search.html
  • plugins/remote/proprietary/amazon/tests/fixtures/seller.html
  • plugins/remote/proprietary/amazon/tests/test_amazon_plugin.py
  • plugins/remote/proprietary/amazon/tests/test_flows.py
  • plugins/remote/proprietary/amazon/tests/test_locale.py
  • plugins/remote/proprietary/amazon/tests/test_parsers.py
  • plugins/remote/proprietary/amazon/tests/test_proxy.py
  • plugins/remote/proprietary/amazon/tests/test_skeleton.py
  • plugins/remote/proprietary/amazon/url_resolver.py
  • plugins/remote/proprietary/google_maps/__init__.py
  • plugins/remote/proprietary/google_maps/fetch.py
  • plugins/remote/proprietary/google_maps/parsers.py
  • plugins/remote/proprietary/google_maps/reviews.py
  • plugins/remote/proprietary/google_maps/schemas.py
  • plugins/remote/proprietary/google_maps/scraper.py
  • plugins/remote/proprietary/google_maps/server.py
  • plugins/remote/proprietary/google_maps/tests/__init__.py
  • plugins/remote/proprietary/google_maps/tests/test_google_maps_plugin.py
  • plugins/remote/proprietary/google_maps/tests/test_parsers.py
  • plugins/remote/proprietary/google_maps/tests/test_reviews.py
  • plugins/remote/proprietary/google_maps/tests/test_search.py
  • plugins/remote/proprietary/google_maps/tests/test_skeleton.py
  • plugins/remote/proprietary/google_maps/url_resolver.py
  • plugins/remote/proprietary/google_search/__init__.py
  • plugins/remote/proprietary/google_search/captcha.py
  • plugins/remote/proprietary/google_search/fetch.py
  • plugins/remote/proprietary/google_search/goto.py
  • plugins/remote/proprietary/google_search/parsers.py
  • plugins/remote/proprietary/google_search/pool_store.py
  • plugins/remote/proprietary/google_search/query_builder.py
  • plugins/remote/proprietary/google_search/schemas.py
  • plugins/remote/proprietary/google_search/scraper.py
  • plugins/remote/proprietary/google_search/searxng.py
  • plugins/remote/proprietary/google_search/server.py
  • plugins/remote/proprietary/google_search/tests/__init__.py
  • plugins/remote/proprietary/google_search/tests/test_browser_loop.py
  • plugins/remote/proprietary/google_search/tests/test_captcha.py
  • plugins/remote/proprietary/google_search/tests/test_fetch_concurrency.py
  • plugins/remote/proprietary/google_search/tests/test_fetch_pool.py
  • plugins/remote/proprietary/google_search/tests/test_google_search_plugin.py
  • plugins/remote/proprietary/google_search/tests/test_goto_links.py
  • plugins/remote/proprietary/google_search/tests/test_pool_store.py
  • plugins/remote/proprietary/google_search/tests/test_searxng_fallback.py
  • plugins/remote/proprietary/google_search/tests/test_skeleton.py
  • plugins/remote/proprietary/indeed/__init__.py
  • plugins/remote/proprietary/indeed/fetch.py
  • plugins/remote/proprietary/indeed/parsers.py
  • plugins/remote/proprietary/indeed/schemas.py
  • plugins/remote/proprietary/indeed/scraper.py
  • plugins/remote/proprietary/indeed/server.py
  • plugins/remote/proprietary/indeed/tests/__init__.py
  • plugins/remote/proprietary/indeed/tests/fixtures/sample_jobcards.json
  • plugins/remote/proprietary/indeed/tests/fixtures/sample_viewjob.html
  • plugins/remote/proprietary/indeed/tests/test_fetch_resilience.py
  • plugins/remote/proprietary/indeed/tests/test_indeed_plugin.py
  • plugins/remote/proprietary/indeed/tests/test_parsers.py
  • plugins/remote/proprietary/indeed/tests/test_scraper.py
  • plugins/remote/proprietary/indeed/tests/test_url_resolver.py
  • plugins/remote/proprietary/indeed/url_resolver.py
  • plugins/remote/proprietary/instagram/__init__.py
  • plugins/remote/proprietary/instagram/fetch.py
  • plugins/remote/proprietary/instagram/parsers.py
  • plugins/remote/proprietary/instagram/schemas.py
  • plugins/remote/proprietary/instagram/scraper.py
  • plugins/remote/proprietary/instagram/server.py
  • plugins/remote/proprietary/instagram/tests/__init__.py
  • plugins/remote/proprietary/instagram/tests/test_budget.py
  • plugins/remote/proprietary/instagram/tests/test_discovery.py
  • plugins/remote/proprietary/instagram/tests/test_fetch_resilience.py
  • plugins/remote/proprietary/instagram/tests/test_instagram_plugin.py
  • plugins/remote/proprietary/instagram/tests/test_parsers.py
  • plugins/remote/proprietary/instagram/tests/test_skeleton.py
  • plugins/remote/proprietary/instagram/url_resolver.py
  • plugins/remote/proprietary/license_check/__init__.py
  • plugins/remote/proprietary/license_check/keygen.py
  • plugins/remote/proprietary/license_check/license_access.py
  • plugins/remote/proprietary/license_check/tests/__init__.py
  • plugins/remote/proprietary/license_check/tests/sample_plugins/proprietary/paid/server.py
  • plugins/remote/proprietary/license_check/tests/stand_in_keygen.py
  • plugins/remote/proprietary/license_check/tests/test_license_access.py
  • plugins/remote/proprietary/reddit/__init__.py
  • plugins/remote/proprietary/reddit/fetch.py
  • plugins/remote/proprietary/reddit/parsers.py
  • plugins/remote/proprietary/reddit/schemas.py
  • plugins/remote/proprietary/reddit/scraper.py
  • plugins/remote/proprietary/reddit/server.py
  • plugins/remote/proprietary/reddit/tests/__init__.py
  • plugins/remote/proprietary/reddit/tests/test_community_listing.py
  • plugins/remote/proprietary/reddit/tests/test_fetch_resilience.py
  • plugins/remote/proprietary/reddit/tests/test_parsers.py
  • plugins/remote/proprietary/reddit/tests/test_reddit_plugin.py
  • plugins/remote/proprietary/reddit/tests/test_search_budget.py
  • plugins/remote/proprietary/reddit/tests/test_skeleton.py
  • plugins/remote/proprietary/reddit/url_resolver.py
  • plugins/remote/proprietary/scraping/__init__.py
  • plugins/remote/proprietary/scraping/browser_loop.py
  • plugins/remote/proprietary/scraping/captcha/__init__.py
  • plugins/remote/proprietary/scraping/captcha/config.py
  • plugins/remote/proprietary/scraping/captcha/solvers.py
  • plugins/remote/proprietary/scraping/config.py
  • plugins/remote/proprietary/scraping/proxy/__init__.py
  • plugins/remote/proprietary/scraping/proxy/base.py
  • plugins/remote/proprietary/scraping/proxy/providers/__init__.py
  • plugins/remote/proprietary/scraping/proxy/providers/dataimpulse.py
  • plugins/remote/proprietary/scraping/proxy/providers/nodemaven.py
  • plugins/remote/proprietary/scraping/proxy/registry.py
  • plugins/remote/proprietary/scraping/proxy/rotation.py
  • plugins/remote/proprietary/scraping/tests/__init__.py
  • plugins/remote/proprietary/scraping/tests/test_captcha_config.py
  • plugins/remote/proprietary/scraping/tests/test_captcha_solvers.py
  • plugins/remote/proprietary/scraping/tests/test_dataimpulse_provider.py
  • plugins/remote/proprietary/scraping/tests/test_nodemaven_provider.py
  • plugins/remote/proprietary/scraping/tests/test_registry.py
  • plugins/remote/proprietary/scraping/tests/test_rotation.py
  • plugins/remote/proprietary/tiktok/__init__.py
  • plugins/remote/proprietary/tiktok/extraction/__init__.py
  • plugins/remote/proprietary/tiktok/extraction/author.py
  • plugins/remote/proprietary/tiktok/extraction/comments.py
  • plugins/remote/proprietary/tiktok/extraction/hydration.py
  • plugins/remote/proprietary/tiktok/extraction/item_list.py
  • plugins/remote/proprietary/tiktok/extraction/scopes.py
  • plugins/remote/proprietary/tiktok/extraction/timestamps.py
  • plugins/remote/proprietary/tiktok/extraction/user_search.py
  • plugins/remote/proprietary/tiktok/extraction/video.py
  • plugins/remote/proprietary/tiktok/flows/__init__.py
  • plugins/remote/proprietary/tiktok/flows/comments.py
  • plugins/remote/proprietary/tiktok/flows/listing.py
  • plugins/remote/proprietary/tiktok/flows/profile.py
  • plugins/remote/proprietary/tiktok/flows/user_search.py
  • plugins/remote/proprietary/tiktok/flows/video.py
  • plugins/remote/proprietary/tiktok/orchestrator.py
  • plugins/remote/proprietary/tiktok/schemas/__init__.py
  • plugins/remote/proprietary/tiktok/schemas/input.py
  • plugins/remote/proprietary/tiktok/schemas/items.py
  • plugins/remote/proprietary/tiktok/server.py
  • plugins/remote/proprietary/tiktok/session/__init__.py
  • plugins/remote/proprietary/tiktok/session/client.py
  • plugins/remote/proprietary/tiktok/session/errors.py
  • plugins/remote/proprietary/tiktok/session/listing.py
  • plugins/remote/proprietary/tiktok/session/proxy.py
  • plugins/remote/proprietary/tiktok/session/stealth.py
  • plugins/remote/proprietary/tiktok/targets/__init__.py
  • plugins/remote/proprietary/tiktok/targets/resolver.py
  • plugins/remote/proprietary/tiktok/targets/types.py
  • plugins/remote/proprietary/tiktok/tests/__init__.py
  • plugins/remote/proprietary/tiktok/tests/test_comments.py
  • plugins/remote/proprietary/tiktok/tests/test_fetch_resilience.py
  • plugins/remote/proprietary/tiktok/tests/test_hydration.py
  • plugins/remote/proprietary/tiktok/tests/test_input.py
  • plugins/remote/proprietary/tiktok/tests/test_item_list.py
  • plugins/remote/proprietary/tiktok/tests/test_listing_retry.py
  • plugins/remote/proprietary/tiktok/tests/test_orchestrator.py
  • plugins/remote/proprietary/tiktok/tests/test_parsers.py
  • plugins/remote/proprietary/tiktok/tests/test_scopes.py
  • plugins/remote/proprietary/tiktok/tests/test_target_resolver.py
  • plugins/remote/proprietary/tiktok/tests/test_tiktok_plugin.py
  • plugins/remote/proprietary/tiktok/tests/test_trending.py
  • plugins/remote/proprietary/tiktok/tests/test_user_search.py
  • plugins/remote/proprietary/walmart/__init__.py
  • plugins/remote/proprietary/walmart/fetch.py
  • plugins/remote/proprietary/walmart/next_data.py
  • plugins/remote/proprietary/walmart/parsers.py
  • plugins/remote/proprietary/walmart/schemas.py
  • plugins/remote/proprietary/walmart/scraper.py
  • plugins/remote/proprietary/walmart/server.py
  • plugins/remote/proprietary/walmart/tests/__init__.py
  • plugins/remote/proprietary/walmart/tests/fixtures/blocked.html
  • plugins/remote/proprietary/walmart/tests/fixtures/listing.html
  • plugins/remote/proprietary/walmart/tests/fixtures/product.html
  • plugins/remote/proprietary/walmart/tests/fixtures/reviews.html
  • plugins/remote/proprietary/walmart/tests/test_flows.py
  • plugins/remote/proprietary/walmart/tests/test_parsers.py
  • plugins/remote/proprietary/walmart/tests/test_walmart_plugin.py
  • plugins/remote/proprietary/walmart/url_resolver.py
  • plugins/remote/proprietary/web_crawl/__init__.py
  • plugins/remote/proprietary/web_crawl/captcha.py
  • plugins/remote/proprietary/web_crawl/classifier.py
  • plugins/remote/proprietary/web_crawl/connector.py
  • plugins/remote/proprietary/web_crawl/contacts.py
  • plugins/remote/proprietary/web_crawl/items.py
  • plugins/remote/proprietary/web_crawl/net_guard.py
  • plugins/remote/proprietary/web_crawl/server.py
  • plugins/remote/proprietary/web_crawl/site_crawler.py
  • plugins/remote/proprietary/web_crawl/stealth.py
  • plugins/remote/proprietary/web_crawl/tests/__init__.py
  • plugins/remote/proprietary/web_crawl/tests/test_captcha.py
  • plugins/remote/proprietary/web_crawl/tests/test_classifier.py
  • plugins/remote/proprietary/web_crawl/tests/test_connector.py
  • plugins/remote/proprietary/web_crawl/tests/test_connector_links.py
  • plugins/remote/proprietary/web_crawl/tests/test_contacts.py
  • plugins/remote/proprietary/web_crawl/tests/test_net_guard.py
  • plugins/remote/proprietary/web_crawl/tests/test_site_crawler.py
  • plugins/remote/proprietary/web_crawl/tests/test_stealth.py
  • plugins/remote/proprietary/web_crawl/tests/test_url_policy.py
  • plugins/remote/proprietary/web_crawl/tests/test_web_crawl_plugin.py
  • plugins/remote/proprietary/web_crawl/url_policy.py
  • plugins/remote/proprietary/youtube/__init__.py
  • plugins/remote/proprietary/youtube/comments.py
  • plugins/remote/proprietary/youtube/innertube.py
  • plugins/remote/proprietary/youtube/parsers.py
  • plugins/remote/proprietary/youtube/schemas.py
  • plugins/remote/proprietary/youtube/scraper.py
  • plugins/remote/proprietary/youtube/search_filters.py
  • plugins/remote/proprietary/youtube/server.py
  • plugins/remote/proprietary/youtube/subtitles.py
  • plugins/remote/proprietary/youtube/tests/__init__.py
  • plugins/remote/proprietary/youtube/tests/test_fetch_resilience.py
  • plugins/remote/proprietary/youtube/tests/test_parsers.py
  • plugins/remote/proprietary/youtube/tests/test_subtitles_retry.py
  • plugins/remote/proprietary/youtube/tests/test_youtube_plugin.py
  • plugins/remote/proprietary/youtube/url_resolver.py
  • plugins/remote/ruff.toml
  • surfsense_local/backend/alembic/versions/0028_remote_plugins.py
  • surfsense_local/backend/api/main.py
  • surfsense_local/backend/bundling/api.spec
  • surfsense_local/backend/modules/agent/agent_threads/replies.py
  • surfsense_local/backend/modules/agent/agent_threads/router.py
  • surfsense_local/backend/modules/agent/agent_threads/steps.py
  • surfsense_local/backend/modules/agent/agent_threads/stored_step.py
  • surfsense_local/backend/modules/agent/agent_threads/turn.py
  • surfsense_local/backend/modules/agent/agent_threads/turn_frames.py
  • surfsense_local/backend/modules/agent/opencode_client/client.py
  • surfsense_local/backend/modules/agent/plugin_proxy/__init__.py
  • surfsense_local/backend/modules/agent/plugin_proxy/allowed_hosts.py
  • surfsense_local/backend/modules/agent/plugin_proxy/server.py
  • surfsense_local/backend/modules/agent/plugin_tools/__init__.py
  • surfsense_local/backend/modules/agent/plugin_tools/changed_tools.py
  • surfsense_local/backend/modules/agent/plugin_tools/give_plugins.py
  • surfsense_local/backend/modules/agent/plugin_tools/given_servers.py
  • surfsense_local/backend/modules/agent/plugin_tools/mentions.py
  • surfsense_local/backend/modules/agent/plugin_tools/refreshed_sign_ins.py
  • surfsense_local/backend/modules/agent/plugin_tools/router.py
  • surfsense_local/backend/modules/agent/plugin_tools/saved_results.py
  • surfsense_local/backend/modules/agent/plugin_tools/stop_plugin_calls.py
  • surfsense_local/backend/modules/agent/plugin_tools/thread_calls.py
  • surfsense_local/backend/modules/agent/plugin_tools/turn_plugins.py
  • surfsense_local/backend/modules/egress/service.py
  • surfsense_local/backend/modules/license/license_key.py
  • surfsense_local/backend/modules/plugins/gateway/__init__.py
  • surfsense_local/backend/modules/plugins/gateway/approval.py
  • surfsense_local/backend/modules/plugins/gateway/listing.py
  • surfsense_local/backend/modules/plugins/gateway/qualified_name.py
  • surfsense_local/backend/modules/plugins/gateway/ready_tool.py
  • surfsense_local/backend/modules/plugins/gateway/source_tool.py
  • surfsense_local/backend/modules/plugins/installed/__init__.py
  • surfsense_local/backend/modules/plugins/installed/custom_plugin.py
  • surfsense_local/backend/modules/plugins/installed/disconnect.py
  • surfsense_local/backend/modules/plugins/installed/first_party_plugin.py
  • surfsense_local/backend/modules/plugins/installed/https_required.py
  • surfsense_local/backend/modules/plugins/installed/less_safe.py
  • surfsense_local/backend/modules/plugins/installed/models.py
  • surfsense_local/backend/modules/plugins/installed/plugin_check.py
  • surfsense_local/backend/modules/plugins/installed/plugin_hosts.py
  • surfsense_local/backend/modules/plugins/installed/plugin_key.py
  • surfsense_local/backend/modules/plugins/installed/plugin_read.py
  • surfsense_local/backend/modules/plugins/installed/plugin_switch.py
  • surfsense_local/backend/modules/plugins/installed/recorded_tools.py
  • surfsense_local/backend/modules/plugins/installed/registry_plugin.py
  • surfsense_local/backend/modules/plugins/installed/server_url.py
  • surfsense_local/backend/modules/plugins/installed/third_party.py
  • surfsense_local/backend/modules/plugins/installed/tool_settings.py
  • surfsense_local/backend/modules/plugins/lists/__init__.py
  • surfsense_local/backend/modules/plugins/lists/first_party/__init__.py
  • surfsense_local/backend/modules/plugins/lists/first_party/entry_read.py
  • surfsense_local/backend/modules/plugins/lists/first_party/first_party_list.py
  • surfsense_local/backend/modules/plugins/lists/first_party/plugins.json
  • surfsense_local/backend/modules/plugins/lists/list_entry.py
  • surfsense_local/backend/modules/plugins/lists/plugin_host.py
  • surfsense_local/backend/modules/plugins/lists/registry/__init__.py
  • surfsense_local/backend/modules/plugins/lists/registry/current_registry.py
  • surfsense_local/backend/modules/plugins/lists/registry/delisted.py
  • surfsense_local/backend/modules/plugins/lists/registry/listed_ids.py
  • surfsense_local/backend/modules/plugins/lists/registry/offered_entry.py
  • surfsense_local/backend/modules/plugins/lists/registry/registry_cache.py
  • surfsense_local/backend/modules/plugins/lists/registry/registry_consent.py
  • surfsense_local/backend/modules/plugins/lists/registry/registry_document.py
  • surfsense_local/backend/modules/plugins/lists/registry/registry_entry.py
  • surfsense_local/backend/modules/plugins/lists/registry/registry_fetch.py
  • surfsense_local/backend/modules/plugins/lists/registry/registry_icons.py
  • surfsense_local/backend/modules/plugins/lists/registry/registry_keys.py
  • surfsense_local/backend/modules/plugins/lists/registry/registry_origin.py
  • surfsense_local/backend/modules/plugins/lists/registry/registry_read.py
  • surfsense_local/backend/modules/plugins/lists/registry/routes.py
  • surfsense_local/backend/modules/plugins/lists/registry/served_entry.py
  • surfsense_local/backend/modules/plugins/remote/__init__.py
  • surfsense_local/backend/modules/plugins/remote/connect_refusal.py
  • surfsense_local/backend/modules/plugins/remote/mcp_client/__init__.py
  • surfsense_local/backend/modules/plugins/remote/mcp_client/http_failure.py
  • surfsense_local/backend/modules/plugins/remote/mcp_client/remote_tool.py
  • surfsense_local/backend/modules/plugins/remote/mcp_client/retries.py
  • surfsense_local/backend/modules/plugins/remote/mcp_client/sdk_errors.py
  • surfsense_local/backend/modules/plugins/remote/mcp_client/server_error.py
  • surfsense_local/backend/modules/plugins/remote/mcp_client/session.py
  • surfsense_local/backend/modules/plugins/remote/mcp_client/session_expired.py
  • surfsense_local/backend/modules/plugins/remote/mcp_client/unwrapped.py
  • surfsense_local/backend/modules/plugins/remote/sign_in/__init__.py
  • surfsense_local/backend/modules/plugins/remote/sign_in/oauth/__init__.py
  • surfsense_local/backend/modules/plugins/remote/sign_in/oauth/callback_page.py
  • surfsense_local/backend/modules/plugins/remote/sign_in/oauth/consented_client.py
  • surfsense_local/backend/modules/plugins/remote/sign_in/oauth/listed_after_refresh.py
  • surfsense_local/backend/modules/plugins/remote/sign_in/oauth/oauth_credential.py
  • surfsense_local/backend/modules/plugins/remote/sign_in/oauth/oauth_discovery.py
  • surfsense_local/backend/modules/plugins/remote/sign_in/oauth/pending_storage.py
  • surfsense_local/backend/modules/plugins/remote/sign_in/oauth/plugin_draft.py
  • surfsense_local/backend/modules/plugins/remote/sign_in/oauth/plugin_sign_ins.py
  • surfsense_local/backend/modules/plugins/remote/sign_in/oauth/routes.py
  • surfsense_local/backend/modules/plugins/remote/sign_in/oauth/sign_in_client.py
  • surfsense_local/backend/modules/plugins/remote/sign_in/oauth/sign_in_flow.py
  • surfsense_local/backend/modules/plugins/remote/sign_in/oauth/sign_in_read.py
  • surfsense_local/backend/modules/plugins/remote/sign_in/oauth/sign_in_state.py
  • surfsense_local/backend/modules/plugins/remote/sign_in/oauth/signed_in_plugin.py
  • surfsense_local/backend/modules/plugins/remote/sign_in/oauth/start_sign_in.py
  • surfsense_local/backend/modules/plugins/remote/sign_in/oauth/token_refresh.py
  • surfsense_local/backend/modules/plugins/remote/sign_in/plugin_headers.py
  • surfsense_local/backend/modules/plugins/remote/sign_in/token_headers.py
  • surfsense_local/backend/modules/plugins/remote/source.py
  • surfsense_local/backend/modules/plugins/routes.py
  • surfsense_local/backend/pyproject.toml
  • surfsense_local/backend/shared/app_version.py
  • surfsense_local/backend/shared/config.py
  • surfsense_local/backend/shared/db.py
  • surfsense_local/backend/tests/integration/agent/conftest.py
  • surfsense_local/backend/tests/integration/agent/opencode_harness.py
  • surfsense_local/backend/tests/integration/agent/test_plugin_proxy.py
  • surfsense_local/backend/tests/integration/agent/test_plugin_tools.py
  • surfsense_local/backend/tests/integration/llm/test_egress.py
  • surfsense_local/backend/tests/integration/plugins/gateway/conftest.py
  • surfsense_local/backend/tests/integration/plugins/gateway/test_listing.py
  • surfsense_local/backend/tests/integration/plugins/gateway/test_tools_added_later.py
  • surfsense_local/backend/tests/integration/plugins/routes/conftest.py
  • surfsense_local/backend/tests/integration/plugins/routes/test_custom_plugins.py
  • surfsense_local/backend/tests/integration/plugins/routes/test_disconnect.py
  • surfsense_local/backend/tests/integration/plugins/routes/test_first_party_plugins.py
  • surfsense_local/backend/tests/integration/plugins/routes/test_oauth_sign_in.py
  • surfsense_local/backend/tests/integration/plugins/routes/test_plugin_check.py
  • surfsense_local/backend/tests/integration/plugins/routes/test_plugin_key.py
  • surfsense_local/backend/tests/integration/plugins/routes/test_plugin_switch.py
  • surfsense_local/backend/tests/integration/plugins/routes/test_registry.py
  • surfsense_local/backend/tests/integration/plugins/routes/test_tool_settings.py
  • surfsense_local/backend/tests/integration/test_migration_0028.py
  • surfsense_local/backend/tests/license_samples.py
  • surfsense_local/backend/tests/mcp_test_server.py
  • surfsense_local/backend/tests/oauth_stand_in.py
  • surfsense_local/backend/tests/packaging/test_spec_data_files.py
  • surfsense_local/backend/tests/registry_stand_in.py
  • surfsense_local/backend/tests/unit/plugins/gateway/test_approval_rules.py
  • surfsense_local/backend/tests/unit/plugins/installed/test_server_url.py
  • surfsense_local/backend/tests/unit/plugins/lists/test_first_party_list.py
  • surfsense_local/backend/tests/unit/plugins/lists/test_registry_compiled.py
  • surfsense_local/backend/tests/unit/plugins/remote/test_mcp_client.py
  • surfsense_local/backend/tests/unit/plugins/remote/test_sign_in_client.py
  • surfsense_local/electron/src/main/external-url.test.ts
  • surfsense_local/electron/src/main/external-url.ts
  • surfsense_local/electron/src/main/sidecars/boot.ts
  • surfsense_local/electron/src/main/sidecars/opencode.test.ts
  • surfsense_local/electron/src/main/sidecars/opencode.ts
  • surfsense_local/electron/src/main/sidecars/python.test.ts
  • surfsense_local/electron/src/main/sidecars/python.ts
  • surfsense_local/electron/src/main/sidecars/types.ts
  • surfsense_local/frontend/src/components/ui/icons.tsx
  • surfsense_local/frontend/src/features/agent/agent-approvals.tsx
  • surfsense_local/frontend/src/features/agent/agent-steps.tsx
  • surfsense_local/frontend/src/features/agent/agent-thread.test.tsx
  • surfsense_local/frontend/src/features/agent/api.ts
  • surfsense_local/frontend/src/features/agent/approval-dialog.tsx
  • surfsense_local/frontend/src/features/agent/approvals-context.ts
  • surfsense_local/frontend/src/features/agent/inline-approval.tsx
  • surfsense_local/frontend/src/features/agent/plugins-unavailable.tsx
  • surfsense_local/frontend/src/features/chat/api.ts
  • surfsense_local/frontend/src/features/chat/chat-composer.tsx
  • surfsense_local/frontend/src/features/chat/message.tsx
  • surfsense_local/frontend/src/features/chat/runs/apply-frame.ts
  • surfsense_local/frontend/src/features/chat/sse.ts
  • surfsense_local/frontend/src/features/chat/thread-messages.ts
  • surfsense_local/frontend/src/features/chat/use-chat-runtime.ts
  • surfsense_local/frontend/src/features/dashboard/dashboard-page.test.tsx
  • surfsense_local/frontend/src/features/dashboard/dashboard-page.tsx
  • surfsense_local/frontend/src/features/egress/api.ts
  • surfsense_local/frontend/src/features/egress/ask-egress.ts
  • surfsense_local/frontend/src/features/egress/egress-prompt.test.tsx
  • surfsense_local/frontend/src/features/egress/egress-prompt.tsx
  • surfsense_local/frontend/src/features/plugins/add-plugin-dialog.tsx
  • surfsense_local/frontend/src/features/plugins/api.ts
  • surfsense_local/frontend/src/features/plugins/disconnect-plugin-button.tsx
  • surfsense_local/frontend/src/features/plugins/first-party-plugins.tsx
  • surfsense_local/frontend/src/features/plugins/plugin-icon.tsx
  • surfsense_local/frontend/src/features/plugins/plugin-mentions.tsx
  • surfsense_local/frontend/src/features/plugins/plugin-of-step.ts
  • surfsense_local/frontend/src/features/plugins/plugin-row.tsx
  • surfsense_local/frontend/src/features/plugins/plugin-step-line.test.tsx
  • surfsense_local/frontend/src/features/plugins/plugin-step-line.tsx
  • surfsense_local/frontend/src/features/plugins/plugin-tool-row.tsx
  • surfsense_local/frontend/src/features/plugins/plugins-settings.test.tsx
  • surfsense_local/frontend/src/features/plugins/plugins-settings.tsx
  • surfsense_local/frontend/src/features/plugins/refusal-message.ts
  • surfsense_local/frontend/src/features/plugins/registry-key-dialog.tsx
  • surfsense_local/frontend/src/features/plugins/registry-plugins.tsx
  • surfsense_local/frontend/src/features/plugins/replace-key-button.tsx
  • surfsense_local/frontend/src/features/plugins/sign-in-again-button.tsx
  • surfsense_local/frontend/src/features/plugins/sign-in-progress.tsx
  • surfsense_local/frontend/src/features/plugins/third-party-switch.tsx
  • surfsense_local/frontend/src/features/plugins/tool-kind.ts
  • surfsense_local/frontend/src/features/plugins/tool-name.ts
  • surfsense_local/frontend/src/features/plugins/use-plugin-changes.ts
  • surfsense_local/frontend/src/features/plugins/use-plugin-sign-in.ts
  • surfsense_local/frontend/src/features/settings/settings-dialog.test.tsx
  • surfsense_local/frontend/src/features/settings/settings-dialog.tsx
  • surfsense_local/frontend/translations/de.json
  • surfsense_local/frontend/translations/en.json
  • surfsense_local/frontend/translations/es.json
  • surfsense_local/frontend/translations/fr.json
  • surfsense_local/frontend/translations/hi.json
  • surfsense_local/frontend/translations/ja.json
  • surfsense_local/frontend/translations/ko.json
  • surfsense_local/frontend/translations/pt-BR.json
  • surfsense_local/frontend/translations/ru.json
  • surfsense_local/frontend/translations/zh-CN.json

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@AnishSarkar22 AnishSarkar22 changed the title Feat/remote plugins feat(plugins): remote MCP plugins, SurfSense's scraper plugins and the third-party registry Oct 12, 2026
@AnishSarkar22
AnishSarkar22 merged commit 1559bc9 into MODSetter:dev Oct 12, 2026
28 of 29 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant