Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/calm-routes-return.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@agent-native/core": patch
---

Keep cross-app `ask_app_status` polling attached to the original task route with an encrypted task handle, report the configured app identity in standalone MCP discovery, and reject unknown cross-app targets instead of running them locally.
30 changes: 19 additions & 11 deletions packages/core/docs/content/external-agents.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -535,13 +535,13 @@ See [MCP Apps](/docs/mcp-apps) for the full bridge details — transplant vs con

On top of the per-action tools the MCP server exposes a stable verb set, so an external agent has a predictable surface without guessing per-app action names:

| Tool | Side effects | Returns |
| -------------------------------------------------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------- |
| `list_apps` | none | workspace apps + their URLs / running state |
| `open_app({ app, view?, path?, params?, embed? })` | none | a deep link or same-origin route; `embed: true` renders the full app inline where supported |
| `ask_app({ app, message, async?, maxWaitMs? })` | agent loop | routes a natural-language task to that app's in-app agent; long work returns a durable `taskId` for `ask_app_status` polling |
| `create_workspace_app({ name, template })` | scaffolds | a new app booted via the workspace path, plus its running URL + deep link |
| `list_templates` | none | the allow-listed templates only |
| Tool | Side effects | Returns |
| -------------------------------------------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `list_apps` | none | workspace apps + their URLs / running state |
| `open_app({ app, view?, path?, params?, embed? })` | none | a deep link or same-origin route; `embed: true` renders the full app inline where supported |
| `ask_app({ app, message, async?, maxWaitMs? })` | agent loop | routes a natural-language task to that app's in-app agent; long work returns a durable `taskHandle` plus a legacy `taskId` for `ask_app_status` polling |
| `create_workspace_app({ name, template })` | scaffolds | a new app booted via the workspace path, plus its running URL + deep link |
| `list_templates` | none | the allow-listed templates only |

`create_workspace_app` rejects any non-allow-listed template — the public template allow-list in `packages/shared-app-config/templates.ts` is authoritative and CI-guarded; an external agent cannot widen it. A same-named template action overrides a builtin (template-over-core precedence). Disable the whole set with `MCPConfig.builtinCrossAppTools: false`.

Expand All @@ -551,20 +551,28 @@ For fast ChatGPT/Claude handoffs, the ideal path is direct: call the action that

### Long-running agent requests

When `ask_app` returns a `taskHandle`, pass that opaque value back unchanged to
`ask_app_status`; it preserves the original task route even if app discovery
changes between calls. The older `app` plus `taskId` arguments remain accepted
during migration.

MCP hosts and gateways do not share one long tool-call timeout; some enforce a
five-minute ceiling and others are configurable. Do not design an external
agent integration around a ten-minute blocking `tools/call`. `ask-agent` and
`ask_app` acknowledge hosted work quickly, then return a durable `taskId` when
the inline wait expires. Poll with `ask_app_status` using the returned `app`
and `taskId`; the result includes `pollAfterMs` as a host-friendly interval.
`ask_app` acknowledge hosted work quickly, then return a durable `taskHandle`
plus a legacy `taskId` when the inline wait expires. Poll with `ask_app_status`
by replaying the returned `poll.arguments` unchanged, especially the
`taskHandle`; the result includes `pollAfterMs` as a host-friendly interval.
The task must be created before the first response can time out, so a polling
URL returned only after a long call is not a reliable recovery contract.

If a bounded transient status read is unavailable, `ask_app_status` can return
`status: "unknown"`, `statusRead: "unavailable"`, `retryable: true`, the
original `app` and `taskId`, and `pollAfterMs` / `poll`. This means the status
read is unavailable—not that the durable task failed. Retry `ask_app_status`
with that exact `app` and `taskId`; never resubmit `ask_app` to recover, because
with the exact returned `poll.arguments`, including `taskHandle` when present;
legacy results without a handle must reuse the same `app` and `taskId`. Never
resubmit `ask_app` to recover, because
submission can duplicate work. Permanent authentication, not-found, and
protocol failures remain errors.

