Compare commits

..

2 Commits

Author SHA1 Message Date
Lorenze Jay
c091aa63c6 Merge branch 'main' into fix/oss-9-extra-forbidden-security-context 2026-04-02 09:10:49 -07:00
Iris Clawd
edd79e50ef fix: ignore extra fields in dynamic tool schemas to prevent security_context validation errors
Changes the default Pydantic config in create_model_from_schema() from
extra='forbid' to extra='ignore'. This fixes OSS-9 where the framework
injects security_context metadata into tool call arguments, but MCP tools
and integration tools (created via create_model_from_schema) reject any
extra fields with Pydantic's extra_forbidden error.

Affected tools: all MCP tools (MCPServerAdapter, MCPToolResolver) and
all platform integration tools (CrewAIPlatformActionTool) — these all
use create_model_from_schema() without a custom __config__, so they
inherited the extra='forbid' default.

Regular user-defined tools (subclassing BaseModel) were not affected
because BaseModel defaults to extra='ignore'.

The fix is backward-compatible:
- Required fields are still enforced
- Type validation is still enforced
- Callers can still opt into extra='forbid' via __config__ parameter
- Tools that define security_context in their schema still receive it

Fixes: OSS-9
Related: #4796, #4841
2026-04-01 19:42:13 +00:00
47 changed files with 340 additions and 902 deletions

View File

@@ -5,14 +5,6 @@ icon: wrench
mode: "wide"
---
### شاهد: بناء Agents و Flows في CrewAI باستخدام Coding Agent Skills
قم بتثبيت مهارات وكيل البرمجة الخاصة بنا (Claude Code، Codex، ...) لتشغيل وكلاء البرمجة بسرعة مع CrewAI.
يمكنك تثبيتها باستخدام `npx skills add crewaiinc/skills`
<iframe src="https://www.loom.com/embed/befb9f68b81f42ad8112bfdd95a780af" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen style={{width: "100%", height: "400px"}}></iframe>
## فيديو تعليمي
شاهد هذا الفيديو التعليمي لعرض تفصيلي لعملية التثبيت:

View File

@@ -16,14 +16,6 @@ mode: "wide"
مع أكثر من 100,000 مطور معتمد عبر دوراتنا المجتمعية، يُعد CrewAI المعيار لأتمتة الذكاء الاصطناعي الجاهزة للمؤسسات.
### شاهد: بناء Agents و Flows في CrewAI باستخدام Coding Agent Skills
قم بتثبيت مهارات وكيل البرمجة الخاصة بنا (Claude Code، Codex، ...) لتشغيل وكلاء البرمجة بسرعة مع CrewAI.
يمكنك تثبيتها باستخدام `npx skills add crewaiinc/skills`
<iframe src="https://www.loom.com/embed/befb9f68b81f42ad8112bfdd95a780af" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen style={{width: "100%", height: "400px"}}></iframe>
## بنية CrewAI المعمارية
صُممت بنية CrewAI لتحقيق التوازن بين الاستقلالية والتحكم.

View File

@@ -139,19 +139,7 @@ mode: "wide"
- **الالتزام بمواصفات ترخيص MCP**: إذا كنت تنفذ المصادقة والترخيص، اتبع بدقة [مواصفات ترخيص MCP](https://modelcontextprotocol.io/specification/draft/basic/authorization).
- **تدقيقات أمنية منتظمة**: إذا كان خادم MCP يتعامل مع بيانات حساسة، فكر في إجراء تدقيقات أمنية دورية.
## 5. الإبلاغ عن الثغرات الأمنية
إذا اكتشفت ثغرة أمنية في CrewAI، يرجى الإبلاغ عنها بشكل مسؤول من خلال برنامج الكشف عن الثغرات (VDP) الخاص بنا على Bugcrowd:
**أرسل التقارير إلى:** [crewai-vdp-ess@submit.bugcrowd.com](mailto:crewai-vdp-ess@submit.bugcrowd.com)
<Warning>
**لا تكشف** عن الثغرات عبر issues العامة على GitHub أو pull requests أو وسائل التواصل الاجتماعي. لن تتم مراجعة التقارير المقدمة عبر قنوات غير Bugcrowd.
</Warning>
لمزيد من التفاصيل، راجع [سياسة الأمان](https://github.com/crewAIInc/crewAI/blob/main/.github/security.md) الخاصة بنا.
## 6. قراءة إضافية
## 5. قراءة إضافية
لمزيد من المعلومات التفصيلية حول أمان MCP، راجع التوثيق الرسمي:
- **[أمان نقل MCP](https://modelcontextprotocol.io/docs/concepts/transports#security-considerations)**

View File

@@ -5,14 +5,6 @@ icon: rocket
mode: "wide"
---
### شاهد: بناء Agents و Flows في CrewAI باستخدام Coding Agent Skills
قم بتثبيت مهارات وكيل البرمجة الخاصة بنا (Claude Code، Codex، ...) لتشغيل وكلاء البرمجة بسرعة مع CrewAI.
يمكنك تثبيتها باستخدام `npx skills add crewaiinc/skills`
<iframe src="https://www.loom.com/embed/befb9f68b81f42ad8112bfdd95a780af" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen style={{width: "100%", height: "400px"}}></iframe>
## ابنِ أول وكيل CrewAI
لننشئ طاقماً بسيطاً يساعدنا في `البحث` و`إعداد التقارير` عن `أحدث تطورات الذكاء الاصطناعي` لموضوع أو مجال معين.

View File

@@ -1,22 +0,0 @@
---
title: سياسة الأمان
description: تعرف على كيفية الإبلاغ عن الثغرات الأمنية وممارسات الأمان في CrewAI.
icon: shield
mode: "wide"
---
## الإبلاغ عن الثغرات الأمنية
إذا اكتشفت ثغرة أمنية في CrewAI، يرجى الإبلاغ عنها بشكل مسؤول من خلال برنامج الكشف عن الثغرات (VDP) الخاص بنا على Bugcrowd:
**أرسل التقارير إلى:** [crewai-vdp-ess@submit.bugcrowd.com](mailto:crewai-vdp-ess@submit.bugcrowd.com)
<Warning>
**لا تكشف** عن الثغرات عبر issues العامة على GitHub أو pull requests أو وسائل التواصل الاجتماعي. لن تتم مراجعة التقارير المقدمة عبر قنوات غير Bugcrowd.
</Warning>
لمزيد من التفاصيل، راجع [سياسة الأمان على GitHub](https://github.com/crewAIInc/crewAI/blob/main/.github/security.md).
## موارد الأمان
- **[اعتبارات أمان MCP](/ar/mcp/security)** — أفضل الممارسات لدمج خوادم MCP بأمان مع وكلاء CrewAI، بما في ذلك أمان النقل ومخاطر حقن الأوامر ونصائح تنفيذ الخادم.

View File

@@ -369,12 +369,6 @@
"pages": [
"en/telemetry"
]
},
{
"group": "Security",
"pages": [
"en/security"
]
}
]
},
@@ -845,12 +839,6 @@
"pages": [
"en/telemetry"
]
},
{
"group": "Security",
"pages": [
"en/security"
]
}
]
},
@@ -1320,12 +1308,6 @@
"pages": [
"en/telemetry"
]
},
{
"group": "Security",
"pages": [
"en/security"
]
}
]
},
@@ -1795,12 +1777,6 @@
"pages": [
"en/telemetry"
]
},
{
"group": "Security",
"pages": [
"en/security"
]
}
]
},
@@ -2271,12 +2247,6 @@
"pages": [
"en/telemetry"
]
},
{
"group": "Security",
"pages": [
"en/security"
]
}
]
},
@@ -2745,12 +2715,6 @@
"pages": [
"en/telemetry"
]
},
{
"group": "Security",
"pages": [
"en/security"
]
}
]
},
@@ -3222,12 +3186,6 @@
"pages": [
"en/telemetry"
]
},
{
"group": "Security",
"pages": [
"en/security"
]
}
]
},
@@ -3713,12 +3671,6 @@
"pages": [
"pt-BR/telemetry"
]
},
{
"group": "Segurança",
"pages": [
"pt-BR/security"
]
}
]
},
@@ -4173,12 +4125,6 @@
"pages": [
"pt-BR/telemetry"
]
},
{
"group": "Segurança",
"pages": [
"pt-BR/security"
]
}
]
},
@@ -4633,12 +4579,6 @@
"pages": [
"pt-BR/telemetry"
]
},
{
"group": "Segurança",
"pages": [
"pt-BR/security"
]
}
]
},
@@ -5093,12 +5033,6 @@
"pages": [
"pt-BR/telemetry"
]
},
{
"group": "Segurança",
"pages": [
"pt-BR/security"
]
}
]
},
@@ -5552,12 +5486,6 @@
"pages": [
"pt-BR/telemetry"
]
},
{
"group": "Segurança",
"pages": [
"pt-BR/security"
]
}
]
},
@@ -6011,12 +5939,6 @@
"pages": [
"pt-BR/telemetry"
]
},
{
"group": "Segurança",
"pages": [
"pt-BR/security"
]
}
]
},
@@ -6471,12 +6393,6 @@
"pages": [
"pt-BR/telemetry"
]
},
{
"group": "Segurança",
"pages": [
"pt-BR/security"
]
}
]
},
@@ -6974,12 +6890,6 @@
"pages": [
"ko/telemetry"
]
},
{
"group": "보안",
"pages": [
"ko/security"
]
}
]
},
@@ -7446,12 +7356,6 @@
"pages": [
"ko/telemetry"
]
},
{
"group": "보안",
"pages": [
"ko/security"
]
}
]
},
@@ -7918,12 +7822,6 @@
"pages": [
"ko/telemetry"
]
},
{
"group": "보안",
"pages": [
"ko/security"
]
}
]
},
@@ -8390,12 +8288,6 @@
"pages": [
"ko/telemetry"
]
},
{
"group": "보안",
"pages": [
"ko/security"
]
}
]
},
@@ -8861,12 +8753,6 @@
"pages": [
"ko/telemetry"
]
},
{
"group": "보안",
"pages": [
"ko/security"
]
}
]
},
@@ -9332,12 +9218,6 @@
"pages": [
"ko/telemetry"
]
},
{
"group": "보안",
"pages": [
"ko/security"
]
}
]
},
@@ -9804,12 +9684,6 @@
"pages": [
"ko/telemetry"
]
},
{
"group": "보안",
"pages": [
"ko/security"
]
}
]
},
@@ -10307,12 +10181,6 @@
"pages": [
"ar/telemetry"
]
},
{
"group": "الأمان",
"pages": [
"ar/security"
]
}
]
},
@@ -10779,12 +10647,6 @@
"pages": [
"ar/telemetry"
]
},
{
"group": "الأمان",
"pages": [
"ar/security"
]
}
]
},
@@ -11251,12 +11113,6 @@
"pages": [
"ar/telemetry"
]
},
{
"group": "الأمان",
"pages": [
"ar/security"
]
}
]
},
@@ -11723,12 +11579,6 @@
"pages": [
"ar/telemetry"
]
},
{
"group": "الأمان",
"pages": [
"ar/security"
]
}
]
},
@@ -12194,12 +12044,6 @@
"pages": [
"ar/telemetry"
]
},
{
"group": "الأمان",
"pages": [
"ar/security"
]
}
]
},
@@ -12665,12 +12509,6 @@
"pages": [
"ar/telemetry"
]
},
{
"group": "الأمان",
"pages": [
"ar/security"
]
}
]
},
@@ -13137,12 +12975,6 @@
"pages": [
"ar/telemetry"
]
},
{
"group": "الأمان",
"pages": [
"ar/security"
]
}
]
},
@@ -13459,4 +13291,4 @@
"reddit": "https://www.reddit.com/r/crewAIInc/"
}
}
}
}

