@ai-matrx/agents 0.28.1 → 0.29.1
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/CHANGELOG.md +16 -0
- package/dist/content-transfer/index.cjs +1 -0
- package/dist/content-transfer/index.cjs.map +1 -1
- package/dist/content-transfer/index.d.cts +2 -1
- package/dist/content-transfer/index.d.ts +2 -1
- package/dist/content-transfer/index.js +1 -0
- package/dist/content-transfer/index.js.map +1 -1
- package/dist/content-transfer/react/index.cjs +1 -0
- package/dist/content-transfer/react/index.cjs.map +1 -1
- package/dist/content-transfer/react/index.js +1 -0
- package/dist/content-transfer/react/index.js.map +1 -1
- package/dist/context/index.cjs +29 -5
- package/dist/context/index.cjs.map +1 -1
- package/dist/context/index.d.cts +65 -13
- package/dist/context/index.d.ts +65 -13
- package/dist/context/index.js +29 -5
- package/dist/context/index.js.map +1 -1
- package/dist/context/react/index.cjs +262 -78
- package/dist/context/react/index.cjs.map +1 -1
- package/dist/context/react/index.d.cts +100 -21
- package/dist/context/react/index.d.ts +100 -21
- package/dist/context/react/index.js +261 -77
- package/dist/context/react/index.js.map +1 -1
- package/dist/index.d.cts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/mandates/index.cjs +3 -2
- package/dist/mandates/index.cjs.map +1 -1
- package/dist/mandates/index.d.cts +6 -5
- package/dist/mandates/index.d.ts +6 -5
- package/dist/mandates/index.js +3 -2
- package/dist/mandates/index.js.map +1 -1
- package/dist/matrx/index.d.cts +4 -2
- package/dist/matrx/index.d.ts +4 -2
- package/dist/{operations-Dp4ut-ac.d.ts → operations-C3iHFcnt.d.ts} +2 -94
- package/dist/{operations--f5ko9Su.d.cts → operations-C9QvBH_2.d.cts} +2 -94
- package/dist/portable/index.cjs +164 -0
- package/dist/portable/index.cjs.map +1 -0
- package/dist/portable/index.d.cts +74 -0
- package/dist/portable/index.d.ts +74 -0
- package/dist/portable/index.js +141 -0
- package/dist/portable/index.js.map +1 -0
- package/dist/portable/mcp.cjs +512 -0
- package/dist/portable/mcp.cjs.map +1 -0
- package/dist/portable/mcp.d.cts +81 -0
- package/dist/portable/mcp.d.ts +81 -0
- package/dist/portable/mcp.js +492 -0
- package/dist/portable/mcp.js.map +1 -0
- package/dist/transport-CxD0fL8p.d.cts +94 -0
- package/dist/transport-CxD0fL8p.d.ts +94 -0
- package/dist/types.generated-BydVlqDC.d.cts +49 -0
- package/dist/types.generated-BydVlqDC.d.ts +49 -0
- package/mandates/snapshots/keys.0.29.0.json +652 -0
- package/mandates/snapshots/keys.0.29.1.json +653 -0
- package/package.json +22 -2
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { CredentialsPort } from '@ai-matrx/data';
|
|
2
|
+
import { M as MatrxTransport } from '../transport-CxD0fL8p.cjs';
|
|
3
|
+
import { J as JsonValue, P as PortableAgentBundle } from '../types.generated-BydVlqDC.cjs';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* `@ai-matrx/agents/portable/mcp` — an agent's server tools as an MCP server.
|
|
7
|
+
*
|
|
8
|
+
* `createAgentToolsMcpHandler` answers MCP JSON-RPC (`initialize`,
|
|
9
|
+
* `tools/list`, `tools/call`, `ping`) for ONE agent. `tools/list` is the
|
|
10
|
+
* bundle's server tools (resolved by the realtime-tools funnel on the server);
|
|
11
|
+
* `tools/call` runs the tool through `POST /ai/tools/execute`, which
|
|
12
|
+
* re-resolves the agent's set and refuses anything outside it, and writes the
|
|
13
|
+
* same `cx_tool_call` row a turn-based call writes.
|
|
14
|
+
*
|
|
15
|
+
* Every call reads the credential FRESH from the `CredentialsPort` and carries
|
|
16
|
+
* `X-Organization-Id` — nothing is cached, and the token never leaves this
|
|
17
|
+
* process (the CLI only ever sees a URL). Signed out, a call answers one plain
|
|
18
|
+
* sentence instead of failing obscurely.
|
|
19
|
+
*
|
|
20
|
+
* Transport-neutral: the host feeds it the parsed JSON body of each HTTP POST
|
|
21
|
+
* (MCP "streamable HTTP") and writes back what it returns. Pure: no Node, no
|
|
22
|
+
* React, no I/O at import.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/** The MCP protocol revisions this handler speaks, newest first. */
|
|
26
|
+
declare const AGENT_TOOLS_MCP_PROTOCOL_VERSIONS: readonly ["2025-06-18", "2025-03-26", "2024-11-05"];
|
|
27
|
+
/** The server name the CLIs see (`mcp__matrx-agent__<tool>` in Claude Code). */
|
|
28
|
+
declare const AGENT_TOOLS_MCP_SERVER_NAME = "matrx-agent";
|
|
29
|
+
/** What a signed-out tool call answers. */
|
|
30
|
+
declare const SIGNED_OUT_TOOL_MESSAGE = "Sign in to AI Matrx to use this tool.";
|
|
31
|
+
/** What a call with no organization answers. */
|
|
32
|
+
declare const NO_ORGANIZATION_TOOL_MESSAGE = "Pick an organization in AI Matrx to use this tool.";
|
|
33
|
+
interface AgentToolsMcpOptions {
|
|
34
|
+
/** The bundle the session loaded — its `tools`, agent/version identity and `surface`. */
|
|
35
|
+
bundle: Pick<PortableAgentBundle, "agent_id" | "version_id" | "name" | "tools" | "surface">;
|
|
36
|
+
/** The credential source, read fresh per call. `null`/absent = signed out. */
|
|
37
|
+
credentials?: CredentialsPort | null;
|
|
38
|
+
/** The organization every call runs in (value or per-call getter). */
|
|
39
|
+
organizationId?: string | null | (() => string | null | undefined);
|
|
40
|
+
/** AI Matrx server base URL, no trailing slash. */
|
|
41
|
+
baseUrl: string;
|
|
42
|
+
/**
|
|
43
|
+
* One conversation id for the whole coding session, so its tool calls group
|
|
44
|
+
* under one `cx_conversation`. Default: a fresh id per handler.
|
|
45
|
+
*/
|
|
46
|
+
conversationId?: string;
|
|
47
|
+
/** Test seam: replaces the transport built from the options above. */
|
|
48
|
+
transport?: MatrxTransport;
|
|
49
|
+
/** Every tool call's outcome, for the host's transcript or logs. */
|
|
50
|
+
onToolCall?: (event: {
|
|
51
|
+
name: string;
|
|
52
|
+
ok: boolean;
|
|
53
|
+
callId: string;
|
|
54
|
+
}) => void;
|
|
55
|
+
}
|
|
56
|
+
/** A JSON-RPC message, structurally. */
|
|
57
|
+
interface McpJsonRpcMessage {
|
|
58
|
+
jsonrpc?: string;
|
|
59
|
+
id?: string | number | null;
|
|
60
|
+
method?: string;
|
|
61
|
+
params?: unknown;
|
|
62
|
+
}
|
|
63
|
+
/** What the host writes back: HTTP status, plus a JSON body when there is one. */
|
|
64
|
+
interface McpHttpReply {
|
|
65
|
+
status: number;
|
|
66
|
+
body?: unknown;
|
|
67
|
+
}
|
|
68
|
+
interface AgentToolsMcpHandler {
|
|
69
|
+
/** Handle one parsed HTTP POST body (a message or a batch). */
|
|
70
|
+
handle(body: unknown): Promise<McpHttpReply>;
|
|
71
|
+
/** The tools `tools/list` returns, in MCP shape. */
|
|
72
|
+
listTools(): McpToolDescriptor[];
|
|
73
|
+
}
|
|
74
|
+
interface McpToolDescriptor {
|
|
75
|
+
name: string;
|
|
76
|
+
description: string;
|
|
77
|
+
inputSchema: Record<string, JsonValue>;
|
|
78
|
+
}
|
|
79
|
+
declare function createAgentToolsMcpHandler(options: AgentToolsMcpOptions): AgentToolsMcpHandler;
|
|
80
|
+
|
|
81
|
+
export { AGENT_TOOLS_MCP_PROTOCOL_VERSIONS, AGENT_TOOLS_MCP_SERVER_NAME, type AgentToolsMcpHandler, type AgentToolsMcpOptions, type McpHttpReply, type McpJsonRpcMessage, type McpToolDescriptor, NO_ORGANIZATION_TOOL_MESSAGE, SIGNED_OUT_TOOL_MESSAGE, createAgentToolsMcpHandler };
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { CredentialsPort } from '@ai-matrx/data';
|
|
2
|
+
import { M as MatrxTransport } from '../transport-CxD0fL8p.js';
|
|
3
|
+
import { J as JsonValue, P as PortableAgentBundle } from '../types.generated-BydVlqDC.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* `@ai-matrx/agents/portable/mcp` — an agent's server tools as an MCP server.
|
|
7
|
+
*
|
|
8
|
+
* `createAgentToolsMcpHandler` answers MCP JSON-RPC (`initialize`,
|
|
9
|
+
* `tools/list`, `tools/call`, `ping`) for ONE agent. `tools/list` is the
|
|
10
|
+
* bundle's server tools (resolved by the realtime-tools funnel on the server);
|
|
11
|
+
* `tools/call` runs the tool through `POST /ai/tools/execute`, which
|
|
12
|
+
* re-resolves the agent's set and refuses anything outside it, and writes the
|
|
13
|
+
* same `cx_tool_call` row a turn-based call writes.
|
|
14
|
+
*
|
|
15
|
+
* Every call reads the credential FRESH from the `CredentialsPort` and carries
|
|
16
|
+
* `X-Organization-Id` — nothing is cached, and the token never leaves this
|
|
17
|
+
* process (the CLI only ever sees a URL). Signed out, a call answers one plain
|
|
18
|
+
* sentence instead of failing obscurely.
|
|
19
|
+
*
|
|
20
|
+
* Transport-neutral: the host feeds it the parsed JSON body of each HTTP POST
|
|
21
|
+
* (MCP "streamable HTTP") and writes back what it returns. Pure: no Node, no
|
|
22
|
+
* React, no I/O at import.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/** The MCP protocol revisions this handler speaks, newest first. */
|
|
26
|
+
declare const AGENT_TOOLS_MCP_PROTOCOL_VERSIONS: readonly ["2025-06-18", "2025-03-26", "2024-11-05"];
|
|
27
|
+
/** The server name the CLIs see (`mcp__matrx-agent__<tool>` in Claude Code). */
|
|
28
|
+
declare const AGENT_TOOLS_MCP_SERVER_NAME = "matrx-agent";
|
|
29
|
+
/** What a signed-out tool call answers. */
|
|
30
|
+
declare const SIGNED_OUT_TOOL_MESSAGE = "Sign in to AI Matrx to use this tool.";
|
|
31
|
+
/** What a call with no organization answers. */
|
|
32
|
+
declare const NO_ORGANIZATION_TOOL_MESSAGE = "Pick an organization in AI Matrx to use this tool.";
|
|
33
|
+
interface AgentToolsMcpOptions {
|
|
34
|
+
/** The bundle the session loaded — its `tools`, agent/version identity and `surface`. */
|
|
35
|
+
bundle: Pick<PortableAgentBundle, "agent_id" | "version_id" | "name" | "tools" | "surface">;
|
|
36
|
+
/** The credential source, read fresh per call. `null`/absent = signed out. */
|
|
37
|
+
credentials?: CredentialsPort | null;
|
|
38
|
+
/** The organization every call runs in (value or per-call getter). */
|
|
39
|
+
organizationId?: string | null | (() => string | null | undefined);
|
|
40
|
+
/** AI Matrx server base URL, no trailing slash. */
|
|
41
|
+
baseUrl: string;
|
|
42
|
+
/**
|
|
43
|
+
* One conversation id for the whole coding session, so its tool calls group
|
|
44
|
+
* under one `cx_conversation`. Default: a fresh id per handler.
|
|
45
|
+
*/
|
|
46
|
+
conversationId?: string;
|
|
47
|
+
/** Test seam: replaces the transport built from the options above. */
|
|
48
|
+
transport?: MatrxTransport;
|
|
49
|
+
/** Every tool call's outcome, for the host's transcript or logs. */
|
|
50
|
+
onToolCall?: (event: {
|
|
51
|
+
name: string;
|
|
52
|
+
ok: boolean;
|
|
53
|
+
callId: string;
|
|
54
|
+
}) => void;
|
|
55
|
+
}
|
|
56
|
+
/** A JSON-RPC message, structurally. */
|
|
57
|
+
interface McpJsonRpcMessage {
|
|
58
|
+
jsonrpc?: string;
|
|
59
|
+
id?: string | number | null;
|
|
60
|
+
method?: string;
|
|
61
|
+
params?: unknown;
|
|
62
|
+
}
|
|
63
|
+
/** What the host writes back: HTTP status, plus a JSON body when there is one. */
|
|
64
|
+
interface McpHttpReply {
|
|
65
|
+
status: number;
|
|
66
|
+
body?: unknown;
|
|
67
|
+
}
|
|
68
|
+
interface AgentToolsMcpHandler {
|
|
69
|
+
/** Handle one parsed HTTP POST body (a message or a batch). */
|
|
70
|
+
handle(body: unknown): Promise<McpHttpReply>;
|
|
71
|
+
/** The tools `tools/list` returns, in MCP shape. */
|
|
72
|
+
listTools(): McpToolDescriptor[];
|
|
73
|
+
}
|
|
74
|
+
interface McpToolDescriptor {
|
|
75
|
+
name: string;
|
|
76
|
+
description: string;
|
|
77
|
+
inputSchema: Record<string, JsonValue>;
|
|
78
|
+
}
|
|
79
|
+
declare function createAgentToolsMcpHandler(options: AgentToolsMcpOptions): AgentToolsMcpHandler;
|
|
80
|
+
|
|
81
|
+
export { AGENT_TOOLS_MCP_PROTOCOL_VERSIONS, AGENT_TOOLS_MCP_SERVER_NAME, type AgentToolsMcpHandler, type AgentToolsMcpOptions, type McpHttpReply, type McpJsonRpcMessage, type McpToolDescriptor, NO_ORGANIZATION_TOOL_MESSAGE, SIGNED_OUT_TOOL_MESSAGE, createAgentToolsMcpHandler };
|
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
// matrx/client.ts
|
|
2
|
+
import { isNetError as isNetError2 } from "@ai-matrx/data";
|
|
3
|
+
import { credentialToHeaders } from "@ai-matrx/data";
|
|
4
|
+
|
|
5
|
+
// matrx/org-context.ts
|
|
6
|
+
import { isUuidShape } from "@ai-matrx/kit/uuid";
|
|
7
|
+
var OrganizationContextError = class extends Error {
|
|
8
|
+
code;
|
|
9
|
+
constructor(code, message) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.name = "OrganizationContextError";
|
|
12
|
+
this.code = code;
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
function requireOrganizationContext(selectedOrganizationId, overrideOrganizationId) {
|
|
16
|
+
const candidate = overrideOrganizationId ?? selectedOrganizationId;
|
|
17
|
+
if (typeof candidate !== "string" || candidate.trim().length === 0) {
|
|
18
|
+
throw new OrganizationContextError(
|
|
19
|
+
"organization_context_required",
|
|
20
|
+
"Select an organization before sending this request."
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
const normalized = candidate.trim();
|
|
24
|
+
if (!isUuidShape(normalized)) {
|
|
25
|
+
throw new OrganizationContextError(
|
|
26
|
+
"organization_context_invalid",
|
|
27
|
+
"The selected organization ID is invalid."
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
return normalized.toLowerCase();
|
|
31
|
+
}
|
|
32
|
+
function applyOrganizationContextHeader(headers, organizationId) {
|
|
33
|
+
const normalizedOrganizationId = requireOrganizationContext(organizationId);
|
|
34
|
+
for (const [name, value] of Object.entries(headers)) {
|
|
35
|
+
if (name.toLowerCase() === "x-organization-id" && value.trim().toLowerCase() !== normalizedOrganizationId) {
|
|
36
|
+
throw new OrganizationContextError(
|
|
37
|
+
"organization_context_mismatch",
|
|
38
|
+
"X-Organization-Id must match the request context organization."
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
const withoutOrganizationHeader = Object.fromEntries(
|
|
43
|
+
Object.entries(headers).filter(
|
|
44
|
+
([name]) => name.toLowerCase() !== "x-organization-id"
|
|
45
|
+
)
|
|
46
|
+
);
|
|
47
|
+
return {
|
|
48
|
+
...withoutOrganizationHeader,
|
|
49
|
+
"X-Organization-Id": normalizedOrganizationId
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// matrx/protocol.ts
|
|
54
|
+
import {
|
|
55
|
+
isNetError,
|
|
56
|
+
resilientFetch
|
|
57
|
+
} from "@ai-matrx/data/net";
|
|
58
|
+
var MATRX_AI_API_VERSION_DEFAULT = "v2";
|
|
59
|
+
function toV2Path(path) {
|
|
60
|
+
const withLead = path.startsWith("/") ? path : `/${path}`;
|
|
61
|
+
if (withLead.startsWith("/api/")) {
|
|
62
|
+
const rest = withLead.slice("/api".length);
|
|
63
|
+
return rest.startsWith("/v2/") ? withLead : `/api/v2${rest}`;
|
|
64
|
+
}
|
|
65
|
+
return withLead.startsWith("/v2/") ? withLead : `/v2${withLead}`;
|
|
66
|
+
}
|
|
67
|
+
var COVERED_INTERPOLATED_PATH = [
|
|
68
|
+
/^(?:\/api)?\/ai\/manual$/,
|
|
69
|
+
/^(?:\/api)?\/ai\/chat$/,
|
|
70
|
+
/^(?:\/api)?\/ai\/agents\/[^/]+$/,
|
|
71
|
+
/^(?:\/api)?\/ai\/agent\/[^/]+$/,
|
|
72
|
+
/^(?:\/api)?\/ai\/conversations\/[^/]+$/,
|
|
73
|
+
/^(?:\/api)?\/ai\/conversation\/[^/]+$/,
|
|
74
|
+
/^(?:\/api)?\/ai\/prompts\/[^/]+$/,
|
|
75
|
+
// `/warm` etc. deliberately don't match
|
|
76
|
+
/^(?:\/api)?\/ai\/mandates\/[^/]+$/
|
|
77
|
+
// the mandate door
|
|
78
|
+
];
|
|
79
|
+
function isCoveredAiPath(path) {
|
|
80
|
+
return COVERED_INTERPOLATED_PATH.some((re) => re.test(path));
|
|
81
|
+
}
|
|
82
|
+
function isV2Path(pathOrUrl) {
|
|
83
|
+
return /\/v2\/ai\//.test(pathOrUrl);
|
|
84
|
+
}
|
|
85
|
+
function toV1FallbackUrl(url) {
|
|
86
|
+
return url.replace(/\/v2\/ai\//, "/ai/");
|
|
87
|
+
}
|
|
88
|
+
function applyAiApiVersion(path, version) {
|
|
89
|
+
if (version !== "v2") return path;
|
|
90
|
+
return isCoveredAiPath(path) ? toV2Path(path) : path;
|
|
91
|
+
}
|
|
92
|
+
async function fetchWithMatrxProtocolFallback(url, init, opts = {}) {
|
|
93
|
+
const { onDowngrade, ...fetchOpts } = opts;
|
|
94
|
+
const logDowngrade = (reason, status2) => {
|
|
95
|
+
console.warn(
|
|
96
|
+
`[matrx] ai_v2_downgrade \u2192 retrying on v1. v2=${url} reason=${reason}`
|
|
97
|
+
);
|
|
98
|
+
onDowngrade?.({ url, reason, ...status2 !== void 0 ? { status: status2 } : {} });
|
|
99
|
+
};
|
|
100
|
+
if (!isV2Path(url)) return resilientFetch(url, init, fetchOpts);
|
|
101
|
+
let result;
|
|
102
|
+
try {
|
|
103
|
+
result = await resilientFetch(url, init, fetchOpts);
|
|
104
|
+
} catch (err) {
|
|
105
|
+
const isAbort = isNetError(err) && err.code === "aborted" || err instanceof Error && err.name === "AbortError";
|
|
106
|
+
if (isAbort) throw err;
|
|
107
|
+
logDowngrade(String(err));
|
|
108
|
+
return resilientFetch(toV1FallbackUrl(url), init, fetchOpts);
|
|
109
|
+
}
|
|
110
|
+
const status = result.response.status;
|
|
111
|
+
if (status === 404 || status === 405 || status >= 500) {
|
|
112
|
+
logDowngrade(`HTTP ${status}`, status);
|
|
113
|
+
return resilientFetch(toV1FallbackUrl(url), init, fetchOpts);
|
|
114
|
+
}
|
|
115
|
+
return result;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// matrx/transport.ts
|
|
119
|
+
var MatrxApiError = class extends Error {
|
|
120
|
+
name = "MatrxApiError";
|
|
121
|
+
/** HTTP status of the failed response. */
|
|
122
|
+
status;
|
|
123
|
+
/** Machine code from the server body (`code`, or `detail.code`), when present. */
|
|
124
|
+
code;
|
|
125
|
+
/** The parsed server error body, verbatim (undefined when unparsable). */
|
|
126
|
+
serverDetail;
|
|
127
|
+
/** The request path the failure came from (server-relative). */
|
|
128
|
+
path;
|
|
129
|
+
constructor(args) {
|
|
130
|
+
super(
|
|
131
|
+
args.message ?? extractMatrxErrorMessage(args.serverDetail) ?? `HTTP ${args.status}`
|
|
132
|
+
);
|
|
133
|
+
this.status = args.status;
|
|
134
|
+
this.path = args.path;
|
|
135
|
+
this.serverDetail = args.serverDetail;
|
|
136
|
+
this.code = extractMatrxErrorCode(args.serverDetail);
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
function isRecord(value) {
|
|
140
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
141
|
+
}
|
|
142
|
+
function nonBlankString(value) {
|
|
143
|
+
return typeof value === "string" && value.trim() ? value : void 0;
|
|
144
|
+
}
|
|
145
|
+
function extractMatrxErrorMessage(serverDetail) {
|
|
146
|
+
if (!isRecord(serverDetail)) return void 0;
|
|
147
|
+
const userMessage = nonBlankString(serverDetail.user_message);
|
|
148
|
+
if (userMessage) return userMessage;
|
|
149
|
+
const message = nonBlankString(serverDetail.message);
|
|
150
|
+
if (message) return message;
|
|
151
|
+
if (Array.isArray(serverDetail.details)) {
|
|
152
|
+
const messages = serverDetail.details.map((entry) => {
|
|
153
|
+
if (!isRecord(entry)) return void 0;
|
|
154
|
+
const detailMessage = nonBlankString(entry.message);
|
|
155
|
+
if (!detailMessage) return void 0;
|
|
156
|
+
const field = nonBlankString(entry.field);
|
|
157
|
+
return field ? `${field}: ${detailMessage}` : detailMessage;
|
|
158
|
+
}).filter((m) => typeof m === "string");
|
|
159
|
+
if (messages.length > 0) return messages.join("; ");
|
|
160
|
+
}
|
|
161
|
+
const detail = serverDetail.detail;
|
|
162
|
+
if (isRecord(detail)) {
|
|
163
|
+
const detailMessage = nonBlankString(detail.message) ?? nonBlankString(detail.user_message);
|
|
164
|
+
if (detailMessage) return detailMessage;
|
|
165
|
+
}
|
|
166
|
+
if (typeof detail === "string" && detail.trim()) return detail;
|
|
167
|
+
if (Array.isArray(detail)) {
|
|
168
|
+
const messages = detail.map(
|
|
169
|
+
(entry) => isRecord(entry) ? nonBlankString(entry.msg) : void 0
|
|
170
|
+
).filter((m) => typeof m === "string");
|
|
171
|
+
if (messages.length > 0) return messages.join("; ");
|
|
172
|
+
}
|
|
173
|
+
return void 0;
|
|
174
|
+
}
|
|
175
|
+
function extractMatrxErrorCode(serverDetail) {
|
|
176
|
+
if (!isRecord(serverDetail)) return null;
|
|
177
|
+
const topLevel = nonBlankString(serverDetail.code);
|
|
178
|
+
if (topLevel) return topLevel;
|
|
179
|
+
const detail = serverDetail.detail;
|
|
180
|
+
if (isRecord(detail)) {
|
|
181
|
+
const nested = nonBlankString(detail.code);
|
|
182
|
+
if (nested) return nested;
|
|
183
|
+
}
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// matrx/client.ts
|
|
188
|
+
function classifyStatus(status) {
|
|
189
|
+
return status >= 400 && status < 500 ? "validation_error" : "http_error";
|
|
190
|
+
}
|
|
191
|
+
function normalizeMatrxError(err) {
|
|
192
|
+
if (err instanceof MatrxApiError) {
|
|
193
|
+
return {
|
|
194
|
+
type: classifyStatus(err.status),
|
|
195
|
+
message: err.message,
|
|
196
|
+
status: err.status,
|
|
197
|
+
serverDetail: err.serverDetail,
|
|
198
|
+
...err.code ? { code: err.code } : {},
|
|
199
|
+
name: err.name
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
if (err instanceof OrganizationContextError) {
|
|
203
|
+
return {
|
|
204
|
+
type: "validation_error",
|
|
205
|
+
message: err.message,
|
|
206
|
+
code: err.code,
|
|
207
|
+
name: err.name,
|
|
208
|
+
...err.stack !== void 0 ? { stack: err.stack } : {}
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
if (err instanceof DOMException && err.name === "AbortError") {
|
|
212
|
+
return { type: "abort_error", message: "Request was cancelled." };
|
|
213
|
+
}
|
|
214
|
+
if (isNetError2(err)) {
|
|
215
|
+
if (err.code === "aborted") {
|
|
216
|
+
return { type: "abort_error", message: err.message };
|
|
217
|
+
}
|
|
218
|
+
if (err.code === "http") {
|
|
219
|
+
const status = err.status ?? 0;
|
|
220
|
+
return { type: classifyStatus(status), message: err.message, status };
|
|
221
|
+
}
|
|
222
|
+
return { type: "network_error", message: err.message };
|
|
223
|
+
}
|
|
224
|
+
if (err instanceof Error) {
|
|
225
|
+
const httpMatch = err.message.match(/HTTP (\d+):\s*(.*)/);
|
|
226
|
+
if (httpMatch) {
|
|
227
|
+
const status = parseInt(httpMatch[1], 10);
|
|
228
|
+
return {
|
|
229
|
+
type: classifyStatus(status),
|
|
230
|
+
message: httpMatch[2] ?? err.message,
|
|
231
|
+
status
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
return { type: "network_error", message: err.message };
|
|
235
|
+
}
|
|
236
|
+
return { type: "unknown", message: String(err ?? "Unknown error") };
|
|
237
|
+
}
|
|
238
|
+
var PRODUCTION_CONNECT_TIMEOUT_MS = 15e3;
|
|
239
|
+
function stripContentType(headers) {
|
|
240
|
+
return Object.fromEntries(
|
|
241
|
+
Object.entries(headers).filter(
|
|
242
|
+
([name]) => name.toLowerCase() !== "content-type"
|
|
243
|
+
)
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
function createMatrxTransport(options) {
|
|
247
|
+
const {
|
|
248
|
+
baseUrl,
|
|
249
|
+
resolveTarget,
|
|
250
|
+
credentials,
|
|
251
|
+
organizationId,
|
|
252
|
+
aiApiVersion,
|
|
253
|
+
connectTimeoutMs = PRODUCTION_CONNECT_TIMEOUT_MS,
|
|
254
|
+
totalTimeoutMs = null,
|
|
255
|
+
expectedErrorStatuses,
|
|
256
|
+
diagnostics,
|
|
257
|
+
source = "matrxTransport"
|
|
258
|
+
} = options;
|
|
259
|
+
if (baseUrl === void 0 === (resolveTarget === void 0)) {
|
|
260
|
+
throw new Error(
|
|
261
|
+
"createMatrxTransport: provide exactly one of `baseUrl` or `resolveTarget`."
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
const resolve = resolveTarget ?? (() => ({ baseUrl }));
|
|
265
|
+
const resolveVersion = () => {
|
|
266
|
+
if (aiApiVersion === void 0) return MATRX_AI_API_VERSION_DEFAULT;
|
|
267
|
+
return typeof aiApiVersion === "function" ? aiApiVersion() : aiApiVersion;
|
|
268
|
+
};
|
|
269
|
+
const resolveOrganizationId = () => {
|
|
270
|
+
if (organizationId === void 0) return null;
|
|
271
|
+
const candidate = typeof organizationId === "function" ? organizationId() : organizationId;
|
|
272
|
+
return requireOrganizationContext(candidate);
|
|
273
|
+
};
|
|
274
|
+
return {
|
|
275
|
+
async fetch(path, init) {
|
|
276
|
+
const target = resolve();
|
|
277
|
+
const versionedPath = applyAiApiVersion(path, resolveVersion());
|
|
278
|
+
const url = `${target.baseUrl}${versionedPath}`;
|
|
279
|
+
const credentialHeaders = credentials ? credentialToHeaders(await credentials.get()) : {};
|
|
280
|
+
let headers = {
|
|
281
|
+
...init.headers,
|
|
282
|
+
...stripContentType(credentialHeaders),
|
|
283
|
+
...stripContentType(target.policyHeaders ?? {})
|
|
284
|
+
};
|
|
285
|
+
const orgId = resolveOrganizationId();
|
|
286
|
+
if (orgId !== null) {
|
|
287
|
+
headers = applyOrganizationContextHeader(headers, orgId);
|
|
288
|
+
}
|
|
289
|
+
const info = {
|
|
290
|
+
url,
|
|
291
|
+
method: init.method,
|
|
292
|
+
path,
|
|
293
|
+
channel: target.channel ?? "default",
|
|
294
|
+
source
|
|
295
|
+
};
|
|
296
|
+
diagnostics?.onRequest?.(info);
|
|
297
|
+
let response;
|
|
298
|
+
try {
|
|
299
|
+
({ response } = await fetchWithMatrxProtocolFallback(
|
|
300
|
+
url,
|
|
301
|
+
{
|
|
302
|
+
method: init.method,
|
|
303
|
+
headers,
|
|
304
|
+
...init.body !== void 0 ? { body: init.body } : {}
|
|
305
|
+
},
|
|
306
|
+
{
|
|
307
|
+
...init.signal ? { signal: init.signal } : {},
|
|
308
|
+
connectTimeoutMs,
|
|
309
|
+
totalTimeoutMs,
|
|
310
|
+
throwOnHttpError: false,
|
|
311
|
+
...diagnostics?.onProtocolDowngrade ? { onDowngrade: diagnostics.onProtocolDowngrade } : {}
|
|
312
|
+
}
|
|
313
|
+
));
|
|
314
|
+
} catch (err) {
|
|
315
|
+
diagnostics?.onError?.(normalizeMatrxError(err), info);
|
|
316
|
+
throw err;
|
|
317
|
+
}
|
|
318
|
+
if (!response.ok && !expectedErrorStatuses?.includes(response.status)) {
|
|
319
|
+
const serverDetail = await response.clone().json().catch(() => void 0);
|
|
320
|
+
const code = extractMatrxErrorCode(serverDetail);
|
|
321
|
+
diagnostics?.onError?.(
|
|
322
|
+
{
|
|
323
|
+
type: classifyStatus(response.status),
|
|
324
|
+
message: extractMatrxErrorMessage(serverDetail) ?? `HTTP ${response.status}`,
|
|
325
|
+
status: response.status,
|
|
326
|
+
serverDetail,
|
|
327
|
+
...code ? { code } : {}
|
|
328
|
+
},
|
|
329
|
+
info
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
return response;
|
|
333
|
+
}
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// matrx/internal.ts
|
|
338
|
+
async function readServerDetail(response) {
|
|
339
|
+
try {
|
|
340
|
+
return await response.json();
|
|
341
|
+
} catch {
|
|
342
|
+
return void 0;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
async function throwApiError(path, response) {
|
|
346
|
+
throw new MatrxApiError({
|
|
347
|
+
status: response.status,
|
|
348
|
+
path,
|
|
349
|
+
serverDetail: await readServerDetail(response)
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
async function requestJson(transport, path, options) {
|
|
353
|
+
const hasBody = options.method !== "GET" && options.body !== void 0;
|
|
354
|
+
const response = await transport.fetch(path, {
|
|
355
|
+
method: options.method,
|
|
356
|
+
headers: hasBody ? { "Content-Type": "application/json" } : {},
|
|
357
|
+
...hasBody ? { body: JSON.stringify(options.body) } : {},
|
|
358
|
+
...options.signal ? { signal: options.signal } : {}
|
|
359
|
+
});
|
|
360
|
+
if (!response.ok) return throwApiError(path, response);
|
|
361
|
+
return await response.json();
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// portable/mcp.ts
|
|
365
|
+
var AGENT_TOOLS_MCP_PROTOCOL_VERSIONS = ["2025-06-18", "2025-03-26", "2024-11-05"];
|
|
366
|
+
var AGENT_TOOLS_MCP_SERVER_NAME = "matrx-agent";
|
|
367
|
+
var SIGNED_OUT_TOOL_MESSAGE = "Sign in to AI Matrx to use this tool.";
|
|
368
|
+
var NO_ORGANIZATION_TOOL_MESSAGE = "Pick an organization in AI Matrx to use this tool.";
|
|
369
|
+
function isRecord2(value) {
|
|
370
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
371
|
+
}
|
|
372
|
+
function newId() {
|
|
373
|
+
return globalThis.crypto.randomUUID();
|
|
374
|
+
}
|
|
375
|
+
function toDescriptor(tool) {
|
|
376
|
+
const schema = isRecord2(tool.parameters) && tool.parameters.type === "object" ? tool.parameters : { type: "object", properties: {} };
|
|
377
|
+
return { name: tool.name, description: tool.description || tool.canonical_name, inputSchema: schema };
|
|
378
|
+
}
|
|
379
|
+
function rpcResult(id, result) {
|
|
380
|
+
return { jsonrpc: "2.0", id: id ?? null, result };
|
|
381
|
+
}
|
|
382
|
+
function rpcError(id, code, message) {
|
|
383
|
+
return { jsonrpc: "2.0", id: id ?? null, error: { code, message } };
|
|
384
|
+
}
|
|
385
|
+
function textResult(text, isError) {
|
|
386
|
+
return { content: [{ type: "text", text }], isError };
|
|
387
|
+
}
|
|
388
|
+
function createAgentToolsMcpHandler(options) {
|
|
389
|
+
const { bundle } = options;
|
|
390
|
+
const conversationId = options.conversationId ?? newId();
|
|
391
|
+
const descriptors = bundle.tools.map(toDescriptor);
|
|
392
|
+
const known = new Set(bundle.tools.map((tool) => tool.name));
|
|
393
|
+
const resolveOrganization = () => {
|
|
394
|
+
const raw = typeof options.organizationId === "function" ? options.organizationId() : options.organizationId;
|
|
395
|
+
return raw && raw.trim() ? raw.trim() : null;
|
|
396
|
+
};
|
|
397
|
+
const transportFor = (organizationId) => options.transport ?? createMatrxTransport({
|
|
398
|
+
baseUrl: options.baseUrl,
|
|
399
|
+
...options.credentials ? { credentials: options.credentials } : {},
|
|
400
|
+
organizationId,
|
|
401
|
+
source: "agentToolsMcp"
|
|
402
|
+
});
|
|
403
|
+
async function callTool(id, params) {
|
|
404
|
+
if (!isRecord2(params) || typeof params.name !== "string") {
|
|
405
|
+
return rpcError(id, -32602, "tools/call needs a tool name.");
|
|
406
|
+
}
|
|
407
|
+
const name = params.name;
|
|
408
|
+
if (!known.has(name)) return rpcError(id, -32602, `Unknown tool: ${name}`);
|
|
409
|
+
const args = isRecord2(params.arguments) ? params.arguments : {};
|
|
410
|
+
const credential = options.credentials ? await options.credentials.get() : null;
|
|
411
|
+
if (!credential) return rpcResult(id, textResult(SIGNED_OUT_TOOL_MESSAGE, true));
|
|
412
|
+
const organizationId = resolveOrganization();
|
|
413
|
+
if (!organizationId) return rpcResult(id, textResult(NO_ORGANIZATION_TOOL_MESSAGE, true));
|
|
414
|
+
const callId = newId();
|
|
415
|
+
const pinned = typeof bundle.version_id === "string" && bundle.version_id.length > 0;
|
|
416
|
+
try {
|
|
417
|
+
const response = await requestJson(
|
|
418
|
+
transportFor(organizationId),
|
|
419
|
+
"/ai/tools/execute",
|
|
420
|
+
{
|
|
421
|
+
method: "POST",
|
|
422
|
+
body: {
|
|
423
|
+
agent_id: pinned ? bundle.version_id : bundle.agent_id,
|
|
424
|
+
is_version: pinned,
|
|
425
|
+
conversation_id: conversationId,
|
|
426
|
+
tool_name: name,
|
|
427
|
+
arguments: args,
|
|
428
|
+
call_id: callId,
|
|
429
|
+
surface: bundle.surface
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
);
|
|
433
|
+
options.onToolCall?.({ name, ok: response.ok, callId });
|
|
434
|
+
return rpcResult(id, textResult(response.output, !response.ok));
|
|
435
|
+
} catch (error) {
|
|
436
|
+
options.onToolCall?.({ name, ok: false, callId });
|
|
437
|
+
if (error instanceof MatrxApiError && error.status === 401) {
|
|
438
|
+
return rpcResult(id, textResult(SIGNED_OUT_TOOL_MESSAGE, true));
|
|
439
|
+
}
|
|
440
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
441
|
+
return rpcResult(id, textResult(`AI Matrx could not run ${name}: ${message}`, true));
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
async function handleOne(message) {
|
|
445
|
+
if (!isRecord2(message) || typeof message.method !== "string") {
|
|
446
|
+
return rpcError(null, -32600, "Invalid request.");
|
|
447
|
+
}
|
|
448
|
+
const { id, params } = message;
|
|
449
|
+
const method = message.method;
|
|
450
|
+
const isNotification = id === void 0;
|
|
451
|
+
switch (method) {
|
|
452
|
+
case "initialize": {
|
|
453
|
+
const requested = isRecord2(params) ? params.protocolVersion : void 0;
|
|
454
|
+
const protocolVersion = typeof requested === "string" && AGENT_TOOLS_MCP_PROTOCOL_VERSIONS.includes(requested) ? requested : AGENT_TOOLS_MCP_PROTOCOL_VERSIONS[0];
|
|
455
|
+
return rpcResult(id, {
|
|
456
|
+
protocolVersion,
|
|
457
|
+
capabilities: { tools: { listChanged: false } },
|
|
458
|
+
serverInfo: { name: AGENT_TOOLS_MCP_SERVER_NAME, version: "1" },
|
|
459
|
+
instructions: `Tools of the AI Matrx agent "${bundle.name}". They run on AI Matrx as the signed-in person.`
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
case "ping":
|
|
463
|
+
return isNotification ? null : rpcResult(id, {});
|
|
464
|
+
case "tools/list":
|
|
465
|
+
return rpcResult(id, { tools: descriptors });
|
|
466
|
+
case "tools/call":
|
|
467
|
+
return callTool(id, params);
|
|
468
|
+
default:
|
|
469
|
+
if (isNotification) return null;
|
|
470
|
+
return rpcError(id, -32601, `Method not found: ${method}`);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
return {
|
|
474
|
+
listTools: () => descriptors,
|
|
475
|
+
async handle(body) {
|
|
476
|
+
if (Array.isArray(body)) {
|
|
477
|
+
const replies = (await Promise.all(body.map(handleOne))).filter((r) => r !== null);
|
|
478
|
+
return replies.length ? { status: 200, body: replies } : { status: 202 };
|
|
479
|
+
}
|
|
480
|
+
const reply = await handleOne(body);
|
|
481
|
+
return reply === null ? { status: 202 } : { status: 200, body: reply };
|
|
482
|
+
}
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
export {
|
|
486
|
+
AGENT_TOOLS_MCP_PROTOCOL_VERSIONS,
|
|
487
|
+
AGENT_TOOLS_MCP_SERVER_NAME,
|
|
488
|
+
NO_ORGANIZATION_TOOL_MESSAGE,
|
|
489
|
+
SIGNED_OUT_TOOL_MESSAGE,
|
|
490
|
+
createAgentToolsMcpHandler
|
|
491
|
+
};
|
|
492
|
+
//# sourceMappingURL=mcp.js.map
|