Record arguments and results safely
By default the middleware records sizes, not contents. When you need the
arguments and results themselves, for debugging or for replaying a failure,
switch to full mode. Secrets are removed before anything reaches a sink.
Choose a mode
Section titled “Choose a mode”| Mode | Records |
|---|---|
off | Nothing; calls pass straight through |
meta (default) | Tool, timing, outcome, identity, argument and result sizes |
full | Also the arguments and result, redacted |
Set it in code:
from fastmcp import FastMCPfrom fastmcp_feedback.instrumentation import MemorySink, instrument
app = FastMCP("My Server")sink = MemorySink()mw = instrument(app, [sink], mode="full")or with the environment, which is read when the middleware is created and
applies when mode is not passed:
FEEDBACK_INSTRUMENTATION_MODE=full uv run server.pyKeep some tools at meta
Section titled “Keep some tools at meta”Tools whose arguments are sensitive by nature (code to run, credentials to
create) can stay at meta while the rest of the server records in full:
mw = instrument(app, [sink], mode="full", meta_only_tools={"run_code", "create_token"})What redaction removes
Section titled “What redaction removes”Redaction applies to arguments, results, error messages, identity and enricher output, and event attributes. It works two ways:
- By key, at any depth: a dict value is replaced with
[REDACTED]when its key, lowercased with-turned into_, is one oftoken,password,authorization,cookie,secret,client_secret,api_key,private_keyand similar, or ends in_token,_secret,_password,_api_key,_apikeyor_private_key. - By value, anywhere in text: JWTs,
Bearer ...,sk-...API keys, GitHub, Slack, AWS, PyPI andbmcp_tokens, andpassword=...,token: ...style pairs in error text.
The full lists are in the redaction reference.
import asyncio
from fastmcp import Client
@app.tooldef deploy(service: str, github_token: str) -> dict: return {"service": service, "log": "pushed with Bearer abcdef0123456789"}
async def main(): async with Client(app) as client: await client.call_tool("deploy", {"service": "api", "github_token": "ghp_x"}) await mw.flush() call = sink.calls[-1] print(call.args) # {'service': 'api', 'github_token': '[REDACTED]'} print(call.result) # {'service': 'api', 'log': 'pushed with [REDACTED]'}
asyncio.run(main())Sizes (args_size, result_size) are measured before redaction.
Add your own rules
Section titled “Add your own rules”Pass a Redactor with extra keys and regular expressions. They are added to
the defaults, not substituted for them:
from fastmcp_feedback.instrumentation import Redactor, instrument
app = FastMCP("Licensed Server")mw = instrument( app, mode="full", redactor=Redactor(extra_keys=["license"], extra_patterns=[r"acme_[A-Za-z0-9]{32}"]),)To replace a default list, pass keys= or patterns= instead. The same
redactor is used for embedded text when you add an
embedding sink.