@opendatalabs/personal-server-ts-server 1.34.1 → 1.35.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 (180) hide show
  1. package/dist/api-auth.d.ts +1 -0
  2. package/dist/api-auth.d.ts.map +1 -1
  3. package/dist/api-auth.js.map +1 -1
  4. package/dist/api.d.ts +3 -0
  5. package/dist/api.d.ts.map +1 -1
  6. package/dist/api.js +3 -0
  7. package/dist/api.js.map +1 -1
  8. package/dist/app.d.ts +14 -2
  9. package/dist/app.d.ts.map +1 -1
  10. package/dist/app.js +64 -8
  11. package/dist/app.js.map +1 -1
  12. package/dist/bootstrap.d.ts +19 -0
  13. package/dist/bootstrap.d.ts.map +1 -1
  14. package/dist/bootstrap.js +87 -4
  15. package/dist/bootstrap.js.map +1 -1
  16. package/dist/client.d.ts +1 -1
  17. package/dist/client.d.ts.map +1 -1
  18. package/dist/client.js +7 -0
  19. package/dist/client.js.map +1 -1
  20. package/dist/enclave-main.d.ts.map +1 -1
  21. package/dist/enclave-main.js +0 -2
  22. package/dist/enclave-main.js.map +1 -1
  23. package/dist/mcp/durable-state.d.ts +2 -1
  24. package/dist/mcp/durable-state.d.ts.map +1 -1
  25. package/dist/mcp/durable-state.js +35 -1
  26. package/dist/mcp/durable-state.js.map +1 -1
  27. package/dist/pdpp/bootstrap.d.ts.map +1 -1
  28. package/dist/pdpp/bootstrap.js +1 -0
  29. package/dist/pdpp/bootstrap.js.map +1 -1
  30. package/dist/pdpp/records-bootstrap.d.ts +47 -1
  31. package/dist/pdpp/records-bootstrap.d.ts.map +1 -1
  32. package/dist/pdpp/records-bootstrap.js +78 -2
  33. package/dist/pdpp/records-bootstrap.js.map +1 -1
  34. package/dist/pdpp/replica-authorization.d.ts +35 -0
  35. package/dist/pdpp/replica-authorization.d.ts.map +1 -0
  36. package/dist/pdpp/replica-authorization.js +234 -0
  37. package/dist/pdpp/replica-authorization.js.map +1 -0
  38. package/dist/pending-blob-deletions.d.ts +1 -0
  39. package/dist/pending-blob-deletions.d.ts.map +1 -1
  40. package/dist/pending-blob-deletions.js +8 -4
  41. package/dist/pending-blob-deletions.js.map +1 -1
  42. package/dist/routes/mcp.d.ts +18 -2
  43. package/dist/routes/mcp.d.ts.map +1 -1
  44. package/dist/routes/mcp.js +111 -11
  45. package/dist/routes/mcp.js.map +1 -1
  46. package/dist/routes/oura.d.ts +6 -0
  47. package/dist/routes/oura.d.ts.map +1 -0
  48. package/dist/routes/oura.js +272 -0
  49. package/dist/routes/oura.js.map +1 -0
  50. package/dist/routes/pdpp-auth-query-keys.d.ts +7 -0
  51. package/dist/routes/pdpp-auth-query-keys.d.ts.map +1 -0
  52. package/dist/routes/pdpp-auth-query-keys.js +25 -0
  53. package/dist/routes/pdpp-auth-query-keys.js.map +1 -0
  54. package/dist/routes/pdpp-auth.d.ts +3 -1
  55. package/dist/routes/pdpp-auth.d.ts.map +1 -1
  56. package/dist/routes/pdpp-auth.js +126 -45
  57. package/dist/routes/pdpp-auth.js.map +1 -1
  58. package/dist/routes/pdpp-blobs.d.ts +13 -1
  59. package/dist/routes/pdpp-blobs.d.ts.map +1 -1
  60. package/dist/routes/pdpp-blobs.js +100 -65
  61. package/dist/routes/pdpp-blobs.js.map +1 -1
  62. package/dist/routes/pdpp-connections.d.ts.map +1 -1
  63. package/dist/routes/pdpp-connections.js +24 -1
  64. package/dist/routes/pdpp-connections.js.map +1 -1
  65. package/dist/routes/pdpp-instance-bindings.d.ts +3 -0
  66. package/dist/routes/pdpp-instance-bindings.d.ts.map +1 -1
  67. package/dist/routes/pdpp-instance-bindings.js +63 -0
  68. package/dist/routes/pdpp-instance-bindings.js.map +1 -1
  69. package/dist/routes/pdpp-record-query.d.ts +19 -0
  70. package/dist/routes/pdpp-record-query.d.ts.map +1 -0
  71. package/dist/routes/pdpp-record-query.js +51 -0
  72. package/dist/routes/pdpp-record-query.js.map +1 -0
  73. package/dist/routes/pdpp-records.d.ts +12 -1
  74. package/dist/routes/pdpp-records.d.ts.map +1 -1
  75. package/dist/routes/pdpp-records.js +120 -233
  76. package/dist/routes/pdpp-records.js.map +1 -1
  77. package/dist/routes/pdpp-replica-quarantine-fixture.d.ts +65 -0
  78. package/dist/routes/pdpp-replica-quarantine-fixture.d.ts.map +1 -0
  79. package/dist/routes/pdpp-replica-quarantine-fixture.js +389 -0
  80. package/dist/routes/pdpp-replica-quarantine-fixture.js.map +1 -0
  81. package/dist/storage/claude-method-transition.d.ts +12 -0
  82. package/dist/storage/claude-method-transition.d.ts.map +1 -0
  83. package/dist/storage/claude-method-transition.js +139 -0
  84. package/dist/storage/claude-method-transition.js.map +1 -0
  85. package/dist/storage/import-operations.d.ts +16 -0
  86. package/dist/storage/import-operations.d.ts.map +1 -1
  87. package/dist/storage/import-operations.js +54 -1
  88. package/dist/storage/import-operations.js.map +1 -1
  89. package/dist/storage/index-schema.d.ts +1 -1
  90. package/dist/storage/index-schema.d.ts.map +1 -1
  91. package/dist/storage/index-schema.js +22 -5
  92. package/dist/storage/index-schema.js.map +1 -1
  93. package/dist/storage/legacy-scope-authority.d.ts.map +1 -1
  94. package/dist/storage/legacy-scope-authority.js +83 -11
  95. package/dist/storage/legacy-scope-authority.js.map +1 -1
  96. package/dist/storage/node-data-storage.d.ts.map +1 -1
  97. package/dist/storage/node-data-storage.js +58 -2
  98. package/dist/storage/node-data-storage.js.map +1 -1
  99. package/dist/storage/pdpp-binary-chunks.d.ts +120 -0
  100. package/dist/storage/pdpp-binary-chunks.d.ts.map +1 -0
  101. package/dist/storage/pdpp-binary-chunks.js +480 -0
  102. package/dist/storage/pdpp-binary-chunks.js.map +1 -0
  103. package/dist/storage/pdpp-records-sqlite-store.d.ts +96 -15
  104. package/dist/storage/pdpp-records-sqlite-store.d.ts.map +1 -1
  105. package/dist/storage/pdpp-records-sqlite-store.js +616 -211
  106. package/dist/storage/pdpp-records-sqlite-store.js.map +1 -1
  107. package/dist/storage/pdpp-replica-canonical-port.d.ts +202 -0
  108. package/dist/storage/pdpp-replica-canonical-port.d.ts.map +1 -0
  109. package/dist/storage/pdpp-replica-canonical-port.js +451 -0
  110. package/dist/storage/pdpp-replica-canonical-port.js.map +1 -0
  111. package/dist/storage/pdpp-replica-coordinator-state.d.ts +16 -0
  112. package/dist/storage/pdpp-replica-coordinator-state.d.ts.map +1 -0
  113. package/dist/storage/pdpp-replica-coordinator-state.js +25 -0
  114. package/dist/storage/pdpp-replica-coordinator-state.js.map +1 -0
  115. package/dist/storage/pdpp-replica-copies.d.ts +81 -0
  116. package/dist/storage/pdpp-replica-copies.d.ts.map +1 -0
  117. package/dist/storage/pdpp-replica-copies.js +59 -0
  118. package/dist/storage/pdpp-replica-copies.js.map +1 -0
  119. package/dist/storage/pdpp-replica-reconciler.d.ts +96 -0
  120. package/dist/storage/pdpp-replica-reconciler.d.ts.map +1 -0
  121. package/dist/storage/pdpp-replica-reconciler.js +710 -0
  122. package/dist/storage/pdpp-replica-reconciler.js.map +1 -0
  123. package/dist/storage/pdpp-replica-sql.d.ts +13 -0
  124. package/dist/storage/pdpp-replica-sql.d.ts.map +1 -0
  125. package/dist/storage/pdpp-replica-sql.js +43 -0
  126. package/dist/storage/pdpp-replica-sql.js.map +1 -0
  127. package/dist/storage/pdpp-replica-staging.d.ts +36 -0
  128. package/dist/storage/pdpp-replica-staging.d.ts.map +1 -0
  129. package/dist/storage/pdpp-replica-staging.js +202 -0
  130. package/dist/storage/pdpp-replica-staging.js.map +1 -0
  131. package/dist/storage/pdpp-replica-visibility-view.d.ts +64 -0
  132. package/dist/storage/pdpp-replica-visibility-view.d.ts.map +1 -0
  133. package/dist/storage/pdpp-replica-visibility-view.js +1397 -0
  134. package/dist/storage/pdpp-replica-visibility-view.js.map +1 -0
  135. package/dist/storage/pdpp-replica-writer-identity.d.ts +64 -0
  136. package/dist/storage/pdpp-replica-writer-identity.d.ts.map +1 -0
  137. package/dist/storage/pdpp-replica-writer-identity.js +81 -0
  138. package/dist/storage/pdpp-replica-writer-identity.js.map +1 -0
  139. package/dist/storage/pdpp-replication-door.d.ts +60 -0
  140. package/dist/storage/pdpp-replication-door.d.ts.map +1 -0
  141. package/dist/storage/pdpp-replication-door.js +203 -0
  142. package/dist/storage/pdpp-replication-door.js.map +1 -0
  143. package/dist/storage/pdpp-replication-host.d.ts +67 -0
  144. package/dist/storage/pdpp-replication-host.d.ts.map +1 -0
  145. package/dist/storage/pdpp-replication-host.js +159 -0
  146. package/dist/storage/pdpp-replication-host.js.map +1 -0
  147. package/dist/storage/pdpp-replication-memory.fixture.d.ts +2 -0
  148. package/dist/storage/pdpp-replication-memory.fixture.d.ts.map +1 -0
  149. package/dist/storage/pdpp-replication-memory.fixture.js +267 -0
  150. package/dist/storage/pdpp-replication-memory.fixture.js.map +1 -0
  151. package/dist/storage/pdpp-replication-preparation.d.ts +3 -0
  152. package/dist/storage/pdpp-replication-preparation.d.ts.map +1 -0
  153. package/dist/storage/pdpp-replication-preparation.js +3 -0
  154. package/dist/storage/pdpp-replication-preparation.js.map +1 -0
  155. package/dist/storage/pdpp-replication-promotion-memory.fixture.d.ts +2 -0
  156. package/dist/storage/pdpp-replication-promotion-memory.fixture.d.ts.map +1 -0
  157. package/dist/storage/pdpp-replication-promotion-memory.fixture.js +65 -0
  158. package/dist/storage/pdpp-replication-promotion-memory.fixture.js.map +1 -0
  159. package/dist/storage/pdpp-replication-receiver.d.ts +230 -0
  160. package/dist/storage/pdpp-replication-receiver.d.ts.map +1 -0
  161. package/dist/storage/pdpp-replication-receiver.js +950 -0
  162. package/dist/storage/pdpp-replication-receiver.js.map +1 -0
  163. package/dist/storage/pdpp-replication-sqlite.d.ts +95 -0
  164. package/dist/storage/pdpp-replication-sqlite.d.ts.map +1 -0
  165. package/dist/storage/pdpp-replication-sqlite.js +615 -0
  166. package/dist/storage/pdpp-replication-sqlite.js.map +1 -0
  167. package/dist/storage/pdpp-replication-transport.d.ts +39 -0
  168. package/dist/storage/pdpp-replication-transport.d.ts.map +1 -0
  169. package/dist/storage/pdpp-replication-transport.js +196 -0
  170. package/dist/storage/pdpp-replication-transport.js.map +1 -0
  171. package/dist/storage/schema-versions.d.ts +5 -0
  172. package/dist/storage/schema-versions.d.ts.map +1 -0
  173. package/dist/storage/schema-versions.js +26 -0
  174. package/dist/storage/schema-versions.js.map +1 -0
  175. package/dist/storage/strava-method-transition.d.ts +18 -0
  176. package/dist/storage/strava-method-transition.d.ts.map +1 -0
  177. package/dist/storage/strava-method-transition.js +209 -0
  178. package/dist/storage/strava-method-transition.js.map +1 -0
  179. package/dist/ui/ps-lite-debug.js +137018 -106931
  180. package/package.json +3 -3
