Mad3oom Developer Platform

وثائق مدعوم للمطوّرين

منصّة واحدة بثلاث واجهات: خادم MCP للوصول لحسابك بالكامل عبر JSON-RPC، وOAuth 2.1 لربط عملاء خارجيين مثل Claude، وواجهة WhatsApp لإرسال رسائل القوالب من تطبيقك. كل رقم ومسار في هذه الصفحة مأخوذ من الكود المنشور ومُختبَر على الخوادم الفعلية.

18أداة MCP متاحة
60/دقيقةحد الطلبات لكل مفتاح
2025-06-18نسخة بروتوكول MCP
mad3oom.comالدومين الرسمي

نظرة عامة على المنصّة #

منصّة مدعوم للمطوّرين مبنية على ثلاث واجهات مستقلة في المصادقة، متكاملة في الوظيفة. اختر الواجهة حسب ما تريد عمله — لا تحتاج الثلاثة معًا.

أيّ واجهة تختار؟

لو عايز…استخدمنوع المصادقة
تقرأ/تكتب تذاكر أو عملاء أو اشتراكات من كودكخادم MCPBearer مفتاح API
تربط حسابك بـ Claude أو أي عميل MCPMCP + OAuth 2.1OAuth 2.1 + PKCE
ترسل رسالة واتساب واحدة من داخل كودك الحاليأداة send_whatsappBearer مفتاح API
تبني تكاملًا إنتاجيًا يرسل قوالب واتساب بكثافةIntegration APIX-API-Key مفتاح تكامل
تستقبل الرسائل الواردة وحالات التسليمWebhook واتسابVerify token من ميتا

كل المفاتيح تُدار من لوحة المطوّرين — تبويب «API» للمفاتيح وسجل الاستخدام، وتبويب «MCP» لحالة الخادم والأدوات المفعّلة.

العناوين الأساسية #

الدومين الرسمي للمنصّة هو mad3oom.com. كل مسارات MCP وOAuth تُخدَم منه مباشرة بلا أي إعادة توجيه.

POST https://mad3oom.com/mcp

الدومين القديم mad3oom.online انتهى ولم يعد يُستخدم. لو عندك كود أو Connector قديم بيشاور عليه، بدّله بـ mad3oom.com فورًا. الطلبات القديمة كانت بتمرّ على إعادة توجيه 301 تحوّل الـPOST إلى GET (سلوك مواصفة Fetch)، فكان الخادم يرد 405 Only POST is supported على طلب أُرسل أصلًا كـPOST.

جدول العناوين الكامل

الوظيفةMethodالعنوان
خادم MCP (كل الأدوات)POSThttps://mad3oom.com/mcp
OAuth — Authorization Server MetadataGEThttps://mad3oom.com/.well-known/oauth-authorization-server
OAuth — Protected Resource MetadataGEThttps://mad3oom.com/.well-known/oauth-protected-resource
OAuth — تسجيل عميل ديناميكيPOSThttps://mad3oom.com/oauth/register
OAuth — بدء التفويضGEThttps://mad3oom.com/oauth/authorize
OAuth — إصدار/تجديد التوكنPOSThttps://mad3oom.com/oauth/token
WhatsApp Integration APIGET POSThttps://srnelrdpqkcntbgudyto.supabase.co/functions/v1/integrations-api

واجهة WhatsApp تُخدَم من نطاق Supabase مباشرةً — ليست ضمن إعادة الكتابة على mad3oom.com. التفاصيل في قسم WhatsApp.

المصادقة والمفاتيح #

كل طلب على /mcp لازم يحمل ترويسة Authorization. مفيش استثناء — الطلب بلا ترويسة بيرجع 401.

Required header
Authorization: Bearer <credential>
Content-Type: application/json

نوعا الاعتماد

لوحة المطوّرين بتولّد نوعين من الاعتماد — الخادم بيقبل الاتنين على نفس الترويسة، ويفرّق بينهم بوجود نقطة . في القيمة.

النوعشكل القيمةالترويسة
API Key + Secret
credential_type: api_key_secret
mad3oom_pk_<32 hex>
mad3oom_sk_<40 حرف>
Bearer mad3oom_pk_….mad3oom_sk_…
Bearer Token
credential_type: bearer
mad3oom_bt_<64 hex> Bearer mad3oom_bt_…

السرّ (mad3oom_sk_…) والـ Bearer Token بيظهروا مرة واحدة فقط لحظة الإنشاء. المخزَّن في قاعدة البيانات بصمة SHA-256 لا القيمة نفسها — فلا الخادم ولا فريق الدعم يقدر يعرضهم لك تاني. لو ضاع منك، ولّد مفتاح جديد أو جدّد السرّ.

إنشاء مفتاح

افتح لوحة المطوّرين

من /mcp/mcp.html → تبويب API → داخلي.

دوس «توليد مفتاح جديد»

اكتب اسمًا واضحًا (حد أقصى 80 حرفًا)، واختر نوع الاعتماد والصلاحيات.

احفظ القيم فورًا

انسخ الـ Key والـ Secret قبل ما تقفل النافذة.

إصدار المفاتيح متاح لمدير الشركة ضمن اشتراك يمنح ميزة api_tokens — وللمنصّة نفسها. أي حساب تاني بيستقبل 403. وصلاحيات أي مفتاح لا تتعدّى سقف حساب صاحبه مهما طلبت، والسقف مفروض على الخادم لا على الواجهة. أقصى مدة صلاحية للمفتاح 730 يومًا.

الصلاحيات (Scopes) #

كل أداة مربوطة بصلاحية واحدة. tools/list بيرجّع الأدوات اللي مفتاحك يملك صلاحيتها فقط — فلو أداة مش ظاهرة عندك، غالبًا الـ scope ناقص.

الصلاحيات المستخدَمة فعليًا في الأدوات

Scopeيفتح
tickets:readlist_tickets، get_ticket، get_dashboard_stats، get_customer، list_customers
tickets:writecreate_ticket، update_ticket، close_ticket
whatsapp:sendsend_whatsapp
subscriptions:readlist_subscriptions، get_subscription
subscriptions:writecreate_subscription
subscriptions:renewrenew_subscription
subscriptions:cancelcancel_subscription
subscriptions:planslist_subscription_plans
notifications:readlist_notifications
notifications:sendsend_notification
notifications:managemanage_notification

انتبه: get_customer وlist_customers بيتطلبوا tickets:read وليس customers:read — ده اللي مسجّل في كتالوج الأدوات فعليًا.

باقي الصلاحيات المتاحة عند الإصدار

الصلاحيات دي مقبولة عند توليد المفتاح وفي OAuth، لكن مفيش أداة MCP بتستهلكها حاليًا — بتستخدمها واجهات داخلية تانية:

tickets:delete · knowledge_base:read · knowledge_base:write · customers:read · customers:write · whatsapp:read · analytics:read · settings:manage · oauth:manage · mcp:connect · chatbot:read · admin:full

الصلاحيات الافتراضية لو مبعتّش scopes عند الإنشاء: tickets:read، tickets:write، whatsapp:send، whatsapp:read، chatbot:read.

أول طلب #

أسرع طريقة تتأكد إن المفتاح شغّال: اطلب قائمة الأدوات. مش محتاج initialize قبلها — الخادم عديم الحالة (stateless).

curl -X POST https://mad3oom.com/mcp \
  -H "Authorization: Bearer YOUR_API_KEY.YOUR_SECRET" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
const res = await fetch("https://mad3oom.com/mcp", {
  method: "POST",
  headers: {
    "Authorization": "Bearer YOUR_API_KEY.YOUR_SECRET",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "tools/list" }),
});
const data = await res.json();
console.log(data.result.tools.map(t => t.name));
import requests

resp = requests.post(
    "https://mad3oom.com/mcp",
    headers={"Authorization": "Bearer YOUR_API_KEY.YOUR_SECRET"},
    json={"jsonrpc": "2.0", "id": 1, "method": "tools/list"},
)
print([t["name"] for t in resp.json()["result"]["tools"]])
الرد المتوقَّع — 200 OK
Response
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "tools": [
      { "name": "create_ticket", "description": "…", "inputSchema": { … } },
      { "name": "list_tickets",  "description": "…", "inputSchema": { … } }
    ]
  }
}

لو رجع 401 راجع شكل الترويسة. ولو رجعت قائمة أقصر من المتوقّع، فالمفتاح ناقصه scopes.

بروتوكول JSON-RPC #

كل شيء بيمرّ من POST /mcp بجسم JSON-RPC 2.0 واحد. الـmethod بتحدّد العملية.

MethodالوظيفةHTTP عند النجاح
initializeالتفاوض على نسخة البروتوكول وبدء جلسة200
notifications/initializedإشعار من العميل بانتهاء التهيئة (بلا جسم رد)202
tools/listالأدوات المتاحة لمفتاحك200
tools/callتنفيذ أداة200

طلبات الدفعات (Batch) غير مدعومة. كل طلب HTTP لازم يحمل كائن JSON-RPC واحد — مش مصفوفة. وأي method غير الأربعة فوق بترجع 404 مع -32601. وGET /mcp بيرجع 405.

initialize

اختياري للاستخدام المباشر، لكنه إلزامي لو بتبني عميل MCP كامل. بيرجّع نسخة البروتوكول المتفق عليها وترويسة Mcp-Session-Id.

Request
POST /mcp
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "initialize",
  "params": {
    "protocolVersion": "2025-06-18"
  }
}
Response
200 OK
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "protocolVersion": "2025-06-18",
    "capabilities": { "tools": {} },
    "serverInfo": {
      "name": "mad3oom-mcp",
      "version": "1.1.0"
    }
  }
}

النسخ المدعومة: 2025-06-18 (الافتراضية)، 2025-03-26، 2024-11-05، 2025-11-25. لو بعتّ نسخة غير معروفة، الخادم بيرجع للافتراضية بدل ما يرفض الطلب. الخادم يعلن قدرة tools فقط — لا resources ولا prompts.

tools/list

بيرجّع مصفوفة الأدوات بعد فلترتين: أدوات مفتاحك يملك صلاحيتها، وغير مُعطَّلة من إدارة المنصّة. كل عنصر فيه name وdescription وinputSchema (JSON Schema).

الكتالوج بيتقري من قاعدة البيانات مع كاش 30 ثانية — فأي تعديل من الإدارة بيبان خلال نصف دقيقة على الأكثر. اعتبر tools/list هو مصدر الحقيقة وقت التشغيل، وهذه الصفحة مرجعًا للشرح.

tools/call

بياخد params.name (اسم الأداة) وparams.arguments (كائن المعاملات).

شكل الطلب العام
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "<tool_name>",
    "arguments": { … }
  }
}

شكل الرد مهم: نتيجة الأداة بترجع كنص JSON جوه result.content[0].text، مش ككائن مباشر. لازم تعمل JSON.parse() عليها.

الرد الخام
200 OK
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "[\n  {\n    \"id\": \"…\"\n  }\n]"
      }
    ]
  }
}
استخراج البيانات
JavaScript
const { result, error } = await res.json();
if (error) throw new Error(error.message);

// نتيجة الأداة نص JSON — لازم parse
const data = JSON.parse(result.content[0].text);

في كل أمثلة مرجع الأدوات تحت، «الرد» معروض بعد هذا الـ parsing مباشرةً.

مرجع الأدوات #

18 أداة، كلها تُستدعى بنفس الطريقة: POST /mcp بـmethod: "tools/call". الجدول تحت فهرس سريع، وتحته بطاقة تفصيلية لكل أداة.

الأداةScopeالوظيفة
list_ticketstickets:readعرض التذاكر المرئية لصاحب مفتاح الـ API، مرتّبة من الأحدث.
get_tickettickets:readجلب تفاصيل تذكرة واحدة بمعرّفها.
create_tickettickets:writeإنشاء تذكرة دعم جديدة نيابةً عن صاحب مفتاح الـ API.
update_tickettickets:writeتعديل تذكرة موجودة. الأدمن يعدّل أي حقل مسموح؛ العميل العادي يقدر يؤرشف تذكرته فقط.
close_tickettickets:writeإغلاق تذكرة — بضبط حالتها على closed.
get_dashboard_statstickets:readإحصائيات سريعة عن التذاكر المرئية لصاحب المفتاح، مجمّعة حسب الحالة.
list_customerstickets:readعرض العملاء المرئيين لصاحب مفتاح الـ API.
get_customertickets:readجلب بيانات عميل واحد بمعرّفه، ضمن نطاق العملاء المرئيين لصاحب المفتاح.
send_whatsappwhatsapp:sendإرسال رسالة واتساب (نصية أو قالب) من رقم مربوط بحسابك.
list_subscriptionssubscriptions:readعرض اشتراكات صاحب مفتاح الـ API مع فلترة اختيارية بالحالة والخطة.
get_subscriptionsubscriptions:readجلب تفاصيل اشتراك واحد.
list_subscription_planssubscriptions:plansعرض خطط الاشتراك المفعّلة حاليًا مع مزاياها.
create_subscriptionsubscriptions:writeإنشاء طلب اشتراك جديد. التفعيل الفعلي يتم لاحقًا من فريق الدعم بعد مراجعة الدفع.
renew_subscriptionsubscriptions:renewطلب تجديد اشتراك حالي — يمدّد المدة من تاريخ انتهاء الاشتراك النشط لنفس الخطة.
cancel_subscriptionsubscriptions:cancelإلغاء طلب اشتراك قيد المراجعة.
list_notificationsnotifications:readعرض إشعارات صاحب مفتاح الـ API نفسه.
send_notificationnotifications:sendإرسال إشعار لمستخدم.
manage_notificationnotifications:manageتحديد إشعار كمقروء أو غير مقروء، أو حذفه.

التذاكر #

إدارة تذاكر الدعم — قراءة وإنشاء وتعديل وإغلاق.

POST

list_tickets

tickets:read

عرض التذاكر المرئية لصاحب مفتاح الـ API، مرتّبة من الأحدث.

المعاملالنوعمطلوبالوصف
statusstringاختياريفلترة بالحالة. القيم الصالحة: open، in-progress، resolved، confirmed، rejected، closed.
limitnumberاختياريعدد الصفوف. الافتراضي 20، والحد الأقصى 100 (أي رقم أكبر يُقصّ تلقائيًا).
offsetnumberاختياريالإزاحة للترقيم. الافتراضي 0.
Request
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "list_tickets",
    "arguments": {
      "status": "open",
      "limit": 5
    }
  }
}
curl -X POST https://mad3oom.com/mcp \
  -H "Authorization: Bearer $MAD3OOM_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"list_tickets","arguments":{"status":"open","limit":5}}}'
Response (بعد الـ parsing)
200 OK
[
  {
    "id": "3f0a…",
    "ticket_number": 128,
    "title": "مشكلة تسجيل دخول",
    "description": "…",
    "status": "open",
    "priority": "high",
    "ticket_type": "problem",
    "category": null,
    "user_id": "9c1e…",
    "created_at": "2026-09-18T10:00:00+00:00"
  }
]

الرد مصفوفة صفوف tickets كاملة (select *). الأدمن يرى كل التذاكر؛ العميل العادي يرى تذاكره وتذاكر مستخدميه الفرعيين فقط.

POST

get_ticket

tickets:read

جلب تفاصيل تذكرة واحدة بمعرّفها.

المعاملالنوعمطلوبالوصف
ticket_idstringمطلوبمعرّف التذكرة (UUID).
Request
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "get_ticket",
    "arguments": {
      "ticket_id": "TICKET_ID"
    }
  }
}
curl -X POST https://mad3oom.com/mcp \
  -H "Authorization: Bearer $MAD3OOM_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"get_ticket","arguments":{"ticket_id":"TICKET_ID"}}}'
Response (بعد الـ parsing)
200 OK
{
  "id": "TICKET_ID",
  "ticket_number": 128,
  "title": "مشكلة تسجيل دخول",
  "status": "open",
  "priority": "high",
  "created_at": "2026-09-18T10:00:00+00:00"
}

لو التذكرة خارج نطاق رؤيتك يرجع -32004 بحالة 403، ولو غير موجودة -32005 بحالة 404.

POST

create_ticket

tickets:write

إنشاء تذكرة دعم جديدة نيابةً عن صاحب مفتاح الـ API.

المعاملالنوعمطلوبالوصف
titlestringمطلوبعنوان التذكرة. لا يقبل قيمة فارغة.
descriptionstringمطلوبوصف المشكلة أو الطلب. لا يقبل قيمة فارغة.
prioritystringاختياريlow | medium | high. الافتراضي medium.
categorystringاختياريتصنيف حر. الافتراضي null.
ticket_typestringاختياريproblem | inquiry. الافتراضي problem.
contact_infostringاختياريبيانات تواصل إضافية. الافتراضي null.

كتالوج الأداة يعلن urgent ضمن قيم priority، لكن قاعدة البيانات تقبل low وmedium وhigh فقط (قيد tickets_priority_check). إرسال urgent يفشل بخطأ داخلي — لا تستخدمه.

Request
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "create_ticket",
    "arguments": {
      "title": "عندي مشكلة في الدخول",
      "description": "الصفحة بترجعني لتسجيل الدخول كل مرة",
      "priority": "medium"
    }
  }
}
curl -X POST https://mad3oom.com/mcp \
  -H "Authorization: Bearer $MAD3OOM_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"create_ticket","arguments":{"title":"عندي مشكلة في الدخول","description":"الصفحة بترجعني لتسجيل الدخول كل مرة","priority":"medium"}}}'
Response (بعد الـ parsing)
200 OK
{
  "id": "e5f6…",
  "ticket_number": 129,
  "user_id": "9c1e…",
  "title": "عندي مشكلة في الدخول",
  "description": "الصفحة بترجعني لتسجيل الدخول كل مرة",
  "status": "open",
  "priority": "medium",
  "ticket_type": "problem",
  "created_at": "2026-09-19T08:12:00+00:00"
}

الرد هو صف التذكرة المُنشأ كاملًا بعد الإدراج.

POST

update_ticket

tickets:write

تعديل تذكرة موجودة. الأدمن يعدّل أي حقل مسموح؛ العميل العادي يقدر يؤرشف تذكرته فقط.

المعاملالنوعمطلوبالوصف
ticket_idstringمطلوبمعرّف التذكرة.
updatesobjectمطلوبكائن الحقول المطلوب تعديلها.

للعميل العادي، المفتاح الوحيد المقبول داخل updates هو archived_by_customer. أي مفتاح غير ذلك يرجع -32004 بحالة 403.

Request
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "update_ticket",
    "arguments": {
      "ticket_id": "TICKET_ID",
      "updates": {
        "priority": "high"
      }
    }
  }
}
curl -X POST https://mad3oom.com/mcp \
  -H "Authorization: Bearer $MAD3OOM_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"update_ticket","arguments":{"ticket_id":"TICKET_ID","updates":{"priority":"high"}}}}'
Response (بعد الـ parsing)
200 OK
{
  "id": "TICKET_ID",
  "priority": "high",
  "status": "open",
  "last_updated_at": "2026-09-19T08:20:00+00:00"
}

تعديلات الأدمن تمرّ عبر دالة قاعدة بيانات محروسة (mcp_admin_update_ticket) وليس تحديثًا مباشرًا.

POST

close_ticket

tickets:write أدمن فقط

إغلاق تذكرة — بضبط حالتها على closed.

المعاملالنوعمطلوبالوصف
ticket_idstringمطلوبمعرّف التذكرة.

متاحة للأدمن فقط. أي حساب آخر يستقبل -32004 بحالة 403 حتى لو المفتاح يحمل tickets:write.

Request
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "close_ticket",
    "arguments": {
      "ticket_id": "TICKET_ID"
    }
  }
}
curl -X POST https://mad3oom.com/mcp \
  -H "Authorization: Bearer $MAD3OOM_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"close_ticket","arguments":{"ticket_id":"TICKET_ID"}}}'
Response (بعد الـ parsing)
200 OK
{
  "id": "TICKET_ID",
  "status": "closed"
}
POST

get_dashboard_stats

tickets:read

إحصائيات سريعة عن التذاكر المرئية لصاحب المفتاح، مجمّعة حسب الحالة.

لا تأخذ أي معاملات — أرسل "arguments": {}.

Request
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "get_dashboard_stats",
    "arguments": {}
  }
}
curl -X POST https://mad3oom.com/mcp \
  -H "Authorization: Bearer $MAD3OOM_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"get_dashboard_stats","arguments":{}}}'
Response (بعد الـ parsing)
200 OK
{
  "total": 37,
  "resolved": 27,
  "confirmed": 8,
  "rejected": 2
}

المفاتيح ديناميكية: total دائمًا موجود، وبعده مفتاح لكل حالة ظهرت فعلًا في بياناتك. لا تفترض وجود open أو closed — اقرأ المفاتيح كما ترجع.

العملاء #

قراءة بيانات العملاء ضمن نطاق رؤية المفتاح.

POST

list_customers

tickets:read

عرض العملاء المرئيين لصاحب مفتاح الـ API.

المعاملالنوعمطلوبالوصف
limitnumberاختياريالافتراضي 20، الحد الأقصى 100.
offsetnumberاختياريالافتراضي 0.
Request
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "list_customers",
    "arguments": {
      "limit": 20
    }
  }
}
curl -X POST https://mad3oom.com/mcp \
  -H "Authorization: Bearer $MAD3OOM_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"list_customers","arguments":{"limit":20}}}'
Response (بعد الـ parsing)
200 OK
[
  {
    "id": "9c1e…",
    "email": "ahmed@example.com",
    "full_name": "أحمد محمد",
    "phone": "+201001234567",
    "role": "user",
    "user_type": "individual",
    "city": "القاهرة",
    "country": "EG",
    "created_at": "2026-05-02T09:00:00+00:00",
    "is_verified": true,
    "super_user_id": null
  }
]

الأعمدة المُعادة مُقيّدة عمدًا بقائمة آمنة: id, email, full_name, phone, role, user_type, city, country, created_at, is_verified, super_user_id. لا تُعاد أي بيانات اعتماد.

POST

get_customer

tickets:read

جلب بيانات عميل واحد بمعرّفه، ضمن نطاق العملاء المرئيين لصاحب المفتاح.

المعاملالنوعمطلوبالوصف
customer_idstringمطلوبمعرّف العميل (UUID — هو نفسه user_id).
Request
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "get_customer",
    "arguments": {
      "customer_id": "CUSTOMER_ID"
    }
  }
}
curl -X POST https://mad3oom.com/mcp \
  -H "Authorization: Bearer $MAD3OOM_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"get_customer","arguments":{"customer_id":"CUSTOMER_ID"}}}'
Response (بعد الـ parsing)
200 OK
{
  "id": "CUSTOMER_ID",
  "email": "ahmed@example.com",
  "full_name": "أحمد محمد",
  "phone": "+201001234567",
  "role": "user",
  "user_type": "individual",
  "city": "القاهرة",
  "country": "EG",
  "created_at": "2026-05-02T09:00:00+00:00",
  "is_verified": true,
  "super_user_id": null
}

غير موجود ⇒ -32005 / 404. خارج نطاقك ⇒ -32004 / 403.

واتساب #

إرسال رسائل واتساب من رقم مربوط بحسابك.

POST

send_whatsapp

whatsapp:send

إرسال رسالة واتساب (نصية أو قالب) من رقم مربوط بحسابك.

المعاملالنوعمطلوبالوصف
tostringمطلوبرقم المستقبِل بالصيغة الدولية بلا «+»، مثل 201001234567.
message_typestringمطلوبtext أو template.
textstringحسب النوعنص الرسالة — مطلوب عند message_type="text".
templateobjectحسب النوع{ name, language, components } — مطلوب عند message_type="template".
phone_number_idstringانظر التنبيهمعرّف رقم الإرسال لدى ميتا.

كتالوج الأداة يعلن phone_number_id كحقل مطلوب، لكن الخادم يستنتجه تلقائيًا لو حسابك مربوط برقم واحد فقط. لو مربوط بأكثر من رقم ولم ترسله، يفشل الطلب برسالة «عندك أكتر من رقم واتساب مرتبط بهذا الحساب — لازم تحديد phone_number_id صراحةً». الأأمَن: ابعته دائمًا.

Request
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "send_whatsapp",
    "arguments": {
      "to": "201001234567",
      "message_type": "text",
      "text": "أهلاً بيك في مدعوم!"
    }
  }
}
curl -X POST https://mad3oom.com/mcp \
  -H "Authorization: Bearer $MAD3OOM_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"send_whatsapp","arguments":{"to":"201001234567","message_type":"text","text":"أهلاً بيك في مدعوم!"}}}'
Response (بعد الـ parsing)
200 OK
{
  "ok": true,
  "status": 200,
  "data": {
    "messaging_product": "whatsapp",
    "contacts": [
      {
        "input": "201001234567",
        "wa_id": "201001234567"
      }
    ],
    "messages": [
      {
        "id": "wamid.HBgM…"
      }
    ]
  }
}

عند الفشل يرجع { "ok": false, "status": …, "error": "…" } — ولاحظ إن ده يوصلك داخل result لا داخل error (الأداة نفّذت، والمزوّد هو اللي رفض). الحالة 402 معناها رصيد محفظة واتساب غير كافٍ.

POST

send_whatsapp · template

whatsapp:send

نفس الأداة بنوع template — لإرسال قالب معتمد من ميتا.

المعاملالنوعمطلوبالوصف
template.namestringمطلوباسم القالب المعتمد لدى ميتا.
template.languagestringاختياريرمز اللغة. الافتراضي ar.
template.componentsarrayاختياريمكوّنات القالب ومتغيّراته. الافتراضي [].
Request
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "send_whatsapp",
    "arguments": {
      "to": "201001234567",
      "message_type": "template",
      "template": {
        "name": "TEMPLATE_NAME",
        "language": "ar",
        "components": []
      }
    }
  }
}
curl -X POST https://mad3oom.com/mcp \
  -H "Authorization: Bearer $MAD3OOM_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"send_whatsapp","arguments":{"to":"201001234567","message_type":"template","template":{"name":"TEMPLATE_NAME","language":"ar","components":[]}}}}'
Response (بعد الـ parsing)
200 OK
{
  "ok": true,
  "status": 200,
  "data": {
    "messaging_product": "whatsapp",
    "messages": [
      {
        "id": "wamid.HBgM…"
      }
    ]
  }
}

لإرسال قوالب على نطاق إنتاجي مع منع تكرار وصندوق رملي، استخدم Integration API بدل هذه الأداة.

الاشتراكات #

طلب وتجديد وإلغاء الاشتراكات وعرض الخطط.

POST

list_subscriptions

subscriptions:read

عرض اشتراكات صاحب مفتاح الـ API مع فلترة اختيارية بالحالة والخطة.

المعاملالنوعمطلوبالوصف
statusstringاختياريpending | active | expired | rejected.
planstringاختياريsupport | whatsapp | bundle.
limitnumberاختياريالافتراضي 20، الأقصى 100.
offsetnumberاختياريالافتراضي 0.
Request
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "list_subscriptions",
    "arguments": {
      "status": "active",
      "limit": 20
    }
  }
}
curl -X POST https://mad3oom.com/mcp \
  -H "Authorization: Bearer $MAD3OOM_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"list_subscriptions","arguments":{"status":"active","limit":20}}}'
Response (بعد الـ parsing)
200 OK
[
  {
    "id": "7ab2…",
    "user_id": "9c1e…",
    "ticket_id": "3f0a…",
    "plan": "bundle",
    "billing_cycle": "monthly",
    "status": "active",
    "start_date": "2026-09-01T00:00:00+00:00",
    "end_date": "2026-10-01T00:00:00+00:00",
    "is_renewal": false,
    "duration_days": 30
  }
]

الصفوف من جدول whatsapp_subscriptions.

POST

get_subscription

subscriptions:read

جلب تفاصيل اشتراك واحد.

المعاملالنوعمطلوبالوصف
subscription_idstringمطلوبمعرّف الاشتراك (UUID).
Request
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "get_subscription",
    "arguments": {
      "subscription_id": "SUBSCRIPTION_ID"
    }
  }
}
curl -X POST https://mad3oom.com/mcp \
  -H "Authorization: Bearer $MAD3OOM_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"get_subscription","arguments":{"subscription_id":"SUBSCRIPTION_ID"}}}'
Response (بعد الـ parsing)
200 OK
{
  "id": "SUBSCRIPTION_ID",
  "plan": "bundle",
  "billing_cycle": "monthly",
  "status": "active",
  "start_date": "2026-09-01T00:00:00+00:00",
  "end_date": "2026-10-01T00:00:00+00:00"
}
POST

list_subscription_plans

subscriptions:plans

عرض خطط الاشتراك المفعّلة حاليًا مع مزاياها.

لا تأخذ أي معاملات — أرسل "arguments": {}.

Request
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "list_subscription_plans",
    "arguments": {}
  }
}
curl -X POST https://mad3oom.com/mcp \
  -H "Authorization: Bearer $MAD3OOM_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"list_subscription_plans","arguments":{}}}'
Response (بعد الـ parsing)
200 OK
[
  {
    "id": "c31f…",
    "key": "bundle",
    "name": "Bundle",
    "name_ar": "دعم فني + واتساب",
    "price_monthly": "1500.00",
    "price_yearly": "15000.00",
    "currency": "EGP",
    "is_active": true,
    "sort_order": 3,
    "features": [
      {
        "plan_id": "c31f…",
        "...": "…"
      }
    ]
  }
]

بيرجّع الخطط النشطة فقط، مرتّبة بـsort_order، وكل خطة معها مصفوفة features الخاصة بها.

POST

create_subscription

subscriptions:write

إنشاء طلب اشتراك جديد. التفعيل الفعلي يتم لاحقًا من فريق الدعم بعد مراجعة الدفع.

المعاملالنوعمطلوبالوصف
planstringمطلوبsupport | whatsapp | bundle.
billing_cyclestringمطلوبmonthly (30 يومًا) | yearly (365 يومًا).

لو عندك طلب pending بالفعل على نفس الخطة، يُرفض الطلب برسالة صريحة بدل إنشاء طلب مكرّر.

Request
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "create_subscription",
    "arguments": {
      "plan": "support",
      "billing_cycle": "monthly"
    }
  }
}
curl -X POST https://mad3oom.com/mcp \
  -H "Authorization: Bearer $MAD3OOM_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"create_subscription","arguments":{"plan":"support","billing_cycle":"monthly"}}}'
Response (بعد الـ parsing)
200 OK
{
  "ticket": {
    "id": "3f0a…",
    "title": "طلب اشتراك - الدعم الفني (شهري)",
    "status": "open",
    "priority": "high"
  },
  "subscription": {
    "id": "7ab2…",
    "plan": "support",
    "billing_cycle": "monthly",
    "status": "pending",
    "is_renewal": false,
    "duration_days": 30
  }
}

الرد كائن فيه مفتاحان: ticket (تذكرة المتابعة المُنشأة تلقائيًا) وsubscription (صف الاشتراك بحالة pending). تواريخ البداية والنهاية في الرد مبدئية وتُحسب نهائيًا عند التأكيد.

POST

renew_subscription

subscriptions:renew

طلب تجديد اشتراك حالي — يمدّد المدة من تاريخ انتهاء الاشتراك النشط لنفس الخطة.

المعاملالنوعمطلوبالوصف
planstringمطلوبsupport | whatsapp | bundle.
billing_cyclestringمطلوبmonthly | yearly.
Request
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "renew_subscription",
    "arguments": {
      "plan": "support",
      "billing_cycle": "monthly"
    }
  }
}
curl -X POST https://mad3oom.com/mcp \
  -H "Authorization: Bearer $MAD3OOM_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"renew_subscription","arguments":{"plan":"support","billing_cycle":"monthly"}}}'
Response (بعد الـ parsing)
200 OK
{
  "ticket": {
    "id": "3f0b…",
    "title": "طلب تجديد اشتراك - الدعم الفني (شهري)",
    "status": "open"
  },
  "subscription": {
    "id": "7ab3…",
    "plan": "support",
    "status": "pending",
    "is_renewal": true,
    "previous_end_date": "2026-10-01T00:00:00+00:00"
  }
}

لو مفيش اشتراك نشط لنفس الخطة، الطلب يُقبل لكن المدة تُحتسب من تاريخ التأكيد، ويُذكر ذلك في وصف التذكرة.

POST

cancel_subscription

subscriptions:cancel

إلغاء طلب اشتراك قيد المراجعة.

المعاملالنوعمطلوبالوصف
subscription_idstringمطلوبمعرّف الاشتراك.

الإلغاء متاح للحالة pending فقط. الاشتراك النشط لا يُلغى من هنا، والمحاولة ترجع رسالة خطأ تذكر الحالة الحالية.

Request
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "cancel_subscription",
    "arguments": {
      "subscription_id": "SUBSCRIPTION_ID"
    }
  }
}
curl -X POST https://mad3oom.com/mcp \
  -H "Authorization: Bearer $MAD3OOM_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"cancel_subscription","arguments":{"subscription_id":"SUBSCRIPTION_ID"}}}'
Response (بعد الـ parsing)
200 OK
{
  "id": "SUBSCRIPTION_ID",
  "status": "rejected",
  "rejection_reason": "تم الإلغاء بواسطة العميل",
  "reviewed_at": "2026-09-19T09:00:00+00:00"
}

الحالة بعد الإلغاء تصير rejected لا cancelled، وتُزامَن تذكرة الطلب المرتبطة تلقائيًا.

الإشعارات #

قراءة وإرسال وإدارة إشعارات المنصّة.

POST

list_notifications

notifications:read

عرض إشعارات صاحب مفتاح الـ API نفسه.

المعاملالنوعمطلوبالوصف
statusstringاختياريall | read | unread.
typestringاختياريفلترة بنوع الإشعار.
limitnumberاختياريالافتراضي 20، الأقصى 100.
offsetnumberاختياريالافتراضي 0.
Request
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "list_notifications",
    "arguments": {
      "status": "unread",
      "limit": 20
    }
  }
}
curl -X POST https://mad3oom.com/mcp \
  -H "Authorization: Bearer $MAD3OOM_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"list_notifications","arguments":{"status":"unread","limit":20}}}'
Response (بعد الـ parsing)
200 OK
[
  {
    "id": "a1b2…",
    "user_id": "9c1e…",
    "title": "تم تحديث تذكرتك",
    "message": "فريق الدعم ردّ على التذكرة رقم 128",
    "type": "info",
    "is_read": false,
    "link": "/tickets.html",
    "created_at": "2026-09-19T07:40:00+00:00"
  }
]

هذه الأداة تُرجع إشعارات صاحب المفتاح وحده دائمًا — حتى لو كان أدمن.

POST

send_notification

notifications:send

إرسال إشعار لمستخدم.

المعاملالنوعمطلوبالوصف
user_idstringمطلوبمعرّف المستخدم المستقبِل.
titlestringمطلوبعنوان الإشعار. لا يقبل قيمة فارغة.
messagestringمطلوبنص الإشعار. لا يقبل قيمة فارغة.
typestringاختياريinfo | success | warning | error | chat | subdomain. الافتراضي info.
linkstringاختياريرابط يفتحه الإشعار. الافتراضي null.

العميل العادي يقدر يبعت لنفسه أو لمستخدميه الفرعيين فقط — أي user_id خارج نطاقه يرجع -32004 / 403. الأدمن يبعت لأي مستخدم مرئي له.

Request
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "send_notification",
    "arguments": {
      "user_id": "USER_ID",
      "title": "عنوان الإشعار",
      "message": "نص الإشعار",
      "type": "info"
    }
  }
}
curl -X POST https://mad3oom.com/mcp \
  -H "Authorization: Bearer $MAD3OOM_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"send_notification","arguments":{"user_id":"USER_ID","title":"عنوان الإشعار","message":"نص الإشعار","type":"info"}}}'
Response (بعد الـ parsing)
200 OK
{
  "id": "b2c3…",
  "user_id": "USER_ID",
  "title": "عنوان الإشعار",
  "message": "نص الإشعار",
  "type": "info",
  "is_read": false,
  "created_at": "2026-09-19T09:10:00+00:00"
}
POST

manage_notification

notifications:manage

تحديد إشعار كمقروء أو غير مقروء، أو حذفه.

المعاملالنوعمطلوبالوصف
notification_idstringمطلوبمعرّف الإشعار.
actionstringمطلوبmark_read | mark_unread | delete.

صاحب الإشعار فقط أو الأدمن. غير ذلك ⇒ -32004 / 403.

Request
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "manage_notification",
    "arguments": {
      "notification_id": "NOTIFICATION_ID",
      "action": "mark_read"
    }
  }
}
curl -X POST https://mad3oom.com/mcp \
  -H "Authorization: Bearer $MAD3OOM_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"manage_notification","arguments":{"notification_id":"NOTIFICATION_ID","action":"mark_read"}}}'
Response (بعد الـ parsing)
200 OK
{
  "id": "NOTIFICATION_ID",
  "is_read": true,
  "title": "تم تحديث تذكرتك",
  "type": "info"
}

عند action="delete" يكون الرد { "deleted": true, "id": "…" } بدل صف الإشعار.

الأخطاء #

الأخطاء بترجع بشكل JSON-RPC قياسي. انتبه: جزء من الأخطاء بيرجع بحالة HTTP 200 — فلا تعتمد على res.ok وحده، افحص وجود error في الجسم.

شكل الخطأ
{
  "jsonrpc": "2.0",
  "id": 1,
  "error": {
    "code": -32003,
    "message": "Missing required scope: tickets:write"
  }
}

جدول الأكواد الكامل

JSON-RPCHTTPالمعنىالحل
-32001401مشكلة مصادقة: ترويسة ناقصة، مفتاح غير صالح، سرّ غلط، مفتاح موقوف أو منتهيراجع شكل الترويسة وصلاحية المفتاح من اللوحة
-32001429Rate limit exceeded (60 req/min)استنى، وطبّق تراجعًا أُسّيًا
-32700400Parse error — الجسم ليس JSON صالحًاتأكد من Content-Type ومن صحة الـ JSON
-32600400Invalid Request — method ناقص أو ليس نصًّاابعت method صحيحًا
-32600405Only POST is supportedاستخدم POST — GET /mcp مرفوض
-32601404Method not found — method خارج الأربعة المدعومةراجع جدول الـ methods
-32602200أداة غير معروفة، أو مُعطَّلة من إدارة المنصّةراجع الاسم عبر tools/list
-32003200المفتاح لا يملك الـ scope المطلوب للأداةضيف الـ scope من لوحة المفاتيح
-32004403ممنوع — العملية خارج صلاحيتك على هذا العنصرراجع قيود الأداة (أدمن فقط، نطاق الرؤية…)
-32005404العنصر المطلوب غير موجودتأكد من الـ id
-32603500خطأ داخلي — يشمل رفض قاعدة البيانات لقيمة غير صالحةراجع قيم المعاملات، ولو استمر تواصل مع الدعم

حالة خاصة — send_whatsapp: فشل المزوّد ليس خطأ JSON-RPC. الأداة تنجح وترجع داخل result كائنًا فيه "ok": false وstatus وerror. افحص ok بعد الـ parsing.

معالجة صحيحة للأخطاء

JavaScript
async function callTool(name, args = {}) {
  const res = await fetch("https://mad3oom.com/mcp", {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${token}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      jsonrpc: "2.0", id: Date.now(),
      method: "tools/call", params: { name, arguments: args },
    }),
  });

  // 429 قابل لإعادة المحاولة — بقية الأخطاء لا
  if (res.status === 429) throw new Error("RATE_LIMITED");

  const data = await res.json();

  // أخطاء كتير بترجع HTTP 200 — الفحص على الجسم لا على res.ok
  if (data.error) {
    throw new Error(`[${data.error.code}] ${data.error.message}`);
  }

  return JSON.parse(data.result.content[0].text);
}

حدود الاستخدام #

المسارالحدالنطاق
POST /mcp60 طلبًا / دقيقةلكل مفتاح API
GET /oauth/authorize60 / دقيقة · 20 / دقيقةلكل IP · لكل (client + IP)
POST /oauth/token40 / دقيقة · 15 / دقيقةلكل IP · لكل (client + IP)
POST /oauth/register5 / ساعةلكل IP
WhatsApp Integration API60 / دقيقة (دفعة 20)لكل تكامل — مع Retry-After

حد /mcp بيتحسب من عدّاد طلبات آخر 60 ثانية لنفس المفتاح. التجاوز يرجع 429 مع -32001.

الترقيم (Pagination)

كل الأدوات اللي بترجع قوائم بتاخد limit وoffset. الافتراضي limit=20 وoffset=0، والحد الأقصى limit=100 — أي رقم أكبر بيتقصّ بصمت لـ 100، فلا تفترض إنك هتستقبل 500 صف.

إعادة المحاولة والتكرار

مفيش Idempotency Key على واجهة MCP. لو عملت retry على create_ticket أو send_whatsapp بعد انقطاع، ممكن العملية تتكرّر فعليًا. لو الـ retry مهم لك: تحقّق من عندك قبل الإرسال، أو استخدم Integration API لرسائل واتساب — فيها منع تكرار مدمج.

أعد المحاولة على 429 و5xx فقط، بتراجع أُسّي (2s، 4s، 8s، 16s) وبحد أقصى 5 محاولات. أخطاء 4xx الأخرى تحتاج إصلاح الطلب نفسه — إعادة المحاولة لن تغيّر النتيجة.

OAuth 2.1 #

لو بتبني Connector لعميل MCP خارجي، استخدم OAuth بدل مفتاح ثابت — المستخدم بيوافق بنفسه، والتوكن بيتجدّد تلقائيًا.

نقاط الاكتشاف

GET https://mad3oom.com/.well-known/oauth-authorization-server
200 OK — الرد الفعلي
{
  "issuer": "https://mad3oom.com",
  "authorization_endpoint": "https://mad3oom.com/oauth/authorize",
  "token_endpoint": "https://mad3oom.com/oauth/token",
  "registration_endpoint": "https://mad3oom.com/oauth/register",
  "scopes_supported": ["tickets:read", "tickets:write", "…"],
  "response_types_supported": ["code"],
  "grant_types_supported": ["authorization_code", "refresh_token"],
  "code_challenge_methods_supported": ["S256"],
  "token_endpoint_auth_methods_supported": ["none", "client_secret_post", "client_secret_basic"]
}
GET https://mad3oom.com/.well-known/oauth-protected-resource
200 OK — الرد الفعلي
{
  "resource": "https://mad3oom.com/mcp",
  "authorization_servers": ["https://mad3oom.com"],
  "bearer_methods_supported": ["header"],
  "resource_documentation": "https://mad3oom.com"
}

التدفّق

الاكتشاف

أول POST /mcp بلا توكن بيرجع 401 مع ترويسة WWW-Authenticate: Bearer resource_metadata="…" تدلّك على نقطة الاكتشاف. من هنا يبدأ أي عميل MCP قياسي تلقائيًا.

تسجيل العميل — POST /oauth/register

ابعت client_name وredirect_uris (لازم HTTPS) → بيرجع 201 مع client_id.

موافقة المستخدم — GET /oauth/authorize

PKCE إجباري بـcode_challenge_method=S256. المستخدم بيوافق من صفحة الموافقة، وبيرجع code على الـ redirect URI.

تبادل الكود — POST /oauth/token

بيرجع access_token صالح 3600 ثانية وrefresh_token صالح 30 يومًا. التوكن بيتسجّل في نفس جدول api_tokens، فبيشتغل على /mcp زي أي مفتاح.

التجديد

grant_type=refresh_token. الـ refresh token يُدوَّر بالكامل عند كل استخدام — القديم يُلغى فورًا، فاحفظ الجديد دائمًا.

الربط بـ Claude وعملاء MCP #

نفس الـ endpoint اللي بتستخدمه من كودك هو اللي بتحطّه في أي عميل MCP يدعم Streamable HTTP. مفيش إعداد إضافي.

POST https://mad3oom.com/mcp

طريقتان للربط

الطريقةإزايمناسبة لـ
مفتاح ثابتحط العنوان وأضف ترويسة Authorization: Bearer … يدويًااستخدامك الشخصي، بيئات التطوير
OAuth 2.1حط العنوان وبس — العميل يكتشف الـ OAuth تلقائيًا من الـ 401 ويفتح صفحة الموافقةClaude، والعملاء اللي بيربطوا مستخدمين حقيقيين

من لوحة المطوّرين → تبويب MCP → الخادم هتلاقي عنوان الربط جاهز للنسخ، وزر «اختبار الاتصال»، وقائمة الأدوات المتاحة لحسابك لحظيًا.

ملاحظات لبناة العملاء

  • النقل: Streamable HTTP — طلب POST واحد لكل رسالة. لا SSE ولا WebSocket.
  • الخادم بيرجع Mcp-Session-Id على initialize، لكنه عديم الحالة: مش شرط ترجّعه في الطلبات التالية.
  • القدرات المُعلَنة: tools فقط. مفيش resources ولا prompts ولا sampling.
  • ترويسات CORS مفتوحة (*) وبتقبل mcp-session-id وmcp-protocol-version.
  • notifications/initialized بيرجع 202 بلا جسم — لا تحاول تعمل .json() عليه.

مدعوم كعميل MCP

في الاتجاه المعاكس، لوحة الإدارة في مدعوم تقدر تتصل بخوادم MCP خارجية (Supabase، GitHub…) نيابةً عن الأدمن. دي ميزة داخلية للوحة — مش جزء من الواجهة اللي بتستهلكها كمطوّر خارجي، وتُدار من تبويب MCP → العميل.

نظرة عامة #

تطبيقك الخارجي يعرف ثلاثة أشياء فقط: اسم القالب المنطقي، رقم المستقبِل، المتغيّرات. كل ما عداه — التوكنات، رقم الإرسال، اسم القالب لدى ميتا، الرصيد — مسؤولية مدعوم.

مسار الرسالة
تطبيقك  →  Integration API  →  حلّ القالب  →  طبقة الرسائل  →  WhatsApp Cloud API

الفرق بينها وبين أداة send_whatsapp

أداة MCP send_whatsappIntegration API
المصادقةAuthorization: Bearer مفتاح APIX-API-Key مفتاح تكامل مستقل
البروتوكولJSON-RPC على /mcpREST على /v1/…
رسائل نصية حرّةنعملا — قوالب فقط
منع التكرارغير مدعومإجباري ومضمون في القاعدة
صندوق رمليلانعم — بتبديل المفتاح فقط
اختيار رقم الإرسالعبر phone_number_idمثبّت من جهة مدعوم — لا يختاره المتصل
مناسبة لـاستخدام سريع داخل تكامل MCP قائمتكامل إنتاجي يرسل قوالب بكثافة

العنوان الأساسي

https://srnelrdpqkcntbgudyto.supabase.co/functions/v1/integrations-api

كل المسارات تحت /v1/…، فالمسار الكامل للإرسال مثلًا:

POST …/functions/v1/integrations-api/v1/messages/template

الدالة تقبل كذلك بادئة /api (/api/v1/messages/template) لو اتحطّ نطاق مخصّص أمامها لاحقًا — فكود المتصل ما يتغيّرش عند نقل النطاق.

مفتاح التكامل سرّ خادم: لا يوضع في متصفح ولا تطبيق موبايل ولا مستودع كود. الـ CORS مضبوط عمدًا على وضع لا يسمح باستدعاء الواجهة من صفحة ويب.

ربط رقم واتساب #

قبل أي إرسال، لازم يكون فيه رقم واتساب مربوط بحساب مدعوم. الربط بيتم من لوحة واتساب بتدفّق Meta Embedded Signup — مش عبر الـ API العام.

تسجيل الدخول بميتا (Embedded Signup)

من لوحة واتساب بتفتح نافذة ميتا بصلاحيتَي whatsapp_business_management وwhatsapp_business_messaging. ميتا بترجّع code ومعاه sessionInfo فيه waba_id وphone_number_id.

تبادل الكود — POST /functions/v1/exchange-token

اللوحة بتبعت الكود للدالة، وهي بتبادله مع ميتا وبتخزّن توكن الوصول مشفّرًا في صف التكامل. الدالة بتتطلب جلسة Supabase (Authorization بجلسة المستخدم) — بلا ترويسة بترجع 401 بالشكل {"code":"UNAUTHORIZED_NO_AUTH_HEADER"}.

تسجيل الرقم — POST /functions/v1/register-whatsapp

جسم الطلب { "phoneNumberId": "…" } بجلسة المستخدم. الدالة بتقرأ حالة الرقم من Graph v25.0، وبتسجّله بـ PIN مولّد تلقائيًا لو محتاج تسجيل، وبتشترك في webhooks الـ WABA.

التحقق من الاشتراك في الـ Webhooks

الدالة بتقرأ الحقول المشتركة فعليًا وبترجّعها، وبتنبّه على أي حقل ناقص من المطلوبة: messages، smb_message_echoes، smb_app_state_sync.

تفعيل التكامل (من فريق مدعوم)

بيتسجّل تطبيقك كـ integration client، وبيتصدرلك مفتاح mad3_test_… للاختبار ثم mad3_live_… للإنتاج.

دوال exchange-token وregister-whatsapp وwhatsapp-phone-status كلها بمصادقة جلسة مستخدم وتُستدعى من اللوحة، لا من تطبيقك الخارجي. مذكورة هنا لتفهم التدفّق كاملًا — لكن تكاملك يبدأ من مفتاح mad3_….

لو الرقم مش مربوط بالتكامل، كل محاولة إرسال بترجع CHANNEL_NOT_CONNECTED بحالة 409.

مفاتيح التكامل #

ترويسة واحدة على كل المسارات عدا /v1/health:

Header
X-API-Key: mad3_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

ويُقبل كذلك Authorization: Bearer <key> لمن يفضّله. شكل المفتاح: mad3_live_ أو mad3_test_ متبوعًا بـ 20–120 حرفًا من A-Z a-z 0-9 _ -.

القاعدةالتفصيل
مستقل تمامًاليس Supabase JWT ولا anon key ولا service role. لا يفتح شيئًا غير هذه الواجهة. أي قيمة تبدأ بـeyJ تُرفض شكلًا قبل أي استعلام.
البيئة من الصف لا النصmad3_live_… يرسل فعلًا، mad3_test_… صندوق رملي. القرار من صف المفتاح في القاعدة — النص تلميح للبشر فقط.
التخزينيُخزَّن SHA-256 فقط. القيمة الكاملة تظهر مرة واحدة وقت الإنشاء ولا يمكن استرجاعها.
التدويرأنشئ مفتاحًا جديدًا، انقل التطبيق إليه، ثم أبطل القديم — بلا انقطاع.
الإبطالفوري. المفتاح المُبطَل يرد API_KEY_REVOKED.
الانتهاءاختياري. بعده API_KEY_EXPIRED.
النطاقاتمفتاح بلا نطاقات محدّدة = النطاق الافتراضي. مفتاح بنطاقات لا تشمل messages:send يرد INSUFFICIENT_SCOPE.

GET /v1/health #

فحص حياة الخدمة. بلا مصادقة، ولا يكشف أي شيء عن حسابك.

GET /v1/health
Request
curl
curl https://srnelrdpqkcntbgudyto.supabase.co\
/functions/v1/integrations-api/v1/health
Response
200 OK
{
  "status": "ok",
  "service": "integrations-api",
  "version": "v1"
}

كل رد من الواجهة بيحمل ترويسة x-request-id — اذكرها عند الإبلاغ عن أي مشكلة. تقدر تبعت واحدة من عندك وهتترجّعلك.

GET /v1/templates #

بيرجّع القوالب المتاحة لتكاملك بعقودها. استخدمه كمصدر الحقيقة لأسماء المتغيّرات قبل أول إرسال — القوالب بتتغيّر من جهة مدعوم بلا تغيير في كودك.

GET /v1/templates

Authentication

Header
X-API-Key: mad3_live_…
Request
curl
curl -H "X-API-Key: $MAD3OOM_API_KEY" \
  "https://srnelrdpqkcntbgudyto.supabase.co/functions/v1/integrations-api/v1/templates"
Response — 200 OK
القالب المسجَّل حاليًا
{
  "success": true,
  "environment": "live",
  "templates": [
    {
      "template": "time_getting",
      "channel": "whatsapp",
      "language": "ar",
      "category": "utility",
      "required_variables": [
        "tenant_name",
        "collection_phone"
      ],
      "optional_variables": [],
      "status": "active"
    }
  ]
}

القالب time_getting حالته active ومربوط بقالب معتمد لدى ميتا، ومتغيّراته المطلوبة: tenant_name وcollection_phone. تحقّق دائمًا بنداء هذا المسار قبل الإرسال — لا تثبّت القائمة في كودك.

حالات القالب

الحالةفي liveفي sandbox
activeيعمليعمل
draftTEMPLATE_NOT_ACTIVEيعمل
inactiveمرفوضمرفوض

لا يظهر في الرد أي اسم قالب لدى المزوّد ولا معرّف رقم — العقد فقط.

POST /v1/messages/template #

طلب إرسال رسالة واتساب بقالب معتمد.

POST /v1/messages/template

Authentication

Headers
X-API-Key: mad3_live_…
Content-Type: application/json

Request body

الحقلالنوعمطلوبالوصف
templatestringمطلوبالاسم المنطقي للقالب. حروف صغيرة وأرقام و_ فقط.
recipientstringمطلوبرقم بصيغة E.164 مثل +201001234567 (علامة + اختيارية). الرقم المحلي الذي يبدأ بصفر — مثل 01001234567 — يُرفض ولا يُخمَّن.
variablesobjectحسب القالبمفاتيح نصية ⇄ قيم نصية أو رقمية. المتغيّر الناقص والزائد كلاهما يُرفض.
idempotency_keystringمطلوب8–200 حرفًا من A-Z a-z 0-9 . _ : -. راجع منع التكرار.
metadataobjectاختياريحتى 20 مفتاحًا، قيم بسيطة ≤ 200 حرف. للتتبّع فقط، وخارج حساب تطابق التكرار.

قائمة الحقول مغلقة. أي حقل آخر — phone_number_id، provider، access_token، اسم القالب لدى المزوّد — يُرفض الطلب بسببه بـINVALID_REQUEST. المتصل لا يختار المزوّد ولا الرقم ولا قالب ميتا.

Request
{
  "template": "time_getting",
  "recipient": "+201001234567",
  "variables": {
    "tenant_name": "أحمد محمد",
    "collection_phone": "01000000000"
  },
  "idempotency_key": "visit_827_tenant_55_2026-09-19",
  "metadata": {
    "source": "aqar",
    "event": "visit_scheduled"
  }
}
curl -X POST \
  "https://srnelrdpqkcntbgudyto.supabase.co/functions/v1/integrations-api/v1/messages/template" \
  -H "X-API-Key: $MAD3OOM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "template": "time_getting",
    "recipient": "+201001234567",
    "variables": {
      "tenant_name": "أحمد محمد",
      "collection_phone": "01000000000"
    },
    "idempotency_key": "visit_827_tenant_55_2026-09-19"
  }'
const BASE = "https://srnelrdpqkcntbgudyto.supabase.co"
  + "/functions/v1/integrations-api";

const res = await fetch(`${BASE}/v1/messages/template`, {
  method: "POST",
  headers: {
    "X-API-Key": process.env.MAD3OOM_API_KEY,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    template: "time_getting",
    recipient: "+201001234567",
    variables: {
      tenant_name: "أحمد محمد",
      collection_phone: "01000000000",
    },
    // نفس الحدث ⇒ نفس المفتاح، دائمًا
    idempotency_key: `visit_${visitId}_tenant_${tenantId}_${dueDate}`,
  }),
});

const body = await res.json();
if (!body.success) {
  // تفرّع على error.code وحده — لا على النص
  if (body.error.retryable) scheduleRetry();
  else throw new Error(body.error.code);
}
Response — 202 Accepted
نجاح
{
  "success": true,
  "message_id": "msg_9f2c1ab34d5e6f708192a3b4",
  "status": "queued",
  "template": "time_getting",
  "recipient": "+2010*****567",
  "environment": "live",
  "sandbox": false,
  "idempotency_key": "visit_827_tenant_55_2026-09-19",
  "idempotent_replay": false
}
خطأ
400 Bad Request
{
  "success": false,
  "error": {
    "code": "MISSING_TEMPLATE_VARIABLE",
    "message": "One or more required template variables are missing.",
    "retryable": false,
    "details": {
      "template": "time_getting",
      "missing_variables": ["collection_phone"],
      "required_variables": [
        "tenant_name", "collection_phone"
      ]
    }
  }
}

حقول رد النجاح

الحقلالمعنى
message_idمعرّف مدعوم للرسالة — ليس معرّف واتساب. خزّنه للمراجعة. في الصندوق الرملي يبدأ بـmsg_test_.
statusqueued — الطلب قُبل ودخل طابور الإرسال.
recipientيعود مقنّعًا دائمًا (+2010*****567). الواجهة لا تعيد الرقم كاملًا أبدًا.
environmentlive أو test — من صف المفتاح.
sandboxtrue يعني لم يُنادَ واتساب ولم يُخصم رصيد.
idempotent_replaytrue يعني الطلب نُفِّذ من قبل ولم يُرسَل ثانيةً — والحالة 200 بدل 202.

منع التكرار (Idempotency) #

idempotency_key إلزامي. اجعله معبّرًا عن الحدث نفسه لا عن لحظة الإرسال.

اختيار المفتاح
rent_due_property_827_tenant_55_2026-09-01   ✅ نفس الحدث ⇒ نفس المفتاح
send_1724180000123                           ❌ يتغيّر كل محاولة — بلا فائدة
الحالةHTTPالاستجابة
أول طلب202يُرسَل فعليًا
تكرار بنفس الحمولة بعد النجاح200نفس message_id مع idempotent_replay: true — بلا إرسال ثانٍ
تكرار أثناء التنفيذ409REQUEST_IN_PROGRESS (قابل لإعادة المحاولة)
نفس المفتاح بحمولة مختلفة409IDEMPOTENCY_KEY_CONFLICT
إعادة محاولة بعد فشل مؤقت—يُعاد التنفيذ بنفس المفتاح
إعادة محاولة بعد فشل دائم—يُعاد نفس الخطأ بلا إرسال

الحماية مفروضة في قاعدة البيانات (قيد فريد على client_id + idempotency_key) لا في كود التطبيق — فطلبان متزامنان تمامًا لا يمكن أن يُرسلا رسالتين. وmetadata خارج حساب التطابق: تغييرها وحدها لا يكسر إعادة المحاولة.

سياسة إعادة المحاولة

  • أعد المحاولة فقط عند retryable: true — وهي 429، 5xx، 402، وREQUEST_IN_PROGRESS.
  • استخدم نفس idempotency_key — هذا بالضبط ما يمنع الازدواج.
  • تراجع أُسّي: 2s، 4s، 8s، 16s — بحد أقصى 5 محاولات.
  • على 429 احترم ترويسة Retry-After بالثواني.
  • رموز جديدة قد تُضاف لاحقًا — عامل الرمز غير المعروف كخطأ غير قابل لإعادة المحاولة ما لم يقل retryable: true.

رموز أخطاء WhatsApp #

تفرّع على error.code وحده. النص (message) للبشر وقد يتغيّر أو يُترجَم؛ الرمز لا يتغيّر معناه أبدًا بعد نشره. وكل رمز مقترن بحالة HTTP واحدة فقط.

CodeHTTPRetryableمتى
MISSING_CREDENTIALS401لالا ترويسة مفتاح
INVALID_API_KEY401لامفتاح غير معروف أو مشوّه
API_KEY_REVOKED401لامفتاح مُبطَل
API_KEY_EXPIRED401لاانتهت صلاحيته
INTEGRATION_SUSPENDED403لاالتكامل موقوف من مدعوم
INSUFFICIENT_SCOPE403لاالمفتاح لا يملك messages:send
INVALID_JSON400لاجسم الطلب ليس JSON
INVALID_REQUEST400لاحقل غير معروف أو شكل خاطئ
MISSING_RECIPIENT400لالا رقم مستقبِل
INVALID_RECIPIENT400لاالرقم ليس E.164
MISSING_IDEMPOTENCY_KEY400لالا مفتاح تكرار
INVALID_IDEMPOTENCY_KEY400لاالطول خارج 8–200 أو حروف غير مقبولة
TEMPLATE_NOT_FOUND404لالا قالب بهذا الاسم
TEMPLATE_NOT_ACTIVE409لاالقالب مسوّدة أو موقوف
TEMPLATE_NOT_ALLOWED403لاالقالب خارج قائمة سماح هذا التكامل
TEMPLATE_NOT_LINKED409لاالقالب لم يُربط بقالب معتمد لدى المزوّد
MISSING_TEMPLATE_VARIABLE400لامتغيّر مطلوب ناقص
UNKNOWN_TEMPLATE_VARIABLE400لامتغيّر لا يعرّفه القالب
INVALID_TEMPLATE_VARIABLE400لاقيمة غير مقبولة (أسطر جديدة، طول مفرط…)
IDEMPOTENCY_KEY_CONFLICT409لانفس المفتاح بحمولة مختلفة
REQUEST_IN_PROGRESS409نعمطلب مطابق ما زال قيد التنفيذ
RATE_LIMITED429نعمتجاوز الحد — راجع Retry-After
INSUFFICIENT_BALANCE402نعمرصيد واتساب لحساب مدعوم لا يكفي
CHANNEL_NOT_CONNECTED409لالا رقم واتساب مربوط لهذا التكامل
PROVIDER_ERROR502نعمعطل أو حد مؤقت لدى المزوّد
PROVIDER_TIMEOUT504نعملم يرد المزوّد في المهلة
PROVIDER_REJECTED422لارفض دائم (الرقم لا يستقبل واتساب، القالب غير معتمد…)
NOT_FOUND404لامسار غير معروف
METHOD_NOT_ALLOWED405لافعل HTTP خاطئ على مسار صحيح
INTERNAL_ERROR500نعمخطأ غير متوقّع

الصندوق الرملي #

مفتاح mad3_test_… بيشغّل المسار كاملًا — مصادقة، حدّ معدل، تحقّق من القالب والمتغيّرات والرقم، منع تكرار، استجابة — بلا نداء واتساب، بلا رسالة حقيقية، بلا خصم رصيد.

  • environment: "test" وsandbox: true في الرد.
  • message_id بيبدأ بـmsg_test_.
  • القوالب في حالة draft بتشتغل في الصندوق الرملي فقط — فتقدر تبني على عقد قالب قبل اعتماده في واتساب.
  • الانتقال للإنتاج بتبديل المفتاح فقط — لا تغيير في الكود ولا في المسار.

التكلفة والرصيد

الإعدادالقيمة الحاليةالمعنى
template_charge_egp0.282تكلفة رسالة القالب بالجنيه
text_charge_egp0تكلفة الرسالة النصية عبر أداة MCP
wallet_charge_enabledtrueالخصم من المحفظة مفعّل
wallet_enforce_sendfalseنقص الرصيد يُسجَّل تحذيرًا ولا يمنع الإرسال حاليًا — قياس أولًا
default_rate_limit_per_minute60حد الطلبات لكل تكامل
default_rate_limit_burst20الدفعة المسموحة

القيم دي إعدادات منصّة قابلة للتغيير من الإدارة — لا تثبّتها في كودك. تعامل مع INSUFFICIENT_BALANCE كخطأ محتمل دائمًا حتى لو الفرض مُعطَّل اليوم.

Webhooks #

الرسائل الواردة وحالات التسليم بتوصل من ميتا على webhook مدعوم، وبتتخزّن في حسابك وتشغّل الأتمتة. ميتا هي اللي بتنادي المسار ده، مش تطبيقك.

GET POST https://srnelrdpqkcntbgudyto.supabase.co/functions/v1/whatsapp-webhook

١. مصافحة التحقّق — GET

ميتا بتنادي المسار بـGET ومعاه ثلاثة باراميترات. لو hub.mode=subscribe وhub.verify_token طابق التوكن المضبوط، بيرجع hub.challenge نصًّا خامًا بحالة 200. غير كده 403 Forbidden.

Verification request
GET /functions/v1/whatsapp-webhook
      ?hub.mode=subscribe
      &hub.verify_token=<WEBHOOK_VERIFY_TOKEN>
      &hub.challenge=1158201444

200 OK
1158201444

٢. استقبال الأحداث — POST

الحمولة بشكل Meta Cloud API القياسي. الخادم بيمرّ على كل entry وكل changes داخلها — لا يكتفي بالأول — وبيعالج كل value بمعزل عن الباقي، فخطأ في واحدة لا يُسقط البقية.

شكل الحمولة
{
  "object": "whatsapp_business_account",
  "entry": [
    {
      "id": "<WABA_ID>",
      "changes": [
        {
          "field": "messages",
          "value": {
            "messaging_product": "whatsapp",
            "metadata": { "phone_number_id": "…" },
            "messages": [ … ],
            "statuses": [ … ]
          }
        }
      ]
    }
  ]
}

الـPOST بيرجع 200 OK دائمًا — حتى لو فشلت معالجة حدث داخلي. ده مقصود: ميتا بتوقف التسليم لو استقبلت أخطاء متكررة، فالأخطاء بتتسجّل في اللوج بدل ما تكسر التسليم.

٣. الحقول المطلوب الاشتراك فيها

عند ربط الرقم بيتم الاشتراك على مستوى الـ WABA، وبيتقرا الاشتراك الفعلي للتأكد. الحقول المطلوبة:

الحقلالغرض
messagesالرسائل الواردة وحالات التسليم
smb_message_echoesصدى الرسائل المُرسَلة من تطبيق واتساب نفسه (وضع التعايش)
smb_app_state_syncمزامنة حالة تطبيق واتساب (وضع التعايش)

نقص أي حقل من دول بيظهر كتحذير وقت الربط بدل ما يتحوّل لمشكلة «الرسائل مش بتوصل» بعد أيام.

واجهات واتساب في اللوحة #

الدوال دي جزء من منظومة واتساب لكنها ليست واجهة عامة للمطوّرين. موثّقة هنا عشان تعرف الحدود بالضبط.

POST /functions/v1/send-whatsapp

نسخة REST من أداة send_whatsapp، بنفس مصادقة مفاتيح API وبنفس صلاحية whatsapp:send.

الحقلمطلوبملاحظة
toمطلوبناقصه ⇒ 400 «الحقل to مطلوب»
message_typeمطلوبtext أو template — غير ذلك 400
textمع textمطلوب عند message_type=text
template.nameمع templateمطلوب عند message_type=template
phone_number_idاختيارييُستنتَج لو الحساب مربوط برقم واحد

الردود المتحقَّق منها: بلا ترويسة ⇒ 401 {"error":"Missing Authorization header"}، وGET ⇒ 405 {"error":"Only POST is supported"}. عند النجاح بيرجع نفس كائن { ok, status, data }، وعند فشل المزوّد بيرجع حالة المزوّد نفسها أو 502.

POST /functions/v1/whatsapp-graph-request

وسيط محروس لعمليات Graph من اللوحة — بمصادقة جلسة مستخدم، مش بمفتاح API. جسم الطلب { "operation": "…", "payload": { … } }، أو multipart/form-data لرفع الوسائط.

العمليات المسموحة حصرًا (أي غيرها ⇒ 400 unsupported_operation):

sendText · sendMedia · uploadMedia · sendTemplate · getTemplates · createTemplate · deleteTemplate · getBillingStatus · updateBusinessProfile · verifyContacts

POST /functions/v1/register-whatsapp وwhatsapp-phone-status

بمصادقة جلسة مستخدم كذلك. بلا ترويسة بيرجعوا 401 بالشكل {"code":"UNAUTHORIZED_NO_AUTH_HEADER","message":"Missing authorization header"}.

لا تبني تكاملًا خارجيًا على الدوال بمصادقة الجلسة — مفيش طريقة مدعومة تحصل بيها على جلسة مستخدم من خادمك. للتكامل الإنتاجي استخدم Integration API.

ما لا تفعله واجهة WhatsApp

  • لا تعطي وصولًا لأي جدول في قاعدة البيانات، ولا تقبل أي استعلام.
  • لا تقبل ولا تعيد توكنات المزوّد.
  • لا تسمح باختيار المزوّد أو رقم الإرسال أو قالب ميتا.
  • لا تعيد رقم المستقبِل كاملًا ولا محتوى الرسائل.
  • لا تصل إلى واجهات الإدارة الداخلية في مدعوم.

غلاف جاهز (SDK) #

كلاس بسيط بيلفّ الـ JSON-RPC، يعمل الـ parsing، ويطلّع أخطاء واضحة — أسهل من إعادة كتابة نفس الكود كل مرة.

class Mad3oomClient {
  constructor(token, baseUrl = "https://mad3oom.com/mcp") {
    this.token = token;
    this.baseUrl = baseUrl;
  }

  async call(method, params) {
    const res = await fetch(this.baseUrl, {
      method: "POST",
      headers: {
        "Authorization": `Bearer ${this.token}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        jsonrpc: "2.0", id: Date.now(), method, params,
      }),
    });

    if (res.status === 429) {
      throw Object.assign(new Error("Rate limited"), { retryable: true });
    }

    const data = await res.json();
    if (data.error) {
      throw new Error(`[${data.error.code}] ${data.error.message}`);
    }
    return data.result;
  }

  async tool(name, args = {}) {
    const result = await this.call("tools/call", { name, arguments: args });
    // نتيجة الأداة نص JSON داخل content[0].text
    return JSON.parse(result.content[0].text);
  }

  listTools() {
    return this.call("tools/list").then(r => r.tools);
  }
}

// الاستخدام
const client = new Mad3oomClient(process.env.MAD3OOM_TOKEN);
const tickets = await client.tool("list_tickets", { status: "open", limit: 10 });
const stats   = await client.tool("get_dashboard_stats");
import json, os, requests


class Mad3oomClient:
    def __init__(self, token, base_url="https://mad3oom.com/mcp"):
        self.token = token
        self.base_url = base_url

    def call(self, method, params=None):
        resp = requests.post(
            self.base_url,
            headers={
                "Authorization": f"Bearer {self.token}",
                "Content-Type": "application/json",
            },
            json={"jsonrpc": "2.0", "id": 1,
                  "method": method, "params": params},
            timeout=30,
        )
        if resp.status_code == 429:
            raise RuntimeError("Rate limited (60 req/min)")

        data = resp.json()
        if "error" in data:
            err = data["error"]
            raise RuntimeError(f"[{err['code']}] {err['message']}")
        return data["result"]

    def tool(self, name, args=None):
        result = self.call("tools/call",
                           {"name": name, "arguments": args or {}})
        # نتيجة الأداة نص JSON داخل content[0]["text"]
        return json.loads(result["content"][0]["text"])

    def list_tools(self):
        return self.call("tools/list")["tools"]


# الاستخدام
client = Mad3oomClient(os.environ["MAD3OOM_TOKEN"])
tickets = client.tool("list_tickets", {"status": "open", "limit": 10})
stats = client.tool("get_dashboard_stats")

Postman Collection #

كل الـ 18 أداة كـ requests جاهزة، مقسّمة بمجلدات حسب المجال، بالإضافة لـinitialize وtools/list. بعد التحميل، اضبط متغيّر البيئة token بمفتاحك.

المتغيّرالقيمة
base_urlhttps://mad3oom.com/mcp
tokenYOUR_API_KEY.YOUR_SECRET أو mad3oom_bt_…

اصطلاحات عامة #

الموضوعالقاعدة
المعرّفاتكل الـ ids هي UUID نصية. ticket_number وحده رقم تسلسلي للعرض — لا تستخدمه في الـ API.
التواريخISO 8601 بتوقيت UTC، مثل 2026-09-19T08:12:00+00:00.
الترميزUTF-8. النصوص العربية مدعومة في كل الحقول بلا ترميز إضافي.
الترتيبكل القوائم مرتّبة تنازليًا بـcreated_at (الأحدث أولًا) عدا الخطط — مرتّبة بـsort_order تصاعديًا.
القيم الفارغةالحقول غير المضبوطة ترجع null لا تُحذف من الرد.
CORS/mcp مفتوح (*). واجهة WhatsApp مغلقة عمدًا أمام المتصفح.
نطاق الرؤيةالأدمن يرى كل شيء. العميل العادي يرى بياناته وبيانات مستخدميه الفرعيين (super_user_id) فقط.
سجل الاستخدامكل طلب على /mcp يُسجَّل باسم الأداة المستخدَمة وكود الرد الفعلي، ويظهر في سجل الاستخدام باللوحة.

آخر التحديثات #

توحيد الدومين على mad3oom.com

  • كل نقاط OAoth والاكتشاف و/mcp بقت تُخدَم من mad3oom.com بلا أي إعادة توجيه.
  • issuer وauthorization_servers وresource كلها بقت تُعلن الدومين الرسمي — ده اللي بيخلّي عملاء MCP الصارمين يقبلوا البيانات الوصفية (RFC 9728 §3.3).
  • الدومين القديم mad3oom.online انتهى. لو عندك Connector قديم، حدّث عنوانه.

تتبّع الاستخدام

  • سجل الاستخدام بقى يسجّل اسم الأداة المستخدمة فعليًا (/mcp:tools/call:list_tickets) بدل قيمة ثابتة.
  • status_code بقى يتسجّل بالقيمة الحقيقية للرد.
  • عدّاد الاستخدام على كل مفتاح بقى يتحدّث فعليًا.

واتساب

  • مسار الإرسال عبر أداة MCP بقى يمرّ بفحص المحفظة والخصم زي باقي المسارات.
  • القالب time_getting بقى active ومربوطًا، ومتغيّراته المطلوبة tenant_name وcollection_phone.