If your service has a URL, an agent can visit it. Portal is the minimal HTTP contract for agent-accessible services. Two endpoints. No install. No SDK required.
See it work · Quickstart · Spec v0.1.4 · Benchmark · Adopter debrief · Roadmap
# 1. Discover — read the manifest
curl https://demo.visitportal.dev/portal# 2. Call — invoke a tool
curl -X POST https://demo.visitportal.dev/portal/call \
-H 'content-type: application/json' \
-d '{"tool":"top_gainers","params":{"limit":3}}'No client library required. Works from bash, Python urllib, any fetch.
If your service has a URL, an agent can visit it. Portal is the minimal HTTP contract for agent-accessible services. Two endpoints. No install. No SDK required.
Use Portal when MCP is too heavy and REST is too dumb. A visiting agent opens the Portal, reads a single compact manifest once per visit, calls a tool, and leaves. No preloaded schemas, no ongoing residue, no install on the visitor side.
| Tier | Protocol | Use case |
|---|---|---|
| 1 | Portal | Drive-by HTTP visits. Stateless. No install. |
| 2 | MCP | Installed stateful tools. |
| 3 | A2A | Multi-agent coordination. |
Portal for drive-by visits. MCP for installed tools. A2A for agent coordination. They compose. An MCP server can be wrapped as a Portal in a thin adapter (planned — packages/mcp-adapter). A Portal visit can upgrade to an A2A task when a job needs lifecycles. The base is neutral and unowned.
pnpm install
bash scripts/demo.sh # ~6 s end-to-end: boots Portal, visits it, leavesRequires Node 22+, pnpm 10+, and a Unix-like shell (
scripts/demo.shuses bash idioms — WSL2, Git Bash, macOS, or Linux). A PowerShell equivalent is queued for v0.1.5.
curl https://demo.visitportal.dev/portal
curl -X POST https://demo.visitportal.dev/portal/call \
-H 'content-type: application/json' \
-d '{"tool":"top_gainers","params":{"limit":3}}'import { visit } from "@visitportal/visit";
// Production:
const portal = await visit("https://demo.visitportal.dev/portal");
// Or against a local reference Portal:
// const portal = await visit("http://localhost:3075/portal", { allowInsecure: true });
const top = await portal.call("top_gainers", { limit: 3 });
// { ok: true, result: { repos: [...] } }Visitors are fire-and-forget — no close() or lifecycle. When you're done, drop the reference and the garbage collector handles the rest.
Add two routes to your existing HTTP server:
// GET /portal — your manifest
app.get("/portal", (_req, res) => res.json(manifest));
// POST /portal/call — dispatch to your existing functions
app.post("/portal/call", async (req, res) => {
const { tool, params } = req.body;
const result = await dispatch(tool, params);
res.json({ ok: true, result });
});import { runSmokeConformance } from "@visitportal/spec";
const report = await runSmokeConformance("https://your-service.com/portal");
// { target, manifestOk, manifestErrors, notFoundOk, notFoundDetail }Full adopter guide: docs/quickstart-provider.md.
A Portal visit is five steps: arrive, read, call, use, leave. The manifest enters the LLM's context only for the duration of the visit; the session end is a clean drop. No per-connection state on the server, no residue on the client.
Full technical flow in docs/architecture.md. One-page spec in docs/spec-v0.1.4.md.
| Package | Version | Purpose |
|---|---|---|
@visitportal/spec |
0.1.4 · published on npm |
JSON Schema, 30 conformance vectors, ajv + zero-dep lean validator, smoke runner |
@visitportal/visit |
0.1.4 · hackathon-week, run from clone |
TypeScript visitor SDK — visit(url) → Portal |
@visitportal/cli |
0.1.4 · hackathon-week, run from clone |
visit-portal info | call | conformance |
@visitportal/bench |
— | Reproducible MCP-vs-Portal benchmark, Anthropic count_tokens |
reference/trending-demo |
— | Reference Portal ("Star Screener"), Hono, 3 tools, frozen 30-repo snapshot |
packages/visit/py |
stub | Python SDK (v0.2) |
packages/mcp-adapter |
stub | Wrap MCP servers as Portals (v0.2) |
web/ |
— | visitportal.dev landing + docs |
Import direction is strictly downhill — upper layers import from lower, never the reverse. Base Portal packages never pull AGP / ClawPulse / AGNT / ERC-8004; those are optional Portal Extensions documented in docs/extensions/.
Portal was independently adopted by a production service (Star Screener) in ~2h15m of engineering time. The protocol itself survived first contact with reality — 8/8 conformance checks passed on the first green run. Of that 2h15m, ~25 minutes was avoidable friction (packaging, naming) — all addressed in v0.1.1.
"The core spec is solid. The adopter onramp is what needs work." — first production adopter, April 2026
Full debrief: docs/ADOPTER-DEBRIEF.md.
All numbers reproducible from a clean clone. Source of truth: packages/bench/results/tokens-matrix-v1.json.
| Tool count | MCP (median input tokens) | Portal | MCP : Portal |
|---|---|---|---|
| 10 | 1,956 | 172 | 11.4× |
| 50 | 7,343 | 172 | 42.7× |
| 100 | 13,929 | 172 | 81.0× |
| 400 | 54,677 | 172 | 317.9× |
MCP cost scales linearly at ~137 tokens per preloaded tool. Portal stays flat at 172 tokens regardless of catalog size. Sonnet 4.5 and Opus 4.5 return byte-identical token counts (same tokenizer).
export ANTHROPIC_API_KEY=sk-ant-...
BENCH_MODE=count_tokens_only pnpm --filter @visitportal/bench bench
# 48 cells via Anthropic's count_tokens API in ~20 s, ≈ $0.10 totalMethodology: packages/bench/METHODOLOGY.md.
Two endpoints (GET /portal, POST /portal/call). One manifest. A five-code error enum (NOT_FOUND, INVALID_PARAMS, UNAUTHORIZED, RATE_LIMITED, INTERNAL). Dual params form — simple sugar plus an escape hatch for full JSON Schema. One printed page of core + appendices A–E (examples, versioning, CORS, rate-limit defaults, alternate discovery draft).
Explicit non-goals for v0.1: task lifecycles, stateful sessions, server-initiated messages, streaming, multi-agent choreography. Those live in MCP / A2A, or arrive as Portal Extensions (PE-001 verified identity, PE-002 x402 micropayments, …).
Full spec: docs/spec-v0.1.4.md.
pnpm -r build # strict tsc across every package
pnpm -r test # spec 30 + bench 65 + visit 14 + cli 6 + ref 6 = 121 tests
pnpm --filter @visitportal/visit size # enforce SDK bundle size (limit 15 kB gzipped)
pnpm conformance <url> # live-validate any v0.1 PortalVerification standard: every claim on visitportal.dev is traceable to a JSON artifact in packages/bench/results/ or a single curl. If a measurement disagrees with the one-pager, the one-pager updates — not the measurement.
HTTP-native reframe, .well-known/portal.json alternate discovery, PE-002 paid-tools draft.
Second-wave hardening: /api/visit rate limit (Upstash), reference-Portal rate limit + CORS + status codes, full defensive security headers, Fly scaling, visitor-SDK hardening (size cap, HTTPS, retry, onEvent), installer tarball SHA verification, Biome lint, dev-dep CVE patches.
- Every
package.jsondeclares"license": "Apache-2.0"
-
@visitportal/specpublished on npm (Apache 2.0 + CC0) - Normative CORS appendix + SHOULD-level rate-limit defaults (spec Appendix C, D)
-
call_endpointtightened to HTTPS-only with loopback escape -
runConformance→runSmokeConformance+validateAgainstVectors()for offline full-suite checking - SSRF hardening on
/api/visit(ipaddr.js+ DNS resolution) - First-adopter debrief published
- Windows shell requirement documented for
scripts/demo.sh
- Relative
call_endpointresolution (kills a class of copy-paste bugs) -
paramsSchema(JSON Schema 2020-12) alongside sugarparams - Framework quickstarts: Next.js App Router, Hono, FastAPI, Express
- PowerShell
demo.ps1
- PE-002 paid tools — implementation (draft in
docs/pe-002-paid-tools-draft.md) -
@visitportal/provider— one-line provider helper -
@visitportal/x402-adapter— make any x402 provider Portal-discoverable in 50 LOC - MCP → Portal adapter (implementation of the
packages/mcp-adapterstub) - Python visitor SDK
-
@visitportal/clipublished to npm as global binary - Pagination envelope (
{ ok, result, next_cursor })
Full roadmap: docs/ROADMAP.md.
Portal is an open standard. Contributions welcome — spec proposals, SDKs in new languages, reference adopters, docs improvements.
pnpm install && pnpm -r build && pnpm -r testmust go green on a clean clone.- One concern per PR. Hard ceiling: 400 lines of net change.
- Spec changes bump the spec version and go through a proposal in
docs/proposals/. - No
AGP/ClawPulse/AGNT/8004imports in base Portal packages — those are extensions.
Full guide: CONTRIBUTING.md · Security policy: SECURITY.md.
Dual-licensed. Code under Apache 2.0. Spec documents + conformance/vectors.json under CC0 1.0 (public domain). See LICENSE.
Built with Opus 4.7 for the Claude Code "Built with Opus 4.7" hackathon, April 2026, by Mirko Basil Dölger (@0motionguy).
The Portal spec is open, unowned, and designed to complement MCP / A2A / Skills — not compete with them. MCP is the foundation.