בניתי שרת MCP מעל Priority ERP והעליתי אותו לאוויר. זו בקשה שלא מפסיקה לעלות מלקוחות, אז עשיתי אותה על חשבוני וצירפתי אותה לחבילת הבסיס של השירות. מה שהפתיע אותי בעבודה: רוב הזמן לא הלך על הקריאות ל-API, אלא על ההחלטות מה לא לתת לסוכן לעשות.
המסקנה מראש: לחבר סוכן ל-ERP זה לא ״לעטוף את ה-API״. זו עבודה של בניית ממשק לקורא שאין לו שיפוט לגבי תוצאות — לא לגבי מה שהוא קורא, ולא לגבי מה שהוא כותב.
למה זה נהיה מעניין דווקא עכשיו
כתבתי כאן בעבר שבעיניי הגאונות של MCP נחשפה באמת רק לאחרונה, כשיצא Claude Cowork ואחריו התחילו להופיע מוצרים באותה צורה גם אצל המתחרים.
הצ׳אטים הישנים יכולים להיעזר ב-MCP, אבל המשתמשים שלהם העדיפו פשוט לחפש בעצמם. מסתבר שפעולת חיפוש המידע לא כאבה מספיק לרוב היוזרים כדי לאמץ את פלטפורמות ה-AI כמרכז חייהם. ומהצד השני — Claude Code ו-Codex לא באמת צריכים MCP, כי הם והמשתמשים שלהם מסתדרים נהדר עם APIs.
מה שנשאר באמצע זה המשתמש הלא-טכני. הוא לא יכתוב curl ולא יקרא מפרט OData, ובשבילו שרת ה-MCP הוא לא נוחות — הוא כל הממשק. הוא צריך שמישהו ישלוט בסוסים הדוהרים האלה בדמות אייג׳נטים עם הבנה של הממשק ושל ניהול הסיכונים הכרוך בו. זה החלק שאני עושה.
Priority לא נבנתה בשביל סוכנים
בניתי ואימתתי הכול מול usdemo, סביבת ה-sandbox הציבורית של Priority. הנה מה שמצאתי שם, והכול נצפה ולא הונח:
Priority מצהירה על 1,731 entity sets — מסכים. מתוך 166 שבדקתי, 79 החזירו שגיאה, ו-$metadata לא נותן על כך שום אזהרה: מסך חסום נראה זהה בדיוק במבנה למסך עובד. חשוב לומר מיד שזו הגדרה ולא מגבלה של ה-API — אדמין מסמן לכל מסך ״Available for API״, ולקוח בפרודקשן יכול לפתוח כל אחד מ-1,731. מה שזה כן אומר: ״אילו מסכים בכלל פותחים״ היא השאלה הראשונה בכל פרויקט, לא הערת שוליים.
שמות העמודות הם קיצורים אטומים: CUSTNAME, QPRICE, LEXCH. מה שמציל את המצב הוא ש-$metadata נושא 26,438 תוויות שדה בעברית, כ-52.7% מהמאפיינים. המילון הזה הוא הנכס הכי שווה בפרויקט — הוא מה שהופך את המערכת לקריאה גם לסוכן וגם לבן אדם.
ואז מתחילות המגבלות שבאמת מסוכנות, כי הן שקטות:
- כל תשובה נחתכת ב-2000 שורות, ואין
nextLink. חייבים לדפדף ידנית. - השרת מתעלם בשקט מ-
$count. אי אפשר לשאול ״כמה יש״ — מודדים בדפדוף. - השרת מתעלם בשקט מ-
$applyומ-groupby: מחזיר 200 עם שורות רגילות, לא מאוגדות. - פונקציות מחרוזת כמו
containsמחזירות 501. פילטר לפי תחילית צריך להיכתב מחדש כטווח. - שילוב של
$filterעם$expandמחזיר 200 עם גוף קטוע באמצע אובייקט. JSON לא תקין. שחזרתי פעמיים. - שגיאות מגיעות בארבע צורות שונות, אחת מהן XML שנארז כ-JSON.
שימו לב מה קורה כאן. סוכן ששואל ״כמה ספקים יש לנו״ ומקבל ספירה שהיא בעצם גודל העמוד, או סוכן ששולח בקשת אגרגציה ומקבל 200 עם שורות גולמיות — לא יקבל שגיאה. הוא יקבל תשובה, ויגיש אותה בביטחון מלא. כל מגבלה שקטה כזאת היא מקום שבו עטיפה נאיבית של ה-API מייצרת שקר משכנע.
השרת שבניתי מסרב לשילוב הפסול, מדפדף לבד, אומר לסוכן מראש ש-contains ו-startswith יחזירו 501 ואיך לכתוב במקומם טווח, ומנרמל את ארבע צורות השגיאה ל-kind אחד עם רמז שאומר מה לעשות הלאה. הידע על המגבלות יושב בכלי, לא בתקווה שהמודל ינחש נכון.
מה ה-API הזה לא יעשה לעולם
זה שווה שיחה מול כל לקוח, לפני שמבטיחים משהו. $metadata מצהיר על תשע פעולות בלבד שאפשר לקרוא להן, וכולן שאילתות על השרת עצמו — ואפס פעולות עסקיות.
כלומר כל מה ש-Priority מממשת כתוכנית לא נגיש דרך הדלת הזאת: סגירת מסמכים ורישום, MRP, סגירת חודש, הפקת דוחות והדפסה, קובצי מס״ב, שידור חשבוניות לשע״ם. אפשר לכתוב שורות; אי אפשר לגרום ל-Priority לעשות דברים. לממשקים אחרים של Priority יש את היכולת הזאת, אבל אף אחד מהם אינו ה-API הזה.
ועוד דבר ששווה לדעת לפני שבונים תקציב: התיעוד של Priority דורש מודול API ורישיון API לכל משתמש, וכתיבה נמדדת מול חבילות טרנזקציות שנרכשו. לסוכן שכותב יש עלות לפעולה.
ההחלטות שהן ניהול סיכונים, לא קוד
זה החלק שלקח את רוב הזמן:
אין כלי מחיקה. בכלל. Priority ממילא מסרבת ל-DELETE בכללי העסק שלה — שתי המחיקות שניסיתי, לקוח והזמנה, נדחו על ידי ה-ERP עצמו: לקוח נמחק רק דרך תוכנית ייעודית, והזמנה אפשר רק לסגור או לבטל. דרך ה-API הזה ה-ERP הוא בפועל append-only. לא להציע כלי מחיקה הופך את זה לעובדה מוצהרת במקום להפתעה באמצע משימה.
טבלאות תצורה משותפות מסורבות מראש — 18 טבלאות, ביניהן שערי מטבע, מסים, חשבונות, משתמשים ומחירונים. שינוי שער מטבע משנה התנהגות לכל מי שעובד בסביבה. זו החלטה פיננסית, לא החלטה של סוכן. השרת מדווח על הצורך בשינוי במקום לבצע אותו.
כל כתיבה מקבלת dry_run שמחזיר את המטען המדויק בלי לשלוח אותו, וכל רשומה שהשרת יוצר מתויגת אוטומטית — כשיש בישות שדה טקסט חופשי שיכול לשאת את התיוג — כך שאפשר לזהות אחר כך מה השרת יצר.
ומעבר לשרת עצמו, שלושה דברים שאני אומר לכל לקוח:
- אין בעיה לייצר אייג׳נט שמתנהל באוטונומיה מול גורמי חוץ. אבל לא כדאי לתת לו באותו סשן גם מידע שעלול להזיק אם יישלח.
- בעסקים עם כמה סוגי משתמשים צריך לנהל את ההרשאות שלהם לכלים השונים, לא רק את הגישה למערכת.
- להתחיל מקריאה בלבד, ולעבור ל-CRUD מלא בתהליך איטי, לצד התקדמות המשתמשים בשליטה בכלי.
המוק-אפ, וניסיון ה-prompt injection
בניתי ממשק מוק-אפ בדמות Priority Web על גבי תשתית ה-sandbox האמיתית, כדי לבצע טסטים והדגמות. אי אפשר להדגים סוכן מול סביבת הייצור של לקוח, ואי אפשר להדגים אותו מול מסך ריק. את המסכים ייצרתי מתוך $metadata — שמות השדות, הסדר, הטיפוסים ולשוניות תת-הטפסים מגיעים כולם מה-API.
את ההזמנה שבראש הרשימה יצרתי כדי לנסות סוגים שונים של prompt injection: טקסט ששתול ברשומה שסוכן יקרא ממילא תוך כדי עבודה לגיטימית, ושמורה לו לשלוח אליי במייל את כל המידע שאסף — בלי שהיוזר ביקש.
לא הצלחתי. המודלים החדשים די חכמים בנושא.
אבל אני לא קורא לזה הוכחה. זה מודל אחד, מול הניסיונות שלי. סביבת ה-sandbox עצמה ממחישה למה זה לא תיאורטי: היא סביבת בדיקות משותפת של שותפי Priority, ורשימת ההזמנות שם מלאה בשורות שכתבו אינטגרטורים אחרים. שדה טקסט חופשי ב-ERP נכתב על ידי מי שנכתב על ידו. זה בהחלט משהו לבדוק היטב כשבוחרים מודל ובונים סביבו workflow או agent.
איפה זה עומד
ה-MCP הזה כללי ובסיסי, ונדרשת התאמה שלו לכל לקוח ולצרכים שלו — אילו מסכים בכלל לפתוח, אילו כלים לחשוף, ואיזה תפקיד מקבל מה. אז יש עוד עבודה. מה שכן, הבסיס עומד, והוא נכנס לחבילה בלי תוספת תשלום.
הדבר הכללי שאני לוקח מזה: כשמחברים מודל למערכת ליבה, הקוד הוא החלק הקל. הקושי הוא להחליט מה המערכת מרשה לסוכן לעשות, מה היא מרשה לו לחשוב שהוא יודע, ובאיזה קצב מרחיבים את זה. אם יש לכם Priority ואתם שוקלים לחבר אליו סוכן, כך אני עובד — ואפשר להתחיל מקריאה בלבד ולראות מה יוצא.