portego
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@portegoList my uploaded HTML artifacts"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
portego
A single TypeScript application. Hono serves the API, and Vite builds the React client. Bun runs both.
Requirements
Bun 1.4 or later
Node.js 24 (see
.nvmrc) if you run editor tooling outside Bun
Related MCP server: publish-artifacts-mcp
Setup
bun install
cp .env.example .envFill in .env. SESSION_SECRET can stay empty in development, but the
admission policy cannot: the server refuses to start when no one could be
admitted. See docs/authentication.md.
Create the database schema:
bun run migrateDevelopment
bun run devThis starts two processes, plus the local issuer described below when .env
points at it:
Hono on
http://127.0.0.1:3000Vite on
http://127.0.0.1:5173
Open the Vite URL. Vite forwards /api, /auth, and /healthz to Hono, so
client code uses the same paths in development and in production.
Signing in without a real provider
A local OpenID Connect issuer signs any visitor in as one fixed person. Point
.env at it:
AUTH_PROVIDERS=oidc
OIDC_ISSUER_URL=http://127.0.0.1:9876
OIDC_CLIENT_ID=test-client-id
OIDC_CLIENT_SECRET=test-client-secret
AUTH_ALLOW_ALL_AUTHENTICATED=truebun run dev sees the loopback issuer URL and starts the issuer before the
server, which reads the discovery document at startup and skips the provider
when it cannot. For bun run dev:server or bun run migrate on their own,
run bun run dev:issuer first. DEV_ISSUER_EMAIL and DEV_ISSUER_NAME
change the person it signs in.
Production
bun run build
bun run startbuild writes the client to dist/client and the bundled server to
dist/server. start runs the bundle with NODE_ENV=production, serves the
built client, and answers the API on the same port.
Startup fails with a message naming the variable when a required production value is absent.
docs/deployment.md covers a single-host deployment: nginx, the systemd units, certificates, and the deploy and rollback commands.
Commands
Command | Purpose |
| Start the API and the client dev server |
| Build the client and the server for release |
| Apply the database schema |
| Compare artifact metadata with files on disk |
| Run the production build |
| Type-check the whole project |
| Lint and check formatting |
| Apply the safe lint and format fixes |
| Run the server and component tests |
| Run the server tests |
| Run the component tests, with a DOM |
| Run the browser tests |
| Run every suite the way CI does |
Environment variables
Name | Default | Notes |
|
|
|
|
| Shown on the sign-in page and by the OAuth consent screen. |
|
| Loopback by default. A reverse proxy is the public listener. |
|
| |
|
| Built client directory. Must be a relative path. |
| none | At least 32 characters. Required when |
|
| Public origin. OAuth callback URLs are built from it. Required when |
|
| Origin serving artifact previews. Must be a different host from |
|
| SQLite and artifact files. Production uses |
|
| Largest upload accepted, in bytes (5 MiB). |
| none | Enabled provider ids, comma separated. Known ids: |
| none | Domains admitted after authentication. |
|
| Admit every authenticated identity. |
| none | Required when |
| none | Required when |
| none | Workspace domain, enforced against the id token |
| none | Issuer or discovery URL. Required when |
| none | Required when |
|
| Scopes the OIDC provider is asked for. |
|
| What the sign-in page calls the OIDC provider. |
|
| Let any caller register an OAuth client. |
Layout
src/server/ Hono app, environment parsing, entry point
src/server/auth/ Identity providers, admission policy, session middleware
src/server/artifacts/ Artifact service, HTTP routes, upload validation
src/server/markdown/ HTML to Markdown conversion and its cache
src/server/mcp/ MCP tools, endpoint, and token principal mapping
src/server/preview/ Preview tokens, isolated content responses
src/server/storage/ Artifact metadata, file storage, migrations
src/web/ React client, views, and Vite entry point
deploy/ systemd units and the nginx server configuration
docs/ Design and operation notes
scripts/ Deployment and test entry pointsAuthentication
Sign-in uses Better Auth. A deployment chooses which identity providers to enable and, separately, which authenticated identities to admit. A deployment can enable Google and admit verified Workspace accounts from one domain, or enable a generic OpenID Connect provider instead, or as well.
docs/authentication.md covers the configuration, the Google setup and callback URLs, the cookie and CSRF behavior, and how to add a provider without changing application code.
Storage
Artifact metadata is in SQLite and the uploaded HTML is on disk, both under
DATA_DIR. Uploads are written to a temporary file and linked into place, so a
failed upload leaves neither a partial file nor a row pointing at nothing.
docs/storage.md covers the data model, the write sequence,
migrations, and the reconciliation report.
Checks required to merge
Every pull request and every push to main runs
.github/workflows/ci.yml. Every job has to pass:
Lint, types, tests, build
bun install --frozen-lockfile, so CI installs what the lockfile saysbun run lint(Biome: lint and formatting)bun run typecheckbun run buildbun run test:ci, which runs the server, component, and browser suites
Dependency audit
bun audit --audit-level=high fails the build on a high or critical advisory.
Everything below that is reported and left to a person to judge. Clearing an
advisory means upgrading, or recording why it does not apply with an explicit
--ignore and a comment in the workflow.
Secrets and deployment details
gitleaks scans the file contents of every commit, the commit messages, and the pull request title and description with the rules in .gitleaks.toml. Besides credentials, the rules reject IP addresses outside the documentation ranges, hosting provider names, and links to agent sessions. Run the same rules before each commit:
brew install gitleaks # or another package manager
git config core.hooksPath scripts/git-hooksbun run test:ci fails when a test fails and when a test is skipped. A
suite that quietly stops running a test is the same problem as a suite that
never had it.
The browser tests need Chromium once:
bun x playwright install chromiumThey run the application in the test process, on a real port, serving the built client. Sign-in uses the same offline provider the server tests use, so nothing in CI reaches Google, and no test can touch a deployment: every suite builds its own temporary directory and database. docs/testing.md describes the suites and the hostile-artifact cases.
Web client
Two views. The gallery lists artifacts as cards and keeps its search term in the URL, so a link reproduces what the sender was looking at. The detail page shows the metadata, an isolated preview, a source download, and a copy-link action. Uploading is a dialog that takes a dropped or chosen file, confirms its name and size, and reports what the server refused when it refuses.
Component tests need a DOM, so bun run test:web registers happy-dom first.
bun test on its own runs the server tests only.
Artifact API
GET, POST under /api/artifacts covers listing, upload, metadata, and
source download, and every route there needs a session. POST /api/uploads
takes a signed upload ticket instead, for an MCP client sending a file. The
rules live in a transport-independent service that the MCP tools call
directly.
docs/api.md lists the endpoints, the shared error codes, and the
upload limits.
Status, archiving, and comments
An artifact is open or solved, and can be archived separately, so a solved artifact can also be archived. The gallery filters on both, archived artifacts stay out of it unless asked for, and their links keep working. Comments are append-only: they cannot be edited, and only their author can remove one. Every change records who made it. See docs/collaboration.md.
Reading an artifact as text
An artifact can be read as Markdown through the API, the detail page, and MCP. The HTML is parsed and never executed, so an artifact that draws itself with JavaScript reports that it has no static content instead of inventing some. docs/markdown.md covers what converts, how URLs are handled, and the caching rule.
MCP
Claude and ChatGPT reach the same artifacts through a remote MCP server at
/mcp, over Streamable HTTP. This application is also the OAuth authorization
server: tokens are bound to the exact MCP URL, and the tools call the same
artifact service the web routes call. Other clients reach it too, including one
that needs a pre-registered client id rather than dynamic registration.
docs/mcp.md covers discovery, scopes, client registration,
hosting a client metadata document, the tools, how a client sends a file
without putting it in a tool argument, and how to connect each client.
Uploaded HTML
Artifacts are untrusted code that has to render. They are served from a second hostname, over a short-lived signed URL, into an iframe whose document has an opaque origin, no cookies, and no network. The application origin never renders an uploaded document. docs/security.md describes the model, what each restriction stops, and the known limitations.
Health check
GET /healthz returns {"status":"ok","uptime":<seconds>}. It reports no
configuration, secret, or database detail, because the reverse proxy exposes
it publicly.
License
This server cannot be deployed
Maintenance
Related MCP Connectors
Publish and manage existing HTML presentations from an MCP-capable Agent.
Deploy HTML from any agent: POST markup, get a live URL. Static hosting API with MCP tools.
Publish HTML, Markdown, and multi-file sites as shareable URLs instantly via MCP.
Create, edit, preview, publish, and manage web pages from MCP-capable AI clients.
Related MCP Servers
- AlicenseAqualityAmaintenanceEnables publishing and managing HTML artifacts on stacktr.ee directly from MCP clients like Claude Desktop.2561 npmMIT
- FlicenseNot gradedqualityCmaintenanceEnables publishing, updating, and sharing HTML artifacts with strict security isolation (origin separation, CSP, API keys) via MCP tools.1-
- AlicenseNot gradedqualityAmaintenanceEnables agents to publish HTML artifacts to permanent, shareable, versioned pages on Artifyde via MCP.55 npmMIT
- FlicenseNot gradedqualityCmaintenanceEnables sharing self-contained HTML files via public or access-key-protected private links. Provides MCP tools to create shares, retrieve public share metadata, and describe the service.3-