@fleetless/contracts 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +68 -2
- package/CODE_OF_CONDUCT.md +83 -0
- package/CONTRIBUTING.md +136 -0
- package/README.md +49 -13
- package/SECURITY.md +55 -0
- package/artifacts/openapi.json +1 -1
- package/artifacts/routes.json +1 -1
- package/dist/alerts.d.ts +19 -24
- package/dist/alerts.js +18 -24
- package/dist/app-users.d.ts +7 -6
- package/dist/app-users.js +6 -6
- package/dist/apps.d.ts +21 -25
- package/dist/apps.js +40 -51
- package/dist/assets.d.ts +70 -132
- package/dist/assets.js +130 -223
- package/dist/audit.d.ts +11 -11
- package/dist/audit.js +25 -51
- package/dist/client-auth.d.ts +4 -4
- package/dist/client-auth.js +3 -4
- package/dist/common.d.ts +27 -35
- package/dist/common.js +26 -35
- package/dist/config-issues.d.ts +4 -3
- package/dist/config-issues.js +7 -6
- package/dist/config.d.ts +31 -37
- package/dist/config.js +81 -110
- package/dist/errors.d.ts +4 -3
- package/dist/errors.js +61 -87
- package/dist/identity.d.ts +18 -21
- package/dist/identity.js +17 -21
- package/dist/index.d.ts +4 -4
- package/dist/index.js +12 -13
- package/dist/introspection.d.ts +7 -6
- package/dist/introspection.js +6 -6
- package/dist/jobs.d.ts +12 -12
- package/dist/jobs.js +20 -25
- package/dist/mcp.d.ts +11 -12
- package/dist/mcp.js +10 -12
- package/dist/oauth.d.ts +13 -18
- package/dist/oauth.js +13 -19
- package/dist/protocol.d.ts +51 -62
- package/dist/protocol.js +107 -139
- package/dist/realtime.d.ts +53 -68
- package/dist/realtime.js +77 -103
- package/dist/rest.d.ts +182 -243
- package/dist/rest.js +301 -395
- package/dist/routes.d.ts +4 -3
- package/dist/routes.js +3 -2
- package/package.json +12 -7
package/dist/introspection.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
1
2
|
import { z } from 'zod';
|
|
2
3
|
/**
|
|
3
|
-
* Introspection
|
|
4
|
+
* Introspection: what the connected bridge can tell the
|
|
4
5
|
* cloud about the robot's ROS graph, so the console can offer a quick pick
|
|
5
6
|
* instead of a blank text field.
|
|
6
7
|
*
|
|
@@ -45,10 +46,10 @@ export interface TypeField {
|
|
|
45
46
|
}
|
|
46
47
|
export declare const typeField: z.ZodType<TypeField>;
|
|
47
48
|
/**
|
|
48
|
-
* A resolved type of one robot. Custom types are per robot
|
|
49
|
-
*
|
|
49
|
+
* A resolved type of one robot. Custom types are per robot: two robots may
|
|
50
|
+
* define `custom_msgs/msg/Speed` differently and both are right.
|
|
50
51
|
*
|
|
51
|
-
*
|
|
52
|
+
* Messages, services and actions all appear here, because a
|
|
52
53
|
* `parameterSpec.name` has to resolve against *something*, and an action has
|
|
53
54
|
* no flat field list — it has a goal, a result and a feedback tree.
|
|
54
55
|
*
|
|
@@ -65,8 +66,8 @@ export declare const typeField: z.ZodType<TypeField>;
|
|
|
65
66
|
* a result in. They are carried so the console can show what an action will
|
|
66
67
|
* report back, and so a client knows the shape of `job.result` in advance.
|
|
67
68
|
*
|
|
68
|
-
* The `msg` member
|
|
69
|
-
*
|
|
69
|
+
* The `msg` member is the flat field list a message resolves to; the other
|
|
70
|
+
* two carry one tree per part.
|
|
70
71
|
*/
|
|
71
72
|
export declare const typeDefinition: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
72
73
|
name: z.ZodString;
|
package/dist/introspection.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
import { rosName, rosTypeName } from './common.js';
|
|
4
4
|
/**
|
|
5
|
-
* Introspection
|
|
5
|
+
* Introspection: what the connected bridge can tell the
|
|
6
6
|
* cloud about the robot's ROS graph, so the console can offer a quick pick
|
|
7
7
|
* instead of a blank text field.
|
|
8
8
|
*
|
|
@@ -31,10 +31,10 @@ export const typeField = z.lazy(() => z.object({
|
|
|
31
31
|
fields: z.array(typeField).nullable(),
|
|
32
32
|
}));
|
|
33
33
|
/**
|
|
34
|
-
* A resolved type of one robot. Custom types are per robot
|
|
35
|
-
*
|
|
34
|
+
* A resolved type of one robot. Custom types are per robot: two robots may
|
|
35
|
+
* define `custom_msgs/msg/Speed` differently and both are right.
|
|
36
36
|
*
|
|
37
|
-
*
|
|
37
|
+
* Messages, services and actions all appear here, because a
|
|
38
38
|
* `parameterSpec.name` has to resolve against *something*, and an action has
|
|
39
39
|
* no flat field list — it has a goal, a result and a feedback tree.
|
|
40
40
|
*
|
|
@@ -51,8 +51,8 @@ export const typeField = z.lazy(() => z.object({
|
|
|
51
51
|
* a result in. They are carried so the console can show what an action will
|
|
52
52
|
* report back, and so a client knows the shape of `job.result` in advance.
|
|
53
53
|
*
|
|
54
|
-
* The `msg` member
|
|
55
|
-
*
|
|
54
|
+
* The `msg` member is the flat field list a message resolves to; the other
|
|
55
|
+
* two carry one tree per part.
|
|
56
56
|
*/
|
|
57
57
|
export const typeDefinition = z.discriminatedUnion('kind', [
|
|
58
58
|
z.object({
|
package/dist/jobs.d.ts
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
1
2
|
import { z } from 'zod';
|
|
2
3
|
/**
|
|
3
|
-
* Jobs
|
|
4
|
+
* Jobs: one running unit of work on a robot — an action
|
|
4
5
|
* goal or a service call — with an id both sides know, so bridge and cloud
|
|
5
6
|
* stay in sync across a disconnect.
|
|
6
7
|
*
|
|
7
8
|
* Two rules shape everything here:
|
|
8
9
|
*
|
|
9
|
-
* 1. **State is observed by slug, not by id.** The id is informative
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* 2. **`lost` is a real outcome and must be said out loud
|
|
10
|
+
* 1. **State is observed by slug, not by id.** The id is informative; a client
|
|
11
|
+
* watches `robot × slug` and sees whatever job is running there, which is
|
|
12
|
+
* also why every observer of a slug sees the same job.
|
|
13
|
+
* 2. **`lost` is a real outcome and must be said out loud.** Job state
|
|
13
14
|
* lives only in the bridge's memory; if it restarts mid-job, the results
|
|
14
15
|
* are gone. The cloud then marks the job `lost` — never leaves it reading
|
|
15
16
|
* "running" because nobody contradicted it. A system that reports a
|
|
@@ -49,8 +50,8 @@ export type Job = z.infer<typeof job>;
|
|
|
49
50
|
/**
|
|
50
51
|
* One update about a job, pushed to subscribers of its slug.
|
|
51
52
|
*
|
|
52
|
-
* `timestamp_ms` is the bridge's capture time, exactly as for a datapoint
|
|
53
|
-
*
|
|
53
|
+
* `timestamp_ms` is the bridge's capture time, exactly as for a datapoint —
|
|
54
|
+
* action feedback carries it too — so a client computes the age
|
|
54
55
|
* of a progress report the same way it computes the age of a sensor value,
|
|
55
56
|
* and a burst of late-delivered feedback after a reconnect is visibly late
|
|
56
57
|
* rather than looking current.
|
|
@@ -86,9 +87,8 @@ export declare const jobEvent: z.ZodObject<{
|
|
|
86
87
|
}, z.core.$strip>;
|
|
87
88
|
export type JobEvent = z.infer<typeof jobEvent>;
|
|
88
89
|
/**
|
|
89
|
-
* What a busy refusal tells the caller
|
|
90
|
-
*
|
|
91
|
-
* to wait or to give up.
|
|
90
|
+
* What a busy refusal tells the caller: what is already running. A refusal that
|
|
91
|
+
* only says "busy" forces the caller to guess whether to wait or to give up.
|
|
92
92
|
*/
|
|
93
93
|
export declare const busyDetails: z.ZodObject<{
|
|
94
94
|
running: z.ZodObject<{
|
|
@@ -115,7 +115,7 @@ export declare const busyDetails: z.ZodObject<{
|
|
|
115
115
|
}, z.core.$strip>;
|
|
116
116
|
export type BusyDetails = z.infer<typeof busyDetails>;
|
|
117
117
|
/**
|
|
118
|
-
* What a `publisher_busy` refusal tells the caller
|
|
118
|
+
* What a `publisher_busy` refusal tells the caller.
|
|
119
119
|
*
|
|
120
120
|
* "Another caller is publishing and has not been quiet long enough" names a
|
|
121
121
|
* state and no action: the caller does not know how much longer, because
|
|
@@ -133,7 +133,7 @@ export declare const publisherBusyDetails: z.ZodObject<{
|
|
|
133
133
|
}, z.core.$strip>;
|
|
134
134
|
export type PublisherBusyDetails = z.infer<typeof publisherBusyDetails>;
|
|
135
135
|
/**
|
|
136
|
-
* What a `job_queue_full` refusal tells the caller
|
|
136
|
+
* What a `job_queue_full` refusal tells the caller.
|
|
137
137
|
*
|
|
138
138
|
* Both numbers, not just the limit: `limit` alone says how big the queue is
|
|
139
139
|
* and nothing about whether waiting will help, and `queued` alone cannot be
|
package/dist/jobs.js
CHANGED
|
@@ -2,16 +2,16 @@
|
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
import { slug, wireSeqCursor, wireTimestampMs } from './common.js';
|
|
4
4
|
/**
|
|
5
|
-
* Jobs
|
|
5
|
+
* Jobs: one running unit of work on a robot — an action
|
|
6
6
|
* goal or a service call — with an id both sides know, so bridge and cloud
|
|
7
7
|
* stay in sync across a disconnect.
|
|
8
8
|
*
|
|
9
9
|
* Two rules shape everything here:
|
|
10
10
|
*
|
|
11
|
-
* 1. **State is observed by slug, not by id.** The id is informative
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* 2. **`lost` is a real outcome and must be said out loud
|
|
11
|
+
* 1. **State is observed by slug, not by id.** The id is informative; a client
|
|
12
|
+
* watches `robot × slug` and sees whatever job is running there, which is
|
|
13
|
+
* also why every observer of a slug sees the same job.
|
|
14
|
+
* 2. **`lost` is a real outcome and must be said out loud.** Job state
|
|
15
15
|
* lives only in the bridge's memory; if it restarts mid-job, the results
|
|
16
16
|
* are gone. The cloud then marks the job `lost` — never leaves it reading
|
|
17
17
|
* "running" because nobody contradicted it. A system that reports a
|
|
@@ -37,17 +37,17 @@ export const job = z.object({
|
|
|
37
37
|
description: 'When this job last changed, as an ISO 8601 timestamp.',
|
|
38
38
|
}),
|
|
39
39
|
/**
|
|
40
|
-
* A monotonic counter, ascending in mint order
|
|
41
|
-
*
|
|
40
|
+
* A monotonic counter, ascending in mint order, and the **named** tiebreaker
|
|
41
|
+
* for any listing that claims an order.
|
|
42
42
|
*
|
|
43
43
|
* `started_at` is not a total order: two jobs minted in the same millisecond
|
|
44
44
|
* sort against each other arbitrarily, and arbitrarily means *differently on
|
|
45
45
|
* each query* — so `GET /api/robots/:id/jobs`, which documents "newest
|
|
46
|
-
* first", can show one twice and the other not at all.
|
|
47
|
-
*
|
|
46
|
+
* first", can show one twice and the other not at all. `auditEvent.seq`
|
|
47
|
+
* exists for the same reason on the audit log.
|
|
48
48
|
*
|
|
49
49
|
* **Scoped honestly: per cloud process, per run.** Job state lives in memory
|
|
50
|
-
*
|
|
50
|
+
* — that is why `lost` exists at all — so this counter restarts when
|
|
51
51
|
* the cloud does, alongside the jobs it orders. Sound, because it only ever
|
|
52
52
|
* orders jobs that coexist in one registry — and stated, because a reader
|
|
53
53
|
* who assumed `auditEvent.seq`'s durable semantics would be wrong.
|
|
@@ -62,14 +62,10 @@ export const job = z.object({
|
|
|
62
62
|
* Present on `failed`; a human message, plus a code where one exists.
|
|
63
63
|
*
|
|
64
64
|
* `details` exists because a refusal that carries only prose forces every
|
|
65
|
-
* consumer to parse it.
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
* "wait for one of N to finish" alert from a shape nothing in the system
|
|
70
|
-
* produced, and its test built that shape by hand — three repos agreeing
|
|
71
|
-
* with each other about a payload none of them exchanged (Momus, W6b
|
|
72
|
-
* review).
|
|
65
|
+
* consumer to parse it. A documented payload with nowhere to put it — the
|
|
66
|
+
* bridge reports a full queue as a job error — ends up formatted into the
|
|
67
|
+
* message and lost, and every consumer then builds the structured shape by
|
|
68
|
+
* hand from its own assumption.
|
|
73
69
|
*
|
|
74
70
|
* Optional, because most job errors have nothing structured to add. Where a
|
|
75
71
|
* code has a documented payload — `job_queue_full` has
|
|
@@ -95,8 +91,8 @@ export const job = z.object({
|
|
|
95
91
|
/**
|
|
96
92
|
* One update about a job, pushed to subscribers of its slug.
|
|
97
93
|
*
|
|
98
|
-
* `timestamp_ms` is the bridge's capture time, exactly as for a datapoint
|
|
99
|
-
*
|
|
94
|
+
* `timestamp_ms` is the bridge's capture time, exactly as for a datapoint —
|
|
95
|
+
* action feedback carries it too — so a client computes the age
|
|
100
96
|
* of a progress report the same way it computes the age of a sensor value,
|
|
101
97
|
* and a burst of late-delivered feedback after a reconnect is visibly late
|
|
102
98
|
* rather than looking current.
|
|
@@ -113,15 +109,14 @@ export const jobEvent = z.object({
|
|
|
113
109
|
timestamp_ms: z.number().int().nonnegative(),
|
|
114
110
|
});
|
|
115
111
|
/**
|
|
116
|
-
* What a busy refusal tells the caller
|
|
117
|
-
*
|
|
118
|
-
* to wait or to give up.
|
|
112
|
+
* What a busy refusal tells the caller: what is already running. A refusal that
|
|
113
|
+
* only says "busy" forces the caller to guess whether to wait or to give up.
|
|
119
114
|
*/
|
|
120
115
|
export const busyDetails = z.object({
|
|
121
116
|
running: job,
|
|
122
117
|
});
|
|
123
118
|
/**
|
|
124
|
-
* What a `publisher_busy` refusal tells the caller
|
|
119
|
+
* What a `publisher_busy` refusal tells the caller.
|
|
125
120
|
*
|
|
126
121
|
* "Another caller is publishing and has not been quiet long enough" names a
|
|
127
122
|
* state and no action: the caller does not know how much longer, because
|
|
@@ -140,7 +135,7 @@ export const publisherBusyDetails = z.object({
|
|
|
140
135
|
retry_after_ms: z.number().int().nonnegative(),
|
|
141
136
|
});
|
|
142
137
|
/**
|
|
143
|
-
* What a `job_queue_full` refusal tells the caller
|
|
138
|
+
* What a `job_queue_full` refusal tells the caller.
|
|
144
139
|
*
|
|
145
140
|
* Both numbers, not just the limit: `limit` alone says how big the queue is
|
|
146
141
|
* and nothing about whether waiting will help, and `queued` alone cannot be
|
package/dist/mcp.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
1
2
|
import { z } from 'zod';
|
|
2
3
|
/**
|
|
3
|
-
* The MCP server
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* described were deleted by the org-central identity redesign (D5/D6).
|
|
4
|
+
* The MCP server: **one** remote MCP endpoint for the whole platform, whose
|
|
5
|
+
* tools are the exposed services and datapoints the signed-in user's roles
|
|
6
|
+
* permit. There is no per-app endpoint.
|
|
7
7
|
*
|
|
8
8
|
* **This file describes the seam, not the protocol.** The MCP messages
|
|
9
9
|
* themselves (`initialize`, `tools/list`, `tools/call`) are defined by the
|
|
@@ -15,8 +15,8 @@ import { z } from 'zod';
|
|
|
15
15
|
* an end user ever connects.
|
|
16
16
|
*/
|
|
17
17
|
/**
|
|
18
|
-
* The protocol revision
|
|
19
|
-
*
|
|
18
|
+
* The protocol revision this server speaks, chosen over the newer
|
|
19
|
+
* `2026-07-28`.
|
|
20
20
|
*
|
|
21
21
|
* This is the latest revision the **stable** MCP TypeScript SDK ships, and it
|
|
22
22
|
* negotiates down to `2024-11-05`, so it covers the AI tools that exist today.
|
|
@@ -24,11 +24,10 @@ import { z } from 'zod';
|
|
|
24
24
|
* Streamable HTTP's session ids, the standalone SSE channel and resumability,
|
|
25
25
|
* and servers speaking only it answer `405` to GET and DELETE.
|
|
26
26
|
*
|
|
27
|
-
* **Which is why this server is stateless anyway.** Building sessions
|
|
28
|
-
*
|
|
29
|
-
* session map is
|
|
30
|
-
*
|
|
31
|
-
* (`max_realtime_connections`), and W8 is where a second instance appears.
|
|
27
|
+
* **Which is why this server is stateless anyway.** Building sessions that a
|
|
28
|
+
* later revision removes is work in the wrong direction, and a per-process
|
|
29
|
+
* session map is an assumption that breaks the moment a second cloud instance
|
|
30
|
+
* exists.
|
|
32
31
|
*/
|
|
33
32
|
export declare const MCP_PROTOCOL_VERSION: "2025-11-25";
|
|
34
33
|
/**
|
|
@@ -144,7 +143,7 @@ export declare const MCP_TOOL_NAME_MAX = 128;
|
|
|
144
143
|
export declare const mcpToolNamePattern: RegExp;
|
|
145
144
|
/**
|
|
146
145
|
* One exposure of a robot, as `robot_describe` and the console's per-role
|
|
147
|
-
* preview list it
|
|
146
|
+
* preview list it. Every exposure the role grants is listed —
|
|
148
147
|
* a missing `description` is shown as `null`, never used to hide the entry.
|
|
149
148
|
*
|
|
150
149
|
* `input_schema` is a JSON Schema document generated from an action's,
|
package/dist/mcp.js
CHANGED
|
@@ -2,10 +2,9 @@
|
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
import { slug } from './common.js';
|
|
4
4
|
/**
|
|
5
|
-
* The MCP server
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* described were deleted by the org-central identity redesign (D5/D6).
|
|
5
|
+
* The MCP server: **one** remote MCP endpoint for the whole platform, whose
|
|
6
|
+
* tools are the exposed services and datapoints the signed-in user's roles
|
|
7
|
+
* permit. There is no per-app endpoint.
|
|
9
8
|
*
|
|
10
9
|
* **This file describes the seam, not the protocol.** The MCP messages
|
|
11
10
|
* themselves (`initialize`, `tools/list`, `tools/call`) are defined by the
|
|
@@ -17,8 +16,8 @@ import { slug } from './common.js';
|
|
|
17
16
|
* an end user ever connects.
|
|
18
17
|
*/
|
|
19
18
|
/**
|
|
20
|
-
* The protocol revision
|
|
21
|
-
*
|
|
19
|
+
* The protocol revision this server speaks, chosen over the newer
|
|
20
|
+
* `2026-07-28`.
|
|
22
21
|
*
|
|
23
22
|
* This is the latest revision the **stable** MCP TypeScript SDK ships, and it
|
|
24
23
|
* negotiates down to `2024-11-05`, so it covers the AI tools that exist today.
|
|
@@ -26,11 +25,10 @@ import { slug } from './common.js';
|
|
|
26
25
|
* Streamable HTTP's session ids, the standalone SSE channel and resumability,
|
|
27
26
|
* and servers speaking only it answer `405` to GET and DELETE.
|
|
28
27
|
*
|
|
29
|
-
* **Which is why this server is stateless anyway.** Building sessions
|
|
30
|
-
*
|
|
31
|
-
* session map is
|
|
32
|
-
*
|
|
33
|
-
* (`max_realtime_connections`), and W8 is where a second instance appears.
|
|
28
|
+
* **Which is why this server is stateless anyway.** Building sessions that a
|
|
29
|
+
* later revision removes is work in the wrong direction, and a per-process
|
|
30
|
+
* session map is an assumption that breaks the moment a second cloud instance
|
|
31
|
+
* exists.
|
|
34
32
|
*/
|
|
35
33
|
export const MCP_PROTOCOL_VERSION = '2025-11-25';
|
|
36
34
|
/**
|
|
@@ -97,7 +95,7 @@ export const MCP_TOOL_NAME_MAX = 128;
|
|
|
97
95
|
export const mcpToolNamePattern = /^[a-z0-9][a-z0-9_-]*$/;
|
|
98
96
|
/**
|
|
99
97
|
* One exposure of a robot, as `robot_describe` and the console's per-role
|
|
100
|
-
* preview list it
|
|
98
|
+
* preview list it. Every exposure the role grants is listed —
|
|
101
99
|
* a missing `description` is shown as `null`, never used to hide the entry.
|
|
102
100
|
*
|
|
103
101
|
* `input_schema` is a JSON Schema document generated from an action's,
|
package/dist/oauth.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
1
2
|
import { z } from 'zod';
|
|
2
3
|
/**
|
|
3
4
|
* **OAuth 2.1, and it remains only for MCP** (2026-09-05 app-user-auth, D8).
|
|
@@ -82,15 +83,11 @@ export type OauthError = z.infer<typeof oauthError>;
|
|
|
82
83
|
/**
|
|
83
84
|
* A redirect URI, and the rule is stricter than "a URL".
|
|
84
85
|
*
|
|
85
|
-
* **The defence
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
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.
|
|
86
|
+
* **The same defence is written down elsewhere in this package.** `config.ts`
|
|
87
|
+
* validates a capture-device path from the wire with a prefix rule *and* an
|
|
88
|
+
* explicit refusal of `..` segments. A redirect URI is the same shape of
|
|
89
|
+
* problem from a less trusted source: an attacker-supplied string that decides
|
|
90
|
+
* where a credential is sent.
|
|
94
91
|
*
|
|
95
92
|
* Matching at the server is **exact string comparison against a registered
|
|
96
93
|
* value** — never a prefix, never a wildcard host, never "starts with". A
|
|
@@ -124,7 +121,7 @@ export declare const MCP_DCR_MAX_REDIRECT_URIS = 5;
|
|
|
124
121
|
* authorization servers understand**, central and per-app.
|
|
125
122
|
*
|
|
126
123
|
* **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
|
|
124
|
+
* than a gap in it.** RFC 7591 §3.1 obliges a registration endpoint to ignore metadata
|
|
128
125
|
* it does not understand, and real MCP clients send `client_uri`, `logo_uri`,
|
|
129
126
|
* `software_id` and `contacts`. A strict shape here would describe a `400`
|
|
130
127
|
* that no conforming client ever earns, and would take the whole
|
|
@@ -132,9 +129,8 @@ export declare const MCP_DCR_MAX_REDIRECT_URIS = 5;
|
|
|
132
129
|
* are therefore stripped by this schema and ignored by the server, which is
|
|
133
130
|
* the same answer said twice.
|
|
134
131
|
*
|
|
135
|
-
* **The server still reads the body field by field
|
|
136
|
-
*
|
|
137
|
-
* not the shape: §3.2.2 distinguishes `invalid_redirect_uri` from
|
|
132
|
+
* **The server still reads the body field by field**, and the reason is the
|
|
133
|
+
* error vocabulary, not the shape: RFC 7591 §3.2.2 distinguishes `invalid_redirect_uri` from
|
|
138
134
|
* `invalid_client_metadata`, and one `safeParse` failure cannot say which of
|
|
139
135
|
* the two a caller earned. So this schema is what the endpoint *accepts*, and
|
|
140
136
|
* the handler is what turns a miss into the right RFC code.
|
|
@@ -262,11 +258,10 @@ export type AuthorizationServerMetadata = z.infer<typeof authorizationServerMeta
|
|
|
262
258
|
/**
|
|
263
259
|
* RFC 9728 — what a *resource* publishes about who may authorize for it.
|
|
264
260
|
*
|
|
265
|
-
*
|
|
266
|
-
*
|
|
267
|
-
*
|
|
268
|
-
*
|
|
269
|
-
* the rejection rather than the presence of the claim.
|
|
261
|
+
* Tokens are minted bound to a resource. **A minting mechanism with no
|
|
262
|
+
* validator is a check that cannot fail**, so the resource itself *rejects* a
|
|
263
|
+
* token whose audience names something else, and that rejection is what a check
|
|
264
|
+
* measures rather than the presence of the claim.
|
|
270
265
|
*/
|
|
271
266
|
export declare const protectedResourceMetadata: z.ZodObject<{
|
|
272
267
|
resource: z.ZodURL;
|
package/dist/oauth.js
CHANGED
|
@@ -89,15 +89,11 @@ export const oauthError = z.object({
|
|
|
89
89
|
/**
|
|
90
90
|
* A redirect URI, and the rule is stricter than "a URL".
|
|
91
91
|
*
|
|
92
|
-
* **The defence
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
* not the traversal but that **the rule existed one file over and was never
|
|
98
|
-
* carried across.** A redirect URI is the same shape of problem from a less
|
|
99
|
-
* trusted source: an attacker-supplied string that decides where a credential
|
|
100
|
-
* is sent.
|
|
92
|
+
* **The same defence is written down elsewhere in this package.** `config.ts`
|
|
93
|
+
* validates a capture-device path from the wire with a prefix rule *and* an
|
|
94
|
+
* explicit refusal of `..` segments. A redirect URI is the same shape of
|
|
95
|
+
* problem from a less trusted source: an attacker-supplied string that decides
|
|
96
|
+
* where a credential is sent.
|
|
101
97
|
*
|
|
102
98
|
* Matching at the server is **exact string comparison against a registered
|
|
103
99
|
* value** — never a prefix, never a wildcard host, never "starts with". A
|
|
@@ -133,7 +129,7 @@ export const redirectUri = z
|
|
|
133
129
|
// yields `"["` for `[::1]:8080`, because an IPv6 literal is *made of*
|
|
134
130
|
// colons. So `[::1]` never matched the allow-list it is named in, in any
|
|
135
131
|
// spelling, while two developer-facing messages went on saying it was
|
|
136
|
-
// permitted.
|
|
132
|
+
// permitted.
|
|
137
133
|
// `hostname` already strips the port and keeps the brackets.
|
|
138
134
|
if (url.protocol === 'http:')
|
|
139
135
|
return ['localhost', '127.0.0.1', '[::1]'].includes(url.hostname);
|
|
@@ -162,7 +158,7 @@ export const MCP_DCR_MAX_REDIRECT_URIS = 5;
|
|
|
162
158
|
* authorization servers understand**, central and per-app.
|
|
163
159
|
*
|
|
164
160
|
* **Not `.strict()`, and that is the schema agreeing with the server rather
|
|
165
|
-
* than a gap in it.** §3.1 obliges a registration endpoint to ignore metadata
|
|
161
|
+
* than a gap in it.** RFC 7591 §3.1 obliges a registration endpoint to ignore metadata
|
|
166
162
|
* it does not understand, and real MCP clients send `client_uri`, `logo_uri`,
|
|
167
163
|
* `software_id` and `contacts`. A strict shape here would describe a `400`
|
|
168
164
|
* that no conforming client ever earns, and would take the whole
|
|
@@ -170,9 +166,8 @@ export const MCP_DCR_MAX_REDIRECT_URIS = 5;
|
|
|
170
166
|
* are therefore stripped by this schema and ignored by the server, which is
|
|
171
167
|
* the same answer said twice.
|
|
172
168
|
*
|
|
173
|
-
* **The server still reads the body field by field
|
|
174
|
-
*
|
|
175
|
-
* not the shape: §3.2.2 distinguishes `invalid_redirect_uri` from
|
|
169
|
+
* **The server still reads the body field by field**, and the reason is the
|
|
170
|
+
* error vocabulary, not the shape: RFC 7591 §3.2.2 distinguishes `invalid_redirect_uri` from
|
|
176
171
|
* `invalid_client_metadata`, and one `safeParse` failure cannot say which of
|
|
177
172
|
* the two a caller earned. So this schema is what the endpoint *accepts*, and
|
|
178
173
|
* the handler is what turns a miss into the right RFC code.
|
|
@@ -359,11 +354,10 @@ export const authorizationServerMetadata = z.object({
|
|
|
359
354
|
/**
|
|
360
355
|
* RFC 9728 — what a *resource* publishes about who may authorize for it.
|
|
361
356
|
*
|
|
362
|
-
*
|
|
363
|
-
*
|
|
364
|
-
*
|
|
365
|
-
*
|
|
366
|
-
* the rejection rather than the presence of the claim.
|
|
357
|
+
* Tokens are minted bound to a resource. **A minting mechanism with no
|
|
358
|
+
* validator is a check that cannot fail**, so the resource itself *rejects* a
|
|
359
|
+
* token whose audience names something else, and that rejection is what a check
|
|
360
|
+
* measures rather than the presence of the claim.
|
|
367
361
|
*/
|
|
368
362
|
export const protectedResourceMetadata = z.object({
|
|
369
363
|
resource: z.url().meta({
|