@fleetless/contracts 1.0.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 (287) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/LICENSE +202 -0
  3. package/NOTICE +17 -0
  4. package/README.md +88 -0
  5. package/artifacts/constants.json +24 -0
  6. package/artifacts/openapi.json +17219 -0
  7. package/artifacts/routes.json +4605 -0
  8. package/artifacts/schema/accept-team-invite-request.schema.json +22 -0
  9. package/artifacts/schema/action-config.schema.json +198 -0
  10. package/artifacts/schema/alert-list-response.schema.json +172 -0
  11. package/artifacts/schema/api-error.schema.json +20 -0
  12. package/artifacts/schema/app-auth-config.schema.json +106 -0
  13. package/artifacts/schema/app-invitation-list-response.schema.json +57 -0
  14. package/artifacts/schema/app-invitation.schema.json +69 -0
  15. package/artifacts/schema/app-list-response.schema.json +82 -0
  16. package/artifacts/schema/app-mail-template-list-response.schema.json +68 -0
  17. package/artifacts/schema/app-mail-template.schema.json +54 -0
  18. package/artifacts/schema/app-oidc-provider-list-response.schema.json +93 -0
  19. package/artifacts/schema/app-oidc-provider.schema.json +80 -0
  20. package/artifacts/schema/app-user-list-response.schema.json +111 -0
  21. package/artifacts/schema/app-user.schema.json +98 -0
  22. package/artifacts/schema/app.schema.json +69 -0
  23. package/artifacts/schema/apply-error.schema.json +41 -0
  24. package/artifacts/schema/asset-list-response.schema.json +288 -0
  25. package/artifacts/schema/asset-sync-request.schema.json +17 -0
  26. package/artifacts/schema/asset-sync-response.schema.json +16 -0
  27. package/artifacts/schema/asset-sync-status.schema.json +136 -0
  28. package/artifacts/schema/asset.schema.json +68 -0
  29. package/artifacts/schema/audit-actor.schema.json +32 -0
  30. package/artifacts/schema/audit-event.schema.json +119 -0
  31. package/artifacts/schema/audit-list-response.schema.json +144 -0
  32. package/artifacts/schema/audit-query.schema.json +79 -0
  33. package/artifacts/schema/auth-error.schema.json +23 -0
  34. package/artifacts/schema/auth-me-response.schema.json +99 -0
  35. package/artifacts/schema/auth-ok.schema.json +115 -0
  36. package/artifacts/schema/authorization-server-metadata.schema.json +80 -0
  37. package/artifacts/schema/bridge-asset-progress.schema.json +99 -0
  38. package/artifacts/schema/bridge-assets-available.schema.json +25 -0
  39. package/artifacts/schema/bridge-camera-state.schema.json +78 -0
  40. package/artifacts/schema/bridge-config-applied.schema.json +67 -0
  41. package/artifacts/schema/bridge-hello.schema.json +65 -0
  42. package/artifacts/schema/bridge-introspect.schema.json +114 -0
  43. package/artifacts/schema/bridge-job-lost.schema.json +22 -0
  44. package/artifacts/schema/bridge-job-update.schema.json +100 -0
  45. package/artifacts/schema/bridge-pong.schema.json +19 -0
  46. package/artifacts/schema/bridge-pressure.schema.json +292 -0
  47. package/artifacts/schema/bridge-state.schema.json +24 -0
  48. package/artifacts/schema/bridge-type-definitions.schema.json +169 -0
  49. package/artifacts/schema/busy-details.schema.json +115 -0
  50. package/artifacts/schema/camera-descriptor.schema.json +45 -0
  51. package/artifacts/schema/camera-list-response.schema.json +58 -0
  52. package/artifacts/schema/camera-source.schema.json +240 -0
  53. package/artifacts/schema/cancel-request.schema.json +20 -0
  54. package/artifacts/schema/client-accept-invitation-request.schema.json +35 -0
  55. package/artifacts/schema/client-auth.schema.json +18 -0
  56. package/artifacts/schema/client-cancel.schema.json +45 -0
  57. package/artifacts/schema/client-identity.schema.json +103 -0
  58. package/artifacts/schema/client-invoke.schema.json +45 -0
  59. package/artifacts/schema/client-login-request.schema.json +29 -0
  60. package/artifacts/schema/client-logout-request.schema.json +14 -0
  61. package/artifacts/schema/client-mcp-interaction-decision-response.schema.json +15 -0
  62. package/artifacts/schema/client-mcp-interaction.schema.json +59 -0
  63. package/artifacts/schema/client-oidc-callback-query.schema.json +28 -0
  64. package/artifacts/schema/client-oidc-exchange-request.schema.json +21 -0
  65. package/artifacts/schema/client-oidc-start-query.schema.json +36 -0
  66. package/artifacts/schema/client-password-reset-confirm-request.schema.json +22 -0
  67. package/artifacts/schema/client-password-reset-request.schema.json +24 -0
  68. package/artifacts/schema/client-provider-list-query.schema.json +17 -0
  69. package/artifacts/schema/client-provider-list-response.schema.json +34 -0
  70. package/artifacts/schema/client-publish.schema.json +40 -0
  71. package/artifacts/schema/client-refresh-request.schema.json +14 -0
  72. package/artifacts/schema/client-register-request.schema.json +44 -0
  73. package/artifacts/schema/client-resend-verification-request.schema.json +24 -0
  74. package/artifacts/schema/client-subscribe.schema.json +43 -0
  75. package/artifacts/schema/client-unsubscribe.schema.json +26 -0
  76. package/artifacts/schema/client-verify-email-request.schema.json +15 -0
  77. package/artifacts/schema/cloud-asset-request.schema.json +37 -0
  78. package/artifacts/schema/cloud-camera-start.schema.json +41 -0
  79. package/artifacts/schema/cloud-camera-stop.schema.json +26 -0
  80. package/artifacts/schema/cloud-cancel.schema.json +33 -0
  81. package/artifacts/schema/cloud-config.schema.json +1635 -0
  82. package/artifacts/schema/cloud-hello-error.schema.json +23 -0
  83. package/artifacts/schema/cloud-hello-ok.schema.json +19 -0
  84. package/artifacts/schema/cloud-introspect-request.schema.json +19 -0
  85. package/artifacts/schema/cloud-invoke.schema.json +40 -0
  86. package/artifacts/schema/cloud-ping.schema.json +19 -0
  87. package/artifacts/schema/cloud-publish.schema.json +28 -0
  88. package/artifacts/schema/cloud-type-request.schema.json +30 -0
  89. package/artifacts/schema/command-result.schema.json +175 -0
  90. package/artifacts/schema/config-draft-response.schema.json +1695 -0
  91. package/artifacts/schema/config-state.schema.json +124 -0
  92. package/artifacts/schema/config-version-response.schema.json +1641 -0
  93. package/artifacts/schema/config-versions-response.schema.json +33 -0
  94. package/artifacts/schema/create-app-invitation-request.schema.json +40 -0
  95. package/artifacts/schema/create-app-oidc-provider-request.schema.json +70 -0
  96. package/artifacts/schema/create-app-request.schema.json +30 -0
  97. package/artifacts/schema/create-app-user-request.schema.json +42 -0
  98. package/artifacts/schema/create-robot-request.schema.json +14 -0
  99. package/artifacts/schema/create-robot-response.schema.json +44 -0
  100. package/artifacts/schema/create-server-key-response.schema.json +65 -0
  101. package/artifacts/schema/create-team-invite-request.schema.json +43 -0
  102. package/artifacts/schema/datapoint-alert-row.schema.json +160 -0
  103. package/artifacts/schema/datapoint-config.schema.json +366 -0
  104. package/artifacts/schema/datapoint-display.schema.json +31 -0
  105. package/artifacts/schema/datapoint-event.schema.json +34 -0
  106. package/artifacts/schema/datapoint-frame.schema.json +28 -0
  107. package/artifacts/schema/datapoint-list-response.schema.json +61 -0
  108. package/artifacts/schema/datapoint-value.schema.json +28 -0
  109. package/artifacts/schema/developer-login-request.schema.json +19 -0
  110. package/artifacts/schema/dynamic-client-registration-request.schema.json +60 -0
  111. package/artifacts/schema/dynamic-client-registration-response.schema.json +68 -0
  112. package/artifacts/schema/error-frame.schema.json +23 -0
  113. package/artifacts/schema/exposure-counts.schema.json +39 -0
  114. package/artifacts/schema/exposure-list-response.schema.json +43 -0
  115. package/artifacts/schema/fetch-types-request.schema.json +19 -0
  116. package/artifacts/schema/fetch-types-response.schema.json +163 -0
  117. package/artifacts/schema/fleetless-user-list-response.schema.json +73 -0
  118. package/artifacts/schema/fleetless-user.schema.json +60 -0
  119. package/artifacts/schema/history-buckets-response.schema.json +79 -0
  120. package/artifacts/schema/history-query.schema.json +58 -0
  121. package/artifacts/schema/history-response.schema.json +150 -0
  122. package/artifacts/schema/history-samples-response.schema.json +68 -0
  123. package/artifacts/schema/introspection-response.schema.json +118 -0
  124. package/artifacts/schema/invoke-or-service-response.schema.json +141 -0
  125. package/artifacts/schema/invoke-request.schema.json +23 -0
  126. package/artifacts/schema/invoke-response.schema.json +125 -0
  127. package/artifacts/schema/job-actor.schema.json +34 -0
  128. package/artifacts/schema/job-event.schema.json +158 -0
  129. package/artifacts/schema/job-response.schema.json +123 -0
  130. package/artifacts/schema/job-run-list-response.schema.json +222 -0
  131. package/artifacts/schema/job-run-query.schema.json +95 -0
  132. package/artifacts/schema/job-run-summary-query.schema.json +23 -0
  133. package/artifacts/schema/job-run-summary.schema.json +33 -0
  134. package/artifacts/schema/job-run.schema.json +195 -0
  135. package/artifacts/schema/job-state.schema.json +11 -0
  136. package/artifacts/schema/job.schema.json +106 -0
  137. package/artifacts/schema/latency-bucket.schema.json +63 -0
  138. package/artifacts/schema/live-session-response.schema.json +41 -0
  139. package/artifacts/schema/mail-outcome.schema.json +20 -0
  140. package/artifacts/schema/mail-template-preview-request.schema.json +35 -0
  141. package/artifacts/schema/mail-template-preview-response.schema.json +31 -0
  142. package/artifacts/schema/mail-template-problem-details.schema.json +24 -0
  143. package/artifacts/schema/mcp-consent-grant-list-response.schema.json +52 -0
  144. package/artifacts/schema/mcp-consent-grant.schema.json +39 -0
  145. package/artifacts/schema/mcp-robot-datasheet.schema.json +115 -0
  146. package/artifacts/schema/mcp-role-preview-response.schema.json +134 -0
  147. package/artifacts/schema/missing-asset-query.schema.json +11 -0
  148. package/artifacts/schema/oauth-authorize-query.schema.json +47 -0
  149. package/artifacts/schema/oauth-redirect-response.schema.json +15 -0
  150. package/artifacts/schema/oauth-token-request.schema.json +47 -0
  151. package/artifacts/schema/oauth-token-response.schema.json +38 -0
  152. package/artifacts/schema/org-alerts-query.schema.json +15 -0
  153. package/artifacts/schema/org-event-dropped.schema.json +26 -0
  154. package/artifacts/schema/org-event-replay.schema.json +97 -0
  155. package/artifacts/schema/org-event-subscribe.schema.json +14 -0
  156. package/artifacts/schema/org-event-unsubscribe.schema.json +14 -0
  157. package/artifacts/schema/org-event.schema.json +75 -0
  158. package/artifacts/schema/org-firing-alerts-response.schema.json +178 -0
  159. package/artifacts/schema/org-health-query.schema.json +13 -0
  160. package/artifacts/schema/org-latency-query.schema.json +42 -0
  161. package/artifacts/schema/org-latency-response.schema.json +124 -0
  162. package/artifacts/schema/org-quota-usage-counts.schema.json +42 -0
  163. package/artifacts/schema/org-quota-usage.schema.json +102 -0
  164. package/artifacts/schema/org-quotas.schema.json +51 -0
  165. package/artifacts/schema/org-usage-query.schema.json +19 -0
  166. package/artifacts/schema/org-usage-response.schema.json +77 -0
  167. package/artifacts/schema/org.schema.json +30 -0
  168. package/artifacts/schema/parameter-invalid-details.schema.json +37 -0
  169. package/artifacts/schema/parameter-spec.schema.json +120 -0
  170. package/artifacts/schema/parameter-violation.schema.json +24 -0
  171. package/artifacts/schema/password-change-request.schema.json +21 -0
  172. package/artifacts/schema/password-reset-confirm.schema.json +19 -0
  173. package/artifacts/schema/password-reset-request.schema.json +14 -0
  174. package/artifacts/schema/patch-app-oidc-provider-request.schema.json +50 -0
  175. package/artifacts/schema/patch-app-user-request.schema.json +34 -0
  176. package/artifacts/schema/patch-auth-me-request.schema.json +22 -0
  177. package/artifacts/schema/patch-fleetless-user-request.schema.json +20 -0
  178. package/artifacts/schema/patch-org-request.schema.json +15 -0
  179. package/artifacts/schema/patch-org-response.schema.json +40 -0
  180. package/artifacts/schema/patch-robot-request.schema.json +15 -0
  181. package/artifacts/schema/patch-robot-response.schema.json +40 -0
  182. package/artifacts/schema/pending-team-invite-list-response.schema.json +52 -0
  183. package/artifacts/schema/pending-team-invite.schema.json +39 -0
  184. package/artifacts/schema/protected-resource-metadata.schema.json +41 -0
  185. package/artifacts/schema/publish-config-response.schema.json +21 -0
  186. package/artifacts/schema/publish-request.schema.json +17 -0
  187. package/artifacts/schema/publisher-config.schema.json +285 -0
  188. package/artifacts/schema/put-app-auth-config-request.schema.json +93 -0
  189. package/artifacts/schema/put-app-mail-template-request.schema.json +35 -0
  190. package/artifacts/schema/put-config-draft-request.schema.json +13 -0
  191. package/artifacts/schema/put-datapoint-display-request.schema.json +31 -0
  192. package/artifacts/schema/put-robot-details-request.schema.json +41 -0
  193. package/artifacts/schema/put-robot-details-response.schema.json +43 -0
  194. package/artifacts/schema/rate-limit-details.schema.json +15 -0
  195. package/artifacts/schema/refresh-request.schema.json +13 -0
  196. package/artifacts/schema/release-live-query.schema.json +13 -0
  197. package/artifacts/schema/rename-slug-request.schema.json +23 -0
  198. package/artifacts/schema/rename-slug-response.schema.json +24 -0
  199. package/artifacts/schema/resource-health-event.schema.json +72 -0
  200. package/artifacts/schema/resource-health-list-response.schema.json +80 -0
  201. package/artifacts/schema/resource-health-state.schema.json +68 -0
  202. package/artifacts/schema/robot-config-doc.schema.json +1616 -0
  203. package/artifacts/schema/robot-delete-query.schema.json +12 -0
  204. package/artifacts/schema/robot-deletion-summary.schema.json +63 -0
  205. package/artifacts/schema/robot-detail-response.schema.json +262 -0
  206. package/artifacts/schema/robot-details-doc.schema.json +33 -0
  207. package/artifacts/schema/robot-jobs-response.schema.json +119 -0
  208. package/artifacts/schema/robot-latency-series.schema.json +81 -0
  209. package/artifacts/schema/robot-list-item.schema.json +94 -0
  210. package/artifacts/schema/robot-list-response.schema.json +106 -0
  211. package/artifacts/schema/robot.schema.json +30 -0
  212. package/artifacts/schema/role-list-response.schema.json +48 -0
  213. package/artifacts/schema/role-permissions.schema.json +61 -0
  214. package/artifacts/schema/role.schema.json +35 -0
  215. package/artifacts/schema/ros-graph.schema.json +99 -0
  216. package/artifacts/schema/server-key-list-response.schema.json +64 -0
  217. package/artifacts/schema/server-key.schema.json +51 -0
  218. package/artifacts/schema/service-call-response.schema.json +13 -0
  219. package/artifacts/schema/service-config.schema.json +198 -0
  220. package/artifacts/schema/session-tokens.schema.json +28 -0
  221. package/artifacts/schema/sign-up-request.schema.json +26 -0
  222. package/artifacts/schema/sign-up-response.schema.json +127 -0
  223. package/artifacts/schema/slug-usage-response.schema.json +32 -0
  224. package/artifacts/schema/snapshot-header.schema.json +44 -0
  225. package/artifacts/schema/snapshot-meta-response.schema.json +85 -0
  226. package/artifacts/schema/subscribe-error.schema.json +31 -0
  227. package/artifacts/schema/team-invite.schema.json +57 -0
  228. package/artifacts/schema/tier-change-request.schema.json +17 -0
  229. package/artifacts/schema/type-definition.schema.json +144 -0
  230. package/artifacts/schema/types-response.schema.json +156 -0
  231. package/artifacts/schema/update-app-request.schema.json +32 -0
  232. package/artifacts/schema/urdf-completeness.schema.json +50 -0
  233. package/artifacts/schema/validation-issue.schema.json +43 -0
  234. package/artifacts/schema/waitlist-request.schema.json +15 -0
  235. package/artifacts/schema-outgoing/bridge-asset-progress.schema.json +102 -0
  236. package/artifacts/schema-outgoing/bridge-assets-available.schema.json +26 -0
  237. package/artifacts/schema-outgoing/bridge-camera-state.schema.json +80 -0
  238. package/artifacts/schema-outgoing/bridge-config-applied.schema.json +69 -0
  239. package/artifacts/schema-outgoing/bridge-hello.schema.json +68 -0
  240. package/artifacts/schema-outgoing/bridge-introspect.schema.json +119 -0
  241. package/artifacts/schema-outgoing/bridge-job-lost.schema.json +23 -0
  242. package/artifacts/schema-outgoing/bridge-job-update.schema.json +102 -0
  243. package/artifacts/schema-outgoing/bridge-pong.schema.json +20 -0
  244. package/artifacts/schema-outgoing/bridge-type-definitions.schema.json +174 -0
  245. package/artifacts/schema-outgoing/datapoint-frame.schema.json +29 -0
  246. package/artifacts/schema-outgoing/snapshot-header.schema.json +45 -0
  247. package/dist/alerts.d.ts +255 -0
  248. package/dist/alerts.js +193 -0
  249. package/dist/app-users.d.ts +606 -0
  250. package/dist/app-users.js +696 -0
  251. package/dist/apps.d.ts +175 -0
  252. package/dist/apps.js +267 -0
  253. package/dist/assets.d.ts +434 -0
  254. package/dist/assets.js +546 -0
  255. package/dist/audit.d.ts +129 -0
  256. package/dist/audit.js +238 -0
  257. package/dist/client-auth.d.ts +409 -0
  258. package/dist/client-auth.js +487 -0
  259. package/dist/common.d.ts +186 -0
  260. package/dist/common.js +199 -0
  261. package/dist/config-issues.d.ts +175 -0
  262. package/dist/config-issues.js +339 -0
  263. package/dist/config.d.ts +862 -0
  264. package/dist/config.js +1988 -0
  265. package/dist/errors.d.ts +52 -0
  266. package/dist/errors.js +786 -0
  267. package/dist/identity.d.ts +549 -0
  268. package/dist/identity.js +503 -0
  269. package/dist/index.d.ts +51 -0
  270. package/dist/index.js +51 -0
  271. package/dist/introspection.d.ts +99 -0
  272. package/dist/introspection.js +97 -0
  273. package/dist/jobs.d.ts +334 -0
  274. package/dist/jobs.js +345 -0
  275. package/dist/mcp.d.ts +239 -0
  276. package/dist/mcp.js +153 -0
  277. package/dist/oauth.d.ts +344 -0
  278. package/dist/oauth.js +488 -0
  279. package/dist/protocol.d.ts +781 -0
  280. package/dist/protocol.js +715 -0
  281. package/dist/realtime.d.ts +494 -0
  282. package/dist/realtime.js +512 -0
  283. package/dist/rest.d.ts +1989 -0
  284. package/dist/rest.js +1963 -0
  285. package/dist/routes.d.ts +94 -0
  286. package/dist/routes.js +2298 -0
  287. package/package.json +61 -0
