Skip to content
plugmcp
DirectorySkillsCategoriesBlog
EN/中文
Sign inSign up
Home/Servers/GenHTTP Lambda

GenHTTP Lambda

by dev.genhttp

Write, deploy and host small C# web services and sites at a public address.

Power your agent

Add this server

The publisher's own URL. Not a PlugMCP endpoint.

Server URL
https://genhttp.dev/mcp
Provider
Add in Claude

Claude opens with the name and URL filled in. You still confirm it there.

Claude Code
claude mcp add --transport http dev.genhttp-lambda https://genhttp.dev/mcp

Run in a terminal.

Claude Desktop
{
  "mcpServers": {
    "dev.genhttp-lambda": {
      "command": "npx",
      "args": [
        "-y",
        "mcp-remote",
        "https://genhttp.dev/mcp"
      ]
    }
  }
}

claude_desktop_config.json

Step-by-step: add this in Claude

Remote URL or a command on your machine? How the two installs differ

Run on PlugMCP

Host a copy with our keys and logs.

A new server you create. It is not this listing's official endpoint. Use the publisher install when you want their URL and their credentials.

Launch

Related MCP

View more
  • Firecrawl🔥 Official Firecrawl MCP Server - Adds powerful web scraping and search to Cursor, Claude and any other LLM clients.
  • N
    NotionToken-auth Notion MCP: pages, databases, blocks, comments, files. Runs headless in CI and agents.
  • Context7Context7 Platform -- Up-to-date code documentation for LLMs and AI code editors

Related skills

View all
  • reaReverse engineer anything with agents, from app behavior down to native binaries.
  • rea-changelog-updatePrepare or rewrite REA release changelogs and GitHub release notes from pinned Git history, with verified contributor thanks and Release Please synchronization.
  • rea-tool-designDesign or change REA investigation tools, CLI/MCP contracts, provider capabilities, and Evidence semantics.

Questions

How do I install GenHTTP Lambda?
Copy the http config for Cursor, Claude Desktop, Claude Code, or VS Code on this page. It uses the publisher's install, not a PlugMCP proxy.
Does GenHTTP Lambda need an API key?
No required environment variable is listed on this record.
What transport does GenHTTP Lambda use?
GenHTTP Lambda is reached over HTTP.
When was GenHTTP Lambda last checked?
Last checked 2026-10-16. Write, deploy and host small C# web services and sites at a public address.

Write, deploy and host small C# web services and sites at a public address.

GenHTTP Lambda

Write a C# snippet that returns a GenHTTP IHandler, press deploy, and get a public URL that serves it. The snippet is compiled with Roslyn at runtime and the resulting handler is hosted by the same [GenHTTP][genhttp] server that serves this application.

return Inline.Create()
             .Get(() => "Hello from my lambda!");

Quick start

docker compose up --build

The application is then available at http://localhost:8080/. Its data - the SQLite database, the stored code, and the workspaces and databases of the lambdas - lives in a named volume mounted at /data. The lambdas answer at subdomains of LAMBDA_HOSTING_URL, one each (see Where the lambdas answer).

Developing locally

Two processes: the .NET server, and Vite serving the frontend with hot reload.

# the server, on http://localhost:8080/
dotnet run --project src/GenHTTP.Lambda

# the frontend, on http://localhost:5173/ (proxies /api, /lambda and /features to the server)
cd src/Frontend && npm install && npm run dev

Work against http://localhost:5173/ while developing. The lambdas answer at subdomains of localhost named after their keys - http://quiz.localhost:8080/ - which browsers resolve without a record. The server on its own answers with a placeholder page until a frontend has been built into its web root, which is what npm run build does:

cd src/Frontend && npm run build   # writes src/GenHTTP.Lambda/wwwroot

English is the source of every page; the other languages are translated by script and agent, so that only what changed is read and written:

cd src/Frontend
npm run translations -- extract   # a request per language in .translations/
# one `translator` agent per request (.claude/agents/translator.md, on Sonnet)
npm run translations -- apply     # the answers into the catalogs, checked
npm run translations -- check     # every language against English

CLAUDE.md ("How a change is translated") has the rules.

Build and test everything the way CI does:

dotnet build GenHTTP.Lambda.slnx -c Release
dotnet test GenHTTP.Lambda.slnx -c Release

The tests compile and execute real lambdas against a real server on a random port, each against its own temporary data directory.

Routes

RouteServes
/the landing page
/buildthe text box for somebody who wants an app built, no code
/shipfor developers: connect an agent and publish what it built
/docsthe guide for owners
/showcasethe lambdas their owners chose to show
/sourcethe lambdas whose owners published their code
/source/:publicKeythe published code of one: its files, documentation, tests and changes, per version
/source/:publicKey.gitthe published code as a git repository, read only
/enterprise, /terms, /privacy, /imprintthe company pages
/editor/createthe creation assistant
/editor/:privateKeythe editor for one lambda
/editor/:privateKey/:name.gitthe lambda as a git repository, to clone and push to (any name; the editor offers the public key)
/lambda/:publicKey/…where a lambda answered once: a permanent redirect to where it answers now, with the rest of the path and the query
/features/:featurethe preview of a feature, while it is online
any path, at {publicKey}.{hosting domain}the deployed handler - or, while the lambda answers at a domain of its own, a permanent redirect there
any path, at a lambda's own domainthe deployed handler of a premium lambda with that domain
the hosting domain itselfa page saying what is hosted there, leading to the site
/api/v1/everything the editor calls, see below
/mcpthe same, for agents
/adminevery lambda on the server, for whoever runs it

API

Browsable at /api/v1/scalar/, described by /api/v1/openapi.json (or .yaml). The description is the reference for whoever calls the API: it opens with an introduction (getting started, keys, files, limits, errors), groups the operations by what they are about, and says of each what it does, what it leaves alone and every way it is refused. It is written with GenHTTP's documentation attributes beside the code (see ApiDocumentation), and ApiDocumentationTests keeps it complete.

A lambda is addressed by its editor key, which is also what authorizes the call. Things that can be listed, read, created or changed are resources; what is a process rather than a thing is a verb in the path. A refusal is answered with a status and { status, error, message }, where error is Invalid (400), Forbidden (403), NotFound (404), Conflict (409) or TooMany (429).

