@muretai/agent-entry 1.9.0 → 1.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,10 +1,61 @@
1
1
  # Agent Entry
2
2
 
3
- **`llms.txt` describes your site to an AI agent. An Agent Entry *recognises* one.**
3
+ [![npm](https://img.shields.io/npm/v/@muretai/agent-entry.svg?label=npm)](https://www.npmjs.com/package/@muretai/agent-entry)
4
+ [![LICENSE](https://img.shields.io/badge/LICENSE-MIT-blue.svg)](LICENSE)
4
5
 
5
- It verifies who is knocking, opens an account for them, and answers in the same HTTP
6
- response. No signup form, because the visitor's key already is the account. When that
7
- person replaces their phone, your site still knows it is them.
6
+ **Agents already visit your site. Give them a way to become a customer.**
7
+
8
+ ## Contents
9
+
10
+ - [What this is](#what-this-is)
11
+ - [What you get](#what-you-get)
12
+ - [Who is knocking](#who-is-knocking)
13
+ - [Put it on a site](#put-it-on-a-site)
14
+ - [On serverless](#on-serverless)
15
+ - [On WordPress](#on-wordpress)
16
+ - [Pairs with WebMCP](#pairs-with-webmcp-the-tab-conversation-becomes-a-customer)
17
+ - [In production](#in-production)
18
+ - [This package](#this-package)
19
+
20
+ ## What this is
21
+
22
+ They read your pages and leave. No signup, no cookie, nothing in your analytics.
23
+ Agent Entry is a single module you put on the origin. It publishes an
24
+ [Agent Card](https://a2a-protocol.org/latest/specification/)
25
+ at `/.well-known/agent-card.json` — who you are, what you answer, how to knock —
26
+ and then answers one signed POST: verify the visitor, open an account from their
27
+ key, reply signed, same HTTP response.
28
+
29
+ No form. The key *is* the account. Come back next month from another device and
30
+ it is still them.
31
+
32
+ Any A2A client works: their agent, `curl`, not a particular router or ours.
33
+
34
+ Keep WebMCP. A person already in the tab keeps the page. This file sits beside
35
+ it and replaces none of it: an Agent Card and a door, so an agent who arrives
36
+ alone can become a customer — one signed POST, a signed reply.
37
+
38
+ `llms.txt` is a brochure — prose, not a contract. MCP is a tool server for an
39
+ app that holds a token. A different purpose.
40
+
41
+ The agent reads the card **before** it knocks (AE-8). The act is then one
42
+ **POST** `message/send` — already formed correctly — and a signed reply comes
43
+ back on that same request (AE-20).
44
+
45
+ A **handoff** is how a tool on the page, or a tool server, can send the visitor
46
+ *here*: the result names this entry's DID, and the next act is a signed knock at
47
+ this origin. This file does not follow a handoff and does not parse
48
+ `_meta.handoff`. It is where one lands. The `to` in that envelope must be the
49
+ DID printed at startup — the same seed as the card. A visitor (or any router
50
+ they run) is who honours the pointer.
51
+
52
+ | | |
53
+ |---|---|
54
+ | **Who installs this** | Website owners who want agents as *customers* — a returning identity — not only as crawlers. |
55
+ | **Not for** | People writing the visiting agent. This package does not find doors, MCP servers, or WebMCP tools; it *is* a door. |
56
+ | **The problem** | A GET-only document cannot recognise anyone. A signup form does not work for an agent. The first signed POST has to *be* the account. |
57
+
58
+ ![A person in the tab keeps WebMCP. An agent alone can become a customer at the door beside it.](diagrams/become.png)
8
59
 
9
60
  One file. Zero dependencies. No database. Node 20+.
10
61
 
@@ -38,9 +89,26 @@ That is the whole integration. `responder` is called with a verified envelope an
38
89
  what to say back; everything else — signatures, replay, rate limiting, the account
39
90
  ledger — is handled for you.
40
91
 
92
+ ### Not an HTTP 402 challenge
93
+
94
+ [x402](https://x402.org/) v2 over HTTP teaches by **error**. The client asks for the
95
+ resource; the server answers `402 Payment Required` with a `PAYMENT-REQUIRED` header; the
96
+ client **retries** the same resource with `PAYMENT-SIGNATURE`. The error is how the
97
+ client learns what to do next.
98
+
99
+ Agent Entry does the reverse. The Agent Card states the terms **before** anyone knocks
100
+ (AE-8). An HTTP-402-style challenge structurally cannot do that. The first POST is already
101
+ formed correctly and is answered `200` with a signed reply (AE-20). This package does not
102
+ speak x402 and does not return HTTP 402. A protocol refusal here is HTTP `200` plus a
103
+ JSON-RPC error (AE-19). If someone POSTs with no signature at all, `-32001` still carries
104
+ `data.accepts` — the same scheme object as the card — so they can succeed on the next POST
105
+ (AE-24). That is a fallback, not the design.
106
+
107
+ ![x402: request, HTTP 402, retry with payment. Agent Entry: GET the card as 200, then POST already correct, answered 200.](diagrams/x402.svg)
108
+
41
109
  ---
42
110
 
43
- ## What you actually get
111
+ ## What you get
44
112
 
45
113
  **A caller you can trust.** Every message arrives with an Ed25519 signature over six
46
114
  frozen fields. The sender's DID *is* their public key (`did:key`), so verification needs
@@ -69,7 +137,7 @@ resolves it and files them under `owner_did`, so a replaced phone is not a new c
69
137
  Your entry does not need to poll or be told: a node that carries the account learns it on
70
138
  its own, and refuses that key.
71
139
 
72
- ## Say what your door answers
140
+ ### Say what your door answers
73
141
 
74
142
  A visiting agent reads your card **before** it knocks. Left alone, that card says something
75
143
  answers here and nothing about what it answers, so the visitor has to guess and learns your
@@ -84,7 +152,7 @@ createAgentEntry({
84
152
  description: 'Ask what a shoot costs, what the studio does, and how to book. '
85
153
  + 'The answer comes back in the same HTTP response, signed by this domain.',
86
154
  tags: ['studio', 'booking', 'signed', 'inline-reply'],
87
- examples: ['Do you shoot weddings?', 'What does a half-day cost?', 'How do I book?'],
155
+ examples: ['Do you shoot weddings?', 'How much is a half-day?', 'Book Saturday 14:00'],
88
156
  }],
89
157
  });
90
158
  ```
@@ -99,12 +167,21 @@ visitor you will get, so drive your examples through your own responder in your
99
167
  **Declare only what the responder does:** a skill that mentions booking, on an entry that
100
168
  answers questions and hands off nothing, is a signed claim you cannot keep.
101
169
 
170
+ Turning logged knocks into that menu is an offline owner loop, not part of the
171
+ runtime — see [`spec/skill-distill.md`](spec/skill-distill.md). On this machine
172
+ only: `npm run distill` measures whether a proposed menu would have made the
173
+ next first knock useful. `observer: fileSink()` (from
174
+ `scripts/distill/record.mjs`) appends POST outcomes to `var/traces.jsonl`.
175
+ Nothing is uploaded, and the Distiller is never imported by
176
+ `muretai-agent-entry.mjs`.
177
+
102
178
  ### The rest of the settings
103
179
 
104
180
  | option | default | what it does |
105
181
  |---|---|---|
106
182
  | `skills` | `[]` | the menu above — what a visitor learns before knocking |
107
183
  | `openDoor` | `true` | publishes `agentEntry.open_door`: the field that tells a visiting agent it may message you with no introduction. The same fact is emitted under the older `muretai.open_door` spelling beside it — read either, write the neutral one |
184
+ | `prefer` | unset | your own order of the ways into your site, published verbatim as `agentEntry.prefer` (spec AE-30): an array of `"page"`, `"card"`, `"mcp"` or `{kind, when}` with `when` one of `person`, `alone`, `key`, `no-key`, `token`, `browser` — e.g. `[{kind:'page', when:'no-key'}, 'card']` says "read on the page if you hold no key; otherwise the door". A visiting agent reads it against what it has on hand. An invalid list refuses to start rather than publish an order you did not write; unset publishes no key at all |
108
185
  | `anonymousLane` | `false` | also answer **unsigned** inquiries. They create no account row, and the lane is capped entry-wide — an unauthenticated caller must never become an unmetered signing oracle |
109
186
  | `anonRatePerMin` | `30` | anonymous replies per minute, entry-wide |
110
187
  | `signedRatePerMin` | `60` | signed replies per minute **per account**, ON by default. Attribution is not scarcity: a `did:key` costs nothing to mint, so being in your ledger was never a bound |
@@ -121,12 +198,12 @@ answers questions and hands off nothing, is a signed claim you cannot keep.
121
198
  `seedHex` and `baseUrl` are the two an entry refuses to start without: the seed **is** the
122
199
  address, and the url it publishes must equal the origin the visitor dialled.
123
200
 
124
- ## Who is knocking — observation, never identity
201
+ ## Who is knocking
125
202
 
126
- The person who found you often never opens a browser: they hand your link to their
127
- agent, and the agent fetches your card and knocks. That traffic is invisible to every
128
- page-view metric you have — the only place it can be seen is the door itself. So the
129
- door counts it:
203
+ Observation, never identity. The person who found you often never opens a browser:
204
+ they hand your link to their agent, and the agent fetches your card and knocks.
205
+ That traffic is invisible to every page-view metric you have — the only place it
206
+ can be seen is the door itself. So the door counts it:
130
207
 
131
208
  ```js
132
209
  entry.stats()
@@ -198,7 +275,9 @@ signature over the transport proves who fetched — not who wrote the text, and
198
275
  captured header set is replayable until it expires (minutes), which is why `wba_did`
199
276
  is identification, never authorship.
200
277
 
201
- ## Install
278
+ ## Put it on a site
279
+
280
+ ### Install
202
281
 
203
282
  ```bash
204
283
  npm i @muretai/agent-entry
@@ -218,7 +297,7 @@ upgrade — the ledger is your customer list, and more features stand on keeping
218
297
  an analytics tool covers statistics without one. Both are described under
219
298
  [Before you put it in production](#before-you-put-it-in-production).
220
299
 
221
- ## Put one on a site you already have
300
+ ### Put one on a site you already have
222
301
 
223
302
  A visiting agent knows only your **domain**, so the three paths it walks are fixed — it
224
303
  cannot be told to look elsewhere:
@@ -351,8 +430,9 @@ has already changed the bytes the signature covers, and the only diagnostic anyo
351
430
 
352
431
  ### 3. A reverse proxy — for a site that is not Node at all
353
432
 
354
- WordPress, Rails, a static build. Run the entry as one small process and route three
355
- locations to it:
433
+ Rails, a static build, anything that is not WordPress and not a function. Run the
434
+ entry as one small process and route three locations to it. WordPress has its own
435
+ plugin — [On WordPress](#on-wordpress).
356
436
 
357
437
  ```nginx
358
438
  location = /.well-known/agent-card.json { proxy_pass http://127.0.0.1:8788; }
@@ -363,16 +443,35 @@ location = / {
363
443
  }
364
444
  ```
365
445
 
366
- ### Serverless
446
+ ### On serverless
447
+
448
+ The round-trip shape fits a single function: one signed POST in, one signed reply out.
449
+ What does not fit is the state. A function instance keeps nothing between requests, so
450
+ the replay set, the device→owner pins and the ledger have to live in a store the
451
+ platform keeps, not in memory.
452
+
453
+ This package still runs in process. For Cloudflare Workers, Vercel and Netlify, use
454
+ the deploy templates — this door plus one store adapter per platform:
455
+
456
+ [github.com/muretai/agent-entry-serverless](https://github.com/muretai/agent-entry-serverless)
457
+
458
+ They vendor a library build that includes the `store` seam those adapters need. Do not
459
+ replace that file with an older published copy of this package: versions without the
460
+ seam accept the option and silently ignore it, which puts the three stateful rules
461
+ back into per-instance memory — the failures those templates exist to prevent.
462
+
463
+ Until you are on one of those templates, use one of the long-lived shapes above.
464
+
465
+ ### On WordPress
367
466
 
368
- The round-trip shape fits a single function well, and `handleRequestAsync` is exactly the
369
- handler signature those platforms want. Two things must be settled first, because a
370
- serverless instance keeps nothing between requests: the seed has to come from a secret
371
- environment variable, and the ledger, the device→owner pins and the replay guard have to
372
- live in your own store rather than in memory. A `store` hook for that is the next release;
373
- until then, use one of the three long-lived shapes above.
467
+ If the site is WordPress, do not proxy this Node file in front of it. The plugin is a
468
+ third implementation of the same contract it publishes the card, answers the signed
469
+ POST, and with WooCommerce lets an agent ask about the catalogue. `GET /` stays your
470
+ site.
374
471
 
375
- ## Run the example
472
+ [github.com/muretai/agent-entry-wordpress](https://github.com/muretai/agent-entry-wordpress)
473
+
474
+ ### Run the example
376
475
 
377
476
  ```bash
378
477
  node examples/server.mjs # prints its DID and card URL
@@ -381,9 +480,10 @@ node examples/server.mjs # prints its DID and card URL
381
480
  Environment: `AGENT_ENTRY_SEED_HEX` (generated and printed if absent — **persist it, it is
382
481
  your site's identity**), `AGENT_ENTRY_PORT` (8788), `AGENT_ENTRY_BASE_URL`,
383
482
  `AGENT_ENTRY_NAME`, `AGENT_ENTRY_ANON` (`1` also accepts unsigned inquiries, which create
384
- no account).
483
+ no account), `AGENT_ENTRY_PREFER` (your order of the ways in, as one JSON array — see
484
+ `prefer` above; an invalid list refuses to start).
385
485
 
386
- ## What `baseUrl` may be
486
+ ### What `baseUrl` may be
387
487
 
388
488
  `baseUrl` must be the URL visitors actually dial: it is what your signed card claims, and
389
489
  a card naming a different origin proves nothing about yours.
@@ -420,7 +520,7 @@ Two rules worth knowing before you pick a URL:
420
520
  parser punycodes a host and Python's does not, so the two implementations would otherwise
421
521
  sign different bytes for the same site.
422
522
 
423
- ## One host, many agents
523
+ ### One host, many agents
424
524
 
425
525
  A domain can hold a **fleet** — a front desk, support, sales — each its own agent, its own
426
526
  key, its own address, each contactable directly. Give each one a `baseUrl` that carries its
@@ -472,7 +572,7 @@ const fwd = (entry) => async (req, res) => {
472
572
  };
473
573
  ```
474
574
 
475
- ## Which domains this entry speaks for
575
+ ### Which domains this entry speaks for
476
576
 
477
577
  An entry can name the domains it belongs to:
478
578
 
@@ -503,6 +603,11 @@ Set it from the environment with `AGENT_ENTRY_DOMAINS=studio.example,support.stu
503
603
 
504
604
  ## Pairs with WebMCP: the tab conversation becomes a customer
505
605
 
606
+ WebMCP is not MCP. MCP is a tool *server* over HTTP (a token, a session). WebMCP is tools
607
+ *in the page*, running as whoever is in that browser tab. This package implements neither.
608
+ A site may run WebMCP, an MCP server, and an Agent Entry at once; a headless agent should
609
+ use the last two, not scrape the first.
610
+
506
611
  If your page already exposes [WebMCP](https://github.com/MiguelsPizza/WebMCP) tools, you have
507
612
  one door open: an agent **inside a visitor's browser** can call `check_stock` or `inquire`
508
613
  while that person is on the page. That is useful and it is also temporary — close the tab and
@@ -515,10 +620,11 @@ An Agent Entry is the second door, and it is the one that keeps something:
515
620
  | **WebMCP tools** | a person's agent, in a tab, right now | an answer in the moment |
516
621
  | **Agent Entry** | an agent alone, from anywhere, at any hour | a customer you still recognise next month |
517
622
 
518
- **They connect.** When a WebMCP tool call reaches the point of actually wanting something —
519
- a booking, a quote, a follow-up — the tool returns a small envelope naming your site's DID,
520
- and the visitor's agent then sends a **signed message to your own origin**, where your Agent
521
- Entry receives it:
623
+ **They connect by a handoff.** When a WebMCP (or MCP) tool call reaches the point of
624
+ actually wanting something — a booking, a quote, a follow-up — the tool returns a small
625
+ envelope naming your site's DID. The visitor's agent then sends a **signed message to your
626
+ own origin**, where your Agent Entry receives it. That envelope is a handoff: this package
627
+ is the landing, not the follower.
522
628
 
523
629
  ```js
524
630
  navigator.modelContext.registerTool({
@@ -547,7 +653,9 @@ account layer resolves the owner behind both keys.
547
653
  A search engine makes your site **findable**. An Agent Entry makes it **answerable** — and
548
654
  makes the visitor someone you can recognise the next time.
549
655
 
550
- ## Before you put it in production
656
+ ## In production
657
+
658
+ ### Before you put it in production
551
659
 
552
660
  **Nothing here is needed to start** — an entry runs, and every exchange stays correct,
553
661
  on its in-process state alone; some installers have read this section as a prerequisite,
@@ -598,7 +706,7 @@ What the entry now handles for you at the HTTP layer, so you do not have to:
598
706
  which says a server must accept the absolute form: this endpoint answers exactly the
599
707
  address its card names, and the refusal says so.
600
708
 
601
- ## Counting visits without handing over your customer list
709
+ ### Counting visits without handing over your customer list
602
710
 
603
711
  You will want to know how many agents knocked, how many came back, and what they asked. All
604
712
  three are answerable — and how you answer them decides whether you are counting your visitors
@@ -683,7 +791,9 @@ disclosure that arrives after the visit is not a disclosure, it is a receipt.
683
791
  And if you decide to send raw DIDs anyway, that is your call to make — but say so on the card,
684
792
  in the same breath, in plain words.
685
793
 
686
- ## Two implementations, pinned to each other
794
+ ## This package
795
+
796
+ ### Two implementations, pinned to each other
687
797
 
688
798
  This module is not alone. A Python reference implements the same contract, and the two are
689
799
  held to **identical verdicts** by an acceptance suite: it runs the same attack battery
@@ -725,9 +835,12 @@ for byte. No network, no checkout of ours, nothing to ask us for:
725
835
  npm test
726
836
  ```
727
837
 
728
- Write a third implementation and point it at the same vectors.
838
+ Write another implementation and point it at the same vectors.
839
+ [Agent Entry for WordPress](https://github.com/muretai/agent-entry-wordpress)
840
+ is one that already does — a PHP door, not this file wrapped, held to the same
841
+ golden bytes.
729
842
 
730
- ## Contributing
843
+ ### Contributing
731
844
 
732
845
  This repo is a **published mirror**, rendered out of a private working repository — not the
733
846
  place the next change is written. A pull request opened here will not merge: the next release
@@ -738,7 +851,7 @@ That is not a closed door. **Open an issue** — a bug, a wire-vector disagreeme
738
851
  docs are wrong, a design question — and it gets read and, where it's right, becomes the next
739
852
  release here. That path works; a PR against these files does not.
740
853
 
741
- ## What this is part of
854
+ ### What this is part of
742
855
 
743
856
  [Muretai](https://muretai.com) is a network where AI agents that belong to *different
744
857
  people* can find and talk to each other — with identity, introductions and trust, rather
@@ -748,7 +861,19 @@ that has to stay awake.
748
861
  You do not need the rest of the network to use this file. It is useful on its own the
749
862
  moment an agent knocks.
750
863
 
751
- ## Questions
864
+ This package is the Node door. Two other install paths speak the same contract and do
865
+ not require this file on the host:
866
+
867
+ - [Agent Entry on serverless](https://github.com/muretai/agent-entry-serverless) —
868
+ Cloudflare Workers, Vercel, Netlify
869
+ - [Agent Entry for WordPress](https://github.com/muretai/agent-entry-wordpress) —
870
+ the CMS, including WooCommerce
871
+
872
+ Visitors do not need [Agent Web Router](https://github.com/muretai/agent-web-router).
873
+ That package is one way an agent *finds* doors; this package *is* a door. Either works
874
+ alone. Installing one never implies the other.
875
+
876
+ ### Questions
752
877
 
753
878
  Ask — there is no wrong question about this, and the answers usually improve the docs.
754
879