العودة إلى التحديثات
New releaseJul 26, 2026

mcpsnoop v0.15.0

Wireshark لـ MCP. وكيل شفاف يعرض كل استدعاء أداة حقيقي بين عميل الذكاء الاصطناعي الخاص بك وخوادم MCP الخاصة بك، مباشر في الطرفية الخاصة بك.

مشاركة

mcpsnoop

Wireshark لـ MCP. وكيل شفاف يعرض كل استدعاء أداة حقيقي بين عميل الذكاء الاصطناعي الخاص بك وخوادم MCP الخاصة بك، مباشرة في الطرفية.

CI Go Reference MIT

mcpsnoop demo

المشكلة

يتصل MCP Inspector الرسمي كعميل خاص به، لذلك لا يرى أبدًا ما يرسله عميلك (Cursor أو Claude Code أو Codex) فعليًا إلى خادمك. وأي شيء ينتظر وصول طلب لا يمكنه إظهار الاستدعاء الذي لم يقم به النموذج أبدًا، أو الذي قام به بوسائط خاطئة. عندما لا يتم استدعاء أداة بصمت، أو لا تتوافق القدرات، أو يتعطل استدعاء، يتبقى لك البحث في السجلات والتخمين.

يجلس mcpsnoop في مسار البيانات الحقيقي بدلاً من ذلك. لفّ أمر الخادم الخاص بك به وشاهد كل إطار JSON-RPC مباشرة، بينما يتحدث عميلك وخادمك الحقيقيان.

بدء سريع

شاهده فورًا، دون الحاجة إلى إعداد أي شيء.```bash mcpsnoop demo

لاستخدامه فعليًا، قم بتغليف خادمك في إعدادات MCP الخاصة بعميلك.```json
{
  "mcpServers": {
    "my-server": {
      "command": "mcpsnoop",
      "args": ["--", "node", "build/index.js"]
    }
  }
}

كل ما بعد -- هو الأمر الذي يشغّل خادمك عادةً. ضع مكانه ما تستخدمه بالفعل، مثل python server.py أو npx -y @scope/server أو ملف ثنائي مُجمَّع.

في Claude Desktop، لا تحتاج إلى إجراء هذا التعديل يدويًا.```bash mcpsnoop wrap my-server # route my-server through mcpsnoop mcpsnoop unwrap my-server # put it back

`wrap` يجد `claude_desktop_config.json`، وينسخه إلى `claude_desktop_config.json.mcpsnoop.bak` في المرة الأولى، ويعيد كتابة إدخال ذلك الخادم الواحد فقط، بحيث لا يتغيّر تنسيقك ولا أيّ خادم آخر. داخل الإدخال المعاد كتابته تعود المفاتيح بترتيب أبجدي. `unwrap` يستعيد الملف، ويزيل النسخة الاحتياطية بمجرد عدم بقاء أي خادم مغلّف. أعد تشغيل Claude Desktop بعد أيٍّ من العمليتين، إذ تُطلق خوادم MCP مرة واحدة عند بدء التشغيل.

ثم استخدم عميلك كالمعتاد وافتح الواجهة.```bash
mcpsnoop

لا خيارات، لا مسارات مآخذ، لا ترتيب تشغيل يجب تذكره. تجد الطبقة الوسيطة (shim) وواجهة المستخدم بعضهما البعض من تلقاء نفسها، وتستعيد واجهة المستخدم الجلسات السابقة من القرص.

بالنسبة لخادم HTTP قابل للبث، شغّل mcpsnoop كوكيل عكسي.```bash mcpsnoop http --target http://localhost:3000/mcp --listen :7000

حالة HTTP لكل استجابة تظهر في الدفق، لذا فإن الاستجابة التي لا تحمل
أي رسالة JSON-RPC خاصة بها ما تزال إطارًا مرئيًا بدلًا من لا شيء: تحدي
401، و403 عند رفض Origin، و202 التي تؤكد إشعارًا،
و502 عندما يتعذر الوصول إلى الهدف إطلاقًا. تُحفظ ترويسة
`WWW-Authenticate` الخاصة بـ401 حرفيًا وتُعرض في المُفتش، لأنها
تشير إلى مخطط المصادقة وبيانات تعريف المورد الذي يجب الانتقال إليه بعد ذلك.
صفِّ حسب الحالة باستخدام `status:401` في TUI، أو حسب أي فشل باستخدام
`status:err`. أي استجابة 4xx أو 5xx تُحتسب كخطأ، لذا فإن التشغيل
الافتراضي لـ`mcpsnoop check` يفشل عندها.

ليس لديك خادم خاص بك؟ [جرّبه فعليًا](https://github.com/kerlenton/mcpsnoop/blob/HEAD/docs/TRY_IT.md) مقابل خادم
اختبار منشور، مدفوعًا بعميلك الخاص. لتفقد جلسة بعد حدوثها،
انظر [مراجعة الجلسات السابقة من السجلات](https://github.com/kerlenton/mcpsnoop/blob/HEAD/docs/POST_MORTEM.md).

### ملف الإعدادات

إذا أعدت استخدام نفس أعلام shim عبر مشروع، ضعها في ملف
`.mcpsnoop.toml` في دليل العمل الحالي.```toml
label = "filesystem"
trace-file = "trace.jsonl"
redact-secrets = true
redact-key = "token,authorization"
redact-value = "sk-[A-Za-z0-9]+"
redact-path = "$.params.arguments.password"
no-trace = false

كرّر redact-key وredact-value وredact-path في أسطر منفصلة لإضافة أكثر من واحد من كلٍّ منها.

هذه كل المفاتيح التي يدعمها.

يتم البحث عن الملف في دليل العمل الحالي فقط، وليس في الدلائل الأبوية.

تتجاوز خيارات سطر الأوامر الصريحة القيم الموجودة في ملف الإعدادات.

الأوامر

