tinyrelaycommit

Commit 97e3b36916a1

browse files at this commit | history

1diff --git a/README.md b/README.md
2index 642d7fd..55b3dc3 100644
3--- a/README.md
4+++ b/README.md
5@@ -56,6 +56,8 @@ See [Agent identities](docs/agents.md) for granting an assistant or bot its own
6
7 **Manage > Health** shows the relay and script versions and whether the browser's WebMCP tools are registered. See [WebMCP tools](docs/webmcp.md) for browser-agent integration and authorization behavior.
8
9+Agents outside the browser connect to `/mcp`, a stateless Model Context Protocol endpoint authenticated with NIP-98, and read `/llms.txt` for a summary of the relay's machine surface. See [MCP](docs/mcp.md).
10+
11 ## Deployment
12
13 Build the container image:
14diff --git a/docs/mcp.md b/docs/mcp.md
15new file mode 100644
16index 0000000..7aed49b
17--- /dev/null
18+++ b/docs/mcp.md
19@@ -0,0 +1,140 @@
20+# MCP
21+
22+The relay serves a [Model Context Protocol](https://modelcontextprotocol.io/specification/2026-07-28) endpoint at `/mcp` for agents that run outside the browser. It implements protocol revision 2026-07-28 over the stateless Streamable HTTP binding: every call is one POST that carries its own protocol version, identity and capabilities. There is no `initialize` handshake, no session and no server-to-client stream. A tenant served under a path prefix has its endpoint at `/r/<name>/mcp`.
23+
24+`/llms.txt` on the same origin summarizes the endpoint together with the relay's other machine surfaces: the HTTP bridge, the management API and Git hosting.
25+
26+## Connecting a client
27+
28+Point an MCP client that speaks revision 2026-07-28 at `https://<relay>/mcp` and give it a NIP-98 signer for the `Authorization` header. Call `server/discover` to read the supported versions, capabilities and server identity, `tools/list` for the tool table and `tools/call` to run a tool. Notifications are accepted with `202 Accepted` and no body. Responses are always `application/json`; the endpoint does not stream.
29+
30+## Authorization
31+
32+The MCP specification recommends OAuth 2.1 for HTTP servers. This relay uses [NIP-98](https://github.com/nostr-protocol/nips/blob/master/98.md) instead, because a Nostr key is already the identity the relay's roles, memberships and write rules are built on. Requiring an OAuth authorization server would add a second identity that maps back to the same key.
33+
34+Every POST carries `Authorization: Nostr <base64 kind 27235 event>`. The event's tags bind it to the method `POST`, the full request URL including any tenant prefix and the SHA-256 of the request body in the `payload` tag. Tokens are valid for one minute and are accepted once. A missing or invalid token answers `401 Unauthorized` with `WWW-Authenticate: Nostr realm="tiny"`.
35+
36+Tools execute as the signing key. Reads return what that key may see, management tools follow the relay's owner, moderator and member roles, and published events pass the same access gates as `POST /events`.
37+
38+## Headers
39+
40+Each request carries these headers and the matching values in the JSON-RPC body:
41+
42+| Header | Value |
43+| --- | --- |
44+| `MCP-Protocol-Version` | `2026-07-28`, equal to `params._meta["io.modelcontextprotocol/protocolVersion"]` |
45+| `Mcp-Method` | Equal to the JSON-RPC `method` |
46+| `Mcp-Name` | For `tools/call`, equal to `params.name` |
47+| `Accept` | Includes `application/json` |
48+| `Content-Type` | `application/json` |
49+| `Origin` | Optional. When present it must be the relay's own origin |
50+
51+`params._meta` also carries `io.modelcontextprotocol/clientCapabilities`, an object that may be empty, and should carry `io.modelcontextprotocol/clientInfo`. A `Mcp-Name` value that is not plain ASCII is sent as `=?base64?<value>?=` and decoded before comparison. `Mcp-Session-Id` and `Last-Event-ID` from earlier revisions are ignored.
52+
53+| Condition | Response |
54+| --- | --- |
55+| GET or DELETE | `405 Method Not Allowed` with `Allow: POST` |
56+| `Origin` names another site | `403 Forbidden` |
57+| Missing or invalid NIP-98 token | `401 Unauthorized` with the `WWW-Authenticate` challenge |
58+| Missing `MCP-Protocol-Version`, `Mcp-Method` or `Mcp-Name`, or a header that differs from the body | `400 Bad Request`, error `-32020` HeaderMismatch |
59+| Protocol version the relay does not serve | `400 Bad Request`, error `-32022` UnsupportedProtocolVersion listing `supported` versions |
60+| Missing `_meta` fields or an unknown tool | `400 Bad Request`, error `-32602` Invalid params |
61+| Unknown JSON-RPC method, including `initialize` | `404 Not Found`, error `-32601` Method not found |
62+| Body that is not one JSON-RPC message | `400 Bad Request`, error `-32700` or `-32600` |
63+
64+Tool results carry `resultType: "complete"`, a text block and `structuredContent`. Arguments that fail the tool's schema, permission errors and rejected events return `isError: true` with a message the agent can act on. `tools/list` returns `ttlMs` and `cacheScope: "private"` so the table may be cached per key.
65+
66+## Tools
67+
68+Read tools, which need a key that may read the relay:
69+
70+| Tool | Description |
71+| --- | --- |
72+| `list_repositories` | List hosted repositories with search and pagination. |
73+| `read_repository` | Read a repository tree, file, history, commit diff or activity at a ref. |
74+| `list_issues` | List a repository's issues with search, status and label filters. |
75+| `read_issue` | Read an issue with its replies and authorized status changes. |
76+| `list_pull_requests` | List a repository's pull requests with the same filters. |
77+| `read_pull_request` | Read a pull request with replies, updates and the available diff. |
78+| `list_files` | List stored files visible to the key. |
79+| `read_file` | Read a stored file's metadata and preview by SHA-256 hash. |
80+| `read_status` | Read service health, storage and job status as an owner or moderator. |
81+| `read_management` | Run a read-only management method: stats, getpolicy, listaudit, listjobs, listbackups, listdumps, deliverystatus, storagestats, gitstorage, listconnections or listmembers. |
82+
83+Management tools, which follow the relay's roles:
84+
85+| Tool | Description |
86+| --- | --- |
87+| `run_job` | Queue an existing job to run now. |
88+| `add_job` | Add a pull, push, import, mirror, dump or backup job. |
89+| `remove_job` | Remove a job and cancel its pending runs. |
90+| `backup_now` | Queue a backup of relay data. |
91+| `dump_now` | Queue an event export. |
92+| `set_policy` | Apply a partial policy update as the owner. |
93+| `set_connections` | Replace the relay's connection list. |
94+| `send_test_notification` | Send the owner's test notice to the inbox and enabled devices. |
95+
96+Write tools, which publish through the same path as `POST /events`:
97+
98+| Tool | Description |
99+| --- | --- |
100+| `publish_event` | Publish any signed Nostr event. |
101+| `create_issue` | Open a kind 1621 issue on a hosted repository. |
102+| `create_pull_request` | Open a kind 1618 pull request with its commit and clone URL. |
103+| `comment` | Reply to an issue, pull request or comment with a kind 1111 event carrying NIP-22 tags. |
104+| `set_status` | Mark an issue or pull request open, resolved, merged, closed or draft with a kind 1630 to 1633 event. |
105+
106+The relay never signs on a caller's behalf. Call a write tool with plain fields, such as `owner`, `repo`, `title` and `content`, and it returns the unsigned event to sign. Call it again with the signed event as `event` and the relay checks the kind and tags before publishing. A malformed event is refused with a message that lists the expected tags.
107+
108+## Example
109+
110+A `tools/call` request for `read_management`, with the NIP-98 token abbreviated:
111+
112+```
113+POST /mcp HTTP/1.1
114+Host: relay.example
115+Authorization: Nostr eyJpZCI6Ii4uLiIsImtpbmQiOjI3MjM1LC4uLn0=
116+Content-Type: application/json
117+Accept: application/json, text/event-stream
118+MCP-Protocol-Version: 2026-07-28
119+Mcp-Method: tools/call
120+Mcp-Name: read_management
121+
122+{
123+ "jsonrpc": "2.0",
124+ "id": 1,
125+ "method": "tools/call",
126+ "params": {
127+ "name": "read_management",
128+ "arguments": {"method": "getpolicy"},
129+ "_meta": {
130+ "io.modelcontextprotocol/protocolVersion": "2026-07-28",
131+ "io.modelcontextprotocol/clientInfo": {"name": "example-agent", "version": "1.0"},
132+ "io.modelcontextprotocol/clientCapabilities": {}
133+ }
134+ }
135+}
136+```
137+
138+The kind 27235 event in the `Authorization` header carries `["u","https://relay.example/mcp"]`, `["method","POST"]` and `["payload","<sha256 of the body>"]`. The response:
139+
140+```
141+HTTP/1.1 200 OK
142+Content-Type: application/json
143+
144+{
145+ "jsonrpc": "2.0",
146+ "id": 1,
147+ "result": {
148+ "resultType": "complete",
149+ "content": [{"type": "text", "text": "{\"result\":{\"owner\":\"...\",\"reads\":\"open\"}}"}],
150+ "structuredContent": {"result": {"owner": "...", "reads": "open"}},
151+ "isError": false,
152+ "_meta": {"io.modelcontextprotocol/serverInfo": {"name": "tinyrelay", "version": "..."}}
153+ }
154+}
155+```
156+
157+## Observability
158+
159+MCP requests are recorded under the `mcp` operation with the outcomes `ok`, `invalid`, `unauthorized` and `error`. Logs carry the method, tool name and outcome, never the caller's key. See [Observability](observability.md).
160diff --git a/docs/webmcp.md b/docs/webmcp.md
161index 5b5fe0d..b5567d4 100644
162--- a/docs/webmcp.md
163+++ b/docs/webmcp.md
164@@ -7,3 +7,5 @@ Choose **Sign in** in the main navigation and connect your Nostr signer. Reading
165 In Chrome, enable **WebMCP for testing** at `chrome://flags/#enable-webmcp-testing` and relaunch. Other browsers can use the same pages and forms. See [Chrome's WebMCP guide](https://developer.chrome.com/docs/ai/webmcp) for availability.
166
167 Read the current policy or connection list before changing settings. Job intervals are measured in hours; zero runs once.
168+
169+Agents that run outside the browser use the same tools over the relay's MCP endpoint. See [MCP](mcp.md).

About

ownerc01503e8035f
visibilitypublic
head97e3b36916a1

Maintainers

  • c01503e8035f owner
  • ba1b5beed5b5 agent hermes

Clone

  • https://012.run/npub1cq2s86qrtadf36eqq3epm38wmhdynrrcwxf84eldk3f94dkv7ttsrulrz5/tinyrelay.git

Refs