Agent Native

connect-tools-and-mcp

This skill teaches how to connect tools and MCP servers to CrewAI agents on version 1.15.x, covering custom Python tools, prebuilt crewai_tools, and MCP…

by CrewAIOfficial publisherNot reviewed

Identity proven by GitHub's verified organisation badge (crewAIInc)

Agents
Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode, claude.ai and API
License
MIT (permissive)
Pin
bebb6e8pinned Oct 9, 2026
Context cost
about 6,300 tokenswhen the skill loads
GitHub stars
44on crewaiinc/skills
Scripts
None found

What it does

“Giving crewAI agents tools and MCP servers on crewai 1.15.x: custom BaseTool with args_schema and @tool, which crewai_tools names really exist, tool caching, max_usage_count, ToolFailure, Agent(mcps=[...]) string and MCPServerStdio/HTTP/SSE forms, rewritten MCP tool names, MCPServerAdapter, timeouts, and what works on a hosted deployment. Use when writing or debugging a tool or MCP connection, when you see 'cannot import name BaseTool from crewai_tools', 'You are missing the mcp package', 'MCPConnectionError', 'Operation timed out after 30 seconds', 'reached its usage limit', 'Invalid MCP reference', 'Anthropic function name ... must start with a letter', an agent that never calls an MCP tool, mcps=[...] that yields no tools (for example an https:// string under akickoff or on CrewAI AMP), or a stdio MCP server that works locally but not after deploy.”
The author's description, from SKILL.md

Generated summary from the skill's own text

This skill teaches how to connect tools and MCP servers to CrewAI agents on version 1.15.x, covering custom Python tools, prebuilt crewai_tools, and MCP server integrations. It provides import corrections, configuration patterns, tool naming rules, timeout behavior, and deployment-specific guidance to avoid common failures like silent tool drops and connection errors.

When to use it

  • Writing or debugging custom tools with BaseTool or @tool decorators
  • Connecting MCP servers via MCPServerStdio, MCPServerHTTP, or MCPServerSSE
  • Troubleshooting ImportError messages or missing tool names
  • Deploying crews to CrewAI AMP with tool or MCP integration issues

When not to

  • Using crewai versions earlier than 1.15.x with different APIs
  • Building tools for frameworks other than CrewAI
  • Integrating tools that are not custom Python, crewai_tools, or MCP-based

Install

These commands install from the publisher's repository and need no account.

skills CLI

npx skills add crewaiinc/skills --skill connect-tools-and-mcp

Installs from the repository's default branch, which may have changed since the commit we checked.

GitHub CLI 2.90 or later

gh skill install crewaiinc/skills connect-tools-and-mcp@bebb6e8fc10bb8d07961969fb7bfd7d6091b421a

Installs the commit we checked (bebb6e8).

Copy by hand

Download the folder at bebb6e8 and put it in .claude/skills/connect-tools-and-mcp for Claude Code and in .agents/skills/connect-tools-and-mcp for Codex, Cursor, Gemini CLI, GitHub Copilot and OpenCode.

Skill folder on GitHub at bebb6e8

Copy for your agent

A prompt for any coding agent. It installs this skill from GitHub at the pinned commit, copies the files as they are, and runs nothing unless you ask.

Prompt

Install the agent skill "connect-tools-and-mcp" by CrewAI.

Source, pinned to commit bebb6e8fc10bb8d07961969fb7bfd7d6091b421a:
https://github.com/crewaiinc/skills/tree/bebb6e8fc10bb8d07961969fb7bfd7d6091b421a/skills/connect-tools-and-mcp

1. Download that folder at exactly this commit. Do not use the default branch.
2. Copy every file unchanged, keeping the folder structure, into .claude/skills/connect-tools-and-mcp/ for Claude Code and .agents/skills/connect-tools-and-mcp/ for Codex, Cursor, Gemini CLI, GitHub Copilot and OpenCode. Create the folders if they do not exist.
3. Do not edit the files, and do not run any script in the folder unless I ask you to.
4. When you are done, list the files you copied and where you put them.

Listing and checks: https://www.agentnative.dev/skills/crewai/connect-tools-and-mcp

SKILL.md at the pin

---
name: connect-tools-and-mcp
description: "Giving crewAI agents tools and MCP servers on crewai 1.15.x: custom BaseTool with args_schema and @tool, which crewai_tools names really exist, tool caching, max_usage_count, ToolFailure, Agent(mcps=[...]) string and MCPServerStdio/HTTP/SSE forms, rewritten MCP tool names, MCPServerAdapter, timeouts, and what works on a hosted deployment. Use when writing or debugging a tool or MCP connection, when you see 'cannot import name BaseTool from crewai_tools', 'You are missing the mcp package', 'MCPConnectionError', 'Operation timed out after 30 seconds', 'reached its usage limit', 'Invalid MCP reference', 'Anthropic function name ... must start with a letter', an agent that never calls an MCP tool, mcps=[...] that yields no tools (for example an https:// string under akickoff or on CrewAI AMP), or a stdio MCP server that works locally but not after deploy."
---

# Connect Tools and MCP Servers to CrewAI Agents

Give an agent working tools - custom Python, prebuilt `crewai_tools`, or MCP servers - without the 0.x-era mistakes that fail silently.

Verified against crewai 1.15.22 and 1.15.23 on 2026-10-01.
Live-tested on CrewAI AMP and real LLMs on 2026-10-01.
Run `crewai version` first; if the major/minor differs from 1.15, re-verify version-sensitive rows with the `ask-docs` skill before trusting them.

---

## 1. Pick the mechanism

| You need | Use | Section |
|---|---|---|
| A quick Python function as a tool | `@tool` from `crewai.tools` | 3 |
| A tool with config, state, or a documented input schema | `BaseTool` subclass with `args_schema` | 3 |
| A prebuilt search/scrape/file tool | `crewai_tools` - but import it once to prove the name exists | 5 |
| A remote MCP server | `Agent(mcps=[MCPServerHTTP(...)])` - not an `https://` string if the crew is deployed or uses `akickoff()` | 6, 8 |
| An MCP server that ships with your code | `Agent(mcps=[MCPServerStdio(...)])` - also works on CrewAI AMP when the server is in the package | 6, 10 |
| An integration connected in your CrewAI AMP account | `mcps=["<slug>"]` or `"<slug>#<tool>"` | 6 |
| Bare MCP tool names or a manual start/stop lifecycle | `MCPServerAdapter` (needs `crewai-tools[mcp]`) | 9 |

`mcp` is already a core dependency of crewai 1.15.x - native `mcps=` needs no extra install.

---

## 2. Imports

| You probably wrote | Current form | What happens with the old form |
|---|---|---|
| `from crewai_tools import BaseTool` | `from crewai.tools import BaseTool` | `ImportError: cannot import name 'BaseTool' from 'crewai_tools'` |
| `from crewai_tools import tool` | `from crewai.tools import tool` | `ImportError: cannot import name 'tool' from 'crewai_tools'` |
| `from crewai_tools import CodeInterpreterTool` | a sandbox tool: `E2BPythonTool`, `DaytonaPythonTool` | `ImportError` - removed |
| `from crewai_tools import ZapierActionsAdapter` | `from crewai_tools.adapters.zapier_adapter import ZapierActionsAdapter` (or top-level `ZapierActionTools`) | `ImportError` at top level |
| `from crewai.mcp import ...` for the adapter | `from crewai_tools import MCPServerAdapter` | adapter lives in crewai_tools; native configs live in `crewai.mcp` |

```python
from crewai.tools import BaseTool, tool, ToolFailure, ToolExecutionFailedError
from crewai.mcp import (
    MCPServerStdio, MCPServerHTTP, MCPServerSSE,
    create_static_tool_filter, create_dynamic_tool_filter,
)
from crewai.mcp.exceptions import MCPConnectionError
```

---

## 3. Custom tools

```python
from pydantic import BaseModel, Field
from crewai.tools import BaseTool, tool

class PriceInput(BaseModel):
    sku: str = Field(..., description="Product SKU, e.g. 'A-100'")
    currency: str = Field("EUR", description="ISO currency code")

class PriceTool(BaseTool):
    name: str = "price_lookup"
    description: str = "Return the unit price for a SKU. Use before quoting a price."
    args_schema: type[BaseModel] = PriceInput

    def _run(self, sku: str, currency: str = "EUR") -> str:
        return f"{sku} costs 9.99 {currency}"

@tool("Unit Converter")
def convert(km: float) -> str:
    """Convert kilometres to miles."""
    return f"{km * 0.621371:.1f} mi"

print(PriceTool().run(sku="A-100"))   # args are validated against PriceInput first
print(convert.run(km=10))
```

Rules that prevent real failures:

| Rule | Failure it prevents |
|---|---|
| Always define `_run`, even for an async tool | `_run` is abstract: `TypeError: Can't instantiate abstract class ... without an implementation for abstract method '_run'` |
| Put async code in `async def _run(...)` | Agents execute tools through `_run` in both `kickoff()` and `akickoff()`; an `_arun` override is only used when you call `tool.arun()` yourself |
| Give `@tool` functions a docstring | `ValueError: Function must have a docstring` at decoration time |
| Write the description for the LLM: what it does and when to use it | The agent picks tools by description; MCP tools in particular have opaque names (section 7) |
| Expect the displayed name to be sanitized | `@tool("Unit Converter")` is offered to the LLM as `unit_converter` (seen in a live run with claude-haiku-4-5) |
| Return a `ToolFailure` instead of an error string | An error string looks like data; a `ToolFailure` is recorded in `TaskOutput.tool_failures` and can abort the run (below) |

Per-tool options (all on `BaseTool` and accepted by `@tool(...)` where noted):

| Field | Effect (verified) |
|---|---|
| `max_usage_count=N` (also `@tool("x", max_usage_count=N)`) | After N runs **within one task** the agent gets `Tool '...' has reached its usage limit of N times and cannot be used anymore.` The next task or kickoff gets a fresh allowance, so enforce "once per run" side effects yourself. `0` or negative is a ValidationError |
| `result_as_answer=True` (also on `@tool`) | The tool's return value becomes the task output; no further LLM call |
| `cache_function=lambda args, result: bool` | Decides per call whether the result may be cached (section 4) |
| `tool_failure_policy="ignore" \| "warn" \| "raise"` | Also on Agent, Task and Crew; default `warn`. `raise` aborts with `ToolExecutionFailedError` |

Failures: a tool that raises does not fail the crew. The exception text is fed back to the agent and recorded as a tool failure, and the crew finishes "successfully". When downstream code must know, return `ToolFailure(...)` and set `tool_failure_policy="raise"`, or inspect `result.tasks_output[i].tool_failures`. Full examples: [references/custom-tools.md](references/custom-tools.md).

`Task(tools=[...])` replaces the agent's tools for that task; it does not add to them.

---

## 4. Tool result caching

| Setting | Default in 1.15.22-1.15.23 | Effect |
|---|---|---|
| `Crew(cache=...)` | `False` | Opt-in. With `True`, a repeat call with identical arguments returns the first result without running the tool |
| `Agent(cache=...)` | `True` | Only *permits* participation; `False` opts that agent out even when the crew caches |
| `BaseTool(cache_function=...)` | always cache | Return `False` to keep a result out of the cache |

Through crewai 1.15.2 the crew cached tool results by default; from 1.15.3 it is opt-in. If you relied on the old behaviour, set `Crew(cache=True)` explicitly. Never enable it for live-data or state-changing tools unless their `cache_function` returns `False`.

A separate guard is not caching: if the agent repeats the exact same call twice in a row, crewai refuses the second call ("I tried reusing the same input...") whether or not caching is on.

---

## 5. crewai_tools: names that exist

crewai-tools 1.15.22-1.15.23 exports 118 names. Import a tool before you write code around it. Models routinely invent these:

| Hallucinated name (ImportError) | Real 1.15.22-1.15.23 name |
|---|---|
| `CodeInterpreterTool` | removed - use `E2BPythonTool` / `DaytonaPythonTool` |
| `BedrockKBRetriever` | `BedrockKBRetrieverTool` |
| `GitHubSearchTool`, `GithubTool` | `GithubSearchTool` |
| `ArxivTool` | `ArxivPaperTool` |
| `ComposioToolSet` | `ComposioTool` |
| `GoogleSearchTool`, `WebSearchTool`, `DuckDuckGoSearchTool` | `SerperDevTool`, `BraveSearchTool`, `TavilySearchTool`, `EXASearchTool` |
| `PDFTextWritingTool`, `PGSearchTool` | none |
| `WikipediaTool`, `PythonREPLTool`, `ShellTool`, `CalculatorTool`, `HumanTool`, `LangChainTool`, `SeleniumTool` | none (`SeleniumScrapingTool` exists) - write a custom tool |

Many tools need a third-party package. When it is missing, constructing the tool asks `You are missing the '<pkg>' package. Would you like to install it? [y/N]` (verified for `EXASearchTool`, `TavilySearchTool`, `FirecrawlSearchTool`, `SeleniumScrapingTool`). With a terminal attached that blocks forever; with stdin closed it raises `click.exceptions.Abort`. Install the extra up front, e.g. `uv add "crewai-tools[exa-py]"`. Prebuilt tools are offered to the LLM under their sanitized display name, not the class name: `DirectoryReadTool` appears as `list_files_in_directory`, `FileReadTool` as `read_a_files_content`. RAG tools (`WebsiteSearchTool`, `PDFSearchTool`, ...) fail at construction without an embedder key. Full list and extras: [references/crewai-tools-names.md](references/crewai-tools-names.md).

---

## 6. Native `Agent(mcps=[...])`

```python
from crewai import Agent
from crewai.mcp import MCPServerHTTP, MCPServerSSE, MCPServerStdio, create_static_tool_filter

agent = Agent(
    role="Inventory analyst",
    goal="Answer stock questions",
    backstory="Careful with numbers.",
    mcps=[
        MCPServerHTTP(url="https://mcp.example.com/mcp",
                      headers={"Authorization": "Bearer <your-key>"}),
        MCPServerSSE(url="https://legacy.example.com/sse"),
        MCPServerStdio(command="uvx", args=["mcp-server-time"],
                       tool_filter=create_static_tool_filter(allowed_tool_names=["get_current_time"])),
        "https://docs.example.com/mcp#search_docs",   # https string, one tool (sync kickoff only - section 8)
        "acme#lookup_stock",                           # AMP-connected integration, one tool
        "acme#list_warehouses",                        # second tool = second ref
    ],
)
```

String refs are validated at construction; nothing connects until a task runs.

| Form | Rule (all verified) |
|---|---|
| `MCPServerStdio(command, args, env, tool_filter, cache_tools_list)` | Spawns a subprocess where the crew runs. The server does **not** inherit your environment: it gets only `HOME, LOGNAME, PATH, SHELL, TERM, USER` plus `env=`, so pass its API keys in `env` explicitly. No `cwd` or timeout field |
| `MCPServerHTTP(url, headers, streamable=True, tool_filter, cache_tools_list)` | Streamable HTTP; `http://` and `https://` both work. Use a hostname, not an IP: `http://127.0.0.1:8765/mcp` makes tool names start with a digit and Anthropic models reject them (`ValueError: Anthropic function name '127_0_0_1_8765_mcp_lookup_stock' must start with a letter or underscore`); `http://localhost:8765/mcp` works |
| `MCPServerSSE(url, headers, tool_filter, cache_tools_list)` | Legacy SSE servers (usually a `/sse` path) |
| `"https://host/path"` string | Must start with `https://`. Optional `#tool` keeps one tool. Resolves to **zero tools, silently,** inside a running event loop - `await crew.akickoff()` and crews deployed on CrewAI AMP (section 8) |
| `"slug"` / `"slug#tool"` string | An integration connected in a CrewAI AMP account; legacy `"crewai-amp:slug"` also works. A slug the account has not connected gives zero tools and the run continues (seen on AMP) |
| `"http://localhost:8000/mcp"` or any string that is neither `https://` nor a slug | `ValidationError: ... Invalid MCP reference: 'http://...'. String references must be an 'https://' URL or a valid slug` when the Agent is built. Use `MCPServerHTTP` for `http://` |
| `"slug#a,b"` | Same `Invalid MCP reference` ValidationError. One ref per tool |
| `"https://...#a,b"` | Accepted, but selects **zero** tools (`#` takes one name). One ref per tool |
| `tool_filter` names | Matched against the **sanitized** tool name: a server tool `getForecast` must be listed as `get_forecast` |

Full examples for each transport, dynamic filters and the AMP form: [references/mcp-connections.md](references/mcp-connections.md).

---

## 7. MCP tool names are rewritten - never hard-code them

Native MCP tools are offered to the LLM as `<server name>_<tool name>`, then sanitized: lowercase, non-alphanumerics become `_`, camelCase is split, max 64 characters.

| Source | Name the LLM sees for tool `lookup_stock` |
|---|---|
| `MCPServerStdio(command="python", args=["servers/inventory.py"])` | `python_servers_inventory_py_lookup_stock` |
| `MCPServerStdio` with absolute paths, e.g. `/opt/app/.venv/bin/python` + `/opt/app/servers/inventory_server.py` | `opt_app_venv_bin_python_opt_app_servers_inventory_serve_a87662e9` - over 64 chars, so it is truncated and hashed and the tool name is gone. `command=sys.executable` does the same, locally and on AMP |
| `MCPServerHTTP(url="http://localhost:8765/mcp")` | `localhost_8765_mcp_lookup_stock` (an IP host would start the name with a digit - section 6) |
| `MCPServerHTTP(url="https://mcp.example.com/mcp")` or the same `https://` string | `mcp_example_com_mcp_lookup_stock` |
| `"acme#lookup_stock"` (AMP) | built from the server URL the account returns, not from `acme` - only the `_lookup_stock` suffix is stable |
| `MCPServerAdapter` | `lookup_stock` (bare) |

Consequences:
- Do not write "call the `lookup_stock` tool" in a task description or backstory. Describe the capability ("check stock levels with the inventory tool"), or derive the exact name at runtime. A native function-calling model usually copes (claude-haiku-4-5 told "call the lookup_stock tool" called `python_mcp_server_py_lookup_stock`), but a model on the text ReAct path fails with `Action 'lookup_stock' don't exist`, and a hashed name leaves only the description to go on.
- Keep stdio `command`/`args` short (`"uvx"`, `"npx"`, a relative script path) so names stay readable and under 64 characters. Absolute paths also put your filesystem layout into every prompt. A scoped npx package already uses about 45 characters: `npx -y @modelcontextprotocol/server-filesystem ./data` hashed `list_allowed_directories` to `..._data_list_4ab656ee`; use `MCPServerAdapter` when bare names matter.
- In code, identify a resolved native MCP tool by `tool.original_tool_name`, not by `tool.name`.

---

## 8. Timeouts, retries and failure behaviour

| Path | Connect | Per tool call | Retries | When the server is down |
|---|---|---|---|---|
| `MCPServerStdio/HTTP/SSE` config and AMP slugs | 30 s | 30 s | 3 attempts on timeout - **the tool runs again each time**. Live run: a 35 s tool ran 3 times (~99 s); the agent got `Error executing tool: Error executing MCP tool slow_report: Operation timed out after 30 seconds` as its observation, a tool failure was recorded and the crew finished. A direct `tool.run()` raises `RuntimeError` instead | kickoff raises `MCPConnectionError` |
| `"https://..."` string | 10 s, 15 s discovery | 60 s | 3 attempts (a 65 s tool ran 3 times, ~198 s) and then returns the error **as a string** | agent runs with **no MCP tools**, no error |
| `MCPServerAdapter` | `connect_timeout=` (default 30) | no crewai timeout (a 35 s call completed) | none | constructor raises `RuntimeError: Failed to initialize MCP Adapter` |

In 1.15.22-1.15.23 the native timeouts are module constants. No field on `MCPServerStdio`, `MCPServerHTTP`, `MCPServerSSE` or `Agent` changes them. So:
- Keep MCP tool calls well under 30 s. Make them idempotent, because a timed-out call is re-sent. For long jobs, return a job id from one tool and poll with another.
- Prefer config objects to `https://` strings in production: they fail loudly at kickoff instead of silently dropping tools.
- `https://` strings discover tools with `asyncio.run()`. Inside a running event loop that fails and the agent gets **zero tools with no error** (only `RuntimeWarning: coroutine 'MCPToolResolver._get_mcp_tool_schemas_async' was never awaited`). Seen live with `await crew.akickoff()` and on a CrewAI AMP deployment - the agent runs without the tools, and its answer may describe tool calls it never made. The same server as `MCPServerHTTP(url=...)` worked in both places. Sync `crew.kickoff()`, including from a sync Flow step, resolves strings fine.
- An MCP result with `isError: true` reaches the agent as text. It is recorded as a `mcp_error` tool failure, and `tool_failure_policy="raise"` turns it into `ToolExecutionFailedError`.

---

## 9. MCPServerAdapter and `@CrewBase` `mcp_server_params`

`MCPServerAdapter` needs the extra: `uv add "crewai-tools[mcp]"` (it installs `mcpadapt`; `mcp` itself is already present). Without the extra, constructing it asks `You are missing the 'mcp' package. Would you like to install it? [y/N]`. Under a terminal that blocks forever; with stdin closed (typical CI) it raises `click.exceptions.Abort`. Install the extra explicitly.

```python
from mcp import StdioServerParameters
from crewai_tools import MCPServerAdapter

params = StdioServerParameters(command="python", args=["mcp_server.py"])
with MCPServerAdapter(params, "lookup_stock", connect_timeout=60) as tools:
    print([t.name for t in tools])   # ['lookup_stock'] - bare names, filtered by name
    # build agents with tools=tools and kick off INSIDE the with-block
```

The server starts in the constructor and stops when the `with` block exits. Tools used after that fail. For a remote server pass a dict and name the transport: `{"url": "https://<host>/mcp", "transport": "streamable-http"}`. Without `"transport"` the adapter speaks SSE, and against a streamable-HTTP `/mcp` endpoint it fails with `Couldn't connect to the MCP server after <connect_timeout> seconds`.

In a `@CrewBase` class, set `mcp_server_params` (and optionally `mcp_connect_timeout`, default 30) and call `self.get_mcp_tools("name", ...)` in an `@agent` method. crewai stops that adapter after every kickoff. Calling `.crew().kickoff()` a second time on the **same instance** made every MCP call return `Event loop is closed`, yet the crew still "succeeded". Build a fresh instance per run: `InventoryCrew().crew().kickoff()`.

---

## 10. Hosted deployments: what works on CrewAI AMP

Measured on a CrewAI AMP deployment (ZIP deploy, crewai 1.15.23, `anthropic/claude-haiku-4-5`), one kickoff per form:

| Form in the deployed crew | Result |
|---|---|
| `MCPServerHTTP(url="https://<public host>/mcp")` | Works - the model called `<host>_mcp_<tool>` |
| `"https://<public host>/mcp#tool"` string | **Zero tools, run SUCCESS** - the model made up a tool call in its answer (section 8) |
| `MCPServerStdio(command="python", args=["-m", "<pkg>.inv_server"])`, server module inside `src/<pkg>/` | Works - a stdio server shipped in the package runs inside the deployment |
| Same with `command=sys.executable` | Works (tool name hashed, section 7) |
| `MCPServerStdio(..., env={"X": os.environ["X"]})` with `X` a deployment env var | Server sees `X`; without `env=` it does not |
| `MCPServerAdapter(StdioServerParameters(...))` with `crewai-tools[mcp]` in `pyproject.toml` | Works, bare tool names |
| `MCPServerStdio` pointing at a file that is not in the deployment (a laptop path) | Run fails with `MCPConnectionError` |
| `MCPServerStdio(command="npx", ...)` | Run fails the same way - the deployment only has what your Python project installs |
| `MCPServerHTTP` at a wrong path (HTTP 404) | Run fails the same way |
| `"<slug>#tool"` not connected in the account | Zero tools, run SUCCESS |

How a failed MCP connection looks from the API: for each `MCPConnectionError` run above, `GET /status/{kickoff_id}` answered **HTTP 500 `Internal Server Error`** (plain text) on every poll, minutes later too, instead of a JSON `FAILED` state. A plain exception in the same crew gave the documented shape: `{"state": "FAILED", "status": "ValueError: ..."}`. Treat a persistent 500 from `/status` as "the run failed", and check the MCP connection first.

Rules:
- A stdio server runs as a child process wherever the crew runs. Ship it inside the package (`src/<pkg>/server.py`, started with `python -m <pkg>.server`) and its Python dependencies in `pyproject.toml`/`uv.lock`; a laptop path or a Node/npx server is not there.
- For remote servers use `MCPServerHTTP(url="https://<host>/mcp")` (or `MCPServerSSE`), never an `https://` string. Point deployments at a stable hostname you control, require auth on it, and do not leave temporary tunnels running.
- Health check: a bare `curl -s -o /dev/null -w "%{http_code}" https://<host>/mcp` returns **406** from a healthy streamable-HTTP MCP endpoint (seen on a public server and locally). **404** means wrong path; `000`/connection refused means it is down.
- Put tokens in `headers` read from environment variables (`os.environ["ACME_MCP_TOKEN"]`), never in the URL string or in code. A stdio server gets deployment env vars only through `env=`.

---

## Common mistakes

| Symptom | Cause | Fix |
|---|---|---|
| `cannot import name 'BaseTool' from 'crewai_tools'` | 0.x import path | `from crewai.tools import BaseTool, tool` |
| `Can't instantiate abstract class ... '_run'` | Only `_arun` defined | Define `_run` (it may be `async def`) |
| `Action 'lookup_stock' don't exist, these are the only available Actions: ...`, or the agent never uses the MCP tool | Prompt names the bare tool (`lookup_stock`) but the LLM sees `python_..._lookup_stock` | Describe the capability; derive names at runtime |
| `ValidationError ... Invalid MCP reference: 'github#a,b'` | `#` takes one tool name | `["github#a", "github#b"]` |
| `mcps=["https://...#a,b"]` gives no tools | Same, but https strings are not validated | One ref per tool |
| `ValidationError ... Invalid MCP reference: 'http://localhost:8000/mcp'` | Strings must be `https://` or a slug | `MCPServerHTTP(url="http://localhost:8000/mcp")` |
| `Anthropic function name '127_0_0_1_...' must start with a letter or underscore` | MCP server URL uses an IP address | Use a hostname (`localhost`) |
| `https://` string MCP gives no tools under `akickoff()` or on AMP; the answer invents tool calls | String refs cannot resolve inside a running event loop | `MCPServerHTTP(url=...)` |
| `GET /status` keeps failing or the run never completes | The run may have hit `MCPConnectionError` (stdio command or file missing, wrong URL) | Run the crew locally to see the error; fix the MCP connection |
| `tool_filter` keeps nothing | Filter used the server's camelCase name | Use the sanitized name (`get_forecast`) |
| Run hangs at "Would you like to install it? [y/N]" | Missing optional package (`crewai-tools[mcp]`, `exa-py`, ...) | Install the extra before running |
| Tool ran 3 times / side effect repeated | Native MCP call exceeded 30 s and was retried | Keep calls short and idempotent |
| Crew "succeeds" but the answer says the tool errored | Tool exceptions are fed to the agent, not raised | Return `ToolFailure`; set `tool_failure_policy="raise"`; check `tool_failures` |
| Stale results after enabling `Crew(cache=True)` | Live-data tool cached | `cache_function=lambda a, r: False` or `Agent(cache=False)` |
| Repeated identical calls now hit the API every time | Crew cache is opt-in in 1.15.x | `Crew(cache=True)` |
| Second kickoff of the same `@CrewBase` instance: `Event loop is closed` | Adapter stopped after the first kickoff | New crew instance per kickoff |
| stdio MCP server says its API key is missing although it is in your `.env` | stdio servers get a minimal environment, not yours | `MCPServerStdio(..., env={"X_API_KEY": os.environ["X_API_KEY"]})` |
| Works locally, fails after deploy | stdio server file or command (`npx`, a laptop path) not in the deployment | Ship the server inside the package, or serve it over HTTP at a stable URL |

---

## Checklist

- [ ] `BaseTool`/`tool` imported from `crewai.tools`; every `crewai_tools` name imported once to prove it exists
- [ ] Custom tools define `_run`, have an `args_schema` (or typed `@tool` signature) and an LLM-oriented description
- [ ] Failures returned as `ToolFailure`; `tool_failure_policy` chosen deliberately
- [ ] `Crew(cache=...)` set explicitly; live-data tools excluded from caching
- [ ] Optional packages and `crewai-tools[mcp]` installed up front, so no interactive prompt can block
- [ ] One `"slug#tool"` ref per tool; `http://` servers via `MCPServerHTTP`, not a string; hostnames, not IP addresses
- [ ] No `https://` string refs in deployed crews or with `akickoff()` - use `MCPServerHTTP`
- [ ] No prompt or code depends on a bare MCP tool name; stdio commands kept short
- [ ] stdio servers get their keys through `env=` (they do not inherit the crew's environment)
- [ ] MCP tool calls finish well under 30 s and are idempotent
- [ ] Deployed crews use HTTP/SSE MCP servers at a stable URL that returns 406 on a bare GET, or stdio servers shipped inside the package
- [ ] A fresh `@CrewBase` instance per kickoff when using `mcp_server_params`

---

## References

- [Custom Tools](references/custom-tools.md) - BaseTool, `@tool`, async, ToolFailure and failure policy, caching, usage limits, with runnable examples
- [MCP Connections](references/mcp-connections.md) - every `mcps=` form, filters, name derivation, timeouts, MCPServerAdapter, `@CrewBase`, deployment
- [crewai_tools Names](references/crewai-tools-names.md) - the full 1.15.22-1.15.23 export list, hallucinated names, extras and construction behaviour
- Public docs: https://docs.crewai.com/en/concepts/tools and https://docs.crewai.com/en/mcp/overview

For related skills:

- **check-crewai-api** - current Agent/Task/Crew API and imports beyond tools
- **design-agent** - choosing how many tools an agent gets and writing role/goal/backstory
- **test-crewai-project** - driving tool calls offline with a stub LLM
- **deploy-to-amp** - getting a project with MCP connections onto CrewAI AMP
- **ask-docs** - query the live docs when the installed version differs

From crewaiinc/skills at bebb6e8, shown as plain text under its license: MIT (permissive). The license text is in LICENSE.

View SKILL.md on GitHub at bebb6e8

Files

4 files, 56 KB in total. Each file has a sha256 in the pin's manifest; the first 12 characters are shown.

Skill folder on GitHub at bebb6e8
All 13 skills from CrewAI

More from CrewAI

  • Invalid--Name

    This skill cannot be described because its name format is invalid and no functional description or instructions are provided in the skill metadata.

    Claude Code, Codex, Cursor +4 · about 0 tokens · pinned 6 days ago

  • minimal-skill

    This skill provides minimal functionality with only required configuration fields.

    Claude Code, Codex, Cursor +4 · about 0 tokens · pinned 6 days ago

  • valid-skill

    This skill provides comprehensive instructions for agents to follow steps effectively with additional context.

    Contains scripts

    Claude Code, Codex, Cursor +3 · about 47 tokens · pinned 6 days ago

  • ask-docs

    This skill queries the official CrewAI documentation to answer framework and platform questions.

    Claude Code, Codex, Cursor +4 · about 3.1K tokens · pinned 6 days ago

  • build-flow

    Provides comprehensive guidance for building CrewAI Flows on version 1.15.x, covering state management with Pydantic models, wiring methods with decorators…

    Claude Code, Codex, Cursor +4 · about 6.7K tokens · pinned 6 days ago

  • call-deployed-crew

    Guides how to call a CrewAI crew or flow deployed on AMP via HTTP by managing bearer tokens, required inputs, polling for results, and writing crews that…

    Claude Code, Codex, Cursor +4 · about 5.5K tokens · pinned 6 days ago

All AI and agents skills
  • anthropic-administrator-automation

    This skill automates Anthropic Admin tasks by connecting to Composio's Anthropic Admin toolkit through Rube MCP.

    by ComposioOfficial publisher

    Agents not labelled yet · about 730 tokens · pinned 6 days ago

  • anthropic_administrator-automation

    This skill automates Anthropic Admin operations including API key management, usage monitoring, workspace administration, and organization settings through…

    by ComposioOfficial publisher

    Agents not labelled yet · about 1K tokens · pinned 6 days ago

  • check-crewai-api

    Loads current CrewAI 1.15.x API surface for agents writing or debugging CrewAI code.

    by CrewAIOfficial publisher

    Claude Code, Codex, Cursor +4 · about 7K tokens · pinned 6 days ago

  • neo4j-agent-memory-skill

    Provides authoritative reference for neo4j-agent-memory, a Python package that adds graph-native memory to AI agents through three distinct layers (short-term…

    by neo4j-contribCommunity

    Claude Code, Codex, Cursor +3 · about 5.5K tokens · pinned 6 days ago

  • -21risk-automation

    This skill automates 21risk operations by connecting to the 21risk toolkit through Composio's Rube MCP server.

    by ComposioOfficial publisher

    Claude Code, Codex, Cursor +3 · about 690 tokens · pinned 6 days ago

  • -2chat-automation

    This skill automates 2chat tasks by integrating with Composio's 2chat toolkit through Rube MCP.

    by ComposioOfficial publisher

    Agents not labelled yet · about 680 tokens · pinned 6 days ago

Open roles at CrewAI

The skills listed here are third-party software, collected from public GitHub repositories as a catalog of recent skills and provided as is. Agent Native does not write or control them and is not responsible for any harm, loss or damage caused by installing or using them. Labels and checks describe what we saw at one commit and are not a guarantee. Read a skill's files before you run it. Terms of use