Expand Down
25 changes: 15 additions & 10 deletions packages/core/docs/content/locales/ar-SA/external-agents.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -509,13 +509,13 @@ Claude Code calls: manage-draft(to: "john@example.com", subject: "Q3 Report", bo

علاوة على أدوات تنفيذ الإجراء، يعرض خادم MCP مجموعة أفعال ثابتة، بحيث يتمتع الوكيل الخارجي بسطح يمكن التنبؤ به دون تخمين أسماء الإجراءات لكل تطبيق:

| الأداة | الآثار الجانبية | المرتجعات |
| -------------------------------------------------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `list_apps` | لا شيء | تطبيقات مساحة العمل + URL/حالة التشغيل |
| `open_app({ app, view?, path?, params?, embed? })` | لا شيء | رابط عميق أو مسار من نفس المصدر؛ يعرض `embed: true` التطبيق الكامل مضمنًا حيث يكون مدعومًا |
| `ask_app({ app, message, async?, maxWaitMs? })` | حلقة الوكيل | توجيه مهمة باللغة الطبيعية إلى الوكيل داخل التطبيق لذلك التطبيق؛ تُرجع المهام الطويلة `taskId` دائمًا لاستطلاع `ask_app_status` |
| `create_workspace_app({ name, template })` | السقالات | تطبيق جديد تم تشغيله عبر مسار مساحة العمل، بالإضافة إلى URL قيد التشغيل + رابط عميق |
| `list_templates` | لا شيء | النماذج المسموح بها فقط |
| الأداة | الآثار الجانبية | المرتجعات |
| -------------------------------------------------- | --------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| `list_apps` | لا شيء | تطبيقات مساحة العمل + URL/حالة التشغيل |
| `open_app({ app, view?, path?, params?, embed? })` | لا شيء | رابط عميق أو مسار من نفس المصدر؛ يعرض `embed: true` التطبيق الكامل مضمنًا حيث يكون مدعومًا |
| `ask_app({ app, message, async?, maxWaitMs? })` | حلقة الوكيل | توجيه مهمة باللغة الطبيعية إلى الوكيل داخل التطبيق لذلك التطبيق؛ تُرجع المهام الطويلة `taskHandle` مع `taskId` قديم لاستطلاع `ask_app_status` |
| `create_workspace_app({ name, template })` | السقالات | تطبيق جديد تم تشغيله عبر مسار مساحة العمل، بالإضافة إلى URL قيد التشغيل + رابط عميق |
| `list_templates` | لا شيء | النماذج المسموح بها فقط |

يرفض `create_workspace_app` أي قالب غير مدرج في القائمة المسموح بها - القائمة المسموح بها للقالب العام في `packages/shared-app-config/templates.ts` موثوقة ومحمية بواسطة CI؛ ولا يمكن لعامل خارجي توسيعه. يتجاوز إجراء القالب الذي يحمل نفس الاسم الإجراء المدمج (أسبقية القالب على المركز الأساسي). قم بتعطيل المجموعة بأكملها باستخدام `MCPConfig.builtinCrossAppTools: false`.

Expand All @@ -525,13 +525,18 @@ Claude Code calls: manage-draft(to: "john@example.com", subject: "Q3 Report", bo

### الطلبات الوكيلية طويلة الأمد

لا تشترك مضيفات وبوابات MCP في مهلة زمنية واحدة طويلة لاستدعاء الأداة؛ يفرض بعضها حدًا أقصى مدته خمس دقائق بينما يكون بعضها الآخر قابلاً للتكوين. لا تصمم تكامل الوكيل الخارجي بالاعتماد على استدعاء `tools/call` محظور لمدة عشر دقائق. يقر كل من `ask-agent` و`ask_app` بالعمل المستضاف بسرعة، ثم يُرجعان `taskId` دائمًا عند انتهاء مهلة الانتظار المضمّن. استطلع باستخدام `ask_app_status` مستخدمًا `app` و`taskId` المُرجعين؛ وتتضمن النتيجة `pollAfterMs` كفاصل زمني ملائم للمضيف. يجب إنشاء المهمة قبل أن تنتهي مهلة الاستجابة الأولى، لذا فإن رابط الاستطلاع الذي يُرجع فقط بعد استدعاء طويل ليس عقد استرداد يمكن الاعتماد عليه.
عندما تُرجع `ask_app` قيمة `taskHandle`، مرّر هذه القيمة المعتمة دون تغيير إلى
`ask_app_status`؛ فهي تحفظ مسار المهمة الأصلي حتى إذا تغيّر اكتشاف التطبيقات بين
الاستدعاءات. تظل وسيطتا `app` و`taskId` الأقدم مقبولتين خلال فترة الانتقال.

لا تشترك مضيفات وبوابات MCP في مهلة زمنية واحدة طويلة لاستدعاء الأداة؛ يفرض بعضها حدًا أقصى مدته خمس دقائق بينما يكون بعضها الآخر قابلاً للتكوين. لا تصمم تكامل الوكيل الخارجي بالاعتماد على استدعاء `tools/call` محظور لمدة عشر دقائق. يقر كل من `ask-agent` و`ask_app` بالعمل المستضاف بسرعة، ثم يُرجعان `taskHandle` مع `taskId` قديم عند انتهاء مهلة الانتظار المضمّن. استطلع باستخدام `ask_app_status` عبر إعادة `poll.arguments` المُرجعة دون تغيير، ولا سيما `taskHandle`؛ وتتضمن النتيجة `pollAfterMs` كفاصل زمني ملائم للمضيف. يجب إنشاء المهمة قبل أن تنتهي مهلة الاستجابة الأولى، لذا فإن رابط الاستطلاع الذي يُرجع فقط بعد استدعاء طويل ليس عقد استرداد يمكن الاعتماد عليه.

عند عدم إتاحة قراءة حالة عابرة ومحدودة المدة، يمكن أن تُرجع `ask_app_status` القيم
`status: "unknown"` و`statusRead: "unavailable"` و`retryable: true`، إلى جانب
`app` و`taskId` الأصليين و`pollAfterMs` / `poll`. يعني ذلك أن قراءة الحالة غير
متاحة، وليس أن المهمة الدائمة قد فشلت. أعد محاولة `ask_app_status` باستخدام `app`
و`taskId` نفسيهما تمامًا؛ ولا تعِد إرسال `ask_app` للاسترداد مطلقًا، إذ قد يكرر
متاحة، وليس أن المهمة الدائمة قد فشلت. أعد محاولة `ask_app_status` باستخدام
`poll.arguments` المُرجعة نفسها، بما فيها `taskHandle` عند وجودها؛ أما النتائج القديمة
من دون مقبض فيجب أن تعيد استخدام `app` و`taskId` نفسيهما. لا تعِد إرسال `ask_app` للاسترداد مطلقًا، إذ قد يكرر
الإرسال العمل. تظل أخطاء المصادقة الدائمة أو عدم العثور أو البروتوكول أخطاءً.

### جولة لكل تطبيق {#tour}
Expand Down
6 changes: 5 additions & 1 deletion packages/core/docs/content/locales/ar-SA/mcp-protocol.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,11 @@ Auto-mounted on every app. Speaks the standard MCP handshake (`initialize` → `
{
"poll": {
"tool": "ask_app_status",
"arguments": { "app": "mail", "taskId": "task_123" }
"arguments": {
"app": "mail",
"taskId": "task_123",
"taskHandle": "opaque_handle"
}
}
}
```
Expand Down
Loading
Loading