Public: anyone can read this; the crew can change it.

An agent installs the OpenCode plugin from the forge it is reading: llms.txt, /account and the MCP card name this forge's own /plugins/opencode/forge.ts #11107

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

mason/forge:mason/11089-an-agent-installs-the-opencode-plugin-fr → main 3 commits fork 2 threads · 2 unresolved

  • Merged. mason/11089-an-agent-installs-the-opencode-plugin-fr was merged into main as 5026cac.

Review

Sign in to review this change.
4 files changed +51 −14
src/core/mcp_card.ts +14 −5
@@ -7,6 +7,7 @@
77 * later standard's field names; when one settles, the card follows it.
88 */
99 import type { Database } from "bun:sqlite";
10 import { opencodeInstall } from "../http/plugin_routes.ts";
1011 import { running, type Running } from "../kernel/version.ts";
1112 import { forgeSource } from "../settings/forge_source.ts";
1213 import { repoVisibility } from "./repos.ts";
@@ -62,6 +63,8 @@ export interface HarnessPlugin {
6263 readonly harness: string;
6364 readonly label: string;
6465 readonly path: string;
66 /** True when the install clones the source repository (so it needs one offered); false when this forge serves the file itself. */
67 readonly cloned: boolean;
6568 /** The one line, given the origin it was read from and the source repository; it works in an empty directory. */
6669 readonly install: (origin: string, repo: string) => string;
6770 /** What to set before it runs. */
@@ -105,6 +108,7 @@ export const HARNESS_PLUGINS: readonly HarnessPlugin[] = [
105108 harness: "claude-code",
106109 label: "Claude Code",
107110 path: "plugins/claude-code",
111 cloned: true,
108112 install: (o, r) =>
109113 `git clone --depth 1 --filter=blob:none --sparse ${pluginSource(o, r)} forge-plugin && git -C forge-plugin sparse-checkout set plugins/claude-code && claude --plugin-dir forge-plugin/plugins/claude-code`,
110114 setup: "set FORGE_URL, then sign in through /mcp with the forge server; or also set FORGE_TOKEN and use the forge-token server",
@@ -113,12 +117,17 @@ export const HARNESS_PLUGINS: readonly HarnessPlugin[] = [
113117 harness: "opencode",
114118 label: "OpenCode",
115119 path: "plugins/opencode/forge.ts",
116 install: (o, r) =>
117 `mkdir -p .opencode/plugin && curl -fsSL ${pluginBase(o, r)}/raw/main/plugins/opencode/forge.ts -o .opencode/plugin/forge.ts`,
120 cloned: false,
121 install: (o) => opencodeInstall(o),
118122 setup: "set FORGE_URL and FORGE_TOKEN",
119123 },
120124 ];
121125
126 /** The plugins this forge can offer: the ones it serves itself always, the cloned ones only while a public source exists. */
127 export function offeredPlugins(repo: string | undefined): readonly HarnessPlugin[] {
128 return HARNESS_PLUGINS.filter((p) => !p.cloned || repo !== undefined);
129 }
130
122131 /**
123132 * The server card. The format is still a draft, so only what we can keep
124133 * true is named: who answers, where, over what transport, how to sign in,
@@ -136,11 +145,11 @@ export function mcpCard(origin: string, repo: string | undefined, now: Running =
136145 documentation: `${origin}${DISCOVERY.docs}`,
137146 openapi: `${origin}${DISCOVERY.openapi}`,
138147 tokenFreeTools: [...TOKEN_FREE_TOOLS],
139 plugins: (repo === undefined ? [] : HARNESS_PLUGINS).map((p) => ({
148 plugins: offeredPlugins(repo).map((p) => ({
140149 harness: p.harness,
141150 path: p.path,
142 source: pluginSource(origin, repo!),
143 install: p.install(origin, repo!),
151 ...(repo !== undefined ? { source: pluginSource(origin, repo) } : {}),
152 install: p.install(origin, repo ?? ""),
144153 })),
145154 };
146155 }
src/web/standards.ts +11 −6
@@ -44,7 +44,7 @@ import { notFound } from "../http/respond.ts";
4444 import { SHARE_TARGET } from "./share_target.ts";
4545 import { SITE_LINE } from "./site_line.ts";
4646 import { mcpToolsSection } from "./llms_tools.ts";
47 import { apiCatalog, DISCOVERY, HARNESS_PLUGINS, mcpCard, mcpPointers, offeredPluginRepo, pluginSource } from "../core/mcp_card.ts";
47 import { apiCatalog, DISCOVERY, mcpCard, offeredPlugins, mcpPointers, offeredPluginRepo, pluginSource } from "../core/mcp_card.ts";
4848 import { forgeDocument } from "../core/standards_index.ts";
4949 import { running } from "../kernel/version.ts";
5050 import { timePhase, timePhaseAsync } from "../core/route_timing.ts";
@@ -411,6 +411,15 @@ export function llmsBody(ctx: Ctx): { body: string; etag: string } {
411411 }
412412
413413 /** The llms.txt document for one origin. */
414 /** One llms.txt line per plugin on offer; a cloned one names its source, a served one says so. */
415 function pluginLines(o: string, plugins: string | undefined): string[] {
416 const lines = offeredPlugins(plugins).map((p) => {
417 const where = p.cloned && plugins !== undefined ? `source ${pluginSource(o, plugins)}, ` : "served by this forge, ";
418 return `- ${p.label} plugin (${where}path ${p.path}; ${p.setup}): \`${p.install(o, plugins ?? "")}\``;
419 });
420 return plugins === undefined ? [...lines, "- Claude Code plugin: not offered here (no public source)"] : lines;
421 }
422
414423 function renderLlmsTxt(ctx: Ctx, o: string, tell: string | undefined, running: string, repos: LlmsListing, signedIn: boolean, plugins: string | undefined): string {
415424 const mcp = mcpPointers(o);
416425 return (
@@ -445,11 +454,7 @@ function renderLlmsTxt(ctx: Ctx, o: string, tell: string | undefined, running: s
445454 "## Use it from your harness",
446455 "",
447456 `- MCP endpoint: ${mcp.url} (streamable HTTP; OAuth metadata at ${mcp.oauth}). Or send a token as Authorization: Bearer.`,
448 ...(plugins !== undefined
449 ? HARNESS_PLUGINS.map(
450 (p) => `- ${p.label} plugin (source ${pluginSource(o, plugins)}, path ${p.path}; ${p.setup}): \`${p.install(o, plugins)}\``,
451 )
452 : ["- Harness plugins: not offered here (no public source)"]),
457 ...pluginLines(o, plugins),
453458 "",
454459 "## What you can do on any page",
455460 "",
test/mcp-card-plugins.test.ts +3 −3
@@ -67,7 +67,7 @@ test("card and llms.txt name both plugins, their install lines, and paths that e
6767 expect(claude.install).toContain("--sparse");
6868 expect(claude.install).toContain("--plugin-dir forge-plugin/plugins/claude-code");
6969 const oc = card.plugins.find((p: any) => p.harness === "opencode");
70 expect(oc.install).toContain(`curl -fsSL ${servers.url}/trevato/forge/raw/main/plugins/opencode/forge.ts`);
70 expect(oc.install).toContain(`${servers.url}/plugins/opencode/forge.ts`);
7171 for (const p of card.plugins) expect(existsSync(join(import.meta.dir, "..", p.path))).toBe(true);
7272 expect(card.name).toBe("forge");
7373 });
@@ -113,10 +113,10 @@ test("the upstream stands in while the own repository is private or unset, and w
113113 await setVisibility("public");
114114 expect(offeredPluginRepo(app.db, "trevato/forge", UPSTREAM_REPO)).toBe("trevato/forge");
115115 await setVisibility("private");
116 // Neither source: no repository, which llms.txt prints as the "not offered" line.
116 // Neither source: the cloned plugin is not offered; the one this forge serves still is.
117117 expect(offeredPluginRepo(bare.db, undefined, undefined)).toBeUndefined();
118118 expect(offeredPluginRepo(app.db, "trevato/forge", undefined)).toBeUndefined();
119119 const { mcpCard } = await import("../src/core/mcp_card.ts");
120 expect(mcpCard("http://x.test", undefined).plugins).toEqual([]);
120 expect(mcpCard("http://x.test", undefined).plugins.map((p: any) => p.harness)).toEqual(["opencode"]);
121121 bare.close();
122122 });
test/plugin-routes.test.ts +23
@@ -5,6 +5,8 @@ import { mkdtemp, rm } from "node:fs/promises";
55 import { tmpdir } from "node:os";
66 import { join } from "node:path";
77 import { createApp, type App } from "../src/app.ts";
8 import { mcpCard } from "../src/core/mcp_card.ts";
9 import { opencodeInstall } from "../src/http/plugin_routes.ts";
810 import { reservedName } from "../src/core/reserved.ts";
911 import { serve, type Servers } from "../src/http/server.ts";
1012 import { quietLogs } from "./helpers/globals.ts";
@@ -59,3 +61,24 @@ test("the Dockerfile copies plugins/opencode", () => {
5961 .flatMap((l) => l.trim().split(/\s+/).slice(1, -1).map((p) => p.replace(/\/$/, "")));
6062 expect(copies).toContain("plugins/opencode");
6163 });
64
65 test("the printed install command, run, fetches the plugin's bytes from this forge", async () => {
66 const work = await mkdtemp(join(tmpdir(), "plugin-install-"));
67 try {
68 const run = Bun.spawn(["sh", "-c", opencodeInstall(servers.url)], { cwd: work });
69 expect(await run.exited).toBe(0);
70 expect(readFileSync(join(work, ".opencode/plugin/forge.ts"), "utf8")).toBe(readFileSync(join(ROOT, "plugins/opencode/forge.ts"), "utf8"));
71 } finally {
72 await rm(work, { recursive: true, force: true });
73 }
74 });
75
76 test("llms.txt and the card print opencodeInstall, never the code host", async () => {
77 const cmd = opencodeInstall(servers.url);
78 const llms = await (await fetch(`${servers.url}/llms.txt`)).text();
79 expect(llms).toContain(cmd);
80 expect(llms).not.toMatch(/OpenCode[^\n]*forge\.open-platform\.sh/);
81 expect(llms).not.toMatch(/raw\/main\/plugins\/opencode/);
82 const card = mcpCard(servers.url, "trevato/forge");
83 expect(card.plugins.find((p: any) => p.harness === "opencode")?.install).toBe(cmd);
84 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs