@agent-native/core 0.209.0-nightly-20261014124140 → 0.209.0
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/dist/agent/production-agent.d.ts +8 -0
- package/dist/agent/production-agent.js +68 -3
- package/dist/agent/strict-tool-input.d.ts +4 -0
- package/dist/agent/strict-tool-input.js +26 -0
- package/dist/agent/types.d.ts +7 -0
- package/dist/cli/skills-content/turn-into-app-attachments.d.ts +1 -1
- package/dist/cli/skills-content/turn-into-app-attachments.js +1 -3
- package/dist/cli/skills-content/turn-into-app-skill.d.ts +2 -2
- package/dist/cli/skills-content/turn-into-app-skill.js +15 -10
- package/dist/client/chat/agentkit-protocol.js +2 -0
- package/dist/client/chat/runtime.d.ts +1 -0
- package/dist/client/chat/runtime.js +35 -3
- package/dist/collab/routes.d.ts +2 -2
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/mcp/actions/call-mcp-tool.js +1 -1
- package/dist/mcp-client/app-api.d.ts +3 -0
- package/dist/mcp-client/app-api.js +8 -1
- package/dist/mcp-client/index.d.ts +7 -2
- package/dist/mcp-client/index.js +10 -4
- package/dist/mcp-client/manager.d.ts +16 -1
- package/dist/mcp-client/manager.js +35 -7
- package/dist/mcp-client/routes.js +9 -4
- package/dist/observability/metrics.d.ts +2 -0
- package/dist/observability/metrics.js +15 -1
- package/dist/observability/traces.js +8 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +11 -11
- package/dist/provider-api/actions/provider-api.d.ts +10 -10
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/server/agent-chat/additional-actions.d.ts +45 -0
- package/dist/server/agent-chat/additional-actions.js +707 -0
- package/dist/server/agent-chat/mcp-glue.d.ts +3 -1
- package/dist/server/agent-chat/mcp-glue.js +12 -1
- package/dist/server/agent-chat/plugin-options.d.ts +27 -0
- package/dist/server/agent-chat-plugin.js +59 -14
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/agent-engine-ollama-models-route.d.ts +2 -2
- package/dist/server/better-auth-instance.js +108 -18
- package/dist/server/builder-browser.d.ts +4 -2
- package/dist/server/builder-browser.js +21 -16
- package/dist/server/index.d.ts +2 -1
- package/dist/server/index.js +1 -1
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/shared/data-url.d.ts +2 -0
- package/dist/shared/data-url.js +12 -0
- package/dist/templates/workspace-core/.agents/skills/turn-into-app/references/attachments.md +1 -3
- package/dist/templates/workspace-core/.agents/skills/turn-into-app/references/fresh-project.md +13 -8
- package/dist/templates/workspace-core/.agents/skills/turn-into-app/references/source-brief.md +2 -2
- package/docs/content/agent-surfaces.mdx +61 -0
- package/docs/content/observability.mdx +2 -0
- package/package.json +5 -4
- package/src/templates/workspace-core/.agents/skills/turn-into-app/references/attachments.md +1 -3
- package/src/templates/workspace-core/.agents/skills/turn-into-app/references/fresh-project.md +13 -8
- package/src/templates/workspace-core/.agents/skills/turn-into-app/references/source-brief.md +2 -2
|
@@ -328,14 +328,19 @@ On a browser host the host is an orchestrator, not the build environment:
|
|
|
328
328
|
(archetype, direction, first viewport, sample-data plan, agent moments).
|
|
329
329
|
2. Call \`start-workspace-app-creation\` on the Agent-Native Dispatch MCP
|
|
330
330
|
connector (\`agent-native-dispatch\`). Put the brief and the design decisions
|
|
331
|
-
in \`prompt\`; pass an \`appId\`, a short \`description\`, the \`template
|
|
332
|
-
|
|
333
|
-
([attachments.md](attachments.md))
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
331
|
+
in \`prompt\`; pass an \`appId\`, a short \`description\`, and the \`template\`.
|
|
332
|
+
The tool's schema describes each argument. Send the Project's own text as
|
|
333
|
+
\`attachments\` ([attachments.md](attachments.md)), never as a summary: the
|
|
334
|
+
Project instructions as \`project-instructions.md\` and every knowledge file,
|
|
335
|
+
each verbatim as \`text/plain\`. Never paste whole knowledge files or binary
|
|
336
|
+
data into \`prompt\`. Attachments are model context
|
|
337
|
+
and are not copied into the generated workspace, so tell the Builder agent in
|
|
338
|
+
\`prompt\` to persist what the app needs: knowledge files as app resources, the
|
|
339
|
+
instructions as the in-app agent's instructions. If a file cannot be
|
|
340
|
+
reproduced verbatim (not in context, too large), say so in the brief and the
|
|
341
|
+
report; never swap in a digest silently. A workbook cannot be attached
|
|
342
|
+
(attachments take images, PDF, text, and JSON): pass a bounded CSV rendering
|
|
343
|
+
as text, or a public URL, and say what was left out.
|
|
339
344
|
3. Report what Dispatch actually returned: the branch, the path, and the status.
|
|
340
345
|
The browser host cannot run or inspect the app, and a returned path can 404
|
|
341
346
|
until the branch merges and deploys, so the report ends at pending or
|
|
@@ -1180,7 +1185,7 @@ heading; write \`n/a\` where a heading does not apply.
|
|
|
1180
1185
|
Source and provenance: type (thread, skill, Project, spreadsheet, export), file names, ids, what was read and what was not
|
|
1181
1186
|
Project goal: the user and the repeatable job, in one sentence
|
|
1182
1187
|
Configuration and constraints: audience, rules, output standards, approved tools
|
|
1183
|
-
Knowledge sources: reference files the app ships with, by name
|
|
1188
|
+
Knowledge sources: reference files the app ships with, by name
|
|
1184
1189
|
Repeatable workflow: the job's trigger, steps, and outcome, in the source's own words
|
|
1185
1190
|
Inputs and outputs: what comes in, what the user gets back, fixed output shapes
|
|
1186
1191
|
Judgment and review points: 1-3 agent moments, each tied to the object it acts on; what the user confirms
|
|
@@ -1198,7 +1203,7 @@ App design: archetype, direction, first-viewport sketch, sample-data plan
|
|
|
1198
1203
|
|
|
1199
1204
|
- Name the latest successful, repeatable job. A walkthrough of one example is
|
|
1200
1205
|
evidence for the job, not the job.
|
|
1201
|
-
- Keep references bounded: a file name, URL,
|
|
1206
|
+
- Keep references bounded: a file name, URL, or short summary
|
|
1202
1207
|
instead of a pasted document. No secrets, credentials, or customer data.
|
|
1203
1208
|
- Corrections the user made in the source are product rules. Record each one
|
|
1204
1209
|
as an invariant.
|
|
@@ -587,6 +587,7 @@ function runtimeMessageToProtocolMessage(message, fallbackTextFormat) {
|
|
|
587
587
|
role: message.role,
|
|
588
588
|
parts: message.content.map((part) => runtimePartToProtocolPart(part, textFormat)),
|
|
589
589
|
createdAt: message.createdAt,
|
|
590
|
+
status: message.status,
|
|
590
591
|
metadata: message.metadata,
|
|
591
592
|
};
|
|
592
593
|
}
|
|
@@ -677,6 +678,7 @@ function protocolMessageToRuntimeMessage(message) {
|
|
|
677
678
|
role: message.role,
|
|
678
679
|
content: message.parts.map(protocolPartToRuntimePart),
|
|
679
680
|
createdAt: message.createdAt,
|
|
681
|
+
status: message.status,
|
|
680
682
|
metadata: message.metadata,
|
|
681
683
|
};
|
|
682
684
|
}
|
|
@@ -78,6 +78,7 @@ export interface AgentChatRuntimeMessage<TContentPart extends AgentChatRuntimeCo
|
|
|
78
78
|
readonly role: AgentChatRuntimeRole;
|
|
79
79
|
readonly content: readonly TContentPart[];
|
|
80
80
|
readonly createdAt?: string;
|
|
81
|
+
readonly status?: "streaming" | "complete" | "error";
|
|
81
82
|
readonly metadata?: AgentChatRuntimeMetadata;
|
|
82
83
|
}
|
|
83
84
|
export interface AgentChatRuntimeAttachment {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { AgentKitRunSlotBusyError } from "@agent-native/agentkit/client";
|
|
2
2
|
import { AUTO_CONTINUE_OF_RUN_METADATA_KEY, CONTINUE_OF_RUN_METADATA_KEY, } from "../../agent/auto-continue.js";
|
|
3
|
+
import { RUN_NOT_STARTED_METADATA_KEY } from "../../shared/agent-chat-run-not-started.js";
|
|
3
4
|
import { agentEngineStatusUrlForChatApi, requireAgentEngineConfiguredForDispatch, } from "../agent-engine-readiness.js";
|
|
4
5
|
import { getOrCreateAnalyticsSessionId } from "../analytics-session.js";
|
|
5
6
|
import { agentChatStreamingUrl, agentNativePath } from "../api-path.js";
|
|
@@ -770,6 +771,36 @@ function isSyntheticToolHistoryOmissionMessage(message) {
|
|
|
770
771
|
(message.id === AGENTKIT_TOOL_HISTORY_OMISSION_MESSAGE_ID_PREFIX ||
|
|
771
772
|
/^agentkit-tool-history-omission-\d+$/.test(message.id)));
|
|
772
773
|
}
|
|
774
|
+
function isUnstartedNativeHistoryMessage(message) {
|
|
775
|
+
if (message.role === "user" && message.status === "error")
|
|
776
|
+
return true;
|
|
777
|
+
if (message.role !== "user" && message.role !== "assistant")
|
|
778
|
+
return false;
|
|
779
|
+
const custom = asRecord(asRecord(message.metadata)?.custom);
|
|
780
|
+
return custom?.[RUN_NOT_STARTED_METADATA_KEY] === true;
|
|
781
|
+
}
|
|
782
|
+
function nativeHistorySourceMessages(messages, currentPrompt) {
|
|
783
|
+
const source = messages ?? [];
|
|
784
|
+
let currentPromptMessageIndex;
|
|
785
|
+
for (let index = source.length - 1; index >= 0; index--) {
|
|
786
|
+
const message = source[index];
|
|
787
|
+
if (isSyntheticToolHistoryOmissionMessage(message))
|
|
788
|
+
continue;
|
|
789
|
+
if (message.role !== "user")
|
|
790
|
+
continue;
|
|
791
|
+
if (!currentPrompt.trim()) {
|
|
792
|
+
currentPromptMessageIndex = index;
|
|
793
|
+
}
|
|
794
|
+
else {
|
|
795
|
+
const match = runtimeMessageTextMatches(message, currentPrompt);
|
|
796
|
+
if (match.matches || !match.complete)
|
|
797
|
+
currentPromptMessageIndex = index;
|
|
798
|
+
}
|
|
799
|
+
break;
|
|
800
|
+
}
|
|
801
|
+
return source.filter((message, index) => index === currentPromptMessageIndex ||
|
|
802
|
+
!isUnstartedNativeHistoryMessage(message));
|
|
803
|
+
}
|
|
773
804
|
function equivalentRuntimeContent(left, right) {
|
|
774
805
|
let remaining = 4096;
|
|
775
806
|
const leftAncestors = new WeakSet();
|
|
@@ -818,7 +849,7 @@ function equivalentRuntimeContent(left, right) {
|
|
|
818
849
|
return compare(left, right, 0);
|
|
819
850
|
}
|
|
820
851
|
function priorNativeHistoryMessages(messages, currentPrompt) {
|
|
821
|
-
const source = messages
|
|
852
|
+
const source = nativeHistorySourceMessages(messages, currentPrompt);
|
|
822
853
|
let currentPromptMessageIndex;
|
|
823
854
|
for (let index = source.length - 1; index >= 0; index--) {
|
|
824
855
|
const message = source[index];
|
|
@@ -1810,6 +1841,7 @@ function assertUnambiguousKeyOnlyApprovedHistory(sources, approvedToolCalls, app
|
|
|
1810
1841
|
}
|
|
1811
1842
|
function nativeStructuredHistoryFromMessages(messages, currentPrompt, currentAttachmentUrls, supplementalMessages = [], supplementalHistoryOmitted = false, supplementalToolHistoryOmitted = false, approvedToolCalls = [], approvedToolCallAskIds = []) {
|
|
1812
1843
|
const structuredHistory = [];
|
|
1844
|
+
const historyMessages = nativeHistorySourceMessages(messages, currentPrompt);
|
|
1813
1845
|
const approvedToolCallSet = new Set(approvedToolCalls);
|
|
1814
1846
|
const approvedToolCallAskIdSet = new Set(approvedToolCallAskIds);
|
|
1815
1847
|
const callCandidates = [];
|
|
@@ -1820,9 +1852,9 @@ function nativeStructuredHistoryFromMessages(messages, currentPrompt, currentAtt
|
|
|
1820
1852
|
const textCandidates = [];
|
|
1821
1853
|
let toolHistoryPosition = 0;
|
|
1822
1854
|
let hasToolHistory = false;
|
|
1823
|
-
const sources = boundedStructuredHistorySources(
|
|
1855
|
+
const sources = boundedStructuredHistorySources(historyMessages, currentPrompt, supplementalMessages, currentAttachmentUrls);
|
|
1824
1856
|
assertUnambiguousKeyOnlyApprovedHistory(sources.messages, approvedToolCalls, approvedToolCallAskIds);
|
|
1825
|
-
const boundaryResultsToOmit = boundaryToolResultPartsToOmit(
|
|
1857
|
+
const boundaryResultsToOmit = boundaryToolResultPartsToOmit(historyMessages, supplementalMessages, sources, supplementalToolHistoryOmitted);
|
|
1826
1858
|
for (const { message, parts, priority, textPriority } of sources.messages) {
|
|
1827
1859
|
if (message.role !== "user" && message.role !== "assistant")
|
|
1828
1860
|
continue;
|
package/dist/collab/routes.d.ts
CHANGED
|
@@ -13,16 +13,16 @@ export declare const getCollabState: import("h3").EventHandlerWithFetch<import("
|
|
|
13
13
|
};
|
|
14
14
|
}>>;
|
|
15
15
|
export declare const postCollabUpdate: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
16
|
-
error: string;
|
|
17
16
|
ok?: undefined;
|
|
17
|
+
error: string;
|
|
18
18
|
} | {
|
|
19
19
|
error?: undefined;
|
|
20
20
|
ok: boolean;
|
|
21
21
|
}>>;
|
|
22
22
|
export declare const postCollabText: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
23
|
-
text?: undefined;
|
|
24
23
|
ok?: undefined;
|
|
25
24
|
error: string;
|
|
25
|
+
text?: undefined;
|
|
26
26
|
} | {
|
|
27
27
|
error?: undefined;
|
|
28
28
|
ok: boolean;
|
|
@@ -11,5 +11,5 @@ export default defineAction({
|
|
|
11
11
|
toolName: z.string().trim().min(1),
|
|
12
12
|
arguments: z.record(z.string(), z.unknown()).default({}),
|
|
13
13
|
}),
|
|
14
|
-
run: ({ serverId, toolName, arguments: args }) => callMcpTool(serverId, toolName, args),
|
|
14
|
+
run: ({ serverId, toolName, arguments: args }, context) => callMcpTool(serverId, toolName, args, { context }),
|
|
15
15
|
});
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ActionRunContext } from "../action.js";
|
|
1
2
|
export interface AppMcpTool {
|
|
2
3
|
serverId: string;
|
|
3
4
|
name: string;
|
|
@@ -20,6 +21,8 @@ export interface ListVisibleMcpToolsOptions {
|
|
|
20
21
|
export interface CallMcpToolOptions {
|
|
21
22
|
/** Refuse the call unless the server's URL belongs to this provider. */
|
|
22
23
|
providerId?: string;
|
|
24
|
+
/** Caller identity and invocation source forwarded to MCP authorization. */
|
|
25
|
+
context?: ActionRunContext;
|
|
23
26
|
}
|
|
24
27
|
export declare class McpAppApiError extends Error {
|
|
25
28
|
readonly statusCode: 401 | 403 | 503;
|
|
@@ -36,7 +36,14 @@ export async function callMcpTool(serverId, originalToolName, args = {}, options
|
|
|
36
36
|
!isServerForProvider(manager, serverId, options.providerId)) {
|
|
37
37
|
throw new McpAppApiError("MCP tool is not available in this request scope.", 403);
|
|
38
38
|
}
|
|
39
|
-
return manager.callTool(buildMcpToolName(serverId, originalToolName), args
|
|
39
|
+
return manager.callTool(buildMcpToolName(serverId, originalToolName), args, {
|
|
40
|
+
context: {
|
|
41
|
+
...options.context,
|
|
42
|
+
caller: options.context?.caller ?? "frontend",
|
|
43
|
+
userEmail: context.userEmail,
|
|
44
|
+
orgId: context.orgId ?? null,
|
|
45
|
+
},
|
|
46
|
+
});
|
|
40
47
|
}
|
|
41
48
|
function requireAuthenticatedRequest() {
|
|
42
49
|
const context = getRequestContext();
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { loadMcpConfig, autoDetectMcpConfig, type McpConfig, type McpServerConfig, } from "./config.js";
|
|
2
|
-
export { McpClientManager, buildMcpToolName, parseMcpToolName, MCP_TOOL_PREFIX, type McpTool, type McpClientManagerOptions, } from "./manager.js";
|
|
2
|
+
export { McpClientManager, buildMcpToolName, parseMcpToolName, MCP_TOOL_PREFIX, type McpTool, type McpToolCallOptions, type McpToolAuthorizationResolver, type McpClientManagerOptions, } from "./manager.js";
|
|
3
3
|
export { listRemoteServers, addRemoteServer, addOAuthRemoteServer, addFirstPartyRemoteServer, isFirstPartyRemoteEndpointTrusted, removeRemoteServer, validateRemoteUrl, normalizeServerName, mergedConfigKey, parseMergedKey, hashEmail, toHttpServerConfig, toHttpServerConfigAsync, materializeHeaders, type RemoteMcpScope, type StoredRemoteMcpServer, } from "./remote-store.js";
|
|
4
4
|
export { findConnectedMcpServersForProvider, type ConnectedMcpProviderResult, type ConnectedMcpProviderServer, } from "./provider-connections.js";
|
|
5
5
|
export { finishMcpOAuthAuthorization, getMcpOAuthAccessToken, readMcpOAuthCredentials, saveMcpOAuthCredentials, startMcpOAuthAuthorization, type McpOAuthCallbackResult, type McpOAuthCredentialBundle, type McpOAuthDiscoveryState, type McpOAuthProviderOptions, type McpOAuthStartResult, } from "./oauth-client.js";
|
|
@@ -14,14 +14,19 @@ export { callMcpTool, listVisibleMcpTools, McpAppApiError, type AppMcpTool, type
|
|
|
14
14
|
export { classifyMcpToolCall, evaluateMcpToolCallPolicy, type McpToolCallClassification, type McpToolEffect, type McpToolFamily, type McpToolInvocationPolicy, type McpToolPolicyDecision, } from "./tool-policy.js";
|
|
15
15
|
export { configureScreenMemory, queryScreenMemoryForAgent, queryScreenMemoryContext, readScreenMemoryStatus, type ScreenMemoryConfig, type ScreenMemoryAgentQueryResult, type ScreenMemoryContextItem, type ScreenMemoryCoverageGap, type ScreenMemoryEvidenceItem, type ScreenMemoryEvidenceSourceType, type ScreenMemoryQueryResult, type ScreenMemoryRetrievalCoverage, type ScreenMemorySegmentReference, type ScreenMemoryStatus, type ScreenMemoryTimeRange, type ScreenMemoryTruncation, } from "./screen-memory-local.js";
|
|
16
16
|
export { MCP_ACTION_RESULT_MARKER, isMcpActionResult, type AgentMcpAppPayload, type AgentMcpAppResourceContent, type McpActionResult, } from "./app-result.js";
|
|
17
|
+
import { type ActionAuthorize } from "../action.js";
|
|
17
18
|
import type { EngineToolResultImagePart } from "../agent/engine/types.js";
|
|
18
19
|
import type { ActionEntry } from "../agent/production-agent.js";
|
|
19
20
|
import type { McpClientManager, McpTool } from "./manager.js";
|
|
20
21
|
import { type McpToolInvocationPolicy } from "./tool-policy.js";
|
|
22
|
+
export interface McpActionEntryResolution extends Partial<Pick<ActionEntry, "needsApproval" | "allowPersistentApproval">> {
|
|
23
|
+
/** Authorization checked on every invocation immediately before the remote call. */
|
|
24
|
+
authorize?: ActionAuthorize<Record<string, unknown>>;
|
|
25
|
+
}
|
|
21
26
|
export interface McpActionEntryOptions {
|
|
22
27
|
invocationPolicy?: McpToolInvocationPolicy;
|
|
23
28
|
toolNames?: readonly string[];
|
|
24
|
-
resolveActionEntry?: (tool: McpTool) =>
|
|
29
|
+
resolveActionEntry?: (tool: McpTool) => McpActionEntryResolution | undefined;
|
|
25
30
|
}
|
|
26
31
|
export declare function mcpToolsToActionEntries(manager: McpClientManager, options?: McpActionEntryOptions): Record<string, ActionEntry>;
|
|
27
32
|
export declare function syncMcpActionEntries(manager: McpClientManager, target: Record<string, ActionEntry>, options?: McpActionEntryOptions): void;
|
package/dist/mcp-client/index.js
CHANGED
|
@@ -16,7 +16,7 @@ export { classifyMcpToolCall, evaluateMcpToolCallPolicy, } from "./tool-policy.j
|
|
|
16
16
|
export { configureScreenMemory, queryScreenMemoryForAgent, queryScreenMemoryContext, readScreenMemoryStatus, } from "./screen-memory-local.js";
|
|
17
17
|
export { MCP_ACTION_RESULT_MARKER, isMcpActionResult, } from "./app-result.js";
|
|
18
18
|
import { getToolUiResourceUri, isToolVisibilityAppOnly, isToolVisibilityModelOnly, } from "@modelcontextprotocol/ext-apps/app-bridge";
|
|
19
|
-
import { MCP_APP_MIME_TYPE } from "../action.js";
|
|
19
|
+
import { MCP_APP_MIME_TYPE, } from "../action.js";
|
|
20
20
|
import { normalizeToolResultImages } from "../agent/tool-result-images.js";
|
|
21
21
|
import { MCP_ACTION_RESULT_MARKER, toolForMcpAppPayload, } from "./app-result.js";
|
|
22
22
|
import { evaluateMcpToolCallPolicy, } from "./tool-policy.js";
|
|
@@ -45,7 +45,7 @@ export function syncMcpActionEntries(manager, target, options = {}) {
|
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
47
|
function mcpToolToActionEntry(manager, tool, options = {}) {
|
|
48
|
-
const resolvedEntry = options.resolveActionEntry?.(tool);
|
|
48
|
+
const { authorize, ...resolvedEntry } = options.resolveActionEntry?.(tool) ?? {};
|
|
49
49
|
return {
|
|
50
50
|
tool: {
|
|
51
51
|
description: tool.description,
|
|
@@ -59,7 +59,7 @@ function mcpToolToActionEntry(manager, tool, options = {}) {
|
|
|
59
59
|
},
|
|
60
60
|
...(tool.annotations?.readOnlyHint === true ? { readOnly: true } : {}),
|
|
61
61
|
...resolvedEntry,
|
|
62
|
-
run: async (args) => {
|
|
62
|
+
run: async (args, context) => {
|
|
63
63
|
// Defense-in-depth: even if a cross-scope MCP tool somehow makes it
|
|
64
64
|
// into the LLM's visible tool list, reject invocation here so we never
|
|
65
65
|
// execute a user's credentials on behalf of another user.
|
|
@@ -73,10 +73,16 @@ function mcpToolToActionEntry(manager, tool, options = {}) {
|
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
75
|
try {
|
|
76
|
-
const result = await manager.callTool(tool.name, args
|
|
76
|
+
const result = await manager.callTool(tool.name, args, {
|
|
77
|
+
context,
|
|
78
|
+
authorize,
|
|
79
|
+
});
|
|
77
80
|
return await buildMcpActionResult(manager, tool, args, result);
|
|
78
81
|
}
|
|
79
82
|
catch (err) {
|
|
83
|
+
if (err?.statusCode === 403) {
|
|
84
|
+
throw err;
|
|
85
|
+
}
|
|
80
86
|
return buildMcpErrorActionResult(tool, args, `Error calling MCP tool ${tool.name}: ${err?.message ?? err}`);
|
|
81
87
|
}
|
|
82
88
|
},
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type ActionAuthorize, type ActionRunContext } from "../action.js";
|
|
1
2
|
import type { McpConfig, McpServerConfig } from "./config.js";
|
|
2
3
|
export declare const MCP_TOOL_PREFIX = "mcp__";
|
|
3
4
|
export interface McpTool {
|
|
@@ -21,6 +22,13 @@ export interface McpClientManagerOptions {
|
|
|
21
22
|
debug?: boolean;
|
|
22
23
|
connectTimeoutMs?: number;
|
|
23
24
|
}
|
|
25
|
+
export interface McpToolCallOptions {
|
|
26
|
+
/** Identity and invocation source forwarded to the authorization callback. */
|
|
27
|
+
context?: ActionRunContext;
|
|
28
|
+
/** Fallback used by standalone action wrappers without a manager resolver. */
|
|
29
|
+
authorize?: ActionAuthorize<Record<string, unknown>>;
|
|
30
|
+
}
|
|
31
|
+
export type McpToolAuthorizationResolver = (tool: McpTool) => ActionAuthorize<Record<string, unknown>> | undefined;
|
|
24
32
|
export declare class McpClientManager {
|
|
25
33
|
private readonly servers;
|
|
26
34
|
private readonly debug;
|
|
@@ -29,8 +37,15 @@ export declare class McpClientManager {
|
|
|
29
37
|
private config;
|
|
30
38
|
private sdk;
|
|
31
39
|
private readonly listeners;
|
|
40
|
+
private resolveToolAuthorization?;
|
|
32
41
|
private reconfigureQueue;
|
|
33
42
|
constructor(config: McpConfig | null, options?: McpClientManagerOptions);
|
|
43
|
+
/**
|
|
44
|
+
* Install the app-owned authorization resolver used by every MCP tool-call
|
|
45
|
+
* surface. Passing `undefined` restores the backwards-compatible no-policy
|
|
46
|
+
* behavior.
|
|
47
|
+
*/
|
|
48
|
+
setToolAuthorizationResolver(resolver?: McpToolAuthorizationResolver): void;
|
|
34
49
|
get enabled(): boolean;
|
|
35
50
|
getConfig(): McpConfig | null;
|
|
36
51
|
get configuredServers(): string[];
|
|
@@ -69,7 +84,7 @@ export declare class McpClientManager {
|
|
|
69
84
|
/** The config the server's current tools were loaded from. */
|
|
70
85
|
getServerConfig(serverId: string): McpServerConfig | null;
|
|
71
86
|
hasServer(serverId: string): boolean;
|
|
72
|
-
callTool(prefixedName: string, args: unknown): Promise<unknown>;
|
|
87
|
+
callTool(prefixedName: string, args: unknown, options?: McpToolCallOptions): Promise<unknown>;
|
|
73
88
|
readResource(serverId: string, uri: string): Promise<unknown>;
|
|
74
89
|
readResourceForTool(prefixedName: string, uri: string): Promise<unknown>;
|
|
75
90
|
stop(): Promise<void>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { MCP_APP_EXTENSION_ID, MCP_APP_MIME_TYPE } from "../action.js";
|
|
1
|
+
import { MCP_APP_EXTENSION_ID, MCP_APP_MIME_TYPE, } from "../action.js";
|
|
2
2
|
import { formatMcpConnectError, httpStatusFromError } from "./errors.js";
|
|
3
3
|
import { isFirstPartyRemoteEndpointTrusted, parseMergedKey, } from "./remote-store.js";
|
|
4
4
|
export const MCP_TOOL_PREFIX = "mcp__";
|
|
@@ -110,12 +110,21 @@ export class McpClientManager {
|
|
|
110
110
|
config;
|
|
111
111
|
sdk = null;
|
|
112
112
|
listeners = new Set();
|
|
113
|
+
resolveToolAuthorization;
|
|
113
114
|
reconfigureQueue = Promise.resolve();
|
|
114
115
|
constructor(config, options = {}) {
|
|
115
116
|
this.config = config;
|
|
116
117
|
this.debug = !!options.debug;
|
|
117
118
|
this.connectTimeoutMs = options.connectTimeoutMs;
|
|
118
119
|
}
|
|
120
|
+
/**
|
|
121
|
+
* Install the app-owned authorization resolver used by every MCP tool-call
|
|
122
|
+
* surface. Passing `undefined` restores the backwards-compatible no-policy
|
|
123
|
+
* behavior.
|
|
124
|
+
*/
|
|
125
|
+
setToolAuthorizationResolver(resolver) {
|
|
126
|
+
this.resolveToolAuthorization = resolver;
|
|
127
|
+
}
|
|
119
128
|
get enabled() {
|
|
120
129
|
return !!this.config && Object.keys(this.config.servers).length > 0;
|
|
121
130
|
}
|
|
@@ -546,7 +555,7 @@ export class McpClientManager {
|
|
|
546
555
|
const entry = this.servers.get(serverId);
|
|
547
556
|
return !!entry?.client && !entry.error;
|
|
548
557
|
}
|
|
549
|
-
async callTool(prefixedName, args) {
|
|
558
|
+
async callTool(prefixedName, args, options = {}) {
|
|
550
559
|
const parsed = parseMcpToolName(prefixedName);
|
|
551
560
|
if (!parsed) {
|
|
552
561
|
throw new Error(`Tool name "${prefixedName}" does not look like an MCP tool (expected mcp__<server>__<tool>)`);
|
|
@@ -555,16 +564,35 @@ export class McpClientManager {
|
|
|
555
564
|
if (!entry || !entry.client) {
|
|
556
565
|
throw new Error(`MCP server "${parsed.serverId}" is not connected${entry?.error ? `: ${entry.error}` : ""}`);
|
|
557
566
|
}
|
|
558
|
-
|
|
567
|
+
const normalizedArgs = args && typeof args === "object" ? args : {};
|
|
568
|
+
// The expired-session retry replays this attempt on a reconnected entry
|
|
569
|
+
// whose tool metadata was reloaded, so authorization must run per attempt.
|
|
570
|
+
return this.withExpiredSessionRetry(entry, async (current) => {
|
|
559
571
|
const known = current.tools.find((t) => t.name === prefixedName);
|
|
560
572
|
if (!known) {
|
|
561
573
|
throw new Error(`MCP server "${parsed.serverId}" does not expose tool "${parsed.toolName}"`);
|
|
562
574
|
}
|
|
563
|
-
|
|
575
|
+
const client = current.client;
|
|
576
|
+
const authorize = this.resolveToolAuthorization?.(known) ?? options.authorize;
|
|
577
|
+
if (authorize) {
|
|
578
|
+
const verdict = await authorize(normalizedArgs, options.context);
|
|
579
|
+
if (verdict === false) {
|
|
580
|
+
const error = new Error("Not authorized");
|
|
581
|
+
error.name = "ForbiddenError";
|
|
582
|
+
error.statusCode = 403;
|
|
583
|
+
throw error;
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
const liveEntry = this.servers.get(parsed.serverId);
|
|
587
|
+
const liveTool = liveEntry?.tools.find((tool) => tool.name === prefixedName);
|
|
588
|
+
if (liveEntry !== current ||
|
|
589
|
+
liveEntry.client !== client ||
|
|
590
|
+
liveTool !== known) {
|
|
591
|
+
throw new Error(`MCP server "${parsed.serverId}" changed while authorizing tool "${parsed.toolName}"`);
|
|
592
|
+
}
|
|
593
|
+
return client.callTool({
|
|
564
594
|
name: parsed.toolName,
|
|
565
|
-
arguments:
|
|
566
|
-
? args
|
|
567
|
-
: {},
|
|
595
|
+
arguments: normalizedArgs,
|
|
568
596
|
});
|
|
569
597
|
});
|
|
570
598
|
}
|
|
@@ -404,10 +404,15 @@ async function withMcpAppRequestContext(event, fn) {
|
|
|
404
404
|
setResponseStatus(event, 401);
|
|
405
405
|
return { error: "Authentication required" };
|
|
406
406
|
}
|
|
407
|
+
const context = {
|
|
408
|
+
caller: "frontend",
|
|
409
|
+
userEmail: email,
|
|
410
|
+
orgId: orgId ?? null,
|
|
411
|
+
};
|
|
407
412
|
return runWithRequestContext({
|
|
408
413
|
userEmail: email ?? undefined,
|
|
409
414
|
orgId: orgId ?? undefined,
|
|
410
|
-
}, fn);
|
|
415
|
+
}, () => fn(context));
|
|
411
416
|
}
|
|
412
417
|
function serverHasVisibleTools(manager, serverId) {
|
|
413
418
|
return manager
|
|
@@ -465,7 +470,7 @@ async function handleMcpAppCallTool(event, manager) {
|
|
|
465
470
|
setResponseStatus(event, 400);
|
|
466
471
|
return { error: "serverId and same-server toolName are required" };
|
|
467
472
|
}
|
|
468
|
-
return withMcpAppRequestContext(event, async () => {
|
|
473
|
+
return withMcpAppRequestContext(event, async (context) => {
|
|
469
474
|
const tool = mcpAppCallableTool(manager, serverId, originalToolName);
|
|
470
475
|
if (!tool) {
|
|
471
476
|
setResponseStatus(event, 403);
|
|
@@ -474,10 +479,10 @@ async function handleMcpAppCallTool(event, manager) {
|
|
|
474
479
|
try {
|
|
475
480
|
return await manager.callTool(tool.name, body.arguments && typeof body.arguments === "object"
|
|
476
481
|
? body.arguments
|
|
477
|
-
: {});
|
|
482
|
+
: {}, { context });
|
|
478
483
|
}
|
|
479
484
|
catch (err) {
|
|
480
|
-
setResponseStatus(event, 400);
|
|
485
|
+
setResponseStatus(event, err?.statusCode === 403 ? 403 : 400);
|
|
481
486
|
return { error: err?.message ?? "MCP tool call failed" };
|
|
482
487
|
}
|
|
483
488
|
});
|
|
@@ -27,6 +27,8 @@ export interface GenAiChatMetric {
|
|
|
27
27
|
supportedModels?: readonly string[];
|
|
28
28
|
durationMs?: number;
|
|
29
29
|
failed?: boolean;
|
|
30
|
+
/** The engine's error code for a failed call, bounded before it becomes `error.type`. */
|
|
31
|
+
errorCode?: string;
|
|
30
32
|
inputTokens?: number;
|
|
31
33
|
outputTokens?: number;
|
|
32
34
|
}
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* a migration plan.
|
|
8
8
|
*/
|
|
9
9
|
import { CORE_ACTION_GROUPS } from "../framework-tools.js";
|
|
10
|
+
import { agentTroubleCauseForCode } from "../shared/analytics-events.js";
|
|
10
11
|
import { getRegisteredObservabilityProvider, } from "./otel-provider.js";
|
|
11
12
|
import { trackingIdentityProperties } from "./tracking-identity.js";
|
|
12
13
|
const METER_NAME = "@agent-native/core";
|
|
@@ -120,6 +121,14 @@ function boundedModel(model, supportedModels) {
|
|
|
120
121
|
? model
|
|
121
122
|
: "_OTHER";
|
|
122
123
|
}
|
|
124
|
+
// Error codes are thrower-supplied strings, so only HTTP statuses and the
|
|
125
|
+
// closed trouble-cause set reach the label; everything else stays `_OTHER`.
|
|
126
|
+
function boundedGenAiErrorType(errorCode) {
|
|
127
|
+
const normalized = errorCode?.trim().toLowerCase();
|
|
128
|
+
if (normalized && /^http_[45]\d\d$/.test(normalized))
|
|
129
|
+
return normalized;
|
|
130
|
+
return agentTroubleCauseForCode(normalized) ?? "_OTHER";
|
|
131
|
+
}
|
|
123
132
|
/** One model round trip (`gen_ai.operation.name=chat`). */
|
|
124
133
|
export function recordGenAiChat(call) {
|
|
125
134
|
const recorded = instruments();
|
|
@@ -131,7 +140,12 @@ export function recordGenAiChat(call) {
|
|
|
131
140
|
...(call.providerName ? { "gen_ai.provider.name": call.providerName } : {}),
|
|
132
141
|
};
|
|
133
142
|
if (call.durationMs !== undefined) {
|
|
134
|
-
recorded.genAiOperationDuration.record(Math.max(0, call.durationMs) / 1_000, call.failed
|
|
143
|
+
recorded.genAiOperationDuration.record(Math.max(0, call.durationMs) / 1_000, call.failed
|
|
144
|
+
? {
|
|
145
|
+
...attributes,
|
|
146
|
+
"error.type": boundedGenAiErrorType(call.errorCode),
|
|
147
|
+
}
|
|
148
|
+
: attributes);
|
|
135
149
|
}
|
|
136
150
|
for (const [tokenType, tokens] of [
|
|
137
151
|
["input", call.inputTokens],
|
|
@@ -523,7 +523,7 @@ export async function instrumentAgentLoop(opts) {
|
|
|
523
523
|
finishOtelModelSpan(tripIndex, {
|
|
524
524
|
status: modelCallFailed ? "error" : "success",
|
|
525
525
|
errorMessage: modelErrorMessage,
|
|
526
|
-
errorCode: finalErrorCode,
|
|
526
|
+
errorCode: trip?.errorCode ?? finalErrorCode,
|
|
527
527
|
attributes: modelSpanAttributes(tripIndex),
|
|
528
528
|
endTime: modelRoundTrips[tripIndex]?.end,
|
|
529
529
|
});
|
|
@@ -646,6 +646,8 @@ export async function instrumentAgentLoop(opts) {
|
|
|
646
646
|
trip.end = end;
|
|
647
647
|
if (event.reason)
|
|
648
648
|
trip.stopReason = event.reason;
|
|
649
|
+
if (event.errorCode)
|
|
650
|
+
trip.errorCode = event.errorCode;
|
|
649
651
|
}
|
|
650
652
|
if (event.reason === undefined || event.reason === "error") {
|
|
651
653
|
modelSpansAwaitingFinalError.add(tripIndex);
|
|
@@ -1362,6 +1364,11 @@ export async function instrumentAgentLoop(opts) {
|
|
|
1362
1364
|
failed: tripIndex === interruptedModelRoundTrip ||
|
|
1363
1365
|
!trip.stopReason ||
|
|
1364
1366
|
trip.stopReason === "error",
|
|
1367
|
+
// Only the last call's failure can be the one that ended the run.
|
|
1368
|
+
errorCode: trip.errorCode ??
|
|
1369
|
+
(tripIndex === modelRoundTrips.length - 1
|
|
1370
|
+
? runTerminalCode
|
|
1371
|
+
: undefined),
|
|
1365
1372
|
inputTokens: trip.usage?.inputTokens,
|
|
1366
1373
|
outputTokens: trip.usage?.outputTokens,
|
|
1367
1374
|
});
|
|
@@ -75,10 +75,10 @@ export declare function createCustomProviderRegistrationAction<TSchema extends Z
|
|
|
75
75
|
user: "user";
|
|
76
76
|
}>>;
|
|
77
77
|
}, z.core.$strip>>, {
|
|
78
|
+
id?: undefined;
|
|
78
79
|
provider?: undefined;
|
|
79
80
|
deleted?: undefined;
|
|
80
|
-
|
|
81
|
-
found?: undefined;
|
|
81
|
+
message?: undefined;
|
|
82
82
|
providers: {
|
|
83
83
|
id: string;
|
|
84
84
|
label: string;
|
|
@@ -89,45 +89,45 @@ export declare function createCustomProviderRegistrationAction<TSchema extends Z
|
|
|
89
89
|
updatedAt: number;
|
|
90
90
|
}[];
|
|
91
91
|
count: number;
|
|
92
|
+
found?: undefined;
|
|
92
93
|
registered?: undefined;
|
|
93
94
|
label?: undefined;
|
|
94
|
-
message?: undefined;
|
|
95
95
|
} | {
|
|
96
|
-
deleted?: undefined;
|
|
97
96
|
id?: undefined;
|
|
98
97
|
count?: undefined;
|
|
98
|
+
deleted?: undefined;
|
|
99
|
+
message?: undefined;
|
|
99
100
|
providers?: undefined;
|
|
100
101
|
found: boolean;
|
|
101
102
|
provider: import("../custom-registry.js").CustomProviderConfig;
|
|
102
103
|
registered?: undefined;
|
|
103
104
|
label?: undefined;
|
|
104
|
-
message?: undefined;
|
|
105
105
|
} | {
|
|
106
106
|
provider?: undefined;
|
|
107
|
-
deleted?: undefined;
|
|
108
107
|
count?: undefined;
|
|
108
|
+
deleted?: undefined;
|
|
109
|
+
message?: undefined;
|
|
109
110
|
providers?: undefined;
|
|
110
111
|
found: boolean;
|
|
111
112
|
id: string;
|
|
112
113
|
registered?: undefined;
|
|
113
114
|
label?: undefined;
|
|
114
|
-
message?: undefined;
|
|
115
115
|
} | {
|
|
116
116
|
provider?: undefined;
|
|
117
117
|
count?: undefined;
|
|
118
|
-
|
|
118
|
+
message?: undefined;
|
|
119
119
|
providers?: undefined;
|
|
120
|
+
found?: undefined;
|
|
120
121
|
deleted: boolean;
|
|
121
122
|
id: string;
|
|
122
123
|
registered?: undefined;
|
|
123
124
|
label?: undefined;
|
|
124
|
-
message?: undefined;
|
|
125
125
|
} | {
|
|
126
126
|
provider?: undefined;
|
|
127
|
-
deleted?: undefined;
|
|
128
127
|
count?: undefined;
|
|
129
|
-
|
|
128
|
+
deleted?: undefined;
|
|
130
129
|
providers?: undefined;
|
|
130
|
+
found?: undefined;
|
|
131
131
|
registered: boolean;
|
|
132
132
|
id: string;
|
|
133
133
|
label: string;
|