Python SDK: support `phase="both"` as decorator sugar
@olaservo is already working on this.
Since Aug 13, 2026.
Assessment
This issue has not been assessed yet.
Description
Follow-up to #30, from @jeongukjae's review thread on types.py about the both phase.
Context
The SEP is explicit that both is not a wire value (docs/sep.md, Hooks section):
Hooks define which Lifecycle Events trigger an interceptor's invocation. The
hooksarray in the interceptor definition contains one or more entries, each declaring a set of Lifecycle Events (events) and a phase (phase:"request"or"response") where this interceptor will be invoked. Interceptors that run on both phases use two entries — one per phase.
The C# SDK follows this: InterceptorPhase.Both is documented as valid only at the attribute/SDK convenience layer, and the reflection layer expands it into two InterceptorHook entries before anything reaches the wire.
The Python SDK currently has no equivalent convenience — Phase = Literal["request", "response"] is used both as the wire type and as the decorator parameter, so an interceptor that runs on both phases must pass an explicit two-entry hooks=[...].
Proposal
Add both as decorator-level sugar only, matching the C# model:
- Introduce a separate literal for the convenience layer (e.g.
HookPhase = Literal["request", "response", "both"]) and accept it as thephase=parameter onInterceptors.validator(),.mutator(), and.sink(). - Expand it inside
build_hooks()ininterceptor.pyinto twoHookentries, one per phase. - Leave the wire type
Phase = Literal["request", "response"]unchanged, soHook,InterceptorResult, and the invoke/execute params keep rejectingboth. - Add a test asserting that
phase="both"produces exactly two hook entries and thatHook(phase="both")still fails validation.
This is purely additive and has no wire impact.
Non-goal
Accepting both on the wire. Hook matching in chain.py should keep comparing against request/response only — the expansion happens at registration time, not at dispatch time.
Related: cross-SDK divergence
Worth tracking separately, but it's the reason this came up. The Go SDK takes the dispatch-time approach rather than the expansion approach: PhaseBoth is a value of InterceptionPhase, which is the type of the JSON-serialized Hook.Phase field, and matchesHooks in chain/chain.go treats PhaseBoth as matching any target phase. That means a Go interceptor declaring both serializes "phase": "both" in its interceptors/list response, which the SEP disallows and which the Python and C# SDKs would reject when parsing. Filing a separate issue for that.
cc @PederHP @Degiorgio @jeongukjae
- Dominant language
- C#
- Stars
- 23
- Forks
- 15
- PR merge metrics
- No merged PRs in 30d
Getting set up
- No Dockerfile or Docker Compose file
- No pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from modelcontextprotocol/experimental-ext-interceptors
-
Difficulty 4/5 3-5 days Newbie friendliness 42/100
modelcontextprotocol/experimental-ext-interceptors#29 · 1 comment ·
-
enhancement
Difficulty 5/5 Over a week Newbie friendliness 25/100
modelcontextprotocol/experimental-ext-interceptors#20 · 1 comment ·
-
Add HTTP example for transparent-proxy (TypeScript SDK)Possibly taken @murthy-g claimed this 4 days ago. Openenhancement
Difficulty 4/5 3-5 days Newbie friendliness 48/100
-
bug
Difficulty 5/5 Over a week Newbie friendliness 35/100
All issues in modelcontextprotocol/experimental-ext-interceptors
Similar issues
-
devtools
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
Maintainers usually reply within 1 day
-
clawsweeper:fix-shape-clear clawsweeper:needs-maintainer-review clawsweeper:no-new-fix-pr clawsweeper:not-repro-on-main impact:ux-release-blocker issue-rating: 🦪 silver shellfish P0
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
openclaw/openclaw-windows-node#1843 · 1 comment · 1 reaction ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 66/100
-
kind:ux size:S type:bug
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
elsa-workflows/elsa-foundation#3256 ·
Maintainers usually reply within 1 day
-
[Bug]: Revit selector reads process ids 2000-2099 as years (flaky AgentClientTests.AYearOrAProcessIdSelectsTheHost)Possibly taken @Wurschdhaud claimed this today. Open
Difficulty 2/5 1-3 hours Newbie friendliness 83/100
pyrevitlabs/pyRevit#3817 ·
Maintainers usually reply within 1 day