@code-yeongyu/senpi 2026.11.10 → 2026.11.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +95 -0
- package/dist/bundle/{SKILL-B4R7BVOT.md → SKILL-R7FEIQKG.md} +1 -1
- package/dist/bundle/chunks/{SKILL-AZ6ONO52.js → SKILL-5O2UKORG.js} +1 -1
- package/dist/bundle/chunks/{SKILL-B4R7BVOT.md → SKILL-R7FEIQKG.md} +1 -1
- package/dist/bundle/chunks/{args-77GYOPEO.js → args-HFYB6URH.js} +1 -1
- package/dist/bundle/chunks/{azure-openai-responses-YFZ734T3.js → azure-openai-responses-MNN2TINI.js} +1 -1
- package/dist/bundle/chunks/{chunk-ZUQCYIRY.js → chunk-2ODGHBUD.js} +1 -1
- package/dist/bundle/chunks/chunk-3IYEGHBI.js +2 -0
- package/dist/bundle/chunks/{chunk-I5ASHSLZ.js → chunk-4JMGWVRH.js} +1 -1
- package/dist/bundle/chunks/{chunk-YKYWL2NO.js → chunk-5F2XC6UC.js} +3 -3
- package/dist/bundle/chunks/{chunk-MSDBBOB2.js → chunk-5H43O4MA.js} +1 -1
- package/dist/bundle/chunks/{chunk-R3OP7IA4.js → chunk-5PIZWSNG.js} +4 -4
- package/dist/bundle/chunks/{chunk-33MZJTQ3.js → chunk-B4WD5XHP.js} +1 -1
- package/dist/bundle/chunks/{chunk-Q63YLZMN.js → chunk-BPEA5SWW.js} +2 -2
- package/dist/bundle/chunks/{chunk-MOVJQVMC.js → chunk-BUDJLVMF.js} +3 -3
- package/dist/bundle/chunks/{chunk-V5FSENW6.js → chunk-COKX65TE.js} +1 -1
- package/dist/bundle/chunks/{chunk-2EJ4BGBM.js → chunk-DHORIIVQ.js} +1 -1
- package/dist/bundle/chunks/{chunk-NH7SYIGN.js → chunk-DLPV4B3R.js} +1 -1
- package/dist/bundle/chunks/{chunk-RCHIMH7N.js → chunk-DQY26AX3.js} +1 -1
- package/dist/bundle/chunks/{chunk-Y7Z7XPUF.js → chunk-DZQA5LVN.js} +1 -1
- package/dist/bundle/chunks/{chunk-2CNSCMYC.js → chunk-E5O4SP5I.js} +1 -1
- package/dist/bundle/chunks/{chunk-3Q5ZENSD.js → chunk-E6ZBTSC6.js} +1 -1
- package/dist/bundle/chunks/{chunk-GVPZPVVO.js → chunk-IBDAV2AL.js} +3 -3
- package/dist/bundle/chunks/chunk-IREUIMFE.js +11 -0
- package/dist/bundle/chunks/{chunk-KU7OCWJF.js → chunk-J4ADZ2OK.js} +1 -1
- package/dist/bundle/chunks/{chunk-T2SMQPRE.js → chunk-K3GAGIPX.js} +1 -1
- package/dist/bundle/chunks/{chunk-BELNOPAI.js → chunk-L3KVP7VJ.js} +3 -3
- package/dist/bundle/chunks/{chunk-IYSJFQUU.js → chunk-L6DF5G6K.js} +1 -1
- package/dist/bundle/chunks/{chunk-7MN7YNO5.js → chunk-L7DGTA3L.js} +1 -1
- package/dist/bundle/chunks/chunk-LJFQYCFH.js +2 -0
- package/dist/bundle/chunks/{chunk-J2NGJKXL.js → chunk-LXINMDMX.js} +1 -1
- package/dist/bundle/chunks/{chunk-H33A33M4.js → chunk-M2VJVJMK.js} +1 -1
- package/dist/bundle/chunks/{chunk-2U6HXWXS.js → chunk-NNQ34ZDW.js} +1 -1
- package/dist/bundle/chunks/chunk-NQKCJQHB.js +2 -0
- package/dist/bundle/chunks/{chunk-EJB5E2JK.js → chunk-NRZAJUUO.js} +2 -2
- package/dist/bundle/chunks/{chunk-R2Z7CX77.js → chunk-OSNR3IRD.js} +1 -1
- package/dist/bundle/chunks/{chunk-O3FG6HML.js → chunk-PJI5GT7U.js} +1 -1
- package/dist/bundle/chunks/{chunk-FDNM2DAE.js → chunk-PUZDKFRT.js} +164 -164
- package/dist/bundle/chunks/{chunk-BPNSOZ2C.js → chunk-QUVSIW2R.js} +1 -1
- package/dist/bundle/chunks/{chunk-WXYMAUFX.js → chunk-QX7WD3IM.js} +1 -1
- package/dist/bundle/chunks/{chunk-Y4ONXQMR.js → chunk-RGHVPUUX.js} +1 -1
- package/dist/bundle/chunks/{chunk-RYPM5BZQ.js → chunk-S7NAJQB3.js} +1 -1
- package/dist/bundle/chunks/{chunk-2BH54YQR.js → chunk-SANWO37B.js} +1 -1
- package/dist/bundle/chunks/{chunk-YQZNARTX.js → chunk-SS5VTBAV.js} +1 -1
- package/dist/bundle/chunks/{chunk-LT353NSF.js → chunk-T4CUD7RF.js} +1 -1
- package/dist/bundle/chunks/{chunk-J7FW7MED.js → chunk-TCZORL3V.js} +1 -1
- package/dist/bundle/chunks/{chunk-KU72IKV5.js → chunk-VGSWOLIY.js} +1 -1
- package/dist/bundle/chunks/{chunk-DBQBJTMF.js → chunk-VUY4BZDI.js} +1 -1
- package/dist/bundle/chunks/chunk-XI6THO4F.js +2 -0
- package/dist/bundle/chunks/{chunk-FAFIGBUD.js → chunk-XUDKT4UH.js} +3 -3
- package/dist/bundle/chunks/{cli-main-BX5AFWJQ.js → cli-main-7II7GCIR.js} +1 -1
- package/dist/bundle/chunks/{credential-accounts-LVVPKFEY.js → credential-accounts-N3B5YJS7.js} +1 -1
- package/dist/bundle/chunks/{dispatch-EP35KKJ3.js → dispatch-YUYZQXRP.js} +1 -1
- package/dist/bundle/chunks/github-copilot.js +1 -1
- package/dist/bundle/chunks/{hand-entry-F6PWKNPJ.js → hand-entry-ONOLRMVB.js} +3 -3
- package/dist/bundle/chunks/{headless-host-HKGNJGYT.js → headless-host-53UMJMM6.js} +1 -1
- package/dist/bundle/chunks/{health-write-FBF74QJ7.js → health-write-JLSF5EVV.js} +1 -1
- package/dist/bundle/chunks/{help-fast-path-5SCPY3OH.js → help-fast-path-552QLMWT.js} +1 -1
- package/dist/bundle/chunks/{host-command-47QQKJEY.js → host-command-KWMJLPBS.js} +1 -1
- package/dist/bundle/chunks/{host-lifecycle-VSAH4M3R.js → host-lifecycle-4WQDCN3Y.js} +1 -1
- package/dist/bundle/chunks/host-lifecycle.js +5 -5
- package/dist/bundle/chunks/{host-session-command-SL4AN5WD.js → host-session-command-ZDYEKCUB.js} +1 -1
- package/dist/bundle/chunks/{host-socket-request-DMYXC56U.js → host-socket-request-QN4Q2P5E.js} +1 -1
- package/dist/bundle/chunks/{interactive-mode-JDMIHJMQ.js → interactive-mode-EPE4M32G.js} +1 -1
- package/dist/bundle/chunks/{login-health-EVXVLHB2.js → login-health-TOSQT5RG.js} +1 -1
- package/dist/bundle/chunks/{main-4RITUAQF.js → main-OM7BKVZG.js} +1 -1
- package/dist/bundle/chunks/{multi-session-host-SKDD4OWM.js → multi-session-host-DB6XMY53.js} +11 -8
- package/dist/bundle/chunks/openai-codex-responses-VRGOAFEG.js +10 -0
- package/dist/bundle/chunks/{openai-responses-NIVBWE2H.js → openai-responses-I7PXRWCM.js} +2 -2
- package/dist/bundle/chunks/{package-manager-cli-B5VA4KFO.js → package-manager-cli-GLU5Q2KV.js} +1 -1
- package/dist/bundle/chunks/{role-HQUN4XXK.js → role-AEOLJRTP.js} +1 -1
- package/dist/bundle/chunks/{rotation-stream-F3BEMXBE.js → rotation-stream-E3FJFHXJ.js} +1 -1
- package/dist/bundle/chunks/rpc-mode-SDJVBT36.js +2 -0
- package/dist/bundle/chunks/{rpc-monitor-source-C6VQ3BC4.js → rpc-monitor-source-PGTDSGWP.js} +1 -1
- package/dist/bundle/chunks/{run-attached-tui-R3RB5P7G.js → run-attached-tui-4AENRP7P.js} +1 -1
- package/dist/bundle/chunks/{schedule-command-FPEQCX33.js → schedule-command-5SQD7M5N.js} +1 -1
- package/dist/bundle/chunks/session-control-endpoint-R25A2QGS.js +3 -0
- package/dist/bundle/chunks/{session-picker-2FZSNFS7.js → session-picker-GZM5CVQL.js} +1 -1
- package/dist/bundle/chunks/{session-slot-CZGQQCOC.js → session-slot-RH7JLO3F.js} +1 -1
- package/dist/bundle/chunks/session-worker.js +335 -332
- package/dist/bundle/chunks/{state-store-S7QAESIF.js → state-store-IECVTYXC.js} +1 -1
- package/dist/bundle/cli.js +2 -2
- package/dist/bundle/host-supervisor.js +5 -5
- package/dist/bundle/index.js +1 -1
- package/dist/bundle/rpc-entry.js +1 -1
- package/dist/bundle/runtime-manifest.json +1 -1
- package/dist/cli.js +9 -3
- package/dist/core/agent-session-runtime.d.ts +2 -0
- package/dist/core/agent-session-runtime.js +5 -0
- package/dist/core/agent-session.d.ts +15 -3
- package/dist/core/agent-session.js +91 -18
- package/dist/core/assistant-error-redaction.d.ts +9 -0
- package/dist/core/assistant-error-redaction.js +27 -0
- package/dist/core/config-reference-redaction.js +90 -10
- package/dist/core/context-files.d.ts +10 -0
- package/dist/core/context-files.js +58 -12
- package/dist/core/credential-accounts.d.ts +2 -0
- package/dist/core/credential-accounts.js +10 -1
- package/dist/core/dynamic-prompt/intent-gate.js +2 -2
- package/dist/core/dynamic-prompt/style.js +2 -2
- package/dist/core/dynamic-prompt/verification.js +2 -2
- package/dist/core/egress/wire.d.ts +13 -0
- package/dist/core/extensions/builtin/anthropic-subscription/accounts.d.ts +3 -0
- package/dist/core/extensions/builtin/anthropic-subscription/auth-lane.js +3 -2
- package/dist/core/extensions/builtin/anthropic-subscription/claude-cli-account-test.js +3 -1
- package/dist/core/extensions/builtin/anthropic-subscription/claude-cli-connection-test.d.ts +3 -1
- package/dist/core/extensions/builtin/anthropic-subscription/claude-cli-connection-test.js +47 -16
- package/dist/core/extensions/builtin/anthropic-subscription/claude-cli-login-slots.d.ts +1 -1
- package/dist/core/extensions/builtin/anthropic-subscription/claude-cli-login-slots.js +4 -2
- package/dist/core/extensions/builtin/anthropic-subscription/claude-settings-env.d.ts +20 -0
- package/dist/core/extensions/builtin/anthropic-subscription/claude-settings-env.js +39 -0
- package/dist/core/extensions/builtin/anthropic-subscription/context-window.d.ts +17 -0
- package/dist/core/extensions/builtin/anthropic-subscription/context-window.js +23 -0
- package/dist/core/extensions/builtin/anthropic-subscription/errors.d.ts +1 -0
- package/dist/core/extensions/builtin/anthropic-subscription/errors.js +14 -5
- package/dist/core/extensions/builtin/anthropic-subscription/oauth-login.js +3 -3
- package/dist/core/extensions/builtin/anthropic-subscription/prompt-cache-ttl.d.ts +10 -4
- package/dist/core/extensions/builtin/anthropic-subscription/prompt-cache-ttl.js +14 -5
- package/dist/core/extensions/builtin/anthropic-subscription/query-environment.d.ts +3 -2
- package/dist/core/extensions/builtin/anthropic-subscription/query-environment.js +5 -4
- package/dist/core/extensions/builtin/anthropic-subscription/sdk-boundary.lazy.d.ts +2 -1
- package/dist/core/extensions/builtin/anthropic-subscription/sdk-boundary.lazy.js +1 -0
- package/dist/core/extensions/builtin/anthropic-subscription/stream.js +11 -1
- package/dist/core/extensions/builtin/config-reload/change-groups.js +6 -2
- package/dist/core/extensions/builtin/goal/continuation.d.ts +6 -0
- package/dist/core/extensions/builtin/goal/continuation.js +3 -2
- package/dist/core/extensions/builtin/goal/index.js +14 -26
- package/dist/core/extensions/builtin/goal/lifecycle-helpers.d.ts +8 -6
- package/dist/core/extensions/builtin/goal/lifecycle-helpers.js +77 -39
- package/dist/core/extensions/builtin/goal/monitor-continuation.js +66 -7
- package/dist/core/extensions/builtin/goal/persistence.js +20 -0
- package/dist/core/extensions/builtin/goal/provider-retry.d.ts +9 -0
- package/dist/core/extensions/builtin/goal/provider-retry.js +16 -0
- package/dist/core/extensions/builtin/goal/reload-reengagement.d.ts +3 -2
- package/dist/core/extensions/builtin/goal/reload-reengagement.js +4 -4
- package/dist/core/extensions/builtin/goal/session-start-continuation.d.ts +28 -0
- package/dist/core/extensions/builtin/goal/session-start-continuation.js +75 -0
- package/dist/core/extensions/builtin/goal/store.d.ts +3 -2
- package/dist/core/extensions/builtin/goal/store.js +9 -2
- package/dist/core/extensions/builtin/goal/types.d.ts +9 -0
- package/dist/core/extensions/builtin/handoff-hint/index.js +15 -11
- package/dist/core/extensions/builtin/handoff-hint/replacement-relay.d.ts +16 -0
- package/dist/core/extensions/builtin/handoff-hint/replacement-relay.js +17 -0
- package/dist/core/extensions/builtin/html-render/tool.js +13 -3
- package/dist/core/extensions/builtin/imagegen/skill/SKILL.md +1 -1
- package/dist/core/extensions/builtin/imagegen/tool.js +4 -14
- package/dist/core/extensions/builtin/mcp/auth/catalog-identity.js +32 -8
- package/dist/core/extensions/builtin/mcp/auth/commands-auth.js +2 -1
- package/dist/core/extensions/builtin/mcp/auth/context.js +3 -0
- package/dist/core/extensions/builtin/mcp/auth/oauth-grant.d.ts +43 -0
- package/dist/core/extensions/builtin/mcp/auth/oauth-grant.js +196 -0
- package/dist/core/extensions/builtin/mcp/auth/oauth-issuer.d.ts +7 -1
- package/dist/core/extensions/builtin/mcp/auth/oauth-issuer.js +13 -0
- package/dist/core/extensions/builtin/mcp/auth/oauth-provider.d.ts +6 -17
- package/dist/core/extensions/builtin/mcp/auth/oauth-provider.js +67 -88
- package/dist/core/extensions/builtin/mcp/auth/oauth-refresh.d.ts +1 -1
- package/dist/core/extensions/builtin/mcp/auth/oauth-refresh.js +25 -4
- package/dist/core/extensions/builtin/mcp/auth/oauth.js +10 -7
- package/dist/core/extensions/builtin/mcp/auth/registered-redirect.d.ts +19 -0
- package/dist/core/extensions/builtin/mcp/auth/registered-redirect.js +55 -0
- package/dist/core/extensions/builtin/mcp/auth/token-store.d.ts +23 -1
- package/dist/core/extensions/builtin/mcp/auth/token-store.js +3 -0
- package/dist/core/extensions/builtin/mcp/catalog-cache.js +7 -6
- package/dist/core/extensions/builtin/mcp/catalog.js +3 -2
- package/dist/core/extensions/builtin/mcp/invocation.js +6 -0
- package/dist/core/extensions/builtin/mcp/service-register.js +16 -2
- package/dist/core/extensions/builtin/mcp/service-tools-changed.js +61 -54
- package/dist/core/extensions/builtin/mcp/startup-race.d.ts +2 -0
- package/dist/core/extensions/builtin/mcp/startup-race.js +25 -7
- package/dist/core/extensions/builtin/permission-system/parsers.js +1 -0
- package/dist/core/extensions/builtin/prompt-preset/claude-fable-5-1.js +3 -3
- package/dist/core/extensions/builtin/prompt-preset/claude-fable-5-2.js +9 -8
- package/dist/core/extensions/builtin/prompt-preset/claude-fable-5.js +3 -3
- package/dist/core/extensions/builtin/prompt-preset/claude-haiku-5-5.js +3 -3
- package/dist/core/extensions/builtin/prompt-preset/claude-opus-5-5.js +3 -3
- package/dist/core/extensions/builtin/prompt-preset/claude-opus-5.js +4 -4
- package/dist/core/extensions/builtin/prompt-preset/claude-sonnet-5-5.js +3 -3
- package/dist/core/extensions/builtin/prompt-preset/deepseek-v4-1-flash.js +3 -3
- package/dist/core/extensions/builtin/prompt-preset/gpt-5.5.js +3 -3
- package/dist/core/extensions/builtin/prompt-preset/gpt-5.6.js +2 -2
- package/dist/core/extensions/builtin/prompt-preset/gpt-6-astra.js +3 -3
- package/dist/core/extensions/builtin/prompt-preset/grok-4.5.js +1 -1
- package/dist/core/extensions/builtin/prompt-preset/grok-4.6.js +4 -4
- package/dist/core/extensions/builtin/prompt-preset/grok-4.7.js +5 -5
- package/dist/core/extensions/builtin/prompt-preset/kimi-k3-1.js +4 -4
- package/dist/core/extensions/builtin/prompt-preset/kimi-k3.js +7 -6
- package/dist/core/extensions/loader.js +24 -4
- package/dist/core/extensions/runner.d.ts +9 -1
- package/dist/core/extensions/runner.js +9 -4
- package/dist/core/extensions/types.d.ts +19 -3
- package/dist/core/hand/hook-advertise.d.ts +41 -0
- package/dist/core/hand/hook-advertise.js +98 -0
- package/dist/core/hand/hook-contract.d.ts +66 -0
- package/dist/core/hand/hook-contract.js +138 -0
- package/dist/core/open-timing.d.ts +2 -0
- package/dist/core/open-timing.js +7 -1
- package/dist/core/resource-loader.js +21 -3
- package/dist/core/retry-fallback/controller-types.d.ts +2 -0
- package/dist/core/retry-fallback/controller.js +9 -2
- package/dist/core/retry-fallback/cooldown.d.ts +7 -0
- package/dist/core/retry-fallback/cooldown.js +13 -1
- package/dist/core/retry-fallback/credential-recovery.d.ts +7 -0
- package/dist/core/retry-fallback/credential-recovery.js +24 -0
- package/dist/core/retry-fallback/log.d.ts +7 -1
- package/dist/core/retry-fallback/log.js +82 -5
- package/dist/core/retry-fallback/model-config-error.js +2 -1
- package/dist/core/retry-fallback/settings.d.ts +8 -5
- package/dist/core/retry-fallback/settings.js +9 -6
- package/dist/core/session-manager.d.ts +2 -0
- package/dist/core/session-manager.js +15 -1
- package/dist/core/settings-manager.d.ts +6 -0
- package/dist/core/settings-manager.js +8 -0
- package/dist/core/turn-origin.d.ts +18 -10
- package/dist/core/turn-origin.js +34 -30
- package/dist/core/ultrafast-lanes.d.ts +1 -0
- package/dist/core/ultrafast-lanes.js +4 -2
- package/dist/modes/interactive/components/footer.d.ts +23 -5
- package/dist/modes/interactive/components/footer.js +45 -16
- package/dist/modes/interactive/interactive-mode.js +9 -1
- package/dist/modes/interactive/session-control-rpc-commands.d.ts +1 -1
- package/dist/modes/interactive/session-control-rpc-commands.js +5 -0
- package/dist/modes/rpc/admit-external-message-command.d.ts +2 -0
- package/dist/modes/rpc/admit-external-message-command.js +5 -1
- package/dist/modes/rpc/compact-admission-validation.js +4 -1
- package/dist/modes/rpc/connection-authority.d.ts +2 -1
- package/dist/modes/rpc/connection-authority.js +11 -9
- package/dist/modes/rpc/connection-handler.d.ts +2 -0
- package/dist/modes/rpc/connection-handler.js +33 -7
- package/dist/modes/rpc/connection-roles.d.ts +7 -0
- package/dist/modes/rpc/connection-roles.js +15 -0
- package/dist/modes/rpc/custom-capability.d.ts +6 -0
- package/dist/modes/rpc/custom-capability.js +6 -0
- package/dist/modes/rpc/delegated-drive-origin.d.ts +6 -6
- package/dist/modes/rpc/delegated-drive-origin.js +7 -8
- package/dist/modes/rpc/delegation-declaration.d.ts +2 -0
- package/dist/modes/rpc/delegation-declaration.js +7 -3
- package/dist/modes/rpc/effect-commands.d.ts +33 -0
- package/dist/modes/rpc/host-capabilities.d.ts +1 -0
- package/dist/modes/rpc/host-capabilities.js +7 -1
- package/dist/modes/rpc/host-gc-evidence.d.ts +1 -1
- package/dist/modes/rpc/host-gc-evidence.js +9 -3
- package/dist/modes/rpc/host-gc.js +4 -1
- package/dist/modes/rpc/host-observe-request.d.ts +2 -0
- package/dist/modes/rpc/host-observe-request.js +6 -1
- package/dist/modes/rpc/host-park-ledger.d.ts +19 -0
- package/dist/modes/rpc/host-park-ledger.js +110 -0
- package/dist/modes/rpc/lifecycle-delivery.js +4 -1
- package/dist/modes/rpc/loopless-tool-handlers.d.ts +6 -0
- package/dist/modes/rpc/loopless-tool-handlers.js +14 -1
- package/dist/modes/rpc/media-placeholders.js +1 -0
- package/dist/modes/rpc/method-classes-schema-host.d.ts +42 -1
- package/dist/modes/rpc/method-classes-schema-host.js +24 -21
- package/dist/modes/rpc/method-classes-schema-session.d.ts +20 -0
- package/dist/modes/rpc/method-classes-schema-session.js +6 -0
- package/dist/modes/rpc/method-classes-schema.d.ts +62 -1
- package/dist/modes/rpc/method-classes-schema.js +5 -2
- package/dist/modes/rpc/method-classes.d.ts +4 -0
- package/dist/modes/rpc/method-classes.js +6 -0
- package/dist/modes/rpc/multi-session-host.js +1 -1
- package/dist/modes/rpc/project-file-commands.d.ts +32 -0
- package/dist/modes/rpc/project-file-commands.js +195 -0
- package/dist/modes/rpc/project-file-path.d.ts +34 -0
- package/dist/modes/rpc/project-file-path.js +93 -0
- package/dist/modes/rpc/rpc-error-types.d.ts +9 -1
- package/dist/modes/rpc/rpc-error-types.js +8 -0
- package/dist/modes/rpc/rpc-host-command-types.d.ts +14 -1
- package/dist/modes/rpc/rpc-host-response-types.d.ts +21 -0
- package/dist/modes/rpc/rpc-mutation-response-types.d.ts +7 -0
- package/dist/modes/rpc/rpc-read-response-types.d.ts +7 -0
- package/dist/modes/rpc/rpc-session-command-types.d.ts +10 -0
- package/dist/modes/rpc/rpc-session-state-types.d.ts +2 -0
- package/dist/modes/rpc/session-attachment-gate.js +6 -0
- package/dist/modes/rpc/session-binding.d.ts +1 -0
- package/dist/modes/rpc/session-binding.js +6 -1
- package/dist/modes/rpc/session-command-activity.js +1 -0
- package/dist/modes/rpc/session-connection-context.d.ts +2 -0
- package/dist/modes/rpc/session-connection-context.js +4 -0
- package/dist/modes/rpc/session-registry-attach.d.ts +2 -0
- package/dist/modes/rpc/session-registry-attach.js +15 -11
- package/dist/modes/rpc/session-registry-claims.js +2 -0
- package/dist/modes/rpc/session-registry-open.js +13 -7
- package/dist/modes/rpc/session-registry-opening.js +2 -1
- package/dist/modes/rpc/session-registry-types.d.ts +4 -0
- package/dist/modes/rpc/session-registry.d.ts +3 -0
- package/dist/modes/rpc/session-registry.js +27 -3
- package/dist/modes/rpc/session-router-close.js +1 -0
- package/dist/modes/rpc/session-router-context.d.ts +4 -2
- package/dist/modes/rpc/session-router-context.js +3 -0
- package/dist/modes/rpc/session-router-host-reads.js +1 -0
- package/dist/modes/rpc/session-router-occupancy.js +10 -1
- package/dist/modes/rpc/session-router-open-result.d.ts +12 -0
- package/dist/modes/rpc/session-router-open-result.js +63 -0
- package/dist/modes/rpc/session-router-open.d.ts +3 -0
- package/dist/modes/rpc/session-router-open.js +15 -63
- package/dist/modes/rpc/session-router-park.d.ts +15 -0
- package/dist/modes/rpc/session-router-park.js +176 -0
- package/dist/modes/rpc/session-router-routing.d.ts +1 -1
- package/dist/modes/rpc/session-router-routing.js +7 -0
- package/dist/modes/rpc/session-status-source.js +2 -2
- package/dist/private-fd-startup-fillers.d.ts +2 -2
- package/dist/private-fd-startup-fillers.js +34 -4
- package/dist/senpi +9 -3
- package/docs/engine-socket/fixtures/admission-routes/h-legacy.json +1 -1
- package/docs/engine-socket/fixtures/rpc-inbound-samples.json +44 -2
- package/docs/extensions.md +11 -1
- package/docs/rpc.md +106 -2
- package/docs/settings.md +5 -3
- package/docs/skills.md +1 -1
- package/docs/usage.md +1 -1
- package/native/socket-secret/prebuilds/win32-arm64/senpi_socket_secret.win32-arm64.node +0 -0
- package/native/socket-secret/prebuilds/win32-x64/senpi_socket_secret.win32-x64.node +0 -0
- package/package.json +6 -6
- package/dist/bundle/chunks/chunk-436DNN4Z.js +0 -2
- package/dist/bundle/chunks/chunk-DLAY7CIU.js +0 -2
- package/dist/bundle/chunks/chunk-SJI6UB23.js +0 -2
- package/dist/bundle/chunks/chunk-XH2MWTXA.js +0 -11
- package/dist/bundle/chunks/chunk-YK3ZZCHI.js +0 -2
- package/dist/bundle/chunks/openai-codex-responses-ABB2J43U.js +0 -10
- package/dist/bundle/chunks/rpc-mode-ZDUGW2IM.js +0 -2
- package/dist/bundle/chunks/session-control-endpoint-MEDV66PS.js +0 -3
|
@@ -1,15 +1,22 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { readFileSync } from "node:fs";
|
|
3
|
+
import { Type } from "typebox";
|
|
4
|
+
import { Check } from "typebox/value";
|
|
3
5
|
import { keyedIdentity } from "../config-identity-key.js";
|
|
4
6
|
import { resolveAuthMode } from "./context.js";
|
|
7
|
+
import { oauthGrantIdentity } from "./oauth-grant.js";
|
|
5
8
|
import { McpTokenStore } from "./token-store.js";
|
|
6
9
|
/** Local snapshot only: never migrates credentials, acquires a lock, or refreshes authentication. */
|
|
7
10
|
export function mcpCredentialIdentity(config, serverName, agentDir, env) {
|
|
8
11
|
const material = credentialMaterial(config, serverName, agentDir, env);
|
|
9
|
-
return material === undefined || material === "none"
|
|
12
|
+
return material === undefined || material === "none" || resolveAuthMode(config) === "oauth"
|
|
13
|
+
? material
|
|
14
|
+
: keyedIdentity(agentDir, material);
|
|
10
15
|
}
|
|
11
16
|
/** The unkeyed identity earlier builds persisted; read only to adopt a cache entry for the same credential. */
|
|
12
17
|
export function legacyMcpCredentialIdentity(config, serverName, agentDir, env) {
|
|
18
|
+
if (resolveAuthMode(config) === "oauth")
|
|
19
|
+
return undefined;
|
|
13
20
|
const material = credentialMaterial(config, serverName, agentDir, env);
|
|
14
21
|
return material === undefined || material === "none"
|
|
15
22
|
? material
|
|
@@ -31,14 +38,15 @@ function credentialMaterial(config, serverName, agentDir, env) {
|
|
|
31
38
|
try {
|
|
32
39
|
const store = new McpTokenStore({ serverName, serverUrl: config.url ?? "", agentDir });
|
|
33
40
|
const value = JSON.parse(readFileSync(store.tokensPath, "utf8"));
|
|
34
|
-
if (
|
|
35
|
-
return undefined;
|
|
36
|
-
if (typeof value.accessToken !== "string" || value.accessToken.length === 0)
|
|
37
|
-
return undefined;
|
|
38
|
-
const refreshToken = "refreshToken" in value ? value.refreshToken : undefined;
|
|
39
|
-
if (refreshToken !== undefined && typeof refreshToken !== "string")
|
|
41
|
+
if (!Check(storedGrantSchema, value))
|
|
40
42
|
return undefined;
|
|
41
|
-
return
|
|
43
|
+
return oauthGrantIdentity(value, {
|
|
44
|
+
serverName,
|
|
45
|
+
serverUrl: store.serverUrl,
|
|
46
|
+
agentDir: store.agentDir,
|
|
47
|
+
clientId: config.oauth?.clientId,
|
|
48
|
+
clientMetadataUrl: config.oauth?.clientMetadataUrl,
|
|
49
|
+
});
|
|
42
50
|
}
|
|
43
51
|
catch (error) {
|
|
44
52
|
if (error instanceof Error)
|
|
@@ -46,4 +54,20 @@ function credentialMaterial(config, serverName, agentDir, env) {
|
|
|
46
54
|
throw error;
|
|
47
55
|
}
|
|
48
56
|
}
|
|
57
|
+
// Parse exactly the stored fields the identity reader consumes; authorization is parsed by its proof owner.
|
|
58
|
+
const storedGrantSchema = Type.Object({
|
|
59
|
+
accessToken: Type.String({ minLength: 1 }),
|
|
60
|
+
refreshToken: Type.Optional(Type.String()),
|
|
61
|
+
issuer: Type.Optional(Type.String()),
|
|
62
|
+
resource: Type.Optional(Type.String()),
|
|
63
|
+
scope: Type.Optional(Type.String()),
|
|
64
|
+
refreshScope: Type.Optional(Type.String()),
|
|
65
|
+
clientInfo: Type.Optional(Type.Object({
|
|
66
|
+
client_id: Type.String(),
|
|
67
|
+
client_secret: Type.Optional(Type.String()),
|
|
68
|
+
token_endpoint_auth_method: Type.Optional(Type.String()),
|
|
69
|
+
issuer: Type.Optional(Type.String()),
|
|
70
|
+
})),
|
|
71
|
+
discoveryState: Type.Optional(Type.Object({ authorizationServerUrl: Type.String() })),
|
|
72
|
+
});
|
|
49
73
|
//# sourceMappingURL=catalog-identity.js.map
|
|
@@ -33,7 +33,8 @@ function buildProvider(deps, redirectUrl, onRedirect) {
|
|
|
33
33
|
export async function runAuth(deps) {
|
|
34
34
|
ensureOAuth(deps);
|
|
35
35
|
if (deps.config.oauth?.flow === "client_credentials") {
|
|
36
|
-
|
|
36
|
+
// No authorize request, so no redirect: any stored registration may serve the grant (senpi#6327).
|
|
37
|
+
const provider = buildProvider(deps);
|
|
37
38
|
await clientCredentialsGrant(provider, deps.flow);
|
|
38
39
|
await deps.onReconnect();
|
|
39
40
|
deps.notify(`MCP server ${deps.serverName} authenticated (client_credentials)`);
|
|
@@ -34,6 +34,9 @@ export function resolveServerAuth(deps) {
|
|
|
34
34
|
// flow and raises UnauthorizedError (-> needs_auth) on a token-less connect,
|
|
35
35
|
// rather than falling through to a client_credentials token request.
|
|
36
36
|
redirectUrl: deps.redirectUrl ?? BACKGROUND_REDIRECT,
|
|
37
|
+
// Only a caller that names its redirect sends an authorize request; background connects and refresh keep
|
|
38
|
+
// the stored registration so an existing sign-in keeps refreshing (senpi#6327).
|
|
39
|
+
requireRegisteredRedirect: deps.redirectUrl !== undefined,
|
|
37
40
|
scopes: deps.config.oauth?.scopes,
|
|
38
41
|
clientId: deps.config.oauth?.clientId,
|
|
39
42
|
clientMetadataUrl: deps.config.oauth?.clientMetadataUrl,
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { OAuthTokens } from "@modelcontextprotocol/sdk/shared/auth.js";
|
|
2
|
+
import type { McpAuthorizationProof, McpClientRegistration, McpStoredAuth } from "./token-store.ts";
|
|
3
|
+
export type McpGrantRecord = Pick<McpStoredAuth, "accessToken" | "refreshToken" | "issuer" | "resource" | "scope" | "refreshScope"> & {
|
|
4
|
+
readonly authorization?: unknown;
|
|
5
|
+
readonly clientInfo?: Pick<McpClientRegistration, "client_id" | "client_secret" | "token_endpoint_auth_method" | "issuer">;
|
|
6
|
+
readonly discoveryState?: {
|
|
7
|
+
readonly authorizationServerUrl?: string | URL;
|
|
8
|
+
};
|
|
9
|
+
};
|
|
10
|
+
export interface McpGrantContext {
|
|
11
|
+
readonly serverName: string;
|
|
12
|
+
readonly serverUrl: string;
|
|
13
|
+
readonly agentDir: string;
|
|
14
|
+
readonly clientId?: string;
|
|
15
|
+
readonly clientMetadataUrl?: string;
|
|
16
|
+
}
|
|
17
|
+
export declare function storedGrantIssuer(record: McpGrantRecord | undefined): string | undefined;
|
|
18
|
+
export declare function freezeGrantAttribution(record: McpStoredAuth | undefined): McpStoredAuth;
|
|
19
|
+
export declare function storedAuthToTokens(record: McpStoredAuth | undefined, now?: number): OAuthTokens | undefined;
|
|
20
|
+
/** Maps one response; the caller decides which preceding fields it is entitled to inherit. */
|
|
21
|
+
export declare function mergeTokensIntoStoredAuth(current: McpStoredAuth, tokens: OAuthTokens): McpStoredAuth;
|
|
22
|
+
export interface McpTokenResponse {
|
|
23
|
+
readonly tokens: OAuthTokens;
|
|
24
|
+
readonly resource: string;
|
|
25
|
+
readonly fresh: boolean;
|
|
26
|
+
readonly presented?: McpStoredAuth;
|
|
27
|
+
readonly clientInfo?: McpClientRegistration;
|
|
28
|
+
}
|
|
29
|
+
/** An SDK response has no locked predecessor; only explicit fresh acquisition can establish its initial proof. */
|
|
30
|
+
export declare function mergeOAuthTokenResponse(current: McpStoredAuth, context: McpGrantContext, response: McpTokenResponse): McpStoredAuth;
|
|
31
|
+
/** Active binding excludes pending registrations and display/redirect metadata. */
|
|
32
|
+
export declare function grantBinding(record: McpGrantRecord, context: McpGrantContext): {
|
|
33
|
+
issuer: string | undefined;
|
|
34
|
+
resource: string | undefined;
|
|
35
|
+
clientIdentity: string | undefined;
|
|
36
|
+
scope: string | undefined;
|
|
37
|
+
refreshScope: string | undefined;
|
|
38
|
+
};
|
|
39
|
+
export declare function validGrantProof(record: McpGrantRecord, context: McpGrantContext): McpAuthorizationProof | undefined;
|
|
40
|
+
/** Only the refresh transaction passes a predecessor, read while holding its existing token-store lock. */
|
|
41
|
+
export declare function withGrantAuthorization(record: McpStoredAuth, context: McpGrantContext, previous?: McpStoredAuth): McpStoredAuth;
|
|
42
|
+
export declare function oauthGrantIdentity(record: McpGrantRecord, context: McpGrantContext): string;
|
|
43
|
+
//# sourceMappingURL=oauth-grant.d.ts.map
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { Type } from "typebox";
|
|
3
|
+
import { Check } from "typebox/value";
|
|
4
|
+
import { isEphemeralIdentity, keyedIdentity } from "../config-identity-key.js";
|
|
5
|
+
import { canonicalAuthorizationServerIssuer } from "./oauth-issuer.js";
|
|
6
|
+
const authorizationSchema = Type.Object({
|
|
7
|
+
version: Type.Literal(1),
|
|
8
|
+
id: Type.String({ pattern: "^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$" }),
|
|
9
|
+
proof: Type.Optional(Type.Object({
|
|
10
|
+
credentialIdentity: Type.String({ pattern: "^[0-9a-f]{64}$" }),
|
|
11
|
+
previousCredentialIdentity: Type.Optional(Type.String({ pattern: "^[0-9a-f]{64}$" })),
|
|
12
|
+
issuer: Type.String({ minLength: 1 }),
|
|
13
|
+
resource: Type.String({ minLength: 1 }),
|
|
14
|
+
clientIdentity: Type.String({ pattern: "^[0-9a-f]{64}$" }),
|
|
15
|
+
scope: Type.String({ minLength: 1 }),
|
|
16
|
+
refreshScope: Type.Optional(Type.String({ minLength: 1 })),
|
|
17
|
+
})),
|
|
18
|
+
});
|
|
19
|
+
const epochSchema = Type.Object({
|
|
20
|
+
version: Type.Literal(1),
|
|
21
|
+
id: authorizationSchema.properties.id,
|
|
22
|
+
});
|
|
23
|
+
function authorizationEpoch(record) {
|
|
24
|
+
const authorization = record.authorization;
|
|
25
|
+
return Check(epochSchema, authorization) ? authorization.id : undefined;
|
|
26
|
+
}
|
|
27
|
+
function normalizeGrantedScope(scope) {
|
|
28
|
+
if (scope === undefined || !/^[\x21\x23-\x5b\x5d-\x7e]+(?: +[\x21\x23-\x5b\x5d-\x7e]+)*$/.test(scope))
|
|
29
|
+
return undefined;
|
|
30
|
+
return [...new Set(scope.split(" "))].filter(Boolean).sort().join(" ");
|
|
31
|
+
}
|
|
32
|
+
export function storedGrantIssuer(record) {
|
|
33
|
+
return record?.issuer ?? record?.discoveryState?.authorizationServerUrl?.toString();
|
|
34
|
+
}
|
|
35
|
+
export function freezeGrantAttribution(record) {
|
|
36
|
+
const next = { ...(record ?? {}) };
|
|
37
|
+
if (next.issuer !== undefined || (next.accessToken === undefined && next.refreshToken === undefined))
|
|
38
|
+
return next;
|
|
39
|
+
const issuer = storedGrantIssuer(next);
|
|
40
|
+
if (issuer !== undefined)
|
|
41
|
+
next.issuer = issuer;
|
|
42
|
+
else
|
|
43
|
+
delete next.refreshToken;
|
|
44
|
+
return next;
|
|
45
|
+
}
|
|
46
|
+
export function storedAuthToTokens(record, now = Date.now()) {
|
|
47
|
+
if (record?.accessToken === undefined || record.accessToken.length === 0)
|
|
48
|
+
return undefined;
|
|
49
|
+
const expiresIn = record.expiresAt === undefined ? undefined : Math.max(0, Math.ceil((record.expiresAt - now) / 1000));
|
|
50
|
+
const issuer = storedGrantIssuer(record);
|
|
51
|
+
return {
|
|
52
|
+
access_token: record.accessToken,
|
|
53
|
+
refresh_token: issuer === undefined ? undefined : record.refreshToken,
|
|
54
|
+
token_type: "Bearer",
|
|
55
|
+
scope: normalizeGrantedScope(record.scope),
|
|
56
|
+
...(expiresIn === undefined ? {} : { expires_in: expiresIn }),
|
|
57
|
+
...(issuer === undefined ? {} : { issuer }),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
/** Maps one response; the caller decides which preceding fields it is entitled to inherit. */
|
|
61
|
+
export function mergeTokensIntoStoredAuth(current, tokens) {
|
|
62
|
+
return {
|
|
63
|
+
...current,
|
|
64
|
+
accessToken: tokens.access_token,
|
|
65
|
+
refreshToken: tokens.refresh_token ?? current.refreshToken,
|
|
66
|
+
scope: normalizeGrantedScope(tokens.scope),
|
|
67
|
+
expiresAt: tokens.expires_in === undefined ? undefined : Date.now() + tokens.expires_in * 1000,
|
|
68
|
+
issuer: tokens.issuer ?? current.issuer,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
/** An SDK response has no locked predecessor; only explicit fresh acquisition can establish its initial proof. */
|
|
72
|
+
export function mergeOAuthTokenResponse(current, context, response) {
|
|
73
|
+
const { tokens, resource, fresh, presented, clientInfo } = response;
|
|
74
|
+
// Attribute a response to the client selected before the request, never to a later store writer.
|
|
75
|
+
const active = {
|
|
76
|
+
...current,
|
|
77
|
+
clientInfo,
|
|
78
|
+
pendingClientInfo: current.pendingClientInfo?.client_id === clientInfo?.client_id ? undefined : current.pendingClientInfo,
|
|
79
|
+
};
|
|
80
|
+
const binding = grantBinding({ ...active, issuer: tokens.issuer, resource }, context);
|
|
81
|
+
const canInherit = !fresh &&
|
|
82
|
+
presented !== undefined &&
|
|
83
|
+
JSON.stringify(grantBinding(presented, context)) === JSON.stringify(grantBinding(current, context)) &&
|
|
84
|
+
JSON.stringify(grantBinding(presented, context)) === JSON.stringify(binding) &&
|
|
85
|
+
presented.accessToken === current.accessToken &&
|
|
86
|
+
presented.refreshToken === current.refreshToken &&
|
|
87
|
+
presented.authorization?.id === current.authorization?.id;
|
|
88
|
+
const base = {
|
|
89
|
+
...active,
|
|
90
|
+
issuer: tokens.issuer,
|
|
91
|
+
resource,
|
|
92
|
+
refreshToken: canInherit ? presented.refreshToken : undefined,
|
|
93
|
+
scope: undefined,
|
|
94
|
+
refreshScope: undefined,
|
|
95
|
+
authorization: undefined,
|
|
96
|
+
};
|
|
97
|
+
const next = mergeTokensIntoStoredAuth(base, tokens);
|
|
98
|
+
return fresh
|
|
99
|
+
? withGrantAuthorization({ ...next, refreshScope: next.scope }, context)
|
|
100
|
+
: { ...next, authorization: { version: 1, id: randomUUID() } };
|
|
101
|
+
}
|
|
102
|
+
/** Active binding excludes pending registrations and display/redirect metadata. */
|
|
103
|
+
export function grantBinding(record, context) {
|
|
104
|
+
const issuer = storedGrantIssuer(record);
|
|
105
|
+
const registration = record.clientInfo;
|
|
106
|
+
const clientId = context.clientId ?? context.clientMetadataUrl ?? registration?.client_id;
|
|
107
|
+
const configured = context.clientId !== undefined || context.clientMetadataUrl !== undefined;
|
|
108
|
+
const clientIssuer = configured ? issuer : (registration?.issuer ?? issuer);
|
|
109
|
+
const clientIdentity = clientId === undefined
|
|
110
|
+
? undefined
|
|
111
|
+
: keyedIdentity(context.agentDir, JSON.stringify([
|
|
112
|
+
"mcp-oauth-client-v1",
|
|
113
|
+
clientId,
|
|
114
|
+
configured ? null : (registration?.client_secret ?? null),
|
|
115
|
+
configured ? null : (registration?.token_endpoint_auth_method ?? null),
|
|
116
|
+
clientIssuer === undefined ? null : canonicalAuthorizationServerIssuer(clientIssuer),
|
|
117
|
+
]));
|
|
118
|
+
return {
|
|
119
|
+
issuer: issuer === undefined ? undefined : canonicalAuthorizationServerIssuer(issuer),
|
|
120
|
+
resource: record.resource,
|
|
121
|
+
clientIdentity,
|
|
122
|
+
scope: normalizeGrantedScope(record.scope),
|
|
123
|
+
refreshScope: normalizeGrantedScope(record.refreshScope),
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
function credentialCommitment(record, context, id) {
|
|
127
|
+
const binding = grantBinding(record, context);
|
|
128
|
+
return keyedIdentity(context.agentDir, JSON.stringify([
|
|
129
|
+
"mcp-oauth-credential-v1",
|
|
130
|
+
context.serverName,
|
|
131
|
+
context.serverUrl,
|
|
132
|
+
id ?? null,
|
|
133
|
+
record.accessToken ?? null,
|
|
134
|
+
record.refreshToken ?? null,
|
|
135
|
+
binding.issuer ?? null,
|
|
136
|
+
binding.resource ?? null,
|
|
137
|
+
binding.clientIdentity ?? null,
|
|
138
|
+
binding.scope ?? null,
|
|
139
|
+
binding.refreshScope ?? null,
|
|
140
|
+
]));
|
|
141
|
+
}
|
|
142
|
+
export function validGrantProof(record, context) {
|
|
143
|
+
const authorization = record.authorization;
|
|
144
|
+
if (!Check(authorizationSchema, authorization) || authorization.proof === undefined)
|
|
145
|
+
return undefined;
|
|
146
|
+
const proof = authorization.proof;
|
|
147
|
+
const binding = grantBinding(record, context);
|
|
148
|
+
if (Object.entries(binding).some(([key, value]) => Reflect.get(proof, key) !== value))
|
|
149
|
+
return undefined;
|
|
150
|
+
return proof.credentialIdentity === credentialCommitment(record, context, authorization.id) ? proof : undefined;
|
|
151
|
+
}
|
|
152
|
+
/** Only the refresh transaction passes a predecessor, read while holding its existing token-store lock. */
|
|
153
|
+
export function withGrantAuthorization(record, context, previous) {
|
|
154
|
+
const predecessor = previous === undefined ? undefined : validGrantProof(previous, context);
|
|
155
|
+
const binding = grantBinding(record, context);
|
|
156
|
+
const sameBinding = predecessor !== undefined &&
|
|
157
|
+
Object.entries(binding).every(([key, value]) => Reflect.get(predecessor, key) === value);
|
|
158
|
+
const id = sameBinding && previous?.authorization !== undefined ? previous.authorization.id : randomUUID();
|
|
159
|
+
const credentialIdentity = credentialCommitment(record, context, id);
|
|
160
|
+
const proof = binding.issuer !== undefined &&
|
|
161
|
+
binding.resource !== undefined &&
|
|
162
|
+
binding.clientIdentity !== undefined &&
|
|
163
|
+
binding.scope !== undefined &&
|
|
164
|
+
!isEphemeralIdentity(credentialIdentity)
|
|
165
|
+
? {
|
|
166
|
+
...binding,
|
|
167
|
+
issuer: binding.issuer,
|
|
168
|
+
resource: binding.resource,
|
|
169
|
+
clientIdentity: binding.clientIdentity,
|
|
170
|
+
scope: binding.scope,
|
|
171
|
+
credentialIdentity,
|
|
172
|
+
previousCredentialIdentity: previous === undefined
|
|
173
|
+
? undefined
|
|
174
|
+
: credentialCommitment(previous, context, authorizationEpoch(previous)),
|
|
175
|
+
}
|
|
176
|
+
: undefined;
|
|
177
|
+
return { ...record, authorization: { version: 1, id, proof } };
|
|
178
|
+
}
|
|
179
|
+
export function oauthGrantIdentity(record, context) {
|
|
180
|
+
const epoch = authorizationEpoch(record);
|
|
181
|
+
const proof = validGrantProof(record, context);
|
|
182
|
+
return proof === undefined
|
|
183
|
+
? keyedIdentity(context.agentDir, JSON.stringify(["mcp-oauth-opaque-v1", credentialCommitment(record, context, epoch)]))
|
|
184
|
+
: keyedIdentity(context.agentDir, JSON.stringify([
|
|
185
|
+
"mcp-oauth-authorization-v1",
|
|
186
|
+
context.serverName,
|
|
187
|
+
context.serverUrl,
|
|
188
|
+
epoch,
|
|
189
|
+
proof.issuer,
|
|
190
|
+
proof.resource,
|
|
191
|
+
proof.clientIdentity,
|
|
192
|
+
proof.scope,
|
|
193
|
+
proof.refreshScope ?? null,
|
|
194
|
+
]));
|
|
195
|
+
}
|
|
196
|
+
//# sourceMappingURL=oauth-grant.js.map
|
|
@@ -1,6 +1,12 @@
|
|
|
1
|
-
import type { OAuthServerInfo } from "@modelcontextprotocol/sdk/client/auth.js";
|
|
1
|
+
import type { OAuthDiscoveryState, OAuthServerInfo } from "@modelcontextprotocol/sdk/client/auth.js";
|
|
2
2
|
import type { McpStoredAuth } from "./token-store.ts";
|
|
3
3
|
export declare function canonicalAuthorizationServerIssuer(issuer: string): string;
|
|
4
|
+
/**
|
|
5
|
+
* Whether a stored discovery record carries its own evidence for the authorization server it names (senpi#4924):
|
|
6
|
+
* that server's metadata, the MCP server's protected-resource metadata naming it, or the MCP server's own origin
|
|
7
|
+
* (the SDK's legacy root fallback). A bare URL from anywhere else would send the SDK to an unverified server.
|
|
8
|
+
*/
|
|
9
|
+
export declare function discoveryNamesItsServer(state: OAuthDiscoveryState, serverUrl: string): boolean;
|
|
4
10
|
export declare function validatedAuthorizationServerIssuer(info: OAuthServerInfo, serverName: string): string;
|
|
5
11
|
export declare function credentialsMatchIssuer(record: McpStoredAuth | undefined, issuer: string): boolean;
|
|
6
12
|
//# sourceMappingURL=oauth-issuer.d.ts.map
|
|
@@ -3,6 +3,19 @@ import { OAuthFlowError } from "./oauth-errors.js";
|
|
|
3
3
|
export function canonicalAuthorizationServerIssuer(issuer) {
|
|
4
4
|
return issuer.replace(/^(https?:\/\/[^/?#]+)\/$/, "$1");
|
|
5
5
|
}
|
|
6
|
+
/**
|
|
7
|
+
* Whether a stored discovery record carries its own evidence for the authorization server it names (senpi#4924):
|
|
8
|
+
* that server's metadata, the MCP server's protected-resource metadata naming it, or the MCP server's own origin
|
|
9
|
+
* (the SDK's legacy root fallback). A bare URL from anywhere else would send the SDK to an unverified server.
|
|
10
|
+
*/
|
|
11
|
+
export function discoveryNamesItsServer(state, serverUrl) {
|
|
12
|
+
if (state.authorizationServerMetadata !== undefined)
|
|
13
|
+
return true;
|
|
14
|
+
const named = canonicalAuthorizationServerIssuer(String(state.authorizationServerUrl));
|
|
15
|
+
if (named === new URL(serverUrl).origin)
|
|
16
|
+
return true;
|
|
17
|
+
return (state.resourceMetadata?.authorization_servers?.some((server) => canonicalAuthorizationServerIssuer(String(server)) === named) ?? false);
|
|
18
|
+
}
|
|
6
19
|
// RFC 8414 section 3.3: identifiers from metadata are compared verbatim, not as normalized URLs.
|
|
7
20
|
export function validatedAuthorizationServerIssuer(info, serverName) {
|
|
8
21
|
// The SDK's legacy fallback discovers root metadata and spells the origin with a trailing slash.
|
|
@@ -1,34 +1,21 @@
|
|
|
1
1
|
import type { OAuthClientProvider, OAuthDiscoveryState } from "@modelcontextprotocol/sdk/client/auth.js";
|
|
2
2
|
import type { OAuthClientInformationFull, OAuthClientInformationMixed, OAuthClientMetadata, OAuthTokens } from "@modelcontextprotocol/sdk/shared/auth.js";
|
|
3
3
|
import type { McpLogger } from "../log.ts";
|
|
4
|
-
import
|
|
5
|
-
|
|
4
|
+
import { type McpGrantContext } from "./oauth-grant.ts";
|
|
5
|
+
import type { McpTokenStore } from "./token-store.ts";
|
|
6
6
|
export interface McpOAuthProviderOptions {
|
|
7
7
|
serverName: string;
|
|
8
8
|
serverUrl: string;
|
|
9
9
|
store: McpTokenStore;
|
|
10
10
|
redirectUrl?: string | URL;
|
|
11
|
+
/** This flow sends an authorize request: reuse only a stored registration naming `redirectUrl` (senpi#6327). */
|
|
12
|
+
requireRegisteredRedirect?: boolean;
|
|
11
13
|
scopes?: string[];
|
|
12
14
|
clientId?: string;
|
|
13
15
|
clientMetadataUrl?: string;
|
|
14
16
|
logger?: McpLogger;
|
|
15
17
|
onRedirect?: (url: URL) => void | Promise<void>;
|
|
16
18
|
}
|
|
17
|
-
export declare function tokenExpiresAt(tokens: OAuthTokens, now?: number): number | undefined;
|
|
18
|
-
/**
|
|
19
|
-
* The authorization server a stored grant belongs to (senpi#2940, GHSA-6qxp-vccf-f47h): its `issuer` stamp, or
|
|
20
|
-
* for a record saved before the stamp existed, the authorization server its sign-in discovered. A record with
|
|
21
|
-
* neither cannot be attributed, so its refresh token is never presented anywhere.
|
|
22
|
-
*/
|
|
23
|
-
export declare function storedGrantIssuer(record: McpStoredAuth | undefined): string | undefined;
|
|
24
|
-
/**
|
|
25
|
-
* Fixes who an unstamped grant belongs to before the discovery record it falls back to is replaced (senpi#2940).
|
|
26
|
-
* A later discovery reflects what the MCP server advertises now, not where the grant was issued, so it must never
|
|
27
|
-
* attribute an existing grant: the grant keeps the server it was attributed to, or loses its refresh token.
|
|
28
|
-
*/
|
|
29
|
-
export declare function freezeGrantAttribution(record: McpStoredAuth | undefined): McpStoredAuth;
|
|
30
|
-
export declare function storedAuthToTokens(record: McpStoredAuth | undefined, now?: number): OAuthTokens | undefined;
|
|
31
|
-
export declare function mergeTokensIntoStoredAuth(current: McpStoredAuth | undefined, tokens: OAuthTokens, serverUrl: string, issuer?: string): McpStoredAuth;
|
|
32
19
|
export declare class McpOAuthProvider implements OAuthClientProvider {
|
|
33
20
|
#private;
|
|
34
21
|
constructor(options: McpOAuthProviderOptions);
|
|
@@ -45,6 +32,7 @@ export declare class McpOAuthProvider implements OAuthClientProvider {
|
|
|
45
32
|
saveDiscoveryState(state: OAuthDiscoveryState): Promise<void>;
|
|
46
33
|
discoveryState(): OAuthDiscoveryState | undefined;
|
|
47
34
|
tokens(): OAuthTokens | undefined;
|
|
35
|
+
withFreshAuthorization<T>(exchange: () => Promise<T>): Promise<T>;
|
|
48
36
|
saveTokens(tokens: OAuthTokens): Promise<void>;
|
|
49
37
|
saveCodeVerifier(codeVerifier: string): Promise<void>;
|
|
50
38
|
codeVerifier(): string;
|
|
@@ -55,5 +43,6 @@ export declare class McpOAuthProvider implements OAuthClientProvider {
|
|
|
55
43
|
get serverUrl(): string;
|
|
56
44
|
get store(): McpTokenStore;
|
|
57
45
|
get scopes(): string[] | undefined;
|
|
46
|
+
get grantContext(): McpGrantContext;
|
|
58
47
|
}
|
|
59
48
|
//# sourceMappingURL=oauth-provider.d.ts.map
|
|
@@ -2,84 +2,9 @@ import { randomBytes } from "node:crypto";
|
|
|
2
2
|
import { oauthBrandName } from "@earendil-works/pi-ai/utils/oauth-page";
|
|
3
3
|
import { fingerprintSecret } from "../log.js";
|
|
4
4
|
import { OAuthFlowError } from "./oauth-errors.js";
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
export function tokenExpiresAt(tokens, now = Date.now()) {
|
|
9
|
-
if (tokens.expires_in === undefined)
|
|
10
|
-
return undefined;
|
|
11
|
-
return now + tokens.expires_in * 1000;
|
|
12
|
-
}
|
|
13
|
-
/**
|
|
14
|
-
* The authorization server a stored grant belongs to (senpi#2940, GHSA-6qxp-vccf-f47h): its `issuer` stamp, or
|
|
15
|
-
* for a record saved before the stamp existed, the authorization server its sign-in discovered. A record with
|
|
16
|
-
* neither cannot be attributed, so its refresh token is never presented anywhere.
|
|
17
|
-
*/
|
|
18
|
-
export function storedGrantIssuer(record) {
|
|
19
|
-
return record?.issuer ?? record?.discoveryState?.authorizationServerUrl?.toString();
|
|
20
|
-
}
|
|
21
|
-
/**
|
|
22
|
-
* Fixes who an unstamped grant belongs to before the discovery record it falls back to is replaced (senpi#2940).
|
|
23
|
-
* A later discovery reflects what the MCP server advertises now, not where the grant was issued, so it must never
|
|
24
|
-
* attribute an existing grant: the grant keeps the server it was attributed to, or loses its refresh token.
|
|
25
|
-
*/
|
|
26
|
-
export function freezeGrantAttribution(record) {
|
|
27
|
-
const next = { ...(record ?? {}) };
|
|
28
|
-
if (next.issuer !== undefined || (next.accessToken === undefined && next.refreshToken === undefined))
|
|
29
|
-
return next;
|
|
30
|
-
const issuer = storedGrantIssuer(next);
|
|
31
|
-
if (issuer !== undefined)
|
|
32
|
-
next.issuer = issuer;
|
|
33
|
-
else
|
|
34
|
-
delete next.refreshToken;
|
|
35
|
-
return next;
|
|
36
|
-
}
|
|
37
|
-
/**
|
|
38
|
-
* Whether a stored discovery record carries its own evidence for the authorization server it names (senpi#4924):
|
|
39
|
-
* that server's metadata, the MCP server's protected-resource metadata naming it, or the MCP server's own origin
|
|
40
|
-
* (the SDK's legacy root fallback). A bare URL from anywhere else would send the SDK to an unverified server.
|
|
41
|
-
*/
|
|
42
|
-
function discoveryNamesItsServer(state, serverUrl) {
|
|
43
|
-
if (state.authorizationServerMetadata !== undefined)
|
|
44
|
-
return true;
|
|
45
|
-
const named = canonicalAuthorizationServerIssuer(String(state.authorizationServerUrl));
|
|
46
|
-
if (named === new URL(serverUrl).origin)
|
|
47
|
-
return true;
|
|
48
|
-
return (state.resourceMetadata?.authorization_servers?.some((server) => canonicalAuthorizationServerIssuer(String(server)) === named) ?? false);
|
|
49
|
-
}
|
|
50
|
-
export function storedAuthToTokens(record, now = Date.now()) {
|
|
51
|
-
if (record?.accessToken === undefined || record.accessToken.length === 0)
|
|
52
|
-
return undefined;
|
|
53
|
-
const expiresIn = record.expiresAt === undefined ? undefined : Math.max(0, Math.ceil((record.expiresAt - now) / 1000));
|
|
54
|
-
const issuer = storedGrantIssuer(record);
|
|
55
|
-
return {
|
|
56
|
-
access_token: record.accessToken,
|
|
57
|
-
// An unattributed refresh token is withheld: the SDK would post it to whichever server it discovers.
|
|
58
|
-
refresh_token: issuer === undefined ? undefined : record.refreshToken,
|
|
59
|
-
token_type: "Bearer",
|
|
60
|
-
...(expiresIn === undefined ? {} : { expires_in: expiresIn }),
|
|
61
|
-
...(issuer === undefined ? {} : { issuer }),
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
export function mergeTokensIntoStoredAuth(current, tokens, serverUrl, issuer) {
|
|
65
|
-
const next = {
|
|
66
|
-
...(current ?? {}),
|
|
67
|
-
accessToken: tokens.access_token,
|
|
68
|
-
resource: current?.resource ?? serverUrl,
|
|
69
|
-
};
|
|
70
|
-
const refreshToken = tokens.refresh_token ?? current?.refreshToken;
|
|
71
|
-
if (refreshToken !== undefined)
|
|
72
|
-
next.refreshToken = refreshToken;
|
|
73
|
-
const expiresAt = tokenExpiresAt(tokens);
|
|
74
|
-
if (expiresAt !== undefined)
|
|
75
|
-
next.expiresAt = expiresAt;
|
|
76
|
-
else
|
|
77
|
-
delete next.expiresAt;
|
|
78
|
-
const stamp = tokens.issuer ?? issuer;
|
|
79
|
-
if (stamp !== undefined)
|
|
80
|
-
next.issuer = stamp;
|
|
81
|
-
return next;
|
|
82
|
-
}
|
|
5
|
+
import { freezeGrantAttribution, grantBinding, mergeOAuthTokenResponse, storedAuthToTokens, storedGrantIssuer, } from "./oauth-grant.js";
|
|
6
|
+
import { canonicalAuthorizationServerIssuer, credentialsMatchIssuer, discoveryNamesItsServer, validatedAuthorizationServerIssuer, } from "./oauth-issuer.js";
|
|
7
|
+
import { grantForFlow, registrationForFlow, withPromotedRegistration, withRegistration, } from "./registered-redirect.js";
|
|
83
8
|
// Implements the SDK OAuthClientProvider backed by the URL-bound token store.
|
|
84
9
|
// All persistence goes through the store so tokens survive process restarts and
|
|
85
10
|
// stay lockable across processes.
|
|
@@ -87,6 +12,10 @@ export class McpOAuthProvider {
|
|
|
87
12
|
#options;
|
|
88
13
|
#expectedState;
|
|
89
14
|
#lastAuthorizationUrl;
|
|
15
|
+
#freshAuthorization = false;
|
|
16
|
+
#resource;
|
|
17
|
+
#presentedGrant;
|
|
18
|
+
#selectedClient;
|
|
90
19
|
constructor(options) {
|
|
91
20
|
this.#options = options;
|
|
92
21
|
}
|
|
@@ -133,14 +62,23 @@ export class McpOAuthProvider {
|
|
|
133
62
|
return { client_id: this.#options.clientId };
|
|
134
63
|
if (this.#options.clientMetadataUrl !== undefined)
|
|
135
64
|
return { client_id: this.#options.clientMetadataUrl };
|
|
136
|
-
|
|
65
|
+
this.#selectedClient = registrationForFlow(this.#store.read(), this.#options);
|
|
66
|
+
return this.#selectedClient;
|
|
137
67
|
}
|
|
138
68
|
async saveClientInformation(info) {
|
|
139
69
|
const discovery = this.discoveryState();
|
|
140
70
|
const issuer = discovery === undefined
|
|
141
71
|
? info.issuer && canonicalAuthorizationServerIssuer(info.issuer)
|
|
142
72
|
: validatedAuthorizationServerIssuer(discovery, this.serverName);
|
|
143
|
-
await this.#store.update((current) =>
|
|
73
|
+
const saved = await this.#store.update((current) => {
|
|
74
|
+
const next = withRegistration(current, { ...info, issuer }, this.#options);
|
|
75
|
+
return current !== undefined &&
|
|
76
|
+
JSON.stringify(grantBinding(current, this.grantContext)) !==
|
|
77
|
+
JSON.stringify(grantBinding(next, this.grantContext))
|
|
78
|
+
? { ...next, scope: undefined, refreshScope: undefined, authorization: undefined }
|
|
79
|
+
: next;
|
|
80
|
+
});
|
|
81
|
+
this.#selectedClient = registrationForFlow(saved, this.#options);
|
|
144
82
|
}
|
|
145
83
|
async saveDiscoveryState(state) {
|
|
146
84
|
const issuer = validatedAuthorizationServerIssuer(state, this.serverName);
|
|
@@ -183,14 +121,37 @@ export class McpOAuthProvider {
|
|
|
183
121
|
return state;
|
|
184
122
|
}
|
|
185
123
|
tokens() {
|
|
186
|
-
|
|
124
|
+
const grant = grantForFlow(this.#store.read(), this.#options);
|
|
125
|
+
this.#presentedGrant = withPromotedRegistration(grant, this.#options);
|
|
126
|
+
return storedAuthToTokens(grant);
|
|
127
|
+
}
|
|
128
|
+
async withFreshAuthorization(exchange) {
|
|
129
|
+
const previous = this.#freshAuthorization;
|
|
130
|
+
this.#freshAuthorization = true;
|
|
131
|
+
try {
|
|
132
|
+
return await exchange();
|
|
133
|
+
}
|
|
134
|
+
finally {
|
|
135
|
+
this.#freshAuthorization = previous;
|
|
136
|
+
}
|
|
187
137
|
}
|
|
188
138
|
async saveTokens(tokens) {
|
|
189
139
|
const discovery = this.discoveryState();
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
140
|
+
// SDK results carry the exchange's issuer. Never restamp a late response from a newer file snapshot.
|
|
141
|
+
const issuer = tokens.issuer !== undefined
|
|
142
|
+
? canonicalAuthorizationServerIssuer(tokens.issuer)
|
|
143
|
+
: this.#freshAuthorization && discovery !== undefined
|
|
144
|
+
? validatedAuthorizationServerIssuer(discovery, this.serverName)
|
|
145
|
+
: storedGrantIssuer(this.#presentedGrant);
|
|
146
|
+
// A flow's staged registration replaces the signed-in client in the same write as its tokens (senpi#6327).
|
|
147
|
+
const response = {
|
|
148
|
+
tokens: { ...tokens, issuer },
|
|
149
|
+
resource: this.#resource ?? new URL(this.serverUrl).href,
|
|
150
|
+
fresh: this.#freshAuthorization,
|
|
151
|
+
presented: this.#presentedGrant,
|
|
152
|
+
clientInfo: this.#selectedClient ?? this.#presentedGrant?.clientInfo,
|
|
153
|
+
};
|
|
154
|
+
this.#presentedGrant = await this.#store.update((current) => mergeOAuthTokenResponse(current ?? {}, this.grantContext, response));
|
|
194
155
|
this.#logFingerprint("saveTokens", tokens.access_token);
|
|
195
156
|
}
|
|
196
157
|
async saveCodeVerifier(codeVerifier) {
|
|
@@ -211,7 +172,9 @@ export class McpOAuthProvider {
|
|
|
211
172
|
}
|
|
212
173
|
// RFC 8707: bind the token to this MCP resource.
|
|
213
174
|
validateResourceURL(serverUrl, resource) {
|
|
214
|
-
|
|
175
|
+
const selected = new URL(resource ?? String(serverUrl));
|
|
176
|
+
this.#resource = selected.href;
|
|
177
|
+
return Promise.resolve(selected);
|
|
215
178
|
}
|
|
216
179
|
async invalidateCredentials(scope) {
|
|
217
180
|
if (scope === "all") {
|
|
@@ -227,10 +190,17 @@ export class McpOAuthProvider {
|
|
|
227
190
|
delete next.refreshToken;
|
|
228
191
|
delete next.expiresAt;
|
|
229
192
|
}
|
|
193
|
+
if (scope === "tokens" || scope === "client") {
|
|
194
|
+
return {
|
|
195
|
+
...next,
|
|
196
|
+
scope: undefined,
|
|
197
|
+
refreshScope: undefined,
|
|
198
|
+
authorization: undefined,
|
|
199
|
+
...(scope === "client" ? { clientInfo: undefined } : {}),
|
|
200
|
+
};
|
|
201
|
+
}
|
|
230
202
|
if (scope === "verifier")
|
|
231
203
|
next.codeVerifier = undefined;
|
|
232
|
-
if (scope === "client")
|
|
233
|
-
next.clientInfo = undefined;
|
|
234
204
|
if (scope === "discovery")
|
|
235
205
|
next.discoveryState = undefined;
|
|
236
206
|
return next;
|
|
@@ -248,6 +218,15 @@ export class McpOAuthProvider {
|
|
|
248
218
|
get scopes() {
|
|
249
219
|
return this.#options.scopes;
|
|
250
220
|
}
|
|
221
|
+
get grantContext() {
|
|
222
|
+
return {
|
|
223
|
+
serverName: this.serverName,
|
|
224
|
+
serverUrl: this.serverUrl,
|
|
225
|
+
agentDir: this.store.agentDir,
|
|
226
|
+
clientId: this.#options.clientId,
|
|
227
|
+
clientMetadataUrl: this.#options.clientMetadataUrl,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
251
230
|
get #store() {
|
|
252
231
|
return this.#options.store;
|
|
253
232
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { OAuthServerInfo } from "@modelcontextprotocol/sdk/client/auth.js";
|
|
2
2
|
import type { AuthorizationServerMetadata, OAuthTokens } from "@modelcontextprotocol/sdk/shared/auth.js";
|
|
3
3
|
import type { FetchLike } from "@modelcontextprotocol/sdk/shared/transport.js";
|
|
4
|
-
import {
|
|
4
|
+
import type { McpOAuthProvider } from "./oauth-provider.ts";
|
|
5
5
|
import type { McpStoredAuth } from "./token-store.ts";
|
|
6
6
|
export interface RefreshManagerOptions {
|
|
7
7
|
fetchFn?: FetchLike;
|