@plitzi/sdk-shared 0.32.11 → 0.32.12

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 CHANGED
@@ -1,5 +1,13 @@
1
1
  # @plitzi/sdk-shared
2
2
 
3
+ ## 0.32.12
4
+
5
+ ### Patch Changes
6
+
7
+ - v0.32.12
8
+ - Updated dependencies
9
+ - @plitzi/nexus@0.32.12
10
+
3
11
  ## 0.32.11
4
12
 
5
13
  ### Patch Changes
@@ -217,6 +217,9 @@ export type ServerRequestLogEvent = ServerLogEventBase & {
217
217
  operation?: string;
218
218
  /** The HTTP status the server answered with. */
219
219
  status: number;
220
+ /** The client the request came from, resolved through the proxies the deployment sits behind. Empty when no
221
+ * address could be determined. It IS personal data — see the note on {@link ServerLogEvent}. */
222
+ clientIp?: string;
220
223
  };
221
224
  /** One plitzi_* tool call inside an MCP request. Reported separately from the request because a failing tool
222
225
  * still answers HTTP 200 — the failure lives in the JSON-RPC payload, so only this event exposes it. */
@@ -238,10 +241,10 @@ export type McpResourceLogEvent = ServerLogEventBase & {
238
241
  * the MCP tool calls and resource reads that happen inside them. Wire a single sink via `SSRServerConfig.logger`
239
242
  * and switch on `kind` — a consumer can render it, ship it to a dashboard or drop the kinds it does not want.
240
243
  *
241
- * PII-free by construction: no headers, cookies, tokens, request body nor client IP ever reach an event, query
242
- * values are stripped from paths and tool arguments are reduced to their shape. The request path itself is kept
243
- * verbatim it is what makes the log usable so consumers that route identifiers through the path should treat
244
- * that field accordingly. */
244
+ * Payload-free by construction: no headers, cookies, tokens nor request body ever reach an event, query values
245
+ * are stripped from paths and tool arguments are reduced to their shape. Two fields are NOT anonymous and a
246
+ * consumer shipping these events must handle them accordingly: `clientIp` on a request event, and the request
247
+ * path, which is kept verbatim because it is what makes the log usable. */
245
248
  export type ServerLogEvent = ServerRequestLogEvent | McpToolLogEvent | McpResourceLogEvent;
246
249
  /** The sink a consumer provides to receive every {@link ServerLogEvent} (see `SSRServerConfig.logger`). */
247
250
  export type ServerLogger = (event: ServerLogEvent) => void;
@@ -331,6 +334,8 @@ export type SSRServerConfig = {
331
334
  };
332
335
  /** Cache-buster appended as ?v=<assetVersion> to all default SDK asset URLs (jsPath, cssPath, react vendor). Compute from file mtime or package version at startup. */
333
336
  assetVersion?: string;
337
+ /** OAuth 2.1 authorization for the MCP server. Omit to keep the server anonymous — see {@link OAuthConfig}. */
338
+ oauth?: OAuthConfig;
334
339
  };
