Message body
### TL;DR
When CrewAI (crewai_tools 0.18.0) connects to the **@softeria/ms-365-mcp-server**
via `MCPServerAdapter(StdioServerParameters)`, the adapter crashes while
building the tool descriptions:
KeyError: ‘#/properties/body/properties/Message/allOf/1/properties/bccRecipients/items’
— i.e. inside `pydantic.json_schema._add_json_refs`.
I already patched the leading-underscore field problem (`__top`, `__filter`, …),
but this second crash still kills the adapter.
Has anyone found a clean workaround other than installing an unreleased
Pydantic build or skipping the mail tools?
---
### Environment
* Python 3.12
* crewai == 0.120.1
* crewai_tools == 0.45.0
* mcpadapt == 0.1.7
* pydantic == 2.4.2
* Node MCP server: `@softeria/ms-365-mcp-server 0.4.3`
---
### Minimal repro script
```python
#!/usr/bin/env python
import os, logging
from dotenv import load_dotenv
import patch_mcp_fields # <- my underscore-alias patch
from crewai import Agent, Task, Crew, Process, LLM
from crewai_tools import MCPServerAdapter
from mcp import StdioServerParameters
from mcpadapt.crewai_adapter import CrewAIAdapter
# --- 1) hot-patch CrewAIAdapter to swallow NameError (works) -------------
# (see bottom of post for code of patch_mcp_fields)
_real_adapt = CrewAIAdapter.adapt
def _patched_adapt(self, func, mcp_tool):
tool = _real_adapt(self, func, mcp_tool)
# wrap _generate_description to survive KeyError -- DOES **NOT** help
orig = tool._generate_description
def safe_desc():
try: orig()
except KeyError: # <- never reached
tool.description = f"{tool.name}({', '.join(tool.args_schema)})"
tool._generate_description = safe_desc
return tool
CrewAIAdapter.adapt = _patched_adapt
# -------------------------------------------------------------------------
load_dotenv()
llm = LLM(model=os.getenv("OPENAI_MODEL_NAME"),
base_url=os.getenv("OPENAI_API_BASE"),
api_key=os.getenv("OPENAI_API_KEY"))
server_params = StdioServerParameters(
command="npx",
args=["-y", "@softeria/ms-365-mcp-server"],
env={"UV_PYTHON": "3.12", **os.environ},
)
with MCPServerAdapter(server_params) as tools: # <-- CRASHES HERE
print("Connected, tools:", [t.name for t in tools])
Full traceback (cut to the interesting part)
…
File ".../mcpadapt/crewai_adapter.py", line 74, in _generate_description
self.args_schema.model_json_schema()
File ".../pydantic/json_schema.py", line 2312, in _add_json_refs
_add_json_refs(v)
File ".../pydantic/json_schema.py", line 2296, in _add_json_refs
defs_ref = self.json_to_defs_refs[json_ref]
KeyError: '#/properties/body/properties/Message/allOf/1/properties/bccRecipients/items'
What I’ve tried
Attempt | Result |
---|---|
Underscore-alias patch (__top → top, alias=“__top”) | ![]() |
Wrapping BaseTool._generate_description / CrewAIAdapter.adapt | ![]() |
Downgrading to Pydantic 1.10.x | ![]() |
Installing pydantic@main (PR #9863) | ![]() |
Filtering out mail tools (`include_tools=[r”^(?!(list-mail | get-mail)).*$”]`) |
Questions for the CrewAI devs / community
- Is there a sanctioned way to tell CrewAI not to callmodel_json_schema() when building tool descriptions?
- Would you accept a PR that wraps the call in a safe-guard until the nextPydantic release?
- Any other work-around people are using in production?
Thanks!
patch_mcp_fields.py
(underscore fix)
import re, inspect
from mcpadapt.utils import modeling as _m
def _scrub(schema):
if isinstance(schema, dict) and "properties" in schema:
schema["properties"] = {
(re.sub(r"^_+", "", k) or "field"): {**v, "alias": k} if k.startswith("_") else v
for k, v in schema["properties"].items()
}
return schema
def _wrap(fn):
def inner(*args, **kw):
if len(args) == 1: # (schema)
return fn(_scrub(args[0]), **kw)
name, schema = args # (name, schema)
return fn(name, _scrub(schema), **kw)
return inner
target = "process_schema" if hasattr(_m, "process_schema") else "create_model_from_json_schema"
setattr(_m, target, _wrap(getattr(_m, target)))
Feel free to cut or expand portions—this should be enough for the maintainers to reproduce and diagnose.