From 7cb0e9084b741f28a698a868f9b040d94278b292 Mon Sep 17 00:00:00 2001 From: Web Artisan Date: Thu, 17 Sep 2026 15:13:02 +0300 Subject: [PATCH] feat(site): agent use cases --- apps/site/src/components/UseCaseCard.astro | 33 +++++++++++ apps/site/src/content.config.ts | 1 + apps/site/src/content/use-cases/3x-ui.mdx | 51 ++++++++++++++++ .../src/content/use-cases/dokploy-agent.mdx | 51 ++++++++++++++++ apps/site/src/content/use-cases/openapi.mdx | 58 +++++++++++++++++++ apps/site/src/pages/index.astro | 31 ++++++---- apps/site/src/pages/use-cases/index.astro | 53 ++++++++++------- docs/endpoints.md | 7 ++- 8 files changed, 248 insertions(+), 37 deletions(-) create mode 100644 apps/site/src/components/UseCaseCard.astro create mode 100644 apps/site/src/content/use-cases/3x-ui.mdx create mode 100644 apps/site/src/content/use-cases/dokploy-agent.mdx create mode 100644 apps/site/src/content/use-cases/openapi.mdx diff --git a/apps/site/src/components/UseCaseCard.astro b/apps/site/src/components/UseCaseCard.astro new file mode 100644 index 0000000..170af9a --- /dev/null +++ b/apps/site/src/components/UseCaseCard.astro @@ -0,0 +1,33 @@ +--- +import { ArrowRight } from "@lucide/vue"; +import type { CollectionEntry } from "astro:content"; + +interface Props { + entry: CollectionEntry<"useCases">; + detailed?: boolean; +} + +const { entry, detailed = false } = Astro.props; +--- + + + {entry.data.client} + {entry.data.title} + + {entry.data.description} + + { + detailed && ( + + Read + + + ) + } + diff --git a/apps/site/src/content.config.ts b/apps/site/src/content.config.ts index b55e64c..82d2635 100644 --- a/apps/site/src/content.config.ts +++ b/apps/site/src/content.config.ts @@ -13,6 +13,7 @@ const useCases = defineCollection({ title: z.string(), description: z.string(), client: z.string(), + group: z.enum(["client", "agent"]).default("client"), order: z.number(), keywords: z.array(z.string()).default([]) }) diff --git a/apps/site/src/content/use-cases/3x-ui.mdx b/apps/site/src/content/use-cases/3x-ui.mdx new file mode 100644 index 0000000..c723b38 --- /dev/null +++ b/apps/site/src/content/use-cases/3x-ui.mdx @@ -0,0 +1,51 @@ +--- +title: A VPN panel your agent operates +description: 3x-ui publishes an OpenAPI spec and issues bearer tokens. Load it into the OpenAPI MCP Server behind Junctio and ask for a client, a limit and a link. +client: 3x-ui +group: agent +order: 8 +keywords: ["3x-ui api mcp", "3x-ui automation claude", "xray panel agent", "mcp vpn panel"] +--- + +3x-ui already has everything this needs: a REST API under `/panel/api/`, a generated OpenAPI document, and bearer tokens you create in the panel. So there is no integration to write. One upstream server in Junctio, pointed at the spec, and the panel becomes a set of tools your agent can call from Claude Desktop. + +What that buys you: ask for a client on an inbound with thirty days and fifty gigabytes, get the subscription link back. Ask which clients are near their limit. Reset the traffic on one, disable an expired one. The panel is still the source of truth and still where you look when something is wrong. + +## The setup + +Make a token first, in Settings, Security, API Token. It is a full-admin credential, so treat it like the panel password. + +Then add one server with runtime `uvx` and the argument `awslabs.openapi-mcp-server@latest`, carrying this environment: + +``` +API_NAME=3x-ui +API_BASE_URL=https://panel.example.com +API_SPEC_URL=https://docs.sanaei.dev/openapi.json +AUTH_TYPE=bearer +AUTH_TOKEN= +``` + +The spec declares its server as `/`, so `API_BASE_URL` carries the real origin, including the base path if your panel runs under one. If the panel sits on the same host as the gateway and is reached over a private address, add `ALLOW_PRIVATE_NETWORKS=true`. Pinning a copy of the spec on the gateway's volume and pointing `API_SPEC_PATH` at it is the safer choice for a panel you upgrade rarely. + +The rest is the usual three clicks: a `vpn` namespace holding this one server, an endpoint with `oauth` auth, then the endpoint URL added as a custom connector in Claude Desktop. The consent screen asks for your admin password once. + +## Instructions worth writing + +The spec describes shapes, not conventions, and the conventions are what an agent gets wrong. Put them in the namespace instructions, by hand in the Namespaces page or through the [management MCP](/docs/management-mcp/): + +- Which inbound new clients go on, when there is more than one. +- The naming convention for clients, and the fact that the identifier has to be unique. +- The units the panel actually uses. Expiry is a millisecond timestamp and the traffic cap is counted in bytes regardless of what the field is called, and an agent that guesses will create a client that expired in 1970. +- How a subscription link is composed from the panel's subscription settings, so the agent hands over a URL rather than a raw config. + +[Endpoints](/docs/endpoints/) explains how that text reaches the client and how a per-tool description override works when one operation needs more than the namespace text can carry. + +## Keeping it safe + +The token is full control of the panel and it stays in the gateway, encrypted with `JUNCTIO_SECRET`. The client gets tools, never the credential. Hide the operations that have no business being one sentence away from a chat window: backup and restore, restart, anything under settings or Xray configuration. An endpoint with `oauth` auth means every client passed the consent screen, and revoking that client in Settings ends its access immediately. + +The panel controls who reaches the network behind it. Whether that is allowed, and for whom, is your jurisdiction's question and not the gateway's. + +## Related + +[Any REST API as MCP tools](/use-cases/openapi/) for the general pattern and every environment variable, [Endpoints](/docs/endpoints/) for tool overrides and auth modes, [a custom connector for claude.ai](/use-cases/claude-ai-connector/) for the OAuth flow. diff --git a/apps/site/src/content/use-cases/dokploy-agent.mdx b/apps/site/src/content/use-cases/dokploy-agent.mdx new file mode 100644 index 0000000..501921c --- /dev/null +++ b/apps/site/src/content/use-cases/dokploy-agent.mdx @@ -0,0 +1,51 @@ +--- +title: A Lovable alternative on a $5 VPS +description: Your agent writes the site and deploys it through the Dokploy MCP behind your gateway. No preview pane, but the code is yours and the bill is a VPS. +client: Dokploy +group: agent +order: 6 +keywords: ["lovable alternative self-hosted", "dokploy mcp claude", "deploy from claude.ai", "ai agent deploy vps"] +--- + +Three pieces, all of which you either already pay for or can rent for the price of a coffee: Dokploy on a small VPS, Junctio next to it, and the Claude or OpenAI subscription in your editor. The gateway holds the Dokploy MCP server and publishes it to your client over OAuth, so the agent that writes the code is also the agent that ships it. + +What this is not: there is no preview pane, no visual editor, no click-to-edit on a rendered page. The agent reads build logs, not screenshots. What it is instead: the code lives in your repository, the server is yours, nothing is locked behind a seat price, and the same gateway serves every other tool the agent needs. + +## Prerequisites + +- Dokploy on the VPS, and Junctio deployed as a Compose service inside it. [Dokploy](/docs/deploy/dokploy/) has the exact YAML, the network it has to join and the Domains tab settings. +- TLS on the gateway's domain, `JUNCTIO_BASE_URL` set to it and `JUNCTIO_TRUST_PROXY=true` behind Traefik. OAuth does not work over plain HTTP. +- A Dokploy API key from Settings, Profile, the API/CLI section. + +## The setup + +1. **Add the server.** Runtime `npx`, arguments `-y` and `@dokploy/mcp`, environment `DOKPLOY_URL` set to the panel's URL without a path and `DOKPLOY_API_KEY` set to the key. The key is stored encrypted with `JUNCTIO_SECRET` and is never handed to a client. +2. **Create a namespace and cut it down.** The package announces upwards of six hundred tools across every corner of the Dokploy API. An agent does not need SSO, user management, registries or cluster settings, and a tool list that size costs context on every request. Keep projects, applications, Compose, domains, environments, deployments and logs; hide the rest. Hiding is per namespace membership and it is real: a hidden tool is not callable, not merely absent from the listing. +3. **Write the instructions.** The namespace description is what the client reads before it calls anything. Name the project the agent should deploy into, the domain pattern for new applications, and the rule that it asks before deleting or stopping something. [Endpoints](/docs/endpoints/) explains how that text is composed with what each upstream announced. +4. **Create the endpoint.** Auth `oauth` for claude.ai and Claude Desktop, `any` if a Claude Code session on your laptop should also reach it with an API key. +5. **Connect the client.** Add the endpoint URL as a custom connector, approve the consent screen, and the client holds a token that refreshes itself from there on. + +## Two ways to write the code + +**With a filesystem.** Claude Code or Codex on your machine writes the files, commits and pushes, then calls `dokploy__application-deploy` through the gateway. The editor does what editors are good at and the gateway does the deployment. + +**Without one.** claude.ai and Claude Desktop have no filesystem, so put a GitHub MCP server in the same namespace. The agent commits through GitHub and deploys through Dokploy, and both arrive at the client as one endpoint behind one consent screen. This is the configuration that reads most like a hosted app builder: you describe a page in a chat window and a URL comes back. + +## A session, roughly + +Ask for a landing page for a client. The agent writes the files and pushes them. It calls the create-application tool with the repository and branch, attaches a domain, triggers a deployment, then polls the deployment tool until the build finishes. If the build fails it reads the log, fixes the Dockerfile or the build command, and deploys again. You see each tool call by its prefixed name, `dokploy__application-deploy` and friends, and every one of them is written to the gateway's request log with the key that made it. + +## Where it stops + +- **No live preview.** The agent cannot see the rendered page. Give it a screenshot yourself, or add a Playwright server to the namespace and let it take one. +- **Builds take what they take.** A small VPS building a Next.js image is not instant, and the agent waits with you. +- **Frameworks are Dokploy's business.** Whatever Nixpacks detects, or whatever your Dockerfile says. Nothing here changes that. +- **Rollbacks are manual in practice.** The tools exist, but knowing which deployment to go back to is still your call. + +## Keeping it safe + +The Dokploy key is full control over that panel, which means the host. It lives in the gateway, encrypted, and the agent gets a set of tools rather than the key itself. Bind the endpoint to OAuth so every client authorizes through the consent screen, and revoke the client in Settings when the work is done; its tokens die with it. If you also switch on the [management MCP](/docs/management-mcp/), remember it can add stdio servers, which is equivalent to shell access on the gateway host. + +## Related + +[Dokploy](/docs/deploy/dokploy/) for the deployment itself, [Endpoints](/docs/endpoints/) for auth modes and tool overrides, [a custom connector for claude.ai](/use-cases/claude-ai-connector/) for the OAuth side in detail. diff --git a/apps/site/src/content/use-cases/openapi.mdx b/apps/site/src/content/use-cases/openapi.mdx new file mode 100644 index 0000000..2d14d93 --- /dev/null +++ b/apps/site/src/content/use-cases/openapi.mdx @@ -0,0 +1,58 @@ +--- +title: Any REST API as MCP tools, in an evening +description: Point the OpenAPI MCP Server at a spec, let an agent write the tool descriptions through the management MCP, hand the client one endpoint. +client: OpenAPI +group: agent +order: 7 +keywords: ["openapi to mcp", "rest api as mcp server", "awslabs openapi-mcp-server", "mcp gateway openapi"] +--- + +The OpenAPI MCP Server from AWS Labs reads an OpenAPI document and turns every operation in it into a tool. That is the whole trick, and on its own it produces tools that are exactly as good as the summaries in the spec, which is to say usually not good enough for an agent to use well. + +Behind Junctio it becomes something better. The gateway runs the Python process and keeps the API credential, so the client never sees either. The tool descriptions and the namespace instructions are rewritten in the gateway, by hand or by an agent, and the upstream spec is left untouched. What reaches the client is a Streamable HTTP endpoint with a curated set of tools. + +## The setup + +Add a server with runtime `uvx` and one argument, `awslabs.openapi-mcp-server@latest`. The environment does the rest: + +| Variable | Meaning | +|---|---| +| `API_NAME` | Short name for the API. It shows up in tool names and prompts. | +| `API_BASE_URL` | Where requests go. The spec's own server URL is often relative or wrong. | +| `API_SPEC_URL` | URL of the OpenAPI document. Use `API_SPEC_PATH` instead for a copy on the gateway's volume. | +| `AUTH_TYPE` | `none`, `bearer`, `basic` or `api_key`. | +| `AUTH_TOKEN` | The bearer token, when `AUTH_TYPE` is `bearer`. | +| `AUTH_API_KEY`, `AUTH_API_KEY_NAME`, `AUTH_API_KEY_IN` | The key, the parameter name and whether it rides in a header, the query string or a cookie. | +| `AUTH_USERNAME`, `AUTH_PASSWORD` | Basic credentials. | +| `INCLUDE_TAGS`, `EXCLUDE_TAGS` | Cut a large spec down at the source, before it becomes tools. | +| `ALLOW_PRIVATE_NETWORKS` | Needed when the API is on the same host or Docker network as the gateway. | + +From there it is an ordinary upstream: put it in a namespace, create an endpoint, connect a client. [Upstream servers](/docs/upstream-servers/) covers the runtime side, including why `uvx` needs an exec-capable `TMPDIR` and why the image already gives it one. + +## Let the agent finish the job + +This is the part a bare OpenAPI server cannot do. Connect the [management MCP](/docs/management-mcp/) from Claude Code or Codex and ask it to work on the namespace you just created: + +- List the tools and read what each one announces. Specs written for humans leave summaries empty or cryptic. +- Rewrite the descriptions that say nothing. A per-namespace override replaces what the upstream announced, and clearing it goes back to the original. +- Hide the operations that should never be called from a chat window: anything that deletes in bulk, anything that rotates credentials. +- Write the namespace instructions. The conventions that are obvious to you and invisible in the spec go here: which identifiers to use, which fields are in milliseconds, the order operations are normally done in. The `preview_namespace_instructions` tool returns the composed text so the agent can check its own work. + +Everything it writes is stored in the gateway. Replace the upstream later and the wording survives. + +## What this fits + +- **A self-hosted shop.** Medusa, Saleor and WooCommerce all publish a spec. The agent creates products, edits prices and answers questions about stock, in the same session where it writes the storefront. +- **A CMS or an internal admin API.** The one with twelve endpoints that nobody wants to build a client for. +- **A panel.** [3x-ui](/use-cases/3x-ui/) is the worked example: a VPN panel the agent operates through its own spec. + +## Where it stops + +- **One spec, one server.** `ADDITIONAL_SPECS` takes more, but a server per API keeps the prefixes and the audit trail legible. +- **No OAuth to the upstream.** This server speaks bearer, basic and API key. When the API behind it needs an OAuth dance, use a native MCP server and Junctio's own [upstream OAuth](/docs/upstream-servers/), which refreshes tokens before they expire. +- **A bad spec makes bad tools.** If the document lies about its parameters, the agent inherits the lie. Overrides fix descriptions, not schemas. +- **Large specs cost context.** Three hundred tools in one namespace is a slow client. Use the tag filters, then hide what survives them. + +## Related + +[Upstream servers](/docs/upstream-servers/) for runtimes and credentials, [Endpoints](/docs/endpoints/) for prefixes, overrides and instructions, [Management MCP](/docs/management-mcp/) for the agent that does the curation. diff --git a/apps/site/src/pages/index.astro b/apps/site/src/pages/index.astro index 8ae1b23..b86f0d0 100644 --- a/apps/site/src/pages/index.astro +++ b/apps/site/src/pages/index.astro @@ -3,11 +3,14 @@ import Base from "@/layouts/Base.astro"; import CopyCommand from "@/components/CopyCommand.vue"; import GatewayFlow from "@/components/GatewayFlow.vue"; import TokenTimeline from "@/components/TokenTimeline.vue"; +import UseCaseCard from "@/components/UseCaseCard.astro"; import { SITE } from "@/lib/site"; import { ArrowRight, Box, EyeOff, KeyRound, Layers, RefreshCw, ShieldCheck, Terminal } from "@lucide/vue"; import { getCollection } from "astro:content"; const useCases = (await getCollection("useCases")).sort((a, b) => a.data.order - b.data.order); +const clientCases = useCases.filter((entry) => entry.data.group === "client"); +const agentCases = useCases.filter((entry) => entry.data.group === "agent"); const features = [ { @@ -164,6 +167,21 @@ const jsonLd = { +
+
+
+

Give the agent hands

+

+ The gateway is the OAuth door for claude.ai, Claude Desktop, Codex and Claude Code. Behind it, anything with + an API becomes tools the agent can call: a deploy platform, a shop, a VPN panel. +

+
+
+ {agentCases.map((entry) => )} +
+
+
+
@@ -174,18 +192,7 @@ const jsonLd = {
- { - useCases.slice(0, 6).map((entry) => ( - - {entry.data.client} - {entry.data.title} - {entry.data.description} - - )) - } + {clientCases.map((entry) => )}
diff --git a/apps/site/src/pages/use-cases/index.astro b/apps/site/src/pages/use-cases/index.astro index 8540cf6..2e3d97d 100644 --- a/apps/site/src/pages/use-cases/index.astro +++ b/apps/site/src/pages/use-cases/index.astro @@ -1,38 +1,47 @@ --- import Base from "@/layouts/Base.astro"; -import { ArrowRight } from "@lucide/vue"; +import UseCaseCard from "@/components/UseCaseCard.astro"; import { getCollection } from "astro:content"; const entries = (await getCollection("useCases")).sort((a, b) => a.data.order - b.data.order); + +const groups = [ + { + id: "client", + title: "Connect a client", + text: "One page per client and per way of hosting it: the endpoint, the auth mode, and what the gateway does for you once it is in place." + }, + { + id: "agent", + title: "Give the agent hands", + text: "The gateway is the OAuth door for claude.ai, Claude Desktop, Codex and Claude Code. Behind it, anything with an API becomes tools the agent can call." + } +]; ---

Use cases

- Each page is one setup, end to end: the client, the endpoint, the auth mode, and what the gateway does for you - once it is in place. + Each page is one setup, end to end: what you connect, how it authenticates, and where it stops.

- + { + groups.map((group) => ( +
+

{group.title}

+

{group.text}

+
+ {entries + .filter((entry) => entry.data.group === group.id) + .map((entry) => ( + + ))} +
+
+ )) + }
diff --git a/docs/endpoints.md b/docs/endpoints.md index 9625c93..17fd684 100644 --- a/docs/endpoints.md +++ b/docs/endpoints.md @@ -42,7 +42,8 @@ Verified by hand before each release. | Client | Version | Status | |---|---|---| -| Claude Code | | pending | -| Codex | | pending | +| Claude Code | | verified, API key and OAuth | +| Codex | | verified, API key and OAuth | | Cursor | | pending | -| claude.ai connector | | implemented, not yet verified against the live service | +| Claude Desktop | | verified, OAuth | +| claude.ai connector | | verified, OAuth |