الأمرالوظيفة
mcpsnoop -- <server>يلفّ خادم stdio كطبقة وسيطة شفافة
mcpsnoopيفتح واجهة TUI المباشرة
mcpsnoop http --target <url>يوكّل خادم HTTP قابلًا للتدفق
mcpsnoop exportيعرض جلسة إلى json أو html أو text أو har أو otlp
mcpsnoop checkيُفشل CI عند وجود أخطاء أو إطارات غير صالحة أو تحذيرات أو عدم تطابق في التوجيه أو استدعاءات معلّقة أو نتائج متأخرة
mcpsnoop baselineيفحص تعريفات الأدوات الموثوقة أو يقبلها أو يعيد تعيينها
mcpsnoop diffيقارن الأدوات والاستدعاءات عبر جلستين ملتقطتين
mcpsnoop openيفتح جلسة محفوظة في واجهة TUI
mcpsnoop pruneيحذف سجلات الجلسات المحفوظة الأقدم من حد زمني
mcpsnoop wrap <server>يوجّه أحد خوادم Claude Desktop عبر mcpsnoop
mcpsnoop unwrap <server>يعيد إدخال ذلك الخادم إلى حالته الأصلية
mcpsnoop remote <user@host>يطبع أمر نفق SSH
mcpsnoop demoيشغّل جلسة مكتوبة مسبقًا

شغّل mcpsnoop help للحصول على القائمة الكاملة، أو mcpsnoop help <command> لمعرفة خيارات أمر واحد.

كيف يقارن

MCP Inspectormcpsnoop
يرى حركة المرور الحقيقية بين عميلك وخادمكلانعم
يرصد الاستدعاءات المعلّقة وأخطاء البثلانعم
يرصد المخرجات الشاردة التي تُفسد البثلانعم
يرصد إطارات JSON-RPC غير الصالحةلانعم
يكتشف انحراف تعريفات الأدوات بعد الموافقةلانعم
واجهة طرفية تفاعليةلانعم
بدون إعدادات، وبدون خيارات أو ترتيبلانعم
مُفحص القدراتجزئينعم
إعادة تشغيل استدعاء مُلتقطلانعم
تصدير الجلسة (json / html / text / otlp)لانعم
ملف تنفيذي واحد، بدون تبعيات تشغيللانعم

التثبيت

Go```bash

go install github.com/kerlenton/mcpsnoop/cmd/mcpsnoop@latest

### Homebrew```bash
brew install mcpsnoop

الملفات الثنائية الجاهزة لكل منصة متوفرة في صفحة الإصدارات.

إكمالات Shell

توفر mcpsnoop إكمالات لـ bash وzsh وfish وPowerShell. شغّل mcpsnoop completion <shell> --help للحصول على خطوات الإعداد، التي تغطي تفعيل الإكمال ومسار التثبيت لنظام التشغيل لديك.

كيف يعمل

يجلس mcpsnoop في الأنبوب بين عميل الذكاء الاصطناعي وخوادم MCP، ناسخًا كل إطار JSON-RPC إلى واجهة طرفية حية

يجمع mcpsnoop بين دورين في ملف ثنائي واحد. mcpsnoop -- <server> هو الغلاف الشفاف الذي يستدعيه عميلك، إذ يعيد توجيه البايتات كما هي بينما يرسل نسخة من كل إطار إلى المحور. أما mcpsnoop بدون وسائط فهو ذلك المحور وواجهة TUI الحية الخاصة به. ويتزاوجان عبر مقبس معروف وسجلات على القرص، فلا يضطر أيٌّ منهما إلى البدء أولًا.

يقوم المحور بتحميل أحدث 100 جلسة محفوظة افتراضيًا، مما يُبقي عمل بدء التشغيل محدودًا دون حذف الآثار الأقدم. استخدم mcpsnoop --history-limit N لاختيار حد آخر، أو mcpsnoop --history-limit 0 لتحميل السجل الكامل. تبقى الجلسات الأقدم متاحة عبر mcpsnoop open <session-id> و mcpsnoop export <session-id>.

يقيّد حد السجل ما يتم تحميله؛ بينما يقيّد mcpsnoop prune ما يتم الاحتفاظ به. يحذف سجلات الجلسات المحفوظة الأقدم من حد زمني معيّن، ولا يعمل أبدًا من تلقاء نفسه.```bash mcpsnoop prune --older-than 30d --dry-run # list what would go, remove nothing mcpsnoop prune --older-than 30d # delete after confirming mcpsnoop prune --older-than 72h --yes # skip the prompt in a script

`--older-than` مطلوب (لا يوجد افتراضي يحذف أي شيء) ويقبل عدد أيام مثل `30d` أو مدة Go مثل `72h`. تُترك خطوط الأساس للأدوات كما هي، لأن خط الأساس مرتبط بتسمية الخادم وليس بالجلسة.

نظرًا لأنه يقع في الأنبوب الفعلي، وليس على الجانب مثل Inspector، فإنه يرى بالضبط ما يقوله عميلك وخادمك الحقيقيان لبعضهما البعض، مهما كانت اللغة التي كُتب بها الخادم.

## اختصارات لوحة المفاتيح

| Key | Action | | Key | Action |
|---|---|---|---|---|
| `enter` | فحص / تعمق | | `/` | تصفية |
| `esc` | رجوع | | `:` | أمر |
| `j` / `k` | تحريك | | `r` | إعادة تشغيل مكالمة |
| `g` / `G` | أعلى / أسفل | | `c` | القدرات |
| `ctrl-f` / `ctrl-b` | صفحة | | `s` | ملخص الأداة |
| `p` | إيقاف مؤقت | | `y` | نسخ |
| `shift`+`<key>` | فرز حسب العمود | | `e` | تصدير |
| `ctrl-d` | حذف الجلسة | | `f` | متابعة |
| `?` | مساعدة | | | |

اضغط `?` في التطبيق للاطلاع على القائمة الكاملة.

## تصفية التدفق

اضغط `/` في جلسة وادمج الرموز المفصولة بمسافات، بعملية AND. النص العادي يطابق الأسلوب، والأداة، والمعرّف، والحمولة.

| Token | Filters by | Example |
|---|---|---|
| `tool:` | اسم الأداة | `tool:search` |
| `method:` | طريقة JSON-RPC | `method:tools/call` |
| `id:` | معرّف الطلب، وأي إعادة محاولة تكمله | `id:7` |
| `task:` | معرّف المهمة | `task:01J...` |
| `dir:` | الاتجاه (`c2s`, `s2c`) | `dir:s2c` |
| `kind:` | نوع الإطار (`req`, `resp`, `notify`, `stderr`, `invalid`) | `kind:invalid` |
| `status:` | نتيجة الاستدعاء (`ok`, `error`, `cancel`, `late`, `cancelled`, `pending`, `bad`, `warn`, `mismatch`, أو حالة HTTP مثل `401`) | `status:error` |

كدّس الرموز للحصول على نتائج محددة.```text
tool:search status:pending        # in-flight calls to one search tool
status:cancel                     # calls the client gave up on (status:cancelled is a cancelled task)
status:late                       # results that arrived after the cancellation
method:tools/call status:error    # tool calls that failed
dir:s2c kind:req                  # server-initiated requests (servers before 2026-07-28)