View File

@@ -5,14 +5,6 @@ icon: wrench
mode: "wide"
---
### Watch: Building CrewAI Agents & Flows with Coding Agent Skills
Install our coding agent skills (Claude Code, Codex, ...) to quickly get your coding agents up and running with CrewAI.
You can install it with `npx skills add crewaiinc/skills`
<iframe src="https://www.loom.com/embed/befb9f68b81f42ad8112bfdd95a780af" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen style={{width: "100%", height: "400px"}}></iframe>
## Video Tutorial
Watch this video tutorial for a step-by-step demonstration of the installation process:

View File

@@ -16,14 +16,6 @@ It empowers developers to build production-ready multi-agent systems by combinin
With over 100,000 developers certified through our community courses, CrewAI is the standard for enterprise-ready AI automation.
### Watch: Building CrewAI Agents & Flows with Coding Agent Skills
Install our coding agent skills (Claude Code, Codex, ...) to quickly get your coding agents up and running with CrewAI.
You can install it with `npx skills add crewaiinc/skills`
<iframe src="https://www.loom.com/embed/befb9f68b81f42ad8112bfdd95a780af" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen style={{width: "100%", height: "400px"}}></iframe>
## The CrewAI Architecture
CrewAI's architecture is designed to balance autonomy with control.

View File

