בניתי שרת MCP ציבורי מעל קטלוגי ההטבות של חמישה מועדונים — בהצדעה, חבר, ישראכרט, max ו-Cal — 22,877 הטבות בקטלוג אחד, שאפשר לשאול עליו בעברית חופשית. בניתי אותו כי חלק מהקטלוגים האלה יושבים מאחורי מסע התחברות מפרך, ואחד מהם גם מאחורי OTP ב-SMS. הוא יצא מעניין כי זה המקרה הכי נקי שנתקלתי בו שבו MCP עדיין מצדיק את עצמו.
הבעיה היא לא ההטבות, היא החיכוך
לא ברור לי למה די הרבה מועדוני צרכנות שמים את קטלוג ההטבות שלהם מאחורי אוטנטיקציה. זה באמת מונע ממשתמשים לנצל הטבות שהם כבר משלמים עליהן — לא כי ההטבה לא שווה, אלא כי הם לא יודעים שהיא קיימת, והדרך לגלות אותה ארוכה מדי.
הרבה כבר ניסו לפתור את זה. ההערכה שלי היא שהם נסגרו משילוב של חוסר עניין ציבורי וחוסר רצון של חברות האשראי לשתף פעולה. יש את אפליקציית FID, אבל בעיניי גם היא לא אינטואיטיבית מספיק לחיפוש, ובעיקר — היא דורשת להוריד אפליקציה לטלפון. למה לא web?
מתי MCP עדיין הגיוני
MCP נכנס לחיינו כרוח סערה, ואז יצא מהאופנה אחרי שכלי החיפוש החופשי המובנים השתפרו. אם המודל יכול פשוט לגלוש לאתר ולקרוא — למה להתקין שרת?
שלושה תנאים, ולדעתי צריך לפחות אחד מהם כדי להצדיק את העבודה:
- המידע נמצא מאחורי חומות. אף כלי חיפוש לא יראה קטלוג שדורש התחברות.
- הוא מבולגן ודורש ידע כדי לקרוא אותו. "20% הנחה" זה לא אותו דבר כשמדובר בזיכוי לכרטיס נטען, בהנחה במעמד החיוב או בהנחה בקופה.
- יש יותר מדי ממנו. 22,877 שורות לא נכנסות לקונטקסט של מודל, ולא צריך שייכנסו.
המקרה הזה מסמן את שלושתם. סעיף 3 הוא אובר-קיל כאן, אבל הוא מה שגורם לזה לעבוד: המידע מסונן דטרמיניסטית לפני שהוא מגיע למודל, והתנאים המלאים נשלפים רק להטבות שכבר נמצאו רלוונטיות.
מה יש בפנים
חמישה ארגונים, תשע קבוצות מקור. הקבוצות נפרדות כי הן באמת מתנהגות אחרת: הכרטיס הכחול של חבר הוא רשת בתי עסק, הארנק הדיגיטלי של בהצדעה הוא תקציב טעינה חודשי, ושובר הוא מוצר שקונים מראש.
| מועדון | קבוצות | הטבות |
|---|---|---|
| בהצדעה | שוברים, ארנק דיגיטלי | 16,566 |
| חבר | כרטיס שחור, צהוב, כחול, שוברים | 4,101 |
| Cal | חנות החוויות | 1,208 |
| max | הטבות max | 682 |
| ישראכרט | הטבות ישראכרט | 320 |
הכול נשמר ב-SQLite אחד עם אינדקס FTS5 מעל שם, קטגוריה ועיר. קטלוג ציבורי בלבד — יתרות, טעינות חודשיות וארבע ספרות אחרונות של כרטיס לא נשמרות בכלל, ויש בדיקת יחידה שמקבעת את זה.
האיסוף היה החלק המלוכלך
אף מקור לא נאסף ב-curl. כולם חוסמים בקשות שלא מגיעות מדפדפן אמיתי, ושניים מהם — בהצדעה וחבר — דרשו גם דפדפן מחובר. כל אחד היה שונה:
- בהצדעה — API פנימי שדוחה בקשות בלי כותרת
OrganizationId(ה-SPA שולח גםnative). אין bearer token; האימות הוא עוגיות HttpOnly, וההתחברות היא תעודת זהות ואז OTP ב-SMS. הקטלוג הוא עץ שצריך לטייל בו, והשרת מגביל קצב — אחרי בערך 1,500 צמתים ההשהיה מזנקת מכ-0.6 שניות לכ-3 שניות לבקשה, אז סריקה מלאה חייבת להיות אינקרמנטלית. - חבר — הכרטיס הכחול הוא קובץ JSON אחד למשתמש מחובר. הכרטיס הצהוב פשוט חושף את כל המערך ב-
window.data. רשת האשראי, לעומת זאת, היא ממשק WebForms בלי דאטהסט מרוכז, ושם צריך לעבור עמוד-עמוד. - ישראכרט — קטלוג "ההטבות לכולם" כולו מוטמע כ-JSON בתוך ה-HTML של דף הבית. פנייה אחת מדפדפן היא כל האיסוף. אין צורך בהתחברות, אבל Cloudflare חוסם כל בקשה שלא מגיעה מדפדפן.
- max — API פנימי מעל אפליקציית Angular. הפרמטרים
clubו-regionמקבלים את המחרוזת המילוליתundefined; אם משמיטים אותם השרת מחזיר מערך ריק בלי שום שגיאה. זה עלה לי סבב שלם. - Cal — מאחורי מנגנון אנטי-בוט. אריחי הרשימה הם תמונות רקע בלי טקסט, אז את השמות והמחירים היה צריך לשלוף מדפי המוצר עצמם.
זה החלק שלא נראה בדמו, והוא רוב העבודה.
החלק הדטרמיניסטי
כל אחת מ-22,877 השורות מקודדת ל-embedding עם Titan v2 ב-512 מימדים. התוצאה היא מערך numpy יחיד בגודל 22,877 על 512 שיושב על הדיסק. חיפוש סמנטי הוא כפל מטריצות אחד ומיון — ברוט-פורס מיידי בסדר הגודל הזה, בלי שום vector DB.
מה שחשוב הוא מה לא קורה: הקטלוג לא נכנס לקונטקסט. חיפוש מחזיר לכל היותר 50 שורות רזות, והתנאים המלאים — האותיות הקטנות — נשלפים בקריאה נפרדת, לפי מזהה של הטבה ספציפית. המודל מקבל את האותיות הקטנות רק אחרי שכבר הוחלט שההטבה רלוונטית.
מה שהקטלוג הזה עוד לא יודע
תנאים ברמת ההטבה קיימים כרגע רק לחבר. נאספו 1,531 עמודים ב-28 ביוני 2026, אבל רק 758 מהם — רשת האשראי — מתחברים בפועל לשורות בקטלוג ומחזירים תנאים, ובכ-97% מהם מנגנון הזיכוי בכלל לא מופיע בעמוד, כי הוא כלל ברמת התוכנית ולא ברמת ההטבה הבודדת. לבהצדעה אין עדיין תנאים ברמת ההטבה, כי הם יושבים מאחורי אותו OTP.
יש גם חורים בקטלוג עצמו. ההנחות במעמד החיוב של max לא נתפסו — הנקודה הזו מחזירה אפס בלי התחברות. השדות של ישראכרט שנפתחים רק למשתמש מחובר ריקים. וב-Cal הקטגוריה שמותנית בזכאות ריקה גם היא. גם הרעננות לא אחידה: חלק מהקטלוג מיולי 2026 וחלק מיוני. מחיר או תנאי שמופיעים בתשובה הם נקודת פתיחה לבדיקה באתר הרשמי של המועדון, לא תחליף לה.
השרת עצמו
שישה כלים לקריאה בלבד: list_sources, search_promotions, list_categories, semantic_search, get_club_terms, get_offer_terms. התיאורים והסכמות מגיעים מאותה הגדרה שממנה מקבל אותם הסוכן שמאחורי הצ׳אט — מקור אמת אחד, ולא שני עותקים שמתפצלים עם הזמן.
הטרנספורט הוא streamable HTTP חסר-מצב עם תשובות JSON — בלי state בין קריאות, ותשובה שכל לקוח HTTP פשוט יודע לקרוא. אין אוטנטיקציה: זה קטלוג ציבורי וכלים לקריאה בלבד.
הכלי היחיד שכותב, report_issue, מוחרג פעמיים: גם מרשימת הכלים וגם בזמן הקריאה עצמה. זה לא כפל מיותר — ה-SDK מעביר לשרת גם שמות כלים שלא הופיעו ברשימה, ולכן הסינון ברשימה הוא נימוס והשומר בזמן הקריאה הוא החסם האמיתי. יש על זה טסט.
הכול רץ על מכונת EC2 אחת מסוג t4g.small ב-eu-central-1, בערך 14 דולר לחודש, עם nginx מול uvicorn וגישה ל-Bedrock דרך IAM role במקום מפתחות סטטיים. מגביל קיבולת של שמונה משימות במקביל דואג לכך שתנועת MCP לא תרעיב את הצ׳אט.
איך מתקינים
מ-Claude Code, שורה אחת:
claude mcp add --scope user --transport http yesh-et-ze https://yesh-et-ze.hopala.io/mcp
מהדפדפן: בהגדרות, Connectors → Add → Add custom connector, ואז מדביקים את אותו URL.
יש גם צ׳אט, והוא סגור
מעל אותו קטלוג בניתי גם ממשק צ׳אט בעברית, עם בורר מקורות ומד שמראה את העלות המשוערת של השיחה הנוכחית בשקלים. הוא סגור כרגע מאחורי סיסמה; המשטח הציבורי הוא ה-MCP.
מה אני לוקח מזה
MCP לא מת ולא ניצח. הוא כלי עם תנאי הצדקה ברורים: מידע מאחורי חומה, שדורש ידע כדי לקרוא אותו נכון, ושיש ממנו יותר מדי מכדי פשוט להדביק. רוב המידע בעולם לא עומד בשלושת התנאים. חלק לא קטן מהמידע שיושב בתוך ארגון דווקא כן.
אם יש לכם מערכת פנימית שאף אחד לא נכנס אליה כי המסע אליה ארוך מדי, ככה אני עובד — מתחילים במיפוי של מה באמת נעול ומה רק נראה ככה.