package/dist/mcp.js ADDED
@@ -0,0 +1,153 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ import { z } from 'zod';
3
+ import { slug } from './common.js';
4
+ /**
5
+ * The MCP server of §17: **one** remote MCP endpoint for the whole platform,
6
+ * whose tools are the exposed services and datapoints the signed-in user's
7
+ * roles permit. The per-app `/mcp/<identifier>` servers this file once
8
+ * described were deleted by the org-central identity redesign (D5/D6).
9
+ *
10
+ * **This file describes the seam, not the protocol.** The MCP messages
11
+ * themselves (`initialize`, `tools/list`, `tools/call`) are defined by the
12
+ * Model Context Protocol and implemented with its official SDK — writing our
13
+ * own zod copies of them would create a second source of truth for somebody
14
+ * else's specification, which is the one thing this package exists to avoid.
15
+ * What lives here is what *Fleetless* decides: which revision we speak, where
16
+ * the endpoint is, how a tool is named, and what the console is shown before
17
+ * an end user ever connects.
18
+ */
19
+ /**
20
+ * The protocol revision W7c speaks. Chosen with André on 2026-08-18 over the
21
+ * newer `2026-07-28`.
22
+ *
23
+ * This is the latest revision the **stable** MCP TypeScript SDK ships, and it
24
+ * negotiates down to `2024-11-05`, so it covers the AI tools that exist today.
25
+ * `2026-07-28` is real and is where the protocol is going — it *removes*
26
+ * Streamable HTTP's session ids, the standalone SSE channel and resumability,
27
+ * and servers speaking only it answer `405` to GET and DELETE.
28
+ *
29
+ * **Which is why this server is stateless anyway.** Building sessions we would
30
+ * have to delete again is work in the wrong direction, and a per-process
31
+ * session map is the assumption that breaks at the second cloud instance —
32
+ * the register already carries one row of exactly that shape
33
+ * (`max_realtime_connections`), and W8 is where a second instance appears.
34
+ */
35
+ export const MCP_PROTOCOL_VERSION = '2025-11-25';
36
+ /**
37
+ * The path of the **central** MCP server — what a *Fleetless user* pastes into
38
+ * their AI tool, appended to the cloud's public base URL.
39
+ *
40
+ * **Not parameterised, and that is now a statement rather than the absence of
41
+ * one.** The central endpoint serves the org's team with the console tool
42
+ * family (2026-09-05, D7); an app's users reach a different endpoint, whose
43
+ * path `mcpAppEndpointPath` builds. Two constants for two audiences, so a call
44
+ * site says which it means instead of an argument deciding it.
45
+ *
46
+ * **The canonical URL is `<PUBLIC_API_BASE_URL>${MCP_ENDPOINT_PATH}`, not the
47
+ * friendly alias.** `mcp.fleetless.dev` is a reverse proxy onto the same
48
+ * cloud, but the cloud mints every OAuth issuer, resource and `aud` from
49
+ * `PUBLIC_API_BASE_URL` and compares the token's `aud` against that string —
50
+ * never against the request's `Host`. Hand out the canonical one.
51
+ */
52
+ export const MCP_ENDPOINT_PATH = '/mcp';
53
+ /**
54
+ * The path of **one app's** MCP server (D7) — what an app user pastes into
55
+ * their AI tool, served only while the app's `appAuthConfig.mcp_enabled` is on.
56
+ *
57
+ * A helper rather than a template literal at four call sites, for
58
+ * `OAUTH_PATHS`' reason: the console shows this string with a copy button, the
59
+ * cloud registers the route from it, and the docs render it. A path spelled in
60
+ * three places is a path two of them will one day spell differently — and this
61
+ * repository has already paid for exactly that, with an `idpStart` entry naming
62
+ * a route the cloud had deleted.
63
+ *
64
+ * **This is the path, not the URL.** Append it to `PUBLIC_API_BASE_URL`, the
65
+ * canonical origin the cloud mints every issuer and audience from, rather than
66
+ * to the friendly `mcp.fleetless.dev` alias — a token's `aud` is compared
67
+ * against the canonical string and never against the request's `Host`.
68
+ *
69
+ * There was a `mcpEndpointPath(appIdentifier)` before, deleted with the per-app
70
+ * endpoint in the central-MCP cut and remembered here because the shape of that
71
+ * mistake is worth not repeating: the console kept offering a copy button for a
72
+ * URL that answered `404`. This one exists **with** its endpoint, and the
73
+ * cloud's route-manifest test is what keeps them together.
74
+ */
75
+ export function mcpAppEndpointPath(appIdentifier) {
76
+ return `/mcp/${appIdentifier}`;
77
+ }
78
+ export function MCP_APP_PATHS(appIdentifier) {
79
+ const endpoint = mcpAppEndpointPath(appIdentifier);
80
+ return {
81
+ endpoint,
82
+ protectedResourceMetadata: `/.well-known/oauth-protected-resource${endpoint}`,
83
+ authorizationServerMetadata: `/.well-known/oauth-authorization-server${endpoint}`,
84
+ register: `${endpoint}/oauth/register`,
85
+ authorize: `${endpoint}/oauth/authorize`,
86
+ token: `${endpoint}/oauth/token`,
87
+ };
88
+ }
89
+ /**
90
+ * Which exposed kind a tool came from. Not the MCP protocol's vocabulary —
91
+ * ours, so the console can group a preview the way the services editor is
92
+ * grouped.
93
+ */
94
+ export const mcpToolKind = z.enum(['datapoint', 'service', 'action', 'publisher', 'camera']);
95
+ /** MCP's own bound on a tool name, and the charset that is safe across clients. */
96
+ export const MCP_TOOL_NAME_MAX = 128;
97
+ export const mcpToolNamePattern = /^[a-z0-9][a-z0-9_-]*$/;
98
+ /**
99
+ * One exposure of a robot, as `robot_describe` and the console's per-role
100
+ * preview list it (FL-006). Every exposure the role grants is listed —
101
+ * a missing `description` is shown as `null`, never used to hide the entry.
102
+ *
103
+ * `input_schema` is a JSON Schema document generated from an action's,
104
+ * service's or publisher's `parameters`; `null` for the other kinds. It is
105
+ * `unknown` for the same reason the retired `mcpToolPreview.input_schema`
106
+ * was: pinning it would mean maintaining a zod description of JSON Schema.
107
+ */
108
+ export const mcpExposure = z.object({
109
+ slug,
110
+ kind: mcpToolKind,
111
+ description: z.string().max(2000).nullable(),
112
+ /** A datapoint's `numeric.unit`, verbatim; `null` for every other kind and for a unitless datapoint. */
113
+ unit: z.string().max(32).nullable(),
114
+ /**
115
+ * A datapoint's `numeric.decimals`, verbatim; `null` for every other kind
116
+ * and for a datapoint that does not set it. Required-nullable rather than
117
+ * optional for the same reason as `unit`: an omitted field would make a
118
+ * producer that forgot the datapoint's configuration indistinguishable from
119
+ * one reporting a datapoint that has none.
120
+ */
121
+ decimals: z.number().int().min(0).max(6).nullable(),
122
+ input_schema: z.unknown().nullable(),
123
+ });
124
+ /** The two role capabilities a robot tool can need beyond a slug grant. `presence` is a stream and has no tool. */
125
+ export const mcpCapabilities = z.object({
126
+ action_history: z.boolean(),
127
+ assets: z.boolean(),
128
+ });
129
+ /** What one caller may do on one robot — the answer to `robot_describe`. */
130
+ export const mcpRobotDatasheet = z.object({
131
+ robot_id: z.uuid(),
132
+ robot_name: z.string().min(1).max(200),
133
+ capabilities: mcpCapabilities,
134
+ exposures: z.array(mcpExposure).max(2000),
135
+ });
136
+ /**
137
+ * What a developer sees before an end user connects: the datasheet each
138
+ * robot of the app would answer for one role. Replaces the per-slug tool
139
+ * preview and its `omitted` list — with a fixed catalog there is no tool to
140
+ * omit, only exposures to grant.
141
+ */
142
+ export const mcpRolePreviewResponse = z.object({
143
+ role_id: z.uuid(),
144
+ robots: z.array(mcpRobotDatasheet).max(500),
145
+ });
146
+ /**
147
+ * Where a signed asset link is served. An MCP session token is refused on
148
+ * REST by design, so `asset_get`/`urdf_get` mint a bearer-free link the agent
149
+ * behind the client can fetch. Lifetime is fixed; the token binds robot, asset
150
+ * and expiry under `JWT_SECRET`.
151
+ */
152
+ export const MCP_ASSET_LINK_PATH = '/api/asset-links';
153
+ export const MCP_ASSET_LINK_TTL_MS = 15 * 60 * 1000;
@@ -0,0 +1,344 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * **OAuth 2.1, and it remains only for MCP** (2026-09-05 app-user-auth, D8).
4
+ *
5
+ * This file used to describe two front doors: an app's end users signing in
6
+ * through a Fleetless-hosted, app-branded login page, and MCP clients signing
7
+ * in for the central endpoint. The first is deleted. An app now has its own UI
8
+ * and calls the JSON client-auth API (`client-auth.ts`); Fleetless renders an
9
+ * app user no page, so there is no hosted login, no consent screen, no
10
+ * developer-registered client and no app-level dynamic registration.
11
+ *
12
+ * What remains is the MCP authorization server — central, for Fleetless users,
13
+ * and per app for an app's users — and the console's own OAuth portal, which
14
+ * answers `oauthRedirectResponse` at its login and sign-up steps.
15
+ *
16
+ * The client model this file was written to get right is still the important
17
+ * part, and it survived the cut intact: an MCP client **registers itself**
18
+ * (RFC 7591) because the person only ever pastes a URL into an AI tool.
19
+ * Nobody vetted it, its redirect URIs arrive from the client itself, and the
20
+ * tools it will call move a physical robot. That is why consent names the
21
+ * client with an explicit *unverified* marker — see `clientMcpInteraction` in
22
+ * `client-auth.ts`, which is where the per-app half of that screen is now
23
+ * described, because the app renders it and Fleetless does not.
24
+ */
25
+ /**
26
+ * **This file speaks two error dialects on purpose, and unifying them would
27
+ * break conformance.**
28
+ *
29
+ * The OAuth endpoints (`/mcp/oauth/authorize`, `/mcp/oauth/token`,
30
+ * registration) answer in RFC 6749 §5.2's shape — a flat `error` string from a fixed set,
31
+ * with an optional `error_description`. That is what an RFC-compliant client
32
+ * parses, and `mcp-inspector` is such a client. A Fleetless `apiError` there
33
+ * would be well-formed JSON that no standard client can read.
34
+ *
35
+ * The *management* endpoints beside them — configuring a provider, editing an
36
+ * app's auth settings — are ordinary console API and use `apiError` with
37
+ * `ERROR_CODES` like everything else.
38
+ *
39
+ * So: **two shapes, split by audience, not by accident.** Written down here
40
+ * because the natural instinct on finding two error formats in one server is
41
+ * to unify them, and doing so silently removes the reason the standard one is
42
+ * there.
43
+ *
44
+ * **The split is by audience and the path prefix will mislead you.**
45
+ * `/mcp/oauth/consent` sits under an `/oauth/` segment and is nevertheless an
46
+ * `apiError` endpoint: it is not in RFC 6749's or RFC 7591's endpoint set, and
47
+ * its only caller is a page this server rendered. Whoever later sorts these by
48
+ * prefix will move it, and be wrong. Ask who parses the response, not where it
49
+ * lives.
50
+ */
51
+ export declare const oauthErrorCode: z.ZodEnum<{
52
+ invalid_request: "invalid_request";
53
+ invalid_client: "invalid_client";
54
+ invalid_grant: "invalid_grant";
55
+ unauthorized_client: "unauthorized_client";
56
+ unsupported_grant_type: "unsupported_grant_type";
57
+ invalid_scope: "invalid_scope";
58
+ access_denied: "access_denied";
59
+ server_error: "server_error";
60
+ temporarily_unavailable: "temporarily_unavailable";
61
+ invalid_target: "invalid_target";
62
+ }>;
63
+ export type OauthErrorCode = z.infer<typeof oauthErrorCode>;
64
+ export declare const oauthError: z.ZodObject<{
65
+ error: z.ZodEnum<{
66
+ invalid_request: "invalid_request";
67
+ invalid_client: "invalid_client";
68
+ invalid_grant: "invalid_grant";
69
+ unauthorized_client: "unauthorized_client";
70
+ unsupported_grant_type: "unsupported_grant_type";
71
+ invalid_scope: "invalid_scope";
72
+ access_denied: "access_denied";
73
+ server_error: "server_error";
74
+ temporarily_unavailable: "temporarily_unavailable";
75
+ invalid_target: "invalid_target";
76
+ }>;
77
+ error_description: z.ZodOptional<z.ZodString>;
78
+ state: z.ZodOptional<z.ZodString>;
79
+ fleetless_code: z.ZodOptional<z.ZodString>;
80
+ }, z.core.$strip>;
81
+ export type OauthError = z.infer<typeof oauthError>;
82
+ /**
83
+ * A redirect URI, and the rule is stricter than "a URL".
84
+ *
85
+ * **The defence for this was already written down in this codebase, twice.**
86
+ * `config.ts` validates a V4L2 device path from the wire with a prefix rule
87
+ * *and* an explicit refusal of `..` segments, tested, with the reasoning
88
+ * recorded; W7's review then found a `package://` traversal in the bridge
89
+ * that the same rule would have prevented, and the finding that mattered was
90
+ * not the traversal but that **the rule existed one file over and was never
91
+ * carried across.** A redirect URI is the same shape of problem from a less
92
+ * trusted source: an attacker-supplied string that decides where a credential
93
+ * is sent.
94
+ *
95
+ * Matching at the server is **exact string comparison against a registered
96
+ * value** — never a prefix, never a wildcard host, never "starts with". A
97
+ * prefix match on `https://app.example.com/cb` accepts
98
+ * `https://app.example.com/cb.evil.test`.
99
+ */
100
+ export declare const redirectUri: z.ZodString;
101
+ export type RedirectUri = z.infer<typeof redirectUri>;
102
+ /**
103
+ * OAuth 2.1 removes the implicit and password grants and **makes PKCE
104
+ * mandatory for every client**, public or confidential. `plain` is not
105
+ * offered: a challenge equal to its verifier defends against nothing, and
106
+ * offering it means a downgrade is negotiable.
107
+ */
108
+ export declare const codeChallengeMethod: z.ZodEnum<{
109
+ S256: "S256";
110
+ }>;
111
+ /**
112
+ * **How many callbacks one dynamic registration may name.**
113
+ *
114
+ * RFC 7591 lets a client register several; five is above every real MCP client
115
+ * observed and far below "a place to store data" on an endpoint that takes no
116
+ * credential. It lives here rather than in the cloud because this schema now
117
+ * *publishes* the bound: a number the reference states and a different number
118
+ * the server enforces is two policies for one decision, and the endpoint spent
119
+ * a release documenting `20` while refusing the sixth URI.
120
+ */
121
+ export declare const MCP_DCR_MAX_REDIRECT_URIS = 5;
122
+ /**
123
+ * RFC 7591 dynamic client registration — **the metadata both MCP
124
+ * authorization servers understand**, central and per-app.
125
+ *
126
+ * **Not `.strict()`, and that is the schema agreeing with the server rather
127
+ * than a gap in it.** §3.1 obliges a registration endpoint to ignore metadata
128
+ * it does not understand, and real MCP clients send `client_uri`, `logo_uri`,
129
+ * `software_id` and `contacts`. A strict shape here would describe a `400`
130
+ * that no conforming client ever earns, and would take the whole
131
+ * paste-the-URL flow down if anything ever parsed against it. Unknown keys
132
+ * are therefore stripped by this schema and ignored by the server, which is
133
+ * the same answer said twice.
134
+ *
135
+ * **The server still reads the body field by field** (`registerMcpDynamicClient`
136
+ * in `cloud/src/mcp-oauth-core.ts`), and the reason is the error vocabulary,
137
+ * not the shape: §3.2.2 distinguishes `invalid_redirect_uri` from
138
+ * `invalid_client_metadata`, and one `safeParse` failure cannot say which of
139
+ * the two a caller earned. So this schema is what the endpoint *accepts*, and
140
+ * the handler is what turns a miss into the right RFC code.
141
+ *
142
+ * **`client_name` is optional because the server treats it as optional**: RFC
143
+ * 7591 makes every metadata field optional, and a registration that omits it
144
+ * is recorded under a default name rather than refused. `redirect_uris` is the
145
+ * one field a registration cannot do without — there is nowhere to return a
146
+ * code otherwise.
147
+ */
148
+ export declare const dynamicClientRegistrationRequest: z.ZodObject<{
149
+ redirect_uris: z.ZodArray<z.ZodString>;
150
+ client_name: z.ZodOptional<z.ZodString>;
151
+ token_endpoint_auth_method: z.ZodOptional<z.ZodEnum<{
152
+ none: "none";
153
+ }>>;
154
+ grant_types: z.ZodOptional<z.ZodArray<z.ZodEnum<{
155
+ refresh_token: "refresh_token";
156
+ authorization_code: "authorization_code";
157
+ }>>>;
158
+ response_types: z.ZodOptional<z.ZodArray<z.ZodEnum<{
159
+ code: "code";
160
+ }>>>;
161
+ scope: z.ZodOptional<z.ZodString>;
162
+ }, z.core.$strip>;
163
+ export type DynamicClientRegistrationRequest = z.infer<typeof dynamicClientRegistrationRequest>;
164
+ export declare const dynamicClientRegistrationResponse: z.ZodObject<{
165
+ client_id: z.ZodString;
166
+ client_name: z.ZodString;
167
+ redirect_uris: z.ZodArray<z.ZodString>;
168
+ grant_types: z.ZodArray<z.ZodString>;
169
+ response_types: z.ZodArray<z.ZodString>;
170
+ token_endpoint_auth_method: z.ZodLiteral<"none">;
171
+ client_id_issued_at: z.ZodNumber;
172
+ client_secret_expires_at: z.ZodLiteral<0>;
173
+ }, z.core.$strip>;
174
+ export type DynamicClientRegistrationResponse = z.infer<typeof dynamicClientRegistrationResponse>;
175
+ /**
176
+ * **The MCP token endpoint's request — one grant, because the servers serve
177
+ * one.**
178
+ *
179
+ * Both authorization servers, central and per-app, exchange through
180
+ * `exchangeMcpAuthorizationCode` (`cloud/src/mcp-oauth-core.ts`), whose first
181
+ * act is to refuse anything but `authorization_code` before a single lookup
182
+ * happens. There is no refresh grant here: a session ends when its token
183
+ * expires and the client signs in again.
184
+ *
185
+ * **This was a `discriminatedUnion` with a `refresh_token` branch, and that
186
+ * branch had no producer left.** It described the app-level OAuth surface,
187
+ * which is deleted; an app user's refresh runs through `POST
188
+ * /api/client/refresh` and `refreshRequest`, a different wire on a different
189
+ * route. Keeping it would have published, to every MCP client author reading
190
+ * `/openapi.json`, a grant the endpoint answers `unsupported_grant_type` to.
191
+ * The argument the branch carried is worth keeping even though the branch is
192
+ * not: **RFC 8707's `resource` has to survive rotation**, because a refresh
193
+ * that drops the audience mints a successor with no `aud`, and the validating
194
+ * resource then refuses a token the caller obtained legitimately — one token
195
+ * lifetime after a login that worked, to somebody who did nothing wrong. If a
196
+ * refresh grant is ever added here, it carries `resource`.
197
+ *
198
+ * `code_verifier`'s bounds are RFC 7636 §4.1's, charset included. A verifier
199
+ * is compared, not parsed, so a length nobody checks is a length an attacker
200
+ * chooses.
201
+ *
202
+ * **Deliberately not `.strict()`**, unlike a registration request: that comes
203
+ * from a client we are about to trust, where an unknown key is a caller
204
+ * assuming a feature into existence, while a token request comes from any
205
+ * RFC-compliant client, which may legitimately send parameters this server
206
+ * does not read. Refusing those would be a conformance bug. The consequence
207
+ * is worth stating because it bit the test for this very schema: unknown keys
208
+ * are **stripped**, so `safeParse().success` cannot tell a present field from
209
+ * an absent one. Assert on the parsed value.
210
+ */
211
+ export declare const oauthTokenRequest: z.ZodObject<{
212
+ grant_type: z.ZodLiteral<"authorization_code">;
213
+ code: z.ZodString;
214
+ redirect_uri: z.ZodString;
215
+ client_id: z.ZodString;
216
+ code_verifier: z.ZodString;
217
+ resource: z.ZodOptional<z.ZodURL>;
218
+ }, z.core.$strip>;
219
+ export type OauthTokenRequest = z.infer<typeof oauthTokenRequest>;
220
+ /**
221
+ * RFC 6749 §5.1's success envelope — **the second deliberate dialect, and this
222
+ * one is a success shape rather than an error shape.**
223
+ *
224
+ * The values inside are the same tokens `/api/client/login` mints; only the
225
+ * envelope differs, because an RFC-compliant client parses this one and knows
226
+ * nothing about Fleetless. So a consumer holding this **normalises it into
227
+ * `sessionTokens` and stores that** — it does not carry the envelope around.
228
+ * Written here rather than invented once in the cloud and once in the SDK,
229
+ * which is how two implementations of one wire shape start disagreeing.
230
+ *
231
+ * `token_type` is `Bearer` as a literal because it is what this server emits.
232
+ * RFC 6749 §5.1 makes the value case-insensitive **for a client reading it**;
233
+ * that leniency belongs in a parser we do not own, not in the shape we
234
+ * produce.
235
+ */
236
+ export declare const oauthTokenResponse: z.ZodObject<{
237
+ access_token: z.ZodString;
238
+ token_type: z.ZodLiteral<"Bearer">;
239
+ expires_in: z.ZodNumber;
240
+ refresh_token: z.ZodOptional<z.ZodString>;
241
+ scope: z.ZodOptional<z.ZodString>;
242
+ }, z.core.$strip>;
243
+ export type OauthTokenResponse = z.infer<typeof oauthTokenResponse>;
244
+ /** RFC 8414 §2 — the document a client reads *instead of* being told anything. */
245
+ export declare const authorizationServerMetadata: z.ZodObject<{
246
+ issuer: z.ZodURL;
247
+ authorization_endpoint: z.ZodURL;
248
+ token_endpoint: z.ZodURL;
249
+ registration_endpoint: z.ZodOptional<z.ZodURL>;
250
+ response_types_supported: z.ZodArray<z.ZodLiteral<"code">>;
251
+ grant_types_supported: z.ZodArray<z.ZodEnum<{
252
+ refresh_token: "refresh_token";
253
+ authorization_code: "authorization_code";
254
+ }>>;
255
+ code_challenge_methods_supported: z.ZodArray<z.ZodEnum<{
256
+ S256: "S256";
257
+ }>>;
258
+ token_endpoint_auth_methods_supported: z.ZodArray<z.ZodLiteral<"none">>;
259
+ scopes_supported: z.ZodOptional<z.ZodArray<z.ZodString>>;
260
+ }, z.core.$strip>;
261
+ export type AuthorizationServerMetadata = z.infer<typeof authorizationServerMetadata>;
262
+ /**
263
+ * RFC 9728 — what a *resource* publishes about who may authorize for it.
264
+ *
265
+ * W7b mints tokens bound to a resource that W7c builds. **A minting mechanism
266
+ * with no validator is the failure mode this project has now met twelve times
267
+ * in one wave: a check that cannot fail.** So W7b also ships a resource that
268
+ * *rejects* a token whose audience names something else, and the gate measures
269
+ * the rejection rather than the presence of the claim.
270
+ */
271
+ export declare const protectedResourceMetadata: z.ZodObject<{
272
+ resource: z.ZodURL;
273
+ authorization_servers: z.ZodArray<z.ZodURL>;
274
+ bearer_methods_supported: z.ZodArray<z.ZodLiteral<"header">>;
275
+ scopes_supported: z.ZodOptional<z.ZodArray<z.ZodString>>;
276
+ }, z.core.$strip>;
277
+ export type ProtectedResourceMetadata = z.infer<typeof protectedResourceMetadata>;
278
+ /**
279
+ * Where the page goes next, and this shape is a **redirect the server chose**,
280
+ * never one the page may be talked into.
281
+ *
282
+ * The server emits exactly two kinds of value here: its own consent path, or a
283
+ * redirect URI already registered for this client with the code appended.
284
+ * A page must navigate to it and nothing else — in particular it must not fall
285
+ * back to any URL that arrived in its own query string if this field is
286
+ * missing, which is how an open redirect gets built by accident on the way to
287
+ * handling an error.
288
+ *
289
+ * JSON rather than a `302` because the page is an application: a redirect
290
+ * cannot carry a field-level credential error back to a form, and a flow that
291
+ * answers errors by navigating loses the state the user typed.
292
+ */
293
+ export declare const oauthRedirectResponse: z.ZodObject<{
294
+ redirect_to: z.ZodString;
295
+ }, z.core.$strip>;
296
+ export type OauthRedirectResponse = z.infer<typeof oauthRedirectResponse>;
297
+ /**
298
+ * The authorization request of RFC 6749 §4.1.1 with PKCE (RFC 7636) — the
299
+ * shape `/mcp/oauth/authorize` reads.
300
+ *
301
+ * **The cloud reads every parameter by hand, and that is not an omission** —
302
+ * each failure has its own answer. `client_id` and `redirect_uri` are refused
303
+ * flat, with no redirect, because until both are confirmed there is no trusted
304
+ * target to bounce a browser to; everything after them is reported to the
305
+ * client's own callback as query parameters. A single `safeParse` would
306
+ * collapse those two answers into one. So this schema pins the successful
307
+ * shape and the documentation, not the error path.
308
+ *
309
+ * **Four route entries point at it**: `GET /mcp/oauth/authorize` and
310
+ * `GET /mcp/:appIdentifier/oauth/authorize` (D7), which read the same wire.
311
+ * They spent a release naming nothing — the app-level `/oauth/authorize` this
312
+ * was written for was deleted, and `query: null` was read as "there is no
313
+ * query here" rather than as "the handler reads it by hand" — and the eight
314
+ * documented parameters left `/openapi.json` with nothing able to notice,
315
+ * because the undocumented-field ratchet counts gaps and a fully documented
316
+ * schema leaving makes that number improve.
317
+ */
318
+ export declare const oauthAuthorizeQuery: z.ZodObject<{
319
+ response_type: z.ZodLiteral<"code">;
320
+ client_id: z.ZodString;
321
+ redirect_uri: z.ZodString;
322
+ code_challenge: z.ZodString;
323
+ code_challenge_method: z.ZodLiteral<"S256">;
324
+ state: z.ZodOptional<z.ZodString>;
325
+ resource: z.ZodOptional<z.ZodString>;
326
+ }, z.core.$strip>;
327
+ export type OauthAuthorizeQuery = z.infer<typeof oauthAuthorizeQuery>;
328
+ /**
329
+ * **`oauthRegisterQuery` is deleted, and this note is what it leaves behind.**
330
+ *
331
+ * It carried one parameter, `app_identifier`, on the argument that RFC 7591's
332
+ * registration body has no field for it and one endpoint could serve every
333
+ * app. It was kept — explicitly, in its own doc comment — "for the per-app MCP
334
+ * registration the MCP train adds (D7), which needs exactly this parameter".
335
+ *
336
+ * **That train shipped and needed no such parameter.** `POST
337
+ * /mcp/:appIdentifier/oauth/register` puts the app in the **path**, built by
338
+ * `MCP_APP_PATHS`, and `resolveAppMcpTarget` reads it from `request.params`;
339
+ * `registerMcpDynamicClient` never looks at a query at all. So the one reason
340
+ * the schema was kept became false the moment its successor arrived, and
341
+ * nothing was watching the reason — which is the failure this comment is here
342
+ * to make expensive to repeat. A schema reserved for a future route is a claim
343
+ * with an expiry date on it, and the expiry has to be checked by somebody.
344
+ */