@mantyx/sdk 0.10.1 → 0.11.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.
- package/CHANGELOG.md +7 -0
- package/dist/a2a-server.cjs +9 -0
- package/dist/a2a-server.cjs.map +1 -1
- package/dist/a2a-server.d.cts +1 -1
- package/dist/a2a-server.d.ts +1 -1
- package/dist/a2a-server.js +1 -1
- package/dist/{chunk-XMUCELMH.js → chunk-DR625E6B.js} +69 -9
- package/dist/chunk-DR625E6B.js.map +1 -0
- package/dist/{client-CZUVldDx.d.cts → client-Byb0Zdo7.d.cts} +155 -1
- package/dist/{client-CZUVldDx.d.ts → client-Byb0Zdo7.d.ts} +155 -1
- package/dist/index.cjs +69 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/docs/agent-runs-protocol.md +373 -220
- package/docs/wire-protocol.md +415 -252
- package/package.json +1 -1
- package/dist/chunk-XMUCELMH.js.map +0 -1
- package/docs/oauth.md +0 -356
package/docs/oauth.md
DELETED
|
@@ -1,356 +0,0 @@
|
|
|
1
|
-
# OAuth 2.0 in MANTYX
|
|
2
|
-
|
|
3
|
-
MANTYX exposes an OAuth 2.0 authorization server at **`/api/oauth/...`** that
|
|
4
|
-
issues access tokens accepted on every existing API surface
|
|
5
|
-
(`/api/v1`, `/api/a2a`, `/mcp`) plus a small identity endpoint
|
|
6
|
-
(`/api/oauth/userinfo`).
|
|
7
|
-
|
|
8
|
-
OAuth tokens are a **drop-in alternative to workspace API keys** — same
|
|
9
|
-
HTTP contract, same `Authorization: Bearer …` header, same per-agent
|
|
10
|
-
allowlist semantics. The only thing that changes is that the token
|
|
11
|
-
carries **scopes**: per-route permissions you grant at consent time
|
|
12
|
-
instead of the coarse `ApiKeyUsage` (`mcp` | `developer_api` | `a2a`)
|
|
13
|
-
on classic workspace API keys.
|
|
14
|
-
|
|
15
|
-
> See `architecture.md` for the full request pipeline and where the
|
|
16
|
-
> bearer resolver sits in it.
|
|
17
|
-
|
|
18
|
-
## When to use OAuth vs. an API key
|
|
19
|
-
|
|
20
|
-
* **Personal scripts and internal tools you control end-to-end** — keep
|
|
21
|
-
using a workspace API key. It's one click to issue, one header to set.
|
|
22
|
-
* **Apps that other people sign in to** — register an OAuth application
|
|
23
|
-
and run the Authorization Code + PKCE flow. End users approve specific
|
|
24
|
-
scopes for a specific workspace. Two visibility modes:
|
|
25
|
-
* **Private** — locked to the workspace that registered the app. Only
|
|
26
|
-
members of that workspace can authorize. Optionally enable
|
|
27
|
-
`client_credentials` for unattended machine-to-machine traffic.
|
|
28
|
-
* **Public** — any user can authorize the app and pick the workspace
|
|
29
|
-
they want to grant access to on the consent screen.
|
|
30
|
-
|
|
31
|
-
## High-level flow
|
|
32
|
-
|
|
33
|
-
```mermaid
|
|
34
|
-
sequenceDiagram
|
|
35
|
-
participant App as Your app
|
|
36
|
-
participant User as End user
|
|
37
|
-
participant Web as MANTYX SPA
|
|
38
|
-
participant API as MANTYX API
|
|
39
|
-
App->>User: Open /oauth/authorize?...
|
|
40
|
-
User->>Web: Sign in (if needed) and review scopes
|
|
41
|
-
Web->>API: POST /api/oauth/authorize/decide (approve)
|
|
42
|
-
API->>App: 302 redirect_uri?code=…&state=…
|
|
43
|
-
App->>API: POST /api/oauth/token (code + verifier)
|
|
44
|
-
API->>App: { access_token, refresh_token, scope, expires_in }
|
|
45
|
-
App->>API: GET /api/v1/workspaces/{slug}/agents (Bearer token)
|
|
46
|
-
API->>App: 200 OK (scope check passes)
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
## Registering an application
|
|
50
|
-
|
|
51
|
-
Open **Developer → OAuth apps** (workspace admins only). Both private and
|
|
52
|
-
public apps are registered from this page; the Visibility radio decides
|
|
53
|
-
which flow you get.
|
|
54
|
-
|
|
55
|
-
Provide:
|
|
56
|
-
|
|
57
|
-
* **Name** and **description** (shown on the consent screen).
|
|
58
|
-
* **Logo URL** (optional).
|
|
59
|
-
* **Visibility** — **Private** locks tokens to this workspace; **Public**
|
|
60
|
-
lets any signed-in user pick a workspace at consent time.
|
|
61
|
-
* **Redirect URIs** — at least one. Allowed schemes:
|
|
62
|
-
* `https://…`
|
|
63
|
-
* `http://localhost`, `http://127.0.0.1`, or `http://[::1]` (any port,
|
|
64
|
-
any path)
|
|
65
|
-
* Custom schemes for native apps, e.g. `myapp://callback`.
|
|
66
|
-
* **Allowed scopes** — only scopes you check here can be requested by
|
|
67
|
-
the application at consent time.
|
|
68
|
-
* **Client secret** — every MANTYX OAuth app is a **confidential
|
|
69
|
-
client**. The `client_secret` is returned **once** on creation; the
|
|
70
|
-
`/token`, `/revoke`, and `/introspect` endpoints all require the
|
|
71
|
-
matching value. We do not support PKCE-only public-client
|
|
72
|
-
registrations — visibility (private vs. public) only controls *who*
|
|
73
|
-
can authorize the app, not whether the app keeps a secret. PKCE is
|
|
74
|
-
still mandatory on top of the secret for defense in depth (see
|
|
75
|
-
below).
|
|
76
|
-
* **Allow `client_credentials` grant** *(private apps only)* — for
|
|
77
|
-
unattended machine-to-machine use; not available on public apps
|
|
78
|
-
because the token has to be bound to a single workspace at mint time.
|
|
79
|
-
|
|
80
|
-
The `client_id` is `mantyx_oa_<id>`. Confidential client secrets are
|
|
81
|
-
`mantyx_oas_<secret>`.
|
|
82
|
-
|
|
83
|
-
OAuth applications require the **`oauthApps`** feature on the registering
|
|
84
|
-
workspace's tier (mirrors the existing `apiKeys` plan check). For public
|
|
85
|
-
apps the same gate is also applied to the workspace each end user picks
|
|
86
|
-
at consent time, so a free workspace can't host paid features through
|
|
87
|
-
a public app authorized for it.
|
|
88
|
-
|
|
89
|
-
## Authorization Code + PKCE (browser, native, server-side)
|
|
90
|
-
|
|
91
|
-
Every grant carries **two** client-binding factors:
|
|
92
|
-
|
|
93
|
-
* `client_secret` — proves the registered client made the call (every
|
|
94
|
-
MANTYX OAuth app is confidential, so this is always required).
|
|
95
|
-
* PKCE `code_verifier` — proves the same browser session that started
|
|
96
|
-
`/authorize` is finishing the exchange. We accept only `S256` and
|
|
97
|
-
reject any token request without a verifier.
|
|
98
|
-
|
|
99
|
-
1. Generate a high-entropy `code_verifier` (43–128 chars, RFC 7636).
|
|
100
|
-
2. Compute `code_challenge = base64url(sha256(code_verifier))` (no
|
|
101
|
-
padding).
|
|
102
|
-
3. Send the user to:
|
|
103
|
-
|
|
104
|
-
```text
|
|
105
|
-
GET /api/oauth/authorize
|
|
106
|
-
?client_id=mantyx_oa_…
|
|
107
|
-
&redirect_uri=<exact registered URI>
|
|
108
|
-
&response_type=code
|
|
109
|
-
&scope=mantyx.identity:read+agents:read+runs:write
|
|
110
|
-
&state=<random per-session token>
|
|
111
|
-
&code_challenge=<S256 challenge>
|
|
112
|
-
&code_challenge_method=S256
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
The MANTYX SPA at `/oauth/authorize` reads the same query, asks the
|
|
116
|
-
user to log in if needed, lets them pick the workspace (third-party
|
|
117
|
-
apps), pick the agent allow-list (when any of `agents:invoke`,
|
|
118
|
-
`runs:write`, `a2a:invoke`, `mcp:connect` are requested), and then
|
|
119
|
-
approves or denies.
|
|
120
|
-
|
|
121
|
-
4. On approve we redirect to:
|
|
122
|
-
|
|
123
|
-
```text
|
|
124
|
-
<redirect_uri>?code=<auth code>&state=<your state>
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
On deny:
|
|
128
|
-
|
|
129
|
-
```text
|
|
130
|
-
<redirect_uri>?error=access_denied&state=<your state>
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
5. Exchange the code:
|
|
134
|
-
|
|
135
|
-
```http
|
|
136
|
-
POST /api/oauth/token
|
|
137
|
-
Content-Type: application/x-www-form-urlencoded
|
|
138
|
-
|
|
139
|
-
grant_type=authorization_code
|
|
140
|
-
&code=…
|
|
141
|
-
&redirect_uri=<exact same URI>
|
|
142
|
-
&client_id=mantyx_oa_…
|
|
143
|
-
&client_secret=mantyx_oas_…
|
|
144
|
-
&code_verifier=<original verifier>
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
Response:
|
|
148
|
-
|
|
149
|
-
```json
|
|
150
|
-
{
|
|
151
|
-
"access_token": "mantyx_at_…",
|
|
152
|
-
"token_type": "Bearer",
|
|
153
|
-
"expires_in": 3600,
|
|
154
|
-
"refresh_token": "mantyx_rt_…",
|
|
155
|
-
"scope": "mantyx.identity:read agents:read runs:write"
|
|
156
|
-
}
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
6. Use the access token like any other workspace bearer:
|
|
160
|
-
|
|
161
|
-
```http
|
|
162
|
-
GET /api/v1/workspaces/<slug>/agents
|
|
163
|
-
Authorization: Bearer mantyx_at_…
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
`<slug>` must be the workspace the consent was for. OAuth tokens
|
|
167
|
-
issued for workspace A return **403** on `/api/v1/workspaces/B/...`.
|
|
168
|
-
|
|
169
|
-
7. **Token lifetimes.**
|
|
170
|
-
|
|
171
|
-
* Access tokens live **1 hour** (`expires_in: 3600`).
|
|
172
|
-
* Refresh tokens are **persistent and non-rotating**: they never
|
|
173
|
-
time-expire. They stop working only when the application access
|
|
174
|
-
is explicitly revoked via `/oauth/revoke` (with the refresh
|
|
175
|
-
token), `DELETE /api/oauth/grants/:id`, or deletion of the
|
|
176
|
-
OAuth application itself.
|
|
177
|
-
* Calling `grant_type=refresh_token` mints a brand-new short-lived
|
|
178
|
-
access token and **echoes back the same refresh token** the
|
|
179
|
-
client already holds. The previous access tokens are **not**
|
|
180
|
-
revoked — multiple backend workers can mint live access tokens
|
|
181
|
-
off a shared refresh without invalidating each other's chains.
|
|
182
|
-
|
|
183
|
-
```http
|
|
184
|
-
POST /api/oauth/token
|
|
185
|
-
|
|
186
|
-
grant_type=refresh_token
|
|
187
|
-
&refresh_token=mantyx_rt_…
|
|
188
|
-
&client_id=mantyx_oa_…
|
|
189
|
-
&client_secret=mantyx_oas_…
|
|
190
|
-
&scope=runs:write # optional narrowing (must be a subset)
|
|
191
|
-
```
|
|
192
|
-
|
|
193
|
-
```json
|
|
194
|
-
{
|
|
195
|
-
"access_token": "mantyx_at_…",
|
|
196
|
-
"token_type": "Bearer",
|
|
197
|
-
"expires_in": 3600,
|
|
198
|
-
"refresh_token": "<same value the client just sent>",
|
|
199
|
-
"scope": "runs:write"
|
|
200
|
-
}
|
|
201
|
-
```
|
|
202
|
-
|
|
203
|
-
Clients should persist the refresh token once at first sign-in
|
|
204
|
-
(treat it as long-lived) and only refresh the access token from
|
|
205
|
-
it as needed.
|
|
206
|
-
|
|
207
|
-
8. **Revoke (RFC 7009).**
|
|
208
|
-
|
|
209
|
-
```http
|
|
210
|
-
POST /api/oauth/revoke
|
|
211
|
-
|
|
212
|
-
token=<access or refresh token>
|
|
213
|
-
&client_id=mantyx_oa_…
|
|
214
|
-
&client_secret=mantyx_oas_…
|
|
215
|
-
```
|
|
216
|
-
|
|
217
|
-
Always returns `200`, even when the token is unknown — by design.
|
|
218
|
-
|
|
219
|
-
* Revoking an **access token** kills only that single access
|
|
220
|
-
token. Other access tokens minted from the same refresh keep
|
|
221
|
-
working until they expire (or until the refresh is revoked).
|
|
222
|
-
* Revoking a **refresh token** kills the refresh and *every* live
|
|
223
|
-
access token tied to its grant in one shot.
|
|
224
|
-
|
|
225
|
-
## Client credentials (private workspace apps, machine-to-machine)
|
|
226
|
-
|
|
227
|
-
Private workspace applications with `allowsClientCredentials: true` can
|
|
228
|
-
request a token without a user:
|
|
229
|
-
|
|
230
|
-
```http
|
|
231
|
-
POST /api/oauth/token
|
|
232
|
-
|
|
233
|
-
grant_type=client_credentials
|
|
234
|
-
&client_id=mantyx_oa_…
|
|
235
|
-
&client_secret=mantyx_oas_…
|
|
236
|
-
&scope=runs:write+agents:invoke # optional, must be a subset of allowedScopes
|
|
237
|
-
```
|
|
238
|
-
|
|
239
|
-
The token's `tenantId` is the application's owning workspace and its
|
|
240
|
-
agent allow-list defaults to every non-system agent in that workspace
|
|
241
|
-
(no end-user consent screen). Use this for cron jobs, internal services
|
|
242
|
-
and partner integrations where there is no end user.
|
|
243
|
-
|
|
244
|
-
## "Sign in with MANTYX"
|
|
245
|
-
|
|
246
|
-
There is **no OIDC** today. The access token is enough:
|
|
247
|
-
|
|
248
|
-
1. Run the auth-code + PKCE flow with `scope=mantyx.identity:read`.
|
|
249
|
-
2. Call:
|
|
250
|
-
|
|
251
|
-
```http
|
|
252
|
-
GET /api/oauth/userinfo
|
|
253
|
-
Authorization: Bearer mantyx_at_…
|
|
254
|
-
```
|
|
255
|
-
|
|
256
|
-
Response:
|
|
257
|
-
|
|
258
|
-
```json
|
|
259
|
-
{
|
|
260
|
-
"sub": "<user id>",
|
|
261
|
-
"email": "user@example.com",
|
|
262
|
-
"workspace": { "id": "…", "slug": "…", "name": "…" }
|
|
263
|
-
}
|
|
264
|
-
```
|
|
265
|
-
|
|
266
|
-
`/api/auth/me` continues to accept the user's web JWT as before; both
|
|
267
|
-
paths can be used to bootstrap session info for "Sign in with MANTYX"
|
|
268
|
-
clients.
|
|
269
|
-
|
|
270
|
-
## Scope catalog
|
|
271
|
-
|
|
272
|
-
Defined in `packages/api/src/oauth/scopes.ts` and mirrored by the SPA
|
|
273
|
-
in `packages/web/src/lib/oauthScopes.ts`. The catalog is also expressed
|
|
274
|
-
in the OpenAPI spec at `packages/api/openapi/developer-v1.yaml`.
|
|
275
|
-
|
|
276
|
-
| Scope | Purpose |
|
|
277
|
-
| --- | --- |
|
|
278
|
-
| `mantyx.identity:read` | `/api/oauth/userinfo` and `/api/auth/me`. |
|
|
279
|
-
| `agents:read` | `GET /api/v1/.../agents`. |
|
|
280
|
-
| `agents:write` | Reserved for future agent CRUD on the Developer API. |
|
|
281
|
-
| `agents:invoke` | Run an agent — required by ephemeral runs, agent sessions, A2A invoke. |
|
|
282
|
-
| `sessions:read` / `sessions:write` | Ephemeral SDK agent sessions. |
|
|
283
|
-
| `runs:read` / `runs:write` | Read run snapshots and SSE streams; start, cancel, submit tool-results. |
|
|
284
|
-
| `models:read` | `GET /api/v1/.../models`. |
|
|
285
|
-
| `tools:read` / `tools:write` | List/manage workspace tools. |
|
|
286
|
-
| `schedules:read` / `schedules:write` | List/manage cron schedules and trigger them manually. |
|
|
287
|
-
| `inbounds:read` / `inbounds:write` | List/manage inbound webhooks/email configs. |
|
|
288
|
-
| `plugins:read` | List installed plugins for the workspace. |
|
|
289
|
-
| `hive:read` / `hive:write` | Workspace Hive objects. |
|
|
290
|
-
| `a2a:discovery` | `GET /api/a2a/{slug}/discovery`. |
|
|
291
|
-
| `a2a:invoke` | Send Agent2Agent JSON-RPC requests (also requires `agents:invoke`). |
|
|
292
|
-
| `mcp:connect` | Open MCP Streamable HTTP sessions. |
|
|
293
|
-
|
|
294
|
-
`runs:write`, `agents:invoke`, `a2a:invoke`, and `mcp:connect` participate
|
|
295
|
-
in the **agent allow-list** that the consent screen surfaces. An empty
|
|
296
|
-
list expands to "every non-system agent in the workspace" at request
|
|
297
|
-
time — same semantics as today's `WorkspaceApiKey.agentIds`.
|
|
298
|
-
|
|
299
|
-
## Redirect URI rules
|
|
300
|
-
|
|
301
|
-
* Exact-match comparison (case-sensitive scheme/host, fragment-stripped).
|
|
302
|
-
Trailing slashes are significant.
|
|
303
|
-
* Loopback HTTP is allowed without TLS for localhost development.
|
|
304
|
-
* Custom schemes are allowed for native apps; pick a scheme you control
|
|
305
|
-
(e.g. `com.example.myapp://callback`).
|
|
306
|
-
* `redirect_uri` on `/api/oauth/token` must equal the value used at
|
|
307
|
-
`/api/oauth/authorize`.
|
|
308
|
-
|
|
309
|
-
## Error model
|
|
310
|
-
|
|
311
|
-
| Where | Body |
|
|
312
|
-
| --- | --- |
|
|
313
|
-
| `/authorize` query validation | `{ "error": "Invalid authorize request", "details": {...} }` |
|
|
314
|
-
| Unknown / unauthorized client | `401 { "error": "invalid_client" }` |
|
|
315
|
-
| PKCE failure, expired/used code, redirect mismatch | `400 { "error": "invalid_grant" }` |
|
|
316
|
-
| Insufficient scope on a Developer API call | `403 { "error": "insufficient_scope", "required": ["..."] }` |
|
|
317
|
-
| Wrong workspace in URL | `403 { "error": "wrong_workspace", "correctSlug": "..." }` |
|
|
318
|
-
| Plan does not include OAuth apps | `403 { "error": "...", "code": "oauth_apps_plan" }` |
|
|
319
|
-
|
|
320
|
-
## Token format
|
|
321
|
-
|
|
322
|
-
* Access tokens: `mantyx_at_<32-byte url-safe random>`.
|
|
323
|
-
* Refresh tokens: `mantyx_rt_<32-byte url-safe random>`.
|
|
324
|
-
* Client ids: `mantyx_oa_<id>`.
|
|
325
|
-
* Client secrets: `mantyx_oas_<secret>`.
|
|
326
|
-
|
|
327
|
-
Stored as **SHA-256 with HMAC** (rate-friendly), with a 12-character
|
|
328
|
-
prefix index for fast lookups (mirrors today's
|
|
329
|
-
`WorkspaceApiKey.keyPrefix`).
|
|
330
|
-
|
|
331
|
-
## Token lifetimes & lifecycle
|
|
332
|
-
|
|
333
|
-
| Token | Lifetime | How it ends |
|
|
334
|
-
| --- | --- | --- |
|
|
335
|
-
| **Access token** | 1 hour (`expires_in: 3600`). | Time-expires; or revoked via `/oauth/revoke`, refresh-token revocation, grant deletion, or app deletion. |
|
|
336
|
-
| **Refresh token** | **No time-based expiry** — persistent. | Revoked via `/oauth/revoke` (refresh token), `DELETE /api/oauth/grants/:id` (user "Revoke access" action), or deletion of the OAuth application. |
|
|
337
|
-
| **Authorization code** | 10 minutes, single-use. | Consumed by `/oauth/token` (auth-code grant) or expires. |
|
|
338
|
-
|
|
339
|
-
Refresh tokens are **non-rotating**. Calling
|
|
340
|
-
`grant_type=refresh_token` issues a new short-lived access token but
|
|
341
|
-
returns the **same refresh token** the client already holds. Multiple
|
|
342
|
-
backend workers may refresh concurrently using the same shared
|
|
343
|
-
refresh token without invalidating each other's chains.
|
|
344
|
-
|
|
345
|
-
This makes refresh tokens the long-lived authorization-of-record:
|
|
346
|
-
clients should persist them once at first sign-in (encrypted at rest)
|
|
347
|
-
and treat the refresh value as the single trust anchor for the grant.
|
|
348
|
-
|
|
349
|
-
## See also
|
|
350
|
-
|
|
351
|
-
* `packages/api/src/routes/oauth.ts` — authorization server endpoints.
|
|
352
|
-
* `packages/api/src/services/bearer-credential.ts` — unified resolver
|
|
353
|
-
for API keys and OAuth tokens.
|
|
354
|
-
* `packages/api/src/middleware/oauth-scope.ts` — `requireScope(...)`.
|
|
355
|
-
* `packages/api/openapi/developer-v1.yaml` — `securitySchemes.oauth2`
|
|
356
|
-
and per-operation scope lists.
|