Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 29 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -703,7 +703,9 @@ how to write apps and to give them a good starting point.
(<https://genhttp.org/documentation/content/>) and use its **high-level APIs
wherever possible** rather than inventing their own functionality.
- **When you add functionality to the platform, add it to the demos as well if
it makes sense**, so agents learn it by reading them. Every demo has the three
it makes sense**, so agents learn it by reading them. `demo-crud` is where a
lambda offers tools to AI agents (`Tools.cs`, over the same store as its API)
and describes its API in the words of the documentation attributes. Every demo has the three
pages of its documentation and tests and a script its tests run
(`EveryDemoSaysWhatItIsWhyAndHowItIsTested`); a new demo gets them too. They
keep their records in the database, migrated with Evolve and read and
Expand Down Expand Up @@ -902,13 +904,31 @@ change; the helpers are in `OperationLog`.
- API reads are not logged (the editor polls), apart from exports and
downloads; an agent's reads through MCP are.
- A refused request is not logged here - its request line has the status.
- **The MCP module's own lines are filtered below warnings** (`Program`): it
writes one per tool call beside the tool's own, and one per refusal. A tool
that fails is an error, and is written down.

### Frameworks: GenHTTP and Ioxide

- We run on GenHTTP and its Ioxide engine. **If you find a bug in either, flag it
clearly** - say what it is, where, and a reproduction - **instead of working
around it**, so it can be fixed at the source. Tell the owner in your answer;
do not bury it in a comment.
- **Use what the framework offers the way it is meant to be used - decided by
the owner** ("embrace it"). When GenHTTP brings a module for something the
platform does by hand, move onto it rather than keep a protocol of our own
beside it; what it lacks is a bug or a feature to ask for at the source.
- **The MCP endpoint is GenHTTP's MCP module.** `/mcp` is `McpEndpoint` over
`GenHTTP.Modules.Mcp`, the tools are the methods of `McpTools` marked
`[Tool]` - their `[Summary]` the description, their typed parameters the
arguments - and a fixed limit of an argument is an attribute on it (`[Range]`,
`[MaxLength]`, `[AllowedValues]`), advertised and checked by the module, not
a check of our own. The module takes every exception but its sealed
`ProviderException` for a fault, so a refusal of the platform
(`LambdaException`) is turned into an error that says why in a sentence
(`Refusing`), never left to be logged as a crash. Its protocol - versions,
notifications, origins - is the framework's and is not tested here; what we
configure (`LAMBDA_MCP_ORIGINS`, the instructions) is.
- **Do not write tests for the frameworks.** Test GenHTTP Lambda only.
- The framework source is normally checked out beside this repository
(`../GenHTTP`). Read it instead of decompiling the NuGet packages.
Expand Down Expand Up @@ -1044,7 +1064,7 @@ touches all that apply:

The same rule is worded the same way in all of them.

The MCP instructions (`McpHandler`) are **short**: how to get from nothing to
The MCP instructions (`McpEndpoint`) are **short**: how to get from nothing to
something online, how a lambda that exists is changed, and the rules an agent
would otherwise break before it reads the guide (where data goes, the
database, waiting for tasks, relative paths, secrets, documentation, pages
Expand Down Expand Up @@ -1077,7 +1097,13 @@ GenHTTP's documentation attributes beside the code they describe.
key, a demo's refusal) is a constant of `ApiDocumentation`.
- **Limits the operator sets are never written as numbers**: say where to
read them (`GET /system`, the summary's `limits`). Fixed ones (100 secrets,
32 KB a value) may be.
32 KB a value) may be. The same holds for the MCP tools, whose descriptions
are fixed when the endpoint starts: a limit of the panel is named where to
read it (`platform_guide`), never written into one.
- **Authentication describes itself.** The admin token's concern declares the
`adminToken` security scheme and the 401 and 403 it answers; an operation
behind it says nothing about the token of its own. Everywhere else the
editor key in the path is the credential, and no operation names a scheme.
- `ApiDocumentationTests` refuses an operation without a summary, a parameter,
answer, model or field without a description, a call about a lambda that
does not say how an unknown key is answered, and a tag in no group. A new
Expand Down
6 changes: 3 additions & 3 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@

<ItemGroup>

<PackageVersion Include="GenHTTP.Full.Ioxide" Version="11.1.0" />
<PackageVersion Include="GenHTTP.Engine.Kestrel" Version="11.1.0" />
<PackageVersion Include="GenHTTP.Full.Ioxide" Version="11.2.0" />
<PackageVersion Include="GenHTTP.Engine.Kestrel" Version="11.2.0" />

<!-- the tests' client and host; no longer carried by GenHTTP.Full.Ioxide since 11.1 -->
<PackageVersion Include="GenHTTP.Testing" Version="11.1.0" />
<PackageVersion Include="GenHTTP.Testing" Version="11.2.0" />

<!-- serves every lambda as a git repository, see Services/Git -->
<PackageVersion Include="GenHTTP.Modules.Git" Version="0.1.1" />
Expand Down
41 changes: 34 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,14 @@ 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.

A lambda may offer tools to AI agents itself, over the Model Context Protocol:
`Tools.Create()` with a class whose methods are marked `[Tool]`, added at a
path such as `mcp`, which an agent then connects to. And a webservice of its
own describes itself as OpenAPI, in the words of `[Summary]`, `[Remarks]`,
`[Tag]` and `[Response]` on its routes and records. The MCP module and those
attributes are imported for every lambda and in its export; `demo-crud` shows
both, and the guide says how (`toolsForAgents`, `describingAnApi`).

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.
Expand Down Expand Up @@ -1361,7 +1369,7 @@ server's configuration and are set in the administration panel while it runs
| `LAMBDA_LOG_GEO_REFRESH_HOURS` | `168` | how often the registry files are refetched |
| `LAMBDA_LOG_GEO_PLACES` | `true` | also fetch the town and network databases |
| `LAMBDA_LOG_MAX_LINES_PER_REQUEST` | `200` | before one request's output is cut off |
| `LAMBDA_MCP_ORIGINS` | - | hosts a browser may use `/mcp` from |
| `LAMBDA_MCP_ORIGINS` | - | hosts (or origins) a browser may use `/mcp` from |
| `LAMBDA_SECRETS_KEY` | `secrets.key` | the installation's half of the key secrets are sealed with, see [Secrets](#secrets) |
| `LAMBDA_PUBLIC_URL` | - | canonical address, enables the sitemap |
| `LAMBDA_HOSTING_URL` | `http://localhost:{LAMBDA_PORT}` | the domain the lambdas answer below, a subdomain each: `https://genhttp.run` |
Expand Down Expand Up @@ -1699,8 +1707,21 @@ enters it.

There is a Model Context Protocol endpoint at `/mcp`, so an agent can build
something here and put it online without a person driving the editor. It is
JSON-RPC over a single path, taking POST; a GET is declined with 405, because
this server answers rather than streams.
GenHTTP's MCP module (`GenHTTP.Modules.Mcp`): JSON-RPC over a single path,
taking POST, in the stateless revision of the protocol and in the ones before
it that start with a handshake, which it answers without keeping a session. A
GET is declined with 405, because this server answers rather than streams.

The tools are the methods of `McpTools` marked `[Tool]`: their `[Summary]` is
what an agent reads, their parameters are the arguments and their schema, and
a fixed limit - a length, a range, the values an argument takes - is an
attribute on the parameter, advertised and checked before the tool runs. A
tool answers with an object, as text for the model and as structured content
for a program. When the platform refuses - a key nobody has, a demo that
cannot be changed, a name that is taken - the answer is an error that says
why in a sentence, as is one for an argument that does not fit; a failed
deployment or merge is an error that carries its diagnostics as structured
content.

```
https://genhttp.dev/mcp
Expand Down Expand Up @@ -1879,10 +1900,14 @@ Nothing is created until `acceptTerms` is true, and the editor key that comes
back is the only way into what was made. There is no session and nothing is
remembered between calls: everything a call needs is in its arguments.

`LAMBDA_MCP_ORIGINS` lists the hosts a browser may drive the endpoint from. An
agent speaking HTTP sends no `Origin` and is never checked against it; the list
is there because a browser does, and a page on another site could otherwise aim
somebody's browser at this endpoint.
`LAMBDA_MCP_ORIGINS` lists the hosts a browser may drive the endpoint from: a
host stands for its page over HTTPS (`genhttp.dev` for
`https://genhttp.dev`), and an entry with a scheme for the origin it names,
port included (`http://localhost:5173`); an entry that names neither is left
out, with a warning on startup. An agent speaking HTTP sends no
`Origin` and is never checked against it; the list is there because a browser
does, and a page on another site could otherwise aim somebody's browser at
this endpoint.

## Administration

Expand All @@ -1899,6 +1924,8 @@ the credential and it only ever reaches one lambda; this reads code that belongs
to other people and can take their work away, so it asks for `LAMBDA_ADMIN_TOKEN`
in an `X-Admin-Token` header (GenHTTP's API key authentication). A request
without the header is answered with 401, one with the wrong token with 403.
The concern says so in the API description itself: every operation behind it
requires the `adminToken` security scheme, which Scalar asks for once.
Until that variable is set there is no panel at all: `/admin`, `/telemetry`
and `/logs` are not there, and answer 404.

Expand Down
25 changes: 22 additions & 3 deletions docker/agent/builder.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -757,9 +757,15 @@ async function run(job) {
if (event.type === 'assistant') {
for (const part of event.message?.content ?? []) {
if (part.type === 'tool_use') {
const tool = String(part.name ?? '').replace('mcp__genhttp__', '');
const name = String(part.name ?? '');
const tool = name.replace('mcp__genhttp__', '');

calls.set(part.id, { tool, step: step(job, begin(tool, part.input)) });
// only a tool the run may call reaches the platform, so only its
// errors are the platform's refusals; the CLI turns away any other
// in words of its own, which are no step's problem
const allowed = (job.kind === 'change' ? CHANGE_ALLOW : ALLOW).includes(name);

calls.set(part.id, { tool, allowed, step: step(job, begin(tool, part.input)) });
closing = '';
}

Expand All @@ -783,7 +789,7 @@ async function run(job) {

const call = calls.get(part.tool_use_id);
const from = call?.tool;
const body = parse(part.content);
const body = parse(part.content) ?? (call?.allowed && part.is_error ? refused(part.content) : null);

if (call) finish(call.step, from, body);

Expand Down Expand Up @@ -1088,6 +1094,19 @@ function parse(content) {
return null;
}

/**
* A refusal of the platform's - a key nobody has, a name that is taken, an
* argument that does not fit - which says why in a sentence rather than in an
* object, as the answer of a step that went wrong.
*/
function refused(content) {
const text = (Array.isArray(content) ? content.filter(c => c.type === 'text').map(c => c.text) : [String(content ?? '')])
.join(' ')
.trim();

return text ? { ok: false, problem: text } : null;
}

/*
* Whether a tool's answer says a version went online. online: true says it,
* in the answers of a deploy and of a save or a merge with deploy: true -
Expand Down
5 changes: 1 addition & 4 deletions src/GenHTTP.Lambda/Api/AdminResource.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using GenHTTP.Api.Content.Services;
using GenHTTP.Api.Content.Documentation;
using GenHTTP.Api.Protocol;

using GenHTTP.Lambda.Api.Infrastructure;
Expand Down Expand Up @@ -40,9 +40,6 @@ namespace GenHTTP.Lambda.Api;
operator's. The answers carry editor keys. The settings and the limits apply to the whole installation, from the next
request on.
""")]
[RequestHeader(AdminAuthentication.Header, AdminAuthentication.HeaderDescription, Required = true)]
[Response(ResponseStatus.Unauthorized, AdminAuthentication.MissingDescription)]
[Response(ResponseStatus.Forbidden, AdminAuthentication.WrongDescription)]
public sealed class AdminResource(IMetaService meta, ITelemetryService telemetry, ISettingsService settings, ILimitsService limits,
ILogger<AdminResource> logger)
{
Expand Down
2 changes: 1 addition & 1 deletion src/GenHTTP.Lambda/Api/BuildResource.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using GenHTTP.Api.Content.Services;
using GenHTTP.Api.Content.Documentation;
using GenHTTP.Api.Protocol;

using GenHTTP.Lambda.Api.Infrastructure;
Expand Down
2 changes: 1 addition & 1 deletion src/GenHTTP.Lambda/Api/CodeResource.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using GenHTTP.Api.Content.Services;
using GenHTTP.Api.Content.Documentation;
using GenHTTP.Api.Protocol;

using GenHTTP.Lambda.Api.Infrastructure;
Expand Down
2 changes: 1 addition & 1 deletion src/GenHTTP.Lambda/Api/DataResource.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using GenHTTP.Api.Content.Services;
using GenHTTP.Api.Content.Documentation;
using GenHTTP.Api.Protocol;

using GenHTTP.Lambda.Api.Model;
Expand Down
2 changes: 1 addition & 1 deletion src/GenHTTP.Lambda/Api/DatabaseResource.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using GenHTTP.Api.Content.Services;
using GenHTTP.Api.Content.Documentation;
using GenHTTP.Api.Protocol;

using GenHTTP.Lambda.Api.Infrastructure;
Expand Down
2 changes: 1 addition & 1 deletion src/GenHTTP.Lambda/Api/DemoResource.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using GenHTTP.Api.Content.Services;
using GenHTTP.Api.Content.Documentation;

using GenHTTP.Lambda.Api.Infrastructure;
using GenHTTP.Lambda.Api.Model;
Expand Down
2 changes: 1 addition & 1 deletion src/GenHTTP.Lambda/Api/DeploymentResource.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using GenHTTP.Api.Content.Services;
using GenHTTP.Api.Content.Documentation;
using GenHTTP.Api.Protocol;

using GenHTTP.Lambda.Api.Infrastructure;
Expand Down
2 changes: 1 addition & 1 deletion src/GenHTTP.Lambda/Api/FeatureResource.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using GenHTTP.Api.Content.Services;
using GenHTTP.Api.Content.Documentation;
using GenHTTP.Api.Protocol;

using GenHTTP.Lambda.Api.Infrastructure;
Expand Down
2 changes: 1 addition & 1 deletion src/GenHTTP.Lambda/Api/FeatureWorkspaceResource.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using GenHTTP.Api.Content.Services;
using GenHTTP.Api.Content.Documentation;
using GenHTTP.Api.Protocol;

using GenHTTP.Lambda.Api.Infrastructure;
Expand Down
2 changes: 1 addition & 1 deletion src/GenHTTP.Lambda/Api/FileResource.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using GenHTTP.Api.Content.Services;
using GenHTTP.Api.Content.Documentation;
using GenHTTP.Api.Protocol;

using GenHTTP.Lambda.Api.Infrastructure;
Expand Down
21 changes: 13 additions & 8 deletions src/GenHTTP.Lambda/Api/Infrastructure/AdminAuthentication.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using GenHTTP.Api.Content;
using GenHTTP.Api.Content.Authentication;
using GenHTTP.Api.Content.Documentation.Security;

using GenHTTP.Lambda.Configuration;

Expand Down Expand Up @@ -37,14 +38,17 @@ public static class AdminAuthentication
/// </summary>
public const string Header = "X-Admin-Token";

// how the API description says it, for each resource behind the token -
// the framework's API key concern does not describe itself

public const string HeaderDescription = "The administration token of the installation (`LAMBDA_ADMIN_TOKEN`)";

public const string MissingDescription = "The token is missing.";

public const string WrongDescription = "The token is wrong.";
/// <summary>
/// How the API description names the token.
/// </summary>
/// <remarks>
/// The concern describes itself to the API description: every operation
/// behind it requires this scheme, and says how a request without the
/// token and one with the wrong token are answered. Named for what it is,
/// since Scalar asks for it once by that name for all of them.
/// </remarks>
public static readonly SecurityScheme Scheme = SecurityScheme.ApiKey(Header, ApiKeyLocation.Header, "The administration token of the installation (`LAMBDA_ADMIN_TOKEN`).")
with { Name = "adminToken" };

/// <summary>
/// The concern that lets a request with the configured token through.
Expand All @@ -55,6 +59,7 @@ public static IConcernBuilder Create(LambdaOptions options)

return ApiKeyAuthentication.Create()
.WithHeader(Header)
.Documentation(Scheme)
.Authenticator((_, presented) =>
{
var actual = Encoding.UTF8.GetBytes(presented);
Expand Down
2 changes: 1 addition & 1 deletion src/GenHTTP.Lambda/Api/KeyResource.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using GenHTTP.Api.Content.Services;
using GenHTTP.Api.Content.Documentation;

using GenHTTP.Lambda.Api.Infrastructure;
using GenHTTP.Lambda.Api.Model;
Expand Down
2 changes: 1 addition & 1 deletion src/GenHTTP.Lambda/Api/LambdaAgentResource.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using GenHTTP.Api.Content.Services;
using GenHTTP.Api.Content.Documentation;
using GenHTTP.Api.Protocol;

using GenHTTP.Lambda.Api.Infrastructure;
Expand Down
2 changes: 1 addition & 1 deletion src/GenHTTP.Lambda/Api/LambdaAnalyticsResource.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using GenHTTP.Api.Content.Services;
using GenHTTP.Api.Content.Documentation;
using GenHTTP.Api.Protocol;

using GenHTTP.Lambda.Api.Infrastructure;
Expand Down
2 changes: 1 addition & 1 deletion src/GenHTTP.Lambda/Api/LambdaDomainResource.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using System.Net;
using System.Net.Sockets;

using GenHTTP.Api.Content.Services;
using GenHTTP.Api.Content.Documentation;
using GenHTTP.Api.Protocol;

using GenHTTP.Lambda.Api.Infrastructure;
Expand Down
2 changes: 1 addition & 1 deletion src/GenHTTP.Lambda/Api/LambdaResource.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using GenHTTP.Api.Content.Services;
using GenHTTP.Api.Content.Documentation;
using GenHTTP.Api.Infrastructure;
using GenHTTP.Api.Protocol;

Expand Down
2 changes: 1 addition & 1 deletion src/GenHTTP.Lambda/Api/LambdaShowcaseResource.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using GenHTTP.Api.Content.Services;
using GenHTTP.Api.Content.Documentation;
using GenHTTP.Api.Protocol;

using GenHTTP.Lambda.Api.Model;
Expand Down
2 changes: 1 addition & 1 deletion src/GenHTTP.Lambda/Api/LambdaSourceResource.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using GenHTTP.Api.Content.Services;
using GenHTTP.Api.Content.Documentation;
using GenHTTP.Api.Protocol;

using GenHTTP.Lambda.Api.Model;
Expand Down
6 changes: 1 addition & 5 deletions src/GenHTTP.Lambda/Api/LogResource.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using GenHTTP.Api.Content.Services;
using GenHTTP.Api.Protocol;
using GenHTTP.Api.Content.Documentation;

using GenHTTP.Lambda.Api.Infrastructure;
using GenHTTP.Lambda.Api.Model;
Expand All @@ -26,9 +25,6 @@ namespace GenHTTP.Lambda.Api;
and what the server said. Behind the token, since it holds whatever a stranger's code decided to print - and, where
the operator records them, the addresses of callers.
""")]
[RequestHeader(AdminAuthentication.Header, AdminAuthentication.HeaderDescription, Required = true)]
[Response(ResponseStatus.Unauthorized, AdminAuthentication.MissingDescription)]
[Response(ResponseStatus.Forbidden, AdminAuthentication.WrongDescription)]
public sealed class LogResource(ILogBook book, IRunLog runs, LambdaOptions options)
{

Expand Down
Loading
Loading