feat(mcp): add tool-call structured logger (#373 stack 3/3)#377
Open
adriannoes wants to merge 19 commits into
Open
feat(mcp): add tool-call structured logger (#373 stack 3/3)#377adriannoes wants to merge 19 commits into
adriannoes wants to merge 19 commits into
Conversation
This was referenced Jul 8, 2026
Introduce allowlisted JSON event builders and a dedicated stdout logger with propagate=False so hosted observability coexists with FastMCP's RichHandler. Add PIPEFY_MCP_LOG_LEVEL to McpSettings and wire it through run_server and FastMCP construction.
Extend the resource-server token mapping with an optional sub field so hosted HTTP request logs can emit caller subject alongside client_id without re-decoding the bearer.
normalize_log_level resolved names via getattr(logging, ...), which returns non-level module attributes (BASIC_FORMAT) and accepts aliases the settings Literal rejects (WARN, FATAL); an explicit map fails loudly on both. The observability package now re-exports its stdlib-only emitter surface per the package convention, and a suite-wide autouse fixture drops leftover stdout handlers so a test that configures the process-global logger cannot leak a closed-stream handler into later tests.
…he token PipefyAccessToken.sub is str | None, so a validly-signed token carrying a numeric sub raised ValidationError inside _to_access_token and verify_token turned it into a 401, a token dev accepted before the field existed. sub feeds request logging only; a malformed value must not gate authentication. RFC 7519 requires StringOrURI, so only non-compliant IdPs ever hit this.
adriannoes
force-pushed
the
feat/373-tool-call-logger
branch
from
July 8, 2026 14:34
f5c9236 to
43c1c93
Compare
adriannoes
force-pushed
the
feat/373-request-log-middleware
branch
from
July 8, 2026 14:34
a843c1c to
f9bca74
Compare
This was referenced Jul 8, 2026
Keep the stdlib-only JSON emitter package surface from this stack and the tool-call middleware composition root from #378; wire both into server.py.
Emit one structured JSON line per HTTP request from middleware that only inspects response.start, stores request_id in scope state, and reads caller identity from the auth context at emit time.
Wire RequestLogMiddleware through streamable_http_app once and replace FastMCP.run(streamable-http) with explicit uvicorn serve and access_log disabled so structured request lines replace text access logs.
configure_observability_logging ran before the transport branch, arming a stdout handler in the stdio process too, where stdout is the JSON-RPC wire. Nothing emits under stdio today, but the D10 guarantee was conventional rather than structural: one future emit call in shared code would corrupt the protocol. The call now lives in _serve_streamable_http, and the stdio test asserts configuration never happens there. Also imports AuthenticatedUser from its defining module (bearer_auth) and extends the middleware tests: bearer absent from output with an Authorization header planted, UnauthenticatedUser yields null identity, the ordering test fails fast instead of hanging, and a send that raises mid-stream still emits exactly one line.
Route the hosted observability handler to stderr so structured lines never share the stdio JSON-RPC stdout channel. Collectors already capture both streams; HTTP-only configure remains the gate that keeps local installs clean.
Prefer an upstream request or correlation id when present so hosted request logs stay correlated across service boundaries; mint a UUID only when both headers are absent or blank.
PIPEFY_MCP_LOG_LEVEL now governs only the FastMCP root logger. The hosted structured emitter stays at INFO so quieting noisy text logs cannot silently drop request/tool debugging lines. Docs frame these events as debugging with a privacy allowlist, not an audit trail.
Reuse build_tool_call_event/emit_structured_event so #378 tool lines share the pinned stderr JSON channel and allowlisted shape (including cancelled and elicitation outcomes) instead of a parallel logger.info path.
adriannoes
force-pushed
the
feat/373-request-log-middleware
branch
from
July 9, 2026 18:43
4b0f34b to
323206e
Compare
adriannoes
force-pushed
the
feat/373-tool-call-logger
branch
from
July 9, 2026 18:44
43c1c93 to
5b0a506
Compare
Collaborator
Author
Stack 3/3 reshaped after #378 landed in
|
adriannoes
marked this pull request as ready for review
July 13, 2026 11:34
http_request lines include sub and client_id by design; tool_call lines omit sub until a consumer needs it. Deduplicate the Hosted structured logging section left by the stack merge.
The parameter was threaded through call sites and tests but never read. Settings-driven toggles can return when there is a real consumer.
Drive wire_hosted_observability with FastMCP's AuthenticationMiddleware so sub/client_id are asserted from a real scope["user"] (including the UnauthenticatedUser 401 path), not from an injected unit-test scope.
Collaborator
Author
Follow-ups from #376 reviewThese commits address Daniel's open review threads on #376 (merged before they were handled):
Threads on #376 are replied to and resolved, with links back here. Original stack scope (CHANGELOG + correlation e2e) is unchanged above these. c/c: @Danielmoraisg |
Danielmoraisg
previously approved these changes
Jul 14, 2026
Swap em dashes for commas/parens in shipped docstrings and the hosted-logging changelog entry per #377 review.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of #373 (stack 3/3).
Summary
request_idcorrelation under stateful Streamable HTTP (D4), using#378'stool_log_middleware+ the shared structured emitter from stacks 1–2.Follow-ups from #376 review (Daniel)
Addressed here so the stack closes cleanly after #376 merged:
801800af): document thathttp_requestlines include callersub/client_idby design;tool_calllines omitsubuntil a consumer needs it. Also dedupe the Hosted structured logging section._settings(7f4635b2):wire_hosted_observability(app)only — the settings arg was never read.c5f9d745): drivewire_hosted_observabilitythrough FastMCP's realAuthenticationMiddlewareand assertsub/client_id(including theUnauthenticatedUser401 path), replacing the call-only wiring mocks.What this PR deliberately does not do
CallToolRequesttool logger. After feat(mcp): add tool-call middleware seam #378 merged todev, tool lines are emitted bytool_log_middleware(unified ontoemit_structured_eventin feat(mcp): add HTTP request log middleware and hosted wiring (#373 stack 2/3) #376). A parallel wrap would double-log.Stack status
devafter feat(mcp): add HTTP request log middleware and hosted wiring (#373 stack 2/3) #376 (+ feat(mcp): replace bind-host guard with auth-posture interlock #396 bind interlock).Test plan
uv run pytest packages/mcp/tests/observability/test_observability_end_to_end.py packages/mcp/tests/observability/test_wiring.py packages/mcp/tests/test_server.py -quv run ruff check packages/mcp && uv run ruff format --check packages/mcp