Skip to content

feat(chat): chat plugins: remote MCP servers as tools in c0mpute chat - #315

Merged
ralyodio merged 1 commit into
masterfrom
feat/chat-plugins
Nov 11, 2026
Merged

ralyodio merged 1 commit into
masterfrom
feat/chat-plugins

Conversation

@ralyodio

Copy link
Copy Markdown
Collaborator

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

  • Web /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.
  • API: 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/completions runs the tool loop whenever a plugin is on (plugins: false skips; plugin_events: true streams steps; approvals: [token] resumes).
  • MCP /api/mcp: chat_plugins_list, chat_plugins_add (returns the sign-in link), chat_plugins_remove; chat uses enabled plugins.
  • CLI: c0mpute chat plugins ls|add|rm|on|off|connect; c0mpute chat prints plugin steps to stderr and asks y/N for destructive tools.
  • Desktop: Account tab, Chat plugins card (and the Chat tab is the web chat).

Security

  • https only; package SSRF guard (DNS resolved, private ranges refused, redirects re-checked, bearer never follows a cross-origin redirect) plus our guard hook (no localhost/.local/.internal/metadata names, no private IP literals incl. mapped/NAT64).
  • Tokens sealed AES-256-GCM under C0MPUTE_PLUGIN_KEY (set in dev2 app.env and the profullstack c0mpute prod vault), AAD = user|slug, never returned.
  • OAuth callback: state must exist server-side for the account AND equal the c0_plugin_state cookie set on the browser at /connect. CLI/desktop/MCP get a one-time ?t= link (HMAC, 10 min, account + plugin bound).
  • Tool results go back as user-role <tool_result> data, never system; rendered as text only.
  • Approvals are HMAC tokens over (account, plugin, tool, exact args), 15 min.
  • c0mpute's own catalog entry (c0mpute.com/api/mcp) is hidden and refused: its tools are native here.

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); Rust chat_plugins tests. tsc + next build clean on vienna (the chat music > node progress failure is pre-existing on master).

Docs: docs/chat-plugins.md.

🤖 Generated with Claude Code

… (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>
@ralyodio
ralyodio merged commit e0413a9 into master Nov 11, 2026
10 of 11 checks passed
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);
@github-actions

Copy link
Copy Markdown

ThreatCrush Security Scan

14 finding(s)

HIGH/CRITICAL: 1 | MEDIUM: 9 | LOW: 4

Severity Rule Location
HIGH js-ssrf-outbound-request apps/web/public/sw.js:24
MEDIUM js-unescaped-html-sink apps/web/src/app/blog/[slug]/page.tsx:53
MEDIUM js-unescaped-html-sink apps/web/src/app/layout.tsx:109
MEDIUM sql-template-interpolation apps/web/src/lib/chat/push.ts:476
MEDIUM sh-remote-script-execution scripts/dev-setup.sh:25
MEDIUM sh-eval-expansion scripts/dev-setup.sh:35
MEDIUM sh-remote-script-execution scripts/install.sh:185
MEDIUM sh-remote-script-execution scripts/install.sh:210
MEDIUM sh-remote-script-execution scripts/install.sh:408
MEDIUM sh-remote-script-execution scripts/install.sh:450
LOW py-ssrf-outbound-request examples/business-strategist-agent/chat.py:37
LOW py-ssrf-outbound-request examples/business-strategist-agent/train.py:54
LOW secret-generic-credential node/crates/c0mpute-core/src/webhook.rs:1171
LOW py-path-traversal node/crates/c0mpute-core/tests/fixtures/fake-infernet-text:29

Snippets are redacted; ThreatCrush never prints matched credential material.

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.

2 participants