@neuraltrust/trustgate 0.1.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/LICENSE +201 -0
- package/README.md +132 -0
- package/dist/agent.d.ts +156 -0
- package/dist/agent.d.ts.map +1 -0
- package/dist/agent.js +216 -0
- package/dist/agent.js.map +1 -0
- package/dist/client.d.ts +88 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +152 -0
- package/dist/client.js.map +1 -0
- package/dist/config.d.ts +44 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +40 -0
- package/dist/config.js.map +1 -0
- package/dist/connections.d.ts +13 -0
- package/dist/connections.d.ts.map +1 -0
- package/dist/connections.js +46 -0
- package/dist/connections.js.map +1 -0
- package/dist/errors.d.ts +91 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +144 -0
- package/dist/errors.js.map +1 -0
- package/dist/formats.d.ts +44 -0
- package/dist/formats.d.ts.map +1 -0
- package/dist/formats.js +299 -0
- package/dist/formats.js.map +1 -0
- package/dist/http.d.ts +23 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +94 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp.d.ts +43 -0
- package/dist/mcp.d.ts.map +1 -0
- package/dist/mcp.js +175 -0
- package/dist/mcp.js.map +1 -0
- package/dist/schema.d.ts +52 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +168 -0
- package/dist/schema.js.map +1 -0
- package/dist/types.d.ts +78 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +52 -0
- package/dist/types.js.map +1 -0
- package/dist/whoami.d.ts +73 -0
- package/dist/whoami.d.ts.map +1 -0
- package/dist/whoami.js +79 -0
- package/dist/whoami.js.map +1 -0
- package/package.json +29 -0
- package/src/agent.ts +267 -0
- package/src/client.ts +203 -0
- package/src/config.ts +78 -0
- package/src/connections.ts +90 -0
- package/src/errors.ts +154 -0
- package/src/formats.ts +362 -0
- package/src/http.ts +106 -0
- package/src/index.ts +41 -0
- package/src/mcp.ts +203 -0
- package/src/schema.ts +193 -0
- package/src/types.ts +98 -0
- package/src/whoami.ts +172 -0
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/** Which actor a handle speaks as. The consumer decides this, not the caller. */
|
|
2
|
+
export declare const Actor: {
|
|
3
|
+
/** The application itself — principal `app:<consumer_id>`. */
|
|
4
|
+
readonly Application: "application";
|
|
5
|
+
/** One of the application's own end users. */
|
|
6
|
+
readonly EndUser: "end_user";
|
|
7
|
+
};
|
|
8
|
+
export type Actor = (typeof Actor)[keyof typeof Actor];
|
|
9
|
+
/**
|
|
10
|
+
* The tool shapes the SDK can emit.
|
|
11
|
+
*
|
|
12
|
+
* These are model providers, not agent frameworks. A framework brings its own
|
|
13
|
+
* MCP client, so it takes the gateway's URL and lists the tools itself — there
|
|
14
|
+
* is nothing to convert. Conversion only happens when you call a provider's
|
|
15
|
+
* API directly, which is why nothing here is called "langchain".
|
|
16
|
+
*/
|
|
17
|
+
export declare const ToolFormat: {
|
|
18
|
+
/** OpenAI Responses API — function tools, flattened. */
|
|
19
|
+
readonly OpenAIResponses: "openai-responses";
|
|
20
|
+
/** OpenAI Chat Completions — function tools, nested under `function`. */
|
|
21
|
+
readonly OpenAIChat: "openai-chat";
|
|
22
|
+
/** Anthropic Messages — `input_schema`. */
|
|
23
|
+
readonly AnthropicMessages: "anthropic-messages";
|
|
24
|
+
/** Google Gemini — a single `functionDeclarations` entry. */
|
|
25
|
+
readonly Gemini: "gemini";
|
|
26
|
+
};
|
|
27
|
+
export type ToolFormat = (typeof ToolFormat)[keyof typeof ToolFormat];
|
|
28
|
+
/** A tool as the gateway serves it, before any provider dialect is applied. */
|
|
29
|
+
export type GatewayTool = {
|
|
30
|
+
name: string;
|
|
31
|
+
title?: string;
|
|
32
|
+
description?: string;
|
|
33
|
+
inputSchema: JSONSchema;
|
|
34
|
+
outputSchema?: JSONSchema;
|
|
35
|
+
};
|
|
36
|
+
export type JSONSchema = Record<string, unknown>;
|
|
37
|
+
/** One upstream account of an actor, as the connections API reports it. */
|
|
38
|
+
export type Connection = {
|
|
39
|
+
provider: string;
|
|
40
|
+
registry?: string;
|
|
41
|
+
code?: string;
|
|
42
|
+
status: 'connected' | 'needs_reconnect' | 'not_connected';
|
|
43
|
+
accountRef?: string;
|
|
44
|
+
expiresAt?: Date;
|
|
45
|
+
};
|
|
46
|
+
/** The link an end user opens to connect their own account. */
|
|
47
|
+
export type ConnectLink = {
|
|
48
|
+
connectUrl: string;
|
|
49
|
+
ticket: string;
|
|
50
|
+
provider?: string;
|
|
51
|
+
expiresAt: Date;
|
|
52
|
+
};
|
|
53
|
+
/** Everything a provider's client needs to reach the gateway. */
|
|
54
|
+
export type Endpoint = {
|
|
55
|
+
url: string;
|
|
56
|
+
headers: Record<string, string>;
|
|
57
|
+
};
|
|
58
|
+
/** A tool call as the SDK understands it, whatever provider asked for it. */
|
|
59
|
+
export type ToolCall = {
|
|
60
|
+
/** The provider's own identifier for this call, echoed back in the result. */
|
|
61
|
+
id: string;
|
|
62
|
+
name: string;
|
|
63
|
+
arguments: Record<string, unknown>;
|
|
64
|
+
};
|
|
65
|
+
/**
|
|
66
|
+
* The exposed name of a tool the caller named.
|
|
67
|
+
*
|
|
68
|
+
* The gateway prefixes every tool with the server it came from, so Linear's
|
|
69
|
+
* `list_issues` is served as `linear_list_issues`. That prefix is the gateway's
|
|
70
|
+
* doing and not the caller's, so a name written without one resolves — as long
|
|
71
|
+
* as exactly one server serves it. Two that do is a genuine question only the
|
|
72
|
+
* caller can answer, and it is asked rather than guessed.
|
|
73
|
+
*
|
|
74
|
+
* A name that matches nothing is returned unchanged, so the error that follows
|
|
75
|
+
* is about the tool rather than about this.
|
|
76
|
+
*/
|
|
77
|
+
export declare function resolveToolName(name: string, tools: GatewayTool[]): string;
|
|
78
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AACA,iFAAiF;AACjF,eAAO,MAAM,KAAK;IACjB,8DAA8D;;IAE9D,8CAA8C;;CAErC,CAAA;AACV,MAAM,MAAM,KAAK,GAAG,CAAC,OAAO,KAAK,CAAC,CAAC,MAAM,OAAO,KAAK,CAAC,CAAA;AAEtD;;;;;;;GAOG;AACH,eAAO,MAAM,UAAU;IACtB,wDAAwD;;IAExD,yEAAyE;;IAEzE,2CAA2C;;IAE3C,6DAA6D;;CAEpD,CAAA;AACV,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,UAAU,CAAC,CAAC,MAAM,OAAO,UAAU,CAAC,CAAA;AAErE,+EAA+E;AAC/E,MAAM,MAAM,WAAW,GAAG;IACzB,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,WAAW,EAAE,UAAU,CAAA;IACvB,YAAY,CAAC,EAAE,UAAU,CAAA;CACzB,CAAA;AAED,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;AAEhD,2EAA2E;AAC3E,MAAM,MAAM,UAAU,GAAG;IACxB,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,WAAW,GAAG,iBAAiB,GAAG,eAAe,CAAA;IACzD,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,SAAS,CAAC,EAAE,IAAI,CAAA;CAChB,CAAA;AAED,+DAA+D;AAC/D,MAAM,MAAM,WAAW,GAAG;IACzB,UAAU,EAAE,MAAM,CAAA;IAClB,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,IAAI,CAAA;CACf,CAAA;AAED,iEAAiE;AACjE,MAAM,MAAM,QAAQ,GAAG;IACtB,GAAG,EAAE,MAAM,CAAA;IACX,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAC/B,CAAA;AAED,6EAA6E;AAC7E,MAAM,MAAM,QAAQ,GAAG;IACtB,8EAA8E;IAC9E,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAClC,CAAA;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,GAAG,MAAM,CAY1E"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { TrustGateError } from './errors.js';
|
|
2
|
+
/** Which actor a handle speaks as. The consumer decides this, not the caller. */
|
|
3
|
+
export const Actor = {
|
|
4
|
+
/** The application itself — principal `app:<consumer_id>`. */
|
|
5
|
+
Application: 'application',
|
|
6
|
+
/** One of the application's own end users. */
|
|
7
|
+
EndUser: 'end_user',
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* The tool shapes the SDK can emit.
|
|
11
|
+
*
|
|
12
|
+
* These are model providers, not agent frameworks. A framework brings its own
|
|
13
|
+
* MCP client, so it takes the gateway's URL and lists the tools itself — there
|
|
14
|
+
* is nothing to convert. Conversion only happens when you call a provider's
|
|
15
|
+
* API directly, which is why nothing here is called "langchain".
|
|
16
|
+
*/
|
|
17
|
+
export const ToolFormat = {
|
|
18
|
+
/** OpenAI Responses API — function tools, flattened. */
|
|
19
|
+
OpenAIResponses: 'openai-responses',
|
|
20
|
+
/** OpenAI Chat Completions — function tools, nested under `function`. */
|
|
21
|
+
OpenAIChat: 'openai-chat',
|
|
22
|
+
/** Anthropic Messages — `input_schema`. */
|
|
23
|
+
AnthropicMessages: 'anthropic-messages',
|
|
24
|
+
/** Google Gemini — a single `functionDeclarations` entry. */
|
|
25
|
+
Gemini: 'gemini',
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* The exposed name of a tool the caller named.
|
|
29
|
+
*
|
|
30
|
+
* The gateway prefixes every tool with the server it came from, so Linear's
|
|
31
|
+
* `list_issues` is served as `linear_list_issues`. That prefix is the gateway's
|
|
32
|
+
* doing and not the caller's, so a name written without one resolves — as long
|
|
33
|
+
* as exactly one server serves it. Two that do is a genuine question only the
|
|
34
|
+
* caller can answer, and it is asked rather than guessed.
|
|
35
|
+
*
|
|
36
|
+
* A name that matches nothing is returned unchanged, so the error that follows
|
|
37
|
+
* is about the tool rather than about this.
|
|
38
|
+
*/
|
|
39
|
+
export function resolveToolName(name, tools) {
|
|
40
|
+
const names = tools.map((tool) => tool.name);
|
|
41
|
+
if (names.includes(name))
|
|
42
|
+
return name;
|
|
43
|
+
const matches = names.filter((candidate) => candidate.endsWith(`_${name}`));
|
|
44
|
+
if (matches.length === 1)
|
|
45
|
+
return matches[0];
|
|
46
|
+
if (matches.length > 1) {
|
|
47
|
+
throw new TrustGateError(`"${name}" is served by more than one of this application's servers ` +
|
|
48
|
+
`(${[...matches].sort().join(', ')}). Name the one you mean.`);
|
|
49
|
+
}
|
|
50
|
+
return name;
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAC5C,iFAAiF;AACjF,MAAM,CAAC,MAAM,KAAK,GAAG;IACpB,8DAA8D;IAC9D,WAAW,EAAE,aAAa;IAC1B,8CAA8C;IAC9C,OAAO,EAAE,UAAU;CACV,CAAA;AAGV;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG;IACzB,wDAAwD;IACxD,eAAe,EAAE,kBAAkB;IACnC,yEAAyE;IACzE,UAAU,EAAE,aAAa;IACzB,2CAA2C;IAC3C,iBAAiB,EAAE,oBAAoB;IACvC,6DAA6D;IAC7D,MAAM,EAAE,QAAQ;CACP,CAAA;AA8CV;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,eAAe,CAAC,IAAY,EAAE,KAAoB;IACjE,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAC5C,IAAI,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAA;IACrC,MAAM,OAAO,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,CAAA;IAC3E,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,OAAO,CAAC,CAAC,CAAC,CAAA;IAC3C,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxB,MAAM,IAAI,cAAc,CACvB,IAAI,IAAI,6DAA6D;YACpE,IAAI,CAAC,GAAG,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,2BAA2B,CAC9D,CAAA;IACF,CAAC;IACD,OAAO,IAAI,CAAA;AACZ,CAAC"}
|
package/dist/whoami.d.ts
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { ResolvedConfig } from './config.js';
|
|
2
|
+
/** Whose account an MCP server reads: the one its instance holds, or one per caller. */
|
|
3
|
+
export type UpstreamAccount = 'shared' | 'user';
|
|
4
|
+
/**
|
|
5
|
+
* Who has to act before a server answers a call that runs as the application.
|
|
6
|
+
*
|
|
7
|
+
* `administrator` is an instance whose shared account nobody has connected —
|
|
8
|
+
* no caller can connect it, because it is the account every other caller rides
|
|
9
|
+
* on. `end_user` is an instance that keeps an account per caller, which an
|
|
10
|
+
* application is not: it names the person it acts for, and the account becomes
|
|
11
|
+
* theirs to connect.
|
|
12
|
+
*/
|
|
13
|
+
export type UpstreamBlockedBy = 'administrator' | 'end_user';
|
|
14
|
+
/** One MCP server the application is bound to, and what it is waiting for. */
|
|
15
|
+
export type KeyUpstream = {
|
|
16
|
+
server: string;
|
|
17
|
+
provider?: string;
|
|
18
|
+
account: UpstreamAccount;
|
|
19
|
+
connected: boolean;
|
|
20
|
+
needsReconnect: boolean;
|
|
21
|
+
blocked?: UpstreamBlockedBy;
|
|
22
|
+
};
|
|
23
|
+
/** One consumer the key reaches, with the address it is served on. */
|
|
24
|
+
export type KeyConsumer = {
|
|
25
|
+
slug: string;
|
|
26
|
+
name?: string;
|
|
27
|
+
/** The plane this consumer belongs to: MCP, LLM or A2A. */
|
|
28
|
+
type: string;
|
|
29
|
+
active: boolean;
|
|
30
|
+
/** Where it answers. Empty when the gateway publishes no host for its plane. */
|
|
31
|
+
url: string;
|
|
32
|
+
/**
|
|
33
|
+
* The servers behind it that read a stored account, answered for this key —
|
|
34
|
+
* which is the application itself.
|
|
35
|
+
*
|
|
36
|
+
* `undefined` is not "nothing to connect": it is also what a gateway that
|
|
37
|
+
* could not read the accounts answers, and a server carrying its own
|
|
38
|
+
* credential is never listed. Read `blocked`, never a length.
|
|
39
|
+
*/
|
|
40
|
+
upstreams?: KeyUpstream[];
|
|
41
|
+
};
|
|
42
|
+
/** The calling key itself. The secret is never echoed. */
|
|
43
|
+
export type KeyInfo = {
|
|
44
|
+
name?: string;
|
|
45
|
+
/** When it retires itself. `undefined` means never. */
|
|
46
|
+
expiresAt?: Date;
|
|
47
|
+
};
|
|
48
|
+
/** Everything the key can say about itself. */
|
|
49
|
+
export type KeyIdentity = {
|
|
50
|
+
gateway: string;
|
|
51
|
+
key: KeyInfo;
|
|
52
|
+
consumers: KeyConsumer[];
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Asks the key what it reaches, when it dies, and what is not connected yet.
|
|
56
|
+
*
|
|
57
|
+
* It is what lets a client be configured with one secret: the slugs were
|
|
58
|
+
* chosen by whoever created the consumers, and the LLM plane is on a host the
|
|
59
|
+
* MCP URL says nothing about, so both have to come from the gateway. The other
|
|
60
|
+
* two answers are the failures a client would otherwise meet at runtime — an
|
|
61
|
+
* expired key as a 401 mid-run, an unconnected server as a refusal on the
|
|
62
|
+
* first tool call — moved to where something can still be done about them.
|
|
63
|
+
*/
|
|
64
|
+
export declare function whoAmI(config: ResolvedConfig, signal?: AbortSignal): Promise<KeyIdentity>;
|
|
65
|
+
/**
|
|
66
|
+
* Picks the one consumer of a plane, or explains why it cannot.
|
|
67
|
+
*
|
|
68
|
+
* A key attached to two consumers of the same type is a legitimate setup that
|
|
69
|
+
* this SDK cannot resolve on its own, so it names them and asks — rather than
|
|
70
|
+
* guessing and running an agent against the wrong surface.
|
|
71
|
+
*/
|
|
72
|
+
export declare function selectConsumer(identity: KeyIdentity, plane: 'MCP' | 'LLM', configured: string | undefined, envName: string): KeyConsumer;
|
|
73
|
+
//# sourceMappingURL=whoami.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"whoami.d.ts","sourceRoot":"","sources":["../src/whoami.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAIjD,wFAAwF;AACxF,MAAM,MAAM,eAAe,GAAG,QAAQ,GAAG,MAAM,CAAA;AAE/C;;;;;;;;GAQG;AACH,MAAM,MAAM,iBAAiB,GAAG,eAAe,GAAG,UAAU,CAAA;AAE5D,8EAA8E;AAC9E,MAAM,MAAM,WAAW,GAAG;IACzB,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,eAAe,CAAA;IACxB,SAAS,EAAE,OAAO,CAAA;IAClB,cAAc,EAAE,OAAO,CAAA;IACvB,OAAO,CAAC,EAAE,iBAAiB,CAAA;CAC3B,CAAA;AAED,sEAAsE;AACtE,MAAM,MAAM,WAAW,GAAG;IACzB,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,2DAA2D;IAC3D,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,OAAO,CAAA;IACf,gFAAgF;IAChF,GAAG,EAAE,MAAM,CAAA;IACX;;;;;;;OAOG;IACH,SAAS,CAAC,EAAE,WAAW,EAAE,CAAA;CACzB,CAAA;AAED,0DAA0D;AAC1D,MAAM,MAAM,OAAO,GAAG;IACrB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,uDAAuD;IACvD,SAAS,CAAC,EAAE,IAAI,CAAA;CAChB,CAAA;AAED,+CAA+C;AAC/C,MAAM,MAAM,WAAW,GAAG;IACzB,OAAO,EAAE,MAAM,CAAA;IACf,GAAG,EAAE,OAAO,CAAA;IACZ,SAAS,EAAE,WAAW,EAAE,CAAA;CACxB,CAAA;AAsBD;;;;;;;;;GASG;AACH,wBAAsB,MAAM,CAAC,MAAM,EAAE,cAAc,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,CAwB/F;AAYD;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC7B,QAAQ,EAAE,WAAW,EACrB,KAAK,EAAE,KAAK,GAAG,KAAK,EACpB,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,OAAO,EAAE,MAAM,GACb,WAAW,CA+Bb"}
|
package/dist/whoami.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { PlaneUnavailableError, TrustGateError } from './errors.js';
|
|
2
|
+
import { requestJSON } from './http.js';
|
|
3
|
+
/**
|
|
4
|
+
* Asks the key what it reaches, when it dies, and what is not connected yet.
|
|
5
|
+
*
|
|
6
|
+
* It is what lets a client be configured with one secret: the slugs were
|
|
7
|
+
* chosen by whoever created the consumers, and the LLM plane is on a host the
|
|
8
|
+
* MCP URL says nothing about, so both have to come from the gateway. The other
|
|
9
|
+
* two answers are the failures a client would otherwise meet at runtime — an
|
|
10
|
+
* expired key as a 401 mid-run, an unconnected server as a refusal on the
|
|
11
|
+
* first tool call — moved to where something can still be done about them.
|
|
12
|
+
*/
|
|
13
|
+
export async function whoAmI(config, signal) {
|
|
14
|
+
const { body } = await requestJSON(config, 'GET', '/whoami', { signal });
|
|
15
|
+
return {
|
|
16
|
+
gateway: body?.gateway ?? '',
|
|
17
|
+
key: {
|
|
18
|
+
name: body?.key?.name || undefined,
|
|
19
|
+
expiresAt: parseDate(body?.key?.expires_at),
|
|
20
|
+
},
|
|
21
|
+
consumers: (body?.consumers ?? []).map((consumer) => ({
|
|
22
|
+
slug: consumer.slug,
|
|
23
|
+
name: consumer.name || undefined,
|
|
24
|
+
type: String(consumer.type ?? '').toUpperCase(),
|
|
25
|
+
active: consumer.active !== false,
|
|
26
|
+
url: consumer.url ?? '',
|
|
27
|
+
upstreams: consumer.upstreams?.map((upstream) => ({
|
|
28
|
+
server: upstream.server ?? '',
|
|
29
|
+
provider: upstream.provider || undefined,
|
|
30
|
+
account: upstream.account === 'shared' ? 'shared' : 'user',
|
|
31
|
+
connected: upstream.connected === true,
|
|
32
|
+
needsReconnect: upstream.needs_reconnect === true,
|
|
33
|
+
blocked: blockedBy(upstream.blocked),
|
|
34
|
+
})),
|
|
35
|
+
})),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
function blockedBy(raw) {
|
|
39
|
+
return raw === 'administrator' || raw === 'end_user' ? raw : undefined;
|
|
40
|
+
}
|
|
41
|
+
function parseDate(raw) {
|
|
42
|
+
if (!raw)
|
|
43
|
+
return undefined;
|
|
44
|
+
const at = new Date(raw);
|
|
45
|
+
return Number.isNaN(at.getTime()) ? undefined : at;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Picks the one consumer of a plane, or explains why it cannot.
|
|
49
|
+
*
|
|
50
|
+
* A key attached to two consumers of the same type is a legitimate setup that
|
|
51
|
+
* this SDK cannot resolve on its own, so it names them and asks — rather than
|
|
52
|
+
* guessing and running an agent against the wrong surface.
|
|
53
|
+
*/
|
|
54
|
+
export function selectConsumer(identity, plane, configured, envName) {
|
|
55
|
+
const candidates = identity.consumers.filter((consumer) => consumer.type === plane);
|
|
56
|
+
if (configured) {
|
|
57
|
+
const named = candidates.find((consumer) => consumer.slug === configured);
|
|
58
|
+
if (named)
|
|
59
|
+
return named;
|
|
60
|
+
throw new PlaneUnavailableError(`this API key does not reach a ${plane} consumer called "${configured}"` +
|
|
61
|
+
(candidates.length ? `; it reaches ${candidates.map((c) => c.slug).join(', ')}` : ''));
|
|
62
|
+
}
|
|
63
|
+
if (candidates.length === 0) {
|
|
64
|
+
throw new PlaneUnavailableError(`this API key reaches no ${plane} consumer. ` +
|
|
65
|
+
'Ask the admin who owns this application to attach one, or name it explicitly.');
|
|
66
|
+
}
|
|
67
|
+
if (candidates.length > 1) {
|
|
68
|
+
throw new TrustGateError(`this API key reaches several ${plane} consumers (${candidates
|
|
69
|
+
.map((consumer) => consumer.slug)
|
|
70
|
+
.join(', ')}); name the one this agent uses.`);
|
|
71
|
+
}
|
|
72
|
+
const only = candidates[0];
|
|
73
|
+
if (!only.url) {
|
|
74
|
+
throw new PlaneUnavailableError(`the gateway publishes no host for its ${plane} plane, so "${only.slug}" has no address. ` +
|
|
75
|
+
`Set ${envName} and a base URL for it, or ask an operator to configure that plane's domain.`);
|
|
76
|
+
}
|
|
77
|
+
return only;
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=whoami.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"whoami.js","sourceRoot":"","sources":["../src/whoami.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@neuraltrust/trustgate",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "TrustGate SDK — governed tools and models for agents, over the MCP and LLM planes of a TrustGate gateway.",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"author": "NeuralTrust",
|
|
7
|
+
"homepage": "https://docs.neuraltrust.ai/trustgate/mcp/connect",
|
|
8
|
+
"repository": { "type": "git", "url": "git+https://github.com/NeuralTrust/trustgate-sdk.git", "directory": "typescript" },
|
|
9
|
+
"bugs": { "url": "https://github.com/NeuralTrust/trustgate-sdk/issues" },
|
|
10
|
+
"keywords": ["mcp", "model-context-protocol", "agents", "llm", "gateway", "trustgate", "neuraltrust"],
|
|
11
|
+
"type": "module",
|
|
12
|
+
"main": "./dist/index.js",
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"exports": { ".": { "types": "./dist/index.d.ts", "default": "./dist/index.js" } },
|
|
15
|
+
"files": ["dist", "src", "README.md", "LICENSE"],
|
|
16
|
+
"engines": { "node": ">=18" },
|
|
17
|
+
"sideEffects": false,
|
|
18
|
+
"publishConfig": { "access": "public" },
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsc -p tsconfig.build.json",
|
|
21
|
+
"prepublishOnly": "npm run typecheck && npm test && npm run build",
|
|
22
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
23
|
+
"test": "vitest run"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"typescript": "^5.6.0",
|
|
27
|
+
"vitest": "^5.0.1"
|
|
28
|
+
}
|
|
29
|
+
}
|
package/src/agent.ts
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import { END_USER_HEADER, type ResolvedConfig } from './config.js'
|
|
2
|
+
import { createConnectLink, listConnections, requireEndUser } from './connections.js'
|
|
3
|
+
import { ToolNotFoundError } from './errors.js'
|
|
4
|
+
import { adapterFor, restoreArguments, type ConversionWarning, type ToolResult } from './formats.js'
|
|
5
|
+
import { MCPTransport } from './mcp.js'
|
|
6
|
+
import {
|
|
7
|
+
Actor,
|
|
8
|
+
ToolFormat,
|
|
9
|
+
resolveToolName,
|
|
10
|
+
type ConnectLink,
|
|
11
|
+
type Connection,
|
|
12
|
+
type Endpoint,
|
|
13
|
+
type GatewayTool,
|
|
14
|
+
type JSONSchema,
|
|
15
|
+
type ToolCall,
|
|
16
|
+
} from './types.js'
|
|
17
|
+
|
|
18
|
+
export type ToolkitOptions = {
|
|
19
|
+
/**
|
|
20
|
+
* Ask the provider to hold the model to the schema. Off by default: it is
|
|
21
|
+
* a stricter promise bought with a lossy rewrite, and some tools cannot be
|
|
22
|
+
* expressed under it at all (see `warnings`).
|
|
23
|
+
*/
|
|
24
|
+
strict?: boolean
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* A tool surface in one provider's dialect, with the executor that belongs to
|
|
29
|
+
* it.
|
|
30
|
+
*
|
|
31
|
+
* They travel together because they are two halves of one translation: what
|
|
32
|
+
* `tools` added on the way out, `execute` has to undo on the way back.
|
|
33
|
+
*
|
|
34
|
+
* `Tool` and `Output` are the provider's own types, named by the caller:
|
|
35
|
+
*
|
|
36
|
+
* ```ts
|
|
37
|
+
* const { tools, execute } = agent.toolkit<
|
|
38
|
+
* OpenAI.Responses.Tool,
|
|
39
|
+
* OpenAI.Responses.ResponseInputItem
|
|
40
|
+
* >(ToolFormat.OpenAIResponses)
|
|
41
|
+
* ```
|
|
42
|
+
*
|
|
43
|
+
* They default to `unknown`, so nothing breaks by leaving them out — but then
|
|
44
|
+
* "pass this straight to the provider's API" is a promise the type does not
|
|
45
|
+
* keep, and the caller casts at the boundary. The SDK cannot name them itself:
|
|
46
|
+
* it carries no dependency on any provider's package, which is what lets one
|
|
47
|
+
* install serve all of them.
|
|
48
|
+
*/
|
|
49
|
+
export class Toolkit<Tool = unknown, Output = unknown> {
|
|
50
|
+
constructor(
|
|
51
|
+
/** Pass this straight to the provider's API. */
|
|
52
|
+
readonly tools: Tool[],
|
|
53
|
+
/** Tools whose schema could not be expressed in the requested dialect. */
|
|
54
|
+
readonly warnings: ConversionWarning[],
|
|
55
|
+
private readonly format: ToolFormat,
|
|
56
|
+
private readonly originals: Map<string, JSONSchema>,
|
|
57
|
+
private readonly transport: MCPTransport
|
|
58
|
+
) {
|
|
59
|
+
// Bound, because the documented way to use this is to destructure it —
|
|
60
|
+
// `const { tools, execute } = agent.toolkit(…)` — and an unbound method
|
|
61
|
+
// loses the format it needs the moment it is called that way.
|
|
62
|
+
this.calls = this.calls.bind(this)
|
|
63
|
+
this.execute = this.execute.bind(this)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** The calls the model asked for, read out of the provider's response. */
|
|
67
|
+
calls(output: unknown): ToolCall[] {
|
|
68
|
+
return adapterFor(this.format).extractCalls(output)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Runs the calls the model asked for and returns what to send back.
|
|
73
|
+
*
|
|
74
|
+
* Every call goes to the gateway, so the policy, the audit trail and the
|
|
75
|
+
* upstream credentials stay where they were. The caller's process only
|
|
76
|
+
* decides whether to make the call at all.
|
|
77
|
+
*/
|
|
78
|
+
async execute(output: unknown, signal?: AbortSignal): Promise<Output[]> {
|
|
79
|
+
const adapter = adapterFor(this.format)
|
|
80
|
+
const calls = adapter.extractCalls(output)
|
|
81
|
+
const results: ToolResult[] = []
|
|
82
|
+
for (const call of calls) {
|
|
83
|
+
const args = restoreArguments(call.arguments, this.originals.get(call.name))
|
|
84
|
+
const result = await this.transport.callTool(call.name, args, signal)
|
|
85
|
+
results.push({ call, result })
|
|
86
|
+
}
|
|
87
|
+
return adapter.toOutputs(results) as Output[]
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* An application's handle on its gateway.
|
|
93
|
+
*
|
|
94
|
+
* It speaks as the application itself: one principal, its own upstream
|
|
95
|
+
* accounts, nothing per-user. Which handle you get is not a choice made here —
|
|
96
|
+
* it follows from how the consumer was configured, which is why `connect()`
|
|
97
|
+
* returns one of these or refuses.
|
|
98
|
+
*/
|
|
99
|
+
export class Agent {
|
|
100
|
+
readonly actor = Actor.Application
|
|
101
|
+
|
|
102
|
+
constructor(
|
|
103
|
+
private readonly config: ResolvedConfig,
|
|
104
|
+
readonly slug: string,
|
|
105
|
+
private readonly transport: MCPTransport,
|
|
106
|
+
/** The tools this consumer serves, as the gateway named them. */
|
|
107
|
+
public tools: GatewayTool[],
|
|
108
|
+
/** Tools asked for in `requires` that the toolkit does not carry. */
|
|
109
|
+
readonly missing: string[],
|
|
110
|
+
/** The application's own upstream accounts, as of `connect()`. */
|
|
111
|
+
readonly connections: Connection[]
|
|
112
|
+
) {}
|
|
113
|
+
|
|
114
|
+
/** URL and headers for a framework that brings its own MCP client. */
|
|
115
|
+
get mcp(): Endpoint {
|
|
116
|
+
return { url: this.transport.url, headers: this.transport.headers }
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* The same surface, translated for a provider you call directly.
|
|
121
|
+
*
|
|
122
|
+
* Name the provider's types to have them travel with it:
|
|
123
|
+
* `toolkit<OpenAI.Responses.Tool, OpenAI.Responses.ResponseInputItem>(…)`.
|
|
124
|
+
*/
|
|
125
|
+
toolkit<Tool = unknown, Output = unknown>(
|
|
126
|
+
format: ToolFormat,
|
|
127
|
+
options: ToolkitOptions = {}
|
|
128
|
+
): Toolkit<Tool, Output> {
|
|
129
|
+
const { tools, warnings, originals } = adapterFor(format).convert(this.tools, {
|
|
130
|
+
strict: options.strict ?? false,
|
|
131
|
+
})
|
|
132
|
+
return new Toolkit<Tool, Output>(
|
|
133
|
+
tools as Tool[],
|
|
134
|
+
warnings,
|
|
135
|
+
format,
|
|
136
|
+
originals,
|
|
137
|
+
this.transport
|
|
138
|
+
)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* One tool, called directly. The escape hatch under the toolkits.
|
|
143
|
+
*
|
|
144
|
+
* The server prefix is optional here: "list_issues" reaches
|
|
145
|
+
* "linear_list_issues" while Linear is the only server of this application
|
|
146
|
+
* that serves it. The gateway put that prefix there, so a caller writing the
|
|
147
|
+
* name by hand should not have to.
|
|
148
|
+
*/
|
|
149
|
+
async callTool(
|
|
150
|
+
name: string,
|
|
151
|
+
args: Record<string, unknown> = {},
|
|
152
|
+
signal?: AbortSignal
|
|
153
|
+
): Promise<Record<string, unknown>> {
|
|
154
|
+
return this.transport.callTool(resolveToolName(name, this.tools), args, signal)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Re-reads the surface.
|
|
159
|
+
*
|
|
160
|
+
* An admin owns this toolkit and can change it under a running agent, so a
|
|
161
|
+
* long-lived process re-reads rather than trusting the list it took at
|
|
162
|
+
* startup.
|
|
163
|
+
*/
|
|
164
|
+
async refresh(signal?: AbortSignal): Promise<GatewayTool[]> {
|
|
165
|
+
this.tools = await this.transport.listTools(signal)
|
|
166
|
+
return this.tools
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** What the application still owes before it can call every server. */
|
|
170
|
+
async refreshConnections(signal?: AbortSignal): Promise<Connection[]> {
|
|
171
|
+
return listConnections(this.config, this.slug, undefined, signal)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* The same application, acting for one named person.
|
|
176
|
+
*
|
|
177
|
+
* No round trip and no second surface to read: the toolkit an admin bound
|
|
178
|
+
* is the application's, identical for everyone it acts for. What changes is
|
|
179
|
+
* one header, and with it whose upstream account the gateway reaches for.
|
|
180
|
+
*/
|
|
181
|
+
forEndUser(endUser: string): EndUserAgent {
|
|
182
|
+
return endUserAgent(this.config, this.slug, endUser, this.transport.url, this.tools)
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* An application's handle for one of its own end users.
|
|
188
|
+
*
|
|
189
|
+
* The user travels in a header, so a handle is a header and nothing more —
|
|
190
|
+
* but the MCP endpoint's headers are fixed when a client connects, which is
|
|
191
|
+
* why each user needs their own transport rather than a shared one.
|
|
192
|
+
*/
|
|
193
|
+
export class EndUserAgent {
|
|
194
|
+
readonly actor = Actor.EndUser
|
|
195
|
+
|
|
196
|
+
constructor(
|
|
197
|
+
private readonly config: ResolvedConfig,
|
|
198
|
+
readonly slug: string,
|
|
199
|
+
readonly endUser: string,
|
|
200
|
+
private readonly transport: MCPTransport,
|
|
201
|
+
public tools: GatewayTool[]
|
|
202
|
+
) {}
|
|
203
|
+
|
|
204
|
+
get mcp(): Endpoint {
|
|
205
|
+
return { url: this.transport.url, headers: this.transport.headers }
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
toolkit<Tool = unknown, Output = unknown>(
|
|
209
|
+
format: ToolFormat,
|
|
210
|
+
options: ToolkitOptions = {}
|
|
211
|
+
): Toolkit<Tool, Output> {
|
|
212
|
+
const { tools, warnings, originals } = adapterFor(format).convert(this.tools, {
|
|
213
|
+
strict: options.strict ?? false,
|
|
214
|
+
})
|
|
215
|
+
return new Toolkit<Tool, Output>(
|
|
216
|
+
tools as Tool[],
|
|
217
|
+
warnings,
|
|
218
|
+
format,
|
|
219
|
+
originals,
|
|
220
|
+
this.transport
|
|
221
|
+
)
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
async callTool(
|
|
225
|
+
name: string,
|
|
226
|
+
args: Record<string, unknown> = {},
|
|
227
|
+
signal?: AbortSignal
|
|
228
|
+
): Promise<Record<string, unknown>> {
|
|
229
|
+
return this.transport.callTool(name, args, signal)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
async refresh(signal?: AbortSignal): Promise<GatewayTool[]> {
|
|
233
|
+
this.tools = await this.transport.listTools(signal)
|
|
234
|
+
return this.tools
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/** Which servers this user has connected, and which they have not. */
|
|
238
|
+
async connections(signal?: AbortSignal): Promise<Connection[]> {
|
|
239
|
+
return listConnections(this.config, this.slug, this.endUser, signal)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* The page to put in front of this user so they can connect an account.
|
|
244
|
+
*
|
|
245
|
+
* Naming a provider narrows it to that one server; omitting it covers every
|
|
246
|
+
* server of the application that forwards a credential. The link expires,
|
|
247
|
+
* so it is minted when it is about to be shown, not cached.
|
|
248
|
+
*/
|
|
249
|
+
async connectLink(provider?: string, signal?: AbortSignal): Promise<ConnectLink> {
|
|
250
|
+
return createConnectLink(this.config, this.slug, this.endUser, provider, signal)
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/** Builds the per-user handle, with the header that names them. */
|
|
255
|
+
export function endUserAgent(
|
|
256
|
+
config: ResolvedConfig,
|
|
257
|
+
slug: string,
|
|
258
|
+
rawEndUser: string,
|
|
259
|
+
url: string,
|
|
260
|
+
tools: GatewayTool[]
|
|
261
|
+
): EndUserAgent {
|
|
262
|
+
const endUser = requireEndUser(rawEndUser)
|
|
263
|
+
const transport = new MCPTransport(config, url, { [END_USER_HEADER]: endUser })
|
|
264
|
+
return new EndUserAgent(config, slug, endUser, transport, tools)
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export { ToolNotFoundError }
|