@@ -0,0 +1,234 @@
1
+ import { PdppAuthorizationUnavailableError } from "@opendatalabs/personal-server-ts-core/ports/pdpp-auth";
2
+ import { resolveLocalInstance } from "@opendatalabs/personal-server-ts-core/replication/identity";
3
+ import { PDPP_DATA_ACCESS_TYPE_V02 } from "@opendatalabs/personal-server-ts-core/pdpp";
4
+ import { z } from "zod";
5
+ const nonempty = z.string().min(1);
6
+ const date = nonempty.refine((value) => Number.isFinite(Date.parse(value)));
7
+ const uniqueNames = z
8
+ .array(nonempty)
9
+ .min(1)
10
+ .refine((values) => new Set(values).size === values.length);
11
+ const stream = z
12
+ .object({
13
+ name: nonempty,
14
+ instance_ids: uniqueNames,
15
+ fields: uniqueNames,
16
+ resources: z.array(nonempty).optional(),
17
+ time_constraint: z
18
+ .object({
19
+ field: nonempty,
20
+ since: date.optional(),
21
+ until: date.optional(),
22
+ })
23
+ .strict()
24
+ .optional(),
25
+ })
26
+ .strict();
27
+ // This adapter accepts the complete frozen v0.2 grant. The v0.1 wire
28
+ // projection omits retained facts; inventing them here would create authority.
29
+ const introspection = z.object({
30
+ active: z.literal(true),
31
+ pdpp_token_kind: z.literal("client"),
32
+ subject_id: nonempty,
33
+ grant_id: nonempty,
34
+ client_id: nonempty,
35
+ exp: z.number().int().positive(),
36
+ authorization_details: z.tuple([
37
+ z.object({
38
+ type: z.literal(PDPP_DATA_ACCESS_TYPE_V02),
39
+ grant: z
40
+ .object({
41
+ version: z.literal("0.2.0"),
42
+ grant_id: nonempty,
43
+ issued_at: date,
44
+ subject: z.object({ id: nonempty }),
45
+ client: z.object({ client_id: nonempty }).passthrough(),
46
+ source: z.object({
47
+ kind: z.enum(["connector", "provider_native"]),
48
+ id: nonempty,
49
+ }),
50
+ source_declaration: z.object({ version: nonempty }),
51
+ purpose_code: nonempty,
52
+ access_mode: z.enum(["single_use", "continuous"]),
53
+ streams: z
54
+ .array(stream)
55
+ .min(1)
56
+ .refine((streams) => new Set(streams.map((entry) => entry.name)).size ===
57
+ streams.length),
58
+ expires_at: date.optional(),
59
+ })
60
+ .passthrough(),
61
+ }),
62
+ ]),
63
+ });
64
+ async function readContext(response) {
65
+ if (!response.ok || !response.body) {
66
+ await response.body?.cancel();
67
+ throw new PdppAuthorizationUnavailableError();
68
+ }
69
+ const reader = response.body.getReader();
70
+ const chunks = [];
71
+ let size = 0;
72
+ try {
73
+ while (true) {
74
+ const { done, value } = await reader.read();
75
+ if (done)
76
+ break;
77
+ size += value.byteLength;
78
+ if (size > 1024 * 1024)
79
+ throw new PdppAuthorizationUnavailableError();
80
+ chunks.push(value);
81
+ }
82
+ const bytes = new Uint8Array(size);
83
+ let offset = 0;
84
+ for (const chunk of chunks) {
85
+ bytes.set(chunk, offset);
86
+ offset += chunk.length;
87
+ }
88
+ return JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
89
+ }
90
+ finally {
91
+ await reader.cancel().catch(() => undefined);
92
+ }
93
+ }
94
+ /**
95
+ * Replica read authority stays at one configured issuer, with no positive cache.
96
+ * The host provisions a source-scoped issuer owner token for the existing
97
+ * authenticated introspection endpoint; it is never accepted as an app token.
98
+ * This harness-only credential has owner read authority. See the issuer's
99
+ * acknowledged separated-RS credential gap in pdpp-auth.ts (RFC 7662 route).
100
+ * No credential, grant, token, or revocation state is imported with records.
101
+ * This is explicit experimental host wiring, not ordinary bootstrap wiring.
102
+ * Inactive RFC 7662 results conceal revocation reasons and yield 401; the
103
+ * co-located issuer can distinguish grant_revoked and returns 403 instead.
104
+ * The returned grant is a translated local enforcement view. Its grant_id
105
+ * identifies the immutable issuer grant; never hash this view as consent.
106
+ */
107
+ export function createReplicaAuthorizationService(options) {
108
+ if (!options.owner)
109
+ throw new Error("Replica owner is required");
110
+ const issuer = new URL(options.issuerOrigin);
111
+ if (issuer.username ||
112
+ issuer.password ||
113
+ issuer.search ||
114
+ issuer.hash ||
115
+ issuer.pathname !== "/" ||
116
+ (issuer.protocol !== "https:" &&
117
+ !(issuer.protocol === "http:" &&
118
+ ["127.0.0.1", "[::1]"].includes(issuer.hostname)))) {
119
+ throw new Error("Replica issuer must be an HTTPS origin or loopback HTTP origin");
120
+ }
121
+ const fetcher = options.fetch ?? globalThis.fetch;
122
+ const now = options.now ?? Date.now;
123
+ const denied = (reason) => {
124
+ options.onDecision?.({ outcome: "denied", reason });
125
+ return { active: false };
126
+ };
127
+ const unavailable = (reason) => {
128
+ options.onDecision?.({ outcome: "unavailable", reason });
129
+ throw new PdppAuthorizationUnavailableError();
130
+ };
131
+ return {
132
+ async resolveToken(accessToken) {
133
+ if (!accessToken)
134
+ return denied("issuer_inactive");
135
+ let credential;
136
+ try {
137
+ credential = await options.credential();
138
+ }
139
+ catch {
140
+ return unavailable("credential_unavailable");
141
+ }
142
+ if (!credential)
143
+ return unavailable("credential_unavailable");
144
+ let wire;
145
+ try {
146
+ const response = await fetcher(`${issuer.origin}/pdpp/v1/introspect`, {
147
+ method: "POST",
148
+ headers: {
149
+ authorization: `Bearer ${credential}`,
150
+ "content-type": "application/x-www-form-urlencoded",
151
+ },
152
+ body: new URLSearchParams({ token: accessToken }),
153
+ redirect: "error",
154
+ cache: "no-store",
155
+ signal: AbortSignal.timeout(10_000),
156
+ });
157
+ wire = await readContext(response);
158
+ }
159
+ catch {
160
+ return unavailable("issuer_unavailable");
161
+ }
162
+ if (z
163
+ .object({ active: z.literal(false) })
164
+ .strict()
165
+ .safeParse(wire).success)
166
+ return denied("issuer_inactive");
167
+ if (z
168
+ .object({
169
+ active: z.literal(true),
170
+ pdpp_token_kind: z.literal("owner"),
171
+ })
172
+ .safeParse(wire).success)
173
+ return denied("token_kind");
174
+ const parsed = introspection.safeParse(wire);
175
+ if (!parsed.success)
176
+ return unavailable("invalid_response");
177
+ const context = parsed.data;
178
+ const grant = context.authorization_details[0].grant;
179
+ if (context.subject_id !== options.owner ||
180
+ grant.subject.id !== options.owner)
181
+ return denied("owner_mismatch");
182
+ if (grant.grant_id !== context.grant_id ||
183
+ grant.client.client_id !== context.client_id)
184
+ return denied("grant_mismatch");
185
+ const currentTime = now();
186
+ if (context.exp * 1000 <= currentTime ||
187
+ (grant.expires_at && Date.parse(grant.expires_at) <= currentTime))
188
+ return denied("expired");
189
+ const mappings = options.connections();
190
+ let mappingFailure;
191
+ const streams = grant.streams.map((stream) => ({
192
+ ...stream,
193
+ instance_ids: stream.instance_ids.map((handle) => {
194
+ const matches = mappings.filter((entry) => entry.issuerInstanceId === handle &&
195
+ entry.owner === options.owner &&
196
+ entry.sourceId === grant.source.id);
197
+ const identities = new Set(matches.map((entry) => entry.connectionId));
198
+ if (identities.size !== 1) {
199
+ mappingFailure =
200
+ identities.size === 0 ? "mapping_missing" : "mapping_ambiguous";
201
+ return "";
202
+ }
203
+ let local;
204
+ try {
205
+ local = resolveLocalInstance({
206
+ owner: options.owner,
207
+ sourceId: grant.source.id,
208
+ connectionId: matches[0].connectionId,
209
+ }, mappings);
210
+ }
211
+ catch {
212
+ mappingFailure = "mapping_invalid";
213
+ }
214
+ if (!local)
215
+ mappingFailure ??= "mapping_missing";
216
+ return local ?? "";
217
+ }),
218
+ }));
219
+ if (mappingFailure)
220
+ return denied(mappingFailure);
221
+ if (streams.some((stream) => new Set(stream.instance_ids).size !== stream.instance_ids.length))
222
+ return denied("mapping_collapsed");
223
+ return {
224
+ active: true,
225
+ tokenKind: "client",
226
+ subjectId: context.subject_id,
227
+ clientId: context.client_id,
228
+ expiresAt: new Date(context.exp * 1000).toISOString(),
229
+ grant: { ...grant, streams },
230
+ };
231
+ },
232
+ };
233
+ }
234
+ //# sourceMappingURL=replica-authorization.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"replica-authorization.js","sourceRoot":"","sources":["../../src/pdpp/replica-authorization.ts"],"names":[],"mappings":"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"}
@@ -5,6 +5,7 @@ import type { PendingBlobDeletionStore } from "@opendatalabs/personal-server-ts-
5
5
  * each. Lives next to `sync-cursor.json` under the server root; written
