permdock 0.0.0 → 0.1.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/README.md +268 -1
- package/dist/a2a/index.d.ts +178 -0
- package/dist/a2a/index.js +271 -0
- package/dist/ai-sdk/index.d.ts +74 -0
- package/dist/ai-sdk/index.js +137 -0
- package/dist/approval-required-TzNB9oQA.js +6 -0
- package/dist/approvals/index.d.ts +3 -0
- package/dist/approvals/index.js +4 -0
- package/dist/approvals-DaxY-Bdg.js +218 -0
- package/dist/approvers-aH0PJkmf.js +20 -0
- package/dist/arazzo-CTeVuXr5.js +70 -0
- package/dist/arazzo-aoTkqYsJ.js +54 -0
- package/dist/authzen/index.d.ts +48 -0
- package/dist/authzen/index.js +336 -0
- package/dist/better-auth/index.d.ts +92 -0
- package/dist/better-auth/index.js +324 -0
- package/dist/budget-JqcRqyhK.d.ts +11 -0
- package/dist/budget-NbEtbJC_.js +11 -0
- package/dist/capability-B68OFnZx.js +171 -0
- package/dist/capability-BDG_nkxt.d.ts +99 -0
- package/dist/catalog/index.d.ts +12 -0
- package/dist/catalog/index.js +2 -0
- package/dist/catalog-B0E6bggc.js +48 -0
- package/dist/catalog-H-vFGwWK.js +55 -0
- package/dist/catalog-doc-BSo_Q_-6.js +298 -0
- package/dist/catalog-idsHc0vo.js +2 -0
- package/dist/catalog-path-CnI0XTFK.js +12 -0
- package/dist/claude-agent/index.d.ts +81 -0
- package/dist/claude-agent/index.js +83 -0
- package/dist/clerk/index.d.ts +56 -0
- package/dist/clerk/index.js +280 -0
- package/dist/cli/bin.d.ts +1 -0
- package/dist/cli/bin.js +20 -0
- package/dist/cli/index.d.ts +46 -0
- package/dist/cli/index.js +5 -0
- package/dist/cloud/index.d.ts +91 -0
- package/dist/cloud/index.js +390 -0
- package/dist/cloud-BNQTrmrr.js +50 -0
- package/dist/cloud-Cuxr6Czw.js +117 -0
- package/dist/collect-B7_TIfmg.js +49 -0
- package/dist/collect-O-tOtbnk.js +350 -0
- package/dist/collect-fRPAQIWb.js +2 -0
- package/dist/commands-BMh_Etm2.js +93 -0
- package/dist/compact-y6vfu2_f.js +19 -0
- package/dist/compare-pfyVnBjE.js +12 -0
- package/dist/compile-CQbvyIBr.d.ts +30 -0
- package/dist/compile-D22iwvuV.js +558 -0
- package/dist/config-BPYmjTOp.js +26 -0
- package/dist/connection-JvqwHODC.js +194 -0
- package/dist/context-CKEAabd7.js +73 -0
- package/dist/context-gtAwAILx.js +6 -0
- package/dist/convex/index.d.ts +40 -0
- package/dist/convex/index.js +55 -0
- package/dist/create-BKF3dgFk.d.ts +162 -0
- package/dist/create-CSkhDU9T.js +222 -0
- package/dist/credential-B_rkUNlY.js +326 -0
- package/dist/credentials-BgRX3WEg.js +268 -0
- package/dist/describe-Bt1YD5uS.js +99 -0
- package/dist/describe-DernyItf.d.ts +37 -0
- package/dist/diff-BUzf5Poi.js +48 -0
- package/dist/diff-x5Il8LVa.js +418 -0
- package/dist/digest-B3kc_PgQ.d.ts +19 -0
- package/dist/digest-BI-6aa6d.js +27 -0
- package/dist/doctor-BGle8Gqi.js +1314 -0
- package/dist/doctor-Bqo6FwUa.js +53 -0
- package/dist/drizzle/index.d.ts +72 -0
- package/dist/drizzle/index.js +160 -0
- package/dist/duration-xK2jGFVc.js +25 -0
- package/dist/elysia/index.d.ts +71 -0
- package/dist/elysia/index.js +103 -0
- package/dist/errors-DSMnAVNa.js +43 -0
- package/dist/errors-DbZ1YOnb.d.ts +138 -0
- package/dist/errors-Iy71s7Oj.js +177 -0
- package/dist/errors-m3u9TmDu.js +27 -0
- package/dist/evaluations-DsAU9ppl.js +479 -0
- package/dist/eve/index.d.ts +95 -0
- package/dist/eve/index.js +181 -0
- package/dist/express/index.d.ts +46 -0
- package/dist/express/index.js +108 -0
- package/dist/fastify/index.d.ts +56 -0
- package/dist/fastify/index.js +141 -0
- package/dist/files-B_wAGYmt.js +121 -0
- package/dist/fixtures-Cvaw0gDG.js +64 -0
- package/dist/freeze-y1_jQL4v.js +31 -0
- package/dist/from-snapshot-BMGnhmgv.js +552 -0
- package/dist/generate-CH7ThJ14.js +173 -0
- package/dist/grantee-Bv04_Vzf.js +521 -0
- package/dist/headers-B5RRv3Xm.js +6 -0
- package/dist/headers-C8SHQccD.d.ts +6 -0
- package/dist/helpers-DnSOrhB9.js +682 -0
- package/dist/hono/index.d.ts +70 -0
- package/dist/hono/index.js +131 -0
- package/dist/hosted-BwKQ1BTA.js +265 -0
- package/dist/http-DPuDaChb.d.ts +13 -0
- package/dist/http-GgVApX1k.js +111 -0
- package/dist/index-CltcQ4Bf.d.ts +138 -0
- package/dist/index-LF_hS-yn.d.ts +177 -0
- package/dist/index-fG6NSXlc.d.ts +9 -0
- package/dist/index-xIxJLwOQ.d.ts +415 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +21 -0
- package/dist/jwt/index.d.ts +87 -0
- package/dist/jwt/index.js +571 -0
- package/dist/kernel-YOGCWNLf.js +211 -0
- package/dist/kysely/index.d.ts +77 -0
- package/dist/kysely/index.js +129 -0
- package/dist/load-BZ24CpZP.js +69 -0
- package/dist/load-QOOm1z70.js +2 -0
- package/dist/load-jose-BL6LH4bL.js +11 -0
- package/dist/lru-8q4WZjBV.js +28 -0
- package/dist/manifest-DDQbC6K0.d.ts +118 -0
- package/dist/map-DowSO96j.js +158 -0
- package/dist/map-claims-B_6I1Rn-.js +360 -0
- package/dist/map-error-RJ3ZsUyA.js +16 -0
- package/dist/markers-B0hYLvMD.js +60 -0
- package/dist/may-use-CnFEjVEp.js +37 -0
- package/dist/mcp/index.d.ts +127 -0
- package/dist/mcp/index.js +563 -0
- package/dist/nest/index.d.ts +80 -0
- package/dist/nest/index.js +270 -0
- package/dist/next/client.d.ts +24 -0
- package/dist/next/client.js +31 -0
- package/dist/next/index.d.ts +110 -0
- package/dist/next/index.js +195 -0
- package/dist/next/plugin.d.ts +14 -0
- package/dist/next/plugin.js +2 -0
- package/dist/node/index.d.ts +43 -0
- package/dist/node/index.js +66 -0
- package/dist/openai/index.d.ts +71 -0
- package/dist/openai/index.js +105 -0
- package/dist/openapi/index.d.ts +93 -0
- package/dist/openapi/index.js +3 -0
- package/dist/openapi-CwePaF7C.js +164 -0
- package/dist/openapi-DWD0Uc_4.js +250 -0
- package/dist/openapi-DcqM8nU7.js +306 -0
- package/dist/openapi-import-BWRUNNeW.js +5959 -0
- package/dist/openapi-schema-D8j_T9tO.js +6902 -0
- package/dist/orpc/index.d.ts +64 -0
- package/dist/orpc/index.js +188 -0
- package/dist/otel/index.d.ts +13 -0
- package/dist/otel/index.js +202 -0
- package/dist/package-root-zGkxO3oN.js +30 -0
- package/dist/parse-DPfIvFMU.js +135 -0
- package/dist/pdp/index.d.ts +22 -0
- package/dist/pdp/index.js +795 -0
- package/dist/peer-BOr_kJE1.js +31 -0
- package/dist/permdock-yoZ7EjxX.js +2956 -0
- package/dist/permissions-B4apnahQ.d.ts +185 -0
- package/dist/permissions-B8vCg_bI.d.ts +52 -0
- package/dist/permissions-CofRqesP.js +142 -0
- package/dist/permissions-DC7GROPA.js +600 -0
- package/dist/pg-4GJWDUxG.js +953 -0
- package/dist/pins-CqE1jZuF.js +12 -0
- package/dist/plugin-4gr7IxFJ.js +65 -0
- package/dist/policy-ANp36PnI.js +971 -0
- package/dist/policy-CInmNGkM.d.ts +1959 -0
- package/dist/policy-Cq2fB8Q5.d.ts +20 -0
- package/dist/prisma/index.d.ts +135 -0
- package/dist/prisma/index.js +430 -0
- package/dist/problem-Cgp9-KZA.js +226 -0
- package/dist/prompt-CXYhLfsS.js +32 -0
- package/dist/protected-B4K3BKoX.js +138 -0
- package/dist/protected-BuPAycJT.d.ts +26 -0
- package/dist/protected-wY7nXhY9.d.ts +23 -0
- package/dist/provider-_wx46HpB.js +48 -0
- package/dist/provider-wTAUda2v.d.ts +6 -0
- package/dist/react/index.d.ts +6 -0
- package/dist/react/index.js +6 -0
- package/dist/react/provider-client.d.ts +2 -0
- package/dist/react/provider-client.js +3 -0
- package/dist/react-native/index.d.ts +42 -0
- package/dist/react-native/index.js +174 -0
- package/dist/refs-ChLSxbST.js +90 -0
- package/dist/relations-r4soIFZB.js +426 -0
- package/dist/revocations-Cy06JNx2.d.ts +21 -0
- package/dist/rls-BOFU3chG.js +233 -0
- package/dist/rls-CJVja9Ww.js +2119 -0
- package/dist/rls-graph-DxQ39PJj.js +371 -0
- package/dist/rls-helpers-BxRuif0l.js +1346 -0
- package/dist/rls-import-CXV6LBlS.js +942 -0
- package/dist/rls-migrate-DrFPbf8p.js +366 -0
- package/dist/row-check-BajTgQZ-.d.ts +43 -0
- package/dist/run-CJ6UvS3a.js +155 -0
- package/dist/runtime-DP9ttFh1.js +27 -0
- package/dist/saas-BRrZzJkM.js +1193 -0
- package/dist/schema-P8yoma__.js +296 -0
- package/dist/scim/index.d.ts +2 -0
- package/dist/scim/index.js +2 -0
- package/dist/scim-9NFa0XVZ.js +1445 -0
- package/dist/scopes-cKVp_WTw.js +212 -0
- package/dist/server/index.d.ts +123 -0
- package/dist/server/index.js +8 -0
- package/dist/sha256-BO0swRXL.js +150 -0
- package/dist/sink-Bd9PSyfq.js +141 -0
- package/dist/sink-Cfr9-7YC.d.ts +104 -0
- package/dist/skills-C_lpV4Jx.js +50 -0
- package/dist/skills-Dx3QLnMY.js +110 -0
- package/dist/snapshot-DE8dE_hz.js +1051 -0
- package/dist/solid/index.d.ts +63 -0
- package/dist/solid/index.js +197 -0
- package/dist/source-DoBBYJi2.js +6 -0
- package/dist/sources-DOpW5Ila.d.ts +347 -0
- package/dist/sources-DVHM3Mwq.js +284 -0
- package/dist/sql-Bll1d7y0.js +22 -0
- package/dist/src-CTNT-PBW.js +430 -0
- package/dist/ssf/index.d.ts +3 -0
- package/dist/ssf/index.js +622 -0
- package/dist/store-Bq90395i.d.ts +27 -0
- package/dist/store-C6hFW6ta.js +508 -0
- package/dist/stores-CkG2HrSg.js +140 -0
- package/dist/stores-CvOnu_2T.d.ts +41 -0
- package/dist/stream-BhMfZOwr.d.ts +12 -0
- package/dist/stream-Q_xlB6Ko.js +52 -0
- package/dist/style-CTm1AXDT.js +22 -0
- package/dist/subject-C6dLxqHU.js +24 -0
- package/dist/subject-settings-xt7V845g.js +82 -0
- package/dist/supabase/index.d.ts +67 -0
- package/dist/supabase/index.js +4 -0
- package/dist/supabase/middleware.d.ts +77 -0
- package/dist/supabase/middleware.js +68 -0
- package/dist/supabase-D8UlBA7Z.js +83 -0
- package/dist/supabase-DTxGtYdi.js +796 -0
- package/dist/supabase-hook-DW-xP4hU.js +2 -0
- package/dist/supabase-hook-D_OqGb_V.js +1528 -0
- package/dist/svelte/Protected.svelte +34 -0
- package/dist/svelte/index.d.ts +10 -0
- package/dist/svelte/index.js +53 -0
- package/dist/svelte/runtime.d.ts +6 -0
- package/dist/svelte/runtime.js +3 -0
- package/dist/svelte/source.d.ts +10 -0
- package/dist/svelte/source.js +8 -0
- package/dist/terminal/index.d.ts +171 -0
- package/dist/terminal/index.js +821 -0
- package/dist/testing/index.d.ts +656 -0
- package/dist/testing/index.js +3208 -0
- package/dist/testing/saas/index.d.ts +194 -0
- package/dist/testing/saas/index.js +3 -0
- package/dist/testing/saas/permissions.d.ts +2 -0
- package/dist/testing/saas/permissions.js +2 -0
- package/dist/text-diff-BdkkYSbi.js +15 -0
- package/dist/thenable-okc2xDsj.js +10 -0
- package/dist/timeout-D51niuQP.js +8 -0
- package/dist/trpc/index.d.ts +80 -0
- package/dist/trpc/index.js +205 -0
- package/dist/types-B8w5PFtS.d.ts +141 -0
- package/dist/types-B9dmiiab.d.ts +66 -0
- package/dist/types-BBK-exvJ.d.ts +177 -0
- package/dist/types-BvBk1wgn.d.ts +118 -0
- package/dist/types-CxquUByI.d.ts +9 -0
- package/dist/types-D6Z6-rKJ.d.ts +312 -0
- package/dist/types-DbWdEfcm.d.ts +87 -0
- package/dist/types-bva3i4Gf.d.ts +98 -0
- package/dist/types-i4BAOvdb.d.ts +97 -0
- package/dist/unplugin/index.d.ts +7 -0
- package/dist/unplugin/index.js +30 -0
- package/dist/usage-1ozMubLY.js +42 -0
- package/dist/usage-CUnK6Ou9.js +530 -0
- package/dist/usage-PyEor6GA.js +2 -0
- package/dist/validity-D0uRmJKJ.js +224 -0
- package/dist/verifier-B90agnhl.js +461 -0
- package/dist/version-CnSh33Fj.js +16 -0
- package/dist/vocabulary-Dk0fs-Jk.d.ts +56 -0
- package/dist/vue/index.d.ts +57 -0
- package/dist/vue/index.js +203 -0
- package/dist/webmcp/index.d.ts +75 -0
- package/dist/webmcp/index.js +313 -0
- package/package.json +423 -4
- package/rulesets/permdock.yaml +29 -0
- package/schemas/catalog-v1.json +507 -0
- package/schemas/openapi/oas-3.1.json +1298 -0
- package/schemas/openapi/oas-3.2.json +1516 -0
- package/schemas/openapi/overlay-1.1.json +79 -0
- package/schemas/supabase-claims-v1.json +139 -0
- package/schemas/supabase-manifest-v1.json +217 -0
- package/skills/permdock/SKILL.md +66 -0
- package/skills/permdock-agents/SKILL.md +77 -0
- package/skills/permdock-agents/references/runtimes.md +211 -0
- package/skills/permdock-approvals/SKILL.md +80 -0
- package/skills/permdock-approvals/references/stores-and-resume.md +83 -0
- package/skills/permdock-audit/SKILL.md +67 -0
- package/skills/permdock-credentials/SKILL.md +55 -0
- package/skills/permdock-credentials/references/tokens-keys-links.md +127 -0
- package/skills/permdock-data/SKILL.md +57 -0
- package/skills/permdock-data/references/queries-and-rls.md +54 -0
- package/skills/permdock-data/references/relationships.md +65 -0
- package/skills/permdock-tenancy/SKILL.md +77 -0
- package/skills/permdock-tenancy/references/ownership-and-custom-roles.md +83 -0
- package/skills/permdock-wire/SKILL.md +116 -0
- package/skills/permdock-wire/references/adapters.md +480 -0
- package/skills/permdock-wire/references/policy-options.md +43 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { $ as DecisionSink, C as Policy, at as LimitStore, ct as MembershipSource, ft as RelationSource, hr as Subject, mr as Principal, pt as RoleSource, sn as PolicySource, tt as EntitlementSource, xt as SnapshotSource } from "../policy-CInmNGkM.js";
|
|
2
|
+
import { d as Permission } from "../permissions-B4apnahQ.js";
|
|
3
|
+
import { f as ApprovalStore } from "../types-B8w5PFtS.js";
|
|
4
|
+
import { t as ApprovalHint } from "../errors-DbZ1YOnb.js";
|
|
5
|
+
import { u as JwtSubjectOptions } from "../types-bva3i4Gf.js";
|
|
6
|
+
import { o as OtelWrap } from "../types-B9dmiiab.js";
|
|
7
|
+
import { CacheHint, Icon, McpServer, PromptCallback, ReadResourceCallback, ReadResourceTemplateCallback, RegisteredPrompt, RegisteredResource, RegisteredResourceTemplate, RegisteredTool, ResourceMetadata, ResourceTemplate, ScopeChallengeHandler, StandardSchemaWithJSON, ToolAnnotations, ToolCallback, Variables } from "@modelcontextprotocol/server";
|
|
8
|
+
//#region src/mcp/types.d.ts
|
|
9
|
+
/** The verified `AuthInfo` the MCP SDK's bearer auth puts on `ctx.http.authInfo`. */
|
|
10
|
+
type McpAuthInfo = {
|
|
11
|
+
readonly token?: string;
|
|
12
|
+
readonly clientId?: string;
|
|
13
|
+
readonly scopes?: readonly string[];
|
|
14
|
+
readonly expiresAt?: number;
|
|
15
|
+
readonly resource?: URL;
|
|
16
|
+
/** Stamped by the SDK's bearer-auth helpers from their `resourceMetadataUrl` option. */
|
|
17
|
+
readonly resourceMetadataUrl?: string;
|
|
18
|
+
readonly extra?: Readonly<Record<string, unknown>>;
|
|
19
|
+
};
|
|
20
|
+
/** Base principal `subjectFromMcp` returns. */
|
|
21
|
+
type McpPrincipal = Principal & {
|
|
22
|
+
readonly issuer?: string;
|
|
23
|
+
readonly claims?: Readonly<Record<string, unknown>>;
|
|
24
|
+
};
|
|
25
|
+
type InputOf<TInput> = TInput extends StandardSchemaWithJSON ? StandardSchemaWithJSON.InferOutput<TInput> : undefined;
|
|
26
|
+
type Guard<TArgs extends readonly unknown[]> = {
|
|
27
|
+
readonly permission: Permission;
|
|
28
|
+
/** Loads the row to decide on; `null` or a throw denies. */
|
|
29
|
+
readonly data?: (...args: TArgs) => unknown;
|
|
30
|
+
};
|
|
31
|
+
type McpToolConfig<TInput extends StandardSchemaWithJSON | undefined = undefined, TOutput extends StandardSchemaWithJSON | undefined = undefined> = Guard<[args: InputOf<TInput>]> & {
|
|
32
|
+
readonly title?: string;
|
|
33
|
+
readonly description?: string;
|
|
34
|
+
readonly inputSchema?: TInput;
|
|
35
|
+
readonly outputSchema?: TOutput;
|
|
36
|
+
readonly annotations?: ToolAnnotations;
|
|
37
|
+
readonly icons?: Icon[];
|
|
38
|
+
readonly scopeChallenge?: ScopeChallengeHandler;
|
|
39
|
+
readonly _meta?: Record<string, unknown>;
|
|
40
|
+
/**
|
|
41
|
+
* Decide again when the handler resolves, without consuming quota, and
|
|
42
|
+
* return the refusal instead of the result unless the call is still
|
|
43
|
+
* allowed. The re-check withholds the result; it cannot undo the work.
|
|
44
|
+
*/
|
|
45
|
+
readonly longRunning?: boolean;
|
|
46
|
+
};
|
|
47
|
+
type McpResourceConfig<TArgs extends readonly unknown[]> = Guard<TArgs> & ResourceMetadata & {
|
|
48
|
+
readonly cacheHint?: CacheHint;
|
|
49
|
+
readonly scopeChallenge?: ScopeChallengeHandler;
|
|
50
|
+
};
|
|
51
|
+
type McpPromptConfig<TArgs extends StandardSchemaWithJSON | undefined = undefined> = Guard<[args: InputOf<TArgs>]> & {
|
|
52
|
+
readonly title?: string;
|
|
53
|
+
readonly description?: string;
|
|
54
|
+
readonly argsSchema?: TArgs;
|
|
55
|
+
readonly icons?: Icon[];
|
|
56
|
+
readonly scopeChallenge?: ScopeChallengeHandler;
|
|
57
|
+
readonly _meta?: Record<string, unknown>;
|
|
58
|
+
};
|
|
59
|
+
/** `McpServer` whose `register*` methods take a `permission` and decide first. */
|
|
60
|
+
type GuardedMcpServer = Omit<McpServer, "registerTool" | "registerResource" | "registerPrompt"> & {
|
|
61
|
+
registerTool<TInput extends StandardSchemaWithJSON | undefined = undefined, TOutput extends StandardSchemaWithJSON | undefined = undefined>(name: string, config: McpToolConfig<TInput, TOutput>, handler: ToolCallback<TInput>): RegisteredTool;
|
|
62
|
+
registerResource(name: string, uri: string, config: McpResourceConfig<[uri: URL]>, read: ReadResourceCallback): RegisteredResource;
|
|
63
|
+
registerResource(name: string, template: ResourceTemplate, config: McpResourceConfig<[uri: URL, variables: Variables]>, read: ReadResourceTemplateCallback): RegisteredResourceTemplate;
|
|
64
|
+
registerPrompt<TArgs extends StandardSchemaWithJSON | undefined = undefined>(name: string, config: McpPromptConfig<TArgs>, handler: PromptCallback<TArgs>): RegisteredPrompt;
|
|
65
|
+
};
|
|
66
|
+
type McpPermDockOptions<TUser = unknown> = {
|
|
67
|
+
/** Receives the verified auth info, or `{}` on a transport without one (stdio). */
|
|
68
|
+
readonly subject: (authInfo: McpAuthInfo) => TUser | Promise<TUser>;
|
|
69
|
+
readonly tenant?: string | ((authInfo: McpAuthInfo) => string | undefined | Promise<string | undefined>);
|
|
70
|
+
/** Deny every call and list nothing when the transport carries no auth info. */
|
|
71
|
+
readonly requireAuthInfo?: boolean;
|
|
72
|
+
/**
|
|
73
|
+
* This server's RFC 8707 resource identifier. A token whose
|
|
74
|
+
* `authInfo.resource` is absent or different is refused with `invalid_token`.
|
|
75
|
+
*/
|
|
76
|
+
readonly resource?: string | URL;
|
|
77
|
+
/**
|
|
78
|
+
* Where a human approves. With `at` set, `approval-required` answers a
|
|
79
|
+
* client that supports URL elicitation with an `input_required` result.
|
|
80
|
+
*/
|
|
81
|
+
readonly approval?: ApprovalHint;
|
|
82
|
+
/** Where the user re-authenticates; `acr_values` and `max_age` are appended to `at`. */
|
|
83
|
+
readonly stepUp?: {
|
|
84
|
+
readonly at: string;
|
|
85
|
+
};
|
|
86
|
+
/**
|
|
87
|
+
* Seals the approval token into `requestState`. Pass the codec from the
|
|
88
|
+
* SDK's `createRequestStateCodec` when the server verifies request state.
|
|
89
|
+
*/
|
|
90
|
+
readonly requestState?: {
|
|
91
|
+
readonly mint: (token: string, context?: never) => Promise<string>;
|
|
92
|
+
};
|
|
93
|
+
readonly memberships?: MembershipSource | readonly MembershipSource[];
|
|
94
|
+
/** The object graph for relation grants that walk a parent chain; without it they deny. */
|
|
95
|
+
readonly relations?: RelationSource;
|
|
96
|
+
readonly entitlements?: EntitlementSource;
|
|
97
|
+
readonly customRoles?: RoleSource;
|
|
98
|
+
/** Hosted grants, read once per instance; see `PolicySource`. */
|
|
99
|
+
readonly policies?: PolicySource;
|
|
100
|
+
readonly store?: ApprovalStore;
|
|
101
|
+
readonly sink?: DecisionSink;
|
|
102
|
+
readonly limits?: LimitStore;
|
|
103
|
+
readonly snapshots?: SnapshotSource;
|
|
104
|
+
/** `(permdock) => withOtel(permdock, options)` from `permdock/otel`. */
|
|
105
|
+
readonly otel?: OtelWrap;
|
|
106
|
+
};
|
|
107
|
+
type McpPermDock = {
|
|
108
|
+
/** Call before registering anything: later `register*` calls must carry a `permission`. */
|
|
109
|
+
readonly protectServer: (server: McpServer) => GuardedMcpServer;
|
|
110
|
+
};
|
|
111
|
+
//#endregion
|
|
112
|
+
//#region src/mcp/create.d.ts
|
|
113
|
+
/** `_meta` key on `tools/call`, `resources/read` and `prompts/get` carrying an approval token. */
|
|
114
|
+
export declare const APPROVAL_META_KEY = "dev.permdock/approval";
|
|
115
|
+
export declare function createPermDock<TUser, TPrincipal extends Principal = Principal>(policy: Policy<TUser, TPrincipal>, options: McpPermDockOptions<TUser>): McpPermDock;
|
|
116
|
+
//#endregion
|
|
117
|
+
//#region src/mcp/subject.d.ts
|
|
118
|
+
type McpSubjectOptions = Pick<JwtSubjectOptions, "claims" | "groupRoles" | "schema" | "delegation">;
|
|
119
|
+
/**
|
|
120
|
+
* Maps the `AuthInfo` the MCP SDK's bearer verification produced to a
|
|
121
|
+
* `Subject`: the principal from the verified claims in `extra`, the client
|
|
122
|
+
* as an `mcp-client` actor, the granted scopes as the delegation. Never
|
|
123
|
+
* verifies and never throws; anything unusable is the anonymous subject.
|
|
124
|
+
*/
|
|
125
|
+
export declare function subjectFromMcp(authInfo: McpAuthInfo | undefined, options?: McpSubjectOptions): Subject<McpPrincipal>;
|
|
126
|
+
//#endregion
|
|
127
|
+
export type { GuardedMcpServer, McpAuthInfo, McpPermDock, McpPermDockOptions, McpPrincipal, McpPromptConfig, McpResourceConfig, McpSubjectOptions, McpToolConfig };
|
|
@@ -0,0 +1,563 @@
|
|
|
1
|
+
import { t as compact } from "../compact-y6vfu2_f.js";
|
|
2
|
+
import { n as freezeDeep } from "../freeze-y1_jQL4v.js";
|
|
3
|
+
import { t as annotationsFor } from "../permissions-DC7GROPA.js";
|
|
4
|
+
import { t as describe } from "../describe-Bt1YD5uS.js";
|
|
5
|
+
import { s as wireDenials } from "../errors-Iy71s7Oj.js";
|
|
6
|
+
import { t as createPermDock$1 } from "../permdock-yoZ7EjxX.js";
|
|
7
|
+
import { t as anonymousSubject } from "../subject-C6dLxqHU.js";
|
|
8
|
+
import { t as mayUse } from "../may-use-CnFEjVEp.js";
|
|
9
|
+
import { l as storedApprovalToken, s as resumeDecision } from "../helpers-DnSOrhB9.js";
|
|
10
|
+
import { n as mapClaimsToSubject } from "../map-claims-B_6I1Rn-.js";
|
|
11
|
+
import { c as stepUpOf, r as bearerChallenge, s as protectedResourceMetadataUrl } from "../problem-Cgp9-KZA.js";
|
|
12
|
+
import { t as boundedMap } from "../lru-8q4WZjBV.js";
|
|
13
|
+
//#region src/mcp/create.ts
|
|
14
|
+
/** `_meta` key on `tools/call`, `resources/read` and `prompts/get` carrying an approval token. */
|
|
15
|
+
const APPROVAL_META_KEY = "dev.permdock/approval";
|
|
16
|
+
const SESSIONS_PER_SERVER = 1e3;
|
|
17
|
+
const CLIENT_CAPABILITIES = "io.modelcontextprotocol/clientCapabilities";
|
|
18
|
+
/** What a completion re-check still accepts after the handler ran. */
|
|
19
|
+
function stillAllowed(decision, approved) {
|
|
20
|
+
switch (decision.outcome) {
|
|
21
|
+
case "granted": return true;
|
|
22
|
+
case "approval-required": return approved !== void 0 && decision.token === approved;
|
|
23
|
+
case "denied": return decision.denials.length > 0 && decision.denials.every((denial) => denial.reason === "limit");
|
|
24
|
+
default: return decision;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function isRecord$1(value) {
|
|
28
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
29
|
+
}
|
|
30
|
+
function contextOf(value) {
|
|
31
|
+
return isRecord$1(value) ? value : {};
|
|
32
|
+
}
|
|
33
|
+
function approvalTokenOf(context) {
|
|
34
|
+
let fromState;
|
|
35
|
+
try {
|
|
36
|
+
fromState = context.mcpReq?.requestState?.();
|
|
37
|
+
} catch {
|
|
38
|
+
fromState = void 0;
|
|
39
|
+
}
|
|
40
|
+
if (typeof fromState === "string" && fromState !== "") return fromState;
|
|
41
|
+
const fromMeta = context.mcpReq?._meta?.[APPROVAL_META_KEY];
|
|
42
|
+
if (typeof fromMeta === "string" && fromMeta !== "") return fromMeta;
|
|
43
|
+
const fromAuth = context.http?.authInfo?.extra?.["approval"];
|
|
44
|
+
return typeof fromAuth === "string" && fromAuth !== "" ? fromAuth : void 0;
|
|
45
|
+
}
|
|
46
|
+
function authorizationDetailsOf(authInfo) {
|
|
47
|
+
const details = authInfo.extra?.["authorizationDetails"] ?? authInfo.extra?.["authorization_details"];
|
|
48
|
+
return Array.isArray(details) ? details : void 0;
|
|
49
|
+
}
|
|
50
|
+
function delegationOf(authInfo) {
|
|
51
|
+
return compact({
|
|
52
|
+
scopes: authInfo.scopes,
|
|
53
|
+
authorizationDetails: authorizationDetailsOf(authInfo)
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
function hasScope(authInfo, scope) {
|
|
57
|
+
return (authInfo.scopes ?? []).includes(scope);
|
|
58
|
+
}
|
|
59
|
+
function resourceMetadataOf(authInfo) {
|
|
60
|
+
if (authInfo.resourceMetadataUrl !== void 0) return authInfo.resourceMetadataUrl;
|
|
61
|
+
const resource = authInfo.resource;
|
|
62
|
+
return resource?.protocol === "https:" || resource?.protocol === "http:" ? protectedResourceMetadataUrl(resource) : void 0;
|
|
63
|
+
}
|
|
64
|
+
function sameResource(expected, actual) {
|
|
65
|
+
if (actual === void 0) return false;
|
|
66
|
+
try {
|
|
67
|
+
return new URL(String(expected)).href === new URL(String(actual)).href;
|
|
68
|
+
} catch {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function acceptsUrlElicitation(context, fallback) {
|
|
73
|
+
const fromEnvelope = context.mcpReq?.envelope?.[CLIENT_CAPABILITIES];
|
|
74
|
+
const capabilities = isRecord$1(fromEnvelope) ? fromEnvelope : fallback?.();
|
|
75
|
+
if (!isRecord$1(capabilities) || !isRecord$1(capabilities["elicitation"])) return false;
|
|
76
|
+
return isRecord$1(capabilities["elicitation"]["url"]);
|
|
77
|
+
}
|
|
78
|
+
function withParams(at, params) {
|
|
79
|
+
let url;
|
|
80
|
+
try {
|
|
81
|
+
url = new URL(at);
|
|
82
|
+
} catch {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
for (const [name, value] of Object.entries(params)) if (value !== void 0) url.searchParams.set(name, value);
|
|
86
|
+
return url.href;
|
|
87
|
+
}
|
|
88
|
+
function urlElicitation(key, message, url, requestState) {
|
|
89
|
+
return compact({
|
|
90
|
+
resultType: "input_required",
|
|
91
|
+
inputRequests: { [key]: {
|
|
92
|
+
method: "elicitation/create",
|
|
93
|
+
params: {
|
|
94
|
+
mode: "url",
|
|
95
|
+
message,
|
|
96
|
+
url
|
|
97
|
+
}
|
|
98
|
+
} },
|
|
99
|
+
requestState
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
function resourceRef(permission, data) {
|
|
103
|
+
if (isRecord$1(data)) {
|
|
104
|
+
const id = data["id"];
|
|
105
|
+
if (typeof id === "string" || typeof id === "number") return {
|
|
106
|
+
type: permission.resource,
|
|
107
|
+
id: String(id)
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
return { type: permission.resource };
|
|
111
|
+
}
|
|
112
|
+
function deniedRefusal(decision, permission, data) {
|
|
113
|
+
const alternatives = decision.alternatives.map((leaf) => leaf.key);
|
|
114
|
+
const resource = resourceRef(permission, data);
|
|
115
|
+
return {
|
|
116
|
+
text: `Denied: ${resource.id === void 0 ? permission.key : `${permission.key} on ${resource.id}`}.${alternatives.length === 0 ? "" : ` You may: ${alternatives.join(", ")}.`}`,
|
|
117
|
+
structured: {
|
|
118
|
+
outcome: "denied",
|
|
119
|
+
permission: permission.key,
|
|
120
|
+
resource,
|
|
121
|
+
denials: wireDenials(decision.denials),
|
|
122
|
+
alternatives,
|
|
123
|
+
detail: describe(decision).detail
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
function approvalRefusal(decision, permission, data) {
|
|
128
|
+
return {
|
|
129
|
+
text: describe(decision).detail,
|
|
130
|
+
structured: {
|
|
131
|
+
outcome: "approval-required",
|
|
132
|
+
permission: permission.key,
|
|
133
|
+
resource: resourceRef(permission, data),
|
|
134
|
+
token: decision.token
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
function plainRefusal(permission, text, extra) {
|
|
139
|
+
return {
|
|
140
|
+
text,
|
|
141
|
+
structured: {
|
|
142
|
+
outcome: "denied",
|
|
143
|
+
permission: permission.key,
|
|
144
|
+
resource: { type: permission.resource },
|
|
145
|
+
alternatives: [],
|
|
146
|
+
...extra
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
function toolRefusal(refusal) {
|
|
151
|
+
if (refusal.inputRequired !== void 0) return refusal.inputRequired;
|
|
152
|
+
return {
|
|
153
|
+
isError: true,
|
|
154
|
+
content: [{
|
|
155
|
+
type: "text",
|
|
156
|
+
text: refusal.text
|
|
157
|
+
}],
|
|
158
|
+
structuredContent: refusal.structured
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
function requirePermission(kind, name, config) {
|
|
162
|
+
const permission = isRecord$1(config) ? config["permission"] : void 0;
|
|
163
|
+
if (!isRecord$1(permission) || typeof permission["key"] !== "string") throw new TypeError(`permdock/mcp: ${kind} ${name} has no permission; every guarded registration needs one.`);
|
|
164
|
+
return permission;
|
|
165
|
+
}
|
|
166
|
+
function sessionKey(context) {
|
|
167
|
+
return context.sessionId ?? "";
|
|
168
|
+
}
|
|
169
|
+
function throwRefusal(refusal) {
|
|
170
|
+
if (refusal.inputRequired !== void 0) return refusal.inputRequired;
|
|
171
|
+
throw new Error(refusal.text);
|
|
172
|
+
}
|
|
173
|
+
function challengeFor(permission, own) {
|
|
174
|
+
return async (context) => {
|
|
175
|
+
const first = await own?.(context);
|
|
176
|
+
if (first !== void 0) return first;
|
|
177
|
+
const authInfo = context.authInfo;
|
|
178
|
+
if (authInfo === void 0 || hasScope(authInfo, permission.scope)) return;
|
|
179
|
+
return { scopes: [permission.scope] };
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
function guardUpdates(registered, wrap, rename, currentName) {
|
|
183
|
+
const update = registered.update.bind(registered);
|
|
184
|
+
registered.update = (updates) => {
|
|
185
|
+
const next = { ...updates };
|
|
186
|
+
if (typeof updates["callback"] === "function") next["callback"] = wrap(updates["callback"]);
|
|
187
|
+
if (rename !== void 0 && currentName !== void 0) {
|
|
188
|
+
const to = updates["name"];
|
|
189
|
+
if (typeof to === "string" || to === null) rename(currentName(), to);
|
|
190
|
+
}
|
|
191
|
+
update(next);
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
function createPermDock(policy, options) {
|
|
195
|
+
const instanceFor = async (authInfo) => {
|
|
196
|
+
const material = authInfo ?? {};
|
|
197
|
+
let user = null;
|
|
198
|
+
try {
|
|
199
|
+
user = await options.subject(material);
|
|
200
|
+
} catch {
|
|
201
|
+
user = null;
|
|
202
|
+
}
|
|
203
|
+
let tenant;
|
|
204
|
+
try {
|
|
205
|
+
tenant = typeof options.tenant === "function" ? await options.tenant(material) : options.tenant;
|
|
206
|
+
} catch {
|
|
207
|
+
tenant = void 0;
|
|
208
|
+
}
|
|
209
|
+
const clientId = authInfo?.clientId;
|
|
210
|
+
const built = await createPermDock$1(policy, user, compact({
|
|
211
|
+
tenant,
|
|
212
|
+
actor: typeof clientId === "string" && clientId !== "" ? {
|
|
213
|
+
id: clientId,
|
|
214
|
+
kind: "mcp-client"
|
|
215
|
+
} : void 0,
|
|
216
|
+
delegation: authInfo === void 0 ? void 0 : delegationOf(authInfo),
|
|
217
|
+
memberships: options.memberships,
|
|
218
|
+
relations: options.relations,
|
|
219
|
+
entitlements: options.entitlements,
|
|
220
|
+
customRoles: options.customRoles,
|
|
221
|
+
policies: options.policies,
|
|
222
|
+
sink: options.sink,
|
|
223
|
+
limits: options.limits
|
|
224
|
+
}));
|
|
225
|
+
return options.otel === void 0 ? built : options.otel(built);
|
|
226
|
+
};
|
|
227
|
+
const forThisServer = (authInfo) => options.resource === void 0 || sameResource(options.resource, authInfo.resource);
|
|
228
|
+
const reachable = (authInfo, permission) => authInfo === void 0 ? options.requireAuthInfo !== true : forThisServer(authInfo) && hasScope(authInfo, permission.scope);
|
|
229
|
+
const approvalInput = async (decision, handlerContext, context, capabilities) => {
|
|
230
|
+
const at = options.approval?.at;
|
|
231
|
+
if (at === void 0 || !acceptsUrlElicitation(context, capabilities)) return;
|
|
232
|
+
const url = withParams(at, { token: decision.token });
|
|
233
|
+
if (url === void 0) return;
|
|
234
|
+
const requestState = options.requestState === void 0 ? decision.token : await options.requestState.mint(decision.token, handlerContext);
|
|
235
|
+
return urlElicitation("approval", options.approval?.hint ?? describe(decision).detail, url, requestState);
|
|
236
|
+
};
|
|
237
|
+
const stepUpRefusal = (decision, permission, data, context, capabilities) => {
|
|
238
|
+
const base = deniedRefusal(decision, permission, data);
|
|
239
|
+
const { acrValues, maxAge } = stepUpOf(decision);
|
|
240
|
+
const at = options.stepUp?.at;
|
|
241
|
+
const url = at === void 0 || !acceptsUrlElicitation(context, capabilities) ? void 0 : withParams(at, {
|
|
242
|
+
acr_values: acrValues?.join(" "),
|
|
243
|
+
max_age: maxAge === void 0 ? void 0 : String(maxAge)
|
|
244
|
+
});
|
|
245
|
+
return compact({
|
|
246
|
+
text: base.text,
|
|
247
|
+
structured: compact({
|
|
248
|
+
...base.structured,
|
|
249
|
+
error: "insufficient_user_authentication",
|
|
250
|
+
acr_values: acrValues?.join(" "),
|
|
251
|
+
max_age: maxAge,
|
|
252
|
+
www_authenticate: bearerChallenge(compact({
|
|
253
|
+
error: "insufficient_user_authentication",
|
|
254
|
+
acrValues,
|
|
255
|
+
maxAge
|
|
256
|
+
}))
|
|
257
|
+
}),
|
|
258
|
+
inputRequired: url === void 0 ? void 0 : urlElicitation("step_up", `Sign in again to use ${permission.key}.`, url, void 0)
|
|
259
|
+
});
|
|
260
|
+
};
|
|
261
|
+
const check = async (permission, load, context, handlerContext, capabilities, completion) => {
|
|
262
|
+
const authInfo = context.http?.authInfo;
|
|
263
|
+
if (authInfo === void 0 && options.requireAuthInfo === true) return {
|
|
264
|
+
ok: false,
|
|
265
|
+
refusal: plainRefusal(permission, "Denied: no verified credentials.", { error: "invalid_token" })
|
|
266
|
+
};
|
|
267
|
+
if (authInfo !== void 0 && !forThisServer(authInfo)) return {
|
|
268
|
+
ok: false,
|
|
269
|
+
refusal: plainRefusal(permission, "Denied: the token was not issued for this server.", { error: "invalid_token" })
|
|
270
|
+
};
|
|
271
|
+
if (authInfo !== void 0 && !hasScope(authInfo, permission.scope)) {
|
|
272
|
+
const resourceMetadata = resourceMetadataOf(authInfo);
|
|
273
|
+
return {
|
|
274
|
+
ok: false,
|
|
275
|
+
refusal: plainRefusal(permission, `Denied: ${permission.key} needs the ${permission.scope} scope.`, compact({
|
|
276
|
+
error: "insufficient_scope",
|
|
277
|
+
scope: permission.scope,
|
|
278
|
+
resource_metadata: resourceMetadata,
|
|
279
|
+
www_authenticate: bearerChallenge(compact({
|
|
280
|
+
error: "insufficient_scope",
|
|
281
|
+
scopes: [permission.scope],
|
|
282
|
+
resourceMetadata
|
|
283
|
+
}))
|
|
284
|
+
}))
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
let data;
|
|
288
|
+
if (load !== void 0) {
|
|
289
|
+
try {
|
|
290
|
+
data = await load();
|
|
291
|
+
} catch {
|
|
292
|
+
return {
|
|
293
|
+
ok: false,
|
|
294
|
+
refusal: plainRefusal(permission, `Denied: ${permission.key} failed closed.`, { denials: [{
|
|
295
|
+
role: null,
|
|
296
|
+
reason: "validation"
|
|
297
|
+
}] })
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
if (data === null || data === void 0) return {
|
|
301
|
+
ok: false,
|
|
302
|
+
refusal: plainRefusal(permission, `Denied: ${permission.key} found nothing to decide on.`, { denials: [{
|
|
303
|
+
role: null,
|
|
304
|
+
reason: "validation"
|
|
305
|
+
}] })
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
try {
|
|
309
|
+
const permdock = await instanceFor(authInfo);
|
|
310
|
+
const raw = permdock.decide(permission, data, {
|
|
311
|
+
source: completion === void 0 ? "adapter" : "simulate",
|
|
312
|
+
adapter: "mcp",
|
|
313
|
+
boundary: "mcp-args"
|
|
314
|
+
});
|
|
315
|
+
if (completion !== void 0) {
|
|
316
|
+
if (stillAllowed(raw, completion.approved)) return { ok: true };
|
|
317
|
+
return {
|
|
318
|
+
ok: false,
|
|
319
|
+
refusal: raw.outcome === "denied" ? deniedRefusal(raw, permission, data) : plainRefusal(permission, `Denied: ${permission.key} was revoked before the call completed.`, {})
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
const decision = await resumeDecision({
|
|
323
|
+
decision: raw,
|
|
324
|
+
permission,
|
|
325
|
+
subject: permdock.subject,
|
|
326
|
+
store: options.store,
|
|
327
|
+
resource: resourceRef(permission, data),
|
|
328
|
+
adapter: "mcp",
|
|
329
|
+
token: approvalTokenOf(context) ?? await storedApprovalToken(options.store, raw, false)
|
|
330
|
+
});
|
|
331
|
+
switch (decision.outcome) {
|
|
332
|
+
case "granted": return raw.outcome === "approval-required" ? {
|
|
333
|
+
ok: true,
|
|
334
|
+
approved: raw.token
|
|
335
|
+
} : { ok: true };
|
|
336
|
+
case "denied": return {
|
|
337
|
+
ok: false,
|
|
338
|
+
refusal: decision.denials.some((denial) => denial.reason === "insufficient-user-authentication") ? stepUpRefusal(decision, permission, data, context, capabilities) : deniedRefusal(decision, permission, data)
|
|
339
|
+
};
|
|
340
|
+
case "approval-required": return {
|
|
341
|
+
ok: false,
|
|
342
|
+
refusal: compact({
|
|
343
|
+
...approvalRefusal(decision, permission, data),
|
|
344
|
+
inputRequired: await approvalInput(decision, handlerContext, context, capabilities)
|
|
345
|
+
})
|
|
346
|
+
};
|
|
347
|
+
default: return decision;
|
|
348
|
+
}
|
|
349
|
+
} catch {
|
|
350
|
+
return {
|
|
351
|
+
ok: false,
|
|
352
|
+
refusal: plainRefusal(permission, `Denied: ${permission.key} failed closed.`, {})
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
};
|
|
356
|
+
const visible = async (context, permissionOf, keys) => {
|
|
357
|
+
const authInfo = context.http?.authInfo;
|
|
358
|
+
const shown = /* @__PURE__ */ new Set();
|
|
359
|
+
const guarded = [];
|
|
360
|
+
for (const key of keys) {
|
|
361
|
+
const permission = permissionOf(key);
|
|
362
|
+
if (permission === void 0) shown.add(key);
|
|
363
|
+
else if (reachable(authInfo, permission)) guarded.push({
|
|
364
|
+
key,
|
|
365
|
+
permission
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
if (guarded.length === 0) return shown;
|
|
369
|
+
let permdock;
|
|
370
|
+
try {
|
|
371
|
+
permdock = await instanceFor(authInfo);
|
|
372
|
+
} catch {
|
|
373
|
+
return shown;
|
|
374
|
+
}
|
|
375
|
+
for (const entry of guarded) if (mayUse(permdock, entry.permission)) shown.add(entry.key);
|
|
376
|
+
return shown;
|
|
377
|
+
};
|
|
378
|
+
const guard = (permission, load, handler, dataArgs, onRefusal, extra = {}) => async (...params) => {
|
|
379
|
+
const raw = params.at(-1);
|
|
380
|
+
const context = contextOf(raw);
|
|
381
|
+
const loader = load === void 0 ? void 0 : () => load(...dataArgs(params));
|
|
382
|
+
const checked = await check(permission, loader, context, raw, extra.capabilities);
|
|
383
|
+
let result;
|
|
384
|
+
if (checked.ok) {
|
|
385
|
+
result = await handler(...params);
|
|
386
|
+
if (extra.longRunning === true) {
|
|
387
|
+
const again = await check(permission, loader, context, raw, extra.capabilities, { approved: checked.approved });
|
|
388
|
+
if (!again.ok) result = onRefusal(again.refusal);
|
|
389
|
+
}
|
|
390
|
+
} else result = onRefusal(checked.refusal);
|
|
391
|
+
await extra.after?.(context);
|
|
392
|
+
return result;
|
|
393
|
+
};
|
|
394
|
+
const protectServer = (server) => {
|
|
395
|
+
const tools = /* @__PURE__ */ new Map();
|
|
396
|
+
const prompts = /* @__PURE__ */ new Map();
|
|
397
|
+
const resources = /* @__PURE__ */ new Map();
|
|
398
|
+
const templates = [];
|
|
399
|
+
const lastListed = boundedMap(SESSIONS_PER_SERVER);
|
|
400
|
+
const toolPermission = (name) => tools.get(name);
|
|
401
|
+
const promptPermission = (name) => prompts.get(name);
|
|
402
|
+
const resourcePermission = (uri) => {
|
|
403
|
+
const direct = resources.get(uri);
|
|
404
|
+
if (direct !== void 0) return direct;
|
|
405
|
+
return templates.find((entry) => entry.template.uriTemplate.match(uri) !== null)?.permission;
|
|
406
|
+
};
|
|
407
|
+
const templatePermission = (uriTemplate) => templates.find((entry) => entry.template.uriTemplate.toString() === uriTemplate)?.permission;
|
|
408
|
+
const listFilter = (field, keyOf, permissionOf, remember) => async (result, rawContext) => {
|
|
409
|
+
if (!isRecord$1(result) || !Array.isArray(result[field])) return result;
|
|
410
|
+
const context = contextOf(rawContext);
|
|
411
|
+
const entries = result[field];
|
|
412
|
+
const shown = await visible(context, permissionOf, entries.map(keyOf));
|
|
413
|
+
const kept = entries.filter((entry) => shown.has(keyOf(entry)));
|
|
414
|
+
if (remember) lastListed.set(sessionKey(context), [...shown].toSorted().join(","));
|
|
415
|
+
return {
|
|
416
|
+
...result,
|
|
417
|
+
[field]: kept,
|
|
418
|
+
cacheScope: "private"
|
|
419
|
+
};
|
|
420
|
+
};
|
|
421
|
+
const filters = {
|
|
422
|
+
"tools/list": listFilter("tools", (entry) => entry.name, toolPermission, true),
|
|
423
|
+
"prompts/list": listFilter("prompts", (entry) => entry.name, promptPermission, false),
|
|
424
|
+
"resources/list": listFilter("resources", (entry) => entry.uri, resourcePermission, false),
|
|
425
|
+
"resources/templates/list": listFilter("resourceTemplates", (entry) => entry.uriTemplate, templatePermission, false)
|
|
426
|
+
};
|
|
427
|
+
const inner = server.server;
|
|
428
|
+
const capabilities = () => inner.getClientCapabilities?.();
|
|
429
|
+
const set = inner.setRequestHandler.bind(inner);
|
|
430
|
+
inner.setRequestHandler = (method, ...rest) => {
|
|
431
|
+
const filter = Object.hasOwn(filters, method) ? filters[method] : void 0;
|
|
432
|
+
const [handler] = rest;
|
|
433
|
+
if (filter !== void 0 && rest.length === 1 && typeof handler === "function") {
|
|
434
|
+
set(method, async (request, context) => filter(await handler(request, context), context));
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
set(method, ...rest);
|
|
438
|
+
};
|
|
439
|
+
for (const method of Object.keys(filters)) {
|
|
440
|
+
const existing = inner._getRequestHandler?.(method);
|
|
441
|
+
if (existing !== void 0) {
|
|
442
|
+
inner.removeRequestHandler(method);
|
|
443
|
+
inner.setRequestHandler(method, (request, context) => existing(request, context));
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
const announce = async (context) => {
|
|
447
|
+
const key = sessionKey(context);
|
|
448
|
+
const before = lastListed.get(key);
|
|
449
|
+
if (before === void 0) return;
|
|
450
|
+
const after = [...await visible(context, toolPermission, [...tools.keys()])].toSorted().join(",");
|
|
451
|
+
if (after === before) return;
|
|
452
|
+
lastListed.set(key, after);
|
|
453
|
+
try {
|
|
454
|
+
await (context.mcpReq?.notify?.({ method: "notifications/tools/list_changed" }) ?? inner.sendToolListChanged?.());
|
|
455
|
+
} catch {}
|
|
456
|
+
};
|
|
457
|
+
const guardTool = (permission, load, handler, longRunning) => guard(permission, load, handler, (params) => params.length >= 2 ? [params[0]] : [void 0], toolRefusal, {
|
|
458
|
+
after: announce,
|
|
459
|
+
longRunning,
|
|
460
|
+
capabilities
|
|
461
|
+
});
|
|
462
|
+
const originalTool = server.registerTool.bind(server);
|
|
463
|
+
const registerTool = (name, config, handler) => {
|
|
464
|
+
const permission = requirePermission("tool", name, config);
|
|
465
|
+
const { permission: _permission, data, scopeChallenge, longRunning: rawLongRunning, ...passthrough } = config;
|
|
466
|
+
const load = data;
|
|
467
|
+
const longRunning = rawLongRunning === true;
|
|
468
|
+
const registered = originalTool(name, {
|
|
469
|
+
...passthrough,
|
|
470
|
+
annotations: {
|
|
471
|
+
...annotationsFor(permission),
|
|
472
|
+
...passthrough["annotations"]
|
|
473
|
+
},
|
|
474
|
+
scopeChallenge: challengeFor(permission, scopeChallenge)
|
|
475
|
+
}, guardTool(permission, load, handler, longRunning));
|
|
476
|
+
tools.set(name, permission);
|
|
477
|
+
let current = name;
|
|
478
|
+
guardUpdates(registered, (callback) => guardTool(permission, load, callback, longRunning), (from, to) => {
|
|
479
|
+
tools.delete(from);
|
|
480
|
+
if (to !== null) {
|
|
481
|
+
tools.set(to, permission);
|
|
482
|
+
current = to;
|
|
483
|
+
}
|
|
484
|
+
}, () => current);
|
|
485
|
+
return registered;
|
|
486
|
+
};
|
|
487
|
+
const originalPrompt = server.registerPrompt.bind(server);
|
|
488
|
+
const registerPrompt = (name, config, handler) => {
|
|
489
|
+
const permission = requirePermission("prompt", name, config);
|
|
490
|
+
const { permission: _permission, data, scopeChallenge, ...passthrough } = config;
|
|
491
|
+
const load = data;
|
|
492
|
+
const wrap = (callback) => guard(permission, load, callback, (params) => params.length >= 2 ? [params[0]] : [void 0], throwRefusal, { capabilities });
|
|
493
|
+
const registered = originalPrompt(name, {
|
|
494
|
+
...passthrough,
|
|
495
|
+
scopeChallenge: challengeFor(permission, scopeChallenge)
|
|
496
|
+
}, wrap(handler));
|
|
497
|
+
prompts.set(name, permission);
|
|
498
|
+
guardUpdates(registered, wrap);
|
|
499
|
+
return registered;
|
|
500
|
+
};
|
|
501
|
+
const originalResource = server.registerResource.bind(server);
|
|
502
|
+
const registerResource = (name, uriOrTemplate, config, read) => {
|
|
503
|
+
const permission = requirePermission("resource", name, config);
|
|
504
|
+
const { permission: _permission, data, scopeChallenge, ...passthrough } = config;
|
|
505
|
+
const load = data;
|
|
506
|
+
const wrap = (callback) => guard(permission, load, callback, (params) => params.slice(0, -1), throwRefusal, { capabilities });
|
|
507
|
+
const registered = originalResource(name, uriOrTemplate, {
|
|
508
|
+
...passthrough,
|
|
509
|
+
scopeChallenge: challengeFor(permission, scopeChallenge)
|
|
510
|
+
}, wrap(read));
|
|
511
|
+
if (typeof uriOrTemplate === "string") resources.set(uriOrTemplate, permission);
|
|
512
|
+
else templates.push({
|
|
513
|
+
template: uriOrTemplate,
|
|
514
|
+
permission
|
|
515
|
+
});
|
|
516
|
+
guardUpdates(registered, wrap);
|
|
517
|
+
return registered;
|
|
518
|
+
};
|
|
519
|
+
const guarded = server;
|
|
520
|
+
guarded["registerTool"] = registerTool;
|
|
521
|
+
guarded["registerPrompt"] = registerPrompt;
|
|
522
|
+
guarded["registerResource"] = registerResource;
|
|
523
|
+
return server;
|
|
524
|
+
};
|
|
525
|
+
return { protectServer };
|
|
526
|
+
}
|
|
527
|
+
//#endregion
|
|
528
|
+
//#region src/mcp/subject.ts
|
|
529
|
+
function isRecord(value) {
|
|
530
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* Maps the `AuthInfo` the MCP SDK's bearer verification produced to a
|
|
534
|
+
* `Subject`: the principal from the verified claims in `extra`, the client
|
|
535
|
+
* as an `mcp-client` actor, the granted scopes as the delegation. Never
|
|
536
|
+
* verifies and never throws; anything unusable is the anonymous subject.
|
|
537
|
+
*/
|
|
538
|
+
function subjectFromMcp(authInfo, options = {}) {
|
|
539
|
+
try {
|
|
540
|
+
if (authInfo === void 0 || !isRecord(authInfo.extra)) return anonymousSubject();
|
|
541
|
+
const claims = compact({
|
|
542
|
+
...authInfo.extra,
|
|
543
|
+
authorization_details: authInfo.extra["authorizationDetails"] ?? authInfo.extra["authorization_details"],
|
|
544
|
+
scope: authInfo.scopes?.join(" "),
|
|
545
|
+
client_id: authInfo.clientId,
|
|
546
|
+
exp: authInfo.expiresAt
|
|
547
|
+
});
|
|
548
|
+
const mapped = mapClaimsToSubject(claims, options);
|
|
549
|
+
if (mapped.invalidClaims || mapped.invalidChain || mapped.subject.principal === null) return anonymousSubject();
|
|
550
|
+
const clientId = authInfo.clientId;
|
|
551
|
+
return freezeDeep(compact({
|
|
552
|
+
...mapped.subject,
|
|
553
|
+
actor: typeof clientId === "string" && clientId !== "" ? {
|
|
554
|
+
id: clientId,
|
|
555
|
+
kind: "mcp-client"
|
|
556
|
+
} : mapped.subject.actor
|
|
557
|
+
}));
|
|
558
|
+
} catch {
|
|
559
|
+
return anonymousSubject();
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
//#endregion
|
|
563
|
+
export { APPROVAL_META_KEY, createPermDock, subjectFromMcp };
|