@proof-holdings/mcp-server 1.0.0 → 1.1.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.
Files changed (107) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +223 -216
  3. package/dist/authBoundary.d.ts +182 -0
  4. package/dist/authBoundary.d.ts.map +1 -0
  5. package/dist/authBoundary.js +328 -0
  6. package/dist/authBoundary.js.map +1 -0
  7. package/dist/factory.d.ts +33 -0
  8. package/dist/factory.d.ts.map +1 -0
  9. package/dist/factory.js +123 -0
  10. package/dist/factory.js.map +1 -0
  11. package/dist/http.d.ts +63 -3
  12. package/dist/http.d.ts.map +1 -1
  13. package/dist/http.js +183 -4
  14. package/dist/http.js.map +1 -1
  15. package/dist/remote.d.ts +111 -0
  16. package/dist/remote.d.ts.map +1 -0
  17. package/dist/remote.js +787 -0
  18. package/dist/remote.js.map +1 -0
  19. package/dist/server.js +11 -57
  20. package/dist/server.js.map +1 -1
  21. package/dist/tools/{projects.d.ts → accounts.d.ts} +1 -1
  22. package/dist/tools/accounts.d.ts.map +1 -0
  23. package/dist/tools/accounts.js +70 -0
  24. package/dist/tools/accounts.js.map +1 -0
  25. package/dist/tools/api-keys.d.ts.map +1 -1
  26. package/dist/tools/api-keys.js +14 -5
  27. package/dist/tools/api-keys.js.map +1 -1
  28. package/dist/tools/auth-flows.d.ts +4 -0
  29. package/dist/tools/auth-flows.d.ts.map +1 -0
  30. package/dist/tools/auth-flows.js +34 -0
  31. package/dist/tools/auth-flows.js.map +1 -0
  32. package/dist/tools/authorizations.d.ts +4 -0
  33. package/dist/tools/authorizations.d.ts.map +1 -0
  34. package/dist/tools/authorizations.js +111 -0
  35. package/dist/tools/authorizations.js.map +1 -0
  36. package/dist/tools/circles.d.ts +4 -0
  37. package/dist/tools/circles.d.ts.map +1 -0
  38. package/dist/tools/circles.js +215 -0
  39. package/dist/tools/circles.js.map +1 -0
  40. package/dist/tools/confirmations.d.ts +4 -0
  41. package/dist/tools/confirmations.d.ts.map +1 -0
  42. package/dist/tools/confirmations.js +86 -0
  43. package/dist/tools/confirmations.js.map +1 -0
  44. package/dist/tools/delegation-verify-outcomes.d.ts +23 -0
  45. package/dist/tools/delegation-verify-outcomes.d.ts.map +1 -0
  46. package/dist/tools/delegation-verify-outcomes.js +51 -0
  47. package/dist/tools/delegation-verify-outcomes.js.map +1 -0
  48. package/dist/tools/delegation-verify.d.ts +24 -0
  49. package/dist/tools/delegation-verify.d.ts.map +1 -0
  50. package/dist/tools/delegation-verify.js +192 -0
  51. package/dist/tools/delegation-verify.js.map +1 -0
  52. package/dist/tools/delegations.d.ts +4 -0
  53. package/dist/tools/delegations.d.ts.map +1 -0
  54. package/dist/tools/delegations.js +84 -0
  55. package/dist/tools/delegations.js.map +1 -0
  56. package/dist/tools/domains.d.ts.map +1 -1
  57. package/dist/tools/domains.js +1 -2
  58. package/dist/tools/domains.js.map +1 -1
  59. package/dist/tools/hitl-keys.d.ts +4 -0
  60. package/dist/tools/hitl-keys.d.ts.map +1 -0
  61. package/dist/tools/hitl-keys.js +52 -0
  62. package/dist/tools/hitl-keys.js.map +1 -0
  63. package/dist/tools/hitl.d.ts +4 -0
  64. package/dist/tools/hitl.d.ts.map +1 -0
  65. package/dist/tools/hitl.js +151 -0
  66. package/dist/tools/hitl.js.map +1 -0
  67. package/dist/tools/phones.js +1 -1
  68. package/dist/tools/phones.js.map +1 -1
  69. package/dist/tools/profiles.d.ts.map +1 -1
  70. package/dist/tools/profiles.js +73 -0
  71. package/dist/tools/profiles.js.map +1 -1
  72. package/dist/tools/proof-me.d.ts +4 -0
  73. package/dist/tools/proof-me.d.ts.map +1 -0
  74. package/dist/tools/proof-me.js +107 -0
  75. package/dist/tools/proof-me.js.map +1 -0
  76. package/dist/tools/proofs.d.ts.map +1 -1
  77. package/dist/tools/proofs.js +9 -6
  78. package/dist/tools/proofs.js.map +1 -1
  79. package/dist/tools/render-auth-link.d.ts +3 -0
  80. package/dist/tools/render-auth-link.d.ts.map +1 -0
  81. package/dist/tools/render-auth-link.js +30 -0
  82. package/dist/tools/render-auth-link.js.map +1 -0
  83. package/dist/tools/sessions.js +5 -5
  84. package/dist/tools/sessions.js.map +1 -1
  85. package/dist/tools/settings.d.ts.map +1 -1
  86. package/dist/tools/settings.js +69 -0
  87. package/dist/tools/settings.js.map +1 -1
  88. package/dist/tools/twofa.d.ts.map +1 -1
  89. package/dist/tools/twofa.js +16 -3
  90. package/dist/tools/twofa.js.map +1 -1
  91. package/dist/tools/user-requests.d.ts.map +1 -1
  92. package/dist/tools/user-requests.js +1 -2
  93. package/dist/tools/user-requests.js.map +1 -1
  94. package/dist/tools/verification-requests.d.ts.map +1 -1
  95. package/dist/tools/verification-requests.js +40 -13
  96. package/dist/tools/verification-requests.js.map +1 -1
  97. package/dist/tools/verifications.d.ts.map +1 -1
  98. package/dist/tools/verifications.js +59 -12
  99. package/dist/tools/verifications.js.map +1 -1
  100. package/dist/types.d.ts +18 -0
  101. package/dist/types.d.ts.map +1 -1
  102. package/dist/types.js +114 -5
  103. package/dist/types.js.map +1 -1
  104. package/package.json +11 -5
  105. package/dist/tools/projects.d.ts.map +0 -1
  106. package/dist/tools/projects.js +0 -159
  107. package/dist/tools/projects.js.map +0 -1
@@ -0,0 +1,182 @@
1
+ /**
2
+ * The boundary between what this server answers with no account and what needs one
3
+ * (`h-mcp-oauth-remote-wiring`).
4
+ *
5
+ * WHY IT LIVES HERE, and not inside a tool handler: the answer to "you need to sign in" is an HTTP
6
+ * 401 carrying `WWW-Authenticate`, and that is what a client's OAuth stack reads. By the time a tool
7
+ * handler runs, the response is already a JSON-RPC result — the same refusal delivered there is
8
+ * text, not a challenge, and every measured client ignores it (`docs/mcp-oauth-client-probe.md`).
9
+ * So the remote server has to classify the message BEFORE handing it to the transport, and this
10
+ * module is the one place that classification is written down.
11
+ *
12
+ * WHERE IT SITS, and why not on `initialize`: the control measurement in the probe put a 401 on the
13
+ * opening handshake and the human saw `Failed to connect — connection timed out after 30000ms`,
14
+ * i.e. a broken connection rather than an invitation to sign in. Anonymous connection is a closed
15
+ * criterion of `h-mcp-remote`, so the 401 lands on the TOOL CALL that needs an account and nowhere
16
+ * else.
17
+ */
18
+ /**
19
+ * Tools that work with no credential at all.
20
+ *
21
+ * Derived from `PUBLIC_PATHS` in `./http.js` — the endpoints the HTTP client is allowed to call
22
+ * with no bearer — plus the two tools that never touch our API: `verify_delegation` /
23
+ * `verify_delegations` run `@proof-holdings/delegation-verifier` against an issuer's public
24
+ * surfaces, and `render_auth_link` is local QR generation.
25
+ *
26
+ * `__tests__/auth-boundary.test.ts` compares this list against the tools the factory actually
27
+ * registers, in BOTH directions: a name here that no tool answers to would refuse nothing while
28
+ * reading exactly like a boundary, and anything the server registers that is not here must be
29
+ * refused without a key.
30
+ */
31
+ export declare const KEYLESS_TOOLS: ReadonlySet<string>;
32
+ /**
33
+ * Tools that WORK over a `start_login` session.
34
+ *
35
+ * Their endpoints are the caller's own account records — the paths `SESSION_AUTH_PREFIXES` in
36
+ * `./http.ts` signs with the session token (plus `/api/v1/auth/me`). Every other keyed tool needs
37
+ * the API key the client's Authenticate step grants, and the refusal below tells the caller which
38
+ * side of that line the tool they asked for is on, BEFORE they walk the road. It used to say
39
+ * "call start_login" to everyone; a human did, signed in through Telegram, and was refused again by
40
+ * a second text that finally explained the difference (h-fix-mcp-refusal-roads).
41
+ *
42
+ * Hand-written here because the package cannot read docs/api-map.yaml at runtime, and held equal to
43
+ * the truth in BOTH directions by `src/__tests__/drift/api-parity.test.ts` (`parseMcpSessionTools`
44
+ * joins this set to the map through the paths the client signs) and by `validateMcpSessionToolCoverage`
45
+ * in `scripts/validate-api-map.ts`. `__tests__/auth-boundary.test.ts` keeps it to registered tools
46
+ * and disjoint from `KEYLESS_TOOLS`.
47
+ */
48
+ export declare const SESSION_TOOLS: ReadonlySet<string>;
49
+ /**
50
+ * What a caller must do to open a tool, in the two channels a client delivers ANONYMOUSLY and
51
+ * ALWAYS: the descriptions in `tools/list` and the `instructions` of the `initialize` result.
52
+ *
53
+ * WHY THE REFUSAL BELOW IS NOT ENOUGH, measured rather than reasoned. The 401 + `WWW-Authenticate`
54
+ * is correct and stays — it is the entire reason an OAuth-capable client offers to authenticate. But
55
+ * an MCP client raises that 401 as a TRANSPORT error: it throws before the body becomes a tool
56
+ * result, so `authorizationRequiredBody` reaches probes and developers and never reaches the model.
57
+ * On a live Claude Code session that gap was filled by the client's own guess — "the token expired",
58
+ * said about a server that had never been handed a token. Returning the refusal as a plain result
59
+ * instead would fix the reading and break the button (`docs/mcp-oauth-client-probe.md`), so the fact
60
+ * is stated a second time, up front, where it is always readable.
61
+ *
62
+ * ONLY WHILE IT IS TRUE. With a credential in hand every notice would be noise, charged to the
63
+ * agent's context on every `tools/list` for every tool — so a keyed server adds nothing at all.
64
+ */
65
+ export interface AccessContext {
66
+ /** The Streamable HTTP entrypoint. Changes the recipe, exactly as it does for `HttpClient`. */
67
+ remote?: boolean;
68
+ /** Whether this server was built with an API key. */
69
+ hasCredential: boolean;
70
+ }
71
+ /**
72
+ * The suffix appended to one tool's description, or `''` when the tool needs nothing.
73
+ *
74
+ * The session/keyed split is the same one `keyless_alternative` makes in the refusal, and it is
75
+ * here for the same reason: telling every refused caller to run `start_login` walked a human
76
+ * through an entire Telegram login and into a SECOND refusal that contradicted the first
77
+ * (`h-fix-mcp-refusal-roads`).
78
+ */
79
+ export declare function accessNoticeFor(tool: string, context: AccessContext): string;
80
+ /**
81
+ * The `instructions` string of the `initialize` result — the one place the whole arrangement can be
82
+ * stated once instead of per tool.
83
+ *
84
+ * Whether a given client actually shows this to its model is NOT assumed here; it is measured on a
85
+ * live client as part of this task, and the per-tool notice above is what carries the fact when the
86
+ * answer is no.
87
+ */
88
+ export declare function serverInstructions(context: AccessContext): string;
89
+ /**
90
+ * RFC 9728 — the document our `WWW-Authenticate` names in `resource_metadata`.
91
+ *
92
+ * A DELIBERATE SECOND COPY of `PROTECTED_RESOURCE_METADATA_PATH` in the backend's
93
+ * `src/constants/mcpOauth.ts`. This package is its own build and published separately, so it cannot
94
+ * import from `src/`; the two are held together by text instead —
95
+ * `src/__tests__/drift/mcp-oauth-remote-wiring.test.ts` reads this literal out of this file and
96
+ * compares it with the constant. Rename either side without the other and that suite goes red,
97
+ * which is the only thing standing between "the header points at a document" and "the header points
98
+ * at a 404 while every test is green".
99
+ */
100
+ export declare const PROTECTED_RESOURCE_METADATA_PATH = "/.well-known/oauth-protected-resource";
101
+ /** Absolute address of the protected-resource document for a given API origin. */
102
+ export declare function resourceMetadataUrl(baseUrl: string): string;
103
+ /**
104
+ * The `WWW-Authenticate` value. Form taken from the run that actually worked — all three clients
105
+ * followed `resource_metadata` from this exact shape (`mcp/probe/oauth-probe.mjs`).
106
+ */
107
+ export declare function authorizationChallenge(baseUrl: string): string;
108
+ export interface AuthorizationRequiredBody {
109
+ error: 'authorization_required';
110
+ error_description: string;
111
+ /**
112
+ * `GET /api/v1/mcp/connect` — the machine-readable connection document, for the CLIENT. It was
113
+ * called `login_url` until a human opened it in a browser expecting a sign-in page and got raw
114
+ * JSON. There is no page that connects an MCP client: connecting happens inside the client.
115
+ */
116
+ connect_instructions_url: string;
117
+ /**
118
+ * What `start_login` gets this caller — true PER TOOL. For a tool in `SESSION_TOOLS` it is a
119
+ * working road; for any other keyed tool it says so plainly, instead of sending them down it.
120
+ */
121
+ keyless_alternative: string;
122
+ }
123
+ /**
124
+ * Stands in for the name of a `tools/call` that did not give one. It never matches a real tool, so
125
+ * it cannot collide with `KEYLESS_TOOLS`, and the refusal below recognises it.
126
+ */
127
+ export declare const MALFORMED_TOOL_NAME = "<unnamed>";
128
+ /**
129
+ * The refusal for a `tools/call` that named no tool.
130
+ *
131
+ * Its own body because it is its own situation: such a call is classified KEYED (fail-closed), but
132
+ * answering it with "the tool <unnamed> needs a Proof account. Sign in…" would send somebody whose
133
+ * message was malformed off to authenticate, which fixes nothing. The 401 and the challenge stay —
134
+ * the classification is what they follow from — while the words say what is actually wrong.
135
+ */
136
+ export declare function malformedToolCallBody(): {
137
+ error: 'invalid_request';
138
+ error_description: string;
139
+ };
140
+ /**
141
+ * The human half of the refusal, written for the WORST measured client rather than the best.
142
+ *
143
+ * MCP Inspector replays the call by itself and Claude Desktop does it after one button press, so
144
+ * neither user ever reads this. Claude Code turns toward OAuth, stops before the browser step,
145
+ * needs a separate `claude mcp login`, and does NOT replay the call — for that person this text is
146
+ * the entire instruction, which is why it names the action twice over rather than reporting a
147
+ * state.
148
+ *
149
+ * `connect_instructions_url` is the existing public `GET /api/v1/mcp/connect` (`h-mcp-showcase`) rather than a new
150
+ * address. Nothing here makes a network call: `start_login` would be the more direct link, but it
151
+ * CREATES a login session, so calling it from a 401 handler would hand any anonymous caller a
152
+ * generator of Redis entries. It is named in the text instead, for clients that cannot speak OAuth
153
+ * at all.
154
+ */
155
+ export declare function authorizationRequiredBody(baseUrl: string, tool: string): AuthorizationRequiredBody;
156
+ /**
157
+ * The refusal for a request whose credential does not match the session it names.
158
+ *
159
+ * A separate text because it is a different situation, not a different tool: nothing the caller
160
+ * asked for is wrong, the session id and the token simply belong to two different connections. The
161
+ * challenge beside it is the same, so a client that lost its token still turns toward the
162
+ * authorization server instead of retrying forever.
163
+ */
164
+ export declare function sessionCredentialMismatchBody(): {
165
+ error: 'authorization_required';
166
+ error_description: string;
167
+ };
168
+ /** One tool call that needs an account. `malformed` means the call named no tool at all. */
169
+ export interface KeyedToolCall {
170
+ name: string;
171
+ malformed: boolean;
172
+ }
173
+ /**
174
+ * Names of the tool calls in this JSON-RPC message that need an account.
175
+ *
176
+ * FAIL-CLOSED on every axis: a name that is not in `KEYLESS_TOOLS` is keyed, including one that
177
+ * matches no tool at all — a typo must break a tool rather than open a hole. Anything that is not a
178
+ * `tools/call` (the handshake, `tools/list`, notifications, an unparseable body) contributes
179
+ * nothing, because refusing those is the arrangement the probe measured as a broken connection.
180
+ */
181
+ export declare function keyedToolsIn(body: unknown): KeyedToolCall[];
182
+ //# sourceMappingURL=authBoundary.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authBoundary.d.ts","sourceRoot":"","sources":["../src/authBoundary.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,aAAa,EAAE,WAAW,CAAC,MAAM,CAc5C,CAAC;AAEH;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,aAAa,EAAE,WAAW,CAAC,MAAM,CAuD5C,CAAC;AAEH;;;;;;;;;;;;;;;GAeG;AACH,MAAM,WAAW,aAAa;IAC5B,+FAA+F;IAC/F,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,qDAAqD;IACrD,aAAa,EAAE,OAAO,CAAC;CACxB;AAwBD;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,GAAG,MAAM,CAQ5E;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,aAAa,GAAG,MAAM,CA4CjE;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,gCAAgC,0CAA0C,CAAC;AAExF,kFAAkF;AAClF,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAE3D;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAE9D;AAED,MAAM,WAAW,yBAAyB;IACxC,KAAK,EAAE,wBAAwB,CAAC;IAChC,iBAAiB,EAAE,MAAM,CAAC;IAC1B;;;;OAIG;IACH,wBAAwB,EAAE,MAAM,CAAC;IACjC;;;OAGG;IACH,mBAAmB,EAAE,MAAM,CAAC;CAC7B;AAED;;;GAGG;AACH,eAAO,MAAM,mBAAmB,cAAc,CAAC;AAE/C;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,IAAI;IAAE,KAAK,EAAE,iBAAiB,CAAC;IAAC,iBAAiB,EAAE,MAAM,CAAA;CAAE,CAO/F;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,yBAAyB,CAmBlG;AAED;;;;;;;GAOG;AACH,wBAAgB,6BAA6B,IAAI;IAAE,KAAK,EAAE,wBAAwB,CAAC;IAAC,iBAAiB,EAAE,MAAM,CAAA;CAAE,CAO9G;AAED,4FAA4F;AAC5F,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,OAAO,CAAC;CACpB;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,OAAO,GAAG,aAAa,EAAE,CA0B3D"}
@@ -0,0 +1,328 @@
1
+ /**
2
+ * The boundary between what this server answers with no account and what needs one
3
+ * (`h-mcp-oauth-remote-wiring`).
4
+ *
5
+ * WHY IT LIVES HERE, and not inside a tool handler: the answer to "you need to sign in" is an HTTP
6
+ * 401 carrying `WWW-Authenticate`, and that is what a client's OAuth stack reads. By the time a tool
7
+ * handler runs, the response is already a JSON-RPC result — the same refusal delivered there is
8
+ * text, not a challenge, and every measured client ignores it (`docs/mcp-oauth-client-probe.md`).
9
+ * So the remote server has to classify the message BEFORE handing it to the transport, and this
10
+ * module is the one place that classification is written down.
11
+ *
12
+ * WHERE IT SITS, and why not on `initialize`: the control measurement in the probe put a 401 on the
13
+ * opening handshake and the human saw `Failed to connect — connection timed out after 30000ms`,
14
+ * i.e. a broken connection rather than an invitation to sign in. Anonymous connection is a closed
15
+ * criterion of `h-mcp-remote`, so the 401 lands on the TOOL CALL that needs an account and nowhere
16
+ * else.
17
+ */
18
+ /**
19
+ * Tools that work with no credential at all.
20
+ *
21
+ * Derived from `PUBLIC_PATHS` in `./http.js` — the endpoints the HTTP client is allowed to call
22
+ * with no bearer — plus the two tools that never touch our API: `verify_delegation` /
23
+ * `verify_delegations` run `@proof-holdings/delegation-verifier` against an issuer's public
24
+ * surfaces, and `render_auth_link` is local QR generation.
25
+ *
26
+ * `__tests__/auth-boundary.test.ts` compares this list against the tools the factory actually
27
+ * registers, in BOTH directions: a name here that no tool answers to would refuse nothing while
28
+ * reading exactly like a boundary, and anything the server registers that is not here must be
29
+ * refused without a key.
30
+ */
31
+ export const KEYLESS_TOOLS = new Set([
32
+ // Bootstrap: create an account and sign in, which is how a keyless caller stops being keyless.
33
+ 'create_account',
34
+ 'send_account_email',
35
+ 'wait_for_account_creation',
36
+ 'start_login',
37
+ 'wait_for_login',
38
+ // Public verification surface: checking somebody else's proof is not an account operation.
39
+ 'validate_proof',
40
+ 'list_revoked_proofs',
41
+ 'verify_delegation',
42
+ 'verify_delegations',
43
+ // Local computation, no HTTP at all.
44
+ 'render_auth_link',
45
+ ]);
46
+ /**
47
+ * Tools that WORK over a `start_login` session.
48
+ *
49
+ * Their endpoints are the caller's own account records — the paths `SESSION_AUTH_PREFIXES` in
50
+ * `./http.ts` signs with the session token (plus `/api/v1/auth/me`). Every other keyed tool needs
51
+ * the API key the client's Authenticate step grants, and the refusal below tells the caller which
52
+ * side of that line the tool they asked for is on, BEFORE they walk the road. It used to say
53
+ * "call start_login" to everyone; a human did, signed in through Telegram, and was refused again by
54
+ * a second text that finally explained the difference (h-fix-mcp-refusal-roads).
55
+ *
56
+ * Hand-written here because the package cannot read docs/api-map.yaml at runtime, and held equal to
57
+ * the truth in BOTH directions by `src/__tests__/drift/api-parity.test.ts` (`parseMcpSessionTools`
58
+ * joins this set to the map through the paths the client signs) and by `validateMcpSessionToolCoverage`
59
+ * in `scripts/validate-api-map.ts`. `__tests__/auth-boundary.test.ts` keeps it to registered tools
60
+ * and disjoint from `KEYLESS_TOOLS`.
61
+ */
62
+ export const SESSION_TOOLS = new Set([
63
+ 'add_domain',
64
+ 'add_verification_provider',
65
+ 'cancel_user_request',
66
+ 'check_domain_credentials',
67
+ 'check_domain_email_status',
68
+ 'check_user_domain_verification',
69
+ 'claim_request_assets',
70
+ 'claim_username',
71
+ 'confirm_domain_email_code',
72
+ 'connect_cloudflare',
73
+ 'connect_dns_provider',
74
+ 'connect_godaddy',
75
+ 'create_api_key',
76
+ 'create_dns_credential',
77
+ 'create_user_request',
78
+ 'delete_dns_credential',
79
+ 'delete_domain',
80
+ 'extend_request',
81
+ 'get_2fa_status',
82
+ 'get_add_email_status',
83
+ 'get_add_phone_status',
84
+ 'get_api_key_usage',
85
+ 'get_current_user',
86
+ 'get_dns_providers',
87
+ 'get_domain',
88
+ 'get_user_domain_verification_status',
89
+ 'list_api_keys',
90
+ 'list_dns_credentials',
91
+ 'list_domains',
92
+ 'list_emails',
93
+ 'list_incoming_requests',
94
+ 'list_my_requests',
95
+ 'list_phones',
96
+ 'regenerate_api_key',
97
+ 'remove_email',
98
+ 'remove_phone',
99
+ 'resend_domain_email',
100
+ 'resend_email_otp',
101
+ 'revoke_api_key',
102
+ 'set_primary_email',
103
+ 'set_primary_phone',
104
+ 'setup_domain_email',
105
+ 'share_request_email',
106
+ 'start_2fa',
107
+ 'start_add_email',
108
+ 'start_add_phone',
109
+ 'start_domain_email_verification',
110
+ 'start_user_domain_verification',
111
+ 'update_public_proofs',
112
+ 'verify_2fa',
113
+ 'verify_2fa_magic_link',
114
+ 'verify_domain',
115
+ 'verify_domain_with_credentials',
116
+ 'verify_email_otp',
117
+ ]);
118
+ /** How to get an account here — the one sentence that differs between the two transports. */
119
+ function credentialRecipe(remote) {
120
+ return remote
121
+ ? 'authenticate this client (in Claude Code: /mcp → this server → Authenticate)'
122
+ : "set PROOF_API_KEY in this server's environment and restart it";
123
+ }
124
+ /**
125
+ * The same recipe as an IMPERATIVE, for the per-tool notice.
126
+ *
127
+ * Separate from the sentence above because every registration outside `KEYLESS_TOOLS` pays for it.
128
+ * Measured on the built server:
129
+ * the full-prose form added 33.8 KB to 41.8 KB of descriptions — it doubled the anonymous
130
+ * `tools/list`, which is the listing this whole epic exists to serve. The recipe survives; the
131
+ * padding does not, and `instructions` carries the long form once.
132
+ */
133
+ function shortRecipe(remote) {
134
+ return remote
135
+ ? 'Authenticate this client (Claude Code: /mcp → Authenticate)'
136
+ : 'Set PROOF_API_KEY and restart this server';
137
+ }
138
+ /**
139
+ * The suffix appended to one tool's description, or `''` when the tool needs nothing.
140
+ *
141
+ * The session/keyed split is the same one `keyless_alternative` makes in the refusal, and it is
142
+ * here for the same reason: telling every refused caller to run `start_login` walked a human
143
+ * through an entire Telegram login and into a SECOND refusal that contradicted the first
144
+ * (`h-fix-mcp-refusal-roads`).
145
+ */
146
+ export function accessNoticeFor(tool, context) {
147
+ if (context.hasCredential)
148
+ return '';
149
+ if (KEYLESS_TOOLS.has(tool))
150
+ return '';
151
+ const recipe = shortRecipe(context.remote);
152
+ return SESSION_TOOLS.has(tool)
153
+ ? `\n\nACCESS: needs a Proof account. ${recipe}, or sign in with start_login — a session opens this tool — then call this tool again.`
154
+ : `\n\nACCESS: needs a Proof account. ${recipe}, then call this tool again. start_login does NOT open this tool.`;
155
+ }
156
+ /**
157
+ * The `instructions` string of the `initialize` result — the one place the whole arrangement can be
158
+ * stated once instead of per tool.
159
+ *
160
+ * Whether a given client actually shows this to its model is NOT assumed here; it is measured on a
161
+ * live client as part of this task, and the per-tool notice above is what carries the fact when the
162
+ * answer is no.
163
+ */
164
+ export function serverInstructions(context) {
165
+ const opening = 'Proof — identity and control verification (proof.holdings): verification requests across SMS, ' +
166
+ 'messenger and biometric channels, human approvals, proof tokens, delegations and public profiles.';
167
+ if (context.hasCredential) {
168
+ return (`${opening} This connection is authenticated. Which tools work depends on the SCOPES of the key it ` +
169
+ 'carries, not on this server: a tool outside them answers 403 insufficient_scope naming the scope it ' +
170
+ 'wanted, which is a different problem from needing an account.');
171
+ }
172
+ // DERIVED from the set rather than retyped beside it: the keyless surface has widened three times
173
+ // in this epic, and a prose list would go on claiming an account is needed for a tool that needs
174
+ // none, with every gate green. Nothing hand-written describes the set either — a sentence
175
+ // categorising what these tools "cover" is true of today's ten and silently false of the
176
+ // eleventh, which is the same drift one clause later.
177
+ const keyless = [...KEYLESS_TOOLS].sort().join(', ');
178
+ // The 401 paragraph is REMOTE-ONLY. On stdio there is no HTTP layer to answer 401: the refusal is
179
+ // an `api_key_required` the handler returns as readable text, so an opaque failure there really is
180
+ // a crashed process or a call that outlived the client's timeout. Telling the agent otherwise
181
+ // would hand it a pre-written wrong cause — the same defect as "the token expired".
182
+ const opaqueFailure = context.remote
183
+ ? 'If a tool call fails at the transport with no readable explanation, the usual cause is this server ' +
184
+ 'answering 401 to ask for authentication — the refusal carries its reason in a body most clients ' +
185
+ 'discard, so no expired token is involved: authenticate, then make the call again. A session left ' +
186
+ 'idle for a long time is the other cause: this server drops idle sessions, and the next call on a ' +
187
+ 'dropped one needs a reconnect rather than a credential.'
188
+ : 'A tool that needs an account answers a readable `api_key_required` result rather than failing, so ' +
189
+ 'an opaque failure here is not a credential problem — it is a crashed server process or a call that ' +
190
+ "outlived your client's request timeout.";
191
+ return [
192
+ `${opening} This connection is ANONYMOUS — nothing has been signed in yet.`,
193
+ `Works right now with no account: ${keyless}.`,
194
+ `Everything else needs a Proof account, and each such tool says so in its own description. Two roads: ${credentialRecipe(context.remote)} and then CALL THE TOOL AGAIN — some clients replay it for you, Claude Code does not. That grants an API key, whose SCOPES then decide which tools work (a tool outside them answers 403 insufficient_scope, not a request to sign in). Or call start_login and wait_for_login, which signs ` +
195
+ 'in a session that opens only your own account records — emails, phones, domains, API keys, 2FA, DNS ' +
196
+ "credentials, verification requests, your public profile's username and proof list.",
197
+ opaqueFailure,
198
+ ].join('\n\n');
199
+ }
200
+ /**
201
+ * RFC 9728 — the document our `WWW-Authenticate` names in `resource_metadata`.
202
+ *
203
+ * A DELIBERATE SECOND COPY of `PROTECTED_RESOURCE_METADATA_PATH` in the backend's
204
+ * `src/constants/mcpOauth.ts`. This package is its own build and published separately, so it cannot
205
+ * import from `src/`; the two are held together by text instead —
206
+ * `src/__tests__/drift/mcp-oauth-remote-wiring.test.ts` reads this literal out of this file and
207
+ * compares it with the constant. Rename either side without the other and that suite goes red,
208
+ * which is the only thing standing between "the header points at a document" and "the header points
209
+ * at a 404 while every test is green".
210
+ */
211
+ export const PROTECTED_RESOURCE_METADATA_PATH = '/.well-known/oauth-protected-resource';
212
+ /** Absolute address of the protected-resource document for a given API origin. */
213
+ export function resourceMetadataUrl(baseUrl) {
214
+ return `${baseUrl.replace(/\/+$/, '')}${PROTECTED_RESOURCE_METADATA_PATH}`;
215
+ }
216
+ /**
217
+ * The `WWW-Authenticate` value. Form taken from the run that actually worked — all three clients
218
+ * followed `resource_metadata` from this exact shape (`mcp/probe/oauth-probe.mjs`).
219
+ */
220
+ export function authorizationChallenge(baseUrl) {
221
+ return `Bearer realm="proof", resource_metadata="${resourceMetadataUrl(baseUrl)}"`;
222
+ }
223
+ /**
224
+ * Stands in for the name of a `tools/call` that did not give one. It never matches a real tool, so
225
+ * it cannot collide with `KEYLESS_TOOLS`, and the refusal below recognises it.
226
+ */
227
+ export const MALFORMED_TOOL_NAME = '<unnamed>';
228
+ /**
229
+ * The refusal for a `tools/call` that named no tool.
230
+ *
231
+ * Its own body because it is its own situation: such a call is classified KEYED (fail-closed), but
232
+ * answering it with "the tool <unnamed> needs a Proof account. Sign in…" would send somebody whose
233
+ * message was malformed off to authenticate, which fixes nothing. The 401 and the challenge stay —
234
+ * the classification is what they follow from — while the words say what is actually wrong.
235
+ */
236
+ export function malformedToolCallBody() {
237
+ return {
238
+ error: 'invalid_request',
239
+ error_description: 'This tools/call named no tool, so there is nothing to run — and nothing to authorize either. ' +
240
+ 'Send params.name with the tool you meant.',
241
+ };
242
+ }
243
+ /**
244
+ * The human half of the refusal, written for the WORST measured client rather than the best.
245
+ *
246
+ * MCP Inspector replays the call by itself and Claude Desktop does it after one button press, so
247
+ * neither user ever reads this. Claude Code turns toward OAuth, stops before the browser step,
248
+ * needs a separate `claude mcp login`, and does NOT replay the call — for that person this text is
249
+ * the entire instruction, which is why it names the action twice over rather than reporting a
250
+ * state.
251
+ *
252
+ * `connect_instructions_url` is the existing public `GET /api/v1/mcp/connect` (`h-mcp-showcase`) rather than a new
253
+ * address. Nothing here makes a network call: `start_login` would be the more direct link, but it
254
+ * CREATES a login session, so calling it from a 401 handler would hand any anonymous caller a
255
+ * generator of Redis entries. It is named in the text instead, for clients that cannot speak OAuth
256
+ * at all.
257
+ */
258
+ export function authorizationRequiredBody(baseUrl, tool) {
259
+ const sessionReaches = SESSION_TOOLS.has(tool);
260
+ return {
261
+ error: 'authorization_required',
262
+ error_description: `The tool "${tool}" needs a Proof account. Sign in, then CALL THE TOOL AGAIN — some clients ` +
263
+ 'replay the call for you, Claude Code does not. Connecting happens inside the client; there is ' +
264
+ 'no web page for it. In Claude Code: /mcp → this server → Authenticate, or `claude mcp login ' +
265
+ '<server>` in a separate terminal window (it needs an interactive terminal). Then repeat the ' +
266
+ 'call yourself.',
267
+ connect_instructions_url: `${baseUrl.replace(/\/+$/, '')}/api/v1/mcp/connect`,
268
+ keyless_alternative: sessionReaches
269
+ ? 'No OAuth support in your client? Call start_login — it returns a sign-in link, and ' +
270
+ 'render_auth_link turns that link into a QR code — then wait_for_login. This tool works over ' +
271
+ 'that signed-in session.'
272
+ : 'start_login will NOT unlock this tool. A signed-in session covers only your own account ' +
273
+ 'records — emails, phones, domains, API keys, 2FA, DNS credentials, verification requests, ' +
274
+ "your public profile's username and proof list. This tool needs the API key that connecting grants.",
275
+ };
276
+ }
277
+ /**
278
+ * The refusal for a request whose credential does not match the session it names.
279
+ *
280
+ * A separate text because it is a different situation, not a different tool: nothing the caller
281
+ * asked for is wrong, the session id and the token simply belong to two different connections. The
282
+ * challenge beside it is the same, so a client that lost its token still turns toward the
283
+ * authorization server instead of retrying forever.
284
+ */
285
+ export function sessionCredentialMismatchBody() {
286
+ return {
287
+ error: 'authorization_required',
288
+ error_description: 'This session was opened with a different credential. A session belongs to the token that ' +
289
+ 'opened it — reconnect to start a new one rather than reusing this session id.',
290
+ };
291
+ }
292
+ /**
293
+ * Names of the tool calls in this JSON-RPC message that need an account.
294
+ *
295
+ * FAIL-CLOSED on every axis: a name that is not in `KEYLESS_TOOLS` is keyed, including one that
296
+ * matches no tool at all — a typo must break a tool rather than open a hole. Anything that is not a
297
+ * `tools/call` (the handshake, `tools/list`, notifications, an unparseable body) contributes
298
+ * nothing, because refusing those is the arrangement the probe measured as a broken connection.
299
+ */
300
+ export function keyedToolsIn(body) {
301
+ const messages = Array.isArray(body) ? body : [body];
302
+ const keyed = [];
303
+ for (const message of messages) {
304
+ const entry = message;
305
+ if (!entry || typeof entry !== 'object')
306
+ continue;
307
+ if (entry.method !== 'tools/call')
308
+ continue;
309
+ const name = entry.params?.name;
310
+ // A `tools/call` whose name is missing or not a string is KEYED, under a placeholder —
311
+ // `continue` would have made a malformed call the one shape that walks past the gate.
312
+ //
313
+ // REACHABLE, and stated so rather than waved away: on the remote transport this classification
314
+ // runs BEFORE the message reaches the SDK, so the SDK's schema never gets to refuse it. Which
315
+ // is why "malformed" is carried as a FLAG rather than inferred later from the placeholder
316
+ // string: a caller may send `params.name: "<unnamed>"`, and deciding at the refusal by
317
+ // comparing strings would answer that caller "this call named no tool" — a sentence about a
318
+ // message they did not send. The decision belongs where the evidence is.
319
+ if (typeof name !== 'string' || !name) {
320
+ keyed.push({ name: MALFORMED_TOOL_NAME, malformed: true });
321
+ continue;
322
+ }
323
+ if (!KEYLESS_TOOLS.has(name))
324
+ keyed.push({ name, malformed: false });
325
+ }
326
+ return keyed;
327
+ }
328
+ //# sourceMappingURL=authBoundary.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authBoundary.js","sourceRoot":"","sources":["../src/authBoundary.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { HttpClient } from './http.js';
3
+ export interface ProofServerOptions {
4
+ /** API key (Bearer). Absent → public mode, exactly as on the stdio path. */
5
+ apiKey?: string;
6
+ baseUrl: string;
7
+ /**
8
+ * Set by the REMOTE entrypoint. The only thing it changes is what a "you need a key" refusal
9
+ * tells the user to do: the stdio instruction (set `PROOF_API_KEY`, restart the server) is
10
+ * impossible on a server run by somebody else.
11
+ */
12
+ remote?: boolean;
13
+ }
14
+ export interface ProofServerInstance {
15
+ server: McpServer;
16
+ http: HttpClient;
17
+ }
18
+ /**
19
+ * Builds one MCP server with its own HTTP client.
20
+ *
21
+ * Why a factory at all (`h-mcp-remote`, SC-4): the stdio entrypoint serves exactly one user, so one
22
+ * `HttpClient` per process is correct there. The remote server serves many, and `HttpClient` keeps
23
+ * `sessionToken` / `refreshToken` as INSTANCE fields written by `captureSetCookie` — sharing one
24
+ * across connections would hand the next user the previous user's logged-in session. So the unit of
25
+ * isolation is the connection, and this function is that unit.
26
+ *
27
+ * The registration list below is the ONLY copy in the package: `server.ts` calls this rather than
28
+ * repeating it. A second copy would work on the day it was written and drift one module at a time
29
+ * afterwards, with both paths green — `__tests__/factory.test.ts` pins both the list's completeness
30
+ * against `src/tools` and the absence of a second copy in `server.ts`.
31
+ */
32
+ export declare function createProofServer(options: ProofServerOptions): ProofServerInstance;
33
+ //# sourceMappingURL=factory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../src/factory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEpE,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAmCvC,MAAM,WAAW,kBAAkB;IACjC,4EAA4E;IAC5E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,SAAS,CAAC;IAClB,IAAI,EAAE,UAAU,CAAC;CAClB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,kBAAkB,GAAG,mBAAmB,CAkFlF"}