@agent-native/core 0.98.5 → 0.98.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +2 -2
- package/corpus/core/CHANGELOG.md +12 -0
- package/corpus/core/docs/content/external-agents.mdx +62 -7
- package/corpus/core/docs/content/locales/ar-SA/external-agents.mdx +38 -7
- package/corpus/core/docs/content/locales/de-DE/external-agents.mdx +39 -7
- package/corpus/core/docs/content/locales/es-ES/external-agents.mdx +39 -7
- package/corpus/core/docs/content/locales/fr-FR/external-agents.mdx +39 -7
- package/corpus/core/docs/content/locales/hi-IN/external-agents.mdx +38 -7
- package/corpus/core/docs/content/locales/ja-JP/external-agents.mdx +38 -7
- package/corpus/core/docs/content/locales/ko-KR/external-agents.mdx +38 -7
- package/corpus/core/docs/content/locales/pt-BR/external-agents.mdx +39 -7
- package/corpus/core/docs/content/locales/zh-CN/external-agents.mdx +37 -7
- package/corpus/core/docs/content/locales/zh-TW/external-agents.mdx +37 -7
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/a2a/handlers.ts +95 -21
- package/corpus/core/src/a2a/task-store.ts +65 -3
- package/corpus/core/src/integrations/adapters/slack.ts +123 -13
- package/corpus/core/src/integrations/identity-links-store.ts +210 -0
- package/corpus/core/src/integrations/identity.ts +132 -0
- package/corpus/core/src/integrations/index.ts +6 -0
- package/corpus/core/src/integrations/plugin.ts +58 -1
- package/corpus/core/src/integrations/types.ts +7 -0
- package/corpus/core/src/mcp/build-server.ts +127 -22
- package/corpus/core/src/mcp/external-agent-policy.ts +18 -0
- package/corpus/core/src/mcp/index.ts +1 -0
- package/corpus/core/src/server/agent-chat/plugin-options.ts +12 -1
- package/corpus/core/src/server/agent-chat/script-entries.ts +20 -3
- package/corpus/core/src/server/agent-chat-plugin.ts +3 -0
- package/corpus/core/src/templates/workspace-core/.agents/skills/external-agents/SKILL.md +34 -5
- package/corpus/templates/analytics/AGENTS.md +20 -5
- package/corpus/templates/analytics/actions/create-session-replay-agent-link.ts +0 -1
- package/corpus/templates/analytics/actions/get-session-replay-events.ts +0 -1
- package/corpus/templates/analytics/actions/get-session-replay-timeline.ts +40 -0
- package/corpus/templates/analytics/actions/list-error-issues.ts +12 -0
- package/corpus/templates/analytics/actions/query-agent-native-analytics.ts +4 -1
- package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +48 -17
- package/corpus/templates/analytics/changelog/2026-07-12-connected-external-agents-can-now-look-up-sessions-error-iss.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-12-session-identities-stay-visible-when-demo-mode-is-off.md +6 -0
- package/corpus/templates/analytics/server/lib/analytics-connector-catalog.ts +2 -2
- package/corpus/templates/analytics/server/lib/error-capture.ts +47 -5
- package/corpus/templates/analytics/server/lib/session-replay-agent-context.ts +49 -17
- package/corpus/templates/analytics/server/lib/session-replay.ts +16 -7
- package/corpus/templates/analytics/server/plugins/agent-chat.ts +4 -0
- package/corpus/templates/analytics/server/plugins/db.ts +15 -0
- package/corpus/templates/forms/app/global.css +54 -8
- package/corpus/templates/forms/app/pages/AskPage.tsx +35 -13
- package/corpus/templates/forms/app/pages/FormBuilderPage.tsx +11 -17
- package/corpus/templates/forms/app/pages/ResponsesPage.tsx +8 -5
- package/corpus/templates/forms/changelog/2026-07-12-ask-forms-suggestions-now-sit-beneath-the-composer-for-a-tig.md +6 -0
- package/corpus/templates/forms/changelog/2026-07-12-builder-response-tables-now-fill-the-view-without-a-redundan.md +6 -0
- package/corpus/templates/forms/changelog/2026-07-12-response-tables-now-fill-the-available-pane-with-a-flexible-.md +6 -0
- package/corpus/templates/mail/AGENTS.md +3 -1
- package/corpus/templates/mail/server/lib/mail-integrations.ts +3 -0
- package/dist/a2a/handlers.d.ts.map +1 -1
- package/dist/a2a/handlers.js +84 -15
- package/dist/a2a/handlers.js.map +1 -1
- package/dist/a2a/task-store.d.ts +20 -1
- package/dist/a2a/task-store.d.ts.map +1 -1
- package/dist/a2a/task-store.js +57 -4
- package/dist/a2a/task-store.js.map +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/integrations/adapters/slack.d.ts.map +1 -1
- package/dist/integrations/adapters/slack.js +98 -13
- package/dist/integrations/adapters/slack.js.map +1 -1
- package/dist/integrations/identity-links-store.d.ts +23 -0
- package/dist/integrations/identity-links-store.d.ts.map +1 -0
- package/dist/integrations/identity-links-store.js +158 -0
- package/dist/integrations/identity-links-store.js.map +1 -0
- package/dist/integrations/identity.d.ts +11 -0
- package/dist/integrations/identity.d.ts.map +1 -0
- package/dist/integrations/identity.js +100 -0
- package/dist/integrations/identity.js.map +1 -0
- package/dist/integrations/index.d.ts +2 -0
- package/dist/integrations/index.d.ts.map +1 -1
- package/dist/integrations/index.js +2 -0
- package/dist/integrations/index.js.map +1 -1
- package/dist/integrations/plugin.d.ts.map +1 -1
- package/dist/integrations/plugin.js +43 -1
- package/dist/integrations/plugin.js.map +1 -1
- package/dist/integrations/types.d.ts +6 -0
- package/dist/integrations/types.d.ts.map +1 -1
- package/dist/integrations/types.js.map +1 -1
- package/dist/mcp/build-server.d.ts +9 -2
- package/dist/mcp/build-server.d.ts.map +1 -1
- package/dist/mcp/build-server.js +97 -20
- package/dist/mcp/build-server.js.map +1 -1
- package/dist/mcp/external-agent-policy.d.ts +19 -0
- package/dist/mcp/external-agent-policy.d.ts.map +1 -0
- package/dist/mcp/external-agent-policy.js +2 -0
- package/dist/mcp/external-agent-policy.js.map +1 -0
- package/dist/mcp/index.d.ts +1 -0
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js.map +1 -1
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/corpus-jobs.d.ts +2 -2
- package/dist/resources/handlers.d.ts +3 -3
- package/dist/server/agent-chat/plugin-options.d.ts +11 -1
- package/dist/server/agent-chat/plugin-options.d.ts.map +1 -1
- package/dist/server/agent-chat/plugin-options.js.map +1 -1
- package/dist/server/agent-chat/script-entries.d.ts.map +1 -1
- package/dist/server/agent-chat/script-entries.js +16 -2
- package/dist/server/agent-chat/script-entries.js.map +1 -1
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +3 -0
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/templates/workspace-core/.agents/skills/external-agents/SKILL.md +34 -5
- package/docs/content/external-agents.mdx +62 -7
- package/docs/content/locales/ar-SA/external-agents.mdx +38 -7
- package/docs/content/locales/de-DE/external-agents.mdx +39 -7
- package/docs/content/locales/es-ES/external-agents.mdx +39 -7
- package/docs/content/locales/fr-FR/external-agents.mdx +39 -7
- package/docs/content/locales/hi-IN/external-agents.mdx +38 -7
- package/docs/content/locales/ja-JP/external-agents.mdx +38 -7
- package/docs/content/locales/ko-KR/external-agents.mdx +38 -7
- package/docs/content/locales/pt-BR/external-agents.mdx +39 -7
- package/docs/content/locales/zh-CN/external-agents.mdx +37 -7
- package/docs/content/locales/zh-TW/external-agents.mdx +37 -7
- package/package.json +1 -1
- package/src/templates/workspace-core/.agents/skills/external-agents/SKILL.md +34 -5
|
@@ -53,6 +53,7 @@ import {
|
|
|
53
53
|
startGoogleDocsPoller,
|
|
54
54
|
handlePushNotification,
|
|
55
55
|
} from "./google-docs-poller.js";
|
|
56
|
+
import { resolveDefaultIntegrationExecutionContext } from "./identity.js";
|
|
56
57
|
import {
|
|
57
58
|
disconnectIntegrationInstallation,
|
|
58
59
|
listIntegrationInstallations,
|
|
@@ -2343,13 +2344,53 @@ export function createIntegrationsPlugin(
|
|
|
2343
2344
|
return { error: `Integration ${platform} is not enabled` };
|
|
2344
2345
|
}
|
|
2345
2346
|
|
|
2346
|
-
|
|
2347
|
+
let incoming = await withCredentialContext(credentialContext, () =>
|
|
2347
2348
|
adapter.parseIncomingMessage(event),
|
|
2348
2349
|
);
|
|
2349
2350
|
if (!incoming) {
|
|
2350
2351
|
setResponseStatus(event, 200);
|
|
2351
2352
|
return "ok";
|
|
2352
2353
|
}
|
|
2354
|
+
if (adapter.hydrateIncomingIdentity) {
|
|
2355
|
+
try {
|
|
2356
|
+
incoming = await withCredentialContext(credentialContext, () =>
|
|
2357
|
+
adapter.hydrateIncomingIdentity!(incoming!),
|
|
2358
|
+
);
|
|
2359
|
+
} catch (err) {
|
|
2360
|
+
// Identity hydration is best-effort for platforms that have an
|
|
2361
|
+
// app-specific resolver. Slack's default DM resolver below will
|
|
2362
|
+
// still fail closed when the identity is absent or unverified.
|
|
2363
|
+
console.warn(
|
|
2364
|
+
`[integrations] Could not hydrate ${platform} sender identity:`,
|
|
2365
|
+
err instanceof Error ? err.message : err,
|
|
2366
|
+
);
|
|
2367
|
+
}
|
|
2368
|
+
}
|
|
2369
|
+
let defaultExecutionContext: IntegrationExecutionContext | null =
|
|
2370
|
+
null;
|
|
2371
|
+
if (
|
|
2372
|
+
incoming.platform === "slack" &&
|
|
2373
|
+
incoming.conversationType === "dm"
|
|
2374
|
+
) {
|
|
2375
|
+
try {
|
|
2376
|
+
defaultExecutionContext = await withCredentialContext(
|
|
2377
|
+
credentialContext,
|
|
2378
|
+
() => resolveDefaultIntegrationExecutionContext(incoming!),
|
|
2379
|
+
);
|
|
2380
|
+
} catch (err) {
|
|
2381
|
+
// An app-specific resolver may intentionally support a legacy
|
|
2382
|
+
// Slack setup. When no override exists, the branch below fails
|
|
2383
|
+
// closed instead of falling back to a service principal.
|
|
2384
|
+
if (!options?.resolveExecutionContext && !options?.resolveOwner) {
|
|
2385
|
+
console.error(
|
|
2386
|
+
`[integrations] default Slack DM identity denied message:`,
|
|
2387
|
+
err,
|
|
2388
|
+
);
|
|
2389
|
+
setResponseStatus(event, 200);
|
|
2390
|
+
return "ok";
|
|
2391
|
+
}
|
|
2392
|
+
}
|
|
2393
|
+
}
|
|
2353
2394
|
let executionContext: IntegrationExecutionContext = {
|
|
2354
2395
|
ownerEmail: `integration@${platform}`,
|
|
2355
2396
|
orgId: null as string | null,
|
|
@@ -2370,6 +2411,8 @@ export function createIntegrationsPlugin(
|
|
|
2370
2411
|
setResponseStatus(event, 200);
|
|
2371
2412
|
return "ok";
|
|
2372
2413
|
}
|
|
2414
|
+
} else if (defaultExecutionContext) {
|
|
2415
|
+
executionContext = defaultExecutionContext;
|
|
2373
2416
|
} else if (options?.resolveOwner) {
|
|
2374
2417
|
try {
|
|
2375
2418
|
executionContext.ownerEmail = await withCredentialContext(
|
|
@@ -2382,6 +2425,20 @@ export function createIntegrationsPlugin(
|
|
|
2382
2425
|
err,
|
|
2383
2426
|
);
|
|
2384
2427
|
}
|
|
2428
|
+
} else {
|
|
2429
|
+
try {
|
|
2430
|
+
executionContext = await withCredentialContext(
|
|
2431
|
+
credentialContext,
|
|
2432
|
+
() => resolveDefaultIntegrationExecutionContext(incoming!),
|
|
2433
|
+
);
|
|
2434
|
+
} catch (err) {
|
|
2435
|
+
console.error(
|
|
2436
|
+
`[integrations] default execution identity denied message:`,
|
|
2437
|
+
err,
|
|
2438
|
+
);
|
|
2439
|
+
setResponseStatus(event, 200);
|
|
2440
|
+
return "ok";
|
|
2441
|
+
}
|
|
2385
2442
|
}
|
|
2386
2443
|
if (executionContext.scopeId) {
|
|
2387
2444
|
incoming.integrationScopeId = executionContext.scopeId;
|
|
@@ -274,6 +274,13 @@ export interface PlatformAdapter {
|
|
|
274
274
|
*/
|
|
275
275
|
hydrateIncomingMessage?(incoming: IncomingMessage): Promise<IncomingMessage>;
|
|
276
276
|
|
|
277
|
+
/**
|
|
278
|
+
* Hydrate only verified sender identity before execution-context selection.
|
|
279
|
+
* This runs on the provider acknowledgement path and must avoid fetching
|
|
280
|
+
* conversation history or file bodies.
|
|
281
|
+
*/
|
|
282
|
+
hydrateIncomingIdentity?(incoming: IncomingMessage): Promise<IncomingMessage>;
|
|
283
|
+
|
|
277
284
|
/**
|
|
278
285
|
* Provider-specific response returned only after a message is verified and
|
|
279
286
|
* durably enqueued. Discord uses this to return a type-5 deferred response
|
|
@@ -51,6 +51,7 @@ import {
|
|
|
51
51
|
MCP_CONNECT_SCOPE,
|
|
52
52
|
} from "./connect-store.js";
|
|
53
53
|
import { MCP_APP_REQUEST_ORIGIN_CSP_SOURCE } from "./embed-app.js";
|
|
54
|
+
import type { ExternalAgentPolicy } from "./external-agent-policy.js";
|
|
54
55
|
import {
|
|
55
56
|
MCP_OAUTH_SCOPES,
|
|
56
57
|
hasMcpOAuthScope,
|
|
@@ -124,8 +125,9 @@ export interface MCPConfig {
|
|
|
124
125
|
* from connectors. It is no longer gated behind an environment variable, and
|
|
125
126
|
* the catalog is never inferred from the client name/user-agent.
|
|
126
127
|
*
|
|
127
|
-
* `tool-search` stays available in the compact catalog
|
|
128
|
-
*
|
|
128
|
+
* `tool-search` stays available in the compact catalog for discovery. A
|
|
129
|
+
* searched action still needs the connector catalog or authenticated-read
|
|
130
|
+
* policy before `tools/call`; callers who need the full surface up front opt in
|
|
129
131
|
* explicitly with `agent-native connect --full-catalog` (embeds a
|
|
130
132
|
* `catalog_scope: "full"` claim in the connect-minted JWT) or the
|
|
131
133
|
* deployment-wide `AGENT_NATIVE_MCP_FULL_CATALOG=1` env override.
|
|
@@ -134,6 +136,11 @@ export interface MCPConfig {
|
|
|
134
136
|
* setting it on `MCPConfig` directly; the plugin copies it through.
|
|
135
137
|
*/
|
|
136
138
|
connectorCatalog?: string[];
|
|
139
|
+
/**
|
|
140
|
+
* Optional policy for automatically exposing explicitly annotated,
|
|
141
|
+
* authenticated read actions to external MCP callers.
|
|
142
|
+
*/
|
|
143
|
+
externalAgents?: ExternalAgentPolicy;
|
|
137
144
|
}
|
|
138
145
|
|
|
139
146
|
/**
|
|
@@ -281,7 +288,7 @@ function explicitlyRequestsFullMcpCatalog(
|
|
|
281
288
|
// (100k+ tokens) into a context window just because a client called itself
|
|
282
289
|
// "code"/"cursor"/"codex" was a recurring footgun. Everything else gets the
|
|
283
290
|
// connector/compact catalog plus `tool-search`, which keeps every tool
|
|
284
|
-
//
|
|
291
|
+
// discoverable; only permitted actions are callable without full opt-in.
|
|
285
292
|
if (process.env.AGENT_NATIVE_MCP_FULL_CATALOG === "1") return true;
|
|
286
293
|
return requestMeta?.fullCatalog === true;
|
|
287
294
|
}
|
|
@@ -309,17 +316,101 @@ function warnFullCatalogServed(toolCount: number): void {
|
|
|
309
316
|
);
|
|
310
317
|
}
|
|
311
318
|
|
|
319
|
+
function isAuthenticatedReadAction(entry: ActionEntry): boolean {
|
|
320
|
+
return (
|
|
321
|
+
entry.http !== false &&
|
|
322
|
+
entry.http?.method === "GET" &&
|
|
323
|
+
entry.readOnly === true &&
|
|
324
|
+
entry.publicAgent?.expose === true &&
|
|
325
|
+
entry.publicAgent.readOnly === true &&
|
|
326
|
+
entry.publicAgent.requiresAuth === true
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
|
|
312
330
|
/**
|
|
313
|
-
*
|
|
314
|
-
*
|
|
315
|
-
*
|
|
316
|
-
*
|
|
317
|
-
*
|
|
331
|
+
* Hard exclusion list for the `authenticatedReads: "auto"` derivation ONLY
|
|
332
|
+
* (see `autoAuthenticatedReadNames` below). Explicit `connectorCatalog`
|
|
333
|
+
* entries are a deliberate, reviewed choice made by the app and are NOT
|
|
334
|
+
* affected by this list — an app can still list any of these names in
|
|
335
|
+
* `connectorCatalog` on purpose.
|
|
336
|
+
*
|
|
337
|
+
* These are the footgun families this file's other comments already call
|
|
338
|
+
* out ("removes footguns (db-exec, seed-*, extension tools, browser-session
|
|
339
|
+
* tools, etc.)" above, and "keeps db-exec / seed-* / extension /
|
|
340
|
+
* browser-session footguns off the external surface" near the connector
|
|
341
|
+
* tier below): generic core SQL access, template demo/seed data, the
|
|
342
|
+
* extension-management suite, live browser-session control, and Context
|
|
343
|
+
* X-Ray internals. `isAuthenticatedReadAction` only inspects action
|
|
344
|
+
* metadata (http/readOnly/publicAgent flags) — nothing stops a future
|
|
345
|
+
* change from mis-annotating one of these with that exact flag set again,
|
|
346
|
+
* the way `db-query`/`db-schema` were briefly (and accidentally) annotated
|
|
347
|
+
* before it was caught in review. These names can never be auto-derived
|
|
348
|
+
* from metadata alone; exposing one to external callers requires an
|
|
349
|
+
* explicit `connectorCatalog` entry.
|
|
318
350
|
*/
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
351
|
+
const AUTO_READ_EXCLUDED_ACTION_NAMES = new Set([
|
|
352
|
+
"db-query",
|
|
353
|
+
"db-schema",
|
|
354
|
+
"db-exec",
|
|
355
|
+
"db-patch",
|
|
356
|
+
"context-manifest-get",
|
|
357
|
+
"context-pin",
|
|
358
|
+
"context-evict",
|
|
359
|
+
"context-restore",
|
|
360
|
+
"context-report",
|
|
361
|
+
]);
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Substring/prefix patterns for excluded name *families* that aren't a
|
|
365
|
+
* fixed, enumerable set: `seed-*` varies per app/template, and the
|
|
366
|
+
* extension-management and browser-session tool suites use varying verb
|
|
367
|
+
* prefixes around a shared noun (e.g. `list-extensions`, `create-extension`,
|
|
368
|
+
* `hide-extension`; `list-browser-sessions`, `run-browser-session-action`) —
|
|
369
|
+
* so a leading-prefix match alone would miss most of them.
|
|
370
|
+
*/
|
|
371
|
+
const AUTO_READ_EXCLUDED_ACTION_PATTERNS: RegExp[] = [
|
|
372
|
+
/^seed-/,
|
|
373
|
+
/extension/,
|
|
374
|
+
/browser-session/,
|
|
375
|
+
];
|
|
376
|
+
|
|
377
|
+
function isAutoReadExcludedActionName(name: string): boolean {
|
|
378
|
+
return (
|
|
379
|
+
AUTO_READ_EXCLUDED_ACTION_NAMES.has(name) ||
|
|
380
|
+
AUTO_READ_EXCLUDED_ACTION_PATTERNS.some((pattern) => pattern.test(name))
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function autoAuthenticatedReadNames(
|
|
385
|
+
actions: Record<string, ActionEntry>,
|
|
386
|
+
config: MCPConfig,
|
|
387
|
+
): Set<string> {
|
|
388
|
+
if (config.externalAgents?.authenticatedReads !== "auto") return new Set();
|
|
389
|
+
return new Set(
|
|
390
|
+
Object.entries(actions)
|
|
391
|
+
.filter(
|
|
392
|
+
([name, entry]) =>
|
|
393
|
+
isAuthenticatedReadAction(entry) &&
|
|
394
|
+
!isAutoReadExcludedActionName(name),
|
|
395
|
+
)
|
|
396
|
+
.map(([name]) => name),
|
|
397
|
+
);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
function externalAgentDenySet(config: MCPConfig): Set<string> {
|
|
401
|
+
return new Set(
|
|
402
|
+
(config.externalAgents?.denyActions ?? [])
|
|
403
|
+
.map((name) => name.trim())
|
|
404
|
+
.filter(Boolean),
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function externalAgentWritesAreAskAppOnly(config: MCPConfig): boolean {
|
|
409
|
+
return (
|
|
410
|
+
config.externalAgents?.writes === "ask_app_only" ||
|
|
411
|
+
(config.externalAgents?.authenticatedReads === "auto" &&
|
|
412
|
+
config.externalAgents?.writes !== "allowlisted")
|
|
413
|
+
);
|
|
323
414
|
}
|
|
324
415
|
|
|
325
416
|
interface ResolvedMcpAppResource {
|
|
@@ -1300,16 +1391,21 @@ export async function createMCPServerForRequest(
|
|
|
1300
1391
|
),
|
|
1301
1392
|
)
|
|
1302
1393
|
: visibleActions;
|
|
1394
|
+
const autoReadNames = autoAuthenticatedReadNames(visibleActions, config);
|
|
1395
|
+
const connectorNames = new Set([
|
|
1396
|
+
...(config.connectorCatalog ?? []),
|
|
1397
|
+
...autoReadNames,
|
|
1398
|
+
]);
|
|
1399
|
+
const denyNames = externalAgentDenySet(config);
|
|
1400
|
+
const automaticConnectorPolicyActive =
|
|
1401
|
+
config.externalAgents?.authenticatedReads === "auto";
|
|
1303
1402
|
// Connector-catalog tier: when a template declares a connector allow-list,
|
|
1304
|
-
// serve exactly that curated surface
|
|
1305
|
-
//
|
|
1306
|
-
//
|
|
1307
|
-
// so the ~105-tool full catalog can never leak just because a deployment
|
|
1308
|
-
// forgot to set one. It also keeps db-exec / seed-* / extension /
|
|
1403
|
+
// serve exactly that curated surface plus any explicitly annotated
|
|
1404
|
+
// authenticated reads from `externalAgents.authenticatedReads: "auto"`.
|
|
1405
|
+
// This stays compact by default and keeps db-exec / seed-* / extension /
|
|
1309
1406
|
// browser-session footguns off the external surface.
|
|
1310
1407
|
const connectorCatalogActive =
|
|
1311
|
-
|
|
1312
|
-
config.connectorCatalog.length > 0 &&
|
|
1408
|
+
(connectorNames.size > 0 || automaticConnectorPolicyActive) &&
|
|
1313
1409
|
!fullCatalogRequested;
|
|
1314
1410
|
// When the connector catalog is active, filter directly from visibleActions
|
|
1315
1411
|
// rather than advertisedActionsBeforeConnector. This ensures the connector
|
|
@@ -1318,9 +1414,18 @@ export async function createMCPServerForRequest(
|
|
|
1318
1414
|
// would have activated the compact catalog for the same caller.
|
|
1319
1415
|
const advertisedActions = connectorCatalogActive
|
|
1320
1416
|
? Object.fromEntries(
|
|
1321
|
-
Object.entries(visibleActions).filter(([name]) =>
|
|
1322
|
-
|
|
1323
|
-
|
|
1417
|
+
Object.entries(visibleActions).filter(([name, entry]) => {
|
|
1418
|
+
if (denyNames.has(name)) return false;
|
|
1419
|
+
if (COMPACT_MCP_APP_CATALOG_BUILTINS.has(name)) return true;
|
|
1420
|
+
if (!connectorNames.has(name)) return false;
|
|
1421
|
+
if (
|
|
1422
|
+
externalAgentWritesAreAskAppOnly(config) &&
|
|
1423
|
+
entry.readOnly !== true
|
|
1424
|
+
) {
|
|
1425
|
+
return false;
|
|
1426
|
+
}
|
|
1427
|
+
return true;
|
|
1428
|
+
}),
|
|
1324
1429
|
)
|
|
1325
1430
|
: advertisedActionsBeforeConnector;
|
|
1326
1431
|
if (fullCatalogRequested) {
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Policy for the default authenticated external-agent connector surface.
|
|
3
|
+
*
|
|
4
|
+
* The policy is intentionally narrow: "auto" only discovers actions that
|
|
5
|
+
* explicitly opt into authenticated, read-only public-agent exposure. It
|
|
6
|
+
* never makes an unannotated action callable and never grants writes.
|
|
7
|
+
*/
|
|
8
|
+
export interface ExternalAgentPolicy {
|
|
9
|
+
/** Automatically advertise authenticated GET/read-only public-agent actions. */
|
|
10
|
+
authenticatedReads?: "off" | "auto";
|
|
11
|
+
/**
|
|
12
|
+
* Keep writes behind `ask_app` by default. `allowlisted` preserves explicit
|
|
13
|
+
* connectorCatalog write exposure for apps that intentionally need it.
|
|
14
|
+
*/
|
|
15
|
+
writes?: "ask_app_only" | "allowlisted";
|
|
16
|
+
/** Explicit action names to remove from the derived connector surface. */
|
|
17
|
+
denyActions?: string[];
|
|
18
|
+
}
|
|
@@ -10,6 +10,7 @@ export {
|
|
|
10
10
|
buildLinkArtifacts,
|
|
11
11
|
} from "./build-server.js";
|
|
12
12
|
export type { MCPCallerIdentity, MCPRequestMeta } from "./build-server.js";
|
|
13
|
+
export type { ExternalAgentPolicy } from "./external-agent-policy.js";
|
|
13
14
|
|
|
14
15
|
// stdio transport for `agent-native mcp serve` (Node-only).
|
|
15
16
|
export { runMCPStdio } from "./stdio.js";
|
|
@@ -5,6 +5,7 @@ import type {
|
|
|
5
5
|
AgentChatReference,
|
|
6
6
|
MentionProvider,
|
|
7
7
|
} from "../../agent/types.js";
|
|
8
|
+
import type { ExternalAgentPolicy } from "../../mcp/external-agent-policy.js";
|
|
8
9
|
import type { DatabaseToolsOption } from "../../scripts/db/tool-mode.js";
|
|
9
10
|
import type { PromptExamples } from "../prompts/index.js";
|
|
10
11
|
|
|
@@ -293,7 +294,9 @@ export interface AgentChatPluginOptions {
|
|
|
293
294
|
* browser-session tools) from connectors. It is no longer gated behind an
|
|
294
295
|
* environment variable, and the catalog is never inferred from the client.
|
|
295
296
|
*
|
|
296
|
-
* `tool-search` stays available
|
|
297
|
+
* `tool-search` stays available for discovery; a trimmed action still needs
|
|
298
|
+
* the connector catalog, authenticated-read policy, or full-catalog opt-in
|
|
299
|
+
* before an external caller can execute it.
|
|
297
300
|
* Callers who need the full surface up front opt in explicitly with
|
|
298
301
|
* `agent-native connect --full-catalog` (embeds a `catalog_scope: "full"`
|
|
299
302
|
* claim in their connect-minted JWT) or the deployment-wide
|
|
@@ -303,6 +306,14 @@ export interface AgentChatPluginOptions {
|
|
|
303
306
|
*/
|
|
304
307
|
connectorCatalog?: string[];
|
|
305
308
|
|
|
309
|
+
/**
|
|
310
|
+
* Default authenticated external-agent policy. In `auto` read mode, every
|
|
311
|
+
* action explicitly marked as GET + readOnly + publicAgent.requiresAuth is
|
|
312
|
+
* added to the connector surface automatically. Writes remain ask_app-only
|
|
313
|
+
* unless `writes: "allowlisted"` is explicitly selected.
|
|
314
|
+
*/
|
|
315
|
+
externalAgents?: ExternalAgentPolicy;
|
|
316
|
+
|
|
306
317
|
/**
|
|
307
318
|
* Skip mounting the remote MCP protocol route.
|
|
308
319
|
*
|
|
@@ -31,7 +31,10 @@ import {
|
|
|
31
31
|
function wrapCliScript(
|
|
32
32
|
tool: ActionTool,
|
|
33
33
|
cliDefault: (args: string[]) => Promise<void>,
|
|
34
|
-
opts?: {
|
|
34
|
+
opts?: {
|
|
35
|
+
allowedArgs?: readonly string[];
|
|
36
|
+
readOnly?: boolean;
|
|
37
|
+
},
|
|
35
38
|
): ActionEntry {
|
|
36
39
|
return {
|
|
37
40
|
tool,
|
|
@@ -39,6 +42,14 @@ function wrapCliScript(
|
|
|
39
42
|
run: async (args: Record<string, string>): Promise<string> => {
|
|
40
43
|
const cliArgs: string[] = [];
|
|
41
44
|
for (const [k, v] of Object.entries(args)) {
|
|
45
|
+
// MCP input schemas are descriptive and some hosts can still send
|
|
46
|
+
// undeclared keys. The externally exposed DB readers must never accept
|
|
47
|
+
// the CLI-only `--db` escape hatch, which could point at another local
|
|
48
|
+
// SQLite file. Keep their runtime surface identical to the advertised
|
|
49
|
+
// schema instead of trusting the client to validate it.
|
|
50
|
+
if (opts?.allowedArgs && !opts.allowedArgs.includes(k)) {
|
|
51
|
+
throw new Error(`Unknown argument: ${k}`);
|
|
52
|
+
}
|
|
42
53
|
const raw = v as unknown;
|
|
43
54
|
const value =
|
|
44
55
|
raw != null && typeof raw === "object"
|
|
@@ -101,7 +112,10 @@ export async function createDbScriptEntries(
|
|
|
101
112
|
},
|
|
102
113
|
},
|
|
103
114
|
schemaMod.default,
|
|
104
|
-
{
|
|
115
|
+
{
|
|
116
|
+
allowedArgs: ["format"],
|
|
117
|
+
readOnly: true,
|
|
118
|
+
},
|
|
105
119
|
),
|
|
106
120
|
"db-query": wrapCliScript(
|
|
107
121
|
{
|
|
@@ -134,7 +148,10 @@ export async function createDbScriptEntries(
|
|
|
134
148
|
},
|
|
135
149
|
},
|
|
136
150
|
queryMod.default,
|
|
137
|
-
{
|
|
151
|
+
{
|
|
152
|
+
allowedArgs: ["sql", "args", "format", "limit"],
|
|
153
|
+
readOnly: true,
|
|
154
|
+
},
|
|
138
155
|
),
|
|
139
156
|
};
|
|
140
157
|
|
|
@@ -1512,6 +1512,9 @@ export function createAgentChatPlugin(
|
|
|
1512
1512
|
...(options?.connectorCatalog
|
|
1513
1513
|
? { connectorCatalog: options.connectorCatalog }
|
|
1514
1514
|
: {}),
|
|
1515
|
+
...(options?.externalAgents
|
|
1516
|
+
? { externalAgents: options.externalAgents }
|
|
1517
|
+
: {}),
|
|
1515
1518
|
askAgent: async (message: string) => {
|
|
1516
1519
|
const mcpEngine = await resolveEngine({
|
|
1517
1520
|
engineOption: options?.engine,
|
|
@@ -160,15 +160,44 @@ tiny by default for ChatGPT/Claude-style app hosts, including OAuth MCP Apps
|
|
|
160
160
|
callers and generic authenticated remote HTTP/static-token callers. The model
|
|
161
161
|
sees the generic app-facing verbs (`list_apps`, `open_app`, `ask_app`, and
|
|
162
162
|
app-only `create_embed_session`) and routes UI through
|
|
163
|
-
`open_app({ embed: true })`. Stdio/code clients
|
|
164
|
-
|
|
165
|
-
`publicAgent.expose` remains the opt-in for safe read/ingest tools
|
|
166
|
-
compact MCP Apps catalog.
|
|
167
|
-
|
|
163
|
+
`open_app({ embed: true })`. Stdio/code clients use the same compact surface
|
|
164
|
+
unless they explicitly opt into the full catalog, and
|
|
165
|
+
`publicAgent.expose` remains the action-level opt-in for safe read/ingest tools
|
|
166
|
+
outside the compact MCP Apps catalog. Apps can set
|
|
167
|
+
`externalAgents.authenticatedReads: "auto"` to automatically advertise every
|
|
168
|
+
action that is GET + `readOnly` + `publicAgent.requiresAuth`, instead of
|
|
169
|
+
maintaining a long connector allow-list. This policy never includes writes by
|
|
170
|
+
default; use `writes: "ask_app_only"` to keep mutations behind `ask_app`, and
|
|
171
|
+
use `denyActions` for defense-in-depth vetoes. `tool-search` remains available
|
|
172
|
+
for discovery, but a searched action still needs to be in the connector
|
|
173
|
+
catalog, included by the authenticated-read policy, or enabled by the explicit
|
|
174
|
+
full-catalog opt-in before `tools/call` can execute it. Do not rely on
|
|
175
|
+
action-specific `mcpApp` resources appearing in ChatGPT/Claude discovery by
|
|
176
|
+
default; use `open_app` for the
|
|
168
177
|
first-class app embed path. If a specific
|
|
169
178
|
action truly must remain visible in that compact app-host catalog, set
|
|
170
179
|
`mcpApp.compactCatalog: true` as a rare escape hatch.
|
|
171
180
|
|
|
181
|
+
Generic core `db-schema` and `db-query` are intentionally not automatic
|
|
182
|
+
external reads. They remain available to the in-app agent through the normal
|
|
183
|
+
scoped SQL path, but broad schema/SQL access is too powerful to infer from
|
|
184
|
+
read-only metadata alone. If an app needs direct external querying, expose an
|
|
185
|
+
app-owned bounded GET action or add an explicit table/column allow-list with
|
|
186
|
+
row, byte, timeout, and audit limits. Keep `db-exec` and `db-patch` outside
|
|
187
|
+
automatic external-agent exposure.
|
|
188
|
+
|
|
189
|
+
Authentication and authorization are separate gates. A verified MCP OAuth or
|
|
190
|
+
connect token identifies the caller and organization; `publicAgent` only opts
|
|
191
|
+
an action into the external protocol surface and does not grant record access.
|
|
192
|
+
Actions still need `accessFilter`, `resolveAccess`, or `assertAccess` so private
|
|
193
|
+
documents/dashboards, shares, organization boundaries, and roles are enforced.
|
|
194
|
+
For Slack, verified DMs are linked to an existing Agent Native org member
|
|
195
|
+
before execution and run with that user's context; shared channels use a
|
|
196
|
+
service principal, and guests/external members cannot borrow personal access.
|
|
197
|
+
Managed Slack OAuth and the generated app manifest both request
|
|
198
|
+
`users:read.email`; existing installs must reconnect/reinstall after a scope
|
|
199
|
+
change, while legacy bot-token installs must add the scope in Slack manually.
|
|
200
|
+
|
|
172
201
|
### 1b. Fast-path expectations for MCP Apps hosts
|
|
173
202
|
|
|
174
203
|
Keep ChatGPT/Claude paths short. For a known app-facing intent, the external
|
|
@@ -113,11 +113,26 @@ details live in `.agents/skills/`.
|
|
|
113
113
|
clicks when needed. The authenticated connector catalog is the fast fallback
|
|
114
114
|
and exposes only bounded, user/org-scoped read actions for incident triage:
|
|
115
115
|
`list-session-recordings`, `get-session-replay-summary`,
|
|
116
|
-
`get-session-replay-
|
|
117
|
-
`list-error-issues`, and `get-error-issue`. Fetch the summary before
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
explicit security
|
|
116
|
+
`get-session-replay-timeline`, `query-agent-native-analytics`,
|
|
117
|
+
`list-error-issues`, and `get-error-issue`. Fetch the summary before the
|
|
118
|
+
sanitized timeline; the timeline contains bounded page/click/error markers
|
|
119
|
+
without raw replay events or storage references. Do not add replay blob or
|
|
120
|
+
dashboard mutation actions to this catalog without an explicit security
|
|
121
|
+
review.
|
|
122
|
+
- While Demo mode is enabled, session and error identities display anonymized
|
|
123
|
+
(`anonymous@builder.io`), so incident lookups must filter by the stable
|
|
124
|
+
`userId`/email parameter (e.g. `userId: "user@example.com"`) or recording/
|
|
125
|
+
issue id — never by matching emails read back from previously displayed
|
|
126
|
+
output.
|
|
127
|
+
- Analytics opts into the framework's authenticated-read connector policy, so
|
|
128
|
+
every Analytics action explicitly marked GET + read-only +
|
|
129
|
+
`publicAgent.requiresAuth` is directly callable by a signed-in external
|
|
130
|
+
agent. This includes the incident reads above plus safe dashboard/analysis
|
|
131
|
+
reads. Generic core `db-schema` / `db-query` remain in-app agent tools and
|
|
132
|
+
are not automatically exposed because broad SQL/schema access is too
|
|
133
|
+
powerful to infer from read-only metadata. Writes remain `ask_app`-only.
|
|
134
|
+
Use the explicit catalog and `denyActions` policy for any unusually
|
|
135
|
+
sensitive read instead of exposing an unannotated action.
|
|
121
136
|
- `/agents` is the Analytics home for admin surfaces. The default Monitoring
|
|
122
137
|
view embeds the shared observability dashboard for traces, conversations,
|
|
123
138
|
evals, experiments, and feedback. `/agents?view=dashboards` shows the
|
|
@@ -40,7 +40,6 @@ export default defineAction({
|
|
|
40
40
|
}),
|
|
41
41
|
http: { method: "GET" },
|
|
42
42
|
readOnly: true,
|
|
43
|
-
publicAgent: { expose: true, readOnly: true, requiresAuth: true },
|
|
44
43
|
run: async (args) => {
|
|
45
44
|
return getSessionReplayEvents(args.recordingId, resolveScope(), {
|
|
46
45
|
startSeq: args.startSeq,
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { defineAction } from "@agent-native/core";
|
|
2
|
+
import {
|
|
3
|
+
getRequestOrgId,
|
|
4
|
+
getRequestUserEmail,
|
|
5
|
+
} from "@agent-native/core/server";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
|
|
8
|
+
import { getSessionReplayTimeline } from "../server/lib/session-replay-agent-context.js";
|
|
9
|
+
|
|
10
|
+
function resolveScope() {
|
|
11
|
+
const userEmail = getRequestUserEmail();
|
|
12
|
+
if (!userEmail) throw new Error("no authenticated user");
|
|
13
|
+
return { userEmail, orgId: getRequestOrgId() || null };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export default defineAction({
|
|
17
|
+
description:
|
|
18
|
+
"Get a bounded, sanitized timeline for one first-party Analytics session recording: page navigation, clicks, inputs, scrolls, custom events, console errors, and failed network requests. Returns marker metadata only; never returns raw rrweb/DOM/input values, replay chunks, or storage references. Use this after get-session-replay-summary when direct incident triage needs the user's click/page sequence.",
|
|
19
|
+
schema: z.object({
|
|
20
|
+
recordingId: z.string().describe("The session_recordings id"),
|
|
21
|
+
eventLimit: z.coerce
|
|
22
|
+
.number()
|
|
23
|
+
.int()
|
|
24
|
+
.min(1)
|
|
25
|
+
.max(10000)
|
|
26
|
+
.optional()
|
|
27
|
+
.default(10000)
|
|
28
|
+
.describe(
|
|
29
|
+
"Maximum replay events to inspect before building the capped timeline",
|
|
30
|
+
),
|
|
31
|
+
}),
|
|
32
|
+
http: { method: "GET" },
|
|
33
|
+
readOnly: true,
|
|
34
|
+
publicAgent: { expose: true, readOnly: true, requiresAuth: true },
|
|
35
|
+
run: async (args) => {
|
|
36
|
+
return getSessionReplayTimeline(args.recordingId, resolveScope(), {
|
|
37
|
+
eventLimit: args.eventLimit,
|
|
38
|
+
});
|
|
39
|
+
},
|
|
40
|
+
});
|
|
@@ -26,6 +26,18 @@ export default defineAction({
|
|
|
26
26
|
.optional()
|
|
27
27
|
.describe("Search across issue title, type, culprit, and fingerprint."),
|
|
28
28
|
app: z.string().optional().describe("Optional app filter."),
|
|
29
|
+
sessionRecordingId: z
|
|
30
|
+
.string()
|
|
31
|
+
.optional()
|
|
32
|
+
.describe(
|
|
33
|
+
"Filter to issues with an occurrence in this session recording.",
|
|
34
|
+
),
|
|
35
|
+
userId: z
|
|
36
|
+
.string()
|
|
37
|
+
.optional()
|
|
38
|
+
.describe(
|
|
39
|
+
"Filter to issues with an occurrence for this user ID or user key.",
|
|
40
|
+
),
|
|
29
41
|
sort: z
|
|
30
42
|
.enum(["lastSeen", "eventCount", "firstSeen"])
|
|
31
43
|
.optional()
|
|
@@ -33,7 +33,10 @@ export default defineAction({
|
|
|
33
33
|
),
|
|
34
34
|
}),
|
|
35
35
|
readOnly: true,
|
|
36
|
-
|
|
36
|
+
// No raw HTTP route: a GET endpoint would put caller SQL in query strings
|
|
37
|
+
// and access logs. External MCP callers reach this through the explicit
|
|
38
|
+
// ANALYTICS_CONNECTOR_CATALOG entry, which dispatches run() in-process.
|
|
39
|
+
http: false,
|
|
37
40
|
publicAgent: { expose: true, readOnly: true, requiresAuth: true },
|
|
38
41
|
run: async (args) => {
|
|
39
42
|
return queryFirstPartyAnalytics(args.sql, resolveScope());
|