6
6
  * atomically (tmp + rename) like the cursor. A version-1 file (whole scopes)
7
7
  * is read as version-less markers the retry expands into exact keys.
8
+ * `planned` keeps each scope's planned-through watermark in the same write.
8
9
  */
9
10
  export declare function createFilePendingBlobDeletionStore(filePath: string): PendingBlobDeletionStore;
10
11
  //# sourceMappingURL=pending-blob-deletions.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"pending-blob-deletions.d.ts","sourceRoot":"","sources":["../src/pending-blob-deletions.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAEV,wBAAwB,EACzB,MAAM,6CAA6C,CAAC;AAOrD;;;;;;GAMG;AACH,wBAAgB,kCAAkC,CAChD,QAAQ,EAAE,MAAM,GACf,wBAAwB,CA+B1B"}
1
+ {"version":3,"file":"pending-blob-deletions.d.ts","sourceRoot":"","sources":["../src/pending-blob-deletions.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAEV,wBAAwB,EACzB,MAAM,6CAA6C,CAAC;AAarD;;;;;;;GAOG;AACH,wBAAgB,kCAAkC,CAChD,QAAQ,EAAE,MAAM,GACf,wBAAwB,CAmC1B"}
@@ -1,12 +1,13 @@
1
1
  import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
2
2
  import { dirname } from "node:path";
3
- import { createPendingBlobDeletionStore, normalizePendingBlobDeletions, } from "@opendatalabs/personal-server-ts-core/sync";
3
+ import { createPendingBlobDeletionStore, normalizePendingBlobDeletionPlanned, normalizePendingBlobDeletions, } from "@opendatalabs/personal-server-ts-core/sync";
4
4
  /**
5
5
  * File-backed retry markers for blob deletions whose gateway tombstone landed
6
6
  * but whose storage DELETE did not finish: one exact (scope, version) key
7
7
  * each. Lives next to `sync-cursor.json` under the server root; written
8
8
  * atomically (tmp + rename) like the cursor. A version-1 file (whole scopes)
9
9
  * is read as version-less markers the retry expands into exact keys.
10
+ * `planned` keeps each scope's planned-through watermark in the same write.
10
11
  */
11
12
  export function createFilePendingBlobDeletionStore(filePath) {
12
13
  const kv = {
@@ -14,7 +15,10 @@ export function createFilePendingBlobDeletionStore(filePath) {
14
15
  try {
15
16
  const raw = await readFile(filePath, "utf8");
16
17
  const parsed = JSON.parse(raw);
17
- return normalizePendingBlobDeletions(parsed.keys ?? parsed.scopes);
18
+ return {
19
+ keys: normalizePendingBlobDeletions(parsed.keys ?? parsed.scopes),
20
+ planned: normalizePendingBlobDeletionPlanned(parsed.planned),
21
+ };
18
22
  }
19
23
  catch (err) {
20
24
  if (err instanceof Error &&
@@ -25,10 +29,10 @@ export function createFilePendingBlobDeletionStore(filePath) {
25
29
  throw err;
26
30
  }
27
31
  },
28
- async write(keys) {
32
+ async write(keys, planned) {
29
33
  await mkdir(dirname(filePath), { recursive: true });
30
34
  const tmpPath = `${filePath}.tmp`;
31
- const state = { version: 2, keys };
35
+ const state = { version: 2, keys, planned };
32
36
  await writeFile(tmpPath, `${JSON.stringify(state, null, 2)}\n`);
33
37
  await rename(tmpPath, filePath);
34
38
  },
@@ -1 +1 @@
1
- {"version":3,"file":"pending-blob-deletions.js","sourceRoot":"","sources":["../src/pending-blob-deletions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACtE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EACL,8BAA8B,EAC9B,6BAA6B,GAE9B,MAAM,4CAA4C,CAAC;AAWpD;;;;;;GAMG;AACH,MAAM,UAAU,kCAAkC,CAChD,QAAgB;IAEhB,MAAM,EAAE,GAA0B;QAChC,KAAK,CAAC,IAAI;YACR,IAAI,CAAC;gBACH,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;gBAC7C,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAI5B,CAAC;gBACF,OAAO,6BAA6B,CAAC,MAAM,CAAC,IAAI,IAAI,MAAM,CAAC,MAAM,CAAC,CAAC;YACrE,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,IACE,GAAG,YAAY,KAAK;oBACpB,MAAM,IAAI,GAAG;oBACZ,GAA6B,CAAC,IAAI,KAAK,QAAQ,EAChD,CAAC;oBACD,OAAO,IAAI,CAAC;gBACd,CAAC;gBACD,MAAM,GAAG,CAAC;YACZ,CAAC;QACH,CAAC;QACD,KAAK,CAAC,KAAK,CAAC,IAAI;YACd,MAAM,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YACpD,MAAM,OAAO,GAAG,GAAG,QAAQ,MAAM,CAAC;YAClC,MAAM,KAAK,GAA6B,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC;YAC7D,MAAM,SAAS,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;YAChE,MAAM,MAAM,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;QAClC,CAAC;KACF,CAAC;IACF,OAAO,8BAA8B,CAAC,EAAE,CAAC,CAAC;AAC5C,CAAC"}
1
+ {"version":3,"file":"pending-blob-deletions.js","sourceRoot":"","sources":["../src/pending-blob-deletions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACtE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EACL,8BAA8B,EAC9B,mCAAmC,EACnC,6BAA6B,GAG9B,MAAM,4CAA4C,CAAC;AAiBpD;;;;;;;GAOG;AACH,MAAM,UAAU,kCAAkC,CAChD,QAAgB;IAEhB,MAAM,EAAE,GAA0B;QAChC,KAAK,CAAC,IAAI;YACR,IAAI,CAAC;gBACH,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;gBAC7C,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAK5B,CAAC;gBACF,OAAO;oBACL,IAAI,EAAE,6BAA6B,CAAC,MAAM,CAAC,IAAI,IAAI,MAAM,CAAC,MAAM,CAAC;oBACjE,OAAO,EAAE,mCAAmC,CAAC,MAAM,CAAC,OAAO,CAAC;iBAC7D,CAAC;YACJ,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,IACE,GAAG,YAAY,KAAK;oBACpB,MAAM,IAAI,GAAG;oBACZ,GAA6B,CAAC,IAAI,KAAK,QAAQ,EAChD,CAAC;oBACD,OAAO,IAAI,CAAC;gBACd,CAAC;gBACD,MAAM,GAAG,CAAC;YACZ,CAAC;QACH,CAAC;QACD,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,OAAO;YACvB,MAAM,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YACpD,MAAM,OAAO,GAAG,GAAG,QAAQ,MAAM,CAAC;YAClC,MAAM,KAAK,GAA6B,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;YACtE,MAAM,SAAS,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;YAChE,MAAM,MAAM,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;QAClC,CAAC;KACF,CAAC;IACF,OAAO,8BAA8B,CAAC,EAAE,CAAC,CAAC;AAC5C,CAAC"}
@@ -19,9 +19,10 @@
19
19
  import { Hono } from "hono";
20
20
  import type { Logger } from "pino";
21
21
  import type { DataPortabilityGatewayConfig, GatewayClient } from "@opendatalabs/vana-sdk/node";
22
- import { buildMcpUrl, buildStableMcpUrl, type McpProofReplayStore, type McpConnectionRecord, type McpConnectionStore, type McpSessionStore, type McpOAuthAuthorizationStore, type McpActivityRecorder, type McpScopeRequestApprovalUrlHook } from "@opendatalabs/personal-server-ts-core/mcp";
22
+ import { buildMcpUrl, buildStableMcpUrl, type McpProofReplayStore, type McpConnectionRecord, type McpConnectionStore, type McpSessionStore, type McpOAuthAuthorizationStore, type McpOAuthClientStore, type McpActivityRecorder, type McpScopeRequestApprovalUrlHook } from "@opendatalabs/personal-server-ts-core/mcp";
23
23
  import type { PersonalServerReadFulfillmentReporter } from "@opendatalabs/personal-server-ts-core/api";
24
24
  import type { AccessLogWriter } from "@opendatalabs/personal-server-ts-core/logging/access-log";
25
+ import type { WriteSessionStore } from "@opendatalabs/personal-server-ts-core/write";
25
26
  import type { ServerSigner } from "@opendatalabs/personal-server-ts-core/signing";
26
27
  import type { HierarchyManagerOptions } from "@opendatalabs/personal-server-ts-core/storage/hierarchy";
27
28
  import type { IndexManager } from "@opendatalabs/personal-server-ts-core/storage/index";
@@ -82,6 +83,8 @@ export interface McpRouteDeps {
82
83
  * `/mcp/oauth/token`, and the owner approval endpoint.
83
84
  */
84
85
  oauthAuthorizationStore?: McpOAuthAuthorizationStore;
86
+ /** DCR client metadata, read back at authorize to name the connection. */
87
+ oauthClientStore?: McpOAuthClientStore;
85
88
  /** Activity recorder for live MCP call visibility. */
86
89
  activityRecorder?: McpActivityRecorder;
87
90
  /**
@@ -95,6 +98,19 @@ export interface McpRouteDeps {
95
98
  * user. Absent: the agent is told to wait for approval in Vana.
96
99
  */
97
100
  scopeRequestApprovalUrl?: McpScopeRequestApprovalUrlHook;
101
+ /**
102
+ * MCP writes (`remember`): the store the data route redeems write sessions
103
+ * from, and a dispatcher into the mounted data route. Absent: MCP stays
104
+ * read-only and `remember` answers write_unavailable.
105
+ */
106
+ mcpWrites?: {
107
+ sessionStore: WriteSessionStore;
108
+ dispatch(request: Request): Promise<Response>;
109
+ };
110
+ /** Loopback auth listener port; requests on it are local MCP clients. */
111
+ localApprovalPort?: number;
112
+ /** Main (tunneled) port; a collision with `localApprovalPort` disables local MCP. */
113
+ serverPort?: number;
98
114
  }
99
115
  /** Shared engine for ordinary MCP connections and authenticated TEE dispatch. */
100
116
  export declare function executeMcpConnectionRequest(request: Request, connection: McpConnectionRecord, deps: McpRouteDeps): Promise<Response>;
@@ -109,7 +125,7 @@ export declare function mcpConnectionsRoutes(deps: McpRouteDeps): Hono;
109
125
  * Mount at `/` so the well-known documents are served from the PS origin and
110
126
  * Claude can discover auth for the stable `/mcp` resource.
111
127
  */
112
- export type McpOAuthRouteDeps = Pick<McpRouteDeps, "connectionStore" | "oauthAuthorizationStore" | "oauthApprovalUrl" | "serverOrigin" | "serverOwner" | "gateway" | "devToken" | "accessToken" | "tokenStore" | "gatewayConfig" | "serverSigner" | "mcpGrantTtlSeconds">;
128
+ export type McpOAuthRouteDeps = Pick<McpRouteDeps, "connectionStore" | "oauthAuthorizationStore" | "oauthClientStore" | "oauthApprovalUrl" | "serverOrigin" | "serverOwner" | "gateway" | "devToken" | "accessToken" | "tokenStore" | "gatewayConfig" | "serverSigner" | "mcpGrantTtlSeconds" | "localApprovalPort" | "serverPort">;
113
129
  export declare function mcpOAuthRoutes(deps: McpOAuthRouteDeps): Hono;
114
130
  /**
115
131
  * Hono sub-app for the Claude-facing Streamable HTTP endpoint.
@@ -1 +1 @@
1
- {"version":3,"file":"mcp.d.ts","sourceRoot":"","sources":["../../src/routes/mcp.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"mcp.d.ts","sourceRoot":"","sources":["../../src/routes/mcp.ts"],"names":[],"mappings":"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"}
@@ -18,16 +18,40 @@
18
18
  */
19
19
  import { randomBytes } from "node:crypto";
20
20
  import { Hono } from "hono";
21
- import { approveMcpOAuthAuthorization, approveMcpOAuthAuthorizationWithScopes, approveMcpConnection, approveMcpScopeAccessRequest, buildMcpProtectedResourceMetadataUrl, buildMcpUrl, buildStableMcpUrl, createInMemoryMcpOAuthAuthorizationStore, createMcpConnection, createMcpOAuthAuthorization, createInMemoryMcpConnectionStore, createInMemoryMcpSessionStore, createInMemoryMcpProofReplayStore, createMcpSession, createMcpSessionAuthPort, buildMcpSessionConnection, createMcpDataReadClient, denyMcpScopeAccessRequest, getMcpConnectionView, handleMcpStreamableHttpRequest, hashConnectionToken, listMcpConnectionViews, loadMcpGranteeAccount, MCP_CONNECTION_NOT_FOUND, McpConnectionNotFoundError, McpConnectionStateError, McpOAuthAuthorizationError, isAllowedOAuthRedirectUri, McpScopeRequestError, MCP_SCOPE_REQUEST_NOT_FOUND, MCP_SCOPE_REQUEST_TOKEN_HEADER, readMcpScopeRequestWithToken, redeemMcpOAuthAuthorizationCode, refreshMcpOAuthToken, requestMcpScopeAccess, toMcpOAuthAuthorizationView, revokeMcpConnection, toMcpConnectionView, } from "@opendatalabs/personal-server-ts-core/mcp";
21
+ import { approveMcpOAuthAuthorization, approveMcpOAuthAuthorizationWithScopes, approveMcpConnection, approveMcpScopeAccessRequest, buildMcpProtectedResourceMetadataUrl, buildMcpUrl, buildStableMcpUrl, createInMemoryMcpOAuthAuthorizationStore, createInMemoryMcpOAuthClientStore, createMcpConnection, createMcpOAuthAuthorization, createInMemoryMcpConnectionStore, createInMemoryMcpSessionStore, createInMemoryMcpProofReplayStore, createMcpSession, createMcpSessionAuthPort, buildMcpSessionConnection, createMcpDataReadClient, createMcpDataWriteClient, denyMcpScopeAccessRequest, getMcpConnectionView, handleMcpStreamableHttpRequest, hashConnectionToken, listMcpConnectionViews, loadMcpGranteeAccount, MCP_CONNECTION_NOT_FOUND, McpConnectionNotFoundError, McpConnectionStateError, McpIngress, McpOAuthAuthorizationError, isAllowedOAuthRedirectUri, McpScopeRequestError, MCP_SCOPE_REQUEST_NOT_FOUND, MCP_SCOPE_REQUEST_TOKEN_HEADER, readMcpScopeRequestWithToken, redeemMcpOAuthAuthorizationCode, refreshMcpOAuthToken, requestMcpScopeAccess, toMcpOAuthAuthorizationView, revokeMcpConnection, toMcpConnectionView, } from "@opendatalabs/personal-server-ts-core/mcp";
22
22
  import { ProtocolError } from "@opendatalabs/personal-server-ts-core/errors";
23
23
  import { createServerApiAuth } from "../api-auth.js";
24
24
  import { createBodyLimit, DEFAULT_MAX_SIZE } from "../middleware/body-limit.js";
25
+ import { arrivedOnLoopbackListener } from "../middleware/local-listener.js";
25
26
  import { authenticateRequest, isWeb3SignedLifetimeError, web3SignedProofId, boundedProofExpiry, } from "@opendatalabs/personal-server-ts-core/auth";
27
+ import { verifyDataWritePolicy } from "@opendatalabs/personal-server-ts-core/policy";
26
28
  import { withLegacyProjection } from "@opendatalabs/personal-server-ts-core/storage/legacy-projection";
27
29
  import { createNodeDataStorage } from "../storage/node-data-storage.js";
28
30
  function resolveOrigin(origin) {
29
31
  return typeof origin === "function" ? origin() : origin;
30
32
  }
33
+ /** Which listener accepted the request: the loopback one or the tunneled main one. */
34
+ function requestIngress(c, deps) {
35
+ const listeners = {
36
+ localApprovalPort: deps.localApprovalPort,
37
+ serverPort: deps.serverPort,
38
+ };
39
+ return arrivedOnLoopbackListener(c, listeners)
40
+ ? McpIngress.Loopback
41
+ : McpIngress.Public;
42
+ }
43
+ /**
44
+ * The origin every OAuth/discovery URL in this response must name. A local
45
+ * client on the loopback listener gets the exact origin it called (RFC 9728:
46
+ * the advertised resource must match the URL the client used, so `localhost`
47
+ * and `127.0.0.1` differ); everyone else gets the public origin.
48
+ */
49
+ function requestOrigin(c, deps) {
50
+ if (requestIngress(c, deps) === McpIngress.Public) {
51
+ return resolveOrigin(deps.serverOrigin);
52
+ }
53
+ return new URL(c.req.url).origin;
54
+ }
31
55
  function jsonError(status, errorCode, message) {
32
56
  return {
33
57
  error: { code: status, errorCode, message },
@@ -59,6 +83,33 @@ const SUPPORTED_GRANT_TYPES = [
59
83
  function isSupportedGrantType(value) {
60
84
  return SUPPORTED_GRANT_TYPES.includes(value);
61
85
  }
86
+ /** Longest DCR client_name kept, in code points; it labels the connection. */
87
+ const MAX_CLIENT_NAME_CHARS = 64;
88
+ const MAX_CLIENT_URI_CHARS = 2048;
89
+ /** Control and format chars: NUL, newlines, bidi overrides, zero-width. */
90
+ const INVISIBLE_CHARS = /[\p{Cc}\p{Cf}]/gu;
91
+ const WEB_PROTOCOLS = new Set(["https:", "http:"]);
92
+ /** A printable label from an untrusted string, e.g. " Maple\u202e " → "Maple". */
93
+ function cleanClientName(value) {
94
+ if (typeof value !== "string")
95
+ return undefined;
96
+ const visible = value.replace(INVISIBLE_CHARS, "").trim();
97
+ const capped = Array.from(visible).slice(0, MAX_CLIENT_NAME_CHARS).join("");
98
+ return capped.trim() || undefined;
99
+ }
100
+ /** An http(s) URL or nothing: the owner may be shown it as a link. */
101
+ function cleanClientUri(value) {
102
+ if (typeof value !== "string" || value.length > MAX_CLIENT_URI_CHARS) {
103
+ return undefined;
104
+ }
105
+ try {
106
+ const url = new URL(value.trim());
107
+ return WEB_PROTOCOLS.has(url.protocol) ? url.href : undefined;
108
+ }
109
+ catch {
110
+ return undefined;
111
+ }
112
+ }
62
113
  function authorizationServerMetadata(origin) {
63
114
  return {
64
115
  issuer: origin,
@@ -140,6 +191,31 @@ function buildDataApiDeps(deps) {
140
191
  logger: deps.logger,
141
192
  };
142
193
  }
194
+ /** Writes as the connection grantee, when the host wired MCP writes. */
195
+ function buildWriteClient(deps, granteeAccount, storage) {
196
+ const serverOwner = deps.serverOwner;
197
+ if (!deps.mcpWrites || !serverOwner)
198
+ return undefined;
199
+ return createMcpDataWriteClient({
200
+ serverOrigin: resolveOrigin(deps.serverOrigin),
201
+ granteeAccount,
202
+ storage,
203
+ sessionStore: deps.mcpWrites.sessionStore,
204
+ dispatch: deps.mcpWrites.dispatch,
205
+ authorize: async ({ scope, grantId }) => {
206
+ await verifyDataWritePolicy({
207
+ signer: granteeAccount.address,
208
+ grantId,
209
+ requestedScope: scope,
210
+ serverOwner,
211
+ }, {
212
+ authSessionVerifier: deps.gateway,
213
+ grantVerifier: deps.gateway,
214
+ runtimeAvailability: deps.runtimeAvailability,
215
+ });
216
+ },
217
+ });
218
+ }
143
219
  /** Shared engine for ordinary MCP connections and authenticated TEE dispatch. */
144
220
  export function executeMcpConnectionRequest(request, connection, deps) {
145
221
  const granteeAccount = loadMcpGranteeAccount({
@@ -147,13 +223,15 @@ export function executeMcpConnectionRequest(request, connection, deps) {
147
223
  publicKey: connection.granteePublicKey,
148
224
  encryptedPrivateKey: connection.encryptedGranteePrivateKey,
149
225
  });
226
+ const dataApiDeps = buildDataApiDeps(deps);
150
227
  return handleMcpStreamableHttpRequest(request, {
151
228
  connection,
152
229
  readClient: createMcpDataReadClient({
153
230
  serverOrigin: resolveOrigin(deps.serverOrigin),
154
231
  granteeAccount,
155
- dataApiDeps: buildDataApiDeps(deps),
232
+ dataApiDeps,
156
233
  }),
234
+ writeClient: buildWriteClient(deps, granteeAccount, dataApiDeps.storage),
157
235
  activityRecorder: deps.activityRecorder,
158
236
  // Denied tool calls are recorded by the wrapper, not the read path.
159
237
  reporterDeps: {
@@ -401,6 +479,7 @@ export function mcpOAuthRoutes(deps) {
401
479
  app.use("*", createBodyLimit(DEFAULT_MAX_SIZE));
402
480
  const connectionStore = deps.connectionStore ?? createInMemoryMcpConnectionStore();
403
481
  const authorizationStore = deps.oauthAuthorizationStore ?? createInMemoryMcpOAuthAuthorizationStore();
482
+ const clientStore = deps.oauthClientStore ?? createInMemoryMcpOAuthClientStore();
404
483
  const ownerAuth = createServerApiAuth({
405
484
  serverOrigin: deps.serverOrigin,
406
485
  serverOwner: deps.serverOwner,
@@ -422,10 +501,10 @@ export function mcpOAuthRoutes(deps) {
422
501
  }
423
502
  }
424
503
  app.get("/.well-known/oauth-protected-resource/mcp", (c) => resolveApprovalUrl(deps)
425
- ? c.json(protectedResourceMetadata(resolveOrigin(deps.serverOrigin)))
504
+ ? c.json(protectedResourceMetadata(requestOrigin(c, deps)))
426
505
  : c.json(jsonError(404, "MCP_OAUTH_NOT_CONFIGURED", "MCP OAuth is not configured"), 404));
427
506
  app.get("/.well-known/oauth-authorization-server", (c) => resolveApprovalUrl(deps)
428
- ? c.json(authorizationServerMetadata(resolveOrigin(deps.serverOrigin)))
507
+ ? c.json(authorizationServerMetadata(requestOrigin(c, deps)))
429
508
  : c.json(jsonError(404, "MCP_OAUTH_NOT_CONFIGURED", "MCP OAuth is not configured"), 404));
430
509
  app.post("/mcp/oauth/register", async (c) => {
431
510
  if (!resolveApprovalUrl(deps)) {
@@ -445,9 +524,20 @@ export function mcpOAuthRoutes(deps) {
445
524
  error_description: "One or more redirect_uris are not allowed",
446
525
  }, 400);
447
526
  }
527
+ // Kept so /authorize can name the connection after the client.
528
+ const clientName = cleanClientName(body.client_name);
529
+ const clientUri = cleanClientUri(body.client_uri);
530
+ const client = {
531
+ clientId: `mcp-client-${crypto.randomUUID()}`,
532
+ ...(clientName ? { clientName } : {}),
533
+ ...(clientUri ? { clientUri } : {}),
534
+ createdAt: new Date().toISOString(),
535
+ };
536
+ await clientStore.create(client);
448
537
  return c.json({
449
- client_id: `mcp-client-${crypto.randomUUID()}`,
450
- client_name: body.client_name ?? "Claude",
538
+ client_id: client.clientId,
539
+ ...(clientName ? { client_name: clientName } : {}),
540
+ ...(clientUri ? { client_uri: clientUri } : {}),
451
541
  redirect_uris: Array.isArray(body.redirect_uris)
452
542
  ? body.redirect_uris
453
543
  : [],
@@ -460,7 +550,7 @@ export function mcpOAuthRoutes(deps) {
460
550
  if (!resolveApprovalUrl(deps)) {
461
551
  return c.json(jsonError(404, "MCP_OAUTH_NOT_CONFIGURED", "MCP OAuth is not configured"), 404);
462
552
  }
463
- const origin = resolveOrigin(deps.serverOrigin);
553
+ const origin = requestOrigin(c, deps);
464
554
  const url = new URL(c.req.url);
465
555
  const responseType = url.searchParams.get("response_type");
466
556
  const clientId = url.searchParams.get("client_id") ?? "";
@@ -473,13 +563,18 @@ export function mcpOAuthRoutes(deps) {
473
563
  return redirectWithOAuthError(redirectUri, "unsupported_response_type", "Only response_type=code is supported", state);
474
564
  }
475
565
  try {
566
+ // Unknown ids (registered elsewhere or before) get the default name.
567
+ const client = clientId ? await clientStore.getById(clientId) : null;
476
568
  const created = await createMcpOAuthAuthorization({
477
569
  clientId,
570
+ clientName: client?.clientName,
571
+ clientUri: client?.clientUri,
478
572
  redirectUri,
479
573
  codeChallenge,
480
574
  codeChallengeMethod,
481
575
  scope,
482
576
  ...(state ? { state } : {}),
577
+ ingress: requestIngress(c, deps),
483
578
  }, {
484
579
  connectionStore,
485
580
  authorizationStore,
@@ -491,7 +586,8 @@ export function mcpOAuthRoutes(deps) {
491
586
  }
492
587
  const approve = new URL(approvalUrl);
493
588
  approve.searchParams.set("mcp_authorization", created.authorizationId);
494
- approve.searchParams.set("ps_origin", origin);
589
+ // The approver signs for the registered public origin, never loopback.
590
+ approve.searchParams.set("ps_origin", resolveOrigin(deps.serverOrigin));
495
591
  return c.redirect(approve.toString(), 302);
496
592
  }
497
593
  catch (err) {
@@ -652,6 +748,7 @@ export function mcpStreamableHttpRoutes(deps) {
652
748
  const response = await handleMcpStreamableHttpRequest(c.req.raw, {
653
749
  connection: record,
654
750
  readClient,
751
+ writeClient: buildWriteClient(deps, granteeAccount, dataApiDeps.storage),
655
752
  activityRecorder: deps.activityRecorder,
656
753
  requestScopeAccess: async (input) => {
657
754
  return requestMcpScopeAccess({ connectionId: record.id, ...input }, { store });
@@ -744,19 +841,22 @@ export function mcpStreamableHttpRoutes(deps) {
744
841
  if (!options.oauthChallenge) {
745
842
  return c.json(jsonError(401, "INVALID_TOKEN", "Missing MCP connection token"), 401);
746
843
  }
747
- return mcpUnauthorized(resolveOrigin(deps.serverOrigin));
844
+ return mcpUnauthorized(requestOrigin(c, deps));
748
845
  }
749
846
  const tokenHash = await hashConnectionToken(rawToken);
750
847
  const record = await store.getByTokenHash(tokenHash);
751
- if (record)
848
+ // A token presented on the other listener reads as unknown.
849
+ const ingress = record?.ingress ?? McpIngress.Public;
850
+ if (record && ingress === requestIngress(c, deps)) {
752
851
  return handleConnectionRead(c, record);
852
+ }
753
853
  const session = await sessionStore.getByTokenHash(tokenHash);
754
854
  if (session)
755
855
  return handleSessionRead(c, session);
756
856
  if (!options.oauthChallenge) {
757
857
  return c.json(jsonError(401, "INVALID_TOKEN", "Unknown or revoked MCP connection"), 401);
758
858
  }
759
- return mcpUnauthorized(resolveOrigin(deps.serverOrigin), "Unknown or revoked MCP connection");
859
+ return mcpUnauthorized(requestOrigin(c, deps), "Unknown or revoked MCP connection");
760
860
  }
761
861
  // Self-signing handshake (Option A): the app proves control of its key + DCR
762
862
  // grant with a Web3Signed proof, and gets a short-lived bearer session token.