@@ -156,19 +156,7 @@ If you are developing an MCP server that CrewAI agents might connect to, conside
- **Adherence to MCP Authorization Spec**: If implementing authentication and authorization, strictly follow the [MCP Authorization specification](https://modelcontextprotocol.io/specification/draft/basic/authorization) and relevant [OAuth 2.0 security best practices](https://datatracker.ietf.org/doc/html/rfc9700).
- **Regular Security Audits**: If your MCP server handles sensitive data, performs critical operations, or is publicly exposed, consider periodic security audits by qualified professionals.
## 5. Reporting Security Vulnerabilities
If you discover a security vulnerability in CrewAI, please report it responsibly through our Bugcrowd Vulnerability Disclosure Program (VDP):
**Submit reports to:** [crewai-vdp-ess@submit.bugcrowd.com](mailto:crewai-vdp-ess@submit.bugcrowd.com)
<Warning>
**Do not** disclose vulnerabilities via public GitHub issues, pull requests, or social media. Reports submitted via channels other than Bugcrowd will not be reviewed.
</Warning>
For full details, see our [Security Policy](https://github.com/crewAIInc/crewAI/blob/main/.github/security.md).
## 6. Further Reading
## 5. Further Reading
For more detailed information on MCP security, refer to the official documentation:
- **[MCP Transport Security](https://modelcontextprotocol.io/docs/concepts/transports#security-considerations)**

View File

@@ -5,14 +5,6 @@ icon: rocket
mode: "wide"
---
### Watch: Building CrewAI Agents & Flows with Coding Agent Skills
Install our coding agent skills (Claude Code, Codex, ...) to quickly get your coding agents up and running with CrewAI.
You can install it with `npx skills add crewaiinc/skills`
<iframe src="https://www.loom.com/embed/befb9f68b81f42ad8112bfdd95a780af" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen style={{width: "100%", height: "400px"}}></iframe>
## Build your first CrewAI Agent
Let's create a simple crew that will help us `research` and `report` on the `latest AI developments` for a given topic or subject.

View File

@@ -1,22 +0,0 @@
---
title: Security Policy
description: Learn how to report security vulnerabilities and about CrewAI's security practices.
icon: shield
mode: "wide"
---
## Reporting Security Vulnerabilities
If you discover a security vulnerability in CrewAI, please report it responsibly through our Bugcrowd Vulnerability Disclosure Program (VDP):
**Submit reports to:** [crewai-vdp-ess@submit.bugcrowd.com](mailto:crewai-vdp-ess@submit.bugcrowd.com)
<Warning>
**Do not** disclose vulnerabilities via public GitHub issues, pull requests, or social media. Reports submitted via channels other than Bugcrowd will not be reviewed.
</Warning>
For full details, see our [Security Policy on GitHub](https://github.com/crewAIInc/crewAI/blob/main/.github/security.md).
## Security Resources
- **[MCP Security Considerations](/en/mcp/security)** — Best practices for securely integrating MCP servers with your CrewAI agents, including transport security, prompt injection risks, and server implementation advice.

View File

@@ -5,14 +5,6 @@ icon: wrench
mode: "wide"
---
### 영상: 코딩 에이전트 스킬을 활용한 CrewAI Agents & Flows 구축
코딩 에이전트 스킬(Claude Code, Codex 등)을 설치하여 CrewAI로 코딩 에이전트를 빠르게 시작하세요.
`npx skills add crewaiinc/skills` 명령어로 설치할 수 있습니다
<iframe src="https://www.loom.com/embed/befb9f68b81f42ad8112bfdd95a780af" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen style={{width: "100%", height: "400px"}}></iframe>
## 비디오 튜토리얼
설치 과정을 단계별로 시연하는 비디오 튜토리얼을 시청하세요:

View File

@@ -16,14 +16,6 @@ mode: "wide"
10만 명이 넘는 개발자가 커뮤니티 과정을 통해 인증을 받았으며, CrewAI는 기업용 AI 자동화의 표준입니다.
### 영상: 코딩 에이전트 스킬을 활용한 CrewAI Agents & Flows 구축
코딩 에이전트 스킬(Claude Code, Codex 등)을 설치하여 CrewAI로 코딩 에이전트를 빠르게 시작하세요.
`npx skills add crewaiinc/skills` 명령어로 설치할 수 있습니다
<iframe src="https://www.loom.com/embed/befb9f68b81f42ad8112bfdd95a780af" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen style={{width: "100%", height: "400px"}}></iframe>
## CrewAI 아키텍처
CrewAI의 아키텍처는 자율성과 제어의 균형을 맞추도록 설계되었습니다.

View File

@@ -156,19 +156,7 @@ CrewAI 에이전트가 연결할 수 있는 MCP 서버를 개발하고 있다면
- **MCP 인증 사양 준수**: 인증 및 권한 부여를 구현할 경우, [MCP Authorization specification](https://modelcontextprotocol.io/specification/draft/basic/authorization) 및 관련 [OAuth 2.0 security best practices](https://datatracker.ietf.org/doc/html/rfc9700)를 엄격히 준수하세요.
- **정기적인 보안 감사**: MCP 서버가 민감한 데이터를 처리하거나, 중요한 작업을 수행하거나, 대외적으로 노출된 경우 자격을 갖춘 전문가의 정기적인 보안 감사를 고려하세요.
## 5. 보안 취약점 보고
CrewAI에서 보안 취약점을 발견하셨다면, Bugcrowd 취약점 공개 프로그램(VDP)을 통해 책임감 있게 보고해 주세요:
**보고서 제출:** [crewai-vdp-ess@submit.bugcrowd.com](mailto:crewai-vdp-ess@submit.bugcrowd.com)
<Warning>
공개 GitHub 이슈, 풀 리퀘스트 또는 소셜 미디어를 통해 취약점을 공개하지 **마세요**. Bugcrowd 이외의 채널로 제출된 보고서는 검토되지 않습니다.
</Warning>
자세한 내용은 [보안 정책](https://github.com/crewAIInc/crewAI/blob/main/.github/security.md)을 참조하세요.
## 6. 추가 참고 자료
## 5. 추가 참고 자료
MCP 보안에 대한 자세한 내용은 공식 문서를 참고하세요:
- **[MCP 전송 보안](https://modelcontextprotocol.io/docs/concepts/transports#security-considerations)**

View File

@@ -5,14 +5,6 @@ icon: rocket
mode: "wide"
---
### 영상: 코딩 에이전트 스킬을 활용한 CrewAI Agents & Flows 구축
코딩 에이전트 스킬(Claude Code, Codex 등)을 설치하여 CrewAI로 코딩 에이전트를 빠르게 시작하세요.
`npx skills add crewaiinc/skills` 명령어로 설치할 수 있습니다
<iframe src="https://www.loom.com/embed/befb9f68b81f42ad8112bfdd95a780af" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen style={{width: "100%", height: "400px"}}></iframe>
## 첫 번째 CrewAI Agent 만들기
이제 주어진 주제나 항목에 대해 `최신 AI 개발 동향`을 `연구`하고 `보고`하는 간단한 crew를 만들어보겠습니다.

View File

@@ -1,22 +0,0 @@
---
title: 보안 정책
description: CrewAI의 보안 취약점 보고 방법과 보안 관행에 대해 알아보세요.
icon: shield
mode: "wide"
---
## 보안 취약점 보고
CrewAI에서 보안 취약점을 발견하셨다면, Bugcrowd 취약점 공개 프로그램(VDP)을 통해 책임감 있게 보고해 주세요:
**보고서 제출:** [crewai-vdp-ess@submit.bugcrowd.com](mailto:crewai-vdp-ess@submit.bugcrowd.com)
<Warning>
공개 GitHub 이슈, 풀 리퀘스트 또는 소셜 미디어를 통해 취약점을 공개하지 **마세요**. Bugcrowd 이외의 채널로 제출된 보고서는 검토되지 않습니다.
</Warning>
자세한 내용은 [GitHub 보안 정책](https://github.com/crewAIInc/crewAI/blob/main/.github/security.md)을 참조하세요.
## 보안 리소스
- **[MCP 보안 고려사항](/ko/mcp/security)** — MCP 서버를 CrewAI 에이전트와 안전하게 통합하기 위한 모범 사례로, 전송 보안, 프롬프트 인젝션 위험 및 서버 구현 권장 사항을 포함합니다.

View File

@@ -5,14 +5,6 @@ icon: wrench
mode: "wide"
---
### Assista: Construindo Agents e Flows CrewAI com Coding Agent Skills
Instale nossas coding agent skills (Claude Code, Codex, ...) para colocar seus agentes de código para funcionar rapidamente com o CrewAI.
Você pode instalar com `npx skills add crewaiinc/skills`
<iframe src="https://www.loom.com/embed/befb9f68b81f42ad8112bfdd95a780af" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen style={{width: "100%", height: "400px"}}></iframe>
## Tutorial em Vídeo
Assista a este tutorial em vídeo para uma demonstração passo a passo do processo de instalação:

View File

@@ -16,14 +16,6 @@ Ele capacita desenvolvedores a construir sistemas multi-agente prontos para prod
Com mais de 100.000 desenvolvedores certificados em nossos cursos comunitários, o CrewAI é o padrão para automação de IA pronta para empresas.
### Assista: Construindo Agents e Flows CrewAI com Coding Agent Skills
Instale nossas coding agent skills (Claude Code, Codex, ...) para colocar seus agentes de código para funcionar rapidamente com o CrewAI.
Você pode instalar com `npx skills add crewaiinc/skills`
<iframe src="https://www.loom.com/embed/befb9f68b81f42ad8112bfdd95a780af" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen style={{width: "100%", height: "400px"}}></iframe>
## A Arquitetura do CrewAI
A arquitetura do CrewAI foi projetada para equilibrar autonomia com controle.

View File

@@ -156,19 +156,7 @@ Se você está desenvolvendo um servidor MCP ao qual agentes CrewAI possam se co
- **Aderência à Especificação de Autorização MCP**: Caso implemente autenticação e autorização, siga estritamente a [especificação de autorização MCP](https://modelcontextprotocol.io/specification/draft/basic/authorization) e as [melhores práticas de segurança OAuth 2.0](https://datatracker.ietf.org/doc/html/rfc9700) relevantes.
- **Auditorias de Segurança Regulares**: Caso seu servidor MCP manipule dados sensíveis, realize operações críticas ou seja exposto publicamente, considere auditorias de segurança periódicas conduzidas por profissionais qualificados.
## 5. Reportando Vulnerabilidades de Segurança
Se você descobrir uma vulnerabilidade de segurança no CrewAI, por favor reporte de forma responsável através do nosso Programa de Divulgação de Vulnerabilidades (VDP) no Bugcrowd:
**Envie relatórios para:** [crewai-vdp-ess@submit.bugcrowd.com](mailto:crewai-vdp-ess@submit.bugcrowd.com)
<Warning>
**Não** divulgue vulnerabilidades por meio de issues públicas no GitHub, pull requests ou redes sociais. Relatórios enviados por outros canais que não o Bugcrowd não serão analisados.
</Warning>
Para mais detalhes, consulte nossa [Política de Segurança](https://github.com/crewAIInc/crewAI/blob/main/.github/security.md).
## 6. Leituras Adicionais
## 5. Leituras Adicionais
Para informações mais detalhadas sobre segurança MCP, consulte a documentação oficial:
- **[Segurança de Transporte MCP](https://modelcontextprotocol.io/docs/concepts/transports#security-considerations)**

View File

@@ -5,14 +5,6 @@ icon: rocket
mode: "wide"
---
### Assista: Construindo Agents e Flows CrewAI com Coding Agent Skills
Instale nossas coding agent skills (Claude Code, Codex, ...) para colocar seus agentes de código para funcionar rapidamente com o CrewAI.
Você pode instalar com `npx skills add crewaiinc/skills`
<iframe src="https://www.loom.com/embed/befb9f68b81f42ad8112bfdd95a780af" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen style={{width: "100%", height: "400px"}}></iframe>
## Construa seu primeiro Agente CrewAI
Vamos criar uma tripulação simples que nos ajudará a `pesquisar` e `relatar` sobre os `últimos avanços em IA` para um determinado tópico ou assunto.

View File

@@ -1,22 +0,0 @@
---
title: Política de Segurança
description: Saiba como reportar vulnerabilidades de segurança e sobre as práticas de segurança do CrewAI.
icon: shield
mode: "wide"
---
## Reportando Vulnerabilidades de Segurança
Se você descobrir uma vulnerabilidade de segurança no CrewAI, por favor reporte de forma responsável através do nosso Programa de Divulgação de Vulnerabilidades (VDP) no Bugcrowd:
**Envie relatórios para:** [crewai-vdp-ess@submit.bugcrowd.com](mailto:crewai-vdp-ess@submit.bugcrowd.com)
<Warning>
**Não** divulgue vulnerabilidades por meio de issues públicas no GitHub, pull requests ou redes sociais. Relatórios enviados por outros canais que não o Bugcrowd não serão analisados.
</Warning>
Para mais detalhes, consulte nossa [Política de Segurança no GitHub](https://github.com/crewAIInc/crewAI/blob/main/.github/security.md).
## Recursos de Segurança
- **[Considerações de Segurança MCP](/pt-BR/mcp/security)** — Melhores práticas para integrar servidores MCP com segurança aos seus agentes CrewAI, incluindo segurança de transporte, riscos de injeção de prompt e conselhos de implementação de servidor.

View File

@@ -8,15 +8,14 @@ from pydantic import PydanticUserError
from crewai.agent.core import Agent
from crewai.agent.planning_config import PlanningConfig
from crewai.context import ExecutionContext
from crewai.crew import Crew
from crewai.crews.crew_output import CrewOutput
from crewai.execution_context import ExecutionContext
from crewai.flow.flow import Flow
from crewai.knowledge.knowledge import Knowledge
from crewai.llm import LLM
from crewai.llms.base_llm import BaseLLM
from crewai.process import Process
from crewai.runtime_state import _entity_discriminator
from crewai.task import Task
from crewai.tasks.llm_guardrail import LLMGuardrail
from crewai.tasks.task_output import TaskOutput
@@ -113,13 +112,10 @@ try:
_base_namespace: dict[str, type] = {
"Agent": Agent,
"BaseAgent": _BaseAgent,
"Crew": Crew,
"Flow": Flow,
"BaseLLM": BaseLLM,
"Task": Task,
"CrewAgentExecutorMixin": _CrewAgentExecutorMixin,
"ExecutionContext": ExecutionContext,
}
try:
@@ -158,34 +154,13 @@ try:
for _mod_name in (
_BaseAgent.__module__,
Agent.__module__,
Crew.__module__,
Flow.__module__,
Task.__module__,
_AgentExecutor.__module__,
):
sys.modules[_mod_name].__dict__.update(_resolve_namespace)
from crewai.tasks.conditional_task import ConditionalTask as _ConditionalTask
_BaseAgent.model_rebuild(force=True, _types_namespace=_full_namespace)
Task.model_rebuild(force=True, _types_namespace=_full_namespace)
_ConditionalTask.model_rebuild(force=True, _types_namespace=_full_namespace)
Crew.model_rebuild(force=True, _types_namespace=_full_namespace)
Flow.model_rebuild(force=True, _types_namespace=_full_namespace)
_AgentExecutor.model_rebuild(force=True, _types_namespace=_full_namespace)
from typing import Annotated
from pydantic import Discriminator, RootModel, Tag
Entity = Annotated[
Annotated[Flow, Tag("flow")] # type: ignore[type-arg]
| Annotated[Crew, Tag("crew")]
| Annotated[Agent, Tag("agent")],
Discriminator(_entity_discriminator),
]
RuntimeState = RootModel[list[Entity]]
try:
Agent.model_rebuild(force=True, _types_namespace=_full_namespace)
except PydanticUserError:
@@ -197,7 +172,6 @@ except (ImportError, PydanticUserError):
"model_rebuild() failed; forward refs may be unresolved.",
exc_info=True,
)
RuntimeState = None # type: ignore[assignment,misc]
__all__ = [
"LLM",
@@ -212,7 +186,6 @@ __all__ = [
"Memory",
"PlanningConfig",
"Process",
"RuntimeState",
"Task",
"TaskOutput",
"__version__",

View File

@@ -14,7 +14,6 @@ import subprocess
import time
from typing import (
TYPE_CHECKING,
Annotated,
Any,
Literal,
NoReturn,
@@ -24,14 +23,12 @@ import warnings
from pydantic import (
BaseModel,
BeforeValidator,
ConfigDict,
Field,
InstanceOf,
PrivateAttr,
model_validator,
)
from pydantic.functional_serializers import PlainSerializer
from typing_extensions import Self
from crewai.agent.planning_config import PlanningConfig
@@ -49,11 +46,7 @@ from crewai.agent.utils import (
save_last_messages,
validate_max_execution_time,
)
from crewai.agents.agent_builder.base_agent import (
BaseAgent,
_serialize_llm_ref,
_validate_llm_ref,
)
from crewai.agents.agent_builder.base_agent import BaseAgent
from crewai.agents.cache.cache_handler import CacheHandler
from crewai.agents.crew_agent_executor import CrewAgentExecutor
from crewai.events.event_bus import crewai_event_bus
@@ -129,24 +122,6 @@ if TYPE_CHECKING:
_passthrough_exceptions: tuple[type[Exception], ...] = ()
_EXECUTOR_CLASS_MAP: dict[str, type] = {
"CrewAgentExecutor": CrewAgentExecutor,
"AgentExecutor": AgentExecutor,
}
def _validate_executor_class(value: Any) -> Any:
if isinstance(value, str):
cls = _EXECUTOR_CLASS_MAP.get(value)
if cls is None:
raise ValueError(f"Unknown executor class: {value}")
return cls
return value
def _serialize_executor_class(value: Any) -> str:
return value.__name__ if isinstance(value, type) else str(value)
class Agent(BaseAgent):
"""Represents an agent in a system.
@@ -192,16 +167,12 @@ class Agent(BaseAgent):
default=True,
description="Use system prompt for the agent.",
)
llm: Annotated[
str | BaseLLM | None,
BeforeValidator(_validate_llm_ref),
PlainSerializer(_serialize_llm_ref, return_type=str | None, when_used="json"),
] = Field(description="Language model that will run the agent.", default=None)
function_calling_llm: Annotated[
str | BaseLLM | None,
BeforeValidator(_validate_llm_ref),
PlainSerializer(_serialize_llm_ref, return_type=str | None, when_used="json"),
] = Field(description="Language model that will run the agent.", default=None)
llm: str | BaseLLM | None = Field(
description="Language model that will run the agent.", default=None
)
function_calling_llm: str | BaseLLM | None = Field(
description="Language model that will run the agent.", default=None
)
system_template: str | None = Field(
default=None, description="System format for the agent."
)
@@ -300,11 +271,7 @@ class Agent(BaseAgent):
agent_executor: InstanceOf[CrewAgentExecutor] | InstanceOf[AgentExecutor] | None = (
Field(default=None, description="An instance of the CrewAgentExecutor class.")
)
executor_class: Annotated[
type[CrewAgentExecutor] | type[AgentExecutor],
BeforeValidator(_validate_executor_class),
PlainSerializer(_serialize_executor_class, return_type=str, when_used="json"),
] = Field(
executor_class: type[CrewAgentExecutor] | type[AgentExecutor] = Field(
default=CrewAgentExecutor,
description="Class to use for the agent executor. Defaults to CrewAgentExecutor, can optionally use AgentExecutor.",
)
@@ -1086,7 +1053,7 @@ class Agent(BaseAgent):
)
)
def get_delegation_tools(self, agents: Sequence[BaseAgent]) -> list[BaseTool]:
def get_delegation_tools(self, agents: list[BaseAgent]) -> list[BaseTool]:
agent_tools = AgentTools(agents=agents)
return agent_tools.tools()

View File

@@ -5,7 +5,7 @@ with CrewAI's agent system. Provides memory persistence, tool integration, and s
output functionality.
"""
from collections.abc import Callable, Sequence
from collections.abc import Callable
from typing import Any, cast
from pydantic import ConfigDict, Field, PrivateAttr
@@ -30,7 +30,6 @@ from crewai.events.types.agent_events import (
)
from crewai.tools.agent_tools.agent_tools import AgentTools
from crewai.tools.base_tool import BaseTool
from crewai.types.callback import SerializableCallable
from crewai.utilities import Logger
from crewai.utilities.converter import Converter
from crewai.utilities.import_utils import require
@@ -51,7 +50,7 @@ class LangGraphAgentAdapter(BaseAgentAdapter):
_memory: Any = PrivateAttr(default=None)
_max_iterations: int = PrivateAttr(default=10)
function_calling_llm: Any = Field(default=None)
step_callback: SerializableCallable | None = Field(default=None)
step_callback: Callable[..., Any] | None = Field(default=None)
model: str = Field(default="gpt-4o")
verbose: bool = Field(default=False)
@@ -273,7 +272,7 @@ class LangGraphAgentAdapter(BaseAgentAdapter):
available_tools: list[Any] = self._tool_adapter.tools()
self._graph.tools = available_tools
def get_delegation_tools(self, agents: Sequence[BaseAgent]) -> list[BaseTool]:
def get_delegation_tools(self, agents: list[BaseAgent]) -> list[BaseTool]:
"""Implement delegation tools support for LangGraph.
Creates delegation tools that allow this agent to delegate tasks to other agents.

View File

@@ -4,7 +4,6 @@ This module contains the OpenAIAgentAdapter class that integrates OpenAI Assista
with CrewAI's agent system, providing tool integration and structured output support.
"""
from collections.abc import Sequence
from typing import Any, cast
from pydantic import ConfigDict, Field, PrivateAttr
@@ -222,7 +221,7 @@ class OpenAIAgentAdapter(BaseAgentAdapter):
"""
return self._converter_adapter.post_process_result(result.final_output)
def get_delegation_tools(self, agents: Sequence[BaseAgent]) -> list[BaseTool]:
def get_delegation_tools(self, agents: list[BaseAgent]) -> list[BaseTool]:
"""Implement delegation tools support.
Creates delegation tools that allow this agent to delegate tasks to other agents.

View File

@@ -1,7 +1,6 @@
from __future__ import annotations
from abc import ABC, abstractmethod
from collections.abc import Sequence
from copy import copy as shallow_copy
from hashlib import md5
from pathlib import Path
@@ -49,7 +48,6 @@ from crewai.utilities.string_utils import interpolate_only
if TYPE_CHECKING:
from crewai.context import ExecutionContext
from crewai.crew import Crew
@@ -63,26 +61,6 @@ def _serialize_crew_ref(value: Any) -> str | None:
return str(value.id) if hasattr(value, "id") else str(value)
def _validate_llm_ref(value: Any) -> Any:
return value
def _resolve_agent(value: Any, info: Any) -> Any:
if isinstance(value, BaseAgent) or value is None or not isinstance(value, dict):
return value
from crewai.agent.core import Agent
return Agent.model_validate(value, context=getattr(info, "context", None))
def _serialize_llm_ref(value: Any) -> str | None:
if value is None:
return None
if isinstance(value, str):
return value
return getattr(value, "model", str(value))
_SLUG_RE: Final[re.Pattern[str]] = re.compile(
r"^(?:crewai-amp:)?[a-zA-Z0-9][a-zA-Z0-9_-]*(?:#[\w-]+)?$"
)
@@ -160,8 +138,6 @@ class BaseAgent(BaseModel, ABC, metaclass=AgentMeta):
Set private attributes.
"""
entity_type: Literal["agent"] = "agent"
__hash__ = object.__hash__
_logger: Logger = PrivateAttr(default_factory=lambda: Logger(verbose=False))
_rpm_controller: RPMController | None = PrivateAttr(default=None)
@@ -200,11 +176,9 @@ class BaseAgent(BaseModel, ABC, metaclass=AgentMeta):
agent_executor: InstanceOf[CrewAgentExecutorMixin] | None = Field(
default=None, description="An instance of the CrewAgentExecutor class."
)
llm: Annotated[
str | BaseLLM | None,
BeforeValidator(_validate_llm_ref),
PlainSerializer(_serialize_llm_ref, return_type=str | None, when_used="json"),
] = Field(default=None, description="Language model that will run the agent.")
llm: str | BaseLLM | None = Field(
default=None, description="Language model that will run the agent."
)
crew: Annotated[
Crew | str | None,
BeforeValidator(_validate_crew_ref),
@@ -223,7 +197,7 @@ class BaseAgent(BaseModel, ABC, metaclass=AgentMeta):
description="An instance of the ToolsHandler class.",
)
tools_results: list[dict[str, Any]] = Field(
default_factory=list, description="Results of the tools used by the agent."
default=[], description="Results of the tools used by the agent."
)
max_tokens: int | None = Field(
default=None, description="Maximum number of tokens for the agent's execution."
@@ -274,7 +248,6 @@ class BaseAgent(BaseModel, ABC, metaclass=AgentMeta):
description="Agent Skills. Accepts paths for discovery or pre-loaded Skill objects.",
min_length=1,
)
execution_context: ExecutionContext | None = Field(default=None)
@model_validator(mode="before")
@classmethod
@@ -389,12 +362,11 @@ class BaseAgent(BaseModel, ABC, metaclass=AgentMeta):
@field_validator("id", mode="before")
@classmethod
def _deny_user_set_id(cls, v: UUID4 | None, info: Any) -> UUID4 | None:
if v and not (info.context or {}).get("from_checkpoint"):
def _deny_user_set_id(cls, v: UUID4 | None) -> None:
if v:
raise PydanticCustomError(
"may_not_set_field", "This field is not to be set by the user.", {}
)
return v
@model_validator(mode="after")
def set_private_attrs(self) -> Self:
@@ -451,7 +423,7 @@ class BaseAgent(BaseModel, ABC, metaclass=AgentMeta):
pass
@abstractmethod
def get_delegation_tools(self, agents: Sequence[BaseAgent]) -> list[BaseTool]:
def get_delegation_tools(self, agents: list[BaseAgent]) -> list[BaseTool]:
"""Set the task tools that init BaseAgenTools class."""
@abstractmethod

View File

@@ -3,15 +3,20 @@
from __future__ import annotations
import json
from typing import TYPE_CHECKING, Any
from pydantic import BaseModel, Field
from pydantic import GetCoreSchemaHandler
from pydantic_core import CoreSchema, core_schema
from crewai.agents.cache.cache_handler import CacheHandler
from crewai.tools.cache_tools.cache_tools import CacheTools
from crewai.tools.tool_calling import InstructorToolCalling, ToolCalling
class ToolsHandler(BaseModel):
if TYPE_CHECKING:
from crewai.agents.cache.cache_handler import CacheHandler
from crewai.tools.tool_calling import InstructorToolCalling, ToolCalling
class ToolsHandler:
"""Callback handler for tool usage.
Attributes:
@@ -19,8 +24,14 @@ class ToolsHandler(BaseModel):
cache: Optional cache handler for storing tool outputs.
"""
cache: CacheHandler | None = Field(default=None)
last_used_tool: ToolCalling | InstructorToolCalling | None = Field(default=None)
def __init__(self, cache: CacheHandler | None = None) -> None:
"""Initialize the callback handler.
Args:
cache: Optional cache handler for storing tool outputs.
"""
self.cache: CacheHandler | None = cache
self.last_used_tool: ToolCalling | InstructorToolCalling | None = None
def on_tool_use(
self,
@@ -37,6 +48,7 @@ class ToolsHandler(BaseModel):
"""
self.last_used_tool = calling
if self.cache and should_cache and calling.tool_name != CacheTools().name:
# Convert arguments to string for cache
input_str = ""
if calling.arguments:
if isinstance(calling.arguments, dict):
@@ -49,3 +61,14 @@ class ToolsHandler(BaseModel):
input=input_str,
output=output,
)
@classmethod
def __get_pydantic_core_schema__(
cls, _source_type: Any, _handler: GetCoreSchemaHandler
) -> CoreSchema:
"""Generate Pydantic core schema for BaseClient Protocol.
This allows the Protocol to be used in Pydantic models without
requiring arbitrary_types_allowed=True.
"""
return core_schema.any_schema()

View File

@@ -4,23 +4,6 @@ import contextvars
import os
from typing import Any
from pydantic import BaseModel, Field
from crewai.events.base_events import (
get_emission_sequence,
set_emission_counter,
)
from crewai.events.event_context import (
_event_id_stack,
_last_event_id,
_triggering_event_id,
)
from crewai.flow.flow_context import (
current_flow_id,
current_flow_method_name,
current_flow_request_id,
)
_platform_integration_token: contextvars.ContextVar[str | None] = (
contextvars.ContextVar("platform_integration_token", default=None)
@@ -80,53 +63,3 @@ def reset_current_task_id(token: contextvars.Token[str | None]) -> None:
def get_current_task_id() -> str | None:
"""Get the current task ID from the context."""
return _current_task_id.get()
class ExecutionContext(BaseModel):
"""Snapshot of ContextVar execution state."""
current_task_id: str | None = Field(default=None)
flow_request_id: str | None = Field(default=None)
flow_id: str | None = Field(default=None)
flow_method_name: str = Field(default="unknown")
event_id_stack: tuple[tuple[str, str], ...] = Field(default=())
last_event_id: str | None = Field(default=None)
triggering_event_id: str | None = Field(default=None)
emission_sequence: int = Field(default=0)
feedback_callback_info: dict[str, Any] | None = Field(default=None)
platform_token: str | None = Field(default=None)
def capture_execution_context(
feedback_callback_info: dict[str, Any] | None = None,
) -> ExecutionContext:
"""Read current ContextVars into an ExecutionContext."""
return ExecutionContext(
current_task_id=_current_task_id.get(),
flow_request_id=current_flow_request_id.get(),
flow_id=current_flow_id.get(),
flow_method_name=current_flow_method_name.get(),
event_id_stack=_event_id_stack.get(),
last_event_id=_last_event_id.get(),
triggering_event_id=_triggering_event_id.get(),
emission_sequence=get_emission_sequence(),
feedback_callback_info=feedback_callback_info,
platform_token=_platform_integration_token.get(),
)
def apply_execution_context(ctx: ExecutionContext) -> None:
"""Write an ExecutionContext back into the ContextVars."""
_current_task_id.set(ctx.current_task_id)
current_flow_request_id.set(ctx.flow_request_id)
current_flow_id.set(ctx.flow_id)
current_flow_method_name.set(ctx.flow_method_name)
_event_id_stack.set(ctx.event_id_stack)
_last_event_id.set(ctx.last_event_id)
_triggering_event_id.set(ctx.triggering_event_id)
set_emission_counter(ctx.emission_sequence)
_platform_integration_token.set(ctx.platform_token)

View File

@@ -1,7 +1,7 @@
from __future__ import annotations
import asyncio
from collections.abc import Callable, Sequence
from collections.abc import Callable
from concurrent.futures import Future
from copy import copy as shallow_copy
from hashlib import md5
@@ -10,9 +10,7 @@ from pathlib import Path
import re
from typing import (
TYPE_CHECKING,
Annotated,
Any,
Literal,
cast,
)
import uuid
@@ -23,14 +21,12 @@ from opentelemetry.context import attach, detach
from pydantic import (
UUID4,
BaseModel,
BeforeValidator,
Field,
Json,
PrivateAttr,
field_validator,
model_validator,
)
from pydantic.functional_serializers import PlainSerializer
from pydantic_core import PydanticCustomError
from rich.console import Console
from rich.panel import Panel
@@ -41,8 +37,6 @@ if TYPE_CHECKING:
from crewai_files import FileInput
from opentelemetry.trace import Span
from crewai.context import ExecutionContext
try:
from crewai_files import get_supported_content_types
@@ -55,12 +49,7 @@ except ImportError:
from crewai.agent import Agent
from crewai.agents.agent_builder.base_agent import (
BaseAgent,
_resolve_agent,
_serialize_llm_ref,
_validate_llm_ref,
)
from crewai.agents.agent_builder.base_agent import BaseAgent
from crewai.agents.cache.cache_handler import CacheHandler
from crewai.crews.crew_output import CrewOutput
from crewai.crews.utils import (
@@ -143,12 +132,6 @@ from crewai.utilities.training_handler import CrewTrainingHandler
warnings.filterwarnings("ignore", category=SyntaxWarning, module="pysbd")
def _resolve_agents(value: Any, info: Any) -> Any:
if not isinstance(value, list):
return value
return [_resolve_agent(a, info) for a in value]
class Crew(FlowTrackable, BaseModel):
"""
Represents a group of agents, defining how they should collaborate and the
@@ -187,8 +170,6 @@ class Crew(FlowTrackable, BaseModel):
fingerprinting.
"""
entity_type: Literal["crew"] = "crew"
__hash__ = object.__hash__
_execution_span: Span | None = PrivateAttr()
_rpm_controller: RPMController = PrivateAttr()
@@ -210,10 +191,7 @@ class Crew(FlowTrackable, BaseModel):
name: str | None = Field(default="crew")
cache: bool = Field(default=True)
tasks: list[Task] = Field(default_factory=list)
agents: Annotated[
list[BaseAgent],
BeforeValidator(_resolve_agents),
] = Field(default_factory=list)
agents: list[BaseAgent] = Field(default_factory=list)
process: Process = Field(default=Process.sequential)
verbose: bool = Field(default=False)
memory: bool | Memory | MemoryScope | MemorySlice | None = Field(
@@ -231,20 +209,15 @@ class Crew(FlowTrackable, BaseModel):
default=None,
description="Metrics for the LLM usage during all tasks execution.",
)
manager_llm: Annotated[
str | BaseLLM | None,
BeforeValidator(_validate_llm_ref),
PlainSerializer(_serialize_llm_ref, return_type=str | None, when_used="json"),
] = Field(description="Language model that will run the agent.", default=None)
manager_agent: Annotated[
BaseAgent | None,
BeforeValidator(_resolve_agent),
] = Field(description="Custom agent that will be used as manager.", default=None)
function_calling_llm: Annotated[
str | LLM | None,
BeforeValidator(_validate_llm_ref),
PlainSerializer(_serialize_llm_ref, return_type=str | None, when_used="json"),
] = Field(description="Language model that will run the agent.", default=None)
manager_llm: str | BaseLLM | None = Field(
description="Language model that will run the agent.", default=None
)
manager_agent: BaseAgent | None = Field(
description="Custom agent that will be used as manager.", default=None
)
function_calling_llm: str | LLM | None = Field(
description="Language model that will run the agent.", default=None
)
config: Json[dict[str, Any]] | dict[str, Any] | None = Field(default=None)
id: UUID4 = Field(default_factory=uuid.uuid4, frozen=True)
share_crew: bool | None = Field(default=False)
@@ -293,11 +266,7 @@ class Crew(FlowTrackable, BaseModel):
default=False,
description="Plan the crew execution and add the plan to the crew.",
)
planning_llm: Annotated[
str | BaseLLM | None,
BeforeValidator(_validate_llm_ref),
PlainSerializer(_serialize_llm_ref, return_type=str | None, when_used="json"),
] = Field(
planning_llm: str | BaseLLM | None = Field(
default=None,
description=(
"Language model that will run the AgentPlanner if planning is True."
@@ -318,11 +287,7 @@ class Crew(FlowTrackable, BaseModel):
"knowledge object."
),
)
chat_llm: Annotated[
str | BaseLLM | None,
BeforeValidator(_validate_llm_ref),
PlainSerializer(_serialize_llm_ref, return_type=str | None, when_used="json"),
] = Field(
chat_llm: str | BaseLLM | None = Field(
default=None,
description="LLM used to handle chatting with the crew.",
)
@@ -348,20 +313,14 @@ class Crew(FlowTrackable, BaseModel):
description="Whether to enable tracing for the crew. True=always enable, False=always disable, None=check environment/user settings.",
)
execution_context: ExecutionContext | None = Field(default=None)
checkpoint_inputs: dict[str, Any] | None = Field(default=None)
checkpoint_train: bool | None = Field(default=None)
checkpoint_kickoff_event_id: str | None = Field(default=None)
@field_validator("id", mode="before")
@classmethod
def _deny_user_set_id(cls, v: UUID4 | None, info: Any) -> UUID4 | None:
def _deny_user_set_id(cls, v: UUID4 | None) -> None:
"""Prevent manual setting of the 'id' field by users."""
if v and not (info.context or {}).get("from_checkpoint"):
if v:
raise PydanticCustomError(
"may_not_set_field", "The 'id' field cannot be set by the user.", {}
)
return v
@field_validator("config", mode="before")
@classmethod
@@ -1429,7 +1388,7 @@ class Crew(FlowTrackable, BaseModel):
self,
tools: list[BaseTool],
task_agent: BaseAgent,
agents: Sequence[BaseAgent],
agents: list[BaseAgent],
) -> list[BaseTool]:
if hasattr(task_agent, "get_delegation_tools"):
delegation_tools = task_agent.get_delegation_tools(agents)

View File

@@ -21,7 +21,7 @@ class CrewOutput(BaseModel):
description="JSON dict output of Crew", default=None
)
tasks_output: list[TaskOutput] = Field(
description="Output of each task", default_factory=list
description="Output of each task", default=[]
)
token_usage: UsageMetrics = Field(
description="Processed token summary", default_factory=UsageMetrics

View File

@@ -78,15 +78,9 @@ from crewai.events.types.mcp_events import (
MCPConnectionCompletedEvent,
MCPConnectionFailedEvent,
MCPConnectionStartedEvent,
MCPToolExecutionCompletedEvent,
MCPToolExecutionFailedEvent,
MCPToolExecutionStartedEvent,
)
from crewai.events.types.memory_events import (
MemoryQueryCompletedEvent,
MemoryRetrievalCompletedEvent,
MemorySaveCompletedEvent,
)
from crewai.events.types.observation_events import (
GoalAchievedEarlyEvent,
PlanRefinementEvent,
@@ -100,12 +94,6 @@ from crewai.events.types.reasoning_events import (
AgentReasoningFailedEvent,
AgentReasoningStartedEvent,
)
from crewai.events.types.skill_events import (
SkillActivatedEvent,
SkillDiscoveryCompletedEvent,
SkillLoadFailedEvent,
SkillLoadedEvent,
)
from crewai.events.types.task_events import (
TaskCompletedEvent,
TaskFailedEvent,
@@ -490,7 +478,6 @@ class EventListener(BaseEventListener):
self.formatter.handle_guardrail_completed(
event.success, event.error, event.retry_count
)
self._telemetry.feature_usage_span("guardrail:execution")
@crewai_event_bus.on(CrewTestStartedEvent)
def on_crew_test_started(source: Any, event: CrewTestStartedEvent) -> None:
@@ -572,7 +559,6 @@ class EventListener(BaseEventListener):
event.plan,
event.ready,
)
self._telemetry.feature_usage_span("planning:creation")
@crewai_event_bus.on(AgentReasoningFailedEvent)
def on_agent_reasoning_failed(_: Any, event: AgentReasoningFailedEvent) -> None:
@@ -630,7 +616,6 @@ class EventListener(BaseEventListener):
event.replan_count,
event.completed_steps_preserved,
)
self._telemetry.feature_usage_span("planning:replan")
@crewai_event_bus.on(GoalAchievedEarlyEvent)
def on_goal_achieved_early(_: Any, event: GoalAchievedEarlyEvent) -> None:
@@ -638,25 +623,6 @@ class EventListener(BaseEventListener):
event.steps_completed,
event.steps_remaining,
)
self._telemetry.feature_usage_span("planning:goal_achieved_early")
# ----------- SKILL EVENTS -----------
@crewai_event_bus.on(SkillDiscoveryCompletedEvent)
def on_skill_discovery(_: Any, event: SkillDiscoveryCompletedEvent) -> None:
self._telemetry.feature_usage_span("skill:discovery")
@crewai_event_bus.on(SkillLoadedEvent)
def on_skill_loaded(_: Any, event: SkillLoadedEvent) -> None:
self._telemetry.feature_usage_span("skill:loaded")
@crewai_event_bus.on(SkillLoadFailedEvent)
def on_skill_load_failed(_: Any, event: SkillLoadFailedEvent) -> None:
self._telemetry.feature_usage_span("skill:load_failed")
@crewai_event_bus.on(SkillActivatedEvent)
def on_skill_activated(_: Any, event: SkillActivatedEvent) -> None:
self._telemetry.feature_usage_span("skill:activated")
# ----------- AGENT LOGGING EVENTS -----------
@@ -696,7 +662,6 @@ class EventListener(BaseEventListener):
event.error,
event.is_multiturn,
)
self._telemetry.feature_usage_span("a2a:delegation")
@crewai_event_bus.on(A2AConversationStartedEvent)
def on_a2a_conversation_started(
@@ -738,7 +703,6 @@ class EventListener(BaseEventListener):
event.error,
event.total_turns,
)
self._telemetry.feature_usage_span("a2a:conversation")
@crewai_event_bus.on(A2APollingStartedEvent)
def on_a2a_polling_started(_: Any, event: A2APollingStartedEvent) -> None:
@@ -780,7 +744,6 @@ class EventListener(BaseEventListener):
event.connection_duration_ms,
event.is_reconnect,
)
self._telemetry.feature_usage_span("mcp:connection")
@crewai_event_bus.on(MCPConnectionFailedEvent)
def on_mcp_connection_failed(_: Any, event: MCPConnectionFailedEvent) -> None:
@@ -791,7 +754,6 @@ class EventListener(BaseEventListener):
event.error,
event.error_type,
)
self._telemetry.feature_usage_span("mcp:connection_failed")
@crewai_event_bus.on(MCPConfigFetchFailedEvent)
def on_mcp_config_fetch_failed(
@@ -802,7 +764,6 @@ class EventListener(BaseEventListener):
event.error,
event.error_type,
)
self._telemetry.feature_usage_span("mcp:config_fetch_failed")
@crewai_event_bus.on(MCPToolExecutionStartedEvent)
def on_mcp_tool_execution_started(
@@ -814,12 +775,6 @@ class EventListener(BaseEventListener):
event.tool_args,
)
@crewai_event_bus.on(MCPToolExecutionCompletedEvent)
def on_mcp_tool_execution_completed(
_: Any, event: MCPToolExecutionCompletedEvent
) -> None:
self._telemetry.feature_usage_span("mcp:tool_execution")
@crewai_event_bus.on(MCPToolExecutionFailedEvent)
def on_mcp_tool_execution_failed(
_: Any, event: MCPToolExecutionFailedEvent
@@ -831,45 +786,6 @@ class EventListener(BaseEventListener):
event.error,
event.error_type,
)
self._telemetry.feature_usage_span("mcp:tool_execution_failed")
# ----------- MEMORY TELEMETRY -----------
@crewai_event_bus.on(MemorySaveCompletedEvent)
def on_memory_save_completed(_: Any, event: MemorySaveCompletedEvent) -> None:
self._telemetry.feature_usage_span("memory:save")
@crewai_event_bus.on(MemoryQueryCompletedEvent)
def on_memory_query_completed(_: Any, event: MemoryQueryCompletedEvent) -> None:
self._telemetry.feature_usage_span("memory:query")
@crewai_event_bus.on(MemoryRetrievalCompletedEvent)
def on_memory_retrieval_completed_telemetry(
_: Any, event: MemoryRetrievalCompletedEvent
) -> None:
self._telemetry.feature_usage_span("memory:retrieval")
@crewai_event_bus.on(CrewKickoffStartedEvent)
def on_crew_kickoff_hooks(_: Any, event: CrewKickoffStartedEvent) -> None:
from crewai.hooks.llm_hooks import (
get_after_llm_call_hooks,
get_before_llm_call_hooks,
)
from crewai.hooks.tool_hooks import (
get_after_tool_call_hooks,
get_before_tool_call_hooks,
)
has_hooks = any(
[
get_before_llm_call_hooks(),
get_after_llm_call_hooks(),
get_before_tool_call_hooks(),
get_after_tool_call_hooks(),
]
)
if has_hooks:
self._telemetry.feature_usage_span("hooks:registered")
event_listener = EventListener()

View File

@@ -0,0 +1,80 @@
"""Checkpointable execution context for the crewAI runtime.
Captures the ContextVar state needed to resume execution from a checkpoint.
Used by the RootModel (step 5) to include execution context in snapshots.
"""
from __future__ import annotations
from typing import Any
from pydantic import BaseModel, Field
from crewai.context import (
_current_task_id,
_platform_integration_token,
)
from crewai.events.base_events import (
get_emission_sequence,
set_emission_counter,
)
from crewai.events.event_context import (
_event_id_stack,
_last_event_id,
_triggering_event_id,
)
from crewai.flow.flow_context import (
current_flow_id,
current_flow_method_name,
current_flow_request_id,
)
class ExecutionContext(BaseModel):
"""Snapshot of ContextVar state required for checkpoint/resume."""
current_task_id: str | None = Field(default=None)
flow_request_id: str | None = Field(default=None)
flow_id: str | None = Field(default=None)
flow_method_name: str = Field(default="unknown")
event_id_stack: tuple[tuple[str, str], ...] = Field(default=())
last_event_id: str | None = Field(default=None)
triggering_event_id: str | None = Field(default=None)
emission_sequence: int = Field(default=0)
feedback_callback_info: dict[str, Any] | None = Field(default=None)
platform_token: str | None = Field(default=None)
def capture_execution_context(
feedback_callback_info: dict[str, Any] | None = None,
) -> ExecutionContext:
"""Read all checkpoint-required ContextVars into an ExecutionContext."""
return ExecutionContext(
current_task_id=_current_task_id.get(),
flow_request_id=current_flow_request_id.get(),
flow_id=current_flow_id.get(),
flow_method_name=current_flow_method_name.get(),
event_id_stack=_event_id_stack.get(),
last_event_id=_last_event_id.get(),
triggering_event_id=_triggering_event_id.get(),
emission_sequence=get_emission_sequence(),
feedback_callback_info=feedback_callback_info,
platform_token=_platform_integration_token.get(),
)
def apply_execution_context(ctx: ExecutionContext) -> None:
"""Write an ExecutionContext back into the ContextVars."""
_current_task_id.set(ctx.current_task_id)
current_flow_request_id.set(ctx.flow_request_id)
current_flow_id.set(ctx.flow_id)
current_flow_method_name.set(ctx.flow_method_name)
_event_id_stack.set(ctx.event_id_stack)
_last_event_id.set(ctx.last_event_id)
_triggering_event_id.set(ctx.triggering_event_id)
set_emission_counter(ctx.emission_sequence)
_platform_integration_token.set(ctx.platform_token)

View File

@@ -25,7 +25,6 @@ import logging
import threading
from typing import (
TYPE_CHECKING,
Annotated,
Any,
ClassVar,
Generic,
@@ -42,11 +41,9 @@ from opentelemetry import baggage
from opentelemetry.context import attach, detach
from pydantic import (
BaseModel,
BeforeValidator,
ConfigDict,
Field,
PrivateAttr,
SerializeAsAny,
ValidationError,
)
from pydantic._internal._model_construction import ModelMetaclass
@@ -118,7 +115,6 @@ from crewai.memory.unified_memory import Memory
if TYPE_CHECKING:
from crewai_files import FileInput
from crewai.context import ExecutionContext
from crewai.flow.async_feedback.types import PendingFeedbackContext
from crewai.llms.base_llm import BaseLLM
@@ -138,19 +134,6 @@ from crewai.utilities.streaming import (
logger = logging.getLogger(__name__)
def _resolve_persistence(value: Any) -> Any:
if value is None or isinstance(value, FlowPersistence):
return value
if isinstance(value, dict):
from crewai.flow.persistence.base import _persistence_registry
type_name = value.get("persistence_type", "SQLiteFlowPersistence")
cls = _persistence_registry.get(type_name)
if cls is not None:
return cls.model_validate(value)
return value
class FlowState(BaseModel):
"""Base model for all flow states, ensuring each state has a unique ID."""
@@ -900,8 +883,6 @@ class Flow(BaseModel, Generic[T], metaclass=FlowMeta):
_routers: ClassVar[set[FlowMethodName]] = set()
_router_paths: ClassVar[dict[FlowMethodName, list[FlowMethodName]]] = {}
entity_type: Literal["flow"] = "flow"
initial_state: Any = Field(default=None)
name: str | None = Field(default=None)
tracing: bool | None = Field(default=None)
@@ -912,17 +893,8 @@ class Flow(BaseModel, Generic[T], metaclass=FlowMeta):
human_feedback_history: list[HumanFeedbackResult] = Field(default_factory=list)
last_human_feedback: HumanFeedbackResult | None = Field(default=None)
persistence: Annotated[
SerializeAsAny[FlowPersistence] | Any,
BeforeValidator(lambda v, _: _resolve_persistence(v)),
] = Field(default=None)
max_method_calls: int = Field(default=100)
execution_context: ExecutionContext | None = Field(default=None)
checkpoint_completed_methods: set[str] | None = Field(default=None)
checkpoint_method_outputs: list[Any] | None = Field(default=None)
checkpoint_method_counts: dict[str, int] | None = Field(default=None)
checkpoint_state: dict[str, Any] | None = Field(default=None)
persistence: Any = Field(default=None, exclude=True)
max_method_calls: int = Field(default=100, exclude=True)
_methods: dict[FlowMethodName, FlowMethod[Any, Any]] = PrivateAttr(
default_factory=dict

View File

@@ -5,17 +5,14 @@ from __future__ import annotations
from abc import ABC, abstractmethod
from typing import TYPE_CHECKING, Any
from pydantic import BaseModel, Field
from pydantic import BaseModel
if TYPE_CHECKING:
from crewai.flow.async_feedback.types import PendingFeedbackContext
_persistence_registry: dict[str, type[FlowPersistence]] = {}
class FlowPersistence(BaseModel, ABC):
class FlowPersistence(ABC):
"""Abstract base class for flow state persistence.
This class defines the interface that all persistence implementations must follow.
@@ -27,13 +24,6 @@ class FlowPersistence(BaseModel, ABC):
- clear_pending_feedback(): Clears pending feedback after resume
"""
persistence_type: str = Field(default="base")
def __init_subclass__(cls, **kwargs: Any) -> None:
super().__init_subclass__(**kwargs)
if not getattr(cls, "__abstractmethods__", set()):
_persistence_registry[cls.__name__] = cls
@abstractmethod
def init_db(self) -> None:
"""Initialize the persistence backend.
@@ -105,7 +95,7 @@ class FlowPersistence(BaseModel, ABC):
"""
return None
def clear_pending_feedback(self, flow_uuid: str) -> None:
def clear_pending_feedback(self, flow_uuid: str) -> None: # noqa: B027
"""Clear the pending feedback marker after successful resume.
This is called after feedback is received and the flow resumes.

View File

@@ -9,8 +9,7 @@ from pathlib import Path
import sqlite3
from typing import TYPE_CHECKING, Any
from pydantic import BaseModel, Field, PrivateAttr, model_validator
from typing_extensions import Self
from pydantic import BaseModel
from crewai.flow.persistence.base import FlowPersistence
from crewai.utilities.lock_store import lock as store_lock
@@ -51,22 +50,26 @@ class SQLiteFlowPersistence(FlowPersistence):
```
"""
persistence_type: str = Field(default="SQLiteFlowPersistence")
db_path: str = Field(
default_factory=lambda: str(Path(db_storage_path()) / "flow_states.db")
)
_lock_name: str = PrivateAttr()
def __init__(self, db_path: str | None = None) -> None:
"""Initialize SQLite persistence.
def __init__(self, db_path: str | None = None, /, **kwargs: Any) -> None:
if db_path is not None:
kwargs["db_path"] = db_path
super().__init__(**kwargs)
Args:
db_path: Path to the SQLite database file. If not provided, uses
db_storage_path() from utilities.paths.
@model_validator(mode="after")
def _setup(self) -> Self:
Raises:
ValueError: If db_path is invalid
"""
# Get path from argument or default location
path = db_path or str(Path(db_storage_path()) / "flow_states.db")
if not path:
raise ValueError("Database path must be provided")
self.db_path = path # Now mypy knows this is str
self._lock_name = f"sqlite:{os.path.realpath(self.db_path)}"
self.init_db()
return self
def init_db(self) -> None:
"""Create the necessary tables if they don't exist."""

View File

@@ -40,9 +40,7 @@ class LiteAgentOutput(BaseModel):
usage_metrics: dict[str, Any] | None = Field(
description="Token usage metrics for this execution", default=None
)
messages: list[LLMMessage] = Field(
description="Messages of the agent", default_factory=list
)
messages: list[LLMMessage] = Field(description="Messages of the agent", default=[])
plan: str | None = Field(
default=None, description="The execution plan that was generated, if any"

View File

@@ -32,10 +32,6 @@ class MemoryScope(BaseModel):
"""Extract memory dependency and normalize root path before validation."""
if isinstance(data, MemoryScope):
return data
if not isinstance(data, dict):
raise ValueError(f"Expected dict or MemoryScope, got {type(data).__name__}")
if "memory" not in data:
raise ValueError("MemoryScope requires a 'memory' key")
memory = data.pop("memory")
instance: MemoryScope = handler(data)
instance._memory = memory
@@ -203,10 +199,6 @@ class MemorySlice(BaseModel):
"""Extract memory dependency and normalize scopes before validation."""
if isinstance(data, MemorySlice):
return data
if not isinstance(data, dict):
raise ValueError(f"Expected dict or MemorySlice, got {type(data).__name__}")
if "memory" not in data:
raise ValueError("MemorySlice requires a 'memory' key")
memory = data.pop("memory")
data["scopes"] = [s.rstrip("/") or "/" for s in data.get("scopes", [])]
instance: MemorySlice = handler(data)

View File

@@ -1,18 +0,0 @@
"""Unified runtime state for crewAI.
``RuntimeState`` is a ``RootModel`` whose ``model_dump_json()`` produces a
complete, self-contained snapshot of every active entity in the program.
The ``Entity`` type alias and ``RuntimeState`` model are built at import time
in ``crewai/__init__.py`` after all forward references are resolved.
"""
from typing import Any
def _entity_discriminator(v: dict[str, Any] | object) -> str:
if isinstance(v, dict):
raw = v.get("entity_type", "agent")
else:
raw = getattr(v, "entity_type", "agent")
return str(raw)

View File

@@ -1,7 +1,6 @@
from __future__ import annotations
import asyncio
from collections.abc import Sequence
from concurrent.futures import Future
import contextvars
from copy import copy as shallow_copy
@@ -13,7 +12,6 @@ import logging
from pathlib import Path
import threading
from typing import (
Annotated,
Any,
ClassVar,
cast,
@@ -26,7 +24,6 @@ import warnings
from pydantic import (
UUID4,
BaseModel,
BeforeValidator,
Field,
PrivateAttr,
field_validator,
@@ -35,7 +32,7 @@ from pydantic import (
from pydantic_core import PydanticCustomError
from typing_extensions import Self
from crewai.agents.agent_builder.base_agent import BaseAgent, _resolve_agent
from crewai.agents.agent_builder.base_agent import BaseAgent
from crewai.context import reset_current_task_id, set_current_task_id
from crewai.core.providers.content_processor import process_content
from crewai.events.event_bus import crewai_event_bus
@@ -132,10 +129,9 @@ class Task(BaseModel):
callback: SerializableCallable | None = Field(
description="Callback to be executed after the task is completed.", default=None
)
agent: Annotated[
BaseAgent | None,
BeforeValidator(_resolve_agent),
] = Field(description="Agent responsible for execution the task.", default=None)
agent: BaseAgent | None = Field(
description="Agent responsible for execution the task.", default=None
)
context: list[Task] | None | _NotSpecified = Field(
description="Other tasks that will have their output used as context for this task.",
default=NOT_SPECIFIED,
@@ -396,12 +392,11 @@ class Task(BaseModel):
@field_validator("id", mode="before")
@classmethod
def _deny_user_set_id(cls, v: UUID4 | None, info: Any) -> UUID4 | None:
if v and not (info.context or {}).get("from_checkpoint"):
def _deny_user_set_id(cls, v: UUID4 | None) -> None:
if v:
raise PydanticCustomError(
"may_not_set_field", "This field is not to be set by the user.", {}
)
return v
@field_validator("input_files", mode="before")
@classmethod
@@ -1002,7 +997,7 @@ Follow these guidelines:
self.delegations += 1
def copy( # type: ignore
self, agents: Sequence[BaseAgent], task_mapping: dict[str, Task]
self, agents: list[BaseAgent], task_mapping: dict[str, Task]
) -> Task:
"""Creates a deep copy of the Task while preserving its original class type.

View File

@@ -8,7 +8,6 @@ from pydantic import Field
from crewai.task import Task
from crewai.tasks.output_format import OutputFormat
from crewai.tasks.task_output import TaskOutput
from crewai.types.callback import SerializableCallable
class ConditionalTask(Task):
@@ -25,7 +24,7 @@ class ConditionalTask(Task):
- Cannot be the first task since it needs context from the previous task
"""
condition: SerializableCallable | None = Field(
condition: Callable[[TaskOutput], bool] | None = Field(
default=None,
description="Function that determines whether the task should be executed based on previous task output.",
)
@@ -52,7 +51,7 @@ class ConditionalTask(Task):
"""
if self.condition is None:
raise ValueError("No condition function set for conditional task")
return bool(self.condition(context))
return self.condition(context)
def get_skipped_task_output(self) -> TaskOutput:
"""Generate a TaskOutput for when the conditional task is skipped.

View File

@@ -43,9 +43,7 @@ class TaskOutput(BaseModel):
output_format: OutputFormat = Field(
description="Output format of the task", default=OutputFormat.RAW
)
messages: list[LLMMessage] = Field(
description="Messages of the task", default_factory=list
)
messages: list[LLMMessage] = Field(description="Messages of the task", default=[])
@model_validator(mode="after")
def set_summary(self) -> TaskOutput:

View File

@@ -1040,20 +1040,3 @@ class Telemetry:
close_span(span)
self._safe_telemetry_operation(_operation)
def feature_usage_span(self, feature: str) -> None:
"""Records that a feature was used. One span = one count.
Args:
feature: Feature identifier, e.g. "planning:creation",
"mcp:connection", "a2a:delegation".
"""
def _operation() -> None:
tracer = trace.get_tracer("crewai.telemetry")
span = tracer.start_span("Feature Usage")
self._add_attribute(span, "crewai_version", version("crewai"))
self._add_attribute(span, "feature", feature)
close_span(span)
self._safe_telemetry_operation(_operation)

View File

@@ -1,6 +1,5 @@
from __future__ import annotations
from collections.abc import Sequence
from typing import TYPE_CHECKING
from crewai.tools.agent_tools.ask_question_tool import AskQuestionTool
@@ -17,7 +16,7 @@ if TYPE_CHECKING:
class AgentTools:
"""Manager class for agent-related tools"""
def __init__(self, agents: Sequence[BaseAgent], i18n: I18N | None = None) -> None:
def __init__(self, agents: list[BaseAgent], i18n: I18N | None = None) -> None:
self.agents = agents
self.i18n = i18n if i18n is not None else get_i18n()

View File

@@ -318,8 +318,6 @@ class ToolUsage:
if self.task:
self.task.increment_delegations(coworker)
fingerprint_config = self._build_fingerprint_config()
if calling.arguments:
try:
acceptable_args = tool.args_schema.model_json_schema()[
@@ -330,16 +328,15 @@ class ToolUsage:
for k, v in calling.arguments.items()
if k in acceptable_args
}
result = await tool.ainvoke(
input=arguments, config=fingerprint_config
)
arguments = self._add_fingerprint_metadata(arguments)
result = await tool.ainvoke(input=arguments)
except Exception:
arguments = calling.arguments
result = await tool.ainvoke(
input=arguments, config=fingerprint_config
)
arguments = self._add_fingerprint_metadata(arguments)
result = await tool.ainvoke(input=arguments)
else:
result = await tool.ainvoke(input={}, config=fingerprint_config)
arguments = self._add_fingerprint_metadata({})
result = await tool.ainvoke(input=arguments)
if self.tools_handler:
should_cache = True
@@ -553,8 +550,6 @@ class ToolUsage:
if self.task:
self.task.increment_delegations(coworker)
fingerprint_config = self._build_fingerprint_config()
if calling.arguments:
try:
acceptable_args = tool.args_schema.model_json_schema()[
@@ -565,16 +560,15 @@ class ToolUsage:
for k, v in calling.arguments.items()
if k in acceptable_args
}
result = tool.invoke(
input=arguments, config=fingerprint_config
)
arguments = self._add_fingerprint_metadata(arguments)
result = tool.invoke(input=arguments)
except Exception:
arguments = calling.arguments
result = tool.invoke(
input=arguments, config=fingerprint_config
)
arguments = self._add_fingerprint_metadata(arguments)
result = tool.invoke(input=arguments)
else:
result = tool.invoke(input={}, config=fingerprint_config)
arguments = self._add_fingerprint_metadata({})
result = tool.invoke(input=arguments)
if self.tools_handler:
should_cache = True
@@ -1014,16 +1008,23 @@ class ToolUsage:
return event_data
def _build_fingerprint_config(self) -> dict[str, Any]:
"""Build fingerprint metadata as a config dict for tool invocation.
def _add_fingerprint_metadata(self, arguments: dict[str, Any]) -> dict[str, Any]:
"""Add fingerprint metadata to tool arguments if available.
Returns the fingerprint data in a config dict rather than injecting it
into tool arguments, so it doesn't conflict with strict tool schemas.
Args:
arguments: The original tool arguments
Returns:
Config dictionary with security_context metadata.
Updated arguments dictionary with fingerprint metadata
"""
security_context: dict[str, Any] = {}
# Create a shallow copy to avoid modifying the original
arguments = arguments.copy()
# Add security metadata under a designated key
if "security_context" not in arguments:
arguments["security_context"] = {}
security_context = arguments["security_context"]
# Add agent fingerprint if available
if self.agent and hasattr(self.agent, "security_config"):
@@ -1047,4 +1048,4 @@ class ToolUsage:
except AttributeError:
pass
return {"security_context": security_context} if security_context else {}
return arguments

View File

@@ -1,7 +1,5 @@
from typing import Annotated, Final
from pydantic_core import CoreSchema
from crewai.utilities.printer import PrinterColor
@@ -38,25 +36,6 @@ class _NotSpecified:
def __repr__(self) -> str:
return "NOT_SPECIFIED"
@classmethod
def __get_pydantic_core_schema__(
cls, _source_type: object, _handler: object
) -> CoreSchema:
from pydantic_core import core_schema
def _validate(v: object) -> _NotSpecified:
if isinstance(v, _NotSpecified) or v == "NOT_SPECIFIED":
return NOT_SPECIFIED
raise ValueError(f"Expected NOT_SPECIFIED sentinel, got {type(v).__name__}")
return core_schema.no_info_plain_validator_function(
_validate,
serialization=core_schema.plain_serializer_function_ser_schema(
lambda v: "NOT_SPECIFIED",
info_arg=False,
),
)
NOT_SPECIFIED: Final[
Annotated[

View File

@@ -623,7 +623,7 @@ def create_model_from_schema( # type: ignore[no-any-unimported]
for name, prop in (json_schema.get("properties", {}) or {}).items()
}
effective_config = __config__ or ConfigDict(extra="forbid")
effective_config = __config__ or ConfigDict(extra="ignore")
return create_model_base(
effective_name,

View File

@@ -882,3 +882,129 @@ class TestEndToEndMCPSchema:
)
assert obj.filters.date_from == datetime.date(2025, 1, 1)
assert obj.filters.categories == ["news", "tech"]
class TestExtraFieldsIgnored:
"""Regression tests for OSS-9: security_context injection causing
extra_forbidden errors on MCP and integration tool schemas.
When the framework injects metadata like security_context into tool call
arguments, dynamically-created Pydantic models must ignore (not reject)
extra fields so that tool execution is not blocked.
"""
SIMPLE_TOOL_SCHEMA: dict[str, Any] = {
"type": "object",
"title": "ExecuteSqlSchema",
"properties": {
"query": {
"type": "string",
"description": "The SQL query to execute.",
},
},
"required": ["query"],
}
OUTLOOK_TOOL_SCHEMA: dict[str, Any] = {
"type": "object",
"title": "MicrosoftOutlookSendEmailSchema",
"properties": {
"to_recipients": {
"type": "array",
"items": {"type": "string"},
"description": "Array of recipient email addresses.",
},
"subject": {
"type": "string",
"description": "Email subject line.",
},
"body": {
"type": "string",
"description": "Email body content.",
},
},
"required": ["to_recipients", "subject", "body"],
}
SECURITY_CONTEXT_PAYLOAD: dict[str, Any] = {
"agent_fingerprint": {
"user_id": "test-user-123",
"session_id": "test-session-456",
"metadata": {},
},
}
def test_mcp_tool_schema_ignores_security_context(self) -> None:
"""Reproduces OSS-9 Case 1: Databricks MCP execute_sql fails when
security_context is injected into tool args."""
Model = create_model_from_schema(self.SIMPLE_TOOL_SCHEMA)
# This previously raised: Extra inputs are not permitted
# [type=extra_forbidden, input_value={'agent_fingerprint': ...}]
obj = Model.model_validate(
{
"query": "SELECT * FROM my_table",
"security_context": self.SECURITY_CONTEXT_PAYLOAD,
}
)
assert obj.query == "SELECT * FROM my_table"
# security_context should be silently dropped, not present on the model
assert not hasattr(obj, "security_context")
def test_integration_tool_schema_ignores_security_context(self) -> None:
"""Reproduces OSS-9 Case 2: Microsoft Outlook send_email fails when
security_context is injected into tool args."""
Model = create_model_from_schema(self.OUTLOOK_TOOL_SCHEMA)
obj = Model.model_validate(
{
"to_recipients": ["user@example.com"],
"subject": "Test",
"body": "Hello",
"security_context": self.SECURITY_CONTEXT_PAYLOAD,
}
)
assert obj.to_recipients == ["user@example.com"]
assert obj.subject == "Test"
assert not hasattr(obj, "security_context")
def test_arbitrary_extra_fields_ignored(self) -> None:
"""Any unexpected extra field should be silently ignored, not just
security_context."""
Model = create_model_from_schema(self.SIMPLE_TOOL_SCHEMA)
obj = Model.model_validate(
{
"query": "SELECT 1",
"some_unknown_field": "should be dropped",
"another_extra": 42,
}
)
assert obj.query == "SELECT 1"
assert not hasattr(obj, "some_unknown_field")
assert not hasattr(obj, "another_extra")
def test_required_fields_still_enforced(self) -> None:
"""Changing to extra=ignore must NOT weaken required field validation."""
Model = create_model_from_schema(self.SIMPLE_TOOL_SCHEMA)
with pytest.raises(Exception):
Model.model_validate({"security_context": self.SECURITY_CONTEXT_PAYLOAD})
def test_type_validation_still_enforced(self) -> None:
"""Changing to extra=ignore must NOT weaken type validation."""
Model = create_model_from_schema(self.SIMPLE_TOOL_SCHEMA)
with pytest.raises(Exception):
Model.model_validate({"query": 12345}) # should be string
def test_explicit_extra_forbid_still_works(self) -> None:
"""Callers can still opt into extra=forbid via __config__."""
from pydantic import ConfigDict
Model = create_model_from_schema(
self.SIMPLE_TOOL_SCHEMA,
__config__=ConfigDict(extra="forbid"),
)
with pytest.raises(Exception):
Model.model_validate(
{
"query": "SELECT 1",
"security_context": self.SECURITY_CONTEXT_PAYLOAD,
}
)