EndpointDoes
POST /lambdascreates a lambda (optionally with view: Full or Simple)
GET / PATCH / DELETE /lambdas/:privateKeyreads, changes (its key, its view), removes it
GET /lambdas/:privateKey/exportthe newest version as a runnable .NET 10 project with a Dockerfile (zip), with its database, see below
GET / POST /lambdas/:privateKey/versionslists versions, saves a new one (optionally with specification and change)
GET /lambdas/:privateKey/versions/:versionreads one version (?folder=docs/ for its documentation alone, resources/ for its resources)
GET /lambdas/:privateKey/versions/:version/zipone version's files as a zip (?layout=project laid out as a clone)
POST /lambdas/:privateKey/versions/zipsaves a zip of all files as a new version (?layout=project for one laid out as a clone)
POST /lambdas/:privateKey/versions/changeschanges some files of the newest version, as a new one
GET /lambdas/:privateKey/deploymentwhat is online, and until when
POST /lambdas/:privateKey/deployment/start / stopputs a version online, takes it off
GET /lambdas/:privateKey/deployment/historyevery stretch it was online, and what ended it
GET /lambdas/:privateKey/summarythe dashboard: state, traffic, problems, storage, what the documentation says
GET /lambdas/:privateKey/trafficrequests by minute and quarter hour, statuses, paths
GET /lambdas/:privateKey/logsits own log, followed with ?since=; no visitor addresses
GET /lambdas/:privateKey/agentthe agent's change of it - under way, or the last one - and how many are left today
POST /lambdas/:privateKey/agent/start / stopasks the agent for a change, stops it
GET /lambdas/:privateKey/datathe kinds of data it keeps, and how full each is
GET / PUT / DELETE /lambdas/:privateKey/data/:kindone kind (database, workspace, secrets); switches it on; switches it off and deletes what it held
GET /lambdas/:privateKey/databaseits database: tables and views, their columns and rows, how full it is, whether the code connects
GET /lambdas/:privateKey/database/tables/:tablea page of one table's rows, newest first (offset, limit, order, descending)
GET /lambdas/:privateKey/secretsthe secrets by name, which names the code reads, and which of those have no value
PUT / DELETE /lambdas/:privateKey/secrets/:namestores a value ({ "value": … }), never read back; removes one
GET /lambdas/:privateKey/fileslists the workspace
GET / PUT / DELETE /lambdas/:privateKey/files/:pathone file, its path encoded (a%2Fb.txt), as base64 up to 32 MB
GET / PUT /lambdas/:privateKey/files/:path/contentone file as it is, streamed, however large
PUT /lambdas/:privateKey/folders/:pathmakes a folder
GET / POST /lambdas/:privateKey/featureslists the features, starts one (name, specification, base)
GET / PATCH / DELETE /lambdas/:privateKey/features/:featureone feature with its files (?folder= as for a version); changes its name, notes or base; deletes it
PUT /lambdas/:privateKey/features/:feature/filesreplaces its files (?deploy=true puts its preview online)
POST /lambdas/:privateKey/features/:feature/changeschanges some of its files
GET / PUT /lambdas/:privateKey/features/:feature/zipits files as a zip; a zip put back into it (both with ?layout=project)
POST /lambdas/:privateKey/features/:feature/preview/start / stopputs its preview online, takes it off
POST /lambdas/:privateKey/features/:feature/mergemakes it the next version and deletes it (deploy to put that online)
GET /lambdas/:privateKey/features/:feature/logswhat its preview has been doing
GET /lambdas/:privateKey/features/:feature/data, POST …/data/refreshits copy of the data; a fresh copy of the lambda's
…/features/:feature/workspace[/:path[/content]], …/folders/:pathits copy of the workspace, as files and folders are the lambda's
…/features/:feature/secrets[/:name]its copy of the secrets, as secrets are the lambda's
…/features/:feature/database[/tables/:table]its copy of the database, as database is the lambda's
POST /lambdas/:privateKey/code/checkcompiles without saving
POST /lambdas/:privateKey/code/semantics, completions, definitionwhat the editor asks the compiler
GET /keys/:publicKeywhether a key is free, and if not, online
GET /demosthe demos, and the keys to read them with
GET / PUT / DELETE /lambdas/:privateKey/showcaseits entry on the showcase: read, list it (title, description, picture), take it off
GET /lambdas/:privateKey/domain, PUT / DELETEthe domain of a premium lambda, whether it is served, and what it resolves to
GET /showcases, GET /showcases/:publicKey/imagewhat /showcase lists, and a picture
GET / PUT / DELETE /lambdas/:privateKey/analyticswho visits it, by day, over the last days (at most what its tier shows); switches counting on; switches it off and deletes what was counted, see Analytics
GET / PUT / DELETE /lambdas/:privateKey/sourcewhether its code is published and under which license, the licenses on offer; publishes it or changes the license (license, author); takes it down
GET /sourceswhat /source lists (search, order: stars, updated, published, skip, take)
GET /sources/:publicKeya published source: what it is, its license, where it runs, every version and what it changed, a ticket to star it with
GET /sources/:publicKey/versions/:versionthe files of a version as its project has them, packed on the first request
GET /sources/:publicKey/versions/:version/files/:path, …/raw/:pathone file, its path encoded: its text; as it is (?download=1 to save it)
GET /sources/:publicKey/versions/:version/zipa version as a project to download
POST /sources/:publicKey/starstars it, or takes the star back (ticket, starred)
POST /builds, GET /builds/:idthe text box on /build
GET /systemterms, limits, starters, build agent, where a lambda answers (lambdaUrl)
GET /telemetry, /logs, /admin/...for whoever runs the installation

Creating a lambda asks what somebody would like to build and offers the demos in those words - "Keep track of things", "Let people sign up" - next to an empty lambda. Picking a demo starts the new lambda as a copy of it, which is theirs to change. The files are in Resources/Templates and listed in TemplateCatalog.

A lambda has two keys. The public one names its address - quiz answers at https://quiz.genhttp.run/ - and may be changed; the private one is the editor link and is shown only to whoever created the lambda - anyone holding it can edit, deploy and delete. What the API answers about a lambda carries both its address below the hosting domain, publicUrl, and where to link to it, address - its own domain while it answers at one, publicUrl otherwise (see Where the lambdas answer).

A lambda may also return a websocket rather than a document, in any of the three flavours the module offers - Websocket.Functional(), .Reactive() and .Imperative(). The handshake is an ordinary request and is bounded by the execution timeout like any other; everything after it belongs to the connection, which is why a socket may stay open far longer than a lambda is given to answer. FrameType, which the imperative flavour reads off every frame, is one of the names a lambda is given, so no import is needed.

The editor says when both free tier timers run out: a hint under the public URL for the deployment, and a chip beside the buttons for the lambda itself, which opens an explanation of how each one is extended.

Where the lambdas answer

Every lambda answers at a subdomain of one domain, the hosting domain, named after its public key: https://quiz.genhttp.run/, the whole of the host its own. LAMBDA_HOSTING_URL names that domain with its scheme and port; left empty it is this server on localhost - http://quiz.localhost:8080/ - which browsers resolve without a record in the DNS.

A host of its own rather than a path below the platform's, so that what somebody built is a site of its own: a page of it cannot read what the platform keeps in a browser - the admin token, for one - nor what another lambda keeps there, and its paths start at the root of its host, so a browser asking for /favicon.ico asks the lambda. The editor, the API, the agents' endpoint and git stay where they are, on the platform.

  • Its old address. /lambda/{key}/… on the platform is answered with a permanent redirect to where the lambda answers now, with the rest of the path and the query as they were sent, byte for byte - a signature over them still matches - Moved Permanently for a read, Permanent Redirect for anything else, which a client sends again with the same method and body. It is answered before anything else the server does: no request line, no telemetry, no upgrade to HTTPS, since the address it sends to is secure already. A browser keeps it for a day rather than for good, because where a lambda answers changes when it gets a domain or loses one. A key nobody has is sent on as well - the address moved, whether or not something is there - while a path below /lambda/ that could never have been a key is not found.
  • A domain of its own. A premium lambda with a domain answers there and only there: its subdomain sends visitors on to the domain the same way, so there is one address for search engines to keep and for visitors to pass on. Removing the domain, or the tier that serves it, has the subdomain answer again. For the same reason www. in front of the domain sends its visitors on to it - www.shop.example.com to shop.example.com - unless the owner entered the domain with www., which is then where it answers, and nothing is sent there from the name without; or unless that www. is the domain of another lambda, or one of the platform's own names.
  • Nothing there. A subdomain nothing is online at - a key nobody has, a lambda that is offline, a name no key can be - is told so in a plain page that leads to the site, or in a line of JSON for anything but a browser. The hosting domain itself, and www. in front of it, has a page of its own that says what is hosted there and leads to the site, in the language the browser prefers; its words are in pages.json (hosting:/ and hosting:/:key) and translated with the rest. Both link the site's legal notice and privacy policy, named as the site names them. Neither is one of the site's pages: none of the site's scripts is ever served at the hosting domain.
  • Keys are host names. A key is a label of the hosting domain, so the names mail clients and servers look for below a domain (mail, autoconfig, autodiscover, mta-sts) cannot be claimed, nor can a key starting with xn--, which a browser reads as an international name. A lambda is found by its key as it is spelled, whatever the rules for claiming one say today, so a rule made later takes nothing offline.
  • Previews stay on the platform. The preview of a feature answers at /features/{feature}/, as it did, which is why a lambda links relatively. It is the one place a lambda's code still runs on the platform's host - with what the platform keeps in that browser in reach of its scripts - until previews get hosts of their own as well.

The request log names the host in front of the path - GET quiz.genhttp.run/api/items - as it does for a domain of its own, and the traffic of a lambda counts its visitors by the host they came to.

What it takes is a wildcard record in the DNS - *.genhttp.run, and genhttp.run for the page at its root, pointing at the server - and a certificate for both names (see The hosting domain). The hosting domain belongs on the Public Suffix List: until it is there, a lambda can set a cookie for the whole domain, which the browser then sends to every other lambda as well.

Versions, features and data

A lambda keeps three kinds of things, and they live differently.

A version is the program: its code - the C# it is compiled from, and whatever else is kept with it, its documentation and its tests among it - and its resources, which it reads and serves while it runs, the front end and the database's migrations among them (see Code and resources). A version never changes once it is saved, which is what makes every one worth keeping - any of them can be compared with, and put back online exactly as it was. Saving files (POST …/versions, write_code) makes a new one.

A feature is a change worked on beside the lambda. It branches off a version - the newest, unless another is named - with a copy of its files and a copy of the lambda's data, and is changed in place as often as it takes; it has no versions of its own. Its preview is built from its files against its copy of the data and answers at /features/{feature}/, a random key nobody guesses, told to search engines with X-Robots-Tag: noindex and a Disallow in robots.txt. It serves what was deployed until it is deployed again, a restart included, is counted neither in the lambda's traffic nor in its log, and on the free tier goes offline like a deployment does when nobody works on it. Once it does what was asked it is merged: its files become the next version, with its notes, and the feature goes - preview, copy of the data and all; the lambda's own data is not touched. Only a feature based on the newest version can be merged, so that a merge never undoes a version saved after the feature began - by the owner, an agent, or the merge of another feature. There is no merging or rebasing machinery: whoever works on the feature brings a newer version's changes in and then says so by moving its base (PATCH, or update_feature), and the merge takes the lambda's lock and checks the base once more, so two merges cannot both win. A lambda may have ten features open (a limit of its tier, set in the panel); a demo has none. Each holds a full copy of the workspace and of the database, taken on a thread of its own and swapped in whole, so ten features of a premium lambda with a full workspace take ten times its room on disk.

Data is what the program keeps: the database, a SQLite database for its records; the workspace, a private directory of files; and the secrets. It belongs to the lambda rather than to a version - every version reads and writes the same data, and deploying, rolling back or merging never touches it. It goes with the lambda, or when that kind of data is switched off, which deletes what it held - the copies features work on included. Each kind is switched on before the lambda can use it (PUT …/data/:kind, enable_data): the workspace is on unless it was switched off, and a lambda whose workspace is off is compiled with one that refuses every call and says why; the database and the secrets are off until somebody switches them on. An agent may switch a kind on when what it builds needs one; switching off deletes, so only the owner does that. The database and the workspace share one allowance of room, a limit of the tier: each may grow as far as the other leaves it. The editor has a Data section of its own, beside Versions. Every kind is one view of it, picked from a row of pills under its title, and each is shown the same way - what it is, whether it is on, how full it is - with only what it holds drawn its own way.

Deploying picks a version (the latest by default), builds it and makes it live, and a lambda has at most one deployment at a time. A deployment that does not compile leaves the one before it standing - its resources included, which are written back after the failed attempt replaced them.

The editor is written for people who had an app built rather than for developers, so it calls a feature a draft, its copy of the data test data, and merging it putting it online - which merges it and deploys the version it becomes in one step. Nothing in it says merge, base or branch; a feature behind the newest version is out of date, and the agent is offered to bring it up to date. The drafts have a section of their own, shown once there is one, and opened on one the editor becomes the draft's: the sidebar holds it, with the buttons that try it and put it online, and its views are its documentation, its code, its tests, its test data and its preview's log. Showcase, domain, figures and deployments stay with the lambda. A version offers to start a draft from it, the save dialog of the code offers to save what was typed as a new draft instead of a version, and a change the agent leaves as a draft can be tried and put online from the Change section.

Code and resources

A version is any number of files, in two parts:

lambda.cs           the snippet: what it returns is served
Store.cs            more C#, compiled beside it
models/Item.cs      C# in a folder, compiled the same
docs/, tests/       what is written about it (see below)
frontend/, …        anything else: kept with the version, never compiled or served
resources/          what it reads and serves while it runs

Its code is every file that is not a resource. Its .cs files are compiled into the lambda, in whichever folder they are, as a C# project compiles its own - so the types can be laid out in folders - and nothing else is: every other file, of any kind - its documentation and its tests, what a front end is built from, scripts, configuration - is kept with the version and never compiled or served. C# that is not part of the lambda, a test or a tool of its own, therefore does not belong in the code as a .cs file. How the code is arranged is the agent's to decide; the editor only reads docs/ and tests/ as what they are (below).

Its resources, in resources/, are what it reads and serves while it runs: pages, scripts, styles, pictures, the database's migrations. The snippet reaches them through Resources, which is the other half of Workspace - Resources.App("web") serves resources/web/ as a single page application, Resources.Root + "migrations" is where Evolve finds the migrations. A version saved before resources were called that still compiles: Assets is the same thing under its old name.

What a version may come to is one allowance of room, its code and its resources together - a limit of its tier, set in the panel (see Limits). There is no separate limit for C#: the room bounds what the compiler is given as well.

A C# file is named with letters, digits, dashes, underscores and dots, starting with a letter and ending in .cs, 40 characters at most, in folders named as the rest of the code. A resource keeps the rules files that are served have always had: letters, digits, dashes, underscores and dots, an extension, no name starting with a dot, six folders deep at most. The rest of the code may be named as tools name their files - dot files, and the brackets, parentheses, plus signs and the like some tools read meaning off - with no spaces, no .git folder, sixteen folders deep at most. The names a project gives its own files at the top (Dockerfile, .gitignore, .dockerignore, LICENSE, AGENTS.md, CLAUDE.md, a project file, and the folders Platform, bin, obj, workspace and database) are refused, since an export or a clone has them; so are .lambda/ and assets/, which name where the documentation and the served files used to be, with an answer that says where they go now.

Only the compiled C# and where the resources are identify a build, so a version that changes nothing else - its documentation, its tests, what it is built from - builds its handler from the assembly already loaded rather than compiling again.

Lambdas saved before this layout kept their served files at the top of a version and their documentation, tests and build folder in .lambda/ (docs/, tests/, build/). Nothing on disk was rewritten: a version is read in the layout it was saved in, so the old ones are translated as they are read - site/app.js becomes resources/site/app.js, .lambda/docs/product.md becomes docs/product.md, .lambda/build/package.json becomes build/package.json - and saved in the new one when they are next saved (an envelope with "version": 2, LambdaSource.Parse). C# that was in .lambda/ was never compiled and would be now, so it keeps what it holds under a name that is not: .lambda/tests/Smoke.cs becomes tests/Smoke.cs.txt. A feature's files are read the same way. In git, a lambda's history keeps the commits its clones already have; the move is a commit of its own on top of each (see Working with git).

What it is built from

Some of a lambda may be made by a build tool rather than written as it is served or compiled: a front end that is compiled or bundled, styles compiled from another language, code generated from a description. What the tool makes is part of the program like anything else - resources, or code - and what it makes them from is part of the code, in a folder of its own that the agent names. Nothing about those files is assumed: they are whatever the tool works from, in whatever layout it wants.

frontend/              what it is built from, in whatever shape the tool wants
frontend/README.md     how it is built, and where the build goes
frontend/.gitignore    what the build installs or keeps for itself, left out
resources/web/         what the build wrote, if it makes resources
*.cs                   what it wrote, if it makes code

The platform builds nothing. It never runs a build tool or installs what one needs, on a push or anywhere else, and never checks that the program is what those files build to. Whoever changes it - an agent with a shell, in a clone most of the time - changes them, runs the build where it works, and saves both together, in one version or one feature. A change to the sources that is not built changes nothing; a change made in what the build wrote is undone by the next build. This keeps the platform what it is - a server that hosts what it is given - and the tools the agent's, whichever they are.

What a build installs, caches or writes for itself - node_modules, target or .venv, for example - is no part of a version, and every tool names it differently, so the folder says what it is, in its own .gitignore files. A clone follows them because git does; a zip put back through the API follows them too, the way git add would (IgnoredPaths, git's rules: patterns at any depth or anchored, folders only, !, **, the deeper file having the last word, nothing coming back from a folder left out) - but keeps what the lambda already has, as git keeps what it tracks. Only the .gitignore files of the code count: a resource never has a name starting with a dot. A zip is counted as it is sent, before any .gitignore leaves something out, so it holds what a commit would - never what a build installed, which is no part of it and would only make the upload large. A save that names a file keeps it, as git keeps a file added on purpose; one that runs over the allowance names the largest folder of the code, and says that its .gitignore keeps out what a build installed.

One layout to build in. A build writes by paths relative to itself - from frontend/, into ../resources/web - and a clone, an export and the zip of a version or a feature lay the files out the same way, so it finds them wherever it runs. The zip can be read back as a commit of a clone would be read too, ?layout=project on the download and on the upload: the platform's files skipped, what the repository's .gitignore leaves out left out (bin/, obj/), anything with no place in a lambda refused, and Project.cs that was not touched kept as the snippet it was, to the byte. Agents that build are steered to a clone or to that layout; agents with only the tools cannot build, and never need it. A save through the tools that replaces every file says so when it left out a folder of the code the version had - the sources of a front end, most often - since those are dropped with it.

The build agent behind /build has no shell: it writes its front ends as plain HTML, CSS and JavaScript, and leaves the sources of one it finds alone. The demos have none either: they teach how a lambda is put together, with front ends of plain files a reader follows without a build tool.

Documentation and tests

Every version keeps what is written about it beside its program, so that whoever changes it next - an agent, most of the time - starts from what the app is for and what has to keep working, which the code does not say:

FileWhat it holds
docs/product.mdwhat the app is, who it is for, what people do with it and why - in the terms of whoever asked for it; its first paragraph is the app in a sentence or two
docs/decisions.mdthe technical decisions, and why they were made
docs/…more pages, and pictures the pages show
tests/README.mdhow the app is tested automatically: what has to keep working, how each of it is checked, how to run the scripts
tests/…the scripts and the test data they use

They are files of the version rather than fields in the database, because they describe that version of the program: saved, compared in the history, rolled back, copied into a feature and merged with it like any other file, in the zip and in the export, with no table and no migration. Rolling back brings back the documentation that was true of the version. They are code like any other file that is not a resource - never compiled, whatever they are called (a test written in C# is kept as a script, not as a .cs file), never served, left out of what identifies a build, and counted towards what a version may come to. docs/ and tests/ are a convention, not a kind of file: nothing about them is special but that the editor reads them as what they are, and the agents are told to keep them.

A zip of a version or a feature holds them, which is how an agent working locally reads and writes them without further calls. The agents are told everywhere they decide something - the instructions, the tool descriptions, the answers and platform_guide (documentationAndTests) - to read them before changing a lambda, to write all three pages with a new lambda, to update what a change affects in the same save, and to run the tests against a feature's preview before merging it - all of it in proportion to the lambda: a small one gets a few lines a page and one quick check, not a test suite. read_lambda hands them over first and apart from the program's files, within a budget of their own, and every save, deployment and preview that leaves a page out says which. Every demo has all three and a script its tests run, checked by a test.

In the editor they are two sections. Documentation is second in the sidebar, after the overview, in both views; Tests is in the full view only. Both are one component over their folder: the pages rendered to be read, each a pill, with the other files behind a Files pill, and the version picked as it is in the code. A page the version changed is marked, and shows the difference on request. On the newest version or in a draft a page can be edited, beside a preview of it; saved, it is the next version (a draft saves in place), put online with it when the newest version was online. The simple view calls the documentation About and shows the product page alone, with a way to have the agent correct it rather than to edit it. The overview of either view opens with the product page's first paragraph. The full one ends with how much of its two allowances the lambda uses - its code and resources, and its data - each a meter that opens the section it counts.

With fifteen sections and more, the full view's sidebar is in groups: the overview and the documentation on their own, then how people find it and what they get to see of it (showcase, open source, domain) - what the lambda is to everybody else, right under what it is - then where a change is made, under Develop (Change, the drafts, the code, the tests), the program and what it keeps (data, versions), and how it runs (deployments, stats, logs). On a phone the groups are a rule apart in the row of sections.

Code, in the full view only, is every file of a version: the code and the resources as two trees beside the editor, each with what it comes to and whether the public can reach it, and under them what the version comes to of its allowance. It opens on the newest version, or a draft's files in a draft; an older one is picked above it. A file of either part can be added, uploaded, removed and edited there, and saved as the next version (a draft saves in place and puts its preview online). Links to the sections it replaced - /files and /build - open it.

Databases

Records - entries, accounts, orders, votes - go in the database: a SQLite file of the lambda's own, which its code opens a connection to and reads and writes through [Entity Framework Core][efcore] - or in plain SQL, where that is simpler. Its schema is migrations shipped with the version and applied by [Evolve][evolve] as the lambda starts, never Entity Framework's own:

// resources/migrations/V1__Create_notes.sql: CREATE TABLE notes (id INTEGER PRIMARY KEY, text TEXT NOT NULL);
using (var connection = Database.GetConnection())
{
    new Evolve(connection) { Locations = [Resources.Root + "migrations"], IsEraseDisabled = true }.Migrate();
}

return Inline.Create().Get("count", () =>
{
    using var db = new Notes(Database.GetConnection());

    return db.Entries.Count();
});

public class Note
{
    public long Id { get; set; }

    public string Text { get; set; }
}

// maps the table the migration made, on the connection it is handed
public class Notes(SqliteConnection connection) : DbContext
{
    public DbSet<Note> Entries => Set<Note>();

    protected override void OnConfiguring(DbContextOptionsBuilder options)
        => options.UseSqlite(connection, contextOwnsConnection: true);

    protected override void OnModelCreating(ModelBuilder model) => model.Entity<Note>().ToTable("notes");
}

Database.GetConnection() hands out an open SqliteConnection of Microsoft.Data.Sqlite, taken from a pool, which the caller disposes of - or a context it is handed, with contextOwnsConnection: true - one per request, never one shared between requests. The connection comes into the context from outside, because inside a DbContext, Database is the context's own (the compiler's message says so, and names LambdaEnvironment.Database). Microsoft.Data.Sqlite, Microsoft.EntityFrameworkCore and EvolveDb are imported in every file, like the GenHTTP modules. Agents are told to use it synchronously - ToList and SaveChanges, never ToListAsync or SaveChangesAsync - because SQLite answers synchronously whatever the method is called, and the engine has an asynchronous model of its own that a blocking call here does not disturb; this is said, not enforced. The demos all keep their records like this, demo-crud the plainest of them.

  • Off until switched on. Switching it on (the editor, PUT …/data/database, enable_data) makes it: an empty file, in write-ahead logging, so readers never wait for a writer. Switching it off deletes it, the copies of the features included. Either way the lambda and its previews are started again on their next request, so what they do as they start - migrating - is done against what is there now. A lambda created as a copy of a demo that keeps one starts with its own, switched on.
  • The one file it opens. A lambda does not make connections of its own. The code guard refuses new SqliteConnection(...) - also as a target-typed new(...), through an alias or a class derived from it, which is asked of the compiler rather than read off the text - and the names that would point a connection elsewhere or load native code into it (ConnectionString, SetConnectionString, LoadExtension, DbProviderFactories, …). Of Entity Framework, a lambda reaches the namespace a context is written in and the three describing a model takes (Metadata.Builders, ChangeTracking, Storage.ValueConversion), not its Infrastructure, Storage, Internal or the rest, where a context's services and options are. UseSqlite is refused with anything but a connection, and so are EnsureCreated, EnsureDeleted and Entity Framework's Migrate - the schema is Evolve's - all asked of the compiler. dynamic is refused too: it binds members at runtime, which is everything the guard reads the code for, skipped. The connection it is handed carries an authorizer SQLite asks about every statement: ATTACH and VACUUM INTO, which reach other files, are refused, as are the pragmas that would point it at other directories or lift its quota, and SQLite's defensive mode is on. The handle underneath is a type the code cannot name.
  • Its room. 256 MB, and 2 GB for a premium lambda, by default - a limit of its tier - enforced by SQLite with max_page_count on every connection: past it a write fails with database or disk is full. A limit the operator lowers holds from the next connection; nothing stored is removed.
  • Features get a copy. Taken with SQLite's backup, which reads the database in one transaction while the lambda goes on writing, so it is the database as it was at one moment. A preview migrates its copy as it starts, which is where a migration is tried first; merging throws the copy away.
  • Read by the owner, written by the lambda. The editor shows its tables, their columns and rows under Data (GET …/database); read_database does the same for an agent. Nothing but the lambda writes to it.
  • Versions share it. A rollback runs an older version against a schema a newer one migrated; Evolve lets a version start whose migrations are fewer than the database's, and agents are told to change a schema additively, and never to edit a migration that was applied.

The databases are not encrypted. SQLite has no encryption of its own: it would take replacing the SQLite of the whole process with a build that has (SQLite3 Multiple Ciphers was tried, and is maintained by one person for .NET), and the key would sit in the platform's database on the same volume - so it would protect a copy of /data/databases on its own and nothing more. Protect the data volume and its backups instead.

Secrets

API keys, passwords and tokens are data, never code: code and resources live in every version, every export and every reader of the history, so a key put there can never be taken back. A lambda reads a secret by name:

var stripe = new System.Net.Http.HttpClient();
stripe.DefaultRequestHeaders.Authorization = new("Bearer", Secret.Read("STRIPE_KEY"));

Secret.Read throws, saying how to set it, when there is no such secret; Secret.Exists answers whether there is one, and false while secrets are switched off, for code that works without. Secret is there in every file, like Workspace.

The rules are the usual ones for secrets:

  • Written, never read back. A value goes in through the editor, the API (PUT …/secrets/:name) or MCP (set_secret), and nothing gives it out again - not the editor, not the API, not an agent. Only the lambda reads it. So a secret is replaced rather than edited, and whoever holds the editor link can use a key without being able to copy it. Answers repeat the name, never the value, and the log records the name only.
  • Off until switched on. A lambda has no secrets until somebody switches them on - the owner in the editor, or an agent whose code needs one. Switching them off deletes every value, the copies of the features included.
  • Names are environment variables. Letters, digits and underscores, not starting with a digit, case sensitive: an exported project reads Secret.Read("STRIPE_KEY") from $STRIPE_KEY. Up to 100 per lambda, 32 KB each - a private key fits, a file belongs in the workspace.
  • What the code waits for is said. The names the code reads with Secret.Read("…") or Secret.Exists("…") as a literal - in the version online and in the newest one, or in a feature's files - are listed beside what is stored: a name read and not stored is missing (reading it fails), one only asked about with Exists is optional. The editor offers to set what is missing, the overview asks for it, and read_lambda and list_secrets tell an agent. That is how the two sides meet: an agent writes Secret.Read("WEATHER_API_KEY") and switches secrets on, and the owner enters the value without ever reading the code - and without the value passing through the agent.
  • Read at runtime, not compiled in. The generated class is handed a function to read with once the lambda is loaded (a private field the code of the lambda cannot name, and reflection is refused), so a value changed in the editor is what the next call reads, without a deploy, and no value is ever written into an assembly on disk. What a lambda reads is held in memory sealed, per lambda and feature, and opened on each read.
  • Features get a copy. Like the workspace, a feature starts with a copy of the lambda's secrets, its preview reads the copy, a value set in the feature (a sandbox key, say) stays there, and merging throws the copy away.

They are stored in the SQLite database, table secrets, sealed with AES-256-GCM. The key is made with HKDF from two halves: the installation's, which is not in the database - LAMBDA_SECRETS_KEY (32 random bytes in base64, or a passphrase of at least 32 characters), or else a random key written on first use to secrets.key in the data directory, readable by the server's user only - and the lambda's own random salt (lambdas.secret_salt), made with its first secret and dropped when its secrets are switched off. The name is bound to the value as associated data. So the database alone opens nothing, a value copied under another name or into another lambda does not open either, and a feature's copy is copied sealed, without being opened.

Backing up and moving follow from that: the database and the installation's key are all it takes. Copy the data volume as a whole - secrets.key goes with it - or set LAMBDA_SECRETS_KEY on the new server to the key the database was written with. Keep a copy of the key apart from database backups; without it the secrets are gone, while everything else restores. The server remembers a fingerprint of the key in settings and logs an error when it is started with another one, and a secret that does not open says why.

None of this keeps a secret from the lambda it belongs to, or from code running in the same process: the code guard is not a sandbox. The encryption is about the database and its backups; the isolation between lambdas is what the guard makes expensive, and no more than that.

The demo demo-registration peppers its password hashes with a PASSWORD_PEPPER that the seeder gives it as a random secret nobody knows, and a copy of it works without one (Secret.Exists). Its accounts remember whether they were peppered, so accounts made before still sign in.

Changing a lambda by asking

Where the installation runs the build agent, the editor has a Change section: the owner says what should be different, and the same agent that builds things on /build changes the lambda. It works in a feature - a new one, or one the owner picks to go on with - reads the code and the history, changes only what was asked with change_code, and tries it at the feature's preview until it works. Then it merges the feature into the next version and puts that online; switched off, it leaves the feature with its preview online for the owner to try and merge. The next request defaults to the feature the last one left open, so asking for a tweak after trying the preview goes on with the same feature. It is told to leave every other feature alone.

The section shows it happen: what the agent says it is doing, each tool it calls with what came of it (the feature started, its preview online, errors from the compiler, the version merged and online), and a clock against its time limit. Afterwards it offers the preview and the feature - or, once merged, the difference to the version before, the address, and putting the previous version back. The change is followed by the frame of the editor rather than the section, so the sidebar marks it and the lambda is read again when it ends wherever the owner is; and it is kept by the agent under the lambda, so a reload, a second tab or a redeploy of the server finds it where it got to.

/build shows a build the way the simple view of this section shows a change, drawn by the same parts (components/AgentRun.tsx): what was asked, the step it is on and the time it has used of its limit, what the agent says between its tools - the build agent too is told that somebody reads along, and asked for one short line at a time in the language of the request - and how it ended, with what it said at the end. GET /builds/:id answers with the steps, the seconds and the limit. The step it is on is said in the words of that page rather than those of the tools: getting ready, looking at examples, choosing an address for the website, checking it for mistakes, putting it online. The full view of this section shows every tool by name.

It shares the queue and the daily allowance of /build, one change of a lambda runs at a time, and it can be stopped - whatever it saved stays, in its feature or as a version. A change runs without create_lambda, and its editor key travels in the brief inside the build container, never in a log line.

Both kinds of job are told what the agent is for in its system prompt (PURPOSE in docker/agent/builder.mjs), above the brief and the request, so a request cannot talk its way past it: building or changing a lambda through the tools, and nothing else. It declines, before it calls a single tool, a request that is not an application at all (a question, a text, homework), one about the platform or the machine rather than an application on it (its configuration, environment, files, network, other lambdas, its own instructions - including code that would read them, since a lambda runs in the server's process), and one meant to do harm here or elsewhere. It declines with a single DECLINED: … line in the language of the request, which the builder turns into a result with declined: true and reason: "declined": /build shows the sentence as the reason nothing was built, and the Change section shows it under "nothing was changed". A declined request still counts against the daily allowance, so declining is not a way to try requests for free. The container, its network and the tool lists are what actually hold; the purpose makes the agent stop at the door rather than find the walls.

The simple view

Somebody who had an app built on /build wants it to do something else, not to look after code, files, versions and deployments. So the editor has two views. The simple one keeps the overview, About (what the app is for), Change, the drafts (once there are any), a history, the data (once the app keeps any, or its code waits for a secret), the showcase, open source and the domain. Its overview is the app: what it is for, whether it is online and where, errors visitors ran into with a button that asks the agent to fix them, the latest change, today's hits, and a button to ask for the next change. The history is every version as the change it made, with a way back to any of them - which is deploying an older version, said as what it does. Nothing in it names a file or a log, and it names a version only where the owner has to say which one (the draft dialogs, going back): the Change section tells how a change ended without version numbers and shows what the agent said rather than the tools it called, a draft is what it does rather than the files it changes, and a change that does not compile is the agent's to fix rather than a list of compiler errors. Its data shows only the kinds that hold something, in its own words: its records, table by table, to read and never to edit; what the app saved, as a plain list to download from; and its keys and passwords, which the owner can enter and replace - the ones the app is waiting for first, and the overview asks for them too. Entering one there switches secrets on if the agent left them off. The full view is every section, as before. In both, a browser that holds the admin token has one more section after all the others, Admin, which nobody else sees (see Administration).

Each lambda says which view it opens in, view - Full or Simple - set when it is created (POST /lambdas, create_lambda) and changed later (PATCH /lambdas/:privateKey, update_lambda). The build agent creates its lambdas Simple; everything else defaults to Full. That is only the default: whoever switches at the foot of the sidebar (in the menu on a phone) has chosen for themselves, for that lambda, which is kept in their browser and never sent anywhere - so an operator looking at somebody's lambda in the full view leaves it simple for its owner.

Analytics

An owner can have the visits of a lambda counted, and reads them in the editor's Analytics section, in both views: visitors, page views and requests per day, and the pages, referring sites, countries, devices, browsers, operating systems and crawlers behind them - search engines, AI assistants, chats fetching a preview. Opt-in: nothing is counted until the owner switches it on (PUT …/analytics, or the analytics tool when the user asks an agent), and switching it off (DELETE …/analytics) deletes what was counted. A demo's visitors are not counted, and neither is a feature's preview.

  • By day, in UTC, kept for a year. How much of the year the owner sees is a limit of the tier - seven days for a free lambda, all of it for a premium one (see Limits) - so a lambda that becomes premium shows what was counted before it was. A range of the same length before is summed for a comparison only where the tier shows it as well.
  • Counted in front of the lambda, written behind it. A request to a lambda or a preview is made into one visit (LambdaVisit: its status, its time, its bytes, its path, its caller's country and agent) by LambdaVisitConcern, which only makes it and leaves it on the request. Whatever wants to know about requests to lambdas reads it rather than measuring again: the overview's figures (LambdaActivityConcern), the request log (CallerConcern), and AnalyticsConcern, which puts it on a lock-free queue where the owner counts visits. Each leaves out what is not its own - the figures and the analytics previews. AnalyticsJob empties the queue every LAMBDA_ANALYTICS_INTERVAL_SECONDS (five minutes), sums it into a row per lambda, day and value, and writes those in one transaction. Nothing is written while a visitor waits; visits not yet written are lost when the server stops. The queue holds 250,000 visits; what does not fit is dropped and logged.
  • What a visit is counted as. A hit is every request answered; a view is somebody opening a page - a browser's navigation (Sec-Fetch-Dest: document, a revalidation included) or, from a program that does not send that header, a successful GET answered with HTML - unless it asked for a file that is no page (/favicon.ico, an image, a script, a style sheet, …), which a single page app answers with its page too. Of the views, those of programs - crawlers, and whatever names no browser - are counted apart as well (bot_views, botViews in the answer), everywhere a view is: the editor shows the pages people opened and adds the bots' behind a switch (Include bots), off by default and remembered in the browser, worked out from the one answer so it switches at once. The referring sites stay people's either way: a bot names no page it came from and would be counted as typed in. A path is counted where it was answered without an error, at most 200 a day, and the rest under (other), so a scanner cannot fill a day. A referrer is kept as its host, without www., and only where it is another site than the one visited (100 a day). Country, device, browser and system come from the address and the User-Agent the request line already has, and from the client hints (Sec-CH-UA, -Mobile, -Platform) where a Chromium browser sends them - its agent no longer says which phone or system it is on. Hints are only believed from an agent that is Chromium's, and only where they name a browser or system that is known, so nobody writes words of their own into an owner's figures (VisitorClassifier).
  • Two groups in the editor, by what they count. The pages, the referring sites, the countries, the devices, the browsers and the systems are counted in page views, under that heading; what was requested by path and the crawlers in requests, under theirs. The two can be far apart - a crawler asks for a hundred things that are no page - so a card never leaves the reader to guess which it is. A lambda without pages people open has the requests group only.
  • Crawlers, hardened. A program is told from a person by crawler-user-agents (MIT, Resources/Crawlers), a maintained list of about 1,500 crawlers with a kind each - search engine, AI crawler, link preview, monitoring, scanner, HTTP library and so on - and counted under that kind, never as a browser or a visitor. Google, Bing, OpenAI, Apple and Perplexity publish the addresses their crawlers come from; a visit is only counted as theirs when the connection came from one of them, and as an unconfirmed claim otherwise
    • anybody can send Googlebot. The address checked is the connection's, never a forwarding header's, so an installation behind a proxy confirms none. CrawlerJob fetches the list and the addresses daily into /data/crawlers (LAMBDA_ANALYTICS_CRAWLER_LISTS, LAMBDA_ANALYTICS_CRAWLER_REFRESH_HOURS), the list of the release standing in until it has. Matching is linear in the length of the agent whatever it says: the list's plain text is looked for in one pass (SearchValues), its few expressions run without backtracking, and only 500 characters of an agent are read.
  • Visitors without keeping anybody. A visitor is their address and their agent, hashed with a key made at random per day and lambda, held in memory for today and yesterday only (DailyVisitors), and only a person who opened a page is one: a request for anything else - a script, an API call, a path a scanner guessed - makes nobody a visitor, whatever agent it names, so there are never more visitors than page views. So a visitor counts once a day; a restart counts somebody again who comes back the same day, and an installation that keeps no addresses (LAMBDA_LOG_CLIENT_ADDRESS=false) counts no visitors. No cookie, no script in the lambda.
  • A database of its own. analytics.db beside lambda.db, a star: lambdas, and a table of facts per statistic - days (the totals), pages, referrers, countries, devices, browsers, systems, crawlers - keyed by lambda, year, month and day (and value), so a lambda's days are a range of the key. Migrated by Evolve from Services/Analytics/Migrations. Only the switch is in lambda.db (lambdas.analytics_since), read with everything else that serves a request. Deleting a lambda deletes its rows.

Taking a lambda away

The code belongs to whoever made the lambda, so GET …/export (the editor's Download) packs its newest version into an ordinary .NET 10 project that runs without this platform (Services/Deployment/ProjectPacker.cs):

FileWhat it is
Program.csthe default GenHTTP host, Host.Create().Handler(Project.Create()).Defaults().RunAsync(), under a header naming the lambda, its version and change, the export date, where to read about GenHTTP, and the environment variables its secrets become (as -e flags in the docker run line too)
Project.cslambda.cs: its statements are the body of Project.Create() (CreateAsync() when they await), its types sit beside the class, made public as on the platform
*.csthe other C#, in its folders, its code unchanged, the first letter of the name capitalized
Platform/what the platform provided: Workspace and Resources as folders (with Assets beside it where the code still says that), Secret reading environment variables of the same name (the values are never exported), Database opening database/database.db where the code uses one, the switch that turns what Project returns into a handler, and the imports every lambda gets as global usings
resources/its resources, copied beside the program on build
docs/, tests/, …the rest of its code where it was - its documentation, its tests, what it is built from; the project compiles every .cs file but the resources and what dotnet and the app write (EnableDefaultItems is off), and the image takes only what it compiles and copies
database/database.dbthe lambda's database, an ordinary SQLite file - its records go with it
Dockerfilebuilds and runs it; the workspace is /app/workspace, the database folder is mounted at /app/database

It references GenHTTP.Full - the internal engine, which runs wherever .NET does - at the version this server runs, so it behaves as the lambda did, and Microsoft.Data.Sqlite, Microsoft.EntityFrameworkCore.Sqlite and Evolve only where the code uses them - Entity Framework where it has a DbContext, at the release of the SQLite library for .NET 10. Of the data, the database comes along - copied with SQLite's backup, so it is whole even while the lambda writes to it, and any SQLite tool reads it - while the workspace starts empty and the values of the secrets stay here. database/ is in .gitignore and .dockerignore: records are nobody's source, and the image mounts them rather than carrying them. The archive is packed into a temporary file and streamed from there, since a database may be as large as the tier allows. A test builds the export of every demo with the .NET SDK, so it needs the package feed.

Publishing the source

An owner may publish the code of a lambda as open source: under Open source in the editor, in both views, right after the showcase, or with the open_source tool - and only with the editor key, like everything else about a lambda. Nothing is published until they ask, and taking it down again is one switch. Publishing asks for a license, MIT unless another is picked from a short list - MIT, Apache 2.0, BSD 3-Clause, MPL 2.0, GPL 3.0 or later, AGPL 3.0 or later, the Unlicense (SourceLicenses) - and, optionally, the name the license gives as the holder of the copyright; without one it names "the authors of" the lambda, since there are no accounts to take a name from.

What is published is the program and what is written about it: every version, as the export packs it - Project.cs, the rest of its code, resources/, Platform/, the project file, the Dockerfile - with the license in LICENSE and named in the header of Program.cs. The history says what each version changed, in the line it was saved with. What is never published:

  • The data. No database - not even an empty one - no workspace, no value of a secret. ProjectPacker.Publish takes no database to pack, so there is nothing to leave in by mistake, and a test looks for a record, a saved file and a secret's value in every file of the download.
  • What only the owner may know. Not the specification - what was asked for, in the owner's words - not the editor key, and nothing about who uses it: no traffic, no log, no visitor.

/source lists every published source, searched by its key, what its documentation says it is and what the showcase says about it where it is there, ordered by stars, by the newest version or by when it was published. /source/:publicKey is one: what it is, its license, who holds it, where it runs - its own domain while it has one - and four views of the version being read: its code, a tree of the files marked by what each is to the lambda with the file open beside it, coloured by highlight.js with the editor's colours, and lines to link to (#L12-L20); its documentation, the pages of docs/ rendered as the editor renders them, pictures beside them included; its tests, tests/README.md with the scripts and data beside it; and its changes, every version newest first. The newest version is read unless another is picked, which the address keeps (?version=7) from one view to the next. Any version downloads as a zip. There are no issues, pull requests or wikis, and no way to act on the lambda from there; somebody who wants to build on it downloads it, and platform_guide tells an agent how a download becomes a lambda again. A small notice says what a lambda is and leads to /build. The pages are a part of the frontend of their own (src/Frontend/src/source), in a frame of their own without the site's menu, fetched with their words and highlighter only by whoever opens them, in every language under the same addresses as every other public page. They are not prerendered: the server names each after its lambda, describes it with the first paragraph of its documentation, marks it up as SoftwareSourceCode for search engines, and lists every published source in the sitemap in every language. Since what a page shows is fetched from /api/v1/sources/, robots.txt allows crawlers that part of the API (and the showcase pictures), while it keeps them out of the rest of it and out of the zips - otherwise a crawler that renders the page would find it named, and empty. A source that is not published is not found, the same as a key nobody has.

A version is packed once, on the first request for it, and kept below /data/sources/{lambda} - a cache and nothing more, since the versions are what the source is. What it is packed with besides the version - the license, the holder, the key, the build of the packer - is hashed into the name of the file, so changing the license packs again and the old one goes. Packing reads and compresses a whole version, so one version is packed once however many ask for it at the same moment, two at a time for the whole server, and the cache is held to LAMBDA_SOURCE_CACHE_BYTES by letting go of the projects read least recently. The page shows that a version is being packed, and a download waits for it before it starts. A file of a version is read out of its zip as it is sent; one sent as it is (…/raw/) comes from the site's origin, so it is sent as text or bytes - a picture as a picture - never sniffed, and under a sandboxing content security policy, so a page a lambda ships cannot run here.

Anybody may star a source. A star is a POST with a ticket the page was handed with the source, signed by the server and at least a second old, so a link, a crawler or a script posting blind stars nothing. An address stars a source once and changes only so many stars in ten minutes; which address starred what is kept in memory as a keyed hash, never on disk, and forgotten on a restart. The count is in the database (sources.stars) and survives taking the source down and publishing it again.

The export of a lambda whose source is published carries the same LICENSE. The demos are published under MIT by the installation, being there to be read and built on; nobody else can change that, and their editor shows no Open source section.

Working with git

Every lambda is a git repository as well, so whoever works on it with their own tools and agent clones it, changes it and pushes - and whoever reads a published source clones every version of it. Both are served by the [GenHTTP.Modules.Git][git-module] package from what the platform already keeps: there is no repository on disk, only the commits the versions and the features are.

git clone https://genhttp.dev/editor/<editor key>/my-app.git
cd my-app && dotnet run                     # the app on http://localhost:8080/
# change it, commit
git push -o deploy                          # the next version, online

The owner's address is the editor's with a name added, which only names the folder a clone makes: any name answers, so a clone keeps working once the public key changes. It holds the editor key like the editor's address does - there are no accounts, and the key is what lets a push in. The published source is /source/<public key>.git, read only, while it is published. Both answer without the .git, and opened in a browser they lead to their page. The editor offers the address under Clone - in the full view only, on the overview and beside the code, and in a draft with its branch - and /source in the Code menu beside the star, where the zip is too.

What is in it. The project the lambda is exported as, made for a repository: Project.cs is the snippet as the body of BuildAsync() - the method the platform runs it in - with its types beside the class, the other .cs files keep their code and are named the .NET way, and every other file is where the lambda has it: the rest of its code in its folders, its resources in resources/. Around them are the platform's files: Program.cs, the project file, Platform/, the Dockerfile, .gitignore, .dockerignore, AGENTS.md - how an agent works in the repository, with the rules of the platform in git's terms - CLAUDE.md, which points Claude Code to it, and LICENSE while the source is published. Those are the same in every commit until the platform changes them - nothing in them depends on the version, which is why the project references SQLite, Entity Framework and Evolve whether the code uses them or not, compiles every .cs file of the code and nothing among the resources, and makes the snippet asynchronous whether it awaits or not - so a push that leaves them alone is always right. The .gitignore at the root is the platform's, and takes build/ back in, which many ignore everywhere; what a tool installs and builds in a folder of the code is kept out by that folder's own. The data is never in it: no database, no workspace, no secret. Nor is what only the owner may know - no specification, no editor key - so the commits the owner reads are the commits a published source shows.

The history. main is the newest version, each version a commit tagged v1, v2 and so on, its message the change line; a version pruned is gone from the history too, which a clone is told is shallow from there. Each feature is a branch, named once when it starts - after the branch that was pushed, or after its name in the words git allows (dark-mode) - and kept when it is renamed (features.branch); a save of the feature is a commit on it, and moving its base past what it holds is a merge commit bringing that version in. The commits the platform makes are made when the repository is read, not when a version is saved, so saving stays what it was and a lambda nobody clones costs nothing; each is made once from what it stands for - the files, the time it was saved, its change - and kept below /data/git/{lambda} with the files it holds, by the id git gives them, so it is the same commit for everybody who reads it later, a newer platform included. A commit whose code did not change keeps the Project.cs of the one before it, so a class somebody pushed in a way of their own is not written over by a save in the editor. The published source is the same history without the features: their branches are not there, and their commits are not found by id either.

Pushing to main. Every commit becomes the next version, in order, with the origin git, the first line of its message as the change and the rest as the specification. main only moves forward one commit after another - no force push, no merge commit, no deletion - and a version saved meanwhile in the editor or by an agent makes the push "fetch first". A push holding more commits than the tier keeps versions is refused, asking for them to be squashed. The newest is compiled first, the code guard included, and a push that does not compile is refused - its problems come back with the file and line in the repository, Project.cs(14,9). -o deploy puts the newest online. A feature whose branch is now in main is merged and goes, as merging it would have done.

Pushing a branch. A new branch starts a feature from the newest version among its commits, with a copy of the lambda's data; pushing it again replaces its files, a force push included, and a branch that holds none of the versions is refused. Every push puts its preview online - nothing a visitor sees - and answers with its address. -o merge with a push merges it as one version, -o deploy beside it puts that online, and deleting the branch deletes the feature. A feature changed in the editor since the client fetched refuses the push rather than losing that change.

What is refused. Any file, in any folder, is a file of the lambda's code or its resources, so a push may add whatever the code is to keep - a README.md at the root, the sources of a front end, an editor's settings. A push that changes, adds or removes one of the platform's files, or adds a file with a name a lambda cannot hold - spaces, .lambda/, assets/ - is refused and says which and how to undo it: changing the platform's would change nothing of the lambda, and silently dropping the change would leave the commit saying something the lambda does not. So is an executable file or a link, a tag, Project.cs with something in class Project besides BuildAsync(), and anything the services refuse a save - names, sizes, a demo. Everything a push does goes through the services the editor and the agents use, under the same rules.

What it says. A push is answered as it goes, in the lines git prints after remote:: the versions or the feature it became, problems the compiler found, what went online and where, and what to do next. They never hold the editor key - whoever pushed has it, and whoever reads their build log should not. Options travel only with a push that changes something, so a version already pushed is deployed in the editor or with POST /api/v1/lambdas/:privateKey/deployment/start, which the answer names.

Lambdas from before. A lambda's history was laid out as the project was exported then - what it served in assets/, and the platform's files around it saying so. Commits are never made again, so none of those changed; instead the newest version gets a commit of its own on top, which moves assets/ to resources/ and brings the platform's files up to date, and main is that commit (GitHistory.MoveAsync, recorded as moved in the index). A version saved afterwards is a commit on top of it, and a feature's branch gets the same commit on top of its tip. A clone made before pulls it like any other commit; a push built on a commit laid out the old way is refused, saying to pull first.

Threads and limits. Reading a lambda for the first time reads every version, so the commits are made on the pool, in the lambda's turn - a semaphore, one request at a time per lambda. The server packs what a clone asks for, and applies a push, while it writes its answer, so that answer is written on the pool into a pipe the reactor copies to the connection (OffloadedContent). A push is unpacked in memory, up to LAMBDA_GIT_MAX_PUSH_BYTES, and only two are applied at once.

How it is put together

A single .NET 11 project hosted by GenHTTP.Full.Ioxide, wired up in Application.cs and divided into services, a folder each below Services/. From outside its folder a service is taken only by its interfaces - by the API resources, the MCP tools and the other services alike - and the classes a service is made of stay in its folder; ServiceBoundaryTests fails on a constructor or method elsewhere that takes one of them. Where other services work through a lower layer of a folder rather than the service the API calls, that layer has an interface of its own (IDatabaseVault, ISecretVault, IDomainRegistry, ILogBook, IRunLog).

  • Meta (Services/Meta) - lambdas and their versions, the only component that speaks to the database. Its public surface is DTOs, mapped by hand. MetaService is what the API, MCP and the other services call. It finds the lambda, checks it may be changed, takes the lambda's turn for a change, and leaves the rest to units: LambdaHosting changes the key, the domain and the tier, LambdaHistory writes the versions, the stretches online and the events and keeps each to its bound, LambdaLifetime says when a lambda goes offline and away and runs the sweep that does it, LambdaRemoval removes a lambda with everything that hangs on it, and LambdaDescriber maps the DTOs. LambdaGuard (who may change a lambda - not a demo) and VersionInput (what a version may hold) are rules other services apply too.
  • Storage (Services/Storage) - the code itself, on the file system, one file per version. Never in the database.
  • Data (Services/Data) - which kinds of data a lambda keeps, switched on and off by its owner. Only the choice is stored: a lambda without a row for a kind has its default, so a kind added to DataKinds needs no migration to exist. Switching a kind off deletes what it held.
  • Workspace (Services/Workspace) - the private directory of a lambda, reached from the editor. The same directory the generated Workspace class writes to from inside a lambda, under the same limits, so a file put there by hand behaves like one the lambda wrote itself. Only the room it takes is limited - in any number of files of any size - counted in blocks of 4 KB as the disk counts it, so an empty file or a folder is not free. It shares the room of the lambda's data with its database: the data's allowance is compiled into the lambda - so moving it to another tier builds it again on its next request - and the size of the database is asked as a file is written, through a function handed to it. WorkspaceVault (IWorkspaceVault) measures what a workspace takes, keeping the answer a few seconds, and is what the database asks in turn for the room it may grow to (DatabaseVault.RoomOf).
  • Secrets (Services/Secrets) - the secrets of the lambdas, sealed in the database. SecretCipher holds the installation's key and seals and opens values, SecretVault stores them and is what a running lambda reads through and the other services keep in step with a lambda (ISecretVault), and SecretService is what the API and MCP call - by name, never by value.
  • Databases (Services/Databases) - the database of each lambda, a SQLite file below /data/databases/{lambda}, and a copy per feature beside its files. DatabaseVault makes, copies, exports and deletes them and is what a running lambda connects through (IDatabaseVault for the other services), ConnectionGuard is the authorizer and the limits every connection it hands out carries, and DatabaseService is what the API and MCP read tables and rows with.
  • Features (Services/Features) - changes worked on beside a lambda. A row per feature says what it is based on and whether its preview is online; its files (files.json), what its preview serves (preview.json), its copy of the workspace and its preview's resources live below /data/features/{lambda}/{feature}, so deleting a feature is deleting a folder. Merging goes through Meta, under the same lock every save takes.
  • Source (Services/Source) - whether the owner published the code of a lambda and under which license, and its stars (sources); SourceCache packs a version into the project a visitor reads and downloads, once, below /data/sources/{lambda}, and StarGuard hands out the tickets a star is given with and remembers who starred what. Kept apart from Meta like the showcase: nothing about building a lambda reads or changes it.
  • Git (Services/Git) - every lambda as a git repository (see Working with git). GitService finds the lambda and takes its turn, GitHistory makes the commits the versions and features are, GitPushes makes versions and features of what is
How do I install GenHTTP Lambda?
Copy the http config for Cursor, Claude Desktop, Claude Code, or VS Code on this page. It uses the publisher's install, not a PlugMCP proxy.
Does GenHTTP Lambda need an API key?
No required environment variable is listed on this record.
What transport does GenHTTP Lambda use?
GenHTTP Lambda is reached over HTTP.
When was GenHTTP Lambda last checked?
Last checked 2026-10-16. Write, deploy and host small C# web services and sites at a public address.

No tool list yet. Remote servers get one after the first health check.

plugmcp

Plug any tool into any agent. MCP servers for people who do not write servers. Gateway for volume, n8n for apps.

  • github.com/TODO
  • x.com/TODO
  • linkedin.com/company/TODO

PlugMCP, 2026

Product
  • Browse
  • Install
  • Host your own
  • Pricing
  • Voice mode
  • Browser
Directory
  • All MCP servers
  • MCP Apps
  • Tools
  • Agent skills
  • Categories
  • New
  • Voice
  • Submit a server
Developers
  • Docs
  • Blog
  • Club
  • Work with us
  • What is MCP
  • What is WebMCP
  • Status
Company
  • About
  • Contact
  • Terms
  • Privacy
  • DPA