335
340
  export type ServerServices = {
336
341
  ssr?: boolean;
@@ -364,6 +369,96 @@ export type SSRServer = {
364
369
  readonly cache: CacheManager | null;
365
370
  readonly plugins: PluginRegistry;
366
371
  };
372
+ /** A key/value store with per-entry expiry, backing the OAuth layer's short-lived protocol state (registered
373
+ * clients, authorization codes, refresh grants). Values are opaque strings the SDK serialises itself. A
374
+ * multi-replica deployment MUST inject a shared implementation — a code minted on one replica is redeemed on
375
+ * whichever replica the token request lands on. */
376
+ export type OAuthStore = {
377
+ put: (key: string, value: string, ttlSeconds: number) => void | Promise<void>;
378
+ get: (key: string) => (string | undefined) | Promise<string | undefined>;
379
+ drop: (key: string) => void | Promise<void>;
380
+ };
381
+ /** Someone who got through {@link OAuthAdapters.authenticate}. `id` is what the other adapters key off; `label`
382
+ * is shown back on the consent screen so the user can see who they are about to grant access as. */
383
+ export type OAuthUser = {
384
+ id: string;
385
+ label: string;
386
+ };
387
+ /** One thing the user may grant the client access to — a Plitzi space, or whatever else a deployment scopes its
388
+ * tokens by. `value` is the opaque handle the SDK round-trips back to {@link OAuthAdapters.issueToken}; only the
389
+ * consumer interprets it. A deployment whose public surface is useful on its own (plitzi_render needs no space)
390
+ * can offer a target that grants nothing, so the user is never forced to pick one. */
391
+ export type OAuthGrantTarget = {
392
+ value: string;
393
+ label: string;
394
+ description?: string;
395
+ };
396
+ /** What the OAuth layer cannot resolve on its own: who the user is, what they may grant, and the bearer to mint
397
+ * for them. The SDK owns the protocol (discovery, registration, PKCE, code exchange); the consumer owns identity
398
+ * and issues a token its own `adapters.getSpaceId` will accept back. */
399
+ export type OAuthAdapters = {
400
+ /** Verify the credentials typed into the consent screen. Return undefined to re-show the form with an error —
401
+ * never throw for a wrong password. */
402
+ authenticate: (credentials: {
403
+ username: string;
404
+ password: string;
405
+ }) => Promise<OAuthUser | undefined>;
406
+ /** What this user may grant access to. An empty list ends the flow with `access_denied`. */
407
+ grantTargets: (user: OAuthUser) => Promise<OAuthGrantTarget[]>;
408
+ /** Mint the bearer the client will send on every MCP request. Return undefined to deny the grant. */
409
+ issueToken: (user: OAuthUser, target: OAuthGrantTarget) => Promise<{
410
+ token: string;
411
+ expiresInSeconds?: number;
412
+ } | undefined>;
413
+ store: OAuthStore;
414
+ };
415
+ /** What the built-in consent screen shows around the form. Ignored when `renderConsent` replaces the page. */
416
+ export type OAuthBranding = {
417
+ /** Shown as the heading, e.g. 'Plitzi'. Defaults to 'Plitzi'. */
418
+ productName?: string;
419
+ /** Absolute or same-origin URL of a logo to show above the heading. */
420
+ logoUrl?: string;
421
+ /** Extra CSS appended to the page's own, for a deployment that wants its own look without replacing the page. */
422
+ css?: string;
423
+ };
424
+ /** Everything the consent screen needs to render itself, for a deployment that replaces the built-in page. Return
425
+ * a full HTML document; the SDK serves it as-is and reads the same form fields back. */
426
+ export type OAuthConsentView = {
427
+ /** 'credentials' asks for username + password; 'target' asks which space to grant, after a successful login. */
428
+ step: 'credentials' | 'target';
429
+ /** Where the form must POST to (the authorize endpoint). */
430
+ action: string;
431
+ /** Hidden fields the form MUST round-trip verbatim, or the flow cannot be resumed. */
432
+ hidden: Record<string, string>;
433
+ /** Offered on the 'target' step only. */
434
+ targets: OAuthGrantTarget[];
435
+ /** Who logged in, on the 'target' step. */
436
+ user?: OAuthUser;
437
+ /** A message to show the user, e.g. after a failed login. */
438
+ error?: string;
439
+ branding: OAuthBranding;
440
+ };
441
+ /** OAuth 2.1 authorization for the MCP server (RFC 9728 discovery + RFC 7591 dynamic client registration +
442
+ * authorization code with PKCE). ENTIRELY OPTIONAL: without this config no endpoint is mounted, discovery keeps
443
+ * answering 404 and the server stays anonymous, which is a working setup — the public surface (handshake, tool
444
+ * and resource listing, the guide, plitzi_render) never needed a token. Configure it only to let a remote host
445
+ * that cannot send custom headers — Claude Desktop, ChatGPT — obtain a space-scoped one. */
446
+ export type OAuthConfig = {
447
+ adapters: OAuthAdapters;
448
+ /** The issuer/resource identifier published in the metadata documents. Defaults to the origin the request came
449
+ * in on, which is correct whenever the server owns its sub-domain; set it when a proxy rewrites the host. */
450
+ issuer?: string;
451
+ /** Scope names advertised and echoed back on the token. Defaults to ['plitzi']. */
452
+ scopes?: string[];
453
+ /** How long an authorization code stays redeemable, in seconds. Default 60 — codes are one-shot and redeemed
454
+ * immediately. */
455
+ codeTtlSeconds?: number;
456
+ /** How long a refresh grant lives, in seconds. Default 30 days. Set 0 to issue no refresh tokens. */
457
+ refreshTtlSeconds?: number;
458
+ branding?: OAuthBranding;
459
+ /** Replaces the built-in consent screen — return a full HTML document for the given step. */
460
+ renderConsent?: (view: OAuthConsentView) => string | Promise<string>;
461
+ };
367
462
  /** A short-TTL, one-shot store for unsaved draft offline-data behind a preview token. The SDK ships an
368
463
  * in-memory default (fine for a single replica); a multi-replica deployment injects a shared (e.g. Redis)
369
464
  * implementation so a preview URL resolves on whichever replica the browser lands on. `take` consumes the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plitzi/sdk-shared",
3
- "version": "0.32.11",
3
+ "version": "0.32.12",
4
4
  "license": "AGPL-3.0",
5
5
  "files": [
6
6
  "dist",
@@ -1230,7 +1230,7 @@
1230
1230
  },
1231
1231
  "dependencies": {
1232
1232
  "@modelcontextprotocol/sdk": "^1.29.0",
1233
- "@plitzi/nexus": "0.32.11",
1233
+ "@plitzi/nexus": "0.32.12",
1234
1234
  "@plitzi/plitzi-ui": "^1.6.18",
1235
1235
  "date-fns": "^4.4.0",
1236
1236
  "date-fns-tz": "^3.2.0",