@hosty-sdk/app 0.10.0 → 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.
@@ -0,0 +1,35 @@
1
+ /** The RFC 9728 document for one MCP endpoint. */
2
+ export interface ProtectedResourceMetadata {
3
+ resource: string;
4
+ authorization_servers: string[];
5
+ scopes_supported: string[];
6
+ bearer_methods_supported: string[];
7
+ }
8
+ export interface ResourceMetadataOptions {
9
+ /** The app's own MCP endpoint URL as clients reach it. Wins over any derivation. */
10
+ resourceUrl?: string;
11
+ /** The public origin of the endpoint serving MCP. Defaults to `HOSTY_PUBLIC_ORIGIN_API` — the
12
+ * per-endpoint variable Core injects when an endpoint is published, for the reference layout
13
+ * where the MCP interface rides the `api` endpoint. An app with a different layout passes its
14
+ * own endpoint's origin (or the full resourceUrl). */
15
+ publicOrigin?: string;
16
+ /** The MCP endpoint's path under that origin. */
17
+ resourcePath?: string;
18
+ /** The authorization server's browser-reachable origin. Defaults to HOSTY_CORE_PUBLIC_ORIGIN —
19
+ * the flow's whole point is a remote client and a browser completing it, so the loopback origin
20
+ * this app dials Core on would send both to the wrong machine. */
21
+ authorizationServerOrigin?: string;
22
+ }
23
+ /**
24
+ * Builds the metadata document, or null when the environment cannot name the two URLs it consists
25
+ * of — which is the ordinary state of an app that is not published to a public origin. Null rather
26
+ * than a guess: a wrong resource identity here would have clients requesting tokens for a URL
27
+ * nothing serves, and no metadata simply means the manual token path, which always works.
28
+ */
29
+ export declare function buildProtectedResourceMetadata(options?: ResourceMetadataOptions): ProtectedResourceMetadata | null;
30
+ /**
31
+ * The `WWW-Authenticate` value a 401 from the MCP endpoint should carry, pointing a stock client at
32
+ * the metadata. The metadata URL is the RFC 9728 derivation: the well-known prefix inserted before
33
+ * the resource's path.
34
+ */
35
+ export declare function buildWwwAuthenticate(metadata: ProtectedResourceMetadata): string;
@@ -0,0 +1,42 @@
1
+ // The resource-server half of the MCP authorization handshake (RFC 9728), for an app's MCP
2
+ // endpoint: the metadata document naming Core as the authorization server, and the 401 challenge
3
+ // header pointing at it.
4
+ //
5
+ // In its own entry like ./scoped-token, and for the same reason: an MCP endpoint is usually not a
6
+ // Next route, and the server slice pulls in "server-only".
7
+ //
8
+ // The app serves the *pointer*; Core is the authorization server and serves everything else. An app
9
+ // never mints, validates, or even sees an OAuth exchange — a client that follows this pointer comes
10
+ // back carrying an ordinary scoped access token, which the app validates through introspection
11
+ // exactly as if the operator had pasted one by hand.
12
+ /**
13
+ * Builds the metadata document, or null when the environment cannot name the two URLs it consists
14
+ * of — which is the ordinary state of an app that is not published to a public origin. Null rather
15
+ * than a guess: a wrong resource identity here would have clients requesting tokens for a URL
16
+ * nothing serves, and no metadata simply means the manual token path, which always works.
17
+ */
18
+ export function buildProtectedResourceMetadata(options = {}) {
19
+ const core = options.authorizationServerOrigin?.trim() || process.env.HOSTY_CORE_PUBLIC_ORIGIN?.trim();
20
+ const publicOrigin = options.publicOrigin?.trim() || process.env.HOSTY_PUBLIC_ORIGIN_API?.trim();
21
+ const path = options.resourcePath?.trim() || "/api/mcp";
22
+ const resource = options.resourceUrl?.trim() || (publicOrigin ? `${publicOrigin.replace(/\/$/, "")}${path}` : undefined);
23
+ if (!core || !resource) {
24
+ return null;
25
+ }
26
+ return {
27
+ resource,
28
+ authorization_servers: [core.replace(/\/$/, "")],
29
+ scopes_supported: ["mcp:read"],
30
+ bearer_methods_supported: ["header"],
31
+ };
32
+ }
33
+ /**
34
+ * The `WWW-Authenticate` value a 401 from the MCP endpoint should carry, pointing a stock client at
35
+ * the metadata. The metadata URL is the RFC 9728 derivation: the well-known prefix inserted before
36
+ * the resource's path.
37
+ */
38
+ export function buildWwwAuthenticate(metadata) {
39
+ const resource = new URL(metadata.resource);
40
+ const metadataUrl = `${resource.origin}/.well-known/oauth-protected-resource${resource.pathname.replace(/\/$/, "")}`;
41
+ return `Bearer resource_metadata="${metadataUrl}"`;
42
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hosty-sdk/app",
3
- "version": "0.10.0",
3
+ "version": "0.11.0",
4
4
  "description": "Hosty app auth: session state machine, recovery bridge, Core revalidation, and the embedder responder.",
5
5
  "license": "AGPL-3.0-only",
6
6
  "type": "module",
@@ -21,6 +21,10 @@
21
21
  "types": "./dist/scoped-token.d.ts",
22
22
  "default": "./dist/scoped-token.js"
23
23
  },
24
+ "./oauth-resource": {
25
+ "types": "./dist/oauth-resource.d.ts",
26
+ "default": "./dist/oauth-resource.js"
27
+ },
24
28
  "./react": {
25
29
  "types": "./dist/react.d.ts",
26
30
  "default": "./dist/react.js"