سرور MCP: وصل کردن سگمنتیک به یک عامل
اتصال امن عاملهای هوش مصنوعی به دادهها و ابزارهای سگمنتیک.
سرور MCP به کلود کد، کلود دسکتاپ، کدکس یا کرسر اجازه میدهد حساب سگمنتیک شما را بخوانند: اینکه واقعا چه رویدادهایی میفرستید، یک فیلتر چند نفر را میگیرد، و یک کمپین چه کرد. اگر کلید مجوزهای نوشتن داشته باشد، میتواند مخاطب بسازد و ویرایش کند، کمپین پیشنویس بسازد، رویداد بنویسد و خروجی صف کند. و اگر مجوز ارسال داشته باشد، میتواند پیام تراکنشی بفرستد یا یک کمپین را روانه کند.
سریعترین راه همانی است که خودمان بالا نگه داشتهایم. کلاینتتان را به https://mcp.segmentic.net/mcp وصل کنید و کلید sk_seg_ خودتان را در هدر Authorization بگذارید؛ چیزی نصب نمیکنید. تنظیمات کامل کلاینت در میزبانی روی HTTP آمده. اگر ترجیح میدهید این پروسه روی ماشین خودتان یا داخل شبکه خودتان بدود، از روی همین مخزن بیلدش میکنید و هر دو مسیر پایینتر نوشته شدهاند.
این چیست
دو فایل Go در backend/cmd/segmentic-mcp، خواندن در main.go و نوشتن در write_tools.go، روی هم حدود هزار خط، کنار ۶۶۶ خط تست. خودش را به کلاینت با نام segmentic و نسخهٔ 1.0.0 معرفی میکند و حداکثر بیستویک ابزار ثبت میکند.
پروتکل را روی دو انتقال حرف میزند و انتخابش با شماست. stdio برای وقتی است که خودتان عامل را روی ماشین خودتان میدوانید: یک پروسه بهازای هر نفر و کلید از محیط. HTTP برای وقتی است که یک نفر آن را یک بار بالا میآورد و بقیه فقط یک نشانی میگیرند، و آنجا کلید هر کس در هدر Authorization خودش میآید.
هر ابزار یک درخواست HTTP به میزبان مدیریتی است، https://api.segmentic.net، با همان کلید sk_seg_ که در یک دستور curl میگذاشتید.
چرا کلاینت API است و نه اتصال به دیتابیس
توضیح بالای فایل، تمام استدلال امنیتی است و ارزش دارد پیش از تصمیم به دادن کلید به یک عامل خوانده شود:
It is a client of the public REST API, not a second way into the database.
That is the whole security design: every permission gate, every tenant scope,
every budget already lives at the HTTP edge, and a tool here cannot reach
past them because it has no other door. An MCP server holding a database
handle would be a second admission path, and the second one is always the one
that forgets a check.
نتیجهٔ عملی: سرور MCP هیچ اجازهای به عامل نمیدهد که کلید از قبل به یک حلقهٔ curl نداده باشد. اگر میخواهید بدانید یک عامل چه میتواند بکند، به جای این صفحه مجوزهای کلید را بخوانید. باطلکردن کلید عامل را همان لحظه متوقف میکند، چون ابزارها هیچ اعتبارنامهٔ دیگری ندارند.
نتیجهٔ معکوس همان است که معمولا از قلم میافتد. چون هر ابزار یک درخواست معمولی API است، عاملی که کلید دستش باشد به ابزارها محدود نیست. با curl میتواند هر مسیری را که کلید اجازه میدهد صدا بزند، از جمله مسیرهایی که سرور MCP برایشان هیچ ابزاری ندارد. فهرست ابزارها یک راحتی است، نه یک قفس.
گرفتن فایل اجرایی
هیچچیز منتشر نشده است. نه ریلیز گیتهاب هست، نه ایمیج مستقل، نه بستهٔ npm، نه فرمول Homebrew، نه نصاب، و نه لینک دانلودی در پنل. هیچ بیلد آمادهٔ مک یا ویندوزی هم هیچجا وجود ندارد.
خودتان بسازید
یک نسخه از مخزن بکاند لازم دارید و Go نسخهٔ 1.26.4 یا بالاتر.
cd backend
go build -o segmentic-mcp ./cmd/segmentic-mcp
روی ویندوز خروجی را segmentic-mcp.exe نام بگذارید. برای ساختن نسخهٔ مک از روی یک دستگاه لینوکسی یا ویندوزی:
cd backend
GOOS=darwin GOARCH=arm64 go build -o segmentic-mcp-darwin-arm64 ./cmd/segmentic-mcp
وابستگیها vendor شدهاند، پس بیلد برای ماژولها به شبکه نیاز ندارد. خود زنجیرهٔ ابزار Go را لازم دارد، و proxy.golang.org به دانلود toolchain از آدرسهای ایران ۴۰۳ میدهد؛ به همین دلیل داکرفایل خود مخزن GOPROXY=off GOFLAGS=-mod=vendor میگذارد. پیش از تلاش، toolchain را از یک آینه نصب کنید.
برای این فایل اجرایی هیچ هدفی در Makefile نیست. دستور make build آن را کامپایل میکند و خروجی را دور میریزد، و به همین خاطر است که دستور بالا -o را صریح میدهد.
اجرا از داخل ایمیج بکاند
ایمیج بکاند همهٔ پوشههای cmd را میسازد، پس /usr/local/bin/segmentic-mcp داخل ghcr.io/segmentic1/segmentic-backend:latest وجود دارد. با یک الگوی wildcard در داکرفایل آنجا رفته و نه از روی قصد، و یک بیلد استاتیک لینوکسی برای معماری amd64 است، پس روی مک و ویندوز بهصورت بومی اجرا نمیشود.
بهعنوان فرمان MCP کار میکند، چون ایمیج فرمان پیشفرض ندارد و با -i ورودی و خروجی استاندارد رد میشود:
docker run -i --rm \
-e SEGMENTIC_API_URL=https://api.segmentic.net \
-e SEGMENTIC_API_KEY=sk_seg_REPLACE_ME \
ghcr.io/segmentic1/segmentic-backend:latest segmentic-mcp
ایمیج خصوصی است. یک docker login ghcr.io با توکنی که اجازهٔ خواندنش را داشته باشد لازم دارید.
کلید
سرور یک کلید API میخواهد، sk_seg_.... در پنل بسازیدش: تنظیمات، اتصالها و یکپارچهسازی، کلیدهای API، یعنی https://app.segmentic.net/fa/settings/keys. ساختنش مجوز apikey.write روی حساب خودتان میخواهد.
فرم سه فیلد دارد: نام، نقش، و اینکه چند وقت معتبر بماند (۳۰ روز، ۹۰ روز یا یک سال، با پیشفرض یک سال). از این مسیر نمیشود کلید بدون انقضا ساخت.
متن کلید فقط یک بار نشان داده میشود، در پاسخ فراخوانی ساخت و در یک کادر روی صفحه. بعد از آن فقط یک هش SHA-256 و یک پیشوند نمایشی ۱۴ نویسهای باقی میماند. اگر گمش کردید، یکی دیگر میسازید.
{
"id": 12,
"name": "agent-readonly",
"prefix": "sk_seg_ABCDEF",
"role": "viewer",
"role_label": "بیننده",
"created_by": "Maryam",
"created_at": "2026-08-07T18:00:00Z",
"expires_at": "2027-08-07T18:00:00Z",
"key": "sk_seg_yqk7..."
}
نقشی که انتخاب میکنید تعیین میکند چه ابزارهایی وجود داشته باشند، چون ابزارها از روی مجوزهای کلید ثبت میشوند. نقش مالک یکسره رد میشود، با کد owner_key_forbidden: هیچ کلیدی حق ندارد مالک باشد، چون پاککردن حساب کاری نیست که یک یکپارچهسازی بتواند ساعت سه بامداد انجام بدهد. کلید برای نقشی بالاتر از نقش خودتان هم نمیتوانید بسازید.
| نقشی که انتخاب میکنید | ابزارهایی که عامل میبیند | مینویسد | ارسال دارد |
|---|---|---|---|
viewer (بیننده) | ۱۱ | ندارد | ندارد |
approver (تأییدکننده) | ۱۱ | ندارد | ندارد |
analyst (تحلیلگر) | ۱۳ | ندارد، ولی خروجی میگیرد | ندارد |
marketer (بازاریاب) | ۲۱ | دارد | دارد |
admin (مدیر) | ۲۱ | دارد | دارد |
finance (مالی) | ۲، فقط segmentic_whoami و segmentic_capabilities | ندارد | ندارد |
owner (مالک) | ساخته نمیشود | ــ | ــ |
این عددها در cmd/segmentic-mcp/registration_test.go تست دارند، پس اگر ابزاری اضافه یا جابهجا شود این جدول با بیلد قرمز میشود نه با شکایت یک مشتری.
تفاوت analyst با viewer دو ابزار خروجی است و همان دو تا تنها چیزیاند که نشانی ایمیل و شمارهٔ تلفن از ساختمان بیرون میبرند. اگر عامل فقط قرار است بخواند و گزارش بدهد، viewer بدهید.
کلید نوشتن ندهید. کلید wk_seg_ داخل باندل جاوااسکریپت و داخل اپ شما میرود، پس ذاتا عمومی است و فقط میتواند رویداد بنویسد. سرور با چنین کلیدی اصلا بالا نمیآید و پیش از فرستادن حتی یک درخواست همین را میگوید.
کلید تصمیم میگیرد، نه یک فلگ
فهرست ابزارها از روی مجوزهای کلید ساخته میشود. کلیدی با نقش viewer یازده ابزار خواندنی میگیرد و بس. کلیدی با segment.write ابزارهای ساخت و ویرایش مخاطب را هم میگیرد. کلیدی با campaign.send دو ابزاری را میگیرد که به آدم واقعی میرسند.
نه فلگ --allow-write وجود دارد و نه --allow-send، و این عمدی است: تصمیم مال کسی است که حساب را دارد، نه مال یک خط فرمان که اپراتور میتواند غلط تایپش کند یا روشن جا بگذارد.
این بخش تا همین اواخر میگفت «پیشفرض فقط خواندن». آن جمله دربارهٔ فهرست ابزارها راست بود و دربارهٔ خود اعتبارنامه هیچوقت راست نبود، و فرقشان همان چیزی است که این صفحه باید صریح بگوید:
مجوز campaign.send را فقط marketer و admin دارند، و هر دو segment.write و campaign.write و journey.publish و template.write و profile.write را هم دارند. پس کلید ارسالدار محدودنشده، یک اعتبارنامهٔ کامل نوشتنی است، چه سرور MCP برایش ابزار داشته باشد چه نداشته باشد: با curl همهاش در دسترس است. نداشتن ابزار، توانایی را نمیگرفت، فقط کار را به مسیری میبرد که نه اعتبارسنجی آرگومان دارد، نه تأیید روی کارهای برگشتناپذیر، و نه ردی از آنچه تلاش شده.
کلید محدودشده، که حالا واقعا ساخته میشود
راهحل درست، تنگکردن خود کلید است نه پنهانکردن ابزار. ستون scopes روی api_keys از مهاجرت ۰۱۶ وجود داشت و مسیر خواندن هم همیشه آن را در اعتبارسنجی اعمال میکرد، ولی هیچجا در آن نمینوشت. حالا مینویسد:
{
"name": "agent-authoring",
"role": "marketer",
"scopes": ["segment.read", "segment.write", "event.read"]
}
آن کلید مخاطب میسازد و ویرایش میکند و هیچوقت نمیفرستد، نه از راه MCP و نه با curl. مجوز مؤثرش اشتراک نقش با این فهرست است، پس محدودکردن فقط میتواند کم کند: مجوزی که نقش ندارد را نمیشود با scope اضافه کرد و سرور ردش میکند با کد scope_exceeds_role.
| اگر بنویسید | چه میشود |
|---|---|
فیلد scopes را ننویسید | کلید کل نقشش را دارد، همان رفتار همیشگی |
| فهرستی از مجوزهای داخل نقش | کلید فقط همانها را دارد |
فهرست خالی [] | رد میشود با scopes_empty. کلیدی که هیچ کاری نمیتواند بکند، از یک اشتباه قابل تشخیص نیست |
| مجوزی که وجود ندارد | رد میشود با unknown_permission و خود رشته در پیام میآید |
| مجوزی بیرون از نقش | رد میشود با scope_exceeds_role |
مقدار scoped در segmentic_whoami میگوید کلید تنگ شده یا نه، و فهرست permissions همان مجموعهٔ مؤثر است، پس عامل از اول میداند چه میتواند بکند.
فرم پنل هنوز فیلد انتخاب مجوز ندارد و کلید محدودشده فعلا از API ساخته میشود. تا وقتی آن فرم بیاید، سادهترین راه یک curl با نشست خودتان است.
پیکربندی
فایل اجرایی دو متغیر محیطی میخواند و سه فلگ میگیرد. فلگ بر متغیر محیطی میچربد، چون مقدار محیطی فقط مقدار پیشفرض فلگ است.
| فلگ | متغیر محیطی | پیشفرض | چیست |
|---|---|---|---|
-api | SEGMENTIC_API_URL | http://localhost:8082 | آدرس پایهٔ API مدیریتی. اسلش انتهایی حذف میشود. |
-key | SEGMENTIC_API_KEY | ندارد | کلید sk_seg_. نبودنش کشنده است. |
-timeout | ندارد | 30s | مهلت هر درخواست HTTP. برای این یکی متغیر محیطی وجود ندارد. |
به جای -key از متغیر محیطی استفاده کنید. مقدار یک فلگ در جدول فرایندها مینشیند، جایی که هر فرایند دیگری روی همان دستگاه میتواند بخواندش.
اگر گزارش میگیرید -timeout را روی 60s بگذارید، و بدانید که این فقط نصف راهحل است. هندلرهای گزارش برای خودشان ۴۵ ثانیه وقت میگذارند، ولی WRITE_TIMEOUT خود سرور HTTP پیشفرض ۳۰ ثانیه است و کلاینت هم پیشفرض ۳۰ ثانیه، پس یک کوئری ماندگاری سنگین از دو طرف قطع میشود در حالی که سمت سرور همچنان میدود و ۲۵ واحد بودجه هم خرج شده است. بالا بردن مهلت کلاینت وقتی کمک میکند که هرکس آن نصب را میگرداند WRITE_TIMEOUT را هم بالا ببرد.
کلود کد
فایل پروژه .mcp.json در ریشهٔ مخزن شما:
{
"mcpServers": {
"segmentic": {
"command": "/absolute/path/to/segmentic-mcp",
"args": ["-timeout", "60s"],
"env": {
"SEGMENTIC_API_URL": "https://api.segmentic.net",
"SEGMENTIC_API_KEY": "sk_seg_REPLACE_ME"
}
}
}
}
کلیدی که زیر mcpServers میگذارید فضای نام میشود، پس ابزارها به شکل mcp__segmentic__segmentic_whoami و مانند آن ظاهر میشوند. همان segmentic را نگه دارید تا با نامی که خود سرور اعلام میکند یکی باشد.
همین کار از خط فرمان:
claude mcp add segmentic --scope project \
--env SEGMENTIC_API_URL=https://api.segmentic.net \
--env SEGMENTIC_API_KEY=sk_seg_REPLACE_ME \
-- /absolute/path/to/segmentic-mcp -timeout 60s
هرچه بعد از -- بیاید فرمان و آرگومانهای خودش است، پس -timeout به فایل اجرایی میرسد و نه به CLI.
برای اجرا از روی ایمیج به جای فایل محلی:
{
"mcpServers": {
"segmentic": {
"command": "docker",
"args": [
"run", "-i", "--rm",
"-e", "SEGMENTIC_API_URL",
"-e", "SEGMENTIC_API_KEY",
"ghcr.io/segmentic1/segmentic-backend:latest",
"segmentic-mcp"
],
"env": {
"SEGMENTIC_API_URL": "https://api.segmentic.net",
"SEGMENTIC_API_KEY": "sk_seg_REPLACE_ME"
}
}
}
}
کلود دسکتاپ
روی مک ~/Library/Application Support/Claude/claude_desktop_config.json و روی ویندوز %APPDATA%\Claude\claude_desktop_config.json.
{
"mcpServers": {
"segmentic": {
"command": "/absolute/path/to/segmentic-mcp",
"args": ["-timeout", "60s"],
"env": {
"SEGMENTIC_API_URL": "https://api.segmentic.net",
"SEGMENTIC_API_KEY": "sk_seg_REPLACE_ME"
}
}
}
}
روی ویندوز مسیر مطلق است و بکاسلشها دوتا میشوند:
{
"mcpServers": {
"segmentic": {
"command": "C:\\Program Files\\Segmentic\\segmentic-mcp.exe",
"args": [],
"env": {
"SEGMENTIC_API_URL": "https://api.segmentic.net",
"SEGMENTIC_API_KEY": "sk_seg_REPLACE_ME"
}
}
}
}
کلود دسکتاپ PATH پوستهٔ شما را به ارث نمیبرد، پس command باید مسیر مطلق باشد.
کدکس
فایل ~/.codex/config.toml:
[mcp_servers.segmentic]
command = "/absolute/path/to/segmentic-mcp"
args = ["-timeout", "60s"]
[mcp_servers.segmentic.env]
SEGMENTIC_API_URL = "https://api.segmentic.net"
SEGMENTIC_API_KEY = "sk_seg_REPLACE_ME"
کرسر
فایل .cursor/mcp.json در پروژه، یا ~/.cursor/mcp.json بهصورت سراسری.
{
"mcpServers": {
"segmentic": {
"command": "/absolute/path/to/segmentic-mcp",
"args": ["-timeout", "60s"],
"env": {
"SEGMENTIC_API_URL": "https://api.segmentic.net",
"SEGMENTIC_API_KEY": "sk_seg_REPLACE_ME"
}
}
}
}
میزبانشده روی HTTP
اگر یک نفر سرور را یک بار بالا بیاورد، بقیه چیزی نصب نمیکنند:
segmentic-mcp -http :8090 -api https://api.segmentic.net
-http و -key با هم جمع نمیشوند و اجرا با هر دو رد میشود. دلیلش طراحی است نه سختگیری: یک پروسه به چند حساب خدمت میدهد، پس کلید باید در هر درخواست بیاید و فهرست ابزار از مجوزهای همان کلید ساخته شود. -key روی سرور میزبانشده یعنی اعتبارنامه یک حساب بهجای همه جواب میدهد.
سمت کلاینت، بهجای دستور، یک نشانی و یک کلید:
{
"mcpServers": {
"segmentic": {
"url": "https://mcp.segmentic.net/mcp",
"headers": { "Authorization": "Bearer sk_seg_..." }
}
}
}
چهار چیزی که این حالت را قابل میزبانی میکند و ارزش گفتن دارد:
- این پروسه هیچ اعتبارنامهای از خودش ندارد. چیزی در آن نیست که دزدیده شود.
- کلید فقط از هدر خوانده میشود، هرگز از کوئریاسترینگ. کلید در URL در هر لاگ دسترسی و هر هدر referrer مینشیند.
- کلید SDK رد میشود. کلید
wk_در باندل جاوااسکریپت هر مشتری هست، پس پذیرفتنش یعنی میشود عاملی را به اعتبارنامهای که از سورس یک صفحه برداشته شده وصل کرد. - مجوز در هر فراخوان سمت API اعمال میشود. هویت کلید حدود دو دقیقه کش میشود و آن کش فقط تعیین میکند چه ابزارهایی فهرست شوند؛ کلید باطلشده در همان فراخوان بعدی جواب ۴۰۱ میگیرد.
GET /healthz هم هست و عمدا پشت کلید نیست: پروبی که اعتبارنامه بخواهد، پروبی است که کسی خاموشش میکند.
توسعهٔ محلی
آدرس پایهٔ پیشفرض http://localhost:8082 است، همان پورتی که API عمومی روی آن گوش میدهد، پس در حالت محلی میشود ننویسیدش:
{
"mcpServers": {
"segmentic-local": {
"command": "/absolute/path/to/segmentic-mcp",
"env": { "SEGMENTIC_API_KEY": "sk_seg_LOCAL_KEY" }
}
}
}
مقدار PUBLIC_API_ADDR پیشفرض خالی است، و خالی یعنی API عمومی اصلا سرو نمیشود. نصبی که تصمیم نگرفته چیزی را در معرض بگذارد، نباید در معرض بگذارد. اگر API را بدون تنظیم این متغیر بالا بیاورید، چیزی روی 8082 گوش نمیدهد و سرور MCP با پیام connection refused بیرون میآید. پورت خود داشبورد، یعنی 8081، جایگزین نیست: یک mux دیگر است و مسیر /v1/whoami را به این شکل ندارد.
بررسی نصب بدون هیچ عاملی
سرور JSON-RPC را روی stdio حرف میزند، پس بدون هیچ کلاینتی میشود کلید را ثابت کرد و تعداد ابزارها را دید. در یک ترمینال اجرایش کنید:
SEGMENTIC_API_KEY=sk_seg_REPLACE_ME \
SEGMENTIC_API_URL=https://api.segmentic.net \
./segmentic-mcp
پیش از ثبت هر ابزاری GET /v1/whoami را صدا میزند، بعد یک خط روی خروجی خطای استاندارد مینویسد و منتظر کلاینت روی ورودی استاندارد میماند:
2026/08/07 21:04:11 segmentic-mcp 1.0.0, tenant 7, role viewer, 8 permissions
شکستخوردن در لحظهٔ بالا آمدن به جای اولین فراخوانی ابزار عمدی است: عاملی که وسط یک گفتگو میفهمد اعتبارنامهاش غلط است، این را «ابزار خراب است» گزارش میکند و آدم هیچوقت دلیل واقعی را نمیبیند.
کلید بد با کد ۱ بیرون میآید و یکی از اینها را روی خروجی خطا مینویسد:
segmentic-mcp: no API key: set SEGMENTIC_API_KEY or pass -key
segmentic-mcp: that is an SDK write key (wk_…), which cannot read anything.
This needs a management key (sk_seg_…) from Settings → API keys.
segmentic-mcp: could not reach the Segmentic API: unauthenticated: a valid API key is required
نگهبان کلید نوشتن روی پیشوند wk_ عمل میکند، پیش از آنکه هیچ درخواستی بیرون برود. توضیح کد میگوید چرا پیام جداست و ۴۰۱ نیست: کلید نوشتن داخل باندل جاوااسکریپت هر مشتری میرود، پس پذیرفتنش یعنی میشود یک عامل را به اعتبارنامهای وصل کرد که از سورس یک صفحه برداشته شده، و شکست همین حالا با جملهای که فرق را توضیح میدهد بهتر از شکست بعدی با ۴۰۱ است.
ابزارها
مجموعه دقیق به مجوزهای کلید بستگی دارد. هر زیربخش مجوزی که ابزار را باز میکند، آرگومانها، مسیری که صدا میزند، بودجهای که خرج میکند و چیزی که برمیگردد را میگوید.
نتیجه همیشه یک بلوک متنی است که داخلش JSON با تورفتگی نشسته. خروجی ساختاریافته وجود ندارد، پس کلاینتی که دنبال structuredContent بگردد چیزی پیدا نمیکند. شکستها بهصورت محتوای ابزار با isError: true برمیگردند و نه بهصورت خطای پروتکل، تا مدل دلیل را بخواند و بتواند کاری بکند.
| ابزار | مجوز | مسیر | بودجه |
|---|---|---|---|
segmentic_whoami | ندارد | هیچ، در زمان بالا آمدن کش شده | ۰ |
segmentic_capabilities | ندارد | GET /v1/capabilities | ۱ |
segmentic_get_audience | segment.read | GET /v1/segments/{id} | ۱ |
segmentic_create_audience | segment.write | POST /v1/segments | ۱ |
segmentic_update_audience | segment.write | PUT /v1/segments/{id} | ۱ |
segmentic_delete_audience | segment.delete | GET سپس DELETE /v1/segments/{id} | ۲ |
segmentic_create_campaign | campaign.write | POST /v1/campaigns | ۱ |
segmentic_submit_campaign_for_approval | campaign.write | POST /v1/campaigns/{id}/submit | ۱ |
segmentic_set_campaign_recurrence | campaign.send | GET سپس PUT /v1/campaigns/{id}/recurrence | ۶ |
segmentic_clear_campaign_recurrence | campaign.send | DELETE /v1/campaigns/{id}/recurrence | ۱ |
segmentic_send_campaign | campaign.send | GET سپس POST /v1/campaigns/{id}/send | ۶ |
segmentic_ingest_events | profile.write | POST /v1/events | ۱۰ |
segmentic_queue_export | data.export | POST /v1/exports | ۲۵ |
segmentic_list_exports | data.export | GET /v1/exports | ۱ |
segmentic_describe_data | event.read | GET /v1/schema/events و GET /v1/schema/traits | ۱۰ |
segmentic_ingest_quality | event.read | GET /v1/ingest/quality | ۵ |
segmentic_list_audiences | segment.read | GET /v1/segments | ۱ |
segmentic_describe_audience | segment.read | POST /v1/audiences/validate | ۱ |
segmentic_count_audience | segment.read | POST /v1/audiences/count | ۲۵ |
segmentic_list_campaigns | campaign.read | GET /v1/campaigns | ۱ |
segmentic_campaign_report | campaign.read | GET /v1/campaigns/{id} | ۵ |
segmentic_funnel_report | analytics.read | POST /v1/reports/funnel | ۲۵ |
segmentic_retention_report | analytics.read | POST /v1/reports/retention | ۲۵ |
segmentic_send_transactional_message | campaign.send | POST /v1/messages | ۱ |
segmentic_whoami
همیشه ثبت میشود، حتی برای کلیدی که هیچ مجوزی ندارد.
آرگومان ندارد. هیچ درخواست HTTP نمیزند: پاسخ یک بار موقع بالا آمدن گرفته شده و از حافظه برمیگردد، پس هیچ هزینهای ندارد و نمیتواند شکست بخورد. در عوض نمیتواند بفهمد کلید پنج دقیقه پیش باطل شده است.
{
"tenant_id": 7,
"role": "analyst",
"permissions": [
"analytics.read", "audit.read", "campaign.read", "data.export",
"event.read", "journey.read", "member.read", "profile.read",
"segment.read", "settings.read", "template.read"
],
"scoped": false
}
فیلد permissions مجموعهٔ مؤثر است و مرتب. یازدهتاست و نه چهارتا، چون نقش تحلیلگر همهچیز را میخواند: مجموعهاش را در backend/internal/auth/role.go ببینید. ابزارهایی که میبینید کمتر از ایناند، چون سرور MCP فقط برای بخشی از این مجوزها ابزار نوشته. فیلد scoped قرار است بگوید کلید پایینتر از نقشش تنگ شده، ولی روی هر کلیدی که این محصول میتواند بسازد false است: ستون محدودسازی روی api_keys هست و هیچ مسیری در آن نمینویسد. نه نشانی ایمیل، نه نام آدم، نه شناسهٔ حساب، و نه شناسهٔ کلید: API خودش api_key_id را برمیگرداند و این ابزار دورش میریزد.
باقیماندهٔ بودجه، قابلیتهای این نصب، یا حالت سرور را گزارش نمیکند. هیچکدام در پاسخ نیستند.
segmentic_describe_data
مجوز event.read. آرگومان ندارد. دو GET پشت سر هم، پس هر فراخوانی ۱۰ واحد بودجه میبرد؛ اگر اولی شکست بخورد دومی اصلا زده نمیشود و کل ابزار خطا میدهد.
این ابزار بهخاطر گرانترین اشتباهی وجود دارد که یک عامل اینجا میکند. از خود فایل:
It invents identifiers. describe_data exists so a segment id or an event
name can be looked up rather than guessed; a guessed event name compiles
cleanly and returns an empty audience, which reads as "nobody matches"
rather than as "that event does not exist".
دو پاسخ سرور یک لایه عمیقتر از بدنهٔ خودشان تو در تو مینشینند:
{
"events": {
"events": [
{
"name": "order_completed",
"volume": 184203,
"prop_keys": ["revenue", "order_id", "currency"],
"last_seen": "2026-08-06"
}
]
},
"traits": {
"traits": ["city", "order_count", "plan"],
"schema": [
{"name": "city", "kind": "string", "users": 91204},
{"name": "order_count", "kind": "number", "users": 58110}
]
}
}
ستونی که واقعا باید خواند last_seen است. رویدادی با حجم بزرگ و تاریخ آخرین دیدهشدنی مال سه هفته پیش، یعنی یکپارچهسازیای که خراب شده، و هیچ عدد دیگری این را نمیگوید، چون حجم بهتنهایی تا یک ماه بعدش هم سالم به نظر میرسد: پنجره نود روزه است.
فیلدهای prop_keys و last_seen وقتی خالی باشند حذف میشوند. مقدار kind یا string است یا number؛ ویژگیای که هر دو شکل فرستاده شده یک بار و بهصورت number میآید. عدد users تعداد پروندههایی است که آن ویژگی را دارند. توضیح خود ابزار برای ویژگیها هم «با حجم» میگوید که دقیق نیست: سطرهای ویژگی شمار پرونده دارند، نه حجم رویداد.
حسابی که هیچچیز نفرستاده باشد {"events":[]} و {"traits":[]} میگیرد.
segmentic_list_audiences
مجوز segment.read. هزینه ۱.
برخلاف نامش، این ابزار سگمنتهای ذخیرهشده را برمیگرداند، همان چیزی که پنل به آن سگمنت میگوید. مسیر GET /v1/segments را صدا میزند که فقط وقتی ثبت شده که آن نصب سگمنت را سرو کند.
آرگومانی نمیگیرد.
چیزی که برمیگردد همهٔ سگمنتها نیست. کوئری خود انبار ORDER BY updated_at DESC LIMIT 200 است، پس ۲۰۰ سگمنتی که تازهتر از همه بهروز شدهاند برمیگردند و بس. سقف بیصداست: نه شماری در پاسخ هست، نه has_more، نه هشداری، و حسابی که ۲۵۰ سگمنت دارد روی هیچ سطحی راهی به آن ۵۰ تای دیگر ندارد. توضیح خود ابزار همین را به مدل میگوید، با این نتیجهگیری که روی حساب بزرگ، نامی که اینجا پیدا نمیکنید را ناشناخته بگیرید و نه ناموجود. ۲۰۰ درخت فیلتر کامل هنوز میتواند بزرگ باشد؛ اگر از سقف ۴ مبیبایتی پاسخ در کلاینت رد شود، ابزار unreadable response (200) را با ۲۰۰ نویسهٔ اول گزارش میکند.
این ابزار تا همین اواخر دو آرگومان limit و cursor میگرفت که هیچ کاری نمیکردند: هندلر کوئریاسترینگ را نمیخواند و هیچ پاسخی next_cursor ندارد. آرگومانی که کاری نمیکند از نبودنش بدتر است، چون مدل باورش میکند و صفحهٔ دوم را میخواهد. هر دو برداشته شدند.
{
"segments": [
{
"id": 41,
"name": "خریداران اخیر",
"kind": "dynamic",
"definition": {
"version": 1,
"root": {
"kind": "event",
"event": "order_completed",
"window": {"kind": "last", "amount": 30, "unit": "day"}
}
},
"description_fa": "کاربرانی که شهرشان تهران است",
"last_size": 18422,
"last_computed_at": "2026-08-06T21:00:00Z",
"updated_at": "2026-08-01T11:12:00Z"
}
]
}
مقدار kind یکی از dynamic و static و realtime است. فیلد definition کل درخت فیلتر است، نه خلاصهاش. فیلد description_fa همیشه فارسی است، در هر پاسخی از این API: مسیرهای عمومی میانافزار زبان ندارند، پس Accept-Language چیزی را عوض نمیکند و سرور MCP هم اصلا چنین هدری نمیفرستد.
segmentic_describe_audience
مجوز segment.read. هزینه ۱. مسیر POST /v1/audiences/validate.
یک آرگومان اجباری دارد، filter، و باید یک تعریف کامل باشد یعنی {"version": 1, "root": {...}} و نه یک شرط تنها. توضیح ابزار آن را بهعنوان تمرین ارزان پیش از شمارش گران معرفی میکند: هیچ کوئری نمیزند، هیچچیز دربارهٔ آدمها برنمیگرداند، و در یک جمله میگوید فیلتر چه میگوید.
{
"filter": {
"version": 1,
"root": {"kind": "trait", "trait": "city", "operator": "eq",
"value": {"type": "string", "str": "تهران"}}
}
}
{
"valid": true,
"description_fa": "کاربرانی که شهرشان تهران است"
}
دو فیلد، و همین. نه توضیح انگلیسی هست، نه فهرست ماشینخوان مشکلها، و نه گزارشی از اینکه فیلتر چقدر به عقب میرسد.
فیلتر نامعتبر ۴۲۲ میگیرد، نه ۲۰۰ با valid: false. داشبورد ۲۰۰ با valid:false میدهد که برای فرمی که همان لحظه تایپ میشود درست است و برای یکپارچهسازیای که مدیریت خطایش روی وضعیت شاخه میزند غلط.
{"error": {"code": "filter_invalid", "message": "segment: unknown node kind"}}
این یکی را مدل کامل میبیند، بهصورت filter_invalid: segment: unknown node kind.
segmentic_count_audience
مجوز segment.read. هزینه ۲۵، سنگینترین وزنی که هست. مسیر POST /v1/audiences/count با همان آرگومان filter بالا.
{
"count": 18422,
"approximate": false,
"description": "کاربرانی که شهرشان تهران است",
"took_ms": 812
}
یک عدد، یک جملهٔ فارسی و یک مدتزمان. هیچچیز دیگر، و توضیح ابزار این را با حروف بزرگ میگوید تا مدل دنبال فهرست آدمها نگردد.
با بودجهٔ پیشفرض ۶۰۰ واحد برای هر کلید در هر دقیقه، این یعنی ۲۴ شمارش در دقیقه تا وقتی API جواب budget_exhausted بدهد. همین سقف تنها ترمز است. نه شرط رویدادی بدون پنجرهٔ زمانی رد میشود و نه سقف همزمانی برای شمارش وجود دارد.
segmentic_list_campaigns
مجوز campaign.read. هزینه ۱. مسیر GET /v1/campaigns.
آرگومانی نمیگیرد. دقیقا مثل segmentic_list_audiences کوئری انبار LIMIT 200 ثابت دارد: ۲۰۰ کمپین تازهتر، بدون next_cursor و بدون هیچ نشانهای در پاسخ از اینکه کمپین دویستویکمی هم هست. توضیح ابزار همین سقف را صریح به مدل میگوید. پیش از این، توضیح ادعا میکرد «صفحهبندیشده؛ اندازهٔ صفحه را سرور محدود میکند» و دو آرگومان بیاثر هم میگرفت؛ نیمهٔ دوم آن جمله درست بود و نیمهٔ اولش نه.
{
"campaigns": [
{
"id": 812,
"name": "بازگشت مشتری تیر",
"channel": "sms",
"status": "finished",
"estimated": 60000,
"processed": 60000,
"sent": 41230,
"scheduled_at": "2026-07-14T06:30:00Z",
"updated_at": "2026-07-14T09:11:00Z"
}
]
}
هیچ شناسهای از گیرندهها، از هیچ نوع.
segmentic_campaign_report
مجوز campaign.read. هزینه ۵. مسیر GET /v1/campaigns/{id} با یک آرگومان اجباری، campaign_id، که توضیح اسکیمایش به مدل میگوید آن را از segmentic_list_campaigns بردارد و از خودش نسازد.
پاسخ چند بخش دارد: خود کمپین با نسخههایش، پیشرفت، تفکیک رسیدن به مخاطب، تفکیک تحویل، تعامل به تفکیک کانال، و اثر افزوده در برابر گروه کنترل.
{
"engagement": [
{
"channel": "sms",
"channel_fa": "پیامک",
"issued": 41230,
"withheld": 180,
"measurable_open": 0,
"measurable_click": 41230,
"opened": 0,
"clicked": 5120,
"opened_unmeasurable": 41230,
"clicked_unmeasurable": 0,
"why_open": "پیامک رسید خواندن ندارد"
}
],
"uplift": {
"verdict": "positive",
"goal": "order_completed",
"treated_users": 41230,
"treated_conversions": 2110,
"control_users": 4581,
"control_conversions": 190,
"lift": 0.117,
"lift_low": 0.041,
"lift_high": 0.194,
"extra": 1420,
"extra_low": 812,
"extra_high": 2030,
"money_known": true,
"computed_at": "2026-07-21T09:30:00Z"
}
}
هر نرخی با صورت کسر و یک مخرج نامدار میآید. عدد opened بدون measurable_open بیمعنی است، و برای پیامک measurable_open صفر است، چون پیامک رسید خواندن ندارد. کمپینی که پیامش لینک نداشته، کمپین با نرخ کلیک صفر نیست. توضیح ابزار دقیقا به همین دلیل به مدل میگوید هر دو عدد را نقل کند و هرگز آنها را در یک درصد جمع نکند.
عدد lift بهتنهایی یک اندازهگیری نیست، وسط یک بازه است. عددهای lift_low و lift_high آن بازهاند، و گزارشی که فقط تخمین نقطهای را نقل کند تنها چیزی را دور ریخته که میگوید کمپین کار کرده یا نه.
کمپین تمامشدهای که پنجرهٔ سنجش هفتروزهاش هنوز بسته نشده، بخش اثر افزوده را با verdict: "too_early" میگیرد و بقیهٔ فیلدها تقریبا خالیاند. این عمدا ساخته میشود، چون در غیر این صورت آن بخش بعد از هر ارسال یک هفته غیب میشد و مثل یک قابلیت جاافتاده خوانده میشد، نه مثل «هنوز داریم میشماریم».
توضیح ابزار به یکی از اینها میگوید «حکم A/B». این دقیق نیست. مقدار uplift.verdict حکم گروه کنترل است، تیمارشده در برابر کنترل. نسخهها داخل campaign.variants هستند، ولی در این پاسخ مقایسهٔ تبدیل بین نسخهها وجود ندارد و ابزار جداگانهای برای آزمایش هم نیست.
شناسهٔ ناموجود و شناسهٔ حساب دیگری هر دو ۴۰۴ میگیرند، که مدل آن را بهصورت خشک http 404 میبیند.
segmentic_funnel_report
مجوز analytics.read. هزینه ۲۵. پشت قفل مالی است، پس حسابی که از سقفش گذشته باشد اینجا هم مثل پنل با account_locked رد میشود.
آرگومانهایش steps (آرایهای از گامها)، from و to (هر دو RFC3339 و اجباری)، window (اجباری) و strict (اختیاری) هستند. سرور بین ۲ تا ۱۲ گام میخواهد و بازهای حداکثر ۷۳۰ روزه.
window یعنی هرکس چقدر وقت دارد تا کل قیف را تمام کند، بهصورت رشتهٔ مدت مثل 7d یا 24h یا 30m. سرور پیشفرضی برایش ندارد و بدون آن درخواست را رد میکند، به این دلیل ساده که قیفی که در سی روز سنجیده شود و همان قیف در یک ساعت، دو سؤال متفاوتاند. مقدارش نباید بلندتر از خود بازه باشد.
strict یعنی گامها باید بدون هیچ رویدادی در میانه پشت سر هم بیایند. پیشفرض خاموش است، که معنای معمول «چه کسی محصولی را دید و خرید» همان است.
این ابزار تا همین اواخر اصلا نمیتوانست موفق شود: ساختار آرگومانهایش فیلد window نداشت، پس بدنه هیچوقت آن را حمل نمیکرد و سرور هر بار رد میکرد. مدل فقط http 400 را میدید، بدون هیچ پیامی، بعد از اینکه ۲۵ واحد بودجه خرج شده بود. اگر نسخهٔ کامپایلشدهتان قدیمی است، از مخزن دوباره بسازید؛ راه دیگر گرفتن قیف مستقیم با POST /v1/reports/funnel است که در گزارش و خروجی توضیح داده شده.
segmentic_retention_report
مجوز analytics.read. هزینه ۲۵. پشت همان قفل مالی. مسیر POST /v1/reports/retention.
آرگومانهایش from و to (اجباری) و چهار اختیاریاند: start و return و granularity و periods.
start و return از هم جدایند چون «برگشت» بهندرت یعنی «همان کار را دوباره کرد». یک فروشگاه میخواهد بداند چه کسی ثبتنام کرد و بعد خرید کرد؛ اینکه بپرسد اپ را باز کرد یا نه، عدد را زیبا میکند و به هیچ سؤالی جواب نمیدهد. هر کدام را خالی بگذارید یعنی «هر فعالیتی».
granularity یکی از day و week و month است. هفته شنبه شروع میشود و ماه جلالی است. پیشفرض day.
این ابزار تا همین اواخر به سؤالی غیر از سؤالی که از آن پرسیده شده جواب میداد، و خطا هم نمیداد: فیلد event میفرستاد، درخواست ماندگاری در سرور فیلدهای start و return دارد و فیلد ناشناخته را به جای رد کردن نادیده میگیرد. پس هر رویدادی که مدل نام میبرد بیصدا دور ریخته میشد و هر فراخوان «هر فعالیتی، بعد هر فعالیتی دوباره» را میسنجید. granularity و periods را هم هرگز نمیفرستاد. جواب باورپذیر ولی غلط از خطا بدتر است. اگر نسخهٔ کامپایلشدهتان قدیمی است، از مخزن دوباره بسازید.
segmentic_send_transactional_message
مجوز campaign.send. هزینه ۱ از بودجهٔ درخواست. مسیر POST /v1/messages که فقط وقتی ثبت است که آن نصب مسیر ارسال داشته باشد.
این تنها ابزاری است که به یک آدم میرسد. توضیحش با همین شروع میشود، با حروف بزرگ، و بلافاصله جایگزین درست را نام میبرد:
Send ONE message to ONE named person, immediately, an order update, a delivery
notice, a login code. THIS REACHES A REAL PERSON'S PHONE OR INBOX AND CANNOT BE
RECALLED. Marketing is refused: use a campaign, which a human schedules. The
idempotency_key must identify the real-world event (for example
'order-8821-shipped'), so that retrying is safe; never invent a random one,
because a fresh key on a retry sends a second message.
| آرگومان | اجباری | توضیح |
|---|---|---|
user_id | بله | همان آدم، با شناسهای که اپش گزارش میکند |
channel | بله | هشدار پایین را ببینید |
template_id | بله | یک قالب ذخیرهشده. متن پیام را نمیشود درجا فرستاد |
idempotency_key | بله | باید با ^[A-Za-z0-9._:-]{8,200}$ بخواند |
category | خیر | transactional (پیشفرض) یا critical. مقدار marketing رد میشود |
vars | خیر | حداکثر ۴۰ کلید، همه با مقدار رشتهای |
{
"user_id": "u_9137",
"channel": "sms",
"template_id": 42,
"vars": {"code": "8391"},
"idempotency_key": "order-8821-shipped"
}
{
"message_id": "t7.order-8821-shipped",
"status": "sent",
"sent_at": "2026-08-07T18:22:31.114Z"
}
فهرست کانالها در اسکیمای این ابزار یکجا غلط است. مقدار web را پیشنهاد میدهد و این مسیر web را نمیپذیرد. مقدارهای پذیرفته دقیقا push و sms و email و webpush و inapp و bale و eitaa و rubika هستند و لفظ به لفظ مقایسه میشوند، بدون هیچ ترجمهٔ نام مستعار. مدلی که از روی توضیح web بفرستد 400 transactional: unknown channel میگیرد و آن را بهصورت خشک http 400 میبیند.
پاسخ ۲۰۰ یعنی پیام رفت نیست. وقتی status برابر suppressed باشد و reason مقدار داشته باشد، پیام عمدا فرستاده نشده: لغو اشتراک، خاموشکردن کانال، فهرست منع، یا نبودن نشانی. توضیح ابزار این را نمیگوید، پس عامل موفقیت گزارش میکند. پیش از اینکه به کسی بگویید اطلاعرسانی رسیده، status و reason را نگاه کنید.
{
"message_id": "t7.order-8821-shipped",
"status": "suppressed",
"reason": "channel_opt_out",
"reason_fa": "این کانال را خاموش کرده است",
"sent_at": "2026-08-07T18:22:31.114Z"
}
مقدار replayed: true یعنی پاسخ از دفتر یکتایی درآمده و ارسال تازهای نبوده. با همین میشود «این کار را قبلا کردیم» را از «همین حالا کردیم» جدا کرد، و وقتی تلاش اول تایماوت خورده و نمیدانید کدام اتفاق افتاده، همین مهم است.
همان کلید که هنوز در پرواز باشد ۴۰۹ میگیرد با Retry-After: 1. پیامی که فرستاده شده ولی سطر دفترش نوشته نشده باز هم ۲۰۰ میگیرد، چون ۵۰۳ باعث میشود فراخوان دوباره تلاش کند و پیام دومی برود، که از گزارششدن یک سطر گمشده بدتر است.
دقیقا چه چیزهایی جلوی این فراخوانی را میگیرند: مجوز روی کلید، چهار آرگومان اجباری که پروتکل پیش از اجرای هندلر چک میکند، رد صریح کلید یکتایی خالی، و دفتر یکتایی و رد بازاریابی در خود سرور. تأیید دومرحلهای، توکن تأیید، سقف ارسال در هر نشست، و بودجهٔ گیرندهٔ هر کلید هیچکدام وجود ندارند. کلید یکتایی را خود مدل مینویسد.
ابزارهای نوشتنی
همه این ابزارها مسیر API عمومی را صدا میزنند. ابزارهای تکرار همراه با مسیرهایشان اضافه شدهاند و بقیه ابزارها مسیرهای قبلی را در دسترس میگذارند.
سه قاعده روی این نیمه برقرار است. اول، بدنه با تایپ درخواست خود سرور ساخته میشود نه با map دستی، پس اگر کسی نام فیلدی را در بکاند عوض کند این فایل کامپایل نمیشود. همان کلاس باگی که دو ابزار گزارش را خراب کرده بود، اینجا اصلا در دسترس نیست. دوم، اعتبارسنجی دو بار انجام میشود، یک بار محلی تا مدل جملهٔ قابلاقدام بگیرد و یک بار سمت سرور که تصمیم واقعی همانجاست. سوم، دو کار برگشتناپذیر اسم خود شیء را پس میخواهند.
segmentic_create_audience و segmentic_update_audience
مجوز segment.write. ساختن چیزی نمیفرستد: مخاطب یک سؤال ذخیرهشده است و کمپین و سناریو بعدا به آن اشاره میکنند.
فیلتر باید تعریف کامل باشد، یعنی {"version": 1, "root": {...}}. اگر یک شرط تنها بفرستید، ابزار پیش از تماس با سرور ردش میکند و میگوید چطور بپیچیدش؛ این شایعترین اشتباه است و پیام خود سرور برایش (unknown node kind: "") به هیچکس نمیگوید چه کند.
ویرایش، جایگزینی است نه ادغام. کل تعریف با چیزی که میفرستید عوض میشود، پس اول با segmentic_get_audience بخوانیدش. هر کمپین و سناریویی که به این مخاطب اشاره میکند، از همان لحظه فیلتر تازه را میبیند.
segmentic_delete_audience
مجوز segment.delete. اول مخاطب را میخواند، بعد confirm_name را با نام واقعیاش مقایسه میکند و اگر یکی نباشد کاری نمیکند. سناریو یا کمپینی که به آن اشاره میکند با آن حذف نمیشود.
segmentic_create_campaign
مجوز campaign.write. همیشه پیشنویس میسازد، هرچه هم پاس بدهید. ساختن و فرستادن دو فراخواناند چون یکیشان برگشتپذیر است.
مخاطب یا segment_id است یا filter، و اگر هر دو یا هیچکدام را بدهید ابزار ردش میکند: دو مخاطب روی یک کمپین چیزی نیست که سرور بتواند حلش کند، و حدسزدن اینکه کدام منظور بوده همانجایی است که پیام به آدمهای اشتباه میرسد. template_id اجباری است، چون متن پیام درجا فرستادنی نیست.
segmentic_submit_campaign_for_approval
مجوز campaign.write. روی حسابهایی که تأیید لازم دارند، پیشنویس را به صف بازبینی میفرستد. چیزی ارسال نمیشود. تأیید به وضعیت فعلی کمپین اثر انگشت میخورد، پس ویرایش بعد از تأیید یعنی باید دوباره فرستاده شود.
segmentic_set_campaign_recurrence
مجوز campaign.send. برنامه تکرار یک کمپین را شروع یا جایگزین میکند. اول کمپین را میخواند و نام دقیق فعلی آن را در confirm_name میخواهد. یک برنامه میتواند در آینده برای همه مخاطبان کمپین بسازد، پس همان محافظ ارسال فوری اینجا هم لازم است.
شیء recurrence تناوب daily، weekly یا monthly دارد. hour و hours با ساعت تهران هستند. شنبه روز 0 هفته است و روز ماه بر پایه تقویم جلالی خوانده میشود. ابزار همه این موارد را پیش از تماس با API بررسی میکند. خواندن ۵ واحد و نوشتن ۱ واحد هزینه دارد، در مجموع ۶ واحد.
segmentic_clear_campaign_recurrence
مجوز campaign.send. تکرارهای خودکار آینده را متوقف میکند. کمپینهایی را که برنامه قبلا ساخته پاک یا ویرایش نمیکند. این تماس ۱ واحد هزینه دارد و فقط campaign_id از segmentic_list_campaigns میخواهد.
segmentic_send_campaign
مجوز campaign.send. این ابزار به همهٔ مخاطبان کمپین میرسد و برگشت ندارد.
پیش از هر کاری کمپین را میخواند و اسمش را با confirm_name مقایسه میکند. اگر نخواند، ارسال انجام نمیشود. اگر اصلا نتواند اسم را بخواند هم انجام نمیشود: جهت شکست عمدا بسته است، چون کمپین پنج دقیقهٔ دیگر هم سر جایش است ولی پیام رفته را نمیشود پس گرفت.
پیش از این فراخوان، تعداد گیرنده را با segmentic_count_audience بگیرید و به آدمی که خواسته بگویید. فرق بین چهارصد نفر و چهارصد هزار نفر، یک فیلتر اشتباه است.
segmentic_ingest_events
مجوز profile.write. همان دری که بکاند خود مشتری از آن رویداد میفرستد، پس راهی است برای واردکردن حقیقت بدون کیت مرورگری.
پاسخ ۲۰۰ یعنی درخواست پذیرفته شد، نه اینکه همهٔ رویدادها. آرایهٔ rejected هر قلم ردشده را با شمارهٔ خانهاش در آرایهٔ شما و دلیلش میگوید. برای هر رویداد یک message_id پایدار از روی خود واقعیت بگذارید، وگرنه تلاش دوباره همان چیز را دو بار میشمارد.
segmentic_queue_export و segmentic_list_exports
مجوز data.export. تنها ابزارهایی که خروجیشان دادهٔ شخصی دارد، یعنی نشانی ایمیل و شمارهٔ تلفن. فایل از پنل برداشته میشود نه از اینجا، پس صفکردن یعنی ساختن چیزی که کسی باید برود و بیاوردش. پشت قفل مالی هم هستند.
چرا نبود یک مجوز، ابزار را پنهان میکند و رد نمیکند
ابزارها از روی کاری که کلید میتواند بکند ثبت میشوند، نه از روی چیزی که API عرضه میکند:
Registered conditionally on what the KEY can do, not on what the API
offers. A tool an agent can see is a tool it will try, and a refusal it
cannot fix reads to the model as a fault worth retrying, so the honest
move is not to offer it.
سه چیز از این نتیجه میشود و هر سه بالاخره کسی را غافلگیر میکنند.
- فهرست ابزارها موقع بالا آمدن فرایند قفل میشود. از روی پاسخ
whoamiساخته شده که پیش از ساخت سرور گرفته شده بود. باطلکردن یک مجوز، یا کل کلید، تا وقتی فرایند را ریاستارت نکنید فهرست اعلامشده را عوض نمیکند؛ فقط فراخوانیها سر API شکست میخورند. - نبودن، نه رد کردن. مجوزی که کلید ندارد یعنی ابزار اصلا در
tools/listظاهر نمیشود، پس مدل نمیبیندش، امتحانش نمیکند، و ۴۰۳ را با چیزی که ارزش تلاش دوباره دارد اشتباه نمیگیرد. - دروازه کلید است، نه آن نصب. سرور هرگز
GET /v1/capabilitiesرا نمیپرسد. کلیدی باcampaign.readروی نصبی که کمپین سرو نمیکند باز همsegmentic_list_campaignsرا میگیرد، و ابزار جواب میدهدunknown_endpoint: no such endpoint: GET /v1/campaigns, see GET /v1/capabilities.
ابزار segmentic_whoami بیرون از همهٔ شرطها نشسته، پس کلیدی که به هیچچیز دسترسی ندارد هم دقیقا یک ابزار عرضه میکند.
چیزی که ابزارها هرگز برنمیگردانند
هیچ ابزاری نشانی ایمیل یا شمارهٔ تلفن برنمیگرداند. حتی یکی.
| ابزار | دادهٔ شخصی در پاسخش |
|---|---|
segmentic_whoami | هیچ. نه ایمیل، نه نام، نه شناسهٔ کلید، نه نامک حساب |
segmentic_describe_data | تجمیعی: نام رویداد، حجم، کلید ویژگیها، نام و شمار ویژگیها. هرگز مقدار یک ویژگی |
segmentic_ingest_quality | شمار چیزهایی که ورودی داده رد یا اصلاح کرده، به تفکیک روز و کد و اپ و کتابخانه کلاینت. هرگز یک مقدار، یک شناسه یا متن خطا |
segmentic_list_audiences | نام سگمنت، توضیح، درخت فیلتر و اندازه. یک فیلتر میتواند مقداری را که یک بازاریاب تایپ کرده در خود داشته باشد، مثل نام شهر یا نام یک طرح، ولی نام هیچ آدمی نمیآید |
segmentic_describe_audience | یک بولین و یک جملهٔ فارسی. هیچ کوئری نمیزند |
segmentic_count_audience | یک عدد، یک جمله، یک مدتزمان |
segmentic_list_campaigns | فراداده و جمعهای کمپین |
segmentic_campaign_report | فقط تجمیعی: هر سطر یک شمارش است که بر اساس یک دلیل یا یک کانال گروه شده |
segmentic_funnel_report | شمار هر مرحله |
segmentic_retention_report | یک شبکه از شمارشها |
segmentic_send_transactional_message | همان user_id که خودتان دادهاید را پس میدهد. نه نشانی، نه متن رندرشده |
تضمین قویتر یک فیلتر نیست، نبودن است. نه ابزاری برای پرونده هست، نه ابزار خط زمانی، نه پیشنمایش سگمنت، نه خروجی، و نه جستجو. تنها هندلر داخلی که نام و نشانی ایمیل و شمارهٔ تلفن و شهر آدمهای واقعی را برمیگرداند روی شنوندهٔ خود داشبورد ثبت شده و از این API اصلا در دسترس نیست. سرور MCP هیچ ابزار شمارشکنندهای ندارد، چون مسیرهایی که چنین چیزی میدادند اصلا صدا زده نمیشوند.
یک چیز تضمین نشده است. متنی که خود تیم شما نوشته بدون حصار به مدل میرسد: نام سگمنت، نام کمپین و توضیحهای فارسی بهصورت رشتهٔ JSON ساده میآیند و هیچ پوشش «محتوای نامعتمد» ندارند. اگر کسی نام یک سگمنت را دستوری بگذارد که به مدل نشانه رفته، مدل آن را متن معمولی میخواند. فیلدهای متن آزاد پنل را یک کانال ورودی به عامل خودتان حساب کنید.
عامل چه چیزی را غلط میکند، و توضیحها چه میکنند
فایل سه حالت شکست را نام میبرد. دو تا از پادزهرها واقعیاند و یکی نیست، و دانستن اینکه کدام کدام است میارزد.
شناسه از خودش درمیآورد. پادزهرش فقط جمله است. ابزار segmentic_describe_data میگوید همیشه اول این را صدا بزن و نتیجهاش را توضیح میدهد. ابزار segmentic_list_audiences میگوید شناسه را پیدا کن و حدس نزن. ابزار segmentic_funnel_report میگوید نام گامها باید از segmentic_describe_data بیاید. توضیح آرگومانهای campaign_id و filter همین را تکرار میکنند. هیچچیز هیچکدام را اجبار نمیکند: فیلتری که نام رویداد ناموجود را ببرد تمیز کامپایل میشود و صفر برمیگرداند، که دقیقا شبیه یک مخاطب واقعی صفرنفره است. نه حالت نشست هست، نه پیششرط، و نه پیشنهاد «منظورت این بود».
بیشتر از نیازش میخواند. پادزهر اعلامشده صفحهبندی سمت سرور با سقفی است که مدل نمیتواند بالا ببرد. نصفش در کد هست و نصفش نیست: سقف واقعا وجود دارد و مدل هم نمیتواند بالا ببردش، چون یک LIMIT 200 ثابت در SQL است، ولی صفحهبندی نیست و هیچ راهی به سطر دویستویکم وجود ندارد. کاری که شد این بود که ابزارها دیگر وانمود نکنند دارند: آرگومانهای بیاثر برداشته شدند و توضیح هر دو ابزار خود سقف را به مدل میگوید. پادزهر واقعی نیمهٔ دوم همان جمله است، اینکه هیچ ابزاری دادهٔ تماس برنمیگرداند، و واقعی است چون آن ابزارها اصلا نوشته نشدند.
در حلقه میافتد. این یکی برقرار است. بودجه وزندار است و سمت سرور اعمال میشود، برای هر کلید و در هر دقیقه، پس مدلی که یک گزارش سنگین را دوباره و دوباره میزند را API رد میکند و نه ادب کلاینت. بودجه عمدا برای هر کلید جداست: مشتری یک کلید باریک به عامل میدهد و کلید یکپارچهسازی خودش را جدا نگه میدارد، و عامل از کنترل خارجشده نباید بتواند بودجهای را که خط سفارشها به آن وابسته است ته بکشد. توضیحها هم به همان سمت هل میدهند: به مدل میگویند یک بار بشمار و نه اینکه واریاسیونها را در حلقه بشمارد، برای هر سؤال یک فراخوانی خرج کن و نه برای هر فرضیه، و کل شبکهٔ ماندگاری را یکجا بخوان و نه کوهورت به کوهورت. ابزار segmentic_describe_audience عمدا رایگان تبلیغ شده تا مدل پیش از خرجکردن تمرین کند.
متن خطا هم به همین دلیل بدون بازنویسی رد میشود:
The API's own code and message, passed through rather than
paraphrased. "budget_exhausted" tells a model to wait; a rewritten
"something went wrong" tells it to retry immediately, which is the
opposite of what the server just asked for.
بودجه
هر فراخوانی سرور از همان بودجهٔ درخواست هر کلید خرج میکند که هر کلاینت دیگر API خرج میکند: پیشفرض ۶۰۰ واحد برای هر کلید در هر دقیقه، در یک پنجرهٔ ثابت. بالا آمدن خودش ۱ واحد بابت whoami خرج میکند، پیش از اجرای هر ابزاری.
تهکشیدن بودجه ۴۲۹ است با Retry-After: 60 و کد budget_exhausted که سالم به مدل میرسد. اگر خود شمارندهٔ بودجه در دسترس نباشد، درخواست رد میشود و اجازه داده نمیشود: این یکی بسته شکست میخورد.
مسیر ارسال تراکنشی یک محدودکنندهٔ دوم و جدا دارد که به جای هر کلید برای هر حساب میشمارد، و آن یکی وقتی کشش پایین باشد باز شکست میخورد، چون این مسیر رسید سفارش و کد ورود حمل میکند و ردکردن همهشان بهخاطر خرابی یک کش، قطعی ما را به شکست پرداخت شما تبدیل میکند. پیشفرض خاموش است.
سقف روزانهٔ گیرنده و سقف روزانهٔ سطرهای دادهٔ شخصی برای هر کلید وجود ندارند. ستونهایشان در یک مهاجرت هستند و هیچچیز آنها را نمیخواند و نمینویسد.
مدل هنگام شکست چه میبیند
شکستها بهصورت محتوای ابزار با isError: true برمیگردند، پس مدل متن را میخواند و میتواند کاری بکند. اینکه متن چه بگوید بستگی دارد کدام بخش سرور خطا را نوشته باشد، و این ناهموارترین لبهٔ کل این برنامه است.
کلاینت فقط یک شکل خطا را میفهمد، همان که لبهٔ عمومی مینویسد: {"error": {"code": ..., "message": ...}}. چند هندلر پشت آن لبه شکل دیگری مینویسند که در آن error یک رشتهٔ ساده است. کلاینت آنها را نمیتواند بخواند، پس فقط عدد وضعیت را گزارش میکند و بس.
سالم میرسند، با کد و پیام:
| کد | وضعیت |
|---|---|
unauthenticated | ۴۰۱ |
write_key_rejected | ۴۰۱ |
key_expired | ۴۰۱ |
forbidden، با نام مجوز در need | ۴۰۳ |
account_locked | ۴۰۳ |
filter_invalid | ۴۲۲ |
budget_exhausted | ۴۲۹ |
budget_unavailable | ۵۰۳ |
unknown_endpoint | ۴۰۴ |
به یک عدد خشک تنزل میکنند:
| چه چیزی واقعا شکست خورد | به مدل چه گفته میشود |
|---|---|
| فیلتری که در شمارش کامپایل نشد | http 400 |
| همهٔ خطاهای اعتبارسنجی قیف و ماندگاری | http 400 |
| همهٔ خطاهای فراخوان در ارسال، از جمله کانال غلط | http 400 |
| محدودکنندهٔ نرخ ارسال | http 429 |
| شناسهٔ کمپینی که وجود ندارد | http 404 |
| در دسترس نبودن اسکیما، فهرست سگمنت، فهرست کمپین یا کوئری شمارش | http 503 |
پس خطایی که طراحی بیشتر از همه میخواهد مدل درست بخواندش، یعنی budget_exhausted، کامل میرسد. خطاهایی که مدل بیشتر از همه برای درستکردن ورودی خودش لازم دارد، نمیرسند. وقتی عاملی http 400 گزارش کرد و نتوانست بگوید چرا، همان فراخوانی را با curl بزنید و بدنه را بخوانید.
بدنهٔ پاسخ تا ۴ مبیبایت خوانده میشود. بزرگتر از آن بریده میشود، بعد پارس نمیشود، و ابزار unreadable response را با ۲۰۰ نویسهٔ اول گزارش میکند.
چه کاری را عمدا نمیکند، و چه چیزی اصلا نیست
عمدی:
- هیچ ابزاری که یک آدم برگرداند. نه جستجوی پرونده، نه خط زمانی، نه پیشنمایش سگمنت، نه جستجو. تنها راهی که دادهٔ شخصی از اینجا بیرون میآید، خروجی است که خودش پشت
data.exportو قفل مالی است. - هیچ فلگی برای عوضکردن اینکه کلید چه میتواند بکند. نه
--allow-writeو نه--allow-send. - هیچ دسترسی مستقیم به دیتابیس. همهٔ دروازهها سر لبهٔ HTTP میمانند، جایی که از قبل تست شدهاند.
- هیچ کار برگشتناپذیری بدون تأیید اسم. ارسال کمپین و حذف مخاطب هر دو شیء را اول میخوانند و اگر اسم را نتوانند بخوانند، انجام نمیدهند.
نیست، و دستکم یکی از اینها را لازم خواهید داشت:
- ابزار سناریو و قالب. این دو هنوز روی API عمومی مسیری ندارند، پس MCP هم نمیتواند سناریو یا قالب بسازد و ویرایش کند. جالب اینکه
GET /v1/capabilitiesهمین حالاjourneysرا گزارش میکند، که دربارهٔ خود نصب راست است و دربارهٔ این سطح API نه. کارت خودش را دارد. - هر بیلد منتشرشدهای. نه ریلیز، نه ایمیج مستقل، نه فایل اجرایی مک یا ویندوز. خودتان کامپایل میکنید.
- فرم انتخاب مجوز در پنل. خود کلید محدودشده حالا ساخته میشود، ولی از API؛ لیست کشوییاش هنوز در صفحهٔ کلیدها نیست.
- صفحهبندی روی دو مسیر فهرست. هر کدام روی ۲۰۰ سطر تازهتر بریده میشوند و هیچچیز در پاسخ نمیگوید بریده شدهاند. ابزارها دستکم دیگر آرگومان صفحهبندی تعارف نمیکنند و سقف را در توضیحشان میگویند، ولی سطر دویستویکم همچنان دستنیافتنی است.
- خطاهای اعتبارسنجی خواندنی، طبق جدول بالا.
- لاگ بهازای هر ابزار. برنامه یک خط موقع بالا آمدن مینویسد و بعد از آن هیچ. لاگ حسابرسی حساب سازوکار جداگانهای است و خواندنها را ثبت نمیکند، پس هیچ ردی از اینکه یک عامل چه چیزی را نگاه کرده وجود ندارد.
- دستورالعمل سطح سرور در MCP. همهٔ هدایت داخل توضیح تکتک ابزارها زندگی میکند، که کلاینت ممکن است کامل به مدل نشان بدهد یا ندهد.
- پوشش تست فراتر از شکل درخواست. تستهای امروز بدنهای که هر ابزار میسازد را به تایپ درخواست خود سرور میدهند و همان تابع کامپایلی را رویش میدوانند که هندلر HTTP صدا میزند، که همان چیزی است که دو ابزار خراب را گرفت. رفتار سرتاسری در برابر یک API واقعی هنوز آزموده نمیشود.
اگر امروز به چیزی از فهرست دوم نیاز دارید، مستقیم از API مدیریتی استفاده کنید. هر کاری که سرور MCP میکند یک درخواست HTTP است، و هیچچیز در آن نیست که با curl نشود انجام داد.