الآخر لا يجد شيئًا إلا على خادم يتحدث بروتوكول 2025-11-25 أو إصدارًا أقدم. وقد أزال تنقيح 2026-07-28 الطلبات المبدوءة من الخادم، والخادم الذي يحتاج شيئًا من العميل يرد الآن على طلب العميل نفسه بطلب ذلك، ثم يعيد العميل المحاولة. يربط mcpsnoop هذه المحاولات المعاد تنفيذها بالطلب الذي تستكمله، فتظهر العملية كاستدعاء واحد بدلاً من عدة استدعاءات.

تصدير الجلسات

حوّل أي جلسة تم التقاطها إلى ملف قابل للنقل.```bash mcpsnoop export -T json|html|text|har|otlp [-o file|-] [session-id|log.jsonl|-]

| الصيغة | ما تحصل عليه |
|---|---|
| `json` | استدعاءات مترابطة، وأعداد لكل أداة مع زمن استجابة p50/p95/p99، وأبطأ الاستدعاءات، والقدرات، والإطارات الخام |
| `html` | ملف متصفح مستقل بذاته مع بحث وJSON قابل للطي |
| `text` | تفريغ نص عادي أنيق ومهيأ |
| `har` | إدخال واحد لكل استدعاء مترابط، قابل للفتح في أدوات مطوري المتصفح وأي شيء آخر يقرأ HAR |
| `otlp` | JSON بصيغة OTLP مع span لكل استدعاء مترابط؛ ينضم سياق تتبع W3C إلى تتبعات المتصل، وإلا يُستخدم تتبع واحد لكل جلسة |

إن MCP ليس HTTP، لذا فإن عنوان URL ورمز الحالة والتوقيتات في إدخال HAR هي تعيين مقصود لكل استدعاء وليست تسجيلًا حرفيًا لما يُرسل عبر الشبكة.

بالنسبة إلى OTLP، يوفّر `_meta.traceparent` الخاص بالطلب معرف التتبع ومعرف الـ span الأصلية لذلك الاستدعاء، ويُرافق `_meta.tracestate` الـ span. عندما يكون traceparent غائبًا أو غير صالح، يحتفظ mcpsnoop بالتتبع المشتق من الجلسة ولا يحمل أي حالة. يراقب mcpsnoop ولا يشارك، لذا لا يضيف أي إدخال مورّد خاص به، ويمرر حالة المتصل دون تغيير.```bash
mcpsnoop export -T html -o out.html                    # an HTML file to open in a browser
mcpsnoop export -T text server.py-48213-7f3a1c9e2b04   # a specific session, as text
mcpsnoop export -T json | jq                           # the newest session, piped to jq
mcpsnoop export -T har -o session.har                  # a HAR file to open in browser devtools
mcpsnoop export -T otlp -o trace.json                  # import into an OTLP-compatible tracing backend

احذف -o للكتابة إلى stdout، واحذف الجلسة لأخذ الأحدث، أو مرّر - لقراءة JSONL من stdin. في واجهة TUI، اضغط e لتصدير الجلسة المحددة بتنسيق HTML، أو شغّل :export json|html|text|har|otlp [path] من وضع الأوامر.

لتنظيف تسجيل موجود قبل فحصه أو مشاركته، مرّر نفس علامات التنقيح المستخدمة أثناء التسجيل إلى export أو open:```bash mcpsnoop export session.jsonl --redact-secrets --redact-key project_token -o shared.json mcpsnoop open session.jsonl --redact-path '$.params.arguments.password'

هذه الخيارات تعيد كتابة الملف المُصدَّر أو عرض TUI في الذاكرة، ولا تمسّ المصدر JSONL أبدًا. يرفض `export` مخرجًا يسمّي نفس ملف الإدخال، ويكتب عبر ملف مؤقت يُعادت تسميته ليحل مكانه، لذا فإن أي تشغيل يفشل يُبقي الملف السابق سليمًا.

`inputSchema` و`outputSchema` الخاصان بأداة، كما هو معلن في نتيجة `tools/list`، تتركهما `--redact-key` و`--redact-secrets` دون مساس. الاسم داخل المخطط هو إعلان نوع وليس قيمة، ويظل الاسم نفسه في السجل في كلتا الحالتين، وتنظيف المخطط الفرعي تحت خاصية اسمها `token` سيأخذ معه فحوصات الأداة نفسها. الاستثناء هو ذلك الموضع فقط، لذا فإن أي وسيط يصادف أن اسمه `inputSchema` يُنظَّف مثل أي وسيط آخر، ويتوقف التنظيف عند `default` و`const` و`examples` و`enum` التي تحمل بيانات لا بنية. استخدم `--redact-path` لتسمية شيء داخل المخطط، أو `--redact-value` التي تطابق النص أينما وُجد باستثناء الكلمتين المفتاحيتين اللتين يحللهما mcpsnoop، وهما `type` و`x-mcp-header`.

يختلف ما يصل إليه كل خيار، لذا تحقق من النتيجة بدلًا من الافتراض. جميع الخيارات الأربعة تنظّف حمولات JSON-RPC، ولا تصل `--redact-key` و`--redact-path` و`--redact-secrets` إلا إلى تلك الحمولات فقط. أما `--redact-value` وحدها فهي التي تنظّف أيضًا stderr، والنصوص الأخرى غير JSON، وداخل السلسلة النصية. تُنظَّف ترويسة `Mcp-Param-*` إلى جانب قيمة الجسم التي تعكسها؛ أما بقية بيانات الغلاف الوصفية، وتسميات الخادم، و`Mcp-Name` و`Mcp-Method` وحالة HTTP، فتُترك كما اُلتُقطت. التنظيف هو جهد بأفضل ما يمكن، لذا استخدم مسار إخراج منفصل واقرأ النتيجة قبل مشاركتها.

### بثّ الاستدعاءات المكتملة إلى مجمّع OTLP

أرسل spans بينما يعمل البروكسي بتوجيهه إلى نقطة نهاية traces بصيغة OTLP/HTTP JSON. كرّر `--otlp-header` من أجل مصادقة المجمّع أو ترويسات المستأجر.```bash
mcpsnoop \
  --otlp-endpoint http://localhost:4318/v1/traces \
  --otlp-header "Authorization=Bearer $OTLP_TOKEN" \
  -- node build/index.js

