@happyvertical/smrt-app-mcp 0.53.4 → 0.53.5
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/AGENTS.md +11 -0
- package/README.md +25 -6
- package/dist/auth.d.ts +77 -4
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +117 -12
- package/dist/auth.js.map +1 -1
- package/dist/chunks/protocol-BlbEg3jn.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/manifest.json +1 -1
- package/dist/own-data.d.ts +21 -0
- package/dist/own-data.d.ts.map +1 -0
- package/dist/server.d.ts +13 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/smrt-knowledge.json +5 -5
- package/dist/sveltekit.d.ts +18 -7
- package/dist/sveltekit.d.ts.map +1 -1
- package/dist/sveltekit.js +2 -1
- package/dist/sveltekit.js.map +1 -1
- package/package.json +4 -4
package/AGENTS.md
CHANGED
|
@@ -34,6 +34,17 @@ tool policy; `./sveltekit` owns the stateless Streamable HTTP transport.
|
|
|
34
34
|
re-check the originating action against the allow-list and `effects`,
|
|
35
35
|
after a principal-level `taskPrincipalPolicy` (defaults: scope policy);
|
|
36
36
|
`mountMcpProtectedResourceMetadataRoute` serves only the advertised path.
|
|
37
|
+
- `createHostedMcpResourceAuth({ profile, runtime })`: locally, a runtime
|
|
38
|
+
`verifyLocalMcpToken` yields the `sessionFallback` adapter
|
|
39
|
+
(`createLocalMcpTokenAuth`): no `Authorization` header keeps the session
|
|
40
|
+
principal; a presented bearer must verify (401, never cookie fallback); no
|
|
41
|
+
metadata (404). Hosted, `runtime.resolveMcpPrincipal` is the default
|
|
42
|
+
identity mapping; an explicit `resolvePrincipal` overrides it.
|
|
43
|
+
- Adapters stamp `principal.tenantBinding` for `bindPrincipal`: the local
|
|
44
|
+
token adapter always `direct`; the JWT adapter the mapping's value or
|
|
45
|
+
`direct-or-inherited`. Never derive it from request input. Both adapters
|
|
46
|
+
read principals/mappings from own data only (`own-data.ts`): inherited
|
|
47
|
+
values, accessors and poison keys deny.
|
|
37
48
|
- Keep tool catalogs private by default. Public caching requires an explicit
|
|
38
49
|
attestation and a global, unauthenticated, read-only, non-tenant catalog with
|
|
39
50
|
no principal-aware policy.
|
package/README.md
CHANGED
|
@@ -126,10 +126,10 @@ binding. `createHostedMcpResourceAuth` from `./auth` builds that source from
|
|
|
126
126
|
|
|
127
127
|
```ts
|
|
128
128
|
import { createHostedMcpResourceAuth } from '@happyvertical/smrt-app-mcp/auth';
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
});
|
|
129
|
+
const { profile } = await runtime.resolvedRuntime();
|
|
130
|
+
// `runtime` supplies both credential bindings (see below); pass
|
|
131
|
+
// `resolvePrincipal` as well to override the hosted identity mapping.
|
|
132
|
+
const auth = createHostedMcpResourceAuth({ profile, runtime });
|
|
133
133
|
// api/mcp/+server.ts
|
|
134
134
|
export const POST = mountMcpAppRoute({
|
|
135
135
|
models: [Item], requiredScopes: ['items.read'], effects: ['read'],
|
|
@@ -139,9 +139,28 @@ export const POST = mountMcpAppRoute({
|
|
|
139
139
|
export const GET = mountMcpProtectedResourceMetadataRoute(auth);
|
|
140
140
|
```
|
|
141
141
|
|
|
142
|
+
The `runtime` option takes any object with the optional
|
|
143
|
+
`verifyLocalMcpToken(token)` and `resolveMcpPrincipal(identity)` methods; the
|
|
144
|
+
s-m-r-t SvelteKit runtime has both.
|
|
145
|
+
|
|
146
|
+
- **Local profile.** With `verifyLocalMcpToken`, the source yields the
|
|
147
|
+
`createLocalMcpTokenAuth` adapter for owner-minted tokens
|
|
148
|
+
(`smrt app token --scopes items.read`). A request with no `Authorization`
|
|
149
|
+
header keeps the session principal, exactly as before. A presented bearer
|
|
150
|
+
must verify: an unknown, revoked or expired token is a bare 401
|
|
151
|
+
(`WWW-Authenticate: Bearer error="invalid_token"`) and never falls back to
|
|
152
|
+
the cookie. The origin check still runs first. Without the runtime the
|
|
153
|
+
source yields `null`, as before.
|
|
154
|
+
- **Hosted profiles.** Without `resolvePrincipal`, the runtime's
|
|
155
|
+
membership-backed `resolveMcpPrincipal` maps the verified issuer/subject to
|
|
156
|
+
a user linked through `oidc_identities` with exactly one active tenant
|
|
157
|
+
membership, and denies anything else. Pass `resolvePrincipal` when users
|
|
158
|
+
belong to several tenants.
|
|
159
|
+
|
|
142
160
|
`mountMcpProtectedResourceMetadataRoute` serves the RFC 9728 document only at
|
|
143
|
-
the adapter's advertised `metadataUrl` path
|
|
144
|
-
the `local` profile
|
|
161
|
+
the adapter's advertised `metadataUrl` path. It returns 404 for any other
|
|
162
|
+
path, and in the `local` profile (no adapter, or the local token adapter). It
|
|
163
|
+
does not implement an OAuth authorization server.
|
|
145
164
|
|
|
146
165
|
For piping a deployed app's MCP surface to a local stdio MCP client, see `@happyvertical/smrt-app-cli` — the client-side runtime CLI exposes a `startMcpBridge()` default and a generic `smrt-mcp-bridge` bin.
|
|
147
166
|
|
package/dist/auth.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** Server-only OAuth protected-resource boundary. This is not an issuer. */
|
|
2
2
|
import { type JWTPayload } from 'jose';
|
|
3
|
-
import type { McpAppPrincipal } from './server.js';
|
|
3
|
+
import type { McpAppPrincipal, McpTenantBinding } from './server.js';
|
|
4
4
|
export type McpDeploymentProfile = 'local' | 'self-hosted' | 'cloud';
|
|
5
5
|
export interface McpVerifiedIdentity {
|
|
6
6
|
readonly issuer: string;
|
|
@@ -13,6 +13,12 @@ export interface McpPrincipalMapping {
|
|
|
13
13
|
tenantId?: string;
|
|
14
14
|
kind?: string;
|
|
15
15
|
roles?: string[];
|
|
16
|
+
/**
|
|
17
|
+
* How the mapped tenant authority may be bound. Defaults to
|
|
18
|
+
* `direct-or-inherited`; a mapping that selected a direct membership
|
|
19
|
+
* returns `direct` so inheritance can never substitute for it.
|
|
20
|
+
*/
|
|
21
|
+
tenantBinding?: McpTenantBinding;
|
|
16
22
|
}
|
|
17
23
|
export interface McpResourceAuthOptions {
|
|
18
24
|
profile: McpDeploymentProfile;
|
|
@@ -35,9 +41,21 @@ export interface McpResourceAuthOptions {
|
|
|
35
41
|
resolvePrincipal(identity: McpVerifiedIdentity): Promise<McpPrincipalMapping | null>;
|
|
36
42
|
}
|
|
37
43
|
export interface McpResourceAuth {
|
|
38
|
-
/**
|
|
44
|
+
/**
|
|
45
|
+
* Mount GET at this URL. Paths use RFC 9728's well-known path insertion.
|
|
46
|
+
* Empty for a {@link McpResourceAuth.sessionFallback} adapter, which
|
|
47
|
+
* advertises no OAuth metadata.
|
|
48
|
+
*/
|
|
39
49
|
readonly metadataUrl: string;
|
|
40
50
|
metadataResponse(): Response;
|
|
51
|
+
/**
|
|
52
|
+
* When `true`, a request that carries no `Authorization` header is not
|
|
53
|
+
* challenged: the route resolves its session principal instead (today's
|
|
54
|
+
* local-profile behaviour). A presented bearer must still verify; an
|
|
55
|
+
* invalid one is refused and never falls back to the session. Only the
|
|
56
|
+
* local owner-token adapter ({@link createLocalMcpTokenAuth}) sets it.
|
|
57
|
+
*/
|
|
58
|
+
readonly sessionFallback?: boolean;
|
|
41
59
|
/** A failure response contains no token, claims or upstream error details. */
|
|
42
60
|
authenticate(request: Request): Promise<{
|
|
43
61
|
ok: true;
|
|
@@ -49,6 +67,51 @@ export interface McpResourceAuth {
|
|
|
49
67
|
response: Response;
|
|
50
68
|
}>;
|
|
51
69
|
}
|
|
70
|
+
/**
|
|
71
|
+
* Credential bindings an application runtime can supply to
|
|
72
|
+
* {@link createHostedMcpResourceAuth}, structurally satisfied by the SMRT
|
|
73
|
+
* SvelteKit runtime from `@happyvertical/smrt-app-runtime/sveltekit`.
|
|
74
|
+
*/
|
|
75
|
+
/**
|
|
76
|
+
* A verified local-token principal as a runtime returns it. Its `scopes` may
|
|
77
|
+
* be a frozen (readonly) array; the adapter copies it.
|
|
78
|
+
*/
|
|
79
|
+
export type McpLocalTokenPrincipal = Omit<McpAppPrincipal, 'scopes'> & {
|
|
80
|
+
id: string;
|
|
81
|
+
scopes?: readonly string[];
|
|
82
|
+
};
|
|
83
|
+
export interface McpAuthRuntime {
|
|
84
|
+
/**
|
|
85
|
+
* Verify an owner-minted local MCP token (`smrt app token`). Resolve the
|
|
86
|
+
* token principal (with scopes already capped to live permissions), or
|
|
87
|
+
* `null` to deny. Used only in the `local` profile.
|
|
88
|
+
*/
|
|
89
|
+
verifyLocalMcpToken?(token: string): Promise<McpLocalTokenPrincipal | null>;
|
|
90
|
+
/**
|
|
91
|
+
* Default hosted identity mapping, used when no explicit
|
|
92
|
+
* `resolvePrincipal` is given (for example the runtime's membership-backed
|
|
93
|
+
* resolver).
|
|
94
|
+
*/
|
|
95
|
+
resolveMcpPrincipal?(identity: McpVerifiedIdentity): Promise<McpPrincipalMapping | null>;
|
|
96
|
+
}
|
|
97
|
+
/** Options for {@link createLocalMcpTokenAuth}. */
|
|
98
|
+
export interface LocalMcpTokenAuthOptions {
|
|
99
|
+
/**
|
|
100
|
+
* Resolve a presented token to its principal, or `null` to deny. A thrown
|
|
101
|
+
* error is also a denial. The principal needs a non-empty `id` and
|
|
102
|
+
* `tenantId` and string `scopes`; `allowCrossTenant` is never honoured.
|
|
103
|
+
*/
|
|
104
|
+
verify(token: string): Promise<McpLocalTokenPrincipal | null>;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Bearer adapter for owner-minted local MCP tokens. It is a
|
|
108
|
+
* {@link McpResourceAuth.sessionFallback} adapter: without an `Authorization`
|
|
109
|
+
* header the route keeps its session principal; with one, the token must
|
|
110
|
+
* verify, otherwise the response is `401` with
|
|
111
|
+
* `WWW-Authenticate: Bearer error="invalid_token"` and no token, claim or
|
|
112
|
+
* upstream detail. It advertises no OAuth metadata (the metadata route 404s).
|
|
113
|
+
*/
|
|
114
|
+
export declare function createLocalMcpTokenAuth(options: LocalMcpTokenAuthOptions): McpResourceAuth;
|
|
52
115
|
/** Options for {@link createHostedMcpResourceAuth}. */
|
|
53
116
|
export interface HostedMcpResourceAuthOptions {
|
|
54
117
|
/**
|
|
@@ -58,10 +121,20 @@ export interface HostedMcpResourceAuthOptions {
|
|
|
58
121
|
profile: McpDeploymentProfile | (() => McpDeploymentProfile);
|
|
59
122
|
/**
|
|
60
123
|
* Application-owned mapping from a verified identity to a current account
|
|
61
|
-
* and active membership
|
|
62
|
-
*
|
|
124
|
+
* and active membership; token claims never select a tenant. Overrides
|
|
125
|
+
* `runtime.resolveMcpPrincipal`. A hosted profile with neither throws on
|
|
126
|
+
* first use.
|
|
63
127
|
*/
|
|
64
128
|
resolvePrincipal?: McpResourceAuthOptions['resolvePrincipal'];
|
|
129
|
+
/**
|
|
130
|
+
* The application runtime (for example the SMRT SvelteKit runtime). In the
|
|
131
|
+
* `local` profile its `verifyLocalMcpToken` turns on owner-minted bearer
|
|
132
|
+
* tokens through {@link createLocalMcpTokenAuth} (requests without a
|
|
133
|
+
* bearer keep the session principal); without it the thunk yields `null`
|
|
134
|
+
* as before. In hosted profiles its membership-backed
|
|
135
|
+
* `resolveMcpPrincipal` is the default identity mapping.
|
|
136
|
+
*/
|
|
137
|
+
runtime?: McpAuthRuntime;
|
|
65
138
|
/** Environment source. Defaults to `process.env`, read lazily. */
|
|
66
139
|
env?: Readonly<Record<string, string | undefined>>;
|
|
67
140
|
/** Default `['RS256']`. */
|
package/dist/auth.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,OAAO,EAAsB,KAAK,UAAU,EAAa,MAAM,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"}
|
package/dist/auth.js
CHANGED
|
@@ -697,7 +697,44 @@ function createRemoteJWKSet(url, options) {
|
|
|
697
697
|
});
|
|
698
698
|
}
|
|
699
699
|
//#endregion
|
|
700
|
+
//#region src/own-data.ts
|
|
701
|
+
var FORBIDDEN_KEYS = /* @__PURE__ */ new Set([
|
|
702
|
+
"__proto__",
|
|
703
|
+
"constructor",
|
|
704
|
+
"prototype"
|
|
705
|
+
]);
|
|
706
|
+
function readOwnFields(value, keys) {
|
|
707
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
708
|
+
const prototype = Object.getPrototypeOf(value);
|
|
709
|
+
if (prototype !== Object.prototype && prototype !== null) return null;
|
|
710
|
+
for (const key of Reflect.ownKeys(value)) if (typeof key === "string" && FORBIDDEN_KEYS.has(key)) return null;
|
|
711
|
+
const fields = /* @__PURE__ */ Object.create(null);
|
|
712
|
+
for (const key of keys) {
|
|
713
|
+
const descriptor = Reflect.getOwnPropertyDescriptor(value, key);
|
|
714
|
+
if (!descriptor) continue;
|
|
715
|
+
if (!("value" in descriptor)) return null;
|
|
716
|
+
fields[key] = descriptor.value;
|
|
717
|
+
}
|
|
718
|
+
return fields;
|
|
719
|
+
}
|
|
720
|
+
function ownStringArray(value) {
|
|
721
|
+
if (!Array.isArray(value) || Object.getPrototypeOf(value) !== Array.prototype) return null;
|
|
722
|
+
const length = value.length;
|
|
723
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
724
|
+
if (key === "length") continue;
|
|
725
|
+
if (typeof key !== "string" || !/^(?:0|[1-9][0-9]*)$/u.test(key) || Number(key) >= length) return null;
|
|
726
|
+
}
|
|
727
|
+
const copy = [];
|
|
728
|
+
for (let index = 0; index < length; index += 1) {
|
|
729
|
+
const descriptor = Reflect.getOwnPropertyDescriptor(value, index);
|
|
730
|
+
if (!descriptor || !("value" in descriptor) || typeof descriptor.value !== "string") return null;
|
|
731
|
+
copy.push(descriptor.value);
|
|
732
|
+
}
|
|
733
|
+
return copy;
|
|
734
|
+
}
|
|
735
|
+
//#endregion
|
|
700
736
|
//#region src/auth.ts
|
|
737
|
+
var TENANT_BINDINGS = ["direct", "direct-or-inherited"];
|
|
701
738
|
var scopePattern = /^[\x21\x23-\x5b\x5d-\x7e]+$/u;
|
|
702
739
|
function validateUrl(value, profile) {
|
|
703
740
|
let url;
|
|
@@ -717,6 +754,57 @@ function validateUrl(value, profile) {
|
|
|
717
754
|
function validString(value) {
|
|
718
755
|
return typeof value === "string" && value.length > 0 && value.length <= 1024 && value.trim() === value && [...value].every((character) => character.charCodeAt(0) > 31 && character.charCodeAt(0) !== 127);
|
|
719
756
|
}
|
|
757
|
+
var LOCAL_BEARER_PATTERN = /^Bearer +([A-Za-z0-9._~+/-]{16,512}=*)$/iu;
|
|
758
|
+
function createLocalMcpTokenAuth(options) {
|
|
759
|
+
const verify = options?.verify;
|
|
760
|
+
if (typeof verify !== "function") throw new TypeError("Local MCP token auth requires a verify function.");
|
|
761
|
+
const deny = () => ({
|
|
762
|
+
ok: false,
|
|
763
|
+
response: new Response(null, {
|
|
764
|
+
status: 401,
|
|
765
|
+
headers: {
|
|
766
|
+
"WWW-Authenticate": "Bearer error=\"invalid_token\"",
|
|
767
|
+
"Cache-Control": "no-store"
|
|
768
|
+
}
|
|
769
|
+
})
|
|
770
|
+
});
|
|
771
|
+
return Object.freeze({
|
|
772
|
+
metadataUrl: "",
|
|
773
|
+
sessionFallback: true,
|
|
774
|
+
metadataResponse: () => new Response(null, {
|
|
775
|
+
status: 404,
|
|
776
|
+
headers: { "Cache-Control": "no-store" }
|
|
777
|
+
}),
|
|
778
|
+
async authenticate(request) {
|
|
779
|
+
const match = LOCAL_BEARER_PATTERN.exec(request.headers.get("authorization") ?? "");
|
|
780
|
+
if (!match) return deny();
|
|
781
|
+
let principal;
|
|
782
|
+
try {
|
|
783
|
+
principal = await verify(match[1]);
|
|
784
|
+
} catch {
|
|
785
|
+
return deny();
|
|
786
|
+
}
|
|
787
|
+
const fields = readOwnFields(principal, [
|
|
788
|
+
"id",
|
|
789
|
+
"tenantId",
|
|
790
|
+
"kind",
|
|
791
|
+
"scopes"
|
|
792
|
+
]);
|
|
793
|
+
const scopes = fields ? ownStringArray(fields.scopes) : null;
|
|
794
|
+
if (!fields || !scopes || !validString(fields.id) || !validString(fields.tenantId) || fields.kind !== void 0 && !validString(fields.kind) || !scopes.every((scope) => scopePattern.test(scope))) return deny();
|
|
795
|
+
return {
|
|
796
|
+
ok: true,
|
|
797
|
+
principal: {
|
|
798
|
+
id: fields.id,
|
|
799
|
+
tenantId: fields.tenantId,
|
|
800
|
+
kind: fields.kind,
|
|
801
|
+
scopes,
|
|
802
|
+
tenantBinding: "direct"
|
|
803
|
+
}
|
|
804
|
+
};
|
|
805
|
+
}
|
|
806
|
+
});
|
|
807
|
+
}
|
|
720
808
|
var HOSTED_MCP_AUTH_ENV = Object.freeze({
|
|
721
809
|
resource: "SMRT_MCP_RESOURCE",
|
|
722
810
|
issuer: "SMRT_MCP_ISSUER",
|
|
@@ -725,9 +813,16 @@ var HOSTED_MCP_AUTH_ENV = Object.freeze({
|
|
|
725
813
|
});
|
|
726
814
|
function createHostedMcpResourceAuth(options) {
|
|
727
815
|
let cached;
|
|
816
|
+
let local;
|
|
817
|
+
const runtime = options.runtime;
|
|
728
818
|
return () => {
|
|
729
819
|
const profile = typeof options.profile === "function" ? options.profile() : options.profile;
|
|
730
|
-
if (profile === "local")
|
|
820
|
+
if (profile === "local") {
|
|
821
|
+
const verify = runtime?.verifyLocalMcpToken;
|
|
822
|
+
if (typeof verify !== "function") return null;
|
|
823
|
+
local ??= createLocalMcpTokenAuth({ verify: (token) => verify.call(runtime, token) });
|
|
824
|
+
return local;
|
|
825
|
+
}
|
|
731
826
|
if (cached) return cached;
|
|
732
827
|
const env = options.env ?? globalThis.process?.env ?? {};
|
|
733
828
|
const required = (name) => {
|
|
@@ -735,7 +830,9 @@ function createHostedMcpResourceAuth(options) {
|
|
|
735
830
|
if (!value) throw new Error(`Hosted MCP requires ${name}.`);
|
|
736
831
|
return value;
|
|
737
832
|
};
|
|
738
|
-
|
|
833
|
+
const runtimeResolver = runtime?.resolveMcpPrincipal;
|
|
834
|
+
const resolvePrincipal = options.resolvePrincipal ?? (typeof runtimeResolver === "function" ? (identity) => runtimeResolver.call(runtime, identity) : void 0);
|
|
835
|
+
if (!resolvePrincipal) throw new Error("Hosted MCP requires an application-owned resolvePrincipal binding or a runtime with resolveMcpPrincipal.");
|
|
739
836
|
cached = createMcpResourceAuth({
|
|
740
837
|
profile,
|
|
741
838
|
resource: required(HOSTED_MCP_AUTH_ENV.resource),
|
|
@@ -745,7 +842,7 @@ function createHostedMcpResourceAuth(options) {
|
|
|
745
842
|
algorithms: options.algorithms ?? ["RS256"],
|
|
746
843
|
tokenType: options.tokenType,
|
|
747
844
|
requireTenant: options.requireTenant,
|
|
748
|
-
resolvePrincipal
|
|
845
|
+
resolvePrincipal
|
|
749
846
|
});
|
|
750
847
|
return cached;
|
|
751
848
|
};
|
|
@@ -819,21 +916,29 @@ function createMcpResourceAuth(options) {
|
|
|
819
916
|
const granted = payload.scope === "" ? [] : payload.scope.split(" ");
|
|
820
917
|
if (granted.some((scope) => !scopePattern.test(scope))) return deny("invalid_token");
|
|
821
918
|
if (scopes.some((scope) => !granted.includes(scope))) return deny("insufficient_scope");
|
|
822
|
-
const
|
|
919
|
+
const fields = readOwnFields(await resolvePrincipal(Object.freeze({
|
|
823
920
|
issuer,
|
|
824
921
|
subject: payload.sub,
|
|
825
922
|
scopes: Object.freeze([...granted]),
|
|
826
923
|
claims: Object.freeze(payload)
|
|
827
|
-
}))
|
|
828
|
-
|
|
924
|
+
})), [
|
|
925
|
+
"id",
|
|
926
|
+
"tenantId",
|
|
927
|
+
"kind",
|
|
928
|
+
"roles",
|
|
929
|
+
"tenantBinding"
|
|
930
|
+
]);
|
|
931
|
+
const roles = fields?.roles === void 0 ? void 0 : ownStringArray(fields.roles);
|
|
932
|
+
if (!fields || roles === null || !validString(fields.id) || requireTenant && !validString(fields.tenantId) || fields.tenantId !== void 0 && !validString(fields.tenantId) || fields.kind !== void 0 && !validString(fields.kind) || roles !== void 0 && !roles.every(validString) || fields.tenantBinding !== void 0 && (typeof fields.tenantBinding !== "string" || !TENANT_BINDINGS.includes(fields.tenantBinding))) return deny("invalid_token");
|
|
829
933
|
return {
|
|
830
934
|
ok: true,
|
|
831
935
|
principal: {
|
|
832
|
-
id:
|
|
833
|
-
tenantId:
|
|
834
|
-
kind:
|
|
835
|
-
roles
|
|
836
|
-
scopes: [...granted]
|
|
936
|
+
id: fields.id,
|
|
937
|
+
tenantId: fields.tenantId,
|
|
938
|
+
kind: fields.kind,
|
|
939
|
+
roles,
|
|
940
|
+
scopes: [...granted],
|
|
941
|
+
tenantBinding: fields.tenantBinding ?? "direct-or-inherited"
|
|
837
942
|
}
|
|
838
943
|
};
|
|
839
944
|
} catch {
|
|
@@ -843,6 +948,6 @@ function createMcpResourceAuth(options) {
|
|
|
843
948
|
};
|
|
844
949
|
}
|
|
845
950
|
//#endregion
|
|
846
|
-
export { HOSTED_MCP_AUTH_ENV, createHostedMcpResourceAuth, createMcpResourceAuth };
|
|
951
|
+
export { HOSTED_MCP_AUTH_ENV, createHostedMcpResourceAuth, createLocalMcpTokenAuth, createMcpResourceAuth };
|
|
847
952
|
|
|
848
953
|
//# sourceMappingURL=auth.js.map
|