portego
Integrates with Google as an identity provider, allowing deployments to sign users in with Google and optionally enforce a Google Workspace hosted domain via the id token hd claim.
Integrates with generic OpenID Connect providers for authentication, using the issuer discovery document and configurable scopes to sign users in.
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.
Connect an agent
Two MCP servers exist. The remote one at /mcp on the deployment lists, reads,
and uploads artifacts. The local portego-upload server adds styled artifact
creation and validation, and uploads through the same deployment.
Claude Code
Install the plugin from the marketplace in this repository. It carries the
local server, asks for the address of the deployment, and adds the
/portego-upload:create-artifact, /portego-upload:share-html, and
/portego-upload:share-markdown skills:
/plugin marketplace add laguna-engineering/portego
/plugin install portego-upload@portegoTo also read artifacts, add the remote server:
claude mcp add --transport http portego https://share.acme.example/mcpSign-in opens a browser. Over SSH the OAuth callback needs a forwarded port; docs/mcp.md shows the commands.
Pi
Pi connects to the remote server with a pre-registered client id. Host the
client metadata document as described in docs/mcp.md, then add
the server to ~/.config/mcp/mcp.json:
{
"mcpServers": {
"portego": {
"url": "https://share.acme.example/mcp",
"auth": "oauth",
"oauth": {
"clientId": "https://share.acme.example/mcp-clients/pi.json",
"redirectUri": "http://localhost:19876/callback",
"scope": "artifacts:read artifacts:write offline_access"
}
}
}
}The first tool call opens the browser for sign-in. After the server is
redeployed, run /mcp reconnect portego in Pi so it refreshes its cached tool
list.
Any other MCP client starts the local server with npx -y portego-upload and
passes PORTEGO_ORIGIN in the server entry's env.
Related MCP server: publish-artifacts-mcp
Requirements
Bun 1.4 or later
Node.js 24 (see
.nvmrc) if you run editor tooling outside Bun
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.
Development
bun run devThis applies the database schema, so a new clone needs no separate step, and a
pull that adds a migration needs none either. Then it 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 application and upload tool |
| Type-check the upload tool only |
| 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). |
|
| Image files one upload may carry. Each image is at most 10 MiB. |
|
| Combined size of one upload's images, in bytes (50 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
plugins/ Claude Code plugin skills and metadata
tools/portego-upload/ Local creation, validation, and upload tool
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 artifact page fills the window under the masthead with an isolated preview, and carries the metadata, versions, comments, a source download, and a copy-link action. A link to it unfurls in chat apps with the artifact's title and the Portego logo. 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. GET, POST, PATCH,
and DELETE under /api/folders and /api/tags manage shared organization. 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.
Folders and tags
Folders form a shared nested tree. An artifact has zero or one folder and any number of shared tags. They organize artifacts only: the normal gallery remains a global view across folders and tags, with its existing open and solved filters. docs/organization.md defines the model, API behavior, and the boundary prepared for a later permission system.
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 artifact page, and MCP. Markdown uploads return the Markdown supplied with their version, and an HTML upload can bring its own Markdown for the same purpose. Without one, 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. The
local plugin can also create and validate an artifact in a configurable style;
docs/artifact-styles.md defines the style format and
resolution order.
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.25277 npmMIT
- FlicenseNot gradedqualityDmaintenanceEnables publishing, updating, and sharing HTML artifacts with strict security isolation (origin separation, CSP, API keys) via MCP tools.1-
- AlicenseNot gradedqualityCmaintenanceEnables agents to publish HTML artifacts to permanent, shareable, versioned pages on Artifyde via MCP.49 npmMIT
- FlicenseNot gradedqualityBmaintenanceEnables 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.5-