mcpsnoop http \
  --target http://localhost:3000/mcp \
  --otlp-endpoint http://localhost:4318/v1/traces

التسليم وفق أفضل جهد ولا يحجب أبدًا حركة مرور MCP المُمرَّرة عبر البروكسي. إذا كان المجمّع غير متاح، يعيد mcpsnoop المحاولة في الخلفية ويتجاهل إطارات التتبع الجديدة عندما تمتلئ قائمة الانتظار المحدودة. يبقى سجل الجلسة JSONL العادي هو السجل الدائم.

مقارنة الجلسات

قارن جلستين محفوظتين بالمعرّف أو بمسار JSONL.```bash mcpsnoop diff before-session after-session mcpsnoop diff old.jsonl new.jsonl

يعرض التقرير الأدوات التي أُضيفت أو أُزيلت، وتغييرات الوصف و`inputSchema`
واستدعاءات الأدوات المطابقة التي تغيّرت حالتها، وتحولات المدة الملحوظة. تُطابق
الاستدعاءات حسب اسم الأداة والوسائط، لذا فإن الاستدعاءات المعاد ترتيبها ما تزال تُقارن بشكل صحيح.
افتراضيًا، يجب أن تختلف تغييرات المدة بمقدار 100 مللي ثانية على الأقل وبمعدل 2x؛
استخدم `--duration-threshold` و`--duration-ratio` لضبط هذه الحدود.

مرّر `--exit-code` لربط CI بالانحدارات: يخرج برمز غير صفري عندما تحذف الجلسة
اللاحقة أداة، أو تغيّر وصف أداة أو عنوانها أو مخطط الإدخال أو مخطط الإخراج
أو التعليقات التوضيحية، أو تحتوي على استدعاء ساءت حالته، أو تتباطأ.
التحسينات (أدوات مضافة، استدعاءات مصححة، تسريعات) ما تزال تخرج برمز صفري، وكذلك تغيير
الأيقونة، الذي يعدّل مظهر الأداة دون تغيير وظيفتها.

## فحص الجلسات في CI

اربط تشغيل وكيل مسجّل بالأخطاء، أو تلف الدفق، أو تحذيرات البروتوكول، أو
عدم تطابق ترويسات التوجيه، أو الاستدعاءات التي لم تحصل على استجابة، أو الإطارات المسقطة التي
تترك الالتقاط غير مكتمل، أو انجراف تعريف الأداة، أو استخدام ميزات البروتوكول
المهجورة.```bash
mcpsnoop check [--format text|junit|sarif] [--fail-on error,invalid,warn,mismatch,pending,late-result,drift,deprecated,incomplete,schema] [session-id|log.jsonl|-]

error و invalid و warn تُفشِل الفحص من تلقاء نفسها. أما الباقي فهو اختياري. مرّر مجموعة فرعية مفصولة بفواصل لتقتصر معايير الإخفاق على ما يهمّ الوظيفة فقط، أو احذف الجلسة لفحص أحدث التقاط، أو استخدم - لقراءة JSONL من stdin.

الإشارةتفشل عند
errorاستدعاء تمّ الرد عليه بخطأ JSON-RPC، أو نتيجة موسومة بـ isError، أو مهمة انتهت بفشل
invalidإطار على قناة البروتوكول ليس JSON-RPC صالحًا، عادةً ما يكون تسجيل خادم إلى stdout
warnإطار يخالف توقعًا تحدده مواصفة MCP أو JSON-RPC
mismatchترويسة توجيه تتعارض مع محتوى الرسالة، أو ترافق دفعة، أو تكون مفقودة حيث تتطلبها المراجعة
pendingطلب ما زال مفتوحًا عند انتهاء الالتقاط، فتُرِك المتصل بانتظاره
late-resultاستجابة وصلت بعد إلغاء طلبها
driftتغيّر تعريف أداة معلن بعد الموافقة على خط الأساس
deprecatedميزة أعلنت المواصفة إهمالها
incompleteإطارات أُسقطت في مرحلة سابقة، ما يجعل كل عدّ آخر حدًّا أدنى لا إجمالًا
schemaمخطط معلن يستخدم بنية أو لهجة لا تنتقل جيدًا بين العملاء

تُحتسب كل إشارة سواء كانت ضمن معايير الإخفاق أم لا، لذا فإن التشغيل يُظهر ما وجده قبل أن تقرر ما الذي ينبغي أن يفشل عنده.``` session build-agent: errors=1 invalid=0 warnings=0 mismatches=0 pending=0 late_results=0 deprecated=0 missing_frames=0 schema_findings=1 schema findings: oneOf: search check failed: error

يُرافق عددُ الإطارات المسقطة القطعَ الأثرية أيضًا، لذا فإن أي التقاطٍ
يُقلِّل من العدد يُفصح عن ذلك أينما فُتح: `missing_frames` في تصدير
JSON، و`log.comment` في HAR، وسمة المورد `mcpsnoop.session.missing_frames`
في OTLP.```bash
mcpsnoop check build-agent
mcpsnoop check --fail-on error,invalid artifacts/session.jsonl
mcpsnoop check --fail-on mismatch gateway-run.jsonl

إلى جانب تعداد الإشارات، تحقّق من شكل التشغيل. تتضافر هذه الخيارات مع بعضها البعض ومع --fail-on، وأي فشل ينتج عنه رمز خروج غير صفري.

العلميفشل عندما
--max-duration <dur>تجاوز واحد أو أكثر من استدعاءات الأدوات المكتملة الميزانية؛ يعرض عددها وأسوأ استدعاء
--expect-tool <name>لم يتم استدعاء الأداة المسماة أبدًا (قابل للتكرار)
--forbid-tool <name>تم استدعاء الأداة المسماة (قابل للتكرار)

a contract for the run: search must run, delete must not, nothing over 2s

mcpsnoop check --expect-tool search --forbid-tool delete --max-duration 2s run.jsonl

### أبلغ عنه حيث ينظر CI بالفعل

