Skip to content

fix(mcp): תקרת גודל לגוף בקשה והגבלת קצב לפי זהות, עם סירוב שנוקב בסיבתו (#3431) - #3435

Closed
amirbiron wants to merge 1 commit into
mainfrom
claude/gracious-einstein-sevk8p-3431
Closed

amirbiron wants to merge 1 commit into
mainfrom
claude/gracious-einstein-sevk8p-3431

Conversation

@amirbiron

@amirbiron amirbiron commented Sep 20, 2026 •

Copy link
Copy Markdown
Owner

✨ תיאור קצר

📦 שינויים עיקריים

  • קוד (Backend)
  • בוט טלגרם
  • מסד נתונים/מיגרציות
  • תיעוד (docs/)
  • DevOps/CI/CD

פירוט נקודות (רשימת תבליטים):

  • תקרת גוף — BodySizeLimitMiddleware, מידלוור ASGI טהור. Content-Length שמצהיר על יותר מהתקרה נדחה לפני שנקרא בית אחד; כותרת חסרה או מכזבת (למשל chunked) נתפסת בספירה של הודעות http.request שבאמת מגיעות. חיץ ולא חריגה מתוך receive, כי _handle_post_request של ה-SDK (mcp 1.28.1) עוטף את קריאת הגוף ב-try שתופס Exception ומחזיר שגיאת JSON-RPC בלי סיבה. הסירוב: {"error": "body_too_large", "max_bytes": ..., "content_length" | "received_bytes": ...}, באותה צורה של ה-401 של האימות. נוסף לפני PATAuthMiddleware בכוונה (add_middleware מכניס בראש המחסנית, Starlette 1.6.0), ולכן במצב PAT בקשה בלי טוקן היא 401 גם כשהיא ענקית; במצב OAuth התקרה היא השכבה החיצונית ברמת האפליקציה.
  • קצב לפי זהות — ב-AdminAwareFastMCP.call_tool. זו המתודה שה-SDK רושם כמטפל של tools/call (FastMCP._setup_handlers, self._mcp_server.call_tool(validate_input=False)(self.call_tool)), כלומר נקודה אחת שכל קריאת כלי עוברת בה — גוף סינכרוני לפני שהוא נמסר לחוט, גוף אסינכרוני לפני שהוא רץ. למה לא במידלוור: הזהות קיימת ב-request.state רק במצב PAT; במצב OAuth (הייצור) PATAuthMiddleware אינו מותקן, ורק current_user_id על הקונטקסט של הקריאה רואה אותה בשני המצבים. מחוץ לבקשה request_context מרים LookupError — אין את מי לחייב, והטסטים שקוראים לכלים ישירות ממשיכים כמו היום. הסירוב הוא תשובת כלי רגילה דרך convert_result של הכלי: {"ok": false, "error": "rate_limited", "limit_per_minute": 60, "retry_after_seconds": N}.
  • למה הגרסה הראשונה של המימוש נזרקה. ניסיון קודם עטף כל כלי ב-add_tool; זה הפיל שישה טסטים קיימים שקוראים fn.__code__.co_name על הפונקציות הרשומות ושניים שקוראים לכלים מחוץ לבקשה. ההעברה ל-call_tool היא הפתרון השורשי: הפונקציות הרשומות נשארות בדיוק מה ש-add_tool בנה, וההכרעה יושבת במקום היחיד שהפרוטוקול עצמו עובר בו.
  • המספרים, ומאיפה (מהתגובה שלי באישו, 2026-09-20). גוף: הארגומנט הלגיטימי הגדול ביותר הוא content של codekeeper_save_file, שחסום ב-MAX_CODE_SIZE = 100,000 תווים; לקוח שמקודד JSON עם ensure_ascii הופך כל תו עברי לשישה בתים, כלומר כ-600KB במקרה הקיצוני — 1MiB משאיר לזה מרווח, ורצפה של 65,536 כדי שטעות תצורה לא תהפוך להפסקת שירות. קצב: הבקשה הציבורית היקרה ביותר היום היא עמוד RST של 500KB — 0.24 שניות מעבד עם תקרת הסקשנים (0.035 לעמוד הצפוף האמיתי); במסלול ה-Markdown המסמך הצפוף האמיתי 0.47 והצורה העוינת 2.3; המכסה 0.5 מעבד = 30 שניות-מעבד בדקה. שישים קריאות של המסמך הצפוף האמיתי הן 28 שניות: זהות אחת יכולה לכל היותר למלא דקת מעבד אחת משלה; קריאה רגילה (10–50ms) הופכת 60 בדקה לאחוז עד שלושה מהמכסה.
  • המנוע הוא rate_limiter.RateLimiter הקיים של הבוט (R6 — לא עותק שני של חלון מתגלגל). שינוי בו: ניקוי החלון אוחד ל-_live_entries (היה משוכפל בשתי מתודות), ונוספה seconds_until_allowed בשביל retry_after_seconds. ה-API הקיים לא השתנה; הסוויטות של הבוט ושל main.py שמשתמשות בו ירוקות.
  • סירוב על קצב נרשם פעם אחת לזהות לחלון (WARNING), כדי שסוכן שממשיך לדפוק לא יהפוך את הלוג לתור; סירוב על גודל נרשם עם הגודל והנתיב בלבד — לא הגוף ולא הטוקן (K13).
  • כיוון: MCP_MAX_REQUEST_BYTES ו-MCP_RATE_LIMIT_PER_MINUTE, נקראים ב-create_app דרך limit_from_env (בכניסה לשירות, לא בזמן ייבוא — import-time-side-effects). ערך שאינו מספר → ברירת המחדל עם WARNING; מתחת למינימום → המינימום עם WARNING; 0 לקצב → כיבוי מפורש עם WARNING (K12 §3: בלי ברירת מחדל שקטה שמרחיבה). בשום מסלול ערך פגום אינו הופך לגבול רחב יותר. נרשמו ב-services/config_inspector_service.py, ב-docs/environment-variables.rst, בטבלת הגבולות ובסעיף חדש "גבולות הבקשה" ב-docs/mcp-server.rst, וב-docs/whats-new.rst.
  • שורת עלייה חדשה: mcp request limits: body <= 1048576 bytes (413 body_too_large), tool calls <= 60 per identity per minute (rate_limited); /healthz sits outside both.
  • שתי שורות ריקות לפני _build_docs_path_doc ב-server.py (E302 שנשאר מ-feat(mcp): docs_get_section קורא גם Markdown, לפי מדיניות נתיבים לכל ריפו #3428; לא ב-selection של CI).

🧪 בדיקות

  • tests/test_mcp_limits.py (חדש, 18 טסטים): המידלוור מול הודעות ASGI ממש (Content-Length מעל התקרה נדחה בלי לקרוא בית; גוף chunked מעל התקרה נדחה והאפליקציה לא רצה; בדיוק התקרה עובר ובית אחד מעל לא; http.disconnect עובר הלאה כמות שהוא; scope שאינו http עובר ישר); האפליקציה האמיתית דרך TestClient (401 לפני 413, ו-413 נוקב בסיבתו; 100 דגימות /healthz תחת מגבלה של קריאה אחת בדקה — כולן 200); המגביל דרך call_tool של ה-FastMCP האמיתי בתוך request_ctx מדומה (סירוב עם הסיבה וההמתנה; תקציב נפרד לכל זהות; מחוץ לבקשה לא נספר כלום; קריאה שנדחתה לא מריצה את הגוף; כלי אסינכרוני מוגבל גם הוא; 0 מכבה; בלי זהות הגוף הוא שמסרב); לוג אחד לזהות לחלון; limit_from_env לעולם אינו מרחיב; seconds_until_allowed.
  • על הקוד הישן (worktree מנותק על origin/main = 3b8628f4): קובץ הטסטים נופל בקולקציה (ImportError על mcp_server.limits) — אף טסט חדש אינו עובר בלי השינוי.
  • סוויטות קיימות: 660 טסטים ב-test_mcp_to_thread, test_mcp_logging_visible, test_mcp_server_build, test_mcp_analytics_privacy, test_mcp_docs_handlers, test_mcp_auth_middleware, test_mcp_primer, test_mcp_outline, test_mcp_repo_backend, test_rate_limiter_basic, test_main_rate_limit_gate, test_bot_handlers_rate_limit_command, test_config_definitions_coverage — ירוקים; ועוד 175 ב-test_bot_rate_limiter, test_handlers_cleanup, test_main_shadow_limits, test_query_profiler_service, test_config_inspector_empty_sensitive, test_config_inspector_service — ירוקים. הששה שנפלו תחת הגרסה הראשונה (העטיפה ב-add_tool) עוברים.
  • uvicorn אמיתי (תת-תהליך, האפליקציה האמיתית עם דמויות, תקרה 1000 בתים ומגבלה של קריאה אחת בדקה): גוף גדול בלי טוקן → 401; עם טוקן ו-Content-Length → 413 {"error":"body_too_large","max_bytes":1000,"content_length":2002}; אותו גוף chunked בלי כותרת → 413 עם received_bytes: 2002; ping קטן → 200; 120 דגימות /healthz → כולן 200; בלוג uvicorn: שורת mcp request limits ושתי שורות mcp request refused.
  • לקוח ה-MCP של ה-SDK מקצה לקצה (streamablehttp_client + ClientSession מול אותו uvicorn): initialize ו-tools/list (22 כלים) לא נספרים; tools/call ראשון → {"found": false}; השני והשלישי → {"ok": false, "error": "rate_limited", "limit_per_minute": 1, "retry_after_seconds": 60} עם isError: false; tools/list אחרי הסירוב עדיין עונה; בלוג: mcp tool rate limit: identity 7 פעם אחת.
  • flake8 עם ה-selection של CI (E9,F63,F7,F82) — 0; flake8 מלא (max-line-length=127) על הקבצים ששיניתי — נקי (ב-config_inspector_service.py יש E501 קיימים שאינם שלי). docutils על שלושת עמודי התיעוד — 0 אזהרות (בלי בנייה מלאה, לפי CLAUDE.md).
  • לא אימתתי: את מצב OAuth מקצה לקצה (דורש ספק OAuth וטוקן אמיתיים; המסלול נשען על current_user_id שכבר עובד בו היום, ועל get_access_token() של ה-SDK), ואת שורת העלייה בשירות בייצור — היא תיראה אחרי הדיפלוי.
  • Unit
  • Integration
  • Manual

🧪 בדיקות נדרשות ב‑PR

  • 🔍 Code Quality & Security
  • Unit Tests (3.11)
  • Unit Tests (3.12)

📝 סוג שינוי

  • feat: פיצ'ר חדש
  • fix: תיקון באג
  • docs: שינוי תיעוד בלבד
  • refactor: שינוי קוד ללא שינוי התנהגות
  • perf: שיפור ביצועים
  • chore/ci: תשתית/CI
  • breaking change: שינוי שובר תאימות

✅ צ'קליסט

  • הקוד עוקב אחרי הסגנון (Black/isort/flake8/mypy)
  • בדיקות רצות ועוברות
  • תיעוד עודכן (README/Docs)
  • אם נוספו ג'ובים חדשים (Background Jobs) – לא נוספו
  • אם נוספו/שונו משתני סביבה – עודכן docs/environment-variables.rst וגם services/config_inspector_service.py
  • אם נוספו/השתנו טוקנים – לא רלוונטי
  • אין סודות/מפתחות בקוד
  • אין מחיקות מסוכנות/פעולות על root (ראו .cursorrules)
  • הודעת הקומיט תואמת Conventional Commits (ע"פ הטבלה)
  • CHANGELOG עודכן אם נדרש — docs/whats-new.rst עודכן
  • כל ה‑Required Checks לעיל ירוקים — ייבדק ב-CI
  • צילום/וידאו UI מצורף אם רלוונטי — לא רלוונטי
  • עיינתי במסמכי אתר התיעוד — נתיב: AI-MAP.md, docs/mcp-server.rst (טבלת הגבולות, "מודל הריצה של הכלים", mcp-analytics), docs/environment-variables.rst (בלוק ה-MCP), docs/doc-authoring.rst, docs/versioning-stable-anchors.rst | המשפט: "השרת של ה-MCP רץ במופע אחד" (numInstances: 1) — הסיבה שמגביל בזיכרון מספיק בלי Redis
  • לא נדרש עיון — התנאי התקיים (משתני סביבה + התנהגות מתועדת)

דפוסי באגים שנקראו ומה שקבעו בקוד: bugbot-rules/blanket-policy-silent-block.md §7–8 — הפטור ל-/healthz מבני (המגביל יושב מתחת לנתיבי ה-HTTP), ולא רשימת נתיבים; bugbot-rules/silent-truncation-at-sink.md — סירוב נוקב בסיבתו במקום חיתוך שקט; CORE-PATTERNS.md U3 + bugbot-rules/external-input-isinstance.md — Content-Length וערכי ENV נבדקים כקלט חיצוני (isdigit, int תחת try צר); CRITICAL-PATTERNS.md K12 §3 — 0 מכבה רק במפורש ועם WARNING, ערך פגום לעולם אינו מרחיב; K13 — הלוג נושא גדלים ונתיב, לא גוף ולא טוקן; K11 — admit מחזיר ערך שנבדק, check_rate_limit מחזיר False ולא זורק; bugbot-rules/import-time-side-effects.md — ENV נקרא ב-create_app, המגביל נבנה ב-build_mcp; bugbot-rules/silent-fallback-to-worse-path.md — ה-LookupError מחוץ לבקשה מתועד כתנאי מבני ולא כ-fallback שקט; RECURRING-PATTERNS.md R6 — RateLimiter הקיים, _live_entries אחד; claude-md-snippets/testing.md — הטסטים הורצו על הקוד הישן ונפלו.

🧩 השפעות/סיכונים

  • התנהגות חדשה בייצור: לקוח שמשלח גוף מעל 1MiB יקבל 413 במקום שהשרת יפענח אותו; זהות שחוצה 60 קריאות כלים בדקה תקבל rate_limited כתשובת כלי רגילה (לא שגיאת פרוטוקול) עם הזמן שנותר. סוכן רגיל רחוק מזה בסדר גודל.
  • מה לא השתנה: הכלים עצמם, הסכימות שלהם, initialize/tools/list/זרם ה-SSE, ו-/healthz.
  • מגביל בזיכרון: מספיק כל עוד השירות רץ במופע אחד (numInstances: 1); הרחבה למופעים מרובים תדרוש מנוע משותף — מתועד בסעיף החדש.

🔗 קישורים

🧯 סיכון / החזרה לאחור (Rollback)

  • revert של הקומיט היחיד מחזיר את השרת למצב בלי הגבולות. בלי לגעת בקוד: MCP_RATE_LIMIT_PER_MINUTE=0 מכבה את המגביל (עם WARNING בעלייה), ו-MCP_MAX_REQUEST_BYTES מרים את התקרה.

🤖 Generated with Claude Code

https://claude.ai/code/session_01SfJTSpDAhDr2yhtmpFkwTx


Generated by Claude Code

Review in cubic

Summary by Sourcery

Add configurable MCP request-size and per-identity tool-call limits with explicit rejection reasons while keeping health checks and non-tool protocol operations unaffected.

Bug Fixes:

  • Protect MCP requests from oversized bodies by rejecting payloads above the configured limit with an explanatory 413 response.
  • Limit tool calls per identity and return actionable rate-limit responses without executing rejected tools.

Enhancements:

  • Reuse the existing rate limiter for MCP identity-based quotas, including retry timing and bounded warning logs.
  • Make request limits configurable through environment variables with safe defaults, validation, and explicit disablement for the rate limit.

Documentation:

  • Document the new MCP request-size and tool-rate limits, configuration variables, operational scope, and health-check behavior.

Tests:

  • Add unit and integration coverage for ASGI body enforcement, authentication ordering, health-check exemptions, identity-specific tool quotas, configuration validation, and retry timing.

…בתו (#3431)

עד היום המידלוור היחיד בשרת ה-MCP היה האימות: כל משתמש מאומת יכול היה
לשלוח בקשות בכל גודל ובכל תדירות. שני גבולות חדשים, כל אחד בשכבה שלו,
במודול חדש mcp_server/limits.py:

- גודל הגוף: מידלוור ASGI טהור (BodySizeLimitMiddleware) שמסרב ב-413
  {"error": "body_too_large", "max_bytes": ...} לפני שהטרנספורט של ה-SDK
  קורא ומפענח JSON. Content-Length שמצהיר על יותר מהתקרה נדחה לפני שנקרא
  בית אחד; כותרת חסרה או מכזבת נתפסת בספירה של מה שבאמת מגיע. חיץ ולא
  חריגה מתוך receive, כי _handle_post_request של ה-SDK עוטף את קריאת הגוף
  ב-try שתופס Exception ומחזיר שגיאת JSON-RPC בלי סיבה. ברירת המחדל 1MiB,
  נגזרת מ-MAX_CODE_SIZE (100,000 תווים, עד ~600KB כ-JSON עם ensure_ascii).
- קצב לפי זהות: ב-AdminAwareFastMCP.call_tool, המתודה שה-SDK רושם כמטפל
  של tools/call, כלומר נקודה אחת שכל קריאת כלי עוברת בה בשני מצבי האימות
  (במצב OAuth PATAuthMiddleware אינו מותקן, ורק הקונטקסט של הקריאה רואה את
  הזהות). ההכרעה נופלת לפני שגוף סינכרוני נמסר לחוט ולפני שגוף אסינכרוני
  רץ; קריאה שנדחתה מחזירה תשובת כלי רגילה {"ok": false, "error":
  "rate_limited", "limit_per_minute": ..., "retry_after_seconds": ...}.
  מחוץ לבקשה (LookupError מ-request_context) אין את מי לחייב, ולכן הטסטים
  שקוראים לכלים ישירות ממשיכים כמו היום. 60 בדקה, מהמדידות בתגובה באישו:
  0.24 שניות מעבד לעמוד RST עוין של 500KB, 0.47 למסמך Markdown הצפוף, 2.3
  לצורה העוינת, מול מכסה של 0.5 מעבד (30 שניות-מעבד בדקה).
- הפטור לנתיבי הדופק מבני ולא רשימה (blanket-policy-silent-block §7):
  /healthz אינו קריאת כלי ואין לו גוף, ומקובע בטסט על האפליקציה האמיתית.
- rate_limiter.RateLimiter הקיים של הבוט משמש כמנוע (R6): ניקוי החלון אוחד
  ל-_live_entries, ונוספה seconds_until_allowed בשביל retry_after_seconds.
- כיוון דרך MCP_MAX_REQUEST_BYTES (מינימום 65536) ו-MCP_RATE_LIMIT_PER_MINUTE
  (0 מכבה במפורש עם WARNING), נקראים ב-create_app ולא בזמן ייבוא; ערך פגום
  לעולם אינו מרחיב את הגבול (K12 §3). נרשמו ב-config_inspector_service
  ובתיעוד.

אומת עם uvicorn אמיתי: 401 לפני 413 במצב PAT, 413 על Content-Length ועל
גוף chunked בלי כותרת, 120 דגימות של /healthz תחת מגבלה של קריאה אחת
בדקה כולן 200; ועם לקוח ה-MCP של ה-SDK על Streamable HTTP: הקריאה השנייה
מחזירה rate_limited ו-tools/list אחריה עדיין עונה.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SfJTSpDAhDr2yhtmpFkwTx
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@qodo-code-review

Copy link
Copy Markdown

ⓘ Qodo reviews are paused because your trial has ended. Ask your workspace admin to add credits to resume reviews. Manage billing

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @amirbiron, you've used your own review budget of 250,000 diff characters for the last 7 days.

You can request another review in 6 hours and 29 minutes by commenting @sourcery-ai review. Upgrade to get a review now.

@coderabbitai

coderabbitai Bot commented Sep 20, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

Next included review available in 27 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: 640d4b8c-2c8b-4b87-970d-9641740eeb35

📥 Commits

Reviewing files that changed from the base of the PR and between 1a45c28 and 9795eb0.

📒 Files selected for processing (9)
  • docs/environment-variables.rst
  • docs/mcp-server.rst
  • docs/whats-new.rst
  • mcp_server/app.py
  • mcp_server/limits.py
  • mcp_server/server.py
  • rate_limiter.py
  • services/config_inspector_service.py
  • tests/test_mcp_limits.py

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown
Contributor

🧯 Dangerous deletes guard report

Policy: see .cursorrules — dangerous deletions are blocked unless wrapped safely.

Summary:

  • Flagged findings (blocking): 0
    0
  • Excluded matches (not blocking): 15
  • Total matches (all files): 128

Flagged findings (file:line:snippet):
(none)

Excluded matches (by path pattern)
./webapp/static/js/md_preview.bundle.js.map:4:  "sourcesContent": ["// Markdown-it plugin to render GitHub-style task lists; see\n//\n// https://github.com/blog/1375-task-lists-in-gfm-issues-pulls-comments\n// https://github.com/blog/1825-t … [truncated]
./docs/DOCUMENTATION_GUIDE.md:453:rm -rf _build
./docs/Makefile:24:	rm -rf $(BUILDDIR)
./Dockerfile:42:    rm -rf /var/lib/apt/lists/*
./Dockerfile:121:    rm -rf /var/lib/apt/lists/*
./node_modules/katex/package.json:153:    "build": "rimraf dist/ && mkdirp dist && cp README.md dist && rollup -c --failAfterWarnings && webpack && node update-sri.js package dist/README.md",
./node_modules/katex/src/fonts/Makefile:139:	rm -rf pfa ff otf ttf woff woff2
./node_modules/mermaid/dist/mermaid.js.map:4:  "sourcesContent": ["/**\n* Default values for dimensions\n*/\nconst defaultIconDimensions = Object.freeze({\n\tleft: 0,\n\ttop: 0,\n\twidth: 16,\n\theight: 16\n});\n/**\n* Default values for tr … [truncated]
./node_modules/mermaid/dist/chunks/mermaid.esm/chunk-2M32CCKP.mjs.map:4:  "sourcesContent": ["{\n  \"name\": \"mermaid\",\n  \"version\": \"11.12.0\",\n  \"description\": \"Markdown-ish syntax for generating flowcharts, mindmaps, sequence d … [truncated]
./node_modules/mermaid/dist/chunks/mermaid.esm.min/chunk-4HFYJGYH.mjs.map:4:  "sourcesContent": ["{\n  \"name\": \"mermaid\",\n  \"version\": \"11.12.0\",\n  \"description\": \"Markdown-ish syntax for generating flowcharts, mindmaps, sequen … [truncated]
./node_modules/mermaid/dist/chunks/mermaid.esm.min/chunk-4HFYJGYH.mjs:1:var r={name:"mermaid",version:"11.12.0",description:"Markdown-ish syntax for generating flowcharts, mindmaps, sequence diagrams, class diagrams, gantt charts, git graph … [truncated]
./node_modules/mermaid/dist/chunks/mermaid.core/chunk-KS23V3DP.mjs.map:4:  "sourcesContent": ["{\n  \"name\": \"mermaid\",\n  \"version\": \"11.12.0\",\n  \"description\": \"Markdown-ish syntax for generating flowcharts, mindmaps, sequence  … [truncated]
./node_modules/mermaid/dist/mermaid.min.js:1524:`,"getStyles"),c1e=RQe});var h1e={};dr(h1e,{diagram:()=>NQe});var NQe,f1e=N(()=>{"use strict";$ge();a1e();l1e();u1e();NQe={parser:Fge,db:n1e,renderer:o1e,styles:c1e}});var m1e,g1e=N(()=>{"use  … [truncated]
./node_modules/mermaid/dist/mermaid.min.js.map:4:  "sourcesContent": ["/**\n* Default values for dimensions\n*/\nconst defaultIconDimensions = Object.freeze({\n\tleft: 0,\n\ttop: 0,\n\twidth: 16,\n\theight: 16\n});\n/**\n* Default values fo … [truncated]
./README.md:842:find . -name "__pycache__" -exec rm -rf {} +

@sourcery-ai

sourcery-ai Bot commented Sep 20, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

ה-PR מוסיף לשרת MCP שתי שכבות הגנה: תקרת גוף ASGI של 1MiB שנאכפת לפני פענוח ה-SDK, והגבלת tools/call ל-60 בדקה לכל זהות באמצעות ה-RateLimiter הקיים; התנהגות הסירוב, התצורה, התיעוד והבדיקות עודכנו בהתאם.

Sequence diagram for MCP body-size rejection

sequenceDiagram
    participant Client as MCP client
    participant Limit as BodySizeLimitMiddleware
    participant Auth as Authentication
    participant SDK as MCP transport

    Client->>Limit: HTTP request
    alt Content-Length exceeds max_bytes
        Limit-->>Client: 413 body_too_large
    else Body exceeds max_bytes while receiving
        Limit->>Limit: buffer http.request messages
        Limit-->>Client: 413 body_too_large
    else Body within limit
        Limit->>Auth: replay buffered request
        Auth->>SDK: authenticated request
        SDK-->>Client: MCP response
    end
Loading

Sequence diagram for identity-based tool-call rate limiting

sequenceDiagram
    participant Client as MCP client
    participant Transport as MCP transport
    participant Server as AdminAwareFastMCP
    participant Limiter as ToolRateLimiter
    participant Tool as Tool body

    Client->>Transport: tools/call
    Transport->>Server: call_tool(name, arguments)
    Server->>Server: current_user_id(get_context())
    Server->>Limiter: admit(user_id)
    alt Limit available
        Limiter-->>Server: None
        Server->>Tool: execute tool
        Tool-->>Client: converted tool result
    else Limit exceeded
        Limiter-->>Server: rate_limited and retry_after_seconds
        Server-->>Client: converted refusal result
    end
Loading

File-Level Changes

Change Details Files
הוספת תקרת גודל לגוף בקשות MCP במידלוור ASGI, עם דחייה מוקדמת ומפורטת.
  • דחיית Content-Length חריג לפני קריאת גוף; ספירת גופים זורמים כשאין הצהרה אמינה.
  • החזרת HTTP 413 עם body_too_large, התקרה וגודל הגוף שנמדד.
  • שמירת סדר המידלוור כך שב-PAT אימות 401 קודם ל-413, תוך השארת נתיבי health ללא שינוי.
mcp_server/limits.py
mcp_server/server.py
mcp_server/app.py
tests/test_mcp_limits.py
הוספת הגבלת קצב לקריאות tools/call לפי זהות המשתמש, בנקודת השיגור המשותפת לכלים.
  • שימוש ב-current_user_id מתוך הקונטקסט כדי לתמוך ב-PAT וב-OAuth.
  • החזרת תשובת כלי רגילה עם rate_limited, המכסה וזמן המתנה, לפני הרצת גוף הכלי.
  • שמירה על התנהגות קריאות מחוץ לבקשה ועל תקציב נפרד לכל זהות, כולל כלים אסינכרוניים.
mcp_server/limits.py
mcp_server/server.py
tests/test_mcp_limits.py
הרחבת מנגנון ה-RateLimiter הקיים לחישוב שימוש וזמן המתנה ללא שכפול לוגיקת חלון.
  • איחוד ניקוי הרשומות ל-_live_entries.
  • הוספת seconds_until_allowed עבור retry_after_seconds.
  • שמירת API ההתנהגות הקיים ובדיקת מקרי גבול של חלון הזמן.
rate_limiter.py
tests/test_mcp_limits.py
הוספת תצורה, תיעוד ואבחון תפעולי לגבולות החדשים.
  • קריאת משתני הסביבה ב-create_app עם ברירות מחדל, מינימום, אזהרות וכיבוי מפורש של מגבלת הקצב.
  • רישום הגבולות בשורת העלייה ובשירות בדיקת התצורה.
  • עדכון תיעוד משתני הסביבה, שרת MCP ומה חדש, כולל מגבלת פריסה למופע יחיד.
mcp_server/app.py
mcp_server/server.py
mcp_server/limits.py
services/config_inspector_service.py
docs/environment-variables.rst
docs/mcp-server.rst
docs/whats-new.rst
הוספת כיסוי בדיקות יחידה, אינטגרציה ובדיקות התנהגות על האפליקציה והמימוש האמיתי של FastMCP.
  • בדיקת ASGI מול Content-Length, chunked bodies, גבולות מדויקים, disconnect ו-scopes שאינם HTTP.
  • בדיקת סדר האימות, פטור /healthz, זהויות נפרדות, סינכרוני/אסינכרוני, כיבוי ולוגים.
  • בדיקת parsing בטוח של משתני סביבה ומנגנון זמן ההמתנה.
tests/test_mcp_limits.py

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@github-actions

Copy link
Copy Markdown
Contributor

⏱️ Performance report

(No performance test durations collected. Mark tests with @pytest.mark.performance.)

@github-actions

Copy link
Copy Markdown
Contributor

📖 Documentation Preview

The documentation has been built successfully!

To view locally:

  1. Download the artifacts
  2. Extract the zip file
  3. Open index.html in your browser

@codecov

codecov Bot commented Sep 20, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 94.24460% with 8 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
mcp_server/app.py 0.00% 4 Missing ⚠️
mcp_server/limits.py 96.66% 2 Missing and 1 partial ⚠️
mcp_server/server.py 96.00% 0 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

@amirbiron

Copy link
Copy Markdown
Owner Author

נכנס דרך #3443

@amirbiron amirbiron closed this Sep 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants