permdock 0.0.0 → 0.2.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 +327 -1
- package/dist/a2a/index.d.ts +169 -0
- package/dist/a2a/index.js +232 -0
- package/dist/adapter-context-mlWsVGGg.js +23 -0
- package/dist/ai-sdk/index.d.ts +95 -0
- package/dist/ai-sdk/index.js +154 -0
- package/dist/approval-required-TzNB9oQA.js +6 -0
- package/dist/approvals/index.d.ts +3 -0
- package/dist/approvals/index.js +5 -0
- package/dist/approvals-DbYAuNkG.js +224 -0
- package/dist/approvers-DT4AVyIg.js +52 -0
- package/dist/arazzo-BAgw9ap6.js +70 -0
- package/dist/arazzo-cmvX2jDe.js +54 -0
- package/dist/authzen/index.d.ts +39 -0
- package/dist/authzen/index.js +302 -0
- package/dist/better-auth/index.d.ts +109 -0
- package/dist/better-auth/index.js +344 -0
- package/dist/better-supabase/index.d.ts +206 -0
- package/dist/better-supabase/index.js +620 -0
- package/dist/bind-CVy3Espv.d.ts +25 -0
- package/dist/bind-VnYn2HKq.js +97 -0
- package/dist/boundary-BM8a_P6E.js +13 -0
- package/dist/boundary-BmHxVH1J.d.ts +15 -0
- package/dist/budget-JqcRqyhK.d.ts +11 -0
- package/dist/budget-NbEtbJC_.js +11 -0
- package/dist/canonical-json-DemiK4Vp.js +26 -0
- package/dist/capability-4qPdO9gk.js +173 -0
- package/dist/capability-Dq43tT1u.d.ts +99 -0
- package/dist/catalog/index.d.ts +12 -0
- package/dist/catalog/index.js +2 -0
- package/dist/catalog-BjZSovXQ.js +2 -0
- package/dist/catalog-doc-Dsc9y7sd.js +309 -0
- package/dist/catalog-h2tlKFkq.js +48 -0
- package/dist/catalog-path-CnI0XTFK.js +12 -0
- package/dist/catalog-z8q6bi_r.js +55 -0
- package/dist/claude-agent/index.d.ts +72 -0
- package/dist/claude-agent/index.js +77 -0
- package/dist/clerk/index.d.ts +65 -0
- package/dist/clerk/index.js +301 -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/clients-C7zLFuWT.d.ts +12 -0
- package/dist/clients-DjU5ABMy.js +23 -0
- package/dist/cloud/index.d.ts +90 -0
- package/dist/cloud/index.js +404 -0
- package/dist/cloud-BGhHS-lY.js +50 -0
- package/dist/cloud-Bo3e9Ak3.js +118 -0
- package/dist/collect-DDDv_ESs.js +502 -0
- package/dist/collect-DLlujdHC.js +83 -0
- package/dist/collect-rG2IQ3t0.js +2 -0
- package/dist/commands-DWOrSQic.js +97 -0
- package/dist/compact-y6vfu2_f.js +19 -0
- package/dist/compare-pfyVnBjE.js +12 -0
- package/dist/compile/index.d.ts +3 -0
- package/dist/compile/index.js +2 -0
- package/dist/compile-Ba4blROR.js +613 -0
- package/dist/compile-CNnl6uIt.d.ts +94 -0
- package/dist/config-BJiM-xAm.js +157 -0
- package/dist/config-Bn00Z3ks.js +36 -0
- package/dist/config-report-Dw_JcUqT.js +44 -0
- package/dist/connection-CU8MQxRi.js +194 -0
- package/dist/context-Ttp0ekMT.js +82 -0
- package/dist/convex/index.d.ts +45 -0
- package/dist/convex/index.js +65 -0
- package/dist/create-BWyx3jua.d.ts +245 -0
- package/dist/create-CPhDL6qx.js +253 -0
- package/dist/credential-8P81ff4u.js +306 -0
- package/dist/delegation-D8pUEuce.js +103 -0
- package/dist/describe-B_gVZEVi.js +94 -0
- package/dist/describe-PdoqESvg.d.ts +36 -0
- package/dist/diff-B8Tunc3o.js +451 -0
- package/dist/diff-CzqLxZ77.js +48 -0
- package/dist/digest-B3kc_PgQ.d.ts +19 -0
- package/dist/digest-BI-6aa6d.js +27 -0
- package/dist/doctor-WpvG6p4k.js +1194 -0
- package/dist/doctor-collect-ByfYlNv1.js +664 -0
- package/dist/doctor-rOr-4ZfS.js +53 -0
- package/dist/drizzle/index.d.ts +73 -0
- package/dist/drizzle/index.js +160 -0
- package/dist/duration-xK2jGFVc.js +25 -0
- package/dist/elysia/index.d.ts +49 -0
- package/dist/elysia/index.js +59 -0
- package/dist/errors-CWfoNg9N.js +47 -0
- package/dist/errors-Cz5zn0mW.js +123 -0
- package/dist/errors-DAqevpNk.d.ts +125 -0
- package/dist/errors-DRnGZHzF.js +30 -0
- package/dist/evaluation-items-CfCCejQd.js +40 -0
- package/dist/evaluations-tz6D9A2G.js +461 -0
- package/dist/eve/index.d.ts +86 -0
- package/dist/eve/index.js +176 -0
- package/dist/expiry-BoBJ8Tvc.js +7 -0
- package/dist/express/index.d.ts +23 -0
- package/dist/express/index.js +78 -0
- package/dist/fastify/index.d.ts +33 -0
- package/dist/fastify/index.js +92 -0
- package/dist/fields-BMl1RxoX.js +26 -0
- package/dist/files-DWUN9PzI.js +125 -0
- package/dist/fixtures-ht28Cplr.js +67 -0
- package/dist/freeze-CrU8nQeX.js +60 -0
- package/dist/from-snapshot-C3sHpZNS.js +569 -0
- package/dist/generate-CH7ThJ14.js +173 -0
- package/dist/grantee-Cv5Sogfv.js +602 -0
- package/dist/graph-sql-B-dsAT6j.d.ts +29 -0
- package/dist/headers-BZSDieNT.js +6 -0
- package/dist/headers-Cf-zWvsO.d.ts +6 -0
- package/dist/helpers-H_KARDVC.js +421 -0
- package/dist/hono/index.d.ts +48 -0
- package/dist/hono/index.js +99 -0
- package/dist/hosted-DJQw46hn.js +266 -0
- package/dist/http-BIhmLfCc.js +95 -0
- package/dist/http-DPuDaChb.d.ts +13 -0
- package/dist/http-SO7qGttf.js +44 -0
- package/dist/ids-DC4V4CFu.js +18 -0
- package/dist/index-A0YNL8jZ.d.ts +177 -0
- package/dist/index-CtE07CBF.d.ts +6 -0
- package/dist/index-D3vbkoSI.d.ts +483 -0
- package/dist/index-D6_cSa1z.d.ts +201 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +26 -0
- package/dist/instance-options-BK_rNOSC.d.ts +11 -0
- package/dist/instance-options-Dhhcnm3y.js +22 -0
- package/dist/json-schema-BsAflUGW.js +106 -0
- package/dist/jwt/index.d.ts +91 -0
- package/dist/jwt/index.js +590 -0
- package/dist/kernel-GSQnMz3O.js +208 -0
- package/dist/kysely/index.d.ts +78 -0
- package/dist/kysely/index.js +129 -0
- package/dist/live-options-H2eG8dfJ.js +60 -0
- package/dist/load-DhWEB9P1.js +75 -0
- package/dist/load-DwJUafYN.js +2 -0
- package/dist/load-jose-BL6LH4bL.js +11 -0
- package/dist/lru-8q4WZjBV.js +28 -0
- package/dist/manifest-iaoBXHKc.js +783 -0
- package/dist/manifest-uDS9y5de.d.ts +206 -0
- package/dist/map-AcxK9o_D.js +147 -0
- package/dist/map-claims-Z4QZOiFF.js +369 -0
- package/dist/map-error-pgUWNNtG.js +23 -0
- package/dist/markers-B0hYLvMD.js +60 -0
- package/dist/may-use-DndQ5tQ-.js +77 -0
- package/dist/mcp/index.d.ts +159 -0
- package/dist/mcp/index.js +603 -0
- package/dist/nest/index.d.ts +75 -0
- package/dist/nest/index.js +261 -0
- package/dist/next/client.d.ts +23 -0
- package/dist/next/client.js +33 -0
- package/dist/next/index.d.ts +93 -0
- package/dist/next/index.js +180 -0
- package/dist/next/plugin.d.ts +14 -0
- package/dist/next/plugin.js +33 -0
- package/dist/node/index.d.ts +24 -0
- package/dist/node/index.js +34 -0
- package/dist/openai/index.d.ts +62 -0
- package/dist/openai/index.js +92 -0
- package/dist/openapi/index.d.ts +176 -0
- package/dist/openapi/index.js +3 -0
- package/dist/openapi-Bbg4wGVL.js +164 -0
- package/dist/openapi-BqRBvAng.js +538 -0
- package/dist/openapi-CqqEbHsL.js +250 -0
- package/dist/openapi-import-cmwetbzw.js +5959 -0
- package/dist/openapi-schema--ARjyfQr.js +6902 -0
- package/dist/orpc/index.d.ts +51 -0
- package/dist/orpc/index.js +145 -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-BuyjxhZV.js +53 -0
- package/dist/parse-local-manifest-BkGQNzAh.d.ts +10 -0
- package/dist/parse-local-manifest-BrMozJCo.js +36 -0
- package/dist/pdp/index.d.ts +22 -0
- package/dist/pdp/index.js +682 -0
- package/dist/peer-BOr_kJE1.js +31 -0
- package/dist/permdock-CzqXjCpU.js +3471 -0
- package/dist/permissions-CDgvKG5L.d.ts +52 -0
- package/dist/permissions-CpZrivYv.js +142 -0
- package/dist/permissions-DVoF6dMt.js +662 -0
- package/dist/permissions-DttnqqxC.d.ts +214 -0
- package/dist/pins-8ILTftC9.js +12 -0
- package/dist/policy-CFZAr_Al.d.ts +2252 -0
- package/dist/policy-D8UFyERw.d.ts +20 -0
- package/dist/policy-xyWg7QdR.js +1122 -0
- package/dist/powersync-A8x_Yyvh.js +2 -0
- package/dist/powersync-C7kLglqX.js +540 -0
- package/dist/powersync-yW1UUFzp.js +62 -0
- package/dist/prisma/index.d.ts +136 -0
- package/dist/prisma/index.js +455 -0
- package/dist/problem-B43ztKIm.js +191 -0
- package/dist/problem-details-CkL50EEf.d.ts +7 -0
- package/dist/problem-details-_AMOYz-w.js +144 -0
- package/dist/prompt-CXYhLfsS.js +32 -0
- package/dist/protected-CNsMzrVd.d.ts +27 -0
- package/dist/protected-Cs6kTaHo.js +100 -0
- package/dist/protected-bZs6rbT1.d.ts +40 -0
- package/dist/provider-Cjr62YMV.d.ts +6 -0
- package/dist/provider-DLzpb2Xz.js +52 -0
- package/dist/provider-store-DNyC1y48.js +43 -0
- package/dist/react/index.d.ts +8 -0
- package/dist/react/index.js +7 -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 +198 -0
- package/dist/react-native/index.js +696 -0
- package/dist/refs-KYb4ntaz.js +91 -0
- package/dist/relations-cjOL01hZ.js +505 -0
- package/dist/replay-BsOyTrdV.js +28 -0
- package/dist/replay-sqBGMnNs.d.ts +5 -0
- package/dist/resource-ref-CaS9yVPi.js +20 -0
- package/dist/revocations-Bhr0aMIC.js +27 -0
- package/dist/revocations-Cy06JNx2.d.ts +21 -0
- package/dist/rls-BMNydpHk.js +2521 -0
- package/dist/rls-CEvs64Kj.js +238 -0
- package/dist/rls-fields-BMasREhR.js +3502 -0
- package/dist/rls-graph-pc9fuXYm.js +435 -0
- package/dist/rls-import-D8F7j35Q.js +943 -0
- package/dist/rls-migrate-7wikfrVI.js +367 -0
- package/dist/rls-ownership-B5Lkq62W.js +843 -0
- package/dist/rls-shared-aOQ4hGTO.js +493 -0
- package/dist/rls-verify-D8B7dOTh.js +745 -0
- package/dist/row-check-Dt35czLy.d.ts +40 -0
- package/dist/rpc-DOK54_CS.js +89 -0
- package/dist/run-YXCJDNYk.js +174 -0
- package/dist/runtime-CB8gQFvS.js +11 -0
- package/dist/saas-Bj_FFkjN.js +1203 -0
- package/dist/schema-D3MnDMxa.js +300 -0
- package/dist/scim/index.d.ts +2 -0
- package/dist/scim/index.js +2 -0
- package/dist/scim-BFGBBAqR.js +1445 -0
- package/dist/scopes-CVdPrWN-.js +239 -0
- package/dist/server/index.d.ts +134 -0
- package/dist/server/index.js +11 -0
- package/dist/server-DlZ28Oz5.js +268 -0
- package/dist/sha256-BO0swRXL.js +150 -0
- package/dist/shared-DRu8VFVM.js +123 -0
- package/dist/sink-Bd9PSyfq.js +141 -0
- package/dist/sink-Gs6TTVa-.d.ts +104 -0
- package/dist/skills-Biq5uup3.js +192 -0
- package/dist/skills-JaymNCdK.js +56 -0
- package/dist/snapshot-Enxd0n-s.js +975 -0
- package/dist/snapshot-cache-D6jLelln.js +44 -0
- package/dist/snapshot-cache-DwQH8ufx.d.ts +45 -0
- package/dist/solid/index.d.ts +93 -0
- package/dist/solid/index.js +188 -0
- package/dist/sources-B0UvrJ21.js +534 -0
- package/dist/sources-BcJgw0lt.d.ts +192 -0
- package/dist/sql-xdg5f4Fi.js +27 -0
- package/dist/src-CSbUiozF.js +568 -0
- package/dist/ssf/index.d.ts +4 -0
- package/dist/ssf/index.js +596 -0
- package/dist/store-DMAWwAJV.js +371 -0
- package/dist/store-D_pjn91-.d.ts +36 -0
- package/dist/store-options-yO0V6gRn.js +716 -0
- package/dist/stores-4_sqiOA1.js +87 -0
- package/dist/stores-DVR5NHLE.d.ts +52 -0
- package/dist/stream-DDpvynSB.js +66 -0
- package/dist/stream-DlvG2hs-.d.ts +12 -0
- package/dist/style-CTm1AXDT.js +22 -0
- package/dist/subject-C6dLxqHU.js +24 -0
- package/dist/subject-settings-BwBmCdTR.js +128 -0
- package/dist/supabase/index.d.ts +183 -0
- package/dist/supabase/index.js +5 -0
- package/dist/supabase/middleware.d.ts +82 -0
- package/dist/supabase/middleware.js +102 -0
- package/dist/supabase-CwafwxxF.js +83 -0
- package/dist/supabase-DTBaC20O.js +774 -0
- package/dist/supabase-config-Bs44dare.js +472 -0
- package/dist/supabase-hook-BuxLQLpU.js +1814 -0
- package/dist/supabase-hook-D5qVd_D8.js +2 -0
- package/dist/supautils-DYZnzRWV.js +91 -0
- package/dist/svelte/PermissionBoundary.svelte +37 -0
- package/dist/svelte/Protected.svelte +40 -0
- package/dist/svelte/index.d.ts +13 -0
- package/dist/svelte/index.js +107 -0
- package/dist/svelte/runtime.d.ts +7 -0
- package/dist/svelte/runtime.js +4 -0
- package/dist/svelte/source.d.ts +13 -0
- package/dist/svelte/source.js +11 -0
- package/dist/terminal/index.d.ts +163 -0
- package/dist/terminal/index.js +833 -0
- package/dist/testing/index.d.ts +745 -0
- package/dist/testing/index.js +3672 -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/token-CIWxJOCh.js +50 -0
- package/dist/trpc/index.d.ts +61 -0
- package/dist/trpc/index.js +122 -0
- package/dist/types-9AWO9EGt.d.ts +197 -0
- package/dist/types-BPxgszlT.d.ts +550 -0
- package/dist/types-BVyImy5I.d.ts +120 -0
- package/dist/types-Bvc4oWT6.d.ts +9 -0
- package/dist/types-C3s9xVJG.d.ts +48 -0
- package/dist/types-Dnemwd77.d.ts +183 -0
- package/dist/types-kxznc7cn.d.ts +52 -0
- package/dist/types-vKyAMtcX.d.ts +157 -0
- package/dist/types-vNSDF7Dq.d.ts +66 -0
- package/dist/types-yF5LmQVD.d.ts +306 -0
- package/dist/unplugin/index.d.ts +7 -0
- package/dist/unplugin/index.js +31 -0
- package/dist/usage-D07N9QJ3.js +42 -0
- package/dist/usage-D82l1CwK.js +2 -0
- package/dist/usage-DG1oeCca.js +693 -0
- package/dist/validation-error-CnjZ4t92.js +33 -0
- package/dist/validation-error-CtF23SrC.d.ts +26 -0
- package/dist/validity-uOp8UpYn.js +245 -0
- package/dist/verifier-BxBo6I44.js +466 -0
- package/dist/version-BvacVTZR.js +17 -0
- package/dist/views-Soe7bGSC.js +95 -0
- package/dist/vocabulary-DA8a8zif.d.ts +56 -0
- package/dist/vue/index.d.ts +79 -0
- package/dist/vue/index.js +196 -0
- package/dist/watch-Etoa_Mvr.js +2 -0
- package/dist/watch-UihESpsJ.js +128 -0
- package/dist/webmcp/index.d.ts +75 -0
- package/dist/webmcp/index.js +307 -0
- package/package.json +450 -5
- package/rulesets/permdock.yaml +29 -0
- package/schemas/approval-request-v1.json +175 -0
- package/schemas/catalog-v1.json +535 -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 +144 -0
- package/schemas/supabase-manifest-v1.json +361 -0
- package/skills/permdock/SKILL.md +66 -0
- package/skills/permdock-agents/SKILL.md +77 -0
- package/skills/permdock-agents/references/runtimes.md +213 -0
- package/skills/permdock-approvals/SKILL.md +80 -0
- package/skills/permdock-approvals/references/stores-and-resume.md +94 -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 +132 -0
- package/skills/permdock-data/SKILL.md +57 -0
- package/skills/permdock-data/references/queries-and-rls.md +62 -0
- package/skills/permdock-data/references/relationships.md +67 -0
- package/skills/permdock-tenancy/SKILL.md +80 -0
- package/skills/permdock-tenancy/references/ownership-and-custom-roles.md +89 -0
- package/skills/permdock-wire/SKILL.md +116 -0
- package/skills/permdock-wire/references/adapters.md +492 -0
- package/skills/permdock-wire/references/policy-options.md +45 -0
|
@@ -0,0 +1,603 @@
|
|
|
1
|
+
import { t as compact } from "../compact-y6vfu2_f.js";
|
|
2
|
+
import { n as freezeDeep } from "../freeze-CrU8nQeX.js";
|
|
3
|
+
import { t as annotationsFor } from "../permissions-DVoF6dMt.js";
|
|
4
|
+
import { a as wireDenials, i as stepUpOf } from "../problem-details-_AMOYz-w.js";
|
|
5
|
+
import { t as describe } from "../describe-B_gVZEVi.js";
|
|
6
|
+
import { i as scopesReaching, r as challengeScope } from "../permdock-CzqXjCpU.js";
|
|
7
|
+
import { n as resourceRef } from "../resource-ref-CaS9yVPi.js";
|
|
8
|
+
import { t as anonymousSubject } from "../subject-C6dLxqHU.js";
|
|
9
|
+
import { t as mayUse } from "../may-use-DndQ5tQ-.js";
|
|
10
|
+
import { c as protectedResourceMetadataUrl, n as bearerChallenge } from "../problem-B43ztKIm.js";
|
|
11
|
+
import { t as clientNameOf } from "../clients-DjU5ABMy.js";
|
|
12
|
+
import { n as mapClaimsToSubject } from "../map-claims-Z4QZOiFF.js";
|
|
13
|
+
import { t as instanceOptions } from "../instance-options-Dhhcnm3y.js";
|
|
14
|
+
import { n as createAgentKernel } from "../kernel-GSQnMz3O.js";
|
|
15
|
+
import { t as boundedMap } from "../lru-8q4WZjBV.js";
|
|
16
|
+
//#region src/mcp/subject.ts
|
|
17
|
+
const DEFAULT_MCP_ACTOR_KIND = "mcp-client";
|
|
18
|
+
/** `kind`, or the default when absent; `undefined` for a kind that is not a non-empty string. */
|
|
19
|
+
function mcpActorKind(kind) {
|
|
20
|
+
if (kind === void 0) return DEFAULT_MCP_ACTOR_KIND;
|
|
21
|
+
return typeof kind === "string" && kind !== "" ? kind : void 0;
|
|
22
|
+
}
|
|
23
|
+
function isRecord$1(value) {
|
|
24
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Maps the `AuthInfo` the MCP SDK's bearer verification produced to a
|
|
28
|
+
* `Subject`: the principal from the verified claims in `extra`, the client
|
|
29
|
+
* as an actor of kind `actorKind` (`mcp-client` by default), the granted
|
|
30
|
+
* scopes as the delegation. Never verifies and never throws; anything
|
|
31
|
+
* unusable, including an `actorKind` that is not a non-empty string, is
|
|
32
|
+
* the anonymous subject.
|
|
33
|
+
*/
|
|
34
|
+
function subjectFromMcp(authInfo, options = {}) {
|
|
35
|
+
try {
|
|
36
|
+
const kind = mcpActorKind(options.actorKind);
|
|
37
|
+
if (authInfo === void 0 || !isRecord$1(authInfo.extra) || kind === void 0) return anonymousSubject();
|
|
38
|
+
const claims = compact({
|
|
39
|
+
...authInfo.extra,
|
|
40
|
+
authorization_details: authInfo.extra["authorizationDetails"] ?? authInfo.extra["authorization_details"],
|
|
41
|
+
scope: authInfo.scopes?.join(" "),
|
|
42
|
+
client_id: authInfo.clientId,
|
|
43
|
+
exp: authInfo.expiresAt
|
|
44
|
+
});
|
|
45
|
+
const mapped = mapClaimsToSubject(claims, options);
|
|
46
|
+
if (mapped.invalidClaims || mapped.invalidChain || mapped.subject.principal === null) return anonymousSubject();
|
|
47
|
+
const clientId = authInfo.clientId;
|
|
48
|
+
return freezeDeep(compact({
|
|
49
|
+
...mapped.subject,
|
|
50
|
+
actor: typeof clientId === "string" && clientId !== "" ? compact({
|
|
51
|
+
id: clientId,
|
|
52
|
+
kind,
|
|
53
|
+
client: clientNameOf(options.clients, clientId)
|
|
54
|
+
}) : mapped.subject.actor
|
|
55
|
+
}));
|
|
56
|
+
} catch {
|
|
57
|
+
return anonymousSubject();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
//#endregion
|
|
61
|
+
//#region src/mcp/create.ts
|
|
62
|
+
/** `_meta` key on `tools/call`, `resources/read` and `prompts/get` carrying an approval token. */
|
|
63
|
+
const APPROVAL_META_KEY = "dev.permdock/approval";
|
|
64
|
+
const SESSIONS_PER_SERVER = 1e3;
|
|
65
|
+
const CLIENT_CAPABILITIES = "io.modelcontextprotocol/clientCapabilities";
|
|
66
|
+
const PROTOCOL_VERSION = "io.modelcontextprotocol/protocolVersion";
|
|
67
|
+
/** The first protocol revision whose list results define `ttlMs` and `cacheScope`. */
|
|
68
|
+
const CACHEABLE_LISTS_SINCE = "2026-07-28";
|
|
69
|
+
/** What a completion re-check still accepts after the handler ran. */
|
|
70
|
+
function stillAllowed(decision, approved) {
|
|
71
|
+
switch (decision.outcome) {
|
|
72
|
+
case "granted": return true;
|
|
73
|
+
case "approval-required": return approved !== void 0 && decision.token === approved;
|
|
74
|
+
case "denied": return decision.denials.length > 0 && decision.denials.every((denial) => denial.reason === "limit");
|
|
75
|
+
default: return decision;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function isRecord(value) {
|
|
79
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
80
|
+
}
|
|
81
|
+
function contextOf(value) {
|
|
82
|
+
return isRecord(value) ? value : {};
|
|
83
|
+
}
|
|
84
|
+
function approvalTokenOf(context) {
|
|
85
|
+
let fromState;
|
|
86
|
+
try {
|
|
87
|
+
fromState = context.mcpReq?.requestState?.();
|
|
88
|
+
} catch {
|
|
89
|
+
fromState = void 0;
|
|
90
|
+
}
|
|
91
|
+
if (typeof fromState === "string" && fromState !== "") return fromState;
|
|
92
|
+
const fromMeta = context.mcpReq?._meta?.[APPROVAL_META_KEY];
|
|
93
|
+
if (typeof fromMeta === "string" && fromMeta !== "") return fromMeta;
|
|
94
|
+
const fromAuth = context.http?.authInfo?.extra?.["approval"];
|
|
95
|
+
return typeof fromAuth === "string" && fromAuth !== "" ? fromAuth : void 0;
|
|
96
|
+
}
|
|
97
|
+
function authorizationDetailsOf(authInfo) {
|
|
98
|
+
const details = authInfo.extra?.["authorizationDetails"] ?? authInfo.extra?.["authorization_details"];
|
|
99
|
+
return Array.isArray(details) ? details : void 0;
|
|
100
|
+
}
|
|
101
|
+
function delegationOf(authInfo) {
|
|
102
|
+
return compact({
|
|
103
|
+
scopes: authInfo.scopes,
|
|
104
|
+
authorizationDetails: authorizationDetailsOf(authInfo)
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
function hasScope(authInfo, scopes) {
|
|
108
|
+
return scopes.some((scope) => (authInfo.scopes ?? []).includes(scope));
|
|
109
|
+
}
|
|
110
|
+
function resourceMetadataOf(authInfo) {
|
|
111
|
+
if (authInfo.resourceMetadataUrl !== void 0) return authInfo.resourceMetadataUrl;
|
|
112
|
+
const resource = authInfo.resource;
|
|
113
|
+
return resource?.protocol === "https:" || resource?.protocol === "http:" ? protectedResourceMetadataUrl(resource) : void 0;
|
|
114
|
+
}
|
|
115
|
+
function sameResource(expected, actual) {
|
|
116
|
+
if (actual === void 0) return false;
|
|
117
|
+
try {
|
|
118
|
+
return new URL(String(expected)).href === new URL(String(actual)).href;
|
|
119
|
+
} catch {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Whether the request was sent under a revision that defines `cacheScope` on
|
|
125
|
+
* list results. Only those requests carry the per-request envelope; a
|
|
126
|
+
* 2025-era request has none and gets no cache fields.
|
|
127
|
+
*/
|
|
128
|
+
function definesCacheScope(context) {
|
|
129
|
+
const version = context.mcpReq?.envelope?.[PROTOCOL_VERSION];
|
|
130
|
+
return typeof version === "string" && version >= CACHEABLE_LISTS_SINCE;
|
|
131
|
+
}
|
|
132
|
+
function acceptsUrlElicitation(context, fallback) {
|
|
133
|
+
const fromEnvelope = context.mcpReq?.envelope?.[CLIENT_CAPABILITIES];
|
|
134
|
+
const capabilities = isRecord(fromEnvelope) ? fromEnvelope : fallback?.();
|
|
135
|
+
if (!isRecord(capabilities) || !isRecord(capabilities["elicitation"])) return false;
|
|
136
|
+
return isRecord(capabilities["elicitation"]["url"]);
|
|
137
|
+
}
|
|
138
|
+
function withParams(at, params) {
|
|
139
|
+
let url;
|
|
140
|
+
try {
|
|
141
|
+
url = new URL(at);
|
|
142
|
+
} catch {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
for (const [name, value] of Object.entries(params)) if (value !== void 0) url.searchParams.set(name, value);
|
|
146
|
+
return url.href;
|
|
147
|
+
}
|
|
148
|
+
function urlElicitation(key, message, url, requestState) {
|
|
149
|
+
return compact({
|
|
150
|
+
resultType: "input_required",
|
|
151
|
+
inputRequests: { [key]: {
|
|
152
|
+
method: "elicitation/create",
|
|
153
|
+
params: {
|
|
154
|
+
mode: "url",
|
|
155
|
+
message,
|
|
156
|
+
url
|
|
157
|
+
}
|
|
158
|
+
} },
|
|
159
|
+
requestState
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
function deniedRefusal(decision, permission, data) {
|
|
163
|
+
const alternatives = decision.alternatives.map((leaf) => leaf.key);
|
|
164
|
+
const resource = resourceRef(permission, data);
|
|
165
|
+
return {
|
|
166
|
+
text: `Denied: ${resource.id === void 0 ? permission.key : `${permission.key} on ${resource.id}`}.${alternatives.length === 0 ? "" : ` You may: ${alternatives.join(", ")}.`}`,
|
|
167
|
+
structured: {
|
|
168
|
+
outcome: "denied",
|
|
169
|
+
permission: permission.key,
|
|
170
|
+
resource,
|
|
171
|
+
denials: wireDenials(decision.denials),
|
|
172
|
+
alternatives,
|
|
173
|
+
detail: describe(decision).detail
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
function approvalRefusal(decision, permission, data) {
|
|
178
|
+
return {
|
|
179
|
+
text: describe(decision).detail,
|
|
180
|
+
structured: {
|
|
181
|
+
outcome: "approval-required",
|
|
182
|
+
permission: permission.key,
|
|
183
|
+
resource: resourceRef(permission, data),
|
|
184
|
+
token: decision.token
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
function plainRefusal(permission, text, extra) {
|
|
189
|
+
return {
|
|
190
|
+
text,
|
|
191
|
+
structured: {
|
|
192
|
+
outcome: "denied",
|
|
193
|
+
permission: permission.key,
|
|
194
|
+
resource: { type: permission.resource },
|
|
195
|
+
alternatives: [],
|
|
196
|
+
...extra
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
function tenantOf(tenant) {
|
|
201
|
+
if (typeof tenant !== "function") return tenant;
|
|
202
|
+
return (call) => tenant(call.authInfo ?? {});
|
|
203
|
+
}
|
|
204
|
+
function failedRefusal(permission, failure) {
|
|
205
|
+
switch (failure) {
|
|
206
|
+
case "load-failed": return plainRefusal(permission, `Denied: ${permission.key} failed closed.`, { denials: [{
|
|
207
|
+
role: null,
|
|
208
|
+
reason: "validation"
|
|
209
|
+
}] });
|
|
210
|
+
case "no-data": return plainRefusal(permission, `Denied: ${permission.key} found nothing to decide on.`, { denials: [{
|
|
211
|
+
role: null,
|
|
212
|
+
reason: "validation"
|
|
213
|
+
}] });
|
|
214
|
+
case "failed": return plainRefusal(permission, `Denied: ${permission.key} failed closed.`, {});
|
|
215
|
+
default: return failure;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
function toolRefusal(refusal) {
|
|
219
|
+
if (refusal.inputRequired !== void 0) return refusal.inputRequired;
|
|
220
|
+
return {
|
|
221
|
+
isError: true,
|
|
222
|
+
content: [{
|
|
223
|
+
type: "text",
|
|
224
|
+
text: refusal.text
|
|
225
|
+
}],
|
|
226
|
+
structuredContent: refusal.structured
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
function requirePermission(kind, name, config) {
|
|
230
|
+
const permission = isRecord(config) ? config["permission"] : void 0;
|
|
231
|
+
if (!isRecord(permission) || typeof permission["key"] !== "string") throw new TypeError(`permdock/mcp: ${kind} ${name} has no permission; every guarded registration needs one.`);
|
|
232
|
+
return permission;
|
|
233
|
+
}
|
|
234
|
+
function sessionKey(context) {
|
|
235
|
+
return context.sessionId ?? "";
|
|
236
|
+
}
|
|
237
|
+
function throwRefusal(refusal) {
|
|
238
|
+
if (refusal.inputRequired !== void 0) return refusal.inputRequired;
|
|
239
|
+
throw new Error(refusal.text);
|
|
240
|
+
}
|
|
241
|
+
function declaredScopes(name, value) {
|
|
242
|
+
if (value === void 0) return;
|
|
243
|
+
const scopes = Array.isArray(value) ? value : [];
|
|
244
|
+
const names = scopes.filter((scope) => typeof scope === "string" && scope !== "");
|
|
245
|
+
const [first, ...rest] = names;
|
|
246
|
+
if (first === void 0 || names.length !== scopes.length) throw new TypeError(`permdock/mcp: tool ${name} sets oauthScopes, which must be a non-empty list of scope names.`);
|
|
247
|
+
return Object.freeze([first, ...rest]);
|
|
248
|
+
}
|
|
249
|
+
function challengeFor(own, reaching, challenged) {
|
|
250
|
+
return async (context) => {
|
|
251
|
+
const first = await own?.(context);
|
|
252
|
+
if (first !== void 0) return first;
|
|
253
|
+
const authInfo = context.authInfo;
|
|
254
|
+
if (authInfo === void 0 || hasScope(authInfo, reaching)) return;
|
|
255
|
+
return { scopes: [challenged] };
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
function guardUpdates(registered, wrap, rename, currentName) {
|
|
259
|
+
const update = registered.update.bind(registered);
|
|
260
|
+
registered.update = (updates) => {
|
|
261
|
+
const next = { ...updates };
|
|
262
|
+
if (typeof updates["callback"] === "function") next["callback"] = wrap(updates["callback"]);
|
|
263
|
+
if (rename !== void 0 && currentName !== void 0) {
|
|
264
|
+
const to = updates["name"];
|
|
265
|
+
if (typeof to === "string" || to === null) rename(currentName(), to);
|
|
266
|
+
}
|
|
267
|
+
update(next);
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
function createPermDock(policy, options) {
|
|
271
|
+
const actorKind = mcpActorKind(options.actorKind);
|
|
272
|
+
if (actorKind === void 0) throw new TypeError("permdock/mcp: actorKind must be a non-empty string");
|
|
273
|
+
const kernel = createAgentKernel(policy, compact({
|
|
274
|
+
subject: (call) => options.subject(call.authInfo ?? {}),
|
|
275
|
+
actor: (call) => {
|
|
276
|
+
const clientId = call.authInfo?.clientId;
|
|
277
|
+
return typeof clientId === "string" && clientId !== "" ? compact({
|
|
278
|
+
id: clientId,
|
|
279
|
+
kind: actorKind,
|
|
280
|
+
client: clientNameOf(options.clients, clientId)
|
|
281
|
+
}) : void 0;
|
|
282
|
+
},
|
|
283
|
+
delegation: (call) => call.authInfo === void 0 ? void 0 : delegationOf(call.authInfo),
|
|
284
|
+
tenant: tenantOf(options.tenant),
|
|
285
|
+
store: options.store,
|
|
286
|
+
adapter: "mcp",
|
|
287
|
+
boundary: "mcp-args",
|
|
288
|
+
wrap: options.otel,
|
|
289
|
+
...instanceOptions(options)
|
|
290
|
+
}));
|
|
291
|
+
const forThisServer = (authInfo) => options.resource === void 0 || sameResource(options.resource, authInfo.resource);
|
|
292
|
+
const reachOf = (permission, declared) => declared === void 0 ? {
|
|
293
|
+
scopes: scopesReaching(policy, permission),
|
|
294
|
+
challenge: challengeScope(policy, permission)
|
|
295
|
+
} : {
|
|
296
|
+
scopes: declared,
|
|
297
|
+
challenge: declared[0]
|
|
298
|
+
};
|
|
299
|
+
const reachable = (authInfo, reach) => authInfo === void 0 ? options.requireAuthInfo !== true : forThisServer(authInfo) && hasScope(authInfo, reach.scopes);
|
|
300
|
+
const approvalInput = async (decision, handlerContext, context, capabilities) => {
|
|
301
|
+
const at = options.approval?.at;
|
|
302
|
+
if (at === void 0 || !acceptsUrlElicitation(context, capabilities)) return;
|
|
303
|
+
const url = withParams(at, { token: decision.token });
|
|
304
|
+
if (url === void 0) return;
|
|
305
|
+
const requestState = options.requestState === void 0 ? decision.token : await options.requestState.mint(decision.token, handlerContext);
|
|
306
|
+
return urlElicitation("approval", options.approval?.hint ?? describe(decision).detail, url, requestState);
|
|
307
|
+
};
|
|
308
|
+
const stepUpRefusal = (decision, permission, data, context, capabilities) => {
|
|
309
|
+
const base = deniedRefusal(decision, permission, data);
|
|
310
|
+
const { acrValues, maxAge } = stepUpOf(decision);
|
|
311
|
+
const at = options.stepUp?.at;
|
|
312
|
+
const url = at === void 0 || !acceptsUrlElicitation(context, capabilities) ? void 0 : withParams(at, {
|
|
313
|
+
acr_values: acrValues?.join(" "),
|
|
314
|
+
max_age: maxAge === void 0 ? void 0 : String(maxAge)
|
|
315
|
+
});
|
|
316
|
+
return compact({
|
|
317
|
+
text: base.text,
|
|
318
|
+
structured: compact({
|
|
319
|
+
...base.structured,
|
|
320
|
+
error: "insufficient_user_authentication",
|
|
321
|
+
acr_values: acrValues?.join(" "),
|
|
322
|
+
max_age: maxAge,
|
|
323
|
+
www_authenticate: bearerChallenge(compact({
|
|
324
|
+
error: "insufficient_user_authentication",
|
|
325
|
+
acrValues,
|
|
326
|
+
maxAge
|
|
327
|
+
}))
|
|
328
|
+
}),
|
|
329
|
+
inputRequired: url === void 0 ? void 0 : urlElicitation("step_up", `Sign in again to use ${permission.key}.`, url, void 0)
|
|
330
|
+
});
|
|
331
|
+
};
|
|
332
|
+
const check = async (permission, reach, load, context, handlerContext, capabilities, completion) => {
|
|
333
|
+
const authInfo = context.http?.authInfo;
|
|
334
|
+
if (authInfo === void 0 && options.requireAuthInfo === true) return {
|
|
335
|
+
ok: false,
|
|
336
|
+
refusal: plainRefusal(permission, "Denied: no verified credentials.", { error: "invalid_token" })
|
|
337
|
+
};
|
|
338
|
+
if (authInfo !== void 0 && !forThisServer(authInfo)) return {
|
|
339
|
+
ok: false,
|
|
340
|
+
refusal: plainRefusal(permission, "Denied: the token was not issued for this server.", { error: "invalid_token" })
|
|
341
|
+
};
|
|
342
|
+
if (authInfo !== void 0 && !hasScope(authInfo, reach.scopes)) {
|
|
343
|
+
const resourceMetadata = resourceMetadataOf(authInfo);
|
|
344
|
+
const challenged = reach.challenge;
|
|
345
|
+
return {
|
|
346
|
+
ok: false,
|
|
347
|
+
refusal: plainRefusal(permission, `Denied: ${permission.key} needs the ${challenged} scope.`, compact({
|
|
348
|
+
error: "insufficient_scope",
|
|
349
|
+
scope: challenged,
|
|
350
|
+
resource_metadata: resourceMetadata,
|
|
351
|
+
www_authenticate: bearerChallenge(compact({
|
|
352
|
+
error: "insufficient_scope",
|
|
353
|
+
scopes: [challenged],
|
|
354
|
+
resourceMetadata
|
|
355
|
+
}))
|
|
356
|
+
}))
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
const binding = compact({
|
|
360
|
+
permission,
|
|
361
|
+
data: load === void 0 ? void 0 : () => load()
|
|
362
|
+
});
|
|
363
|
+
const call = compact({ authInfo });
|
|
364
|
+
if (completion !== void 0) {
|
|
365
|
+
const rechecked = await kernel.check(binding, void 0, call, { simulate: true });
|
|
366
|
+
if (!rechecked.ok) return {
|
|
367
|
+
ok: false,
|
|
368
|
+
refusal: failedRefusal(permission, rechecked.failure)
|
|
369
|
+
};
|
|
370
|
+
if (stillAllowed(rechecked.raw, completion.approved)) return { ok: true };
|
|
371
|
+
return {
|
|
372
|
+
ok: false,
|
|
373
|
+
refusal: rechecked.raw.outcome === "denied" ? deniedRefusal(rechecked.raw, permission, rechecked.data) : plainRefusal(permission, `Denied: ${permission.key} was revoked before the call completed.`, {})
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
const checked = await kernel.check(binding, void 0, call, compact({ resumeToken: approvalTokenOf(context) }));
|
|
377
|
+
if (!checked.ok) return {
|
|
378
|
+
ok: false,
|
|
379
|
+
refusal: failedRefusal(permission, checked.failure)
|
|
380
|
+
};
|
|
381
|
+
const { raw, decision, data } = checked;
|
|
382
|
+
switch (decision.outcome) {
|
|
383
|
+
case "granted": return raw.outcome === "approval-required" ? {
|
|
384
|
+
ok: true,
|
|
385
|
+
approved: raw.token
|
|
386
|
+
} : { ok: true };
|
|
387
|
+
case "denied": return {
|
|
388
|
+
ok: false,
|
|
389
|
+
refusal: decision.denials.some((denial) => denial.reason === "insufficient-user-authentication") ? stepUpRefusal(decision, permission, data, context, capabilities) : deniedRefusal(decision, permission, data)
|
|
390
|
+
};
|
|
391
|
+
case "approval-required": try {
|
|
392
|
+
return {
|
|
393
|
+
ok: false,
|
|
394
|
+
refusal: compact({
|
|
395
|
+
...approvalRefusal(decision, permission, data),
|
|
396
|
+
inputRequired: await approvalInput(decision, handlerContext, context, capabilities)
|
|
397
|
+
})
|
|
398
|
+
};
|
|
399
|
+
} catch {
|
|
400
|
+
return {
|
|
401
|
+
ok: false,
|
|
402
|
+
refusal: failedRefusal(permission, "failed")
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
default: return decision;
|
|
406
|
+
}
|
|
407
|
+
};
|
|
408
|
+
const visible = async (context, permissionOf, keys, declaredOf = () => void 0) => {
|
|
409
|
+
const authInfo = context.http?.authInfo;
|
|
410
|
+
const shown = /* @__PURE__ */ new Set();
|
|
411
|
+
const guarded = [];
|
|
412
|
+
for (const key of keys) {
|
|
413
|
+
const permission = permissionOf(key);
|
|
414
|
+
if (permission === void 0) shown.add(key);
|
|
415
|
+
else if (reachable(authInfo, reachOf(permission, declaredOf(key)))) guarded.push({
|
|
416
|
+
key,
|
|
417
|
+
permission
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
if (guarded.length === 0) return shown;
|
|
421
|
+
let permdock;
|
|
422
|
+
try {
|
|
423
|
+
permdock = await kernel.instance(compact({ authInfo }));
|
|
424
|
+
} catch {
|
|
425
|
+
return shown;
|
|
426
|
+
}
|
|
427
|
+
for (const entry of guarded) if (mayUse(permdock, entry.permission)) shown.add(entry.key);
|
|
428
|
+
return shown;
|
|
429
|
+
};
|
|
430
|
+
const guard = (permission, load, handler, dataArgs, onRefusal, extra = {}) => async (...params) => {
|
|
431
|
+
const raw = params.at(-1);
|
|
432
|
+
const context = contextOf(raw);
|
|
433
|
+
const loader = load === void 0 ? void 0 : () => load(...dataArgs(params));
|
|
434
|
+
const reach = extra.reach ?? reachOf(permission, void 0);
|
|
435
|
+
const checked = await check(permission, reach, loader, context, raw, extra.capabilities);
|
|
436
|
+
let result;
|
|
437
|
+
if (checked.ok) {
|
|
438
|
+
result = await handler(...params);
|
|
439
|
+
if (extra.longRunning === true) {
|
|
440
|
+
const again = await check(permission, reach, loader, context, raw, extra.capabilities, { approved: checked.approved });
|
|
441
|
+
if (!again.ok) result = onRefusal(again.refusal);
|
|
442
|
+
}
|
|
443
|
+
} else result = onRefusal(checked.refusal);
|
|
444
|
+
await extra.after?.(context);
|
|
445
|
+
return result;
|
|
446
|
+
};
|
|
447
|
+
const protectServer = (server, enforcement) => {
|
|
448
|
+
const tools = /* @__PURE__ */ new Map();
|
|
449
|
+
const toolScopes = /* @__PURE__ */ new Map();
|
|
450
|
+
const prompts = /* @__PURE__ */ new Map();
|
|
451
|
+
const resources = /* @__PURE__ */ new Map();
|
|
452
|
+
const templates = [];
|
|
453
|
+
const lastListed = boundedMap(SESSIONS_PER_SERVER);
|
|
454
|
+
const toolPermission = (name) => tools.get(name);
|
|
455
|
+
const toolDeclared = (name) => toolScopes.get(name);
|
|
456
|
+
const promptPermission = (name) => prompts.get(name);
|
|
457
|
+
const resourcePermission = (uri) => {
|
|
458
|
+
const direct = resources.get(uri);
|
|
459
|
+
if (direct !== void 0) return direct;
|
|
460
|
+
return templates.find((entry) => entry.template.uriTemplate.match(uri) !== null)?.permission;
|
|
461
|
+
};
|
|
462
|
+
const templatePermission = (uriTemplate) => templates.find((entry) => entry.template.uriTemplate.toString() === uriTemplate)?.permission;
|
|
463
|
+
const listFilter = (field, keyOf, permissionOf, remember) => async (result, rawContext) => {
|
|
464
|
+
if (!isRecord(result) || !Array.isArray(result[field])) return result;
|
|
465
|
+
const context = contextOf(rawContext);
|
|
466
|
+
const entries = result[field];
|
|
467
|
+
const shown = await visible(context, permissionOf, entries.map(keyOf), remember ? toolDeclared : void 0);
|
|
468
|
+
const kept = entries.filter((entry) => shown.has(keyOf(entry)));
|
|
469
|
+
if (remember) lastListed.set(sessionKey(context), [...shown].toSorted().join(","));
|
|
470
|
+
return definesCacheScope(context) ? {
|
|
471
|
+
...result,
|
|
472
|
+
[field]: kept,
|
|
473
|
+
cacheScope: "private"
|
|
474
|
+
} : {
|
|
475
|
+
...result,
|
|
476
|
+
[field]: kept
|
|
477
|
+
};
|
|
478
|
+
};
|
|
479
|
+
const filters = {
|
|
480
|
+
"tools/list": listFilter("tools", (entry) => entry.name, toolPermission, true),
|
|
481
|
+
"prompts/list": listFilter("prompts", (entry) => entry.name, promptPermission, false),
|
|
482
|
+
"resources/list": listFilter("resources", (entry) => entry.uri, resourcePermission, false),
|
|
483
|
+
"resources/templates/list": listFilter("resourceTemplates", (entry) => entry.uriTemplate, templatePermission, false)
|
|
484
|
+
};
|
|
485
|
+
const inner = server.server;
|
|
486
|
+
const capabilities = () => inner.getClientCapabilities?.();
|
|
487
|
+
const set = inner.setRequestHandler.bind(inner);
|
|
488
|
+
inner.setRequestHandler = (method, ...rest) => {
|
|
489
|
+
const filter = Object.hasOwn(filters, method) ? filters[method] : void 0;
|
|
490
|
+
const [handler] = rest;
|
|
491
|
+
if (filter !== void 0 && rest.length === 1 && typeof handler === "function") {
|
|
492
|
+
set(method, async (request, context) => filter(await handler(request, context), context));
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
set(method, ...rest);
|
|
496
|
+
};
|
|
497
|
+
for (const method of Object.keys(filters)) {
|
|
498
|
+
const existing = inner._getRequestHandler?.(method);
|
|
499
|
+
if (existing !== void 0) {
|
|
500
|
+
inner.removeRequestHandler(method);
|
|
501
|
+
inner.setRequestHandler(method, (request, context) => existing(request, context));
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
const announce = async (context) => {
|
|
505
|
+
const key = sessionKey(context);
|
|
506
|
+
const before = lastListed.get(key);
|
|
507
|
+
if (before === void 0) return;
|
|
508
|
+
const after = [...await visible(context, toolPermission, [...tools.keys()], toolDeclared)].toSorted().join(",");
|
|
509
|
+
if (after === before) return;
|
|
510
|
+
lastListed.set(key, after);
|
|
511
|
+
try {
|
|
512
|
+
await (context.mcpReq?.notify?.({ method: "notifications/tools/list_changed" }) ?? inner.sendToolListChanged?.());
|
|
513
|
+
} catch {}
|
|
514
|
+
};
|
|
515
|
+
const guardTool = (permission, reach, load, handler, longRunning) => guard(permission, load, handler, (params) => params.length >= 2 ? [params[0]] : [void 0], toolRefusal, {
|
|
516
|
+
after: announce,
|
|
517
|
+
longRunning,
|
|
518
|
+
capabilities,
|
|
519
|
+
reach
|
|
520
|
+
});
|
|
521
|
+
const originalTool = server.registerTool.bind(server);
|
|
522
|
+
const toolPermissionOf = (name, config) => {
|
|
523
|
+
if (enforcement === void 0 || config["permission"] !== void 0) return requirePermission("tool", name, config);
|
|
524
|
+
for (const field of ["data", "longRunning"]) if (config[field] !== void 0) throw new TypeError(`permdock/mcp: tool ${name} sets ${field}, but with enforce: 'procedure' its procedure decides.`);
|
|
525
|
+
return requirePermission("tool", name, { permission: enforcement.permissionFor(name) });
|
|
526
|
+
};
|
|
527
|
+
/** With `enforce: 'procedure'` the handler runs undecided: the procedure it calls decides. */
|
|
528
|
+
const wrapTool = (permission, reach, load, handler, longRunning) => enforcement === void 0 ? guardTool(permission, reach, load, handler, longRunning) : async (...params) => {
|
|
529
|
+
const result = await handler(...params);
|
|
530
|
+
await announce(contextOf(params.at(-1)));
|
|
531
|
+
return result;
|
|
532
|
+
};
|
|
533
|
+
const registerTool = (name, config, handler) => {
|
|
534
|
+
const permission = toolPermissionOf(name, config);
|
|
535
|
+
const { permission: _permission, data, scopeChallenge, longRunning: rawLongRunning, oauthScopes: rawScopes, ...passthrough } = config;
|
|
536
|
+
const load = data;
|
|
537
|
+
const longRunning = rawLongRunning === true;
|
|
538
|
+
const declared = declaredScopes(name, rawScopes === void 0 ? enforcement?.oauthScopesFor?.(name) : rawScopes);
|
|
539
|
+
const reach = reachOf(permission, declared);
|
|
540
|
+
const registered = originalTool(name, {
|
|
541
|
+
...passthrough,
|
|
542
|
+
annotations: {
|
|
543
|
+
...annotationsFor(permission),
|
|
544
|
+
...passthrough["annotations"]
|
|
545
|
+
},
|
|
546
|
+
scopeChallenge: challengeFor(scopeChallenge, reach.scopes, reach.challenge)
|
|
547
|
+
}, wrapTool(permission, reach, load, handler, longRunning));
|
|
548
|
+
tools.set(name, permission);
|
|
549
|
+
if (declared !== void 0) toolScopes.set(name, declared);
|
|
550
|
+
let current = name;
|
|
551
|
+
guardUpdates(registered, (callback) => wrapTool(permission, reach, load, callback, longRunning), (from, to) => {
|
|
552
|
+
tools.delete(from);
|
|
553
|
+
toolScopes.delete(from);
|
|
554
|
+
if (to !== null) {
|
|
555
|
+
tools.set(to, permission);
|
|
556
|
+
if (declared !== void 0) toolScopes.set(to, declared);
|
|
557
|
+
current = to;
|
|
558
|
+
}
|
|
559
|
+
}, () => current);
|
|
560
|
+
return registered;
|
|
561
|
+
};
|
|
562
|
+
const originalPrompt = server.registerPrompt.bind(server);
|
|
563
|
+
const registerPrompt = (name, config, handler) => {
|
|
564
|
+
const permission = requirePermission("prompt", name, config);
|
|
565
|
+
const { permission: _permission, data, scopeChallenge, ...passthrough } = config;
|
|
566
|
+
const load = data;
|
|
567
|
+
const wrap = (callback) => guard(permission, load, callback, (params) => params.length >= 2 ? [params[0]] : [void 0], throwRefusal, { capabilities });
|
|
568
|
+
const registered = originalPrompt(name, {
|
|
569
|
+
...passthrough,
|
|
570
|
+
scopeChallenge: challengeFor(scopeChallenge, scopesReaching(policy, permission), challengeScope(policy, permission))
|
|
571
|
+
}, wrap(handler));
|
|
572
|
+
prompts.set(name, permission);
|
|
573
|
+
guardUpdates(registered, wrap);
|
|
574
|
+
return registered;
|
|
575
|
+
};
|
|
576
|
+
const originalResource = server.registerResource.bind(server);
|
|
577
|
+
const registerResource = (name, uriOrTemplate, config, read) => {
|
|
578
|
+
const permission = requirePermission("resource", name, config);
|
|
579
|
+
const { permission: _permission, data, scopeChallenge, ...passthrough } = config;
|
|
580
|
+
const load = data;
|
|
581
|
+
const wrap = (callback) => guard(permission, load, callback, (params) => params.slice(0, -1), throwRefusal, { capabilities });
|
|
582
|
+
const registered = originalResource(name, uriOrTemplate, {
|
|
583
|
+
...passthrough,
|
|
584
|
+
scopeChallenge: challengeFor(scopeChallenge, scopesReaching(policy, permission), challengeScope(policy, permission))
|
|
585
|
+
}, wrap(read));
|
|
586
|
+
if (typeof uriOrTemplate === "string") resources.set(uriOrTemplate, permission);
|
|
587
|
+
else templates.push({
|
|
588
|
+
template: uriOrTemplate,
|
|
589
|
+
permission
|
|
590
|
+
});
|
|
591
|
+
guardUpdates(registered, wrap);
|
|
592
|
+
return registered;
|
|
593
|
+
};
|
|
594
|
+
const guarded = server;
|
|
595
|
+
guarded["registerTool"] = registerTool;
|
|
596
|
+
guarded["registerPrompt"] = registerPrompt;
|
|
597
|
+
guarded["registerResource"] = registerResource;
|
|
598
|
+
return server;
|
|
599
|
+
};
|
|
600
|
+
return { protectServer };
|
|
601
|
+
}
|
|
602
|
+
//#endregion
|
|
603
|
+
export { APPROVAL_META_KEY, createPermDock, subjectFromMcp };
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { C as Policy, Nr as Principal, ln as PermDock } from "../policy-CFZAr_Al.js";
|
|
2
|
+
import { f as Permission } from "../permissions-DttnqqxC.js";
|
|
3
|
+
import { n as RevocationFeed } from "../revocations-Cy06JNx2.js";
|
|
4
|
+
import { A as ConnectionOptions, O as Connection, h as InvalidSignatureError, i as ProtectOptions, n as OpenApiHooks, w as verifyWebBotAuth, x as discoverViaSignatureAgent } from "../create-BWyx3jua.js";
|
|
5
|
+
import { t as ServerAdapterOptions } from "../bind-CVy3Espv.js";
|
|
6
|
+
import { i as sendResponse, t as NodeRequest } from "../http-DPuDaChb.js";
|
|
7
|
+
import { CanActivate, DynamicModule, ExceptionFilter, ExecutionContext, Type } from "@nestjs/common";
|
|
8
|
+
//#region src/nest/http.d.ts
|
|
9
|
+
/**
|
|
10
|
+
* `@nestjs/platform-express` hands the guard an `IncomingMessage`;
|
|
11
|
+
* `@nestjs/platform-fastify` hands it a `FastifyRequest` whose stream is `raw`.
|
|
12
|
+
*/
|
|
13
|
+
export declare function toRequest(req: NodeRequest): Request;
|
|
14
|
+
//#endregion
|
|
15
|
+
//#region src/nest/create.d.ts
|
|
16
|
+
type NestRequest = NodeRequest & {
|
|
17
|
+
readonly params?: Readonly<Record<string, string>>;
|
|
18
|
+
permdock?: PermDock;
|
|
19
|
+
permdockData?: unknown;
|
|
20
|
+
};
|
|
21
|
+
type NestPermDockOptions<TUser = unknown> = ServerAdapterOptions<NestRequest, TUser> & {
|
|
22
|
+
/**
|
|
23
|
+
* Maps a `ws`, `rpc` or `graphql` context to the request its subject comes
|
|
24
|
+
* from. Without it, a protected handler outside HTTP is denied.
|
|
25
|
+
*/
|
|
26
|
+
readonly request?: (context: ExecutionContext) => NestRequest | undefined;
|
|
27
|
+
/** Ends or revalidates open gateway connections. */
|
|
28
|
+
readonly revocations?: RevocationFeed;
|
|
29
|
+
};
|
|
30
|
+
/** A gateway client: Socket.IO (`emit`, `disconnect`) or `ws` (`close`). */
|
|
31
|
+
type NestSocket = {
|
|
32
|
+
emit?(event: string, payload: unknown): unknown;
|
|
33
|
+
disconnect?(close?: boolean): unknown;
|
|
34
|
+
close?(code?: number, reason?: string): unknown;
|
|
35
|
+
};
|
|
36
|
+
/** `Protect(null, loadData?, { oauthScopes })` needs a principal and one of the OAuth scopes, but no permission. */
|
|
37
|
+
type NestProtect = (permission: Permission | null, loadData?: (req: NestRequest) => unknown, protectOptions?: ProtectOptions) => ClassDecorator & MethodDecorator;
|
|
38
|
+
type NestHandlerOptions = {
|
|
39
|
+
/** Controller path; may hold route params such as `:org`. Default `api/permdock`. */
|
|
40
|
+
readonly path?: string;
|
|
41
|
+
};
|
|
42
|
+
type PermDockModuleOptions = {
|
|
43
|
+
/** `"global"` registers `PermDockGuard` as `APP_GUARD`, so every `Protect` rule runs without `UseGuards`. */
|
|
44
|
+
readonly guard?: "global";
|
|
45
|
+
};
|
|
46
|
+
type NestPermDock = {
|
|
47
|
+
readonly PermDockModule: Type<unknown> & {
|
|
48
|
+
readonly forRoot: (options?: PermDockModuleOptions) => DynamicModule;
|
|
49
|
+
};
|
|
50
|
+
readonly PermDockGuard: Type<CanActivate>;
|
|
51
|
+
readonly Protect: NestProtect;
|
|
52
|
+
readonly InjectPermDock: () => ParameterDecorator;
|
|
53
|
+
readonly PermDockExceptionFilter: Type<ExceptionFilter>;
|
|
54
|
+
readonly permdockHandler: (options?: NestHandlerOptions) => Type<unknown>;
|
|
55
|
+
/**
|
|
56
|
+
* Opens the connection for a gateway client in `handleConnection`, from the
|
|
57
|
+
* handshake request. `Protect` rules on that client's messages then decide
|
|
58
|
+
* against the connection's current instance.
|
|
59
|
+
*/
|
|
60
|
+
readonly connection: (client: NestSocket, req: NestRequest, connectionOptions?: ConnectionOptions) => Promise<Connection>;
|
|
61
|
+
readonly openapi: OpenApiHooks;
|
|
62
|
+
};
|
|
63
|
+
export declare function createPermDock<TUser, TPrincipal extends Principal = Principal>(policy: Policy<TUser, TPrincipal>, options: NestPermDockOptions<TUser>): NestPermDock;
|
|
64
|
+
//#endregion
|
|
65
|
+
//#region src/nest/decorate.d.ts
|
|
66
|
+
/**
|
|
67
|
+
* Applies method decorators to `cls.prototype[key]` without decorator syntax,
|
|
68
|
+
* for projects limited to erasable TypeScript. Decorators apply in the order
|
|
69
|
+
* given; each sees the descriptor the previous one left.
|
|
70
|
+
*/
|
|
71
|
+
export declare function decorateMethod(cls: {
|
|
72
|
+
readonly prototype: object;
|
|
73
|
+
}, key: string, ...decorators: readonly MethodDecorator[]): void;
|
|
74
|
+
//#endregion
|
|
75
|
+
export { InvalidSignatureError, type NestHandlerOptions, type NestPermDock, type NestPermDockOptions, type NestProtect, type NestRequest, type NestSocket, type PermDockModuleOptions, discoverViaSignatureAgent, sendResponse, verifyWebBotAuth };
|