`--format junit` يكتب عنصر `<testcase>` واحدًا لكل إشارة وجلسة، وتتبع حالات فشله
نفس تحديد `--fail-on` المستخدم في إخراج النص.```yaml
- name: Check captured MCP session
  run: |
    mkdir -p test-results
    mcpsnoop check --format junit artifacts/session.jsonl > test-results/mcpsnoop.xml
- name: Upload mcpsnoop JUnit report
  if: always()
  uses: actions/upload-artifact@v4
  with:
    name: mcpsnoop-junit
    path: test-results/mcpsnoop.xml

--format sarif يكتب سجل SARIF 2.1.0 بدلاً من ذلك. بينما يُبلّغ junit عن تجميع واحد لكل إشارة، يُبلّغ SARIF عن نتيجة واحدة لكل اكتشاف، حاملاً الجلسة، وSeq الخاص بالإطار، ونص الإطار الخاص بتحذيره أو انجرافه، ومشيراً إلى السطر من السجل الذي فُكّ منه الإطار. الإشارة المذكورة في --fail-on تُبلَّغ بمستوى error، والإشارة غير المذكورة فيه بمستوى note، لذلك لا يتعارض التقرير والبوابة أبداً.

تشير النتيجة إلى السجل بمسار نسبي إلى دليل العمل، والذي يحلّه code scanning بعد ذلك مقابل جذر المستودع. يظهر التنبيه بأسطره المحيطة فقط عندما يكون ذلك المسار ملفاً في الالتزام المُحلَّل، لذا فإن التقاطاً أنتجه سير العمل في artifacts/ يفتح تنبيهاً يحمل الرسالة والقاعدة ورقم السطر لكن دون عرض المصدر. إن إيداع التقاطٍ تريد عرضه بالكامل هو السبيل الوحيد للحصول على ذلك. السجل المقروء من دليل الحالة أو من stdin لا يحصل على أي مسار إطلاقاً.

يرفض code scanning أي ملف يحتوي تشغيله على أكثر من 25,000 نتيجة ويعرض فقط أول 5,000 مما يقبله، لذا يُحدَّ التقرير بسقف 5,000: الاكتشافات التي فشلت البوابة عندها أولاً، ثم نتيجة mcpsnoop/report-truncated توضح عدد ما تم استبعاده. تبقى صيغتا text وjunit كاملتين.

لوضع الاكتشافات في تبويب Security، سلّم سجل SARIF إلى upload-sarif. تحتاج المهمة إلى security-events: write، وإلا سيرد الرفع بـ 403. يخرج check برمز خروج غير صفري عند وجود اكتشاف، لذا تحتاج خطوة الرفع إلى if: always() كي تعمل أصلاً على التشغيلات التي لديها ما تُبلّغ عنه؛ وcontinue-on-error يسلّم الحكم إلى فحص code scanning، الذي يفشل عند تنبيه بمستوى error ويمكن جعله فحصاً إلزامياً. احذفها إذا كنت تفضّل أن تكون خطوة الفحص نفسها هي ما يجعل المهمة حمراء.```yaml permissions:

required for all workflows

security-events: write

only required for workflows in private repositories

actions: read contents: read

steps:

  • name: Check captured MCP session continue-on-error: true run: mcpsnoop check --format sarif artifacts/session.jsonl > mcpsnoop.sarif
  • name: Upload mcpsnoop SARIF report if: always() uses: github/codeql-action/upload-sarif@v4 with: sarif_file: mcpsnoop.sarif category: mcpsnoop
### التقاط ترويسة توجيه تتعارض مع محتوى الطلب

في نقل streamable-HTTP، توجّه البوابة بناءً على `Mcp-Method` و`Mcp-Name`
بينما يقرأ الخادم محتوى الطلب، لذا فإن ترويسة تتعارض مع ذلك المحتوى تعني
أن الطرفين ينظران إلى طلبين مختلفين. تغطي إشارة `mismatch` تلك الحالة،
وترويسة ترد مع دفعة لا يمكنها توجيهها، وترويسة مطلوبة مفقودة
تمامًا.

اعتبارًا من 2026-07-28، يُعدّ غياب ترويسة التوجيه فشل تحقق، ويرفض الخادم المطابق
الطلب برمز `400` و`-32020`. لا يطلق mcpsnoop هذه الإشارة إلا بعد أن يُعرف
أن الجلسة تتحدث بذلك الإصدار أو أحدث، لأن الإصدارات السابقة
لا تعرّف هذه الترويسات إطلاقًا، وإغفالها هناك صحيح. رفض الخادم ذاته
لطلب برمز `-32020` يُحتسب كالإشارة نفسها.

الاسم أو URI للمورد الذي لن يتسع لقيمة حقل HTTP يُنقل بترميز Base64 داخل
مؤشّر `=?base64?…?=`، ويُفكّ ترميزه قبل المقارنة، لذا لا يُعلَّم
على عميل يرمّز بشكل صحيح أبدًا.

في طلبات `tools/call` عبر HTTP، يعرض mcpsnoop أيضًا كل ترويسة `Mcp-Param-{Name}`،
وعندما يكون تعريف الأداة المُعلَن المطابق معروفًا، يقارنها بمسار الوسائط
المُعلَّم. تُعالَج الخصائص المتداخلة، ومؤشّر Base64، والقيم المنطقية،
والأعداد الصحيحة الآمنة المكافئة عدديًا دون نتائج إيجابية كاذبة من مقارنة
السلاسل النصية. تبقى ترويسات الوسائط غير المعروفة والجلسات التي لا تتضمن
تعريف أداة مطابقًا ضمن نطاق الملاحظة فقط. ينطبق التنقيح القائم على المفتاح والقيمة
على قيم ترويسات الوسائط الملتقطة قبل وصولها إلى نقطة الاستهلاك، ولا يُبلَغ
أبدًا عن قيمة نظّفها mcpsnoop بنفسه على أنها تعارض.

### كشف انحراف تعريف الأداة

أول `tools/list` كامل يُلاحَظ لوسم خادم يصبح خط الأساس الموثوق
له. تقارن الجلسات اللاحقة خط الأساس المذكور حقلًا بحقل: الوصف،
والعنوان، ومخطّطي الإدخال والإخراج، والتعليقات التوضيحية والأيقونات، بالإضافة إلى
الأدوات التي أُضيفت أو أُزيلت. التعليقات التوضيحية هي الأهم، لأن أداة اعتُمدت
مع `readOnlyHint` ثم تعلن لاحقًا عن نفسها كأداة تخريبية هي سحب البساط الذي
وُجد هذا الفحص من أجله، وتوجّه المواصفات العملاء إلى معاملة التعليقات التوضيحية
باعتبارها غير موثوقة. يُتتبَّع العنوان والأيقونات لأنهما ما يراه المستخدم، وتصنّف
المواصفات `title` الخاص بالأداة فوق `annotations.title` وفوق اسمها. يشير جدول الجلسات
وملخص الأدوات إلى الانحراف دون حظر أو تغيير حركة مرور MCP.

