GenHTTP Lambda

MCP serverCloud & infra

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

Available today. Use it from your connected AI after setup.

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/mcp

    Add a custom connector in Settings, paste this address, and approve the sign-in.

  • Cursor

    cursor://anysphere.cursor-deeplink/mcp/install?name=genhttp-lambda&config=eyJ1cmwiOiJodHRwczovL2dlbmh0dHAuZGV2L21jcCJ9

    Open the link and Cursor adds the server at that address.

  • ChatGPT

    https://genhttp.dev/mcp

    In 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

RouteServes
/the landing page
/editor/createthe creation assistant
/editor/:privateKeythe editor for one lambda
/lambda/:publicKeythe deployed handler
/features/:featurethe preview of a feature, while it is online
any path, at a lambda's own domainthe deployed handler of a premium lambda with that domain
/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/. 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.

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 lambda as a runnable project (zip)
GET / POST /lambdas/:privateKey/versionslists versions, saves a new one (optionally with specification and change)
GET /lambdas/:privateKey/versions/:versionreads one version
GET /lambdas/:privateKey/versions/:version/zipone version's files as a zip
POST /lambdas/:privateKey/versions/zipsaves a zip of all files as a new version
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
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; switches it on; switches it off and deletes what it held
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; 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
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
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
POST /builds, GET /builds/:idthe text box on /build
GET /systemterms, 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 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 - 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 calls PrepareAsync() 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) - an IHandler, 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_lambda
  • update_lambda
  • write_code
  • change_code
  • create_feature
  • update_feature
  • merge_feature
  • delete_feature
  • check_code
  • deploy
  • read_lambda
  • read_logs
  • upload_file
  • list_files
  • delete_file
  • showcase
  • list_demos
  • platform_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