@opendatalabs/personal-server-ts-server 0.0.1-canary.016adf9 → 0.0.1-canary.052d47e

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 (213) hide show
  1. package/dist/api-auth.d.ts +31 -0
  2. package/dist/api-auth.d.ts.map +1 -0
  3. package/dist/api-auth.js +148 -0
  4. package/dist/api-auth.js.map +1 -0
  5. package/dist/api.d.ts +4 -0
  6. package/dist/api.d.ts.map +1 -1
  7. package/dist/api.js +3 -0
  8. package/dist/api.js.map +1 -1
  9. package/dist/app.d.ts +77 -2
  10. package/dist/app.d.ts.map +1 -1
  11. package/dist/app.js +165 -9
  12. package/dist/app.js.map +1 -1
  13. package/dist/bootstrap.d.ts +36 -3
  14. package/dist/bootstrap.d.ts.map +1 -1
  15. package/dist/bootstrap.js +434 -39
  16. package/dist/bootstrap.js.map +1 -1
  17. package/dist/client.d.ts +17 -0
  18. package/dist/client.d.ts.map +1 -0
  19. package/dist/client.js +369 -0
  20. package/dist/client.js.map +1 -0
  21. package/dist/config/defaults.d.ts +12 -0
  22. package/dist/config/defaults.d.ts.map +1 -0
  23. package/dist/config/defaults.js +14 -0
  24. package/dist/config/defaults.js.map +1 -0
  25. package/dist/config/index.d.ts +4 -0
  26. package/dist/config/index.d.ts.map +1 -0
  27. package/dist/config/index.js +4 -0
  28. package/dist/config/index.js.map +1 -0
  29. package/dist/config/loader.d.ts +8 -0
  30. package/dist/config/loader.d.ts.map +1 -0
  31. package/dist/config/loader.js +78 -0
  32. package/dist/config/loader.js.map +1 -0
  33. package/dist/config/paths.d.ts +9 -0
  34. package/dist/config/paths.d.ts.map +1 -0
  35. package/dist/config/paths.js +22 -0
  36. package/dist/config/paths.js.map +1 -0
  37. package/dist/enclave-main.d.ts +10 -0
  38. package/dist/enclave-main.d.ts.map +1 -0
  39. package/dist/enclave-main.js +115 -0
  40. package/dist/enclave-main.js.map +1 -0
  41. package/dist/hono-context.d.ts +14 -0
  42. package/dist/hono-context.d.ts.map +1 -0
  43. package/dist/hono-context.js +2 -0
  44. package/dist/hono-context.js.map +1 -0
  45. package/dist/index.js +48 -12
  46. package/dist/index.js.map +1 -1
  47. package/dist/jobs/types.d.ts +17 -0
  48. package/dist/jobs/types.d.ts.map +1 -0
  49. package/dist/jobs/types.js +3 -0
  50. package/dist/jobs/types.js.map +1 -0
  51. package/dist/jobs/worker.d.ts +30 -0
  52. package/dist/jobs/worker.d.ts.map +1 -0
  53. package/dist/jobs/worker.js +217 -0
  54. package/dist/jobs/worker.js.map +1 -0
  55. package/dist/keys/server-account.d.ts +28 -0
  56. package/dist/keys/server-account.d.ts.map +1 -0
  57. package/dist/keys/server-account.js +48 -0
  58. package/dist/keys/server-account.js.map +1 -0
  59. package/dist/listen.d.ts +11 -0
  60. package/dist/listen.d.ts.map +1 -0
  61. package/dist/listen.js +23 -0
  62. package/dist/listen.js.map +1 -0
  63. package/dist/logger/index.d.ts +5 -0
  64. package/dist/logger/index.d.ts.map +1 -0
  65. package/dist/logger/index.js +9 -0
  66. package/dist/logger/index.js.map +1 -0
  67. package/dist/logging/access-log.d.ts +4 -0
  68. package/dist/logging/access-log.d.ts.map +1 -0
  69. package/dist/logging/access-log.js +25 -0
  70. package/dist/logging/access-log.js.map +1 -0
  71. package/dist/logging/access-reader.d.ts +19 -0
  72. package/dist/logging/access-reader.d.ts.map +1 -0
  73. package/dist/logging/access-reader.js +50 -0
  74. package/dist/logging/access-reader.js.map +1 -0
  75. package/dist/middleware/access-log.d.ts.map +1 -1
  76. package/dist/middleware/access-log.js +8 -2
  77. package/dist/middleware/access-log.js.map +1 -1
  78. package/dist/middleware/builder-check.d.ts +2 -2
  79. package/dist/middleware/builder-check.d.ts.map +1 -1
  80. package/dist/middleware/builder-check.js +7 -2
  81. package/dist/middleware/builder-check.js.map +1 -1
  82. package/dist/middleware/cors.d.ts +18 -0
  83. package/dist/middleware/cors.d.ts.map +1 -0
  84. package/dist/middleware/cors.js +51 -0
  85. package/dist/middleware/cors.js.map +1 -0
  86. package/dist/middleware/owner-check.js +1 -1
  87. package/dist/middleware/owner-check.js.map +1 -1
  88. package/dist/middleware/web3-auth.d.ts +12 -0
  89. package/dist/middleware/web3-auth.d.ts.map +1 -1
  90. package/dist/middleware/web3-auth.js +27 -31
  91. package/dist/middleware/web3-auth.js.map +1 -1
  92. package/dist/migrations/detect-stuck-version-ledger.d.ts +43 -0
  93. package/dist/migrations/detect-stuck-version-ledger.d.ts.map +1 -0
  94. package/dist/migrations/detect-stuck-version-ledger.js +73 -0
  95. package/dist/migrations/detect-stuck-version-ledger.js.map +1 -0
  96. package/dist/migrations/local-state.d.ts +35 -0
  97. package/dist/migrations/local-state.d.ts.map +1 -0
  98. package/dist/migrations/local-state.js +133 -0
  99. package/dist/migrations/local-state.js.map +1 -0
  100. package/dist/migrations/state-migrations.d.ts +78 -0
  101. package/dist/migrations/state-migrations.d.ts.map +1 -0
  102. package/dist/migrations/state-migrations.js +72 -0
  103. package/dist/migrations/state-migrations.js.map +1 -0
  104. package/dist/pending-blob-deletions.d.ts +10 -0
  105. package/dist/pending-blob-deletions.d.ts.map +1 -0
  106. package/dist/pending-blob-deletions.js +38 -0
  107. package/dist/pending-blob-deletions.js.map +1 -0
  108. package/dist/routes/access-logs.d.ts +7 -0
  109. package/dist/routes/access-logs.d.ts.map +1 -1
  110. package/dist/routes/access-logs.js +11 -17
  111. package/dist/routes/access-logs.js.map +1 -1
  112. package/dist/routes/auth-device.d.ts +49 -0
  113. package/dist/routes/auth-device.d.ts.map +1 -0
  114. package/dist/routes/auth-device.js +581 -0
  115. package/dist/routes/auth-device.js.map +1 -0
  116. package/dist/routes/data.d.ts +67 -2
  117. package/dist/routes/data.d.ts.map +1 -1
  118. package/dist/routes/data.js +41 -201
  119. package/dist/routes/data.js.map +1 -1
  120. package/dist/routes/derivatives.d.ts +33 -0
  121. package/dist/routes/derivatives.d.ts.map +1 -0
  122. package/dist/routes/derivatives.js +33 -0
  123. package/dist/routes/derivatives.js.map +1 -0
  124. package/dist/routes/enclave-jobs.d.ts +9 -0
  125. package/dist/routes/enclave-jobs.d.ts.map +1 -0
  126. package/dist/routes/enclave-jobs.js +104 -0
  127. package/dist/routes/enclave-jobs.js.map +1 -0
  128. package/dist/routes/grants.d.ts +9 -2
  129. package/dist/routes/grants.d.ts.map +1 -1
  130. package/dist/routes/grants.js +18 -196
  131. package/dist/routes/grants.js.map +1 -1
  132. package/dist/routes/health.d.ts +7 -1
  133. package/dist/routes/health.d.ts.map +1 -1
  134. package/dist/routes/health.js +39 -2
  135. package/dist/routes/health.js.map +1 -1
  136. package/dist/routes/mcp.d.ts +115 -0
  137. package/dist/routes/mcp.d.ts.map +1 -0
  138. package/dist/routes/mcp.js +676 -0
  139. package/dist/routes/mcp.js.map +1 -0
  140. package/dist/routes/oauth-token.d.ts +56 -0
  141. package/dist/routes/oauth-token.d.ts.map +1 -0
  142. package/dist/routes/oauth-token.js +49 -0
  143. package/dist/routes/oauth-token.js.map +1 -0
  144. package/dist/routes/sync.d.ts +7 -0
  145. package/dist/routes/sync.d.ts.map +1 -1
  146. package/dist/routes/sync.js +12 -39
  147. package/dist/routes/sync.js.map +1 -1
  148. package/dist/routes/ui-config.d.ts.map +1 -1
  149. package/dist/routes/ui-config.js +17 -82
  150. package/dist/routes/ui-config.js.map +1 -1
  151. package/dist/routes/ui-registration.d.ts +9 -0
  152. package/dist/routes/ui-registration.d.ts.map +1 -0
  153. package/dist/routes/ui-registration.js +217 -0
  154. package/dist/routes/ui-registration.js.map +1 -0
  155. package/dist/routes/ui.d.ts +1 -0
  156. package/dist/routes/ui.d.ts.map +1 -1
  157. package/dist/routes/ui.js +46 -4
  158. package/dist/routes/ui.js.map +1 -1
  159. package/dist/routes/write-session.d.ts +37 -0
  160. package/dist/routes/write-session.d.ts.map +1 -0
  161. package/dist/routes/write-session.js +34 -0
  162. package/dist/routes/write-session.js.map +1 -0
  163. package/dist/storage/hierarchy.d.ts +36 -0
  164. package/dist/storage/hierarchy.d.ts.map +1 -0
  165. package/dist/storage/hierarchy.js +282 -0
  166. package/dist/storage/hierarchy.js.map +1 -0
  167. package/dist/storage/index-manager.d.ts +60 -0
  168. package/dist/storage/index-manager.d.ts.map +1 -0
  169. package/dist/storage/index-manager.js +174 -0
  170. package/dist/storage/index-manager.js.map +1 -0
  171. package/dist/storage/index-schema.d.ts +5 -0
  172. package/dist/storage/index-schema.d.ts.map +1 -0
  173. package/dist/storage/index-schema.js +69 -0
  174. package/dist/storage/index-schema.js.map +1 -0
  175. package/dist/storage/node-data-storage.d.ts +9 -0
  176. package/dist/storage/node-data-storage.d.ts.map +1 -0
  177. package/dist/storage/node-data-storage.js +105 -0
  178. package/dist/storage/node-data-storage.js.map +1 -0
  179. package/dist/storage/question-store.d.ts +9 -0
  180. package/dist/storage/question-store.d.ts.map +1 -0
  181. package/dist/storage/question-store.js +151 -0
  182. package/dist/storage/question-store.js.map +1 -0
  183. package/dist/sync-cursor.d.ts +15 -0
  184. package/dist/sync-cursor.d.ts.map +1 -0
  185. package/dist/sync-cursor.js +110 -0
  186. package/dist/sync-cursor.js.map +1 -0
  187. package/dist/token-store.d.ts +22 -0
  188. package/dist/token-store.d.ts.map +1 -0
  189. package/dist/token-store.js +120 -0
  190. package/dist/token-store.js.map +1 -0
  191. package/dist/tunnel/auth.d.ts +4 -0
  192. package/dist/tunnel/auth.d.ts.map +1 -1
  193. package/dist/tunnel/auth.js +2 -1
  194. package/dist/tunnel/auth.js.map +1 -1
  195. package/dist/tunnel/config.d.ts +1 -0
  196. package/dist/tunnel/config.d.ts.map +1 -1
  197. package/dist/tunnel/config.js +1 -1
  198. package/dist/tunnel/config.js.map +1 -1
  199. package/dist/tunnel/manager.d.ts +21 -1
  200. package/dist/tunnel/manager.d.ts.map +1 -1
  201. package/dist/tunnel/manager.js +78 -23
  202. package/dist/tunnel/manager.js.map +1 -1
  203. package/dist/ui/browser_tls_rustls_bg.wasm +0 -0
  204. package/dist/ui/index.html +2192 -0
  205. package/dist/ui/ps-lite-debug.d.ts +93 -0
  206. package/dist/ui/ps-lite-debug.d.ts.map +1 -0
  207. package/dist/ui/ps-lite-debug.js +135347 -0
  208. package/dist/ui/ps-lite-debug.js.map +1 -0
  209. package/package.json +20 -6
  210. package/dist/middleware/grant-check.d.ts +0 -12
  211. package/dist/middleware/grant-check.d.ts.map +0 -1
  212. package/dist/middleware/grant-check.js +0 -90
  213. package/dist/middleware/grant-check.js.map +0 -1
@@ -0,0 +1,676 @@
1
+ /**
2
+ * MCP routes — both the Claude-facing `/mcp/:token` Streamable HTTP endpoint
3
+ * and the owner-only `/v1/mcp/connections` management surface.
4
+ *
5
+ * §1–§3 of `260604-PLAN-vana-mcp-personal-server.md`.
6
+ *
7
+ * Owner endpoints reuse `createServerApiAuth().authorizeOwner` so the same
8
+ * auth surface that gates `/v1/grants` gates connection management. The
9
+ * Claude-facing endpoint uses the connection token as a capability — there
10
+ * is no Web3Signed payload from Claude; the token resolves to a connection
11
+ * and we then sign reads ourselves with the per-connection grantee.
12
+ *
13
+ * NOTE on Phase-1 storage: this route accepts an `McpConnectionStore` from
14
+ * the caller. The in-memory implementation in `core/src/mcp/store.ts` is
15
+ * fine for tests and the first server-side smoke; for Web PS Lite an
16
+ * IndexedDB-backed adapter is the natural production path (Phase-2 §4
17
+ * "encrypt grantee private keys at rest" applies there).
18
+ */
19
+ import { createHash, randomBytes } from "node:crypto";
20
+ import { Hono } from "hono";
21
+ import { approveMcpOAuthAuthorization, approveMcpOAuthAuthorizationWithScopes, approveMcpConnection, buildMcpProtectedResourceMetadataUrl, buildMcpUrl, buildStableMcpUrl, createInMemoryMcpOAuthAuthorizationStore, createMcpConnection, createMcpOAuthAuthorization, createInMemoryMcpConnectionStore, createInMemoryMcpSessionStore, createInMemoryMcpProofReplayStore, createMcpSession, createMcpSessionAuthPort, buildMcpSessionConnection, createMcpDataReadClient, handleMcpStreamableHttpRequest, hashConnectionToken, listMcpConnectionViews, loadMcpGranteeAccount, McpConnectionNotFoundError, McpConnectionStateError, McpOAuthAuthorizationError, redeemMcpOAuthAuthorizationCode, toMcpOAuthAuthorizationView, revokeMcpConnection, toMcpConnectionView, } from "@opendatalabs/personal-server-ts-core/mcp";
22
+ import { ProtocolError } from "@opendatalabs/personal-server-ts-core/errors";
23
+ import { createServerApiAuth } from "../api-auth.js";
24
+ import { authenticateRequest } from "@opendatalabs/personal-server-ts-core/auth";
25
+ import { createNodeDataStorage } from "../storage/node-data-storage.js";
26
+ function resolveOrigin(origin) {
27
+ return typeof origin === "function" ? origin() : origin;
28
+ }
29
+ function jsonError(status, errorCode, message) {
30
+ return {
31
+ error: { code: status, errorCode, message },
32
+ };
33
+ }
34
+ function bearerToken(request) {
35
+ const authorization = request.headers.get("authorization");
36
+ if (!authorization?.startsWith("Bearer "))
37
+ return null;
38
+ const token = authorization.slice("Bearer ".length).trim();
39
+ return token.length > 0 ? token : null;
40
+ }
41
+ function mcpUnauthorized(origin, message = "MCP authorization required") {
42
+ return new Response(JSON.stringify(jsonError(401, "MCP_AUTH_REQUIRED", message)), {
43
+ status: 401,
44
+ headers: {
45
+ "content-type": "application/json",
46
+ "www-authenticate": `Bearer resource_metadata="${buildMcpProtectedResourceMetadataUrl(origin)}", scope="vana:read"`,
47
+ },
48
+ });
49
+ }
50
+ function authorizationServerMetadata(origin) {
51
+ return {
52
+ issuer: origin,
53
+ authorization_endpoint: `${origin}/mcp/oauth/authorize`,
54
+ token_endpoint: `${origin}/mcp/oauth/token`,
55
+ registration_endpoint: `${origin}/mcp/oauth/register`,
56
+ response_types_supported: ["code"],
57
+ grant_types_supported: ["authorization_code"],
58
+ token_endpoint_auth_methods_supported: ["none"],
59
+ code_challenge_methods_supported: ["S256"],
60
+ scopes_supported: ["vana:read"],
61
+ };
62
+ }
63
+ function protectedResourceMetadata(origin) {
64
+ return {
65
+ resource: buildStableMcpUrl(origin),
66
+ authorization_servers: [origin],
67
+ bearer_methods_supported: ["header"],
68
+ scopes_supported: ["vana:read"],
69
+ resource_name: "Vana Personal Server MCP",
70
+ };
71
+ }
72
+ function resolveApprovalUrl(deps) {
73
+ const value = deps.oauthApprovalUrl;
74
+ if (!value)
75
+ return null;
76
+ return typeof value === "function" ? value() : value;
77
+ }
78
+ function redirectWithOAuthError(redirectUri, error, description, state) {
79
+ try {
80
+ const url = new URL(redirectUri);
81
+ url.searchParams.set("error", error);
82
+ url.searchParams.set("error_description", description);
83
+ if (state)
84
+ url.searchParams.set("state", state);
85
+ return Response.redirect(url.toString(), 302);
86
+ }
87
+ catch {
88
+ return new Response(JSON.stringify({
89
+ error,
90
+ error_description: description,
91
+ ...(state ? { state } : {}),
92
+ }), {
93
+ status: 400,
94
+ headers: { "content-type": "application/json" },
95
+ });
96
+ }
97
+ }
98
+ async function parseFormBody(request) {
99
+ const text = await request.text();
100
+ return new URLSearchParams(text);
101
+ }
102
+ function buildDataApiDeps(deps) {
103
+ const dataStorage = deps.dataStorage ??
104
+ (deps.indexManager && deps.hierarchyOptions
105
+ ? createNodeDataStorage({
106
+ indexManager: deps.indexManager,
107
+ hierarchyOptions: deps.hierarchyOptions,
108
+ })
109
+ : undefined);
110
+ if (!dataStorage) {
111
+ throw new Error("mcpRoutes requires either `dataStorage` or `{ indexManager, hierarchyOptions }` to build the read path");
112
+ }
113
+ const auth = createServerApiAuth({
114
+ serverOrigin: deps.serverOrigin,
115
+ serverOwner: deps.serverOwner,
116
+ gateway: deps.gateway,
117
+ devToken: deps.devToken,
118
+ accessToken: deps.accessToken,
119
+ tokenStore: deps.tokenStore,
120
+ dataStorage,
121
+ runtimeAvailability: deps.runtimeAvailability,
122
+ });
123
+ return {
124
+ storage: dataStorage,
125
+ auth,
126
+ accessLogWriter: deps.accessLogWriter,
127
+ readFulfillmentReporter: deps.readFulfillmentReporter,
128
+ runtimeAvailability: deps.runtimeAvailability,
129
+ logger: deps.logger,
130
+ };
131
+ }
132
+ /**
133
+ * Hono sub-app for the owner-only `/v1/mcp/connections` endpoints.
134
+ * Mount under `/v1/mcp/connections` in `app.ts`.
135
+ */
136
+ export function mcpConnectionsRoutes(deps) {
137
+ const app = new Hono();
138
+ const store = deps.connectionStore ?? createInMemoryMcpConnectionStore();
139
+ const ownerAuth = createServerApiAuth({
140
+ serverOrigin: deps.serverOrigin,
141
+ serverOwner: deps.serverOwner,
142
+ gateway: deps.gateway,
143
+ devToken: deps.devToken,
144
+ accessToken: deps.accessToken,
145
+ tokenStore: deps.tokenStore,
146
+ });
147
+ async function requireOwner(c) {
148
+ try {
149
+ await ownerAuth.authorizeOwner(c.req.raw);
150
+ return null;
151
+ }
152
+ catch (err) {
153
+ if (err instanceof ProtocolError) {
154
+ return c.json(err.toJSON(), err.code);
155
+ }
156
+ throw err;
157
+ }
158
+ }
159
+ app.post("/", async (c) => {
160
+ const err = await requireOwner(c);
161
+ if (err)
162
+ return err;
163
+ let body = {};
164
+ try {
165
+ body = (await c.req.json());
166
+ }
167
+ catch {
168
+ body = {};
169
+ }
170
+ const created = await createMcpConnection({ displayName: body.displayName }, { store, publicOrigin: resolveOrigin(deps.serverOrigin) });
171
+ return c.json(created, 201);
172
+ });
173
+ app.get("/", async (c) => {
174
+ const err = await requireOwner(c);
175
+ if (err)
176
+ return err;
177
+ const records = await listMcpConnectionViews(store);
178
+ return c.json({ connections: records });
179
+ });
180
+ app.post("/:id/approve", async (c) => {
181
+ const err = await requireOwner(c);
182
+ if (err)
183
+ return err;
184
+ const id = c.req.param("id");
185
+ let body = {};
186
+ try {
187
+ body = (await c.req.json());
188
+ }
189
+ catch {
190
+ return c.json(jsonError(400, "INVALID_BODY", "Body must be JSON"), 400);
191
+ }
192
+ if (!Array.isArray(body.grants) || body.grants.length === 0) {
193
+ return c.json(jsonError(400, "GRANTS_REQUIRED", "Approve requires at least one grant — mint grants in the consent flow first"), 400);
194
+ }
195
+ try {
196
+ const updated = await approveMcpConnection({ connectionId: id, grants: body.grants }, { store });
197
+ return c.json(toMcpConnectionView(updated));
198
+ }
199
+ catch (caught) {
200
+ if (caught instanceof McpConnectionNotFoundError) {
201
+ return c.json(jsonError(404, "NOT_FOUND", caught.message), 404);
202
+ }
203
+ if (caught instanceof McpConnectionStateError) {
204
+ return c.json(jsonError(409, "INVALID_STATE", caught.message), 409);
205
+ }
206
+ throw caught;
207
+ }
208
+ });
209
+ app.delete("/:id", async (c) => {
210
+ const err = await requireOwner(c);
211
+ if (err)
212
+ return err;
213
+ const id = c.req.param("id");
214
+ try {
215
+ const updated = await revokeMcpConnection(id, { store });
216
+ return c.json(toMcpConnectionView(updated));
217
+ }
218
+ catch (caught) {
219
+ if (caught instanceof McpConnectionNotFoundError) {
220
+ return c.json(jsonError(404, "NOT_FOUND", caught.message), 404);
221
+ }
222
+ throw caught;
223
+ }
224
+ });
225
+ return app;
226
+ }
227
+ /**
228
+ * Top-level MCP OAuth/discovery routes.
229
+ *
230
+ * Mount at `/` so the well-known documents are served from the PS origin and
231
+ * Claude can discover auth for the stable `/mcp` resource.
232
+ */
233
+ export function mcpOAuthRoutes(deps) {
234
+ const app = new Hono();
235
+ const connectionStore = deps.connectionStore ?? createInMemoryMcpConnectionStore();
236
+ const authorizationStore = deps.oauthAuthorizationStore ?? createInMemoryMcpOAuthAuthorizationStore();
237
+ const ownerAuth = createServerApiAuth({
238
+ serverOrigin: deps.serverOrigin,
239
+ serverOwner: deps.serverOwner,
240
+ gateway: deps.gateway,
241
+ devToken: deps.devToken,
242
+ accessToken: deps.accessToken,
243
+ tokenStore: deps.tokenStore,
244
+ });
245
+ async function requireOwner(c) {
246
+ try {
247
+ await ownerAuth.authorizeOwner(c.req.raw);
248
+ return null;
249
+ }
250
+ catch (err) {
251
+ if (err instanceof ProtocolError) {
252
+ return c.json(err.toJSON(), err.code);
253
+ }
254
+ throw err;
255
+ }
256
+ }
257
+ app.get("/.well-known/oauth-protected-resource/mcp", (c) => resolveApprovalUrl(deps)
258
+ ? c.json(protectedResourceMetadata(resolveOrigin(deps.serverOrigin)))
259
+ : c.json(jsonError(404, "MCP_OAUTH_NOT_CONFIGURED", "MCP OAuth is not configured"), 404));
260
+ app.get("/.well-known/oauth-authorization-server", (c) => resolveApprovalUrl(deps)
261
+ ? c.json(authorizationServerMetadata(resolveOrigin(deps.serverOrigin)))
262
+ : c.json(jsonError(404, "MCP_OAUTH_NOT_CONFIGURED", "MCP OAuth is not configured"), 404));
263
+ app.post("/mcp/oauth/register", async (c) => {
264
+ if (!resolveApprovalUrl(deps)) {
265
+ return c.json(jsonError(404, "MCP_OAUTH_NOT_CONFIGURED", "MCP OAuth is not configured"), 404);
266
+ }
267
+ let body = {};
268
+ try {
269
+ body = (await c.req.json());
270
+ }
271
+ catch {
272
+ body = {};
273
+ }
274
+ return c.json({
275
+ client_id: `mcp-client-${crypto.randomUUID()}`,
276
+ client_name: body.client_name ?? "Claude",
277
+ redirect_uris: Array.isArray(body.redirect_uris)
278
+ ? body.redirect_uris
279
+ : [],
280
+ token_endpoint_auth_method: "none",
281
+ grant_types: ["authorization_code"],
282
+ response_types: ["code"],
283
+ }, 201);
284
+ });
285
+ app.get("/mcp/oauth/authorize", async (c) => {
286
+ if (!resolveApprovalUrl(deps)) {
287
+ return c.json(jsonError(404, "MCP_OAUTH_NOT_CONFIGURED", "MCP OAuth is not configured"), 404);
288
+ }
289
+ const origin = resolveOrigin(deps.serverOrigin);
290
+ const url = new URL(c.req.url);
291
+ const responseType = url.searchParams.get("response_type");
292
+ const clientId = url.searchParams.get("client_id") ?? "";
293
+ const redirectUri = url.searchParams.get("redirect_uri") ?? "";
294
+ const codeChallenge = url.searchParams.get("code_challenge") ?? "";
295
+ const codeChallengeMethod = url.searchParams.get("code_challenge_method") ?? "";
296
+ const state = url.searchParams.get("state");
297
+ const scope = url.searchParams.get("scope") ?? "vana:read";
298
+ if (responseType !== "code") {
299
+ return redirectWithOAuthError(redirectUri, "unsupported_response_type", "Only response_type=code is supported", state);
300
+ }
301
+ try {
302
+ const created = await createMcpOAuthAuthorization({
303
+ clientId,
304
+ redirectUri,
305
+ codeChallenge,
306
+ codeChallengeMethod,
307
+ scope,
308
+ ...(state ? { state } : {}),
309
+ }, {
310
+ connectionStore,
311
+ authorizationStore,
312
+ publicOrigin: origin,
313
+ });
314
+ const approvalUrl = resolveApprovalUrl(deps);
315
+ if (!approvalUrl) {
316
+ return c.json(jsonError(500, "MCP_APPROVAL_URL_MISSING", "MCP OAuth approval URL is not configured"), 500);
317
+ }
318
+ const approve = new URL(approvalUrl);
319
+ approve.searchParams.set("mcp_authorization", created.authorizationId);
320
+ approve.searchParams.set("ps_origin", origin);
321
+ return c.redirect(approve.toString(), 302);
322
+ }
323
+ catch (err) {
324
+ const message = err instanceof Error ? err.message : String(err);
325
+ return redirectWithOAuthError(redirectUri, err instanceof McpOAuthAuthorizationError
326
+ ? err.code
327
+ : "invalid_request", message, state);
328
+ }
329
+ });
330
+ app.post("/mcp/oauth/token", async (c) => {
331
+ if (!resolveApprovalUrl(deps)) {
332
+ return c.json(jsonError(404, "MCP_OAUTH_NOT_CONFIGURED", "MCP OAuth is not configured"), 404);
333
+ }
334
+ const body = await parseFormBody(c.req.raw);
335
+ if (body.get("grant_type") !== "authorization_code") {
336
+ return c.json({
337
+ error: "unsupported_grant_type",
338
+ error_description: "Only authorization_code is supported",
339
+ }, 400);
340
+ }
341
+ try {
342
+ const token = await redeemMcpOAuthAuthorizationCode({
343
+ authorizationCode: body.get("code") ?? "",
344
+ codeVerifier: body.get("code_verifier") ?? "",
345
+ clientId: body.get("client_id") ?? "",
346
+ redirectUri: body.get("redirect_uri") ?? "",
347
+ }, { authorizationStore, connectionStore });
348
+ return c.json({
349
+ access_token: token.accessToken,
350
+ token_type: "Bearer",
351
+ ...(token.scope ? { scope: token.scope } : {}),
352
+ });
353
+ }
354
+ catch (err) {
355
+ return c.json({
356
+ error: err instanceof McpOAuthAuthorizationError
357
+ ? err.code
358
+ : "invalid_grant",
359
+ error_description: err instanceof Error ? err.message : String(err),
360
+ }, 400);
361
+ }
362
+ });
363
+ app.get("/v1/mcp/oauth/authorizations/:id", async (c) => {
364
+ const err = await requireOwner(c);
365
+ if (err)
366
+ return err;
367
+ const record = await authorizationStore.getById(c.req.param("id"));
368
+ if (!record) {
369
+ return c.json(jsonError(404, "NOT_FOUND", "Authorization not found"), 404);
370
+ }
371
+ return c.json(toMcpOAuthAuthorizationView(record));
372
+ });
373
+ app.post("/v1/mcp/oauth/authorizations/:id/approve", async (c) => {
374
+ const err = await requireOwner(c);
375
+ if (err)
376
+ return err;
377
+ let body = {};
378
+ try {
379
+ body = (await c.req.json());
380
+ }
381
+ catch {
382
+ return c.json(jsonError(400, "INVALID_BODY", "Body must be JSON"), 400);
383
+ }
384
+ if (Array.isArray(body.scopes) && body.scopes.length > 0) {
385
+ if (!deps.gatewayConfig?.url) {
386
+ return c.json(jsonError(500, "SERVER_NOT_CONFIGURED", "Gateway config is not configured"), 500);
387
+ }
388
+ try {
389
+ const approved = await approveMcpOAuthAuthorizationWithScopes({
390
+ authorizationId: c.req.param("id"),
391
+ scopes: body.scopes,
392
+ ...(body.expiresAt !== undefined
393
+ ? { expiresAt: body.expiresAt }
394
+ : {}),
395
+ ...(body.nonce !== undefined ? { nonce: body.nonce } : {}),
396
+ }, {
397
+ connectionStore,
398
+ authorizationStore,
399
+ gateway: deps.gateway,
400
+ gatewayConfig: deps.gatewayConfig,
401
+ gatewayUrl: deps.gatewayConfig.url,
402
+ serverOwner: deps.serverOwner,
403
+ serverSigner: deps.serverSigner,
404
+ });
405
+ return c.json({ redirectTo: approved.redirectTo });
406
+ }
407
+ catch (err) {
408
+ if (err instanceof McpOAuthAuthorizationError) {
409
+ return c.json(jsonError(err.status, err.code, err.message), err.status);
410
+ }
411
+ throw err;
412
+ }
413
+ }
414
+ if (!Array.isArray(body.grants) || body.grants.length === 0) {
415
+ return c.json(jsonError(400, "GRANTS_REQUIRED", "Approve requires grants or scopes"), 400);
416
+ }
417
+ try {
418
+ const approved = await approveMcpOAuthAuthorization({ authorizationId: c.req.param("id"), grants: body.grants }, { connectionStore, authorizationStore });
419
+ return c.json({ redirectTo: approved.redirectTo });
420
+ }
421
+ catch (err) {
422
+ if (err instanceof McpOAuthAuthorizationError) {
423
+ return c.json(jsonError(err.status, err.code, err.message), err.status);
424
+ }
425
+ throw err;
426
+ }
427
+ });
428
+ return app;
429
+ }
430
+ /**
431
+ * Hono sub-app for the Claude-facing Streamable HTTP endpoint.
432
+ * Mount under `/mcp` in `app.ts`; stable `/mcp` accepts OAuth bearer tokens
433
+ * and legacy `/mcp/:connectionToken` still accepts token-in-path calls.
434
+ *
435
+ * NOTE: this MUST receive the SAME `connectionStore` instance you pass to
436
+ * `mcpConnectionsRoutes` — otherwise the management API creates connections
437
+ * that the Claude endpoint can never see.
438
+ */
439
+ export function mcpStreamableHttpRoutes(deps) {
440
+ const app = new Hono();
441
+ const store = deps.connectionStore ?? createInMemoryMcpConnectionStore();
442
+ const sessionStore = deps.sessionStore ?? createInMemoryMcpSessionStore();
443
+ const proofReplayStore = deps.proofReplayStore ?? createInMemoryMcpProofReplayStore();
444
+ if (!deps.connectionStore) {
445
+ deps.logger.warn("mcpStreamableHttpRoutes: no connectionStore passed; using a fresh in-memory store. The management routes will not see these connections unless they share the same store.");
446
+ }
447
+ function dataApiDepsOrError(c) {
448
+ try {
449
+ return buildDataApiDeps(deps);
450
+ }
451
+ catch (caught) {
452
+ deps.logger.error({ err: caught }, "mcp route data deps failed");
453
+ return c.json(jsonError(500, "SERVER_NOT_CONFIGURED", String(caught)), 500);
454
+ }
455
+ }
456
+ // Owner's-Claude path: PS holds the per-connection grantee key and signs.
457
+ async function handleConnectionRead(c, record) {
458
+ const dataApiDeps = dataApiDepsOrError(c);
459
+ if (dataApiDeps instanceof Response)
460
+ return dataApiDeps;
461
+ const granteeAccount = loadMcpGranteeAccount({
462
+ address: record.granteeAddress,
463
+ publicKey: record.granteePublicKey,
464
+ encryptedPrivateKey: record.encryptedGranteePrivateKey,
465
+ });
466
+ const readClient = createMcpDataReadClient({
467
+ serverOrigin: resolveOrigin(deps.serverOrigin),
468
+ granteeAccount,
469
+ dataApiDeps,
470
+ });
471
+ const response = await handleMcpStreamableHttpRequest(c.req.raw, {
472
+ connection: record,
473
+ readClient,
474
+ activityRecorder: deps.activityRecorder,
475
+ });
476
+ void store
477
+ .update(record.id, { lastUsedAt: new Date().toISOString() })
478
+ .catch((err) => {
479
+ console.warn("[mcp] failed to update lastUsedAt", err);
480
+ });
481
+ return response;
482
+ }
483
+ // Chatbot-first path (Option A): the app self-signed the handshake, so the PS
484
+ // holds NO key. We authorize reads as the app via a session auth port; the
485
+ // read client's own signature (with a throwaway key) is ignored.
486
+ async function handleSessionRead(c, session) {
487
+ // Fail closed: session reads can't be authorized without this server's
488
+ // owner to bind the grant to (verifyDataReadPolicy requires it).
489
+ if (!deps.serverOwner) {
490
+ return c.json(jsonError(500, "SERVER_NOT_CONFIGURED", "Server owner is not configured"), 500);
491
+ }
492
+ // Fail closed: if x402 is enabled but the gateway isn't fully configured,
493
+ // refuse the read rather than silently serving it for free.
494
+ if (deps.paymentEnabled && !deps.gatewayConfig?.url) {
495
+ return c.json(jsonError(500, "SERVER_NOT_CONFIGURED", "x402 payment is enabled but the gateway URL is not configured"), 500);
496
+ }
497
+ const baseDeps = dataApiDepsOrError(c);
498
+ if (baseDeps instanceof Response)
499
+ return baseDeps;
500
+ // Paid session: wire x402 for this self-signing session only. Owner/OAuth
501
+ // reads never build this, so they stay free.
502
+ let payment;
503
+ const gatewayUrl = deps.gatewayConfig?.url;
504
+ if (deps.paymentEnabled && deps.gatewayConfig && gatewayUrl) {
505
+ payment = {
506
+ dataApiDeps: {
507
+ ...baseDeps,
508
+ gateway: deps.gateway,
509
+ gatewayConfig: deps.gatewayConfig,
510
+ gatewayUrl,
511
+ serverOwner: deps.serverOwner,
512
+ serverSigner: deps.serverSigner,
513
+ serverAddress: deps.serverAddress,
514
+ paymentEnabled: true,
515
+ },
516
+ gateway: deps.gateway,
517
+ gatewayConfig: deps.gatewayConfig,
518
+ gatewayUrl,
519
+ };
520
+ }
521
+ const auth = createMcpSessionAuthPort({
522
+ builderAddress: session.builderAddress,
523
+ grantId: session.grantId,
524
+ authSessionVerifier: deps.gateway,
525
+ grantVerifier: deps.gateway,
526
+ serverOwner: deps.serverOwner,
527
+ runtimeAvailability: deps.runtimeAvailability,
528
+ payment,
529
+ });
530
+ const { connection, account } = buildMcpSessionConnection(session);
531
+ const readClient = createMcpDataReadClient({
532
+ serverOrigin: resolveOrigin(deps.serverOrigin),
533
+ granteeAccount: account,
534
+ dataApiDeps: { ...baseDeps, auth },
535
+ // Paid session: mark reads as x402-enforced so tools skip any
536
+ // payment-bypassing shortcut (e.g. the searchScopeIndex preview path).
537
+ enforcesPayment: Boolean(payment),
538
+ });
539
+ return handleMcpStreamableHttpRequest(c.req.raw, {
540
+ connection,
541
+ readClient,
542
+ activityRecorder: deps.activityRecorder,
543
+ });
544
+ }
545
+ async function handleToken(c, rawToken, options) {
546
+ if (!rawToken) {
547
+ if (!options.oauthChallenge) {
548
+ return c.json(jsonError(401, "INVALID_TOKEN", "Missing MCP connection token"), 401);
549
+ }
550
+ return mcpUnauthorized(resolveOrigin(deps.serverOrigin));
551
+ }
552
+ const tokenHash = await hashConnectionToken(rawToken);
553
+ const record = await store.getByTokenHash(tokenHash);
554
+ if (record)
555
+ return handleConnectionRead(c, record);
556
+ const session = await sessionStore.getByTokenHash(tokenHash);
557
+ if (session)
558
+ return handleSessionRead(c, session);
559
+ if (!options.oauthChallenge) {
560
+ return c.json(jsonError(401, "INVALID_TOKEN", "Unknown or revoked MCP connection"), 401);
561
+ }
562
+ return mcpUnauthorized(resolveOrigin(deps.serverOrigin), "Unknown or revoked MCP connection");
563
+ }
564
+ // Self-signing handshake (Option A): the app proves control of its key + DCR
565
+ // grant with a Web3Signed proof, and gets a short-lived bearer session token.
566
+ // Registered BEFORE `/:connectionToken` so it isn't captured as a token.
567
+ app.post("/session", async (c) => {
568
+ // Fail closed: minting a session requires this server's owner to bind the
569
+ // grant to (createMcpSession rejects grants issued by a different owner).
570
+ if (!deps.serverOwner) {
571
+ return c.json(jsonError(500, "SERVER_NOT_CONFIGURED", "Server owner is not configured"), 500);
572
+ }
573
+ let authResult;
574
+ try {
575
+ authResult = await authenticateRequest({
576
+ request: c.req.raw,
577
+ serverOrigin: deps.serverOrigin,
578
+ devToken: deps.devToken,
579
+ accessToken: deps.accessToken,
580
+ sessionTokenVerifier: deps.tokenStore,
581
+ serverOwner: deps.serverOwner,
582
+ });
583
+ }
584
+ catch (err) {
585
+ return c.json(jsonError(401, "MCP_SESSION_AUTH_FAILED", err instanceof Error ? err.message : String(err)), 401);
586
+ }
587
+ if (authResult.mechanism !== "web3-signed") {
588
+ return c.json(jsonError(401, "MCP_SESSION_PROOF_REQUIRED", "POST /mcp/session requires a Web3Signed proof signed by the app key"), 401);
589
+ }
590
+ const grantId = authResult.auth.payload.grantId;
591
+ if (!grantId) {
592
+ return c.json(jsonError(400, "GRANT_ID_REQUIRED", "The Web3Signed proof must carry a grantId (the DCR grant issued to the app)"), 400);
593
+ }
594
+ // Replay guard: bind to a digest of the exact proof header, remembered
595
+ // until the proof's own expiry (a replay only matters while still valid).
596
+ const proofHeader = c.req.raw.headers.get("authorization") ?? "";
597
+ const proofId = createHash("sha256").update(proofHeader).digest("hex");
598
+ const expSec = authResult.auth.payload.exp;
599
+ const expiresAtMs = (typeof expSec === "number"
600
+ ? expSec
601
+ : Math.floor(Date.now() / 1000) + 300) * 1000;
602
+ try {
603
+ const session = await createMcpSession({
604
+ builderAddress: authResult.auth.signer,
605
+ grantId,
606
+ proof: { id: proofId, expiresAtMs },
607
+ }, {
608
+ store: sessionStore,
609
+ authSessionVerifier: deps.gateway,
610
+ grantVerifier: deps.gateway,
611
+ serverOwner: deps.serverOwner,
612
+ randomToken: () => `vana_mcp_${randomBytes(32).toString("hex")}`,
613
+ replayStore: proofReplayStore,
614
+ });
615
+ return c.json({
616
+ access_token: session.accessToken,
617
+ token_type: "Bearer",
618
+ expires_in: session.expiresInSeconds,
619
+ scope: session.scopes.join(" "),
620
+ });
621
+ }
622
+ catch (err) {
623
+ if (err instanceof ProtocolError) {
624
+ return c.json(err.toJSON(), err.code);
625
+ }
626
+ return c.json(jsonError(400, "MCP_SESSION_FAILED", err instanceof Error ? err.message : String(err)), 400);
627
+ }
628
+ });
629
+ app.all("/", async (c) => handleToken(c, bearerToken(c.req.raw), {
630
+ oauthChallenge: Boolean(resolveApprovalUrl(deps)),
631
+ }));
632
+ app.all("/:connectionToken", async (c) => {
633
+ const rawToken = c.req.param("connectionToken");
634
+ return handleToken(c, rawToken, { oauthChallenge: false });
635
+ });
636
+ return app;
637
+ }
638
+ /**
639
+ * Owner-only activity feed route. Mount at `/v1/mcp/activity` in `app.ts`.
640
+ * Returns the current snapshot from the shared `McpActivityRecorder`.
641
+ */
642
+ export function mcpActivityRoutes(deps) {
643
+ const app = new Hono();
644
+ const ownerAuth = createServerApiAuth({
645
+ serverOrigin: deps.serverOrigin,
646
+ serverOwner: deps.serverOwner,
647
+ gateway: deps.gateway,
648
+ devToken: deps.devToken,
649
+ accessToken: deps.accessToken,
650
+ tokenStore: deps.tokenStore,
651
+ });
652
+ async function requireOwner(c) {
653
+ try {
654
+ await ownerAuth.authorizeOwner(c.req.raw);
655
+ return null;
656
+ }
657
+ catch (err) {
658
+ if (err instanceof ProtocolError) {
659
+ return c.json(err.toJSON(), err.code);
660
+ }
661
+ throw err;
662
+ }
663
+ }
664
+ app.get("/", async (c) => {
665
+ const err = await requireOwner(c);
666
+ if (err)
667
+ return err;
668
+ const snapshot = deps.activityRecorder
669
+ ? deps.activityRecorder.snapshot()
670
+ : { events: [], running: 0, total: 0 };
671
+ return c.json(snapshot);
672
+ });
673
+ return app;
674
+ }
675
+ export { buildMcpUrl, buildStableMcpUrl };
676
+ //# sourceMappingURL=mcp.js.map