traceAI-mcp-use 0.1.0__tar.gz

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.
@@ -0,0 +1,399 @@
1
+ Metadata-Version: 2.4
2
+ Name: traceAI-mcp-use
3
+ Version: 0.1.0
4
+ Summary: OpenTelemetry tracing for mcp-use agents: one span per agent run, LLM call and tool call
5
+ License: Apache-2.0
6
+ Keywords: traceai,futureagi,opentelemetry,tracing,observability,llm,mcp-use
7
+ Author: Future AGI
8
+ Author-email: no-reply@futureagi.com
9
+ Requires-Python: >=3.11,<3.14
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Framework :: OpenTelemetry
12
+ Classifier: Framework :: OpenTelemetry :: Instrumentations
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: Apache Software License
15
+ Classifier: Programming Language :: Python
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Programming Language :: Python :: 3 :: Only
21
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
+ Classifier: Topic :: System :: Monitoring
23
+ Requires-Dist: fi-instrumentation-otel (>=1.1.0)
24
+ Requires-Dist: langchain-core (>=1.0.0,<2)
25
+ Requires-Dist: mcp-use (>=1.7.1,<2)
26
+ Project-URL: Changelog, https://github.com/future-agi/traceAI/blob/dev/python/frameworks/mcp-use/CHANGELOG.md
27
+ Project-URL: Documentation, https://docs.futureagi.com/docs/tracing
28
+ Project-URL: Homepage, https://futureagi.com
29
+ Project-URL: Issues, https://github.com/future-agi/traceAI/issues
30
+ Project-URL: Repository, https://github.com/future-agi/traceAI/tree/dev/python/frameworks/mcp-use
31
+ Description-Content-Type: text/markdown
32
+
33
+ # traceAI-mcp-use
34
+
35
+ OpenTelemetry tracing for [mcp-use](https://pypi.org/project/mcp-use/)
36
+ agents (`mcp_use.MCPAgent`), sent to Future AGI.
37
+
38
+ `FutureAGICallback` is a LangChain callback handler. You pass it to the
39
+ agent with `MCPAgent(..., callbacks=[...])`. It records one span for each
40
+ agent run, each LLM call and each tool call. It patches nothing, imports
41
+ neither `langfuse` nor `traceai_mcp`, and sets no environment variables.
42
+
43
+ This package traces the agent loop. [`traceAI-mcp`](../mcp) (`traceai_mcp`)
44
+ works at the MCP transport level and is a different layer: a green
45
+ transport test does not show the agent run, its model calls or its tool
46
+ calls. See [Other tracing in the same process](#other-tracing-in-the-same-process).
47
+
48
+ ## Installation
49
+
50
+ ```bash
51
+ pip install traceAI-mcp-use
52
+ ```
53
+
54
+ It accepts `mcp-use>=1.7.1,<2`, `langchain-core>=1.0.0,<2` and
55
+ `fi-instrumentation-otel>=1.1.0`, on Python `>=3.11,<3.14` (mcp-use itself
56
+ requires 3.11). The suite runs with mcp-use 1.7.1 (the latest release on
57
+ 2026-10-16) on Python 3.11, 3.12 and 3.13, and was run end to end against a
58
+ real model and a real stdio MCP server with mcp-use 1.7.1, langchain-core
59
+ 1.6.12, langchain-openai 1.7.2 and langgraph 1.2.16.
60
+
61
+ ## Usage
62
+
63
+ ```python
64
+ from fi_instrumentation import register
65
+ from fi_instrumentation.fi_types import ProjectType
66
+ from mcp_use import MCPAgent, MCPClient
67
+ from traceai_mcp_use import FutureAGICallback
68
+
69
+ trace_provider = register(project_type=ProjectType.OBSERVE, project_name="mcp-use-agent")
70
+
71
+ client = MCPClient.from_dict({"mcpServers": {"calculator": {"command": "python", "args": ["server.py"]}}})
72
+ agent = MCPAgent(
73
+ llm=llm, # any LangChain chat model
74
+ client=client,
75
+ callbacks=[FutureAGICallback(tracer_provider=trace_provider)],
76
+ )
77
+ result = await agent.run("What is 2 + 3?")
78
+ ```
79
+
80
+ Pass the provider `register()` returns: `register()` does not set the
81
+ global provider unless you ask it to, and without `tracer_provider` the
82
+ callback uses the global one. One callback can serve many agents and
83
+ concurrent runs. See [`examples/agent_with_tools.py`](examples/agent_with_tools.py).
84
+
85
+ `MCPAgent.run()`, `stream()` and `stream_events()` are all traced. The
86
+ Python `MCPAgent` is async only.
87
+
88
+ ## Spans
89
+
90
+ | Span | Name | `gen_ai.span.kind` | One per |
91
+ |---|---|---|---|
92
+ | Agent | `mcp_use.agent` | `AGENT` | `run()`, `stream()` or `stream_events()` call |
93
+ | LLM | `chat <model>` (`text_completion <model>` for completion models; `chat` when no model name is reported) | `LLM` | model call |
94
+ | Tool | `execute_tool <tool>` | `TOOL` | tool call |
95
+
96
+ The LLM and tool spans are children of the agent span. In the LangChain
97
+ run tree, LangGraph's `model` and `tools` nodes sit between them; graph
98
+ nodes and middleware are not spans, so a tool span is a sibling of the LLM
99
+ spans, not a child of the LLM call that requested it. The agent span is a
100
+ child of the span that is current when the run starts, or a root span.
101
+
102
+ A streamed completion is one LLM span, ended when the stream ends. Each
103
+ token LangChain reports is a `mcp_use.llm.chunk` event carrying only its
104
+ index (`mcp_use.llm.chunk.index`, never the text), for the first 128
105
+ tokens; `mcp_use.llm.chunk_count` holds the exact number.
106
+
107
+ The spans are not made current while their run executes, as in
108
+ `traceAI-langchain`: a span another instrumentation starts inside a tool
109
+ call (for example an HTTP client span) is not a child of the tool span.
110
+
111
+ ## Span attributes
112
+
113
+ | Attribute | Span | Value |
114
+ |---|---|---|
115
+ | `gen_ai.operation.name` | all | `invoke_agent`, `chat`, `text_completion` or `execute_tool` |
116
+ | `mcp_use.agent.llm_call_count`, `mcp_use.agent.tool_call_count`, `mcp_use.agent.tool_error_count` | agent | Model calls, tool calls and failed tool calls in the run |
117
+ | `gen_ai.request.model` | LLM | From LangChain's `ls_model_name`, else the invocation `model` / `model_name` / `model_id` |
118
+ | `gen_ai.provider.name`, `gen_ai.request.temperature`, `gen_ai.request.max_tokens` | LLM | From LangChain's `ls_provider`, `ls_temperature`, `ls_max_tokens` |
119
+ | `gen_ai.response.model`, `gen_ai.response.finish_reasons` | LLM | From the response metadata |
120
+ | `gen_ai.usage.input_tokens`, `gen_ai.usage.output_tokens`, `gen_ai.usage.total_tokens`, `gen_ai.usage.cache_read_tokens` | LLM | From the message's `usage_metadata` (or the provider's `token_usage`) |
121
+ | `mcp_use.llm.input_message_count` | LLM | Messages sent to the model |
122
+ | `mcp_use.llm.tool_call_count` | LLM | Tool calls the model asked for |
123
+ | `mcp_use.llm.chunk_count` | LLM | Streamed tokens, when streamed |
124
+ | `gen_ai.tool.name`, `gen_ai.tool.call.id` | tool | The tool and the model's tool-call id |
125
+ | `mcp_use.tool.error_type` | tool | Error type, when the tool's failure came back as its result |
126
+ | `mcp_use.cancelled`, `mcp_use.incomplete` | any | See [Errors and cancellation](#errors-and-cancellation) |
127
+
128
+ A value the callback is not given is omitted, never written as 0. Content
129
+ is recorded too, unless a `TraceConfig` hide flag turns it off (see
130
+ [Privacy](#privacy)):
131
+
132
+ | Attribute | Span | Value |
133
+ |---|---|---|
134
+ | `input.value` | agent | The last user message of the run's input |
135
+ | `output.value` | agent | The final answer (the last assistant message without tool calls) |
136
+ | `gen_ai.input.messages.<i>.message.role` / `.content` / `.tool_calls.<j>.tool_call.*` | LLM | The 32 most recent input messages |
137
+ | `gen_ai.output.messages.<i>.message.*` | LLM | The model's reply, with up to 16 tool calls (name, id, arguments) |
138
+ | `input.value`, `output.value` | LLM | The last input message's text and the reply's text |
139
+ | `gen_ai.tool.call.arguments`, `input.value` | tool | The arguments, as JSON |
140
+ | `gen_ai.tool.call.result`, `output.value` | tool | The tool's result text |
141
+
142
+ Spans come from an `fi_instrumentation.FITracer`, so the attributes of
143
+ `using_session`, `using_user`, `using_metadata`, `using_tags` and
144
+ `using_attributes` (`session.id`, `user.id`, `metadata`, `tag.tags`) are
145
+ set on every span of a run started inside them:
146
+
147
+ ```python
148
+ from fi_instrumentation import using_metadata, using_session, using_tags, using_user
149
+
150
+ with using_session("session-1"), using_user("user-1"), using_metadata({"tenant": "t1"}), using_tags(["beta"]):
151
+ await agent.run("What is 2 + 3?")
152
+ ```
153
+
154
+ The Python `MCPAgent` has no `set_metadata` or `set_tags` (the TypeScript
155
+ agent does); use these context managers instead.
156
+
157
+ ## Privacy
158
+
159
+ Content follows `TraceConfig`, like every traceAI instrumentor: prompts,
160
+ messages, model output, tool arguments, tool results and error messages are
161
+ recorded by default, and the standard hide flags turn them off. There is no
162
+ separate switch in this package.
163
+
164
+ ```bash
165
+ export FI_HIDE_INPUTS=true # prompts, input messages, tool arguments
166
+ export FI_HIDE_OUTPUTS=true # model output, output messages, tool results
167
+ ```
168
+
169
+ or `FutureAGICallback(tracer_provider=..., config=TraceConfig(hide_inputs=True, hide_outputs=True))`.
170
+ With both flags on, spans carry names, kinds, the model and provider,
171
+ request parameters, usage, finish reasons, counts, the tool-call id,
172
+ statuses and exception types; `input.value` and `output.value` are
173
+ `__REDACTED__`.
174
+
175
+ Never recorded, whatever the flags:
176
+
177
+ - stack traces. The `exception` event has `exception.type` and, when
178
+ content is on, `exception.message`; never `exception.stacktrace`. mcp-use
179
+ sends a failed tool call back to the model as `format_error()`'s dict,
180
+ whose `stack` is a full traceback; in that tool message the trace is
181
+ replaced with `[stack trace removed]` and the rest of the JSON is kept.
182
+ Any other text loses everything from `Traceback (most recent call last):`
183
+ on;
184
+ - absolute local paths (`/Users/...`, `/home/...`, `/tmp/...`, `C:\...`
185
+ and similar), replaced with `[path]`;
186
+ - this machine's user name and host name, replaced with `[user]` and
187
+ `[host]` (names of 4 characters or more that are not generic, such as
188
+ `root` or `app`).
189
+
190
+ ```python
191
+ FutureAGICallback(
192
+ tracer_provider=trace_provider,
193
+ config=TraceConfig(hide_inputs=True), # optional; default reads FI_HIDE_* from the environment
194
+ redact=["my-mcp-server-token"], # extra secret strings to remove
195
+ )
196
+ ```
197
+
198
+ `redact` must be an iterable of strings; anything else raises `TypeError`.
199
+
200
+ Secrets are removed from every text the callback writes, including names
201
+ and ids, with content on or hidden. They are replaced by `[redacted]`:
202
+
203
+ - the strings passed as `redact=`;
204
+ - the value (8 characters or longer) of every environment variable whose
205
+ name contains `KEY`, `SECRET`, `TOKEN`, `PASSWORD`, `PASSWD`,
206
+ `CREDENTIAL` or `AUTH`, read each time a text is written. That covers
207
+ provider keys such as `OPENAI_API_KEY` and `FI_API_KEY`/`FI_SECRET_KEY`;
208
+ - `Bearer`/`Basic` credentials, `sk-`/`pk-`/`rk-` style keys and
209
+ `api_key=`, `token:`, `password=` or `Authorization:` assignments.
210
+
211
+ The callback never reads LangChain's serialized model payload (which can
212
+ include the model's repr, and so its key) or the invocation parameters
213
+ other than the model name.
214
+
215
+ `TraceConfig` settings apply. Pass `config=TraceConfig(...)` or set the
216
+ environment variables before creating the callback:
217
+
218
+ | Setting | Effect |
219
+ |---|---|
220
+ | `hide_inputs` / `FI_HIDE_INPUTS=true` | `input.value` is `__REDACTED__`; input messages and tool-call arguments (on tool spans and in model replies) are dropped. Every other text of the run has each input it has seen replaced with `__REDACTED__`: the user messages, the messages sent to the model, and each string value of each tool call's arguments. Error messages are dropped: the status and the `exception` event carry the exception type only. |
221
+ | `hide_outputs` / `FI_HIDE_OUTPUTS=true` | `output.value` is `__REDACTED__`; output messages and tool results are dropped. Model replies and tool results sent back to the model as input messages (later in the same run, or from earlier turns; see below) keep their role but lose their content and tool-call arguments, and an LLM span whose last input message is one of them has `input.value` `__REDACTED__`. Every other text of the run has each output it has seen (model replies, their tool-call arguments, tool results, earlier turns' included) replaced with `__REDACTED__`. Error messages are dropped: the status and the `exception` event carry the exception type only. |
222
+ | `hide_input_messages`, `hide_output_messages`, `hide_input_text`, `hide_output_text` (`FI_HIDE_INPUT_MESSAGES`, ...) | Applied by `FITracer` to the `gen_ai.input.messages.*` / `gen_ai.output.messages.*` attributes, as for every traceAI instrumentor. |
223
+ | `pii_redaction` / `FI_PII_REDACTION=true` | Emails, phone numbers, SSNs, card numbers, IPv4 addresses and `sk-`/`pk-` keys become tokens such as `<EMAIL_ADDRESS>` in every recorded text: attributes, the error status and the `exception` event. It runs after secrets (and hidden inputs/outputs) are removed and before the size caps. |
224
+
225
+ `config` must be a `fi_instrumentation.TraceConfig`; anything else raises
226
+ `TypeError`. Other `TraceConfig` fields act only through `FITracer`'s
227
+ attribute masking.
228
+
229
+ mcp-use's own console logging is not part of the trace and is not changed
230
+ by these flags: at its default level it prints query previews and tool
231
+ results (INFO), and a failed tool call's formatted error with its stack
232
+ trace (ERROR, `mcp_use/errors/error_formatting.py`). `Logger.set_debug(0)`
233
+ (`from mcp_use import Logger`) hides the INFO lines;
234
+ `logging.getLogger("mcp_use").setLevel(logging.CRITICAL)` also hides the
235
+ ERROR line.
236
+
237
+ Multi-turn conversations: `MCPAgent` keeps memory by default
238
+ (`memory_enabled=True`) and sends the earlier turns, model replies and tool
239
+ results included, back to the model as input on the next `run()`,
240
+ `stream()` or `stream_events()` call (`mcp_use/agents/mcpagent.py:76`,
241
+ `:724-732`, `:879-880`, `:981-983`); `external_history` does the same.
242
+ Each call is its own agent span. Under `hide_outputs`, every assistant- or
243
+ tool-role message (any role other than user and system) among a run's
244
+ input messages counts as an output: its content and tool-call arguments
245
+ are dropped from the input messages and removed from every other text of
246
+ that run, error text included. The callback reads them when the run
247
+ starts, before it writes any of the run's spans. This is
248
+ tested over three turns on one agent through `run()`, `stream()`,
249
+ `stream_events()` and `external_history`, and on the export. User messages
250
+ of earlier turns are inputs: they are recorded unless `hide_inputs` is on.
251
+ The texts a run remembers include its replayed history, so in a long
252
+ conversation the per-run limit below is reached sooner.
253
+
254
+ The hide flags match each text verbatim, and also in its Python-repr and
255
+ JSON-quoted forms; a copy that was escaped differently, cut or reworded
256
+ stays. Texts shorter than 3 characters are only matched in quoted form,
257
+ and non-string argument values (numbers, booleans) are not matched. With a
258
+ hide flag on, the callback remembers at most 2048 text forms or about 4
259
+ million characters per run; past that it fails closed and records no
260
+ content for the rest of that run (no messages, arguments, results or
261
+ error messages).
262
+
263
+ ## Errors and cancellation
264
+
265
+ A failed tool call is a tool span with status ERROR and one `exception`
266
+ event. mcp-use 1.7.1 does not raise for a failed MCP call: it returns a
267
+ formatted error to the model as the tool's result
268
+ (`mcp_use/agents/adapters/langchain_adapter.py:181-206`). The callback
269
+ recognises that result (an object with mcp-use's `error`, `details`,
270
+ `stack` and `code` keys and a `tool` key equal to the tool's name; a tool's
271
+ own result with those keys but no matching `tool` is a result), takes the
272
+ error type from it
273
+ (`mcp_use.tool.error_type`) and counts it in
274
+ `mcp_use.agent.tool_error_count`. A tool call LangChain reports as failed
275
+ (for example arguments that fail validation) is recorded the same way,
276
+ with the exception's type. Either way the model sees the error and the run
277
+ usually goes on, so the agent span ends OK when the run returns an answer.
278
+
279
+ When the run itself fails (for example the model call raises), the LLM
280
+ span and the agent span end with status ERROR and an `exception` event,
281
+ and the exception reaches your code unchanged.
282
+
283
+ By default the status description is `<type>: <message>` and the event
284
+ carries `exception.type` and `exception.message` (cut to 1 KB, cleaned as
285
+ described in [Privacy](#privacy): secrets, local paths, user and host name
286
+ removed). With `hide_inputs` or `hide_outputs` both carry the exception type
287
+ only. No stack trace is recorded.
288
+
289
+ Cancelling the task that runs the agent (`asyncio.CancelledError`) or
290
+ leaving `stream()` before it ends (`GeneratorExit`) ends the open spans
291
+ with status ERROR, description `cancelled` and `mcp_use.cancelled` =
292
+ `true`, without an exception event. A span whose own end never came
293
+ because its parent ended first is ended with `mcp_use.incomplete` =
294
+ `true` and status ERROR, `ended without a result`.
295
+
296
+ If the callback itself fails (starting a span, reading a payload, setting
297
+ an attribute), it logs at debug level and the agent goes on unchanged.
298
+ Inside `fi_instrumentation.suppress_tracing()` it records nothing.
299
+
300
+ ## Turning it off
301
+
302
+ Leave the callback out. `callbacks=[]` is the Python switch for no
303
+ callbacks at all: mcp-use's `ObservabilityManager` returns that empty list
304
+ as is and does not add its default Langfuse handler
305
+ (`mcp_use/agents/observability/callbacks_manager.py:72-74`). With
306
+ `callbacks=None`, mcp-use uses its Langfuse handler when Langfuse is
307
+ configured. There is no `observe=False` on the Python `MCPAgent` (that is
308
+ the TypeScript agent).
309
+
310
+ ## Other tracing in the same process
311
+
312
+ - **traceAI-mcp (`traceai_mcp`)** works at the MCP transport. Version
313
+ 0.1.2 records no spans of its own; it carries trace context in the
314
+ `_meta` of MCP requests. With both packages installed you get the same
315
+ spans as with this one alone: one agent span and one tool span per tool
316
+ call, none duplicated (tested). Because this package's spans are not made
317
+ current, the context `traceai_mcp` sends is whichever span is current in
318
+ your code. mcp-use binds `stdio_client` when it is imported
319
+ (`mcp_use/client/task_managers/stdio.py:12`), so run
320
+ `MCPInstrumentor().instrument()` before importing `mcp_use` for
321
+ `traceai_mcp`'s stdio wrapper to apply.
322
+ - **traceAI-langchain** adds its own tracer to every LangChain callback
323
+ manager, so an `MCPAgent` run would also produce a full LangChain tree
324
+ (graph, nodes, model and tool runs). Use one of the two for an agent.
325
+ - **mcp-use's own observability**: the Langfuse handler is used only when
326
+ `callbacks` is `None` (see above). Laminar is set up when `mcp_use` is
327
+ imported if `LAMINAR_PROJECT_API_KEY` is set and `lmnr` is installed,
328
+ whatever `callbacks` holds (`mcp_use/agents/observability/__init__.py:6`,
329
+ `laminar.py:19-37`); `MCP_USE_LAMINAR=false` turns it off.
330
+ - **mcp-use's usage telemetry** sends anonymized product events to PostHog
331
+ and Scarf, not spans; this package does not change it.
332
+ `MCP_USE_ANONYMIZED_TELEMETRY=false` turns it off
333
+ (`mcp_use/telemetry/telemetry.py:139`).
334
+
335
+ ## Limits
336
+
337
+ - Recorded text is cut on a UTF-8 character boundary after it is cleaned
338
+ (secrets, then hidden inputs/outputs, then PII): 4 KB for each content
339
+ attribute, 256 bytes for names and ids (model, provider, tool name,
340
+ tool-call id, exception type) and 1 KB for the error message in the
341
+ status and the `exception` event. With `pii_redaction`, `FITracer` runs its own PII pass on every
342
+ attribute after the cut; a token such as `<PHONE_NUMBER>` can make an
343
+ attribute a few bytes longer than its cap. The status and events are not
344
+ passed through it. At most 32 messages and 16 tool calls per message are
345
+ recorded per LLM span, and 128 chunk events per streamed LLM span.
346
+ - One callback tracks at most 10,000 open runs. Past that, the oldest
347
+ agent run is ended with `mcp_use.incomplete` and forgotten. Ending a run
348
+ looks only at the open runs of its own agent run, not at other agents'.
349
+ - With `use_server_manager=True`, mcp-use restarts the graph run when the
350
+ tool set changes during a run, up to 3 times
351
+ (`mcp_use/agents/mcpagent.py:741-876`); each restart is a new agent span.
352
+ The server manager's own tools return their failures as mcp-use errors
353
+ without a `tool` key (`mcp_use/agents/managers/tools/connect_server.py:73`,
354
+ `disconnect_server.py:39`, `list_servers_tool.py:47`), so those tool spans
355
+ end OK with the error as their result. This path is not covered by the
356
+ tests.
357
+ - With `output_schema`, mcp-use formats the answer after the graph run with
358
+ a model call that gets no callbacks (`mcp_use/agents/mcpagent.py:595`),
359
+ so that call has no span.
360
+ - A remote agent (`MCPAgent(agent_id=...)`) never reads `callbacks`
361
+ (`mcp_use/agents/mcpagent.py:117-120`) and is not traced.
362
+ - The Python `MCPAgent` has no `flush()`. The provider `register()`
363
+ returns exports what is left when the process exits; in a long-running
364
+ process call `trace_provider.force_flush()` when you need the spans now.
365
+ - The callback runs on the agent's event loop, not in a thread. With
366
+ `register(batch=False)`, each span is exported synchronously on that loop
367
+ as it ends.
368
+ - Passed to a LangChain runnable other than `MCPAgent`, the callback still
369
+ names the outermost run `mcp_use.agent`.
370
+ - Tested with mcp-use 1.7.1 only (the latest release on 2026-10-16).
371
+
372
+ ## Tests
373
+
374
+ From the repository root:
375
+
376
+ ```bash
377
+ env -u PYTHONPATH PYTHONPATH="python/frameworks/mcp-use:python:python/tests" \
378
+ uv run --no-project --python 3.11 \
379
+ --with pytest --with pytest-asyncio --with opentelemetry-api --with opentelemetry-sdk \
380
+ --with opentelemetry-instrumentation --with opentelemetry-exporter-otlp-proto-http \
381
+ --with wrapt --with requests --with jsonschema --with protobuf --with opentelemetry-proto \
382
+ --with 'mcp-use==1.7.1' \
383
+ pytest python/frameworks/mcp-use/tests -q -p no:cacheprovider --noconftest -o addopts= -rfEs
384
+ ```
385
+
386
+ Nothing calls a real model, MCP server or Future AGI. The LLM is a
387
+ scripted LangChain chat model. The MCP server is the FastMCP server in
388
+ `tests/_mcp_server.py`, on a 127.0.0.1 port for most tests and over stdio
389
+ for the contract test; the example test uses the example's own stdio
390
+ calculator server. Those send spans through
391
+ `fi_instrumentation.register()` to the shared `harness.Receiver` on
392
+ 127.0.0.1 and check the `/tracer/v1/traces` path, both auth headers,
393
+ `project_type=observe`, that content is exported by default and not with
394
+ `FI_HIDE_INPUTS`/`FI_HIDE_OUTPUTS`, and that no key, stack trace or local
395
+ path is exported either way.
396
+ The example test runs `examples/agent_with_tools.py` as its own process,
397
+ once alone and once with `traceai_mcp` instrumented. The suite turns off
398
+ mcp-use's usage telemetry.
399
+