GenHTTP Lambda
MCP serverCloud & infraWrite, deploy and host small C# web services and sites at a public address.
Available today. Use it from your connected AI after setup.
No other account needed.
Add ahel to your AI once: Claude, ChatGPT, Cursor, Claude Code or Codex. Then ask it to use this.
Then ask your AI: use GenHTTP Lambda to create lambda
Install GenHTTP Lambda
The server’s own address, for the clients that take one directly. Or connect ahel onceand every client you use reads it from one address, with the account kept on ahel rather than in each client’s config.
Claude Code
claude mcp add --transport http --scope user genhttp-lambda 'https://genhttp.dev/mcp'Run it once in your project, then open /mcp to approve any sign-in the server asks for.
Claude Desktop
https://genhttp.dev/mcpAdd a custom connector in Settings, paste this address, and approve the sign-in.
Cursor
cursor://anysphere.cursor-deeplink/mcp/install?name=genhttp-lambda&config=eyJ1cmwiOiJodHRwczovL2dlbmh0dHAuZGV2L21jcCJ9Open the link and Cursor adds the server at that address.
ChatGPT
https://genhttp.dev/mcpIn Settings, enable Developer mode, create an MCP app, and paste this address. Your plan and workspace must allow custom apps.
Codex
codex mcp add genhttp-lambda --url 'https://genhttp.dev/mcp'Run it once, then sign in with codex mcp login genhttp-lambda if the server asks for an account.
From the project's README
As published by mda2av/genhttp.lambda in README.md.
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 lambda workspaces - lives in a named
volume mounted at /data.
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 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
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
| Route | Serves |
|---|---|
/ | the landing page |
/editor/create | the creation assistant |
/editor/:privateKey | the editor for one lambda |
/lambda/:publicKey | the deployed handler |
/features/:feature | the preview of a feature, while it is online |
| any path, at a lambda's own domain | the deployed handler of a premium lambda with that domain |
/api/v1/ | everything the editor calls, see below |
/mcp | the same, for agents |
/admin | every lambda on the server, for whoever runs it |
API
Browsable at /api/v1/scalar/. 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.
| Endpoint | Does |
|---|---|
POST /lambdas | creates a lambda (optionally with view: Full or Simple) |
GET / PATCH / DELETE /lambdas/:privateKey | reads, changes (its key, its view), removes it |
GET /lambdas/:privateKey/export | the lambda as a runnable project (zip) |
GET / POST /lambdas/:privateKey/versions | lists versions, saves a new one (optionally with specification and change) |
GET /lambdas/:privateKey/versions/:version | reads one version |
GET /lambdas/:privateKey/versions/:version/zip | one version's files as a zip |
POST /lambdas/:privateKey/versions/zip | saves a zip of all files as a new version |
POST /lambdas/:privateKey/versions/changes | changes some files of the newest version, as a new one |
GET /lambdas/:privateKey/deployment | what is online, and until when |
POST /lambdas/:privateKey/deployment/start / stop | puts a version online, takes it off |
GET /lambdas/:privateKey/deployment/history | every stretch it was online, and what ended it |
GET /lambdas/:privateKey/summary | the dashboard: state, traffic, problems, storage |
GET /lambdas/:privateKey/traffic | requests by minute and quarter hour, statuses, paths |
GET /lambdas/:privateKey/logs | its own log, followed with ?since=; no visitor addresses |
GET /lambdas/:privateKey/agent | the agent's change of it - under way, or the last one - and how many are left today |
POST /lambdas/:privateKey/agent/start / stop | asks the agent for a change, stops it |
GET /lambdas/:privateKey/data | the kinds of data it keeps, and how full each is |
GET / PUT / DELETE /lambdas/:privateKey/data/:kind | one kind; switches it on; switches it off and deletes what it held |
GET /lambdas/:privateKey/files | lists the workspace |
GET / PUT / DELETE /lambdas/:privateKey/files/:path | one file, its path encoded (a%2Fb.txt), as base64 up to 32 MB |
GET / PUT /lambdas/:privateKey/files/:path/content | one file as it is, streamed, however large |
PUT /lambdas/:privateKey/folders/:path | makes a folder |
GET / POST /lambdas/:privateKey/features | lists the features, starts one (name, specification, base) |
GET / PATCH / DELETE /lambdas/:privateKey/features/:feature | one feature with its files; changes its name, notes or base; deletes it |
PUT /lambdas/:privateKey/features/:feature/files | replaces its files (?deploy=true puts its preview online) |
POST /lambdas/:privateKey/features/:feature/changes | changes some of its files |
GET / PUT /lambdas/:privateKey/features/:feature/zip | its files as a zip; a zip put back into it |
POST /lambdas/:privateKey/features/:feature/preview/start / stop | puts its preview online, takes it off |
POST /lambdas/:privateKey/features/:feature/merge | makes it the next version and deletes it (deploy to put that online) |
GET /lambdas/:privateKey/features/:feature/logs | what its preview has been doing |
GET /lambdas/:privateKey/features/:feature/data, POST …/data/refresh | its copy of the data; a fresh copy of the lambda's |
…/features/:feature/workspace[/:path[/content]], …/folders/:path | its copy of the workspace, as files and folders are the lambda's |
POST /lambdas/:privateKey/code/check | compiles without saving |
POST /lambdas/:privateKey/code/semantics, completions, definition | what the editor asks the compiler |
GET /keys/:publicKey | whether a key is free, and if not, online |
GET /demos | the demos, and the keys to read them with |
POST /builds, GET /builds/:id | the text box on /build |
GET /system | terms, limits, starters, build agent |
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 is part of its URL 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.
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.
Versions, features and data
A lambda keeps three kinds of things, and they live differently.
A version is the program: every C# file and every asset, the front end
included. 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
(LAMBDA_MAX_FEATURES); a demo has none. Each holds a full copy of the
workspace, 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: for now the workspace, a private directory
of files, with a database and secrets meant to follow. 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 its owner switches that kind of data off, which deletes what it held -
the copies features work on included. Each kind is switched on by the owner
(PUT …/data/:kind); 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 editor has a Data section of its own, beside Files, which
holds the files of a version.
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 assets 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 code, 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.
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.
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.
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, Change, the drafts (once there
are any), a history, the showcase and the domain. Its overview is the app:
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 version, a file or a log: 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. The full view is every
section, as before.
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.
How it is put together
A single .NET 11 project hosted by GenHTTP.Full.Ioxide, wired up in
Application.cs and divided into
services that the API resources talk to through interfaces:
- Meta (
Services/Meta) - lambdas and their versions, the only component that speaks to the database. Its public surface is DTOs, mapped by hand. - 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 toDataKindsneeds 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 generatedWorkspaceclass 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 - 256 MB for most lambdas and 2 GB for a premium one, 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. The quota is compiled into the lambda, so moving it to another tier builds it again on its next request. - 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 assets 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. - Deployment (
Services/Deployment) - wraps a snippet in a method body, compiles it with Roslyn, loads the assembly and callsPrepareAsync()on the resulting handler. Compiled once, then cached - one handler for what a lambda has online, and one for the preview of each of its features. - Execution (
Services/Execution) - anIHandler, not a web service: it looks up the handler for a request and runs it. - Protection (
Services/Protection) - concerns in front of execution that resolve the lambda, rate limit per client and cap concurrency. - Background (
Services/Background) - a small scheduler that undeploys free tier lambdas a day after their last deployment and removes them after 30 days without a save.
The editor asks the compiler what the code means rather than guessing from how
it is spelled. Services/Deployment/Compilation/SemanticClassifier.cs says
what each name is, so a type, a method, a parameter and a local are coloured
apart, and CompletionResolver.cs answers what may be written where the caret
sits - including what follows a dot, which needs the type of what precedes it
and therefore needs a compiler. Both bind without emitting; the browser's own
grammar colours each keystroke in the meantime so nothing waits on the network.
Snippets are compiled against the GenHTTP module surface with its namespaces
already imported, so no using directives are needed. A guard
(Compilation/CodeGuard.cs) rejects code that reaches for the file system,
processes, reflection, sockets or the internals of this application before it
is ever compiled, and a lambda that does need files gets a directory of its
own under /data/workspaces.
Compiled lambdas run in the server process. The guard raises the cost of misbehaving; it is not a sandbox, which is why the container runs unprivileged and the seccomp profile is left alone.
Configuration
Everything is read from the environment on startup, see
LambdaOptions.
Shortened here. Read the whole README on GitHub.
Tools it offers (18)
What this server listed when ahel dialed its public endpoint in Sep 2026, with no key and no account of yours. The names are the server’s own.
create_lambdaupdate_lambdawrite_codechange_codecreate_featureupdate_featuremerge_featuredelete_featurecheck_codedeployread_lambdaread_logsupload_filelist_filesdelete_fileshowcaselist_demosplatform_guide
Signals
- GitHub stars
- 1
- Last commit
- Sep 2026
Advanced
- Delivery
- lambda MCP server → your ahel connector (mcp.ahel.ai) → your AI.
- Item type
- mcp-server
- Key
dev-genhttp-lambda- Source
- github.com/mda2av/genhttp.lambda
- Hosted endpoint
https://genhttp.dev/mcp