Repository navigation
feat(chat): chat plugins: remote MCP servers as tools in c0mpute chat - #315
Merged
Merged
Conversation
… (web, CLI, desktop, API, MCP) A chat plugin is a remote MCP server. Paste a URL or pick one from the catalog, sign in if it asks (automatic: DCR + PKCE through @profullstack/chat-plugins), and its tools are usable by the model in every c0mpute chat surface. Not the node plugins under /plugins. - Store: chat_plugins / chat_plugin_clients / chat_plugin_pending on SQLite Cloud; token records sealed AES-256-GCM (C0MPUTE_PLUGIN_KEY, row-bound AAD), never returned by the API. - REST: /api/v1/chat/plugins (GET/POST), /<slug> (GET/DELETE), /<slug>/toggle, /<slug>/connect (browser redirect or one-time link for key callers), /callback (state stored per account AND bound to a browser cookie). - Chat completions run the package tool loop (prompt protocol) when a plugin is on; each model step billed via infernetFor. Plugin events stream as c0mpute.plugin_event chunks when plugin_events: true. Destructive tools stop with a signed approval token; approvals: [token] resumes by running exactly that call. - Web /chat: /plugin add|ls|rm|on|off|connect, Plugins panel with catalog one-click Add (popup sign-in), tool and approval cards. - MCP: chat_plugins_list / _add (returns connect_url) / _remove; chat tool uses enabled plugins. - CLI: c0mpute chat plugins ls|add|rm|on|off|connect; c0mpute chat shows plugin steps on stderr and asks y/N for destructive tools. - Desktop: Account tab Chat plugins card on the same endpoints. - Extra network guard (no internal hostnames / private literals) via the package's guard hook; catalog hides c0mpute's own MCP entry. - docs/chat-plugins.md Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| const { slug } = await params; | ||
| const url = new URL(req.url); | ||
| const ticket = url.searchParams.get("t"); | ||
| let userId: string | null = ticket ? verifyTicket(ticket, slug) : null; |
| }); | ||
| test("the catalog hides c0mpute's own MCP server", () => { | ||
| const c = catalogFor(); | ||
| expect(c.some((e) => e.url.includes("c0mpute.com"))).toBe(false); |
| } | ||
| if (j.error) throw new Error(j.error.message ?? "stream error"); | ||
| if (j.object === PLUGIN_EVENT_OBJECT) { | ||
| if (j.event && onEvent) onEvent(j.event as PluginEvent); |
ThreatCrush Security Scan14 finding(s) HIGH/CRITICAL: 1 | MEDIUM: 9 | LOW: 4
Snippets are redacted; ThreatCrush never prints matched credential material. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Chat plugins for c0mpute chat (Anthony 2026-11-11: "a plugins system similar to connectors but easier, less hoops ... cli/pwa/mcp/api/desktop"). A chat plugin = a remote MCP server; add it by URL or catalog name, sign-in is automatic, its tools work in chat. Built on
@profullstack/chat-plugins@^0.1.0(published). Named "chat plugins" everywhere so the node plugins (/plugins,module.toml) are untouched.Surfaces
/chat:/plugin add <url|name>,/plugin ls|rm|on|off|connect, a sidebar plugins panel (catalog one-click Add with popup sign-in, URL box, toggles, remove), tool-call/result cards and approve/deny cards for destructive tools.GET/POST /api/v1/chat/plugins,GET/DELETE /api/v1/chat/plugins/<slug>,POST .../<slug>/toggle,GET .../<slug>/connect,GET /api/v1/chat/plugins/callback./api/v1/chat/completionsruns the tool loop whenever a plugin is on (plugins: falseskips;plugin_events: truestreams steps;approvals: [token]resumes)./api/mcp:chat_plugins_list,chat_plugins_add(returns the sign-in link),chat_plugins_remove;chatuses enabled plugins.c0mpute chat plugins ls|add|rm|on|off|connect;c0mpute chatprints plugin steps to stderr and asks y/N for destructive tools.Security
guardhook (no localhost/.local/.internal/metadata names, no private IP literals incl. mapped/NAT64).C0MPUTE_PLUGIN_KEY(set in dev2 app.env and the profullstack c0mpute prod vault), AAD = user|slug, never returned.c0_plugin_statecookie set on the browser at/connect. CLI/desktop/MCP get a one-time?t=link (HMAC, 10 min, account + plugin bound).<tool_result>data, never system; rendered as text only.Billing: each model step is a normal completion through
infernetFor(user.coinpay_sub); max 6 steps per turn.Tests:
plugin-store.test.ts(sealing, store, guard, command parser),plugins.test.ts(tickets, approvals, catalog filter, a full loop against a local MCP server incl. approve/resume and cross-account approval refusal); Rustchat_pluginstests. tsc +next buildclean on vienna (thechat music > node progressfailure is pre-existing on master).Docs:
docs/chat-plugins.md.🤖 Generated with Claude Code