تُقارن التعليقات التوضيحية عبر قيمها الافتراضية في المواصفات، لذا لا يُبلَغ عن خادم
يبدأ بذكر تلميح صراحة كان يعتمد عليه أصلًا. خط الأساس المسجَّل قبل أن يتتبّع
mcpsnoop حقلًا ما يظل يعمل للحقول التي يسجلها فعلًا، ويوضح أيّها لا يمكنه
الإجابة عنه؛ أعد التسجيل باستخدام `mcpsnoop baseline --accept` بمجرد أن تثق
بالتعريفات الحالية.

تغيير ما يسجّله التنقيح يغيّر ما يقارنه فحص الانحراف. خط أساس مأخوذ
بدون `--redact-value` ثم يُفحص مقابل التقاط مأخوذ به
يُبلِغ عن الحقول المنظّفة كحقول متغيّرة، وهذا صحيح، لأن التعريف المسجَّل
تغيّر فعلًا. أعد التسجيل باستخدام `--accept` بعد تغيير إعدادات التنقيح.

استخدم `--label` ثابتًا وفريدًا لكل خادم قد يتعارض اسم أمره أو مضيفه
الهدف من دونه. تُخزَّن خطوط الأساس تحت دليل حالة mcpsnoop العادي،
فتنطبق `MCPSNOOP_HOME` و`XDG_STATE_HOME`.```bash
mcpsnoop check --fail-on drift session.jsonl
mcpsnoop baseline session.jsonl
mcpsnoop baseline --accept session.jsonl  # trust a legitimate definition change
mcpsnoop baseline --reset session.jsonl   # trust the next complete tools/list

في بيئة CI المؤقتة، يبدأ دليل الحالة فارغًا، لذا يسجّل التشغيل الأول خط الأساس فقط ولا يُبلِّغ عن أي انحراف. يجب أن يستمر خط الأساس عبر عمليات التشغيل كي تتمكن عمليات التشغيل اللاحقة من التحقق بناءً عليه. وجّه --baseline إلى دليل مُضمَّن في المستودع أو مُخزَّن مؤقتًا، أو عيّن MCPSNOOP_HOME إلى مسار دائم.```bash mcpsnoop check --fail-on drift --baseline .mcpsnoop/baselines session.jsonl

`drift` اختياري لأمر `check`؛ وبوابة `error,invalid,warn` الافتراضية لم تتغير.

### وضع علامة على ميزات البروتوكول المهملة

يُهمِل تنقيح 2026-07-28 كلاً من Roots وSampling وLogging. وستستمر في العمل لمدة عام على الأقل، لذلك يقوم mcpsnoop بوضع علامة عليها بدلاً من التعامل معها كأخطاء. ويقوم كل من التدفق (stream) ومُفحص القدرات (capability inspector) والتصدير (export) بوضع علامة عليها، ويسمّي كل مؤشر البديل.

أصبح الوصول إلى اثنتين من الثلاث غير ممكن إلا عبر طلب متعدد الجولات (multi round-trip)، حيث يقع اسم الطريقة داخل خريطة `inputRequests` الخاصة بالخادم بدلاً من أن يكون على الإطار نفسه. وهذه أيضًا يتم وضع علامة عليها، بحيث لا يتوقف الخادم الذي انتقل إلى النمط الجديد عن الإبلاغ بصمت.```bash
mcpsnoop check --fail-on deprecated session.jsonl

مثل drift، فإن deprecated اختياري التفعيل. التشغيل الافتراضي يُبلِّغ عن العدد ويبقى أخضر، لذا فإن جلسة تستخدم ميزة مهمَلة لا تزال مسموحًا بها لا تُحوِّل CI إلى الأحمر من تلقاء نفسها.

التنبيه إلى تراكيب المخطط التي يتعامل معها العملاء بشكل سيئ

يمكن أن يكون الخادم صحيحًا تمامًا ومع ذلك يصعب على الوكيل استخدامه. يختلف العملاء في مقدار ما يدعمونه فعلًا من JSON Schema، والأداة التي يستمر النموذج في استدعائها بشكل خاطئ غالبًا ما تكون أداة طلب مخططُها أكثر مما يقدّمه العميل.

ملخص الأدوات، الذي يُفتح بـ s، يحتوي على عمود SCHEMA يُسمّي أبرز ما في مخطط كل أداة معلنة، مع + في النهاية عندما يكون هناك أكثر من نوع واحد.

المعروضالمعنى
no rootأن inputSchema غائب، أو ليس كائن JSON، أو له نوع جذر غير "object"
dialect$schema تُسمّي لهجة غير 2020-12 التي تعتمدها المراجعة افتراضيًا
ext ref$ref يشير إلى خارج المستند، وهي أيضًا الحالة التي تحذّر المواصفات المنفِّذين من اتباعها عمياء
oneOf, anyOf, allOf, notكلمة تركيب، تُعالَج بشكل غير متسق بين العملاء
ref$ref يشير إلى داخل المستند نفسه
untypedخاصية لا تُصرِّح بنوع ولا بأي وسيلة أخرى لبيان ما تقبله

جميعها ما عدا الأولى ملاحظات وليست أحكامًا. المخطط الذي يستخدم oneOf ليس خاطئًا، بل يُرجَّح فقط أن تختلف طريقة قراءته بين العملاء المختلفين، ويجوز للمخطط أن يصرّح بأي لهجة يريدها. أما no root فهو الاستثناء: تعريف Tool يتطلب inputSchema ويثبّت نوع جذره على "object"، لذا فإن العميل الذي يتحقق من صحة القائمة يرفض تلك الأداة رفضًا قاطعًا ولا تصبح قابلة للاستدعاء أبدًا، دون أي شيء على الشبكة يوضح السبب. ولهذا السبب يتصدّر no root العمود، ولا يُبلَّغ أبدًا عن مخطط مسحه تنقيح mcpsnoop الخاص، لأن المخطط غير المقروء ليس مخططًا خاطئًا.

هذا الانقسام يحدّد ما يفعله check بها. no root تحذير على إطار tools/list، لذا فهو يُفشِل بوابة error,invalid,warn الافتراضية دون أي خيار إطلاقًا، وهذا هو المقصود: الخادم الذي يُصدر أداة غير قابلة للاستخدام يستجيب لكل مصافحة بشكل طبيعي ولا يتلقى tools/call أبدًا. تُحسب الملاحظات كـ schema_findings وتُبلَّغ تحت schema findings:، ولا تُفشِل التشغيل إلا عندما تضيف schema إلى --fail-on. كلاهما يصل إلى --format junit و--format sarif، ويتضمن export القائمة الخاصة بكل أداة تحت summary.definitions.per_tool[].findings.```bash mcpsnoop check session.jsonl # a non-object root already fails this mcpsnoop check --fail-on schema session.jsonl # and now so do the observations

العمود يحمل لون التحذير ولا يحمل أبدًا اللون الأحمر لعمود ERR، وما زال mcpsnoop لا يغيّر شيئًا في حركة المرور التي يمررها.

لا يتم حل أي شيء أو جلبه. يتم التعرف على `$ref` الخارجي من شكله وحده، ولا تتم قراءة المخطط الذي يشير إليه أبدًا.

### انظر كم يكلفك الخادم في السياق

تدخل تعريفات الأدوات إلى سياق النموذج في كل محادثة، وتدخل نتائج الأدوات في كل استدعاء. يقيس ملخص الأدوات (`s`) كليهما من الجلسة التي التقطتها فعليًا.

سطر `definitions` هو التكلفة الثابتة: ما يزنه `tools/list` الخاص بهذا الخادم قبل إجراء أي استدعاء واحد. يوضح عمود `DEF` ذلك لكل أداة، و`RESULT` هو ما كلفت إجابات كل أداة حتى الآن. يبقى الجدول مرتّبًا حسب الأخطاء وزمن الاستجابة، لذلك افحص `DEF` للعثور على التعريفات المكلفة؛ يسردها التصدير من الأثقل أولًا. سطر أسفل الجدول يسمي النتيجة الأثقل المفردة، وهو ما يخفيه المجموع الإجمالي.

أرقام التعريفات هي JSON مع إزالة المسافات البيضاء غير المهمة، لذلك لا يُحتسب الخادم الذي يطبع `tools/list` الخاص به بشكل منسّق على أنه أكثر تكلفة من الذي لا يفعل ذلك، ويقيس الخادم نفسه نفس القيم عبر عمليات الالتقاط المختلفة. `RESULT` هو البايتات كما وصلت: النتيجة هي حمولة لمرة واحدة وليست عقدًا يستحق التطبيع.```bash
mcpsnoop export -T json | jq '.summary.definitions'

يحمل التصدير نفس الأرقام، لكل أداة ومقسّمةً إلى بايتات الوصف وschema، لذا يبقى الوصف الضخم وschema الضخم قابلَين للفصل ويمكن تتبّع أيٍّ منهما عبر عمليات الالتقاط. يخبرك mcpsnoop diff ما إذا تغيّر وصفٌ أو schema بين جلستين؛ والتصدير هو موضع قياس حجم ذلك التغيير.

هذه بايتات لا رموز (tokens). يعتمد عدد الرموز على النموذج، لذا فإن قياسها يعني شحن مُرمِّز واختيار النموذج الذي سيُعتمد عليه. البايتات دقيقة ويمكنك تطبيق النسبة التي تناسبك. tools/list غير المكتمل يُبلِّغ عمّا رآه بوصفه حدًّا أدنى ويصرّح بذلك، بدلًا من تمرير مجموعٍ جزئي على أنه الإجمالي.

اكتشاف عميل يعبث بحالة الخادم

في نمط الرحلات المتعددة (multi round-trip)، يسلم الخادمُ للعميل requestState معتمًا ويجب على العميل إعادته كما هو دون تعديل عند إعادة المحاولة. ويُطلب من الخادم التعاملُ معه كمدخلات يتحكم بها المهاجم، لأن العميل الذي يعبث به قد يحاول تغيير سلوك الخادم أو تجاوز فحص تفويض.

وبينما يجلس mcpsnoop في قناة الاتصال، يرى القيمة تغادر وتعود، فيستطيع أن يقول متى انكسر العقد. وهناك ثلاث طرق يمكن أن ينكسر بها، وكلٌّ منها يُبلَّغ كتحذير بروتوكول عند إعادة المحاولة.

المُبلَّغالمعنى
MRTR retry changed requestStateأعاد العميل شيئًا غير ما أصدره الخادم
MRTR retry is missing requestStateأصدر الخادم واحدًا وأغفلته إعادة المحاولة
MRTR retry invented requestStateحملت إعادة المحاولة واحدًا لم يصدره الخادم أبدًا

هذه انتهاكات بروتوكول من جانب العميل، لا ملاحظات من طرفنا، لذا فهي تمر عبر إشارة التحذير المعتادة، وتُفشِل عمليةُ تشغيل check الافتراضية عند حدوث واحدة. هذا مقصود. فالعميل الذي يعبث بحالة الخادم يستحق أن يُوقف البناء من أجله.

القيمة نفسها لا تُعرَض ولا تُسجَّل أبدًا، ولا شيء يفكّ تشفيرها أو يحللها. قد تكون blob مشفّرًا يحمل principal وtoken، ومقارنة البايتات المعتمة هي الفحص بأكمله.

هناك حالة واحدة خارج متناول الرصد. عندما يجيب خادمٌ بـrequestState دون inputRequests، لا تطابق إعادةُ محاولةٍ عُبث بها شيئًا ولا تردّ بأي مفاتيح، فلا يتبقّى ما يربطها بالطلب الأصلي، فتُقرأ كاستدعاء غير مرتبط بدلًا من كونها انتهاكًا.

المراقبة من جهاز آخر

أبقِ الالتقاط محليًا على الجهاز الذي يحدث فيه المرور، واستخدم SSH لاجتياز الشبكة، حتى لا يحتاج mcpsnoop أبدًا إلى ناقلٍ عن بُعد خاص به.

العرض المباشر

شغّل TUI على محطة عملك وأعِد توجيه مقبس mcpsnoop الخاص بالجهاز البعيد إليها. يستخدم النفق المباشر إعادة توجيه مقبس Unix عبر SSH، لذا يجب أن يعمل الطرفان بنظامي Linux أو macOS. على Windows، استخدم نسخة سجل ما بعد الحدث أدناه.```bash

on your workstation, start the TUI

mcpsnoop

create the remote socket directory once

ssh remote-user@remote-host 'mkdir -p ~/.local/state/mcpsnoop'

print the tunnel command, then run the printed ssh -R line

mcpsnoop remote remote-user@remote-host

on the remote host, wrap your server as usual

mcpsnoop -- node build/index.js

يقع المقبس تحت دليل الحالة الخاص بالجهاز البعيد، ويُحلّ مساره كالتالي: `MCPSNOOP_HOME`، وإلا `XDG_STATE_HOME/mcpsnoop`، وإلا `~/.local/state/mcpsnoop`. افتراضيًا، يفترض mcpsnoop أن دليل home في لينكس هو `/home/<user>` المستمد من `user@host` لديك، ويطبع تذكيرًا إلى stderr كلما لجأ إلى هذا التخمين. إذا كان الجهاز البعيد يُحلّ إلى مكان آخر، فاذكر الجزء الوحيد غير الافتراضي.```bash
# a non-Linux or custom home, macOS is /Users/<user> and root is /root
mcpsnoop remote --remote-home /Users/remote-user remote-user@remote-host

# an explicit MCPSNOOP_HOME on the remote
mcpsnoop remote --remote-mcpsnoop-home /srv/mcpsnoop remote-user@remote-host

# an explicit XDG_STATE_HOME on the remote
mcpsnoop remote --remote-xdg-state-home /var/lib/state remote-user@remote-host

Post-mortem

قم ببثّ جلسة عن بُعد مباشرة إلى واجهة TUI عبر SSH، دون الحاجة إلى نسخة محلية.```bash ssh remote-user@remote-host 'cat ~/.local/state/mcpsnoop/sessions/session.jsonl' | mcpsnoop open -

للاحتفاظ بنسخة محلية بدلاً من ذلك، انسخ السجلات عبر scp إلى دليل الجلسات الخاص بك وقم بتشغيل
TUI كالمعتاد.```bash
# copy the remote logs into your local sessions directory
mkdir -p ~/.local/state/mcpsnoop/sessions
scp remote-user@remote-host:'~/.local/state/mcpsnoop/sessions/*.jsonl' \
  ~/.local/state/mcpsnoop/sessions/

# open the TUI, it backfills the copied sessions
mcpsnoop

الأمان

يشغّل mcpsnoop أمر الخادم الذي تغلّفه، لذا لا تغلّف إلا الخوادم التي تثق بها، وشغّل غير الموثوقة منها داخل حاوية. ولا ينفّذ أبدًا أي شيء لم تضعه في إعداد عميلك.

يمكن أن تتضمن الإطارات الملتقطة المطالبات، ووسائط الأدوات، وبيانات الاعتماد، ونتائج الأدوات. إذا كانت الحمولات قد تحمل أسرارًا، ففعّل خيار التنقيح لتنظيف نسخ التتبّع الملتَقطة بينما تمرر البايتات الوسيطة دون تغيير.

يستبدل التنقيح المستند إلى المفاتيح القيم الكاملة الموجودة تحت مفاتيح كائنات JSON المتطابقة، وتُطبَّق مجموعة المفاتيح نفسها بأفضل جهد ممكن على وسائط سطر أوامر الخادم المغلَّف، لذا يُنظَّف --api-key=sk-x و--token sk-x عند استخدام --redact-secrets. أما الوسيط الذي يحمل سرًا دون اسم عَلَم معروف فلا يمكن اكتشافه.

يستبدل التنقيح المستند إلى المسارات القيم التي يحددها تعبير JSONPath فقط، وهو مفيد عندما يكون اسم مفتاح شائع حساسًا في موضعٍ ما وآمنًا في موضعٍ آخر. كرّر --redact-path لتنظيف أكثر من موضع.

يطبّق التنقيح المستند إلى القيم تعبيراتٍ نمطية على قيم السلاسل الملتَقطة، ونص stderr، وإطارات النص غير JSON.

الأنواع الثلاثة كلها تعمل بأفضل جهد ممكن. فقد تفوت التعبيرات النمطية بعض الأسرار، أو تطابق نصًا غير ضار بشكل مفرط، أو تفشل في رؤية القيم المحوَّلة أو المشفَّرة.

لا يتحول التنقيح أبدًا إلى اتهام. كل فحص يقارن شيئًا ملتَقطًا بآخر — ترويسة توجيه بجسم الرسالة، أو قيمة Mcp-Param بالوسيط الذي تعكسه، أو مخطط أداة بما تتطلبه المراجعة منها — يعرف متى كان mcpsnoop هو الطرف الذي أعاد كتابة البايتات، فيلتزم الصمت بدلًا من الإبلاغ عن خادم بسبب إعداد خصوصية المستخدم نفسه. انحراف تعريف الأداة هو الاستثناء، وهو مقصود، لأن تفعيل التنقيح يغيّر ما يُسجَّل وبالتالي ما يحتويه خط الأساس. انظر اكتشاف انحراف تعريف الأداة.```bash

built-in preset of common secret keys

mcpsnoop --redact-secrets -- node build/index.js

or name your own keys

mcpsnoop --redact-key token,api_key,password -- node build/index.js

scrub one location without redacting every field named password

mcpsnoop --redact-path '$.params.arguments.password' -- node build/index.js

wildcards scrub every matching array element

mcpsnoop --redact-path '$.params.arguments.accounts[*].password' -- node build/index.js

scrub obvious token-shaped values outside known keys

mcpsnoop --redact-value 'sk-[A-Za-z0-9]+' -- node build/index.js

combine the layers in http mode

mcpsnoop http --target http://localhost:3000/mcp --redact-secrets --redact-value 'Bearer\s+\S+'

بالنسبة لسير العمل عن بُعد، استخدم نفق SSH أو نقل الملفات عبر SSH بحيث تبقى مصادقة النقل، والتشفير، والتحقق من المضيف، وتدوير المفاتيح، وسياسة التدقيق ضمن إعداد SSH الحالي لديك.

## المساهمة

نرحب بالمشكلات وطلبات السحب. راجع [CONTRIBUTING.md](https://github.com/kerlenton/mcpsnoop/blob/HEAD/CONTRIBUTING.md) للتفاصيل.

## الترخيص

[MIT](https://github.com/kerlenton/mcpsnoop/blob/HEAD/LICENSE)

الفئات