Spaces:
Running
Running
mfn
Your own agent: hosted relay by default (api.mrfake.name/relay), MCP over HTTP snippets, local relay moved to Advanced
e5b3e51 Download src/lib/agentDocs.ts from mrfakename/flow: direct link, hf CLI and curl.
- Browser
- Download file 7.81 kB
-
https://huggingface.co/spaces/mrfakename/flow/resolve/main/src/lib/agentDocs.ts
- Command line
-
hf download hf://spaces/mrfakename/flow/src/lib/agentDocs.ts
-
curl -L -o agentDocs.ts https://huggingface.co/spaces/mrfakename/flow/resolve/main/src/lib/agentDocs.ts
7.81 kB
| import { TOOLS } from "./copilotTools"; | |
| /** Lifetime of a bridge token. Short on purpose: it grants full control of the canvas. */ | |
| export const TOKEN_TTL_MS = 4 * 60 * 60 * 1000; | |
| export const HOSTED_BRIDGE_URL = "https://api.mrfake.name/relay"; | |
| export const DEFAULT_BRIDGE_URL = (import.meta.env.VITE_BRIDGE_URL as string | undefined) || HOSTED_BRIDGE_URL; | |
| /** Default of older builds (local relay); saved settings with it are moved to the hosted relay. */ | |
| export const LEGACY_BRIDGE_URL = "http://127.0.0.1:7433"; | |
| /** The hosted relay speaks MCP over HTTP at /mcp; a local gr-canvas-bridge.mjs speaks MCP on stdio. */ | |
| export const isHostedRelay = (url: string) => url.replace(/\/+$/, "") === DEFAULT_BRIDGE_URL || url.replace(/\/+$/, "") === HOSTED_BRIDGE_URL; | |
| export const appOrigin = () => (typeof location !== "undefined" ? location.origin : "https://gr-canvas.local"); | |
| export const docsUrl = () => `${appOrigin()}/llms.txt`; | |
| export const bridgeScriptUrl = () => `${appOrigin()}/bridge/gr-canvas-bridge.mjs`; | |
| function schemaLine(name: string, schema: Record<string, unknown> | undefined, required: string[]): string { | |
| const props = (schema?.properties ?? {}) as Record<string, { type?: string; description?: string; default?: unknown }>; | |
| const keys = Object.keys(props); | |
| if (!keys.length) return ` ${name}()`; | |
| const args = keys.map((k) => { | |
| const p = props[k]; | |
| const req = required.includes(k) ? "" : "?"; | |
| const desc = p.description ? ` β ${p.description}` : ""; | |
| return ` ${k}${req}: ${p.type ?? "any"}${desc}`; | |
| }); | |
| return ` ${name}(\n${args.join("\n")}\n )`; | |
| } | |
| /** Plain-text tool reference generated from the live tool definitions, so it never drifts. */ | |
| export function toolReference(): string { | |
| return Object.values(TOOLS) | |
| .map((t) => { | |
| const f = t.spec.function; | |
| const params = f.parameters as { properties?: Record<string, unknown>; required?: string[] } | undefined; | |
| return `${schemaLine(f.name, params as Record<string, unknown>, params?.required ?? [])}\n [${t.kind}] ${f.description}`; | |
| }) | |
| .join("\n\n"); | |
| } | |
| /** Tool manifest sent to the relay (and exposed as /tools and MCP tools/list). */ | |
| export function toolManifest() { | |
| return Object.values(TOOLS).map((t) => ({ name: t.spec.function.name, description: t.spec.function.description, kind: t.kind, parameters: t.spec.function.parameters })); | |
| } | |
| export interface DocsOpts { | |
| bridgeUrl: string; | |
| token: string; | |
| expires: number; | |
| } | |
| /** The whole guide an external agent needs: how to connect + tool reference. */ | |
| export function agentDocs(o: DocsOpts): string { | |
| const ttl = Math.max(0, Math.round((o.expires - Date.now()) / 60000)); | |
| return `# Flow β drive the canvas from your own agent | |
| Flow is a node editor where any public Gradio Space on the Hugging Face Hub is a node. | |
| A workflow is a graph: input nodes (in:text, in:image, in:audio, in:video, in:file, in:number, | |
| in:toggle) feed values into Space endpoint nodes (space:owner/name/endpoint). Links go from an | |
| output port to an input port with a compatible type (image, audio, video, file, text, number, | |
| boolean, gallery, json; "any" matches anything). Outputs flow between Spaces as URLs. | |
| You control the canvas that is open in the user's browser tab through a relay: the tab keeps a | |
| connection open to it, and your calls are forwarded into the tab. Keep using the same token; the | |
| tab must stay open. | |
| ## Connect | |
| Relay: ${o.bridgeUrl} | |
| Token: ${o.token} (expires in ~${ttl} min; the user can rotate it in Flow β Copilot β Your own agent) | |
| Docs: ${docsUrl()} | |
| Every request carries the token: Authorization: Bearer ${o.token} | |
| GET ${o.bridgeUrl}/status β {"ok":true,"tab":true,"tools":${Object.keys(TOOLS).length}} (tab=false means the user has not connected the tab) | |
| GET ${o.bridgeUrl}/tools β JSON tool list with JSON-schema parameters | |
| POST ${o.bridgeUrl}/call body {"name":"get_workflow","arguments":{}} | |
| β {"ok":true,"result":...} or {"ok":false,"error":"..."} | |
| ${isHostedRelay(o.bridgeUrl) ? ` POST ${o.bridgeUrl}/mcp MCP (Streamable HTTP, JSON-RPC): initialize, tools/list, tools/call, ping | |
| GET ${o.bridgeUrl}/llms.txt this guide | |
| Slow tools (wait_for_run, run-related calls) can take minutes: keep the request open (up to ~10 min). | |
| ` : ""} | |
| Example: | |
| curl -s ${o.bridgeUrl}/call -H "Authorization: Bearer ${o.token}" -H "content-type: application/json" \\ | |
| -d '{"name":"search_spaces","arguments":{"query":"remove background","limit":5}}' | |
| ## How to work | |
| 1. Call get_workflow first so you know the current node ids and which Spaces are loaded. | |
| 2. To use a Space: search_spaces β pick one that is running (usable=true, most likes) β add_space to load | |
| its endpoints. Read the returned ports before adding nodes. | |
| 3. Build with add_node and connect, using the ids you got back. Set sensible widget values with set_values. | |
| Then call auto_layout so the graph reads left to right with nothing overlapping; use move_nodes for | |
| fine placement (positions and sizes come from get_workflow; x grows right, y grows down). | |
| 4. Only call run_workflow when the user asked for a run: it spends their Hugging Face quota. Then call | |
| wait_for_run and report the outcome. | |
| 5. If something fails, read get_run_status (log + per-node errors) and get_node on the failing node, and | |
| quote the actual error text. | |
| 6. Node types look like "space:owner/name/endpoint" or "in:text". Never invent types; use ones returned by tools. | |
| 7. The user watches the canvas live. Edits are applied immediately, without confirmation, so keep them deliberate. | |
| ## Tools | |
| ${toolReference()} | |
| `; | |
| } | |
| /** Short prompt to paste into any chat agent. */ | |
| export function agentPrompt(o: DocsOpts): string { | |
| return `You can control my Flow workflow canvas (a node editor for Hugging Face Gradio Spaces). | |
| Read the guide first: ${docsUrl()} | |
| Then call tools over HTTP through the relay at ${o.bridgeUrl} with header "Authorization: Bearer ${o.token}": | |
| GET /status, GET /tools, and POST /call with {"name": "<tool>", "arguments": {...}}. | |
| Start with get_workflow. Only run_workflow when I ask (it costs my quota), then wait_for_run.`; | |
| } | |
| /** Claude Code / Cursor / Claude Desktop style MCP config. */ | |
| export function mcpConfig(o: DocsOpts, scriptPath: string): string { | |
| if (isHostedRelay(o.bridgeUrl)) | |
| return JSON.stringify({ mcpServers: { flow: { type: "http", url: `${o.bridgeUrl}/mcp`, headers: { Authorization: `Bearer ${o.token}` } } } }, null, 2); | |
| return JSON.stringify( | |
| { | |
| mcpServers: { | |
| "flow": { | |
| command: "node", | |
| args: [scriptPath, "--mcp", "--port", String(portOf(o.bridgeUrl))], | |
| env: { GR_CANVAS_TOKEN: o.token }, | |
| }, | |
| }, | |
| }, | |
| null, | |
| 2, | |
| ); | |
| } | |
| /** One-line `claude mcp add` for Claude Code. */ | |
| export function claudeCodeCommand(o: DocsOpts, scriptPath: string): string { | |
| if (isHostedRelay(o.bridgeUrl)) return `claude mcp add --transport http flow ${o.bridgeUrl}/mcp --header "Authorization: Bearer ${o.token}"`; | |
| return `claude mcp add flow -e GR_CANVAS_TOKEN=${o.token} -- node ${scriptPath} --mcp --port ${portOf(o.bridgeUrl)}`; | |
| } | |
| export function curlExample(o: DocsOpts): string { | |
| return `curl -s ${o.bridgeUrl}/call -H "Authorization: Bearer ${o.token}" -H "content-type: application/json" \\\n -d '{"name":"get_workflow","arguments":{}}'`; | |
| } | |
| export function portOf(url: string): number { | |
| try { | |
| const u = new URL(url); | |
| return Number(u.port) || (u.protocol === "https:" ? 443 : 80); | |
| } catch { | |
| return 7433; | |
| } | |
| } | |
| export function newToken(): string { | |
| const b = new Uint8Array(24); | |
| crypto.getRandomValues(b); | |
| return "grc_" + btoa(String.fromCharCode(...b)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, ""); | |
| } | |