@m6d/cortex-cli 0.21.0 → 0.22.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/package.json +1 -1
- package/src/contracts/client-tools/index.ts +21 -0
- package/src/contracts/egress-policy/index.ts +1 -0
- package/src/contracts/egress-policy/url.ts +1 -1
- package/src/contracts/management/procedures.generated.ts +114 -16
- package/src/contracts/manifest/index.ts +61 -17
- package/src/contracts/server/index.ts +2 -1
- package/src/contracts/wire/index.ts +43 -0
- package/src/scaffold/files.ts +2 -2
package/package.json
CHANGED
|
@@ -60,6 +60,27 @@ export type LocationResult = LatLng & { accuracy?: number };
|
|
|
60
60
|
|
|
61
61
|
export type RequestLocationOutcome = ClientToolOutcome<LocationResult>;
|
|
62
62
|
|
|
63
|
+
/**
|
|
64
|
+
* Asks the end user to connect their own account to an MCP server that signs
|
|
65
|
+
* in each end user. Its call is stamped with where the sign-in starts
|
|
66
|
+
* (`ConnectAccountBinding` in the wire contract).
|
|
67
|
+
*/
|
|
68
|
+
export const CONNECT_ACCOUNT_TOOL = "connectAccount";
|
|
69
|
+
|
|
70
|
+
/** The model's arguments: the MCP server to connect, by name. */
|
|
71
|
+
export type ConnectAccountInput = { server: string };
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* `completed` once the account is connected with the access it needs,
|
|
75
|
+
* `cancelled` when the end user said Not now or cancelled the sign-in,
|
|
76
|
+
* `failed` when the sign-in came back without that access, or ran out
|
|
77
|
+
* first.
|
|
78
|
+
*/
|
|
79
|
+
export type ConnectAccountOutcome = ClientToolOutcome<never>;
|
|
80
|
+
|
|
81
|
+
/** How long a sign-in Cortex starts stays open, and so how long the widget waits for its connection. */
|
|
82
|
+
export const SIGN_IN_TTL_MS = 10 * 60 * 1000;
|
|
83
|
+
|
|
63
84
|
/**
|
|
64
85
|
* Parse a `message` event into a handshake, or null when the origin doesn't
|
|
65
86
|
* match the tool's `embedOrigin` or the payload isn't a well-formed handshake.
|
|
@@ -79,7 +79,7 @@ export function isPrivateAddress(address: string) {
|
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
/** Loopback by its normalized spelling: `[::1]` arrives from a URL without its brackets. */
|
|
82
|
-
function isLoopback(hostname: string) {
|
|
82
|
+
export function isLoopback(hostname: string) {
|
|
83
83
|
return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1";
|
|
84
84
|
}
|
|
85
85
|
|
|
@@ -233,6 +233,7 @@ export const managementProcedureKinds = {
|
|
|
233
233
|
"credentials.delete": "mutation",
|
|
234
234
|
"credentials.list": "query",
|
|
235
235
|
"credentials.names": "query",
|
|
236
|
+
"credentials.rotateKey": "mutation",
|
|
236
237
|
"credentials.update": "mutation",
|
|
237
238
|
"datasetHosts.create": "mutation",
|
|
238
239
|
"datasetHosts.delete": "mutation",
|
|
@@ -257,6 +258,8 @@ export const managementProcedureKinds = {
|
|
|
257
258
|
"manifests.sync": "mutation",
|
|
258
259
|
"mcpServers.create": "mutation",
|
|
259
260
|
"mcpServers.delete": "mutation",
|
|
261
|
+
"mcpServers.detectSignIn": "query",
|
|
262
|
+
"mcpServers.disconnect": "mutation",
|
|
260
263
|
"mcpServers.get": "query",
|
|
261
264
|
"mcpServers.list": "query",
|
|
262
265
|
"mcpServers.publish": "mutation",
|
|
@@ -885,7 +888,7 @@ export type ManagementProcedures = {
|
|
|
885
888
|
output: {
|
|
886
889
|
id: string;
|
|
887
890
|
actorId: string;
|
|
888
|
-
credentialKind: "session" | "managementKey" | "operatorToken" | "system";
|
|
891
|
+
credentialKind: "session" | "managementKey" | "operatorToken" | "oauthToken" | "system";
|
|
889
892
|
credentialId: string | null;
|
|
890
893
|
entityType: string;
|
|
891
894
|
entityId: string;
|
|
@@ -902,6 +905,7 @@ export type ManagementProcedures = {
|
|
|
902
905
|
displayName: Partial<Record<"en" | "ar", string>>;
|
|
903
906
|
fingerprint: string;
|
|
904
907
|
} | null;
|
|
908
|
+
oauthClientName: string | null;
|
|
905
909
|
before: unknown;
|
|
906
910
|
after: unknown;
|
|
907
911
|
};
|
|
@@ -922,7 +926,7 @@ export type ManagementProcedures = {
|
|
|
922
926
|
items: Array<{
|
|
923
927
|
id: string;
|
|
924
928
|
actorId: string;
|
|
925
|
-
credentialKind: "session" | "managementKey" | "operatorToken" | "system";
|
|
929
|
+
credentialKind: "session" | "managementKey" | "operatorToken" | "oauthToken" | "system";
|
|
926
930
|
credentialId: string | null;
|
|
927
931
|
entityType: string;
|
|
928
932
|
entityId: string;
|
|
@@ -939,6 +943,7 @@ export type ManagementProcedures = {
|
|
|
939
943
|
displayName: Partial<Record<"en" | "ar", string>>;
|
|
940
944
|
fingerprint: string;
|
|
941
945
|
} | null;
|
|
946
|
+
oauthClientName: string | null;
|
|
942
947
|
}>;
|
|
943
948
|
/** Every row the caller may see, ignoring the search and filters. */
|
|
944
949
|
count: number;
|
|
@@ -1134,15 +1139,17 @@ export type ManagementProcedures = {
|
|
|
1134
1139
|
name: string;
|
|
1135
1140
|
};
|
|
1136
1141
|
};
|
|
1137
|
-
/** Create a
|
|
1142
|
+
/** Create a credential over a stored secret, with a display name (`{}` for none). `static` presents the secret's value; `oauth2-client-credentials` gets a bearer at `tokenUrl` (https) as `clientId`, by `authMethod`: `client_secret_basic` sends the secret; `private_key_jwt` signs with a key Cortex generates into a new secret named `secretName`, which nothing else may compose or rewrite (`signing_key_secret`), and needs `issuer` and an engine public URL to publish its key under (`jwks_unavailable`); `none` is a public app for MCP sign-in at `issuer`, with no secret. `issuer` is the authorization server's exact identifier. Name, kind and auth method never change. Needs `secrets:write`. */
|
|
1138
1143
|
"credentials.create": {
|
|
1139
1144
|
input: {
|
|
1140
1145
|
displayName: Partial<Record<"en" | "ar", string>>;
|
|
1141
1146
|
kind: "static" | "oauth2-client-credentials";
|
|
1147
|
+
authMethod: "client_secret_basic" | "private_key_jwt" | "none";
|
|
1142
1148
|
secretName: string;
|
|
1143
1149
|
tokenUrl: string;
|
|
1144
1150
|
clientId: string;
|
|
1145
1151
|
scope: string;
|
|
1152
|
+
issuer: string;
|
|
1146
1153
|
/** A name: lowercase letters, digits, and single hyphens. */
|
|
1147
1154
|
name: string;
|
|
1148
1155
|
};
|
|
@@ -1151,10 +1158,12 @@ export type ManagementProcedures = {
|
|
|
1151
1158
|
name: string;
|
|
1152
1159
|
displayName: Partial<Record<"en" | "ar", string>>;
|
|
1153
1160
|
kind: "static" | "oauth2-client-credentials";
|
|
1154
|
-
|
|
1161
|
+
authMethod: "client_secret_basic" | "private_key_jwt" | "none" | null;
|
|
1162
|
+
secretName: string | null;
|
|
1155
1163
|
tokenUrl: string | null;
|
|
1156
1164
|
clientId: string | null;
|
|
1157
1165
|
scope: string | null;
|
|
1166
|
+
issuer: string | null;
|
|
1158
1167
|
createdAt: string;
|
|
1159
1168
|
updatedAt: string;
|
|
1160
1169
|
};
|
|
@@ -1169,15 +1178,17 @@ export type ManagementProcedures = {
|
|
|
1169
1178
|
name: string;
|
|
1170
1179
|
displayName: Partial<Record<"en" | "ar", string>>;
|
|
1171
1180
|
kind: "static" | "oauth2-client-credentials";
|
|
1172
|
-
|
|
1181
|
+
authMethod: "client_secret_basic" | "private_key_jwt" | "none" | null;
|
|
1182
|
+
secretName: string | null;
|
|
1173
1183
|
tokenUrl: string | null;
|
|
1174
1184
|
clientId: string | null;
|
|
1175
1185
|
scope: string | null;
|
|
1186
|
+
issuer: string | null;
|
|
1176
1187
|
createdAt: string;
|
|
1177
1188
|
updatedAt: string;
|
|
1178
1189
|
};
|
|
1179
1190
|
};
|
|
1180
|
-
/** List credentials with pagination and search: the name tools, dataset hosts and triggers reference, its display name, its kind, the secret it composes,
|
|
1191
|
+
/** List credentials with pagination and search: the name tools, dataset hosts and triggers reference, its display name, its kind and auth method, the secret it composes, how many rows name it with the first few (`usedBy`), and, for a `private_key_jwt` one, the URL of its public key set (`jwksUrl`, null without an engine public URL), which its authorization server is registered with, and the `kid` of the key it signs with now (null for any other). */
|
|
1181
1192
|
"credentials.list": {
|
|
1182
1193
|
input: {
|
|
1183
1194
|
pageIndex?: number;
|
|
@@ -1190,10 +1201,12 @@ export type ManagementProcedures = {
|
|
|
1190
1201
|
name: string;
|
|
1191
1202
|
displayName: Partial<Record<"en" | "ar", string>>;
|
|
1192
1203
|
kind: "static" | "oauth2-client-credentials";
|
|
1193
|
-
|
|
1204
|
+
authMethod: "client_secret_basic" | "private_key_jwt" | "none" | null;
|
|
1205
|
+
secretName: string | null;
|
|
1194
1206
|
tokenUrl: string | null;
|
|
1195
1207
|
clientId: string | null;
|
|
1196
1208
|
scope: string | null;
|
|
1209
|
+
issuer: string | null;
|
|
1197
1210
|
createdAt: string;
|
|
1198
1211
|
updatedAt: string;
|
|
1199
1212
|
usedBy: {
|
|
@@ -1205,6 +1218,8 @@ export type ManagementProcedures = {
|
|
|
1205
1218
|
id: string;
|
|
1206
1219
|
}>;
|
|
1207
1220
|
};
|
|
1221
|
+
jwksUrl: string | null;
|
|
1222
|
+
kid: string | null;
|
|
1208
1223
|
}>;
|
|
1209
1224
|
/** Every row the caller may see, ignoring the search and filters. */
|
|
1210
1225
|
count: number;
|
|
@@ -1212,25 +1227,47 @@ export type ManagementProcedures = {
|
|
|
1212
1227
|
filteredCount: number;
|
|
1213
1228
|
};
|
|
1214
1229
|
};
|
|
1215
|
-
/** Each credential's name and display name, in name order, for a picker. `present`: what a tool, dataset host, webhook or trigger run may present upstream (every credential but a trigger's fire credential). `fire`: what a trigger may be fired with (static credentials). */
|
|
1230
|
+
/** Each credential's name and display name, in name order, for a picker. `present`: what a tool, dataset host, webhook or trigger run may present upstream (every credential but a trigger's fire credential). `fire`: what a trigger may be fired with (static credentials). `signIn`: what an MCP server's sign-in may present as its pre-registered app (OAuth2 client credentials ones naming their `issuer`). */
|
|
1216
1231
|
"credentials.names": {
|
|
1217
1232
|
input: {
|
|
1218
|
-
use: "present" | "fire";
|
|
1233
|
+
use: "present" | "fire" | "signIn";
|
|
1219
1234
|
};
|
|
1220
1235
|
output: Array<{
|
|
1221
1236
|
name: string;
|
|
1222
1237
|
displayName: Partial<Record<"en" | "ar", string>>;
|
|
1223
1238
|
}>;
|
|
1224
1239
|
};
|
|
1225
|
-
/**
|
|
1240
|
+
/** Replace a `private_key_jwt` credential's signing key with a new one, under a new `kid`, rewriting its secret (`no_signing_key` for any other credential). Its key set serves only the new public key at once, so assertions verify again once the authorization server refetches it. */
|
|
1241
|
+
"credentials.rotateKey": {
|
|
1242
|
+
input: {
|
|
1243
|
+
id: string;
|
|
1244
|
+
};
|
|
1245
|
+
output: {
|
|
1246
|
+
id: string;
|
|
1247
|
+
name: string;
|
|
1248
|
+
displayName: Partial<Record<"en" | "ar", string>>;
|
|
1249
|
+
kind: "static" | "oauth2-client-credentials";
|
|
1250
|
+
authMethod: "client_secret_basic" | "private_key_jwt" | "none" | null;
|
|
1251
|
+
secretName: string | null;
|
|
1252
|
+
tokenUrl: string | null;
|
|
1253
|
+
clientId: string | null;
|
|
1254
|
+
scope: string | null;
|
|
1255
|
+
issuer: string | null;
|
|
1256
|
+
createdAt: string;
|
|
1257
|
+
updatedAt: string;
|
|
1258
|
+
};
|
|
1259
|
+
};
|
|
1260
|
+
/** Change a credential's display name, the secret it composes and, for an OAuth2 one, its token URL, client id, scope and issuer. Applies to the next call of every tool, dataset host and trigger naming it. Changing the secret, the token URL or the issuer needs `secrets:write` too (`secret_binding_forbidden`): each sends a stored secret somewhere new. A `private_key_jwt` one keeps its secret, which holds its key, like its kind and auth method (`kind_locked`). */
|
|
1226
1261
|
"credentials.update": {
|
|
1227
1262
|
input: {
|
|
1228
1263
|
displayName: Partial<Record<"en" | "ar", string>>;
|
|
1229
1264
|
kind: "static" | "oauth2-client-credentials";
|
|
1265
|
+
authMethod: "client_secret_basic" | "private_key_jwt" | "none";
|
|
1230
1266
|
secretName: string;
|
|
1231
1267
|
tokenUrl: string;
|
|
1232
1268
|
clientId: string;
|
|
1233
1269
|
scope: string;
|
|
1270
|
+
issuer: string;
|
|
1234
1271
|
id: string;
|
|
1235
1272
|
};
|
|
1236
1273
|
output: {
|
|
@@ -1238,10 +1275,12 @@ export type ManagementProcedures = {
|
|
|
1238
1275
|
name: string;
|
|
1239
1276
|
displayName: Partial<Record<"en" | "ar", string>>;
|
|
1240
1277
|
kind: "static" | "oauth2-client-credentials";
|
|
1241
|
-
|
|
1278
|
+
authMethod: "client_secret_basic" | "private_key_jwt" | "none" | null;
|
|
1279
|
+
secretName: string | null;
|
|
1242
1280
|
tokenUrl: string | null;
|
|
1243
1281
|
clientId: string | null;
|
|
1244
1282
|
scope: string | null;
|
|
1283
|
+
issuer: string | null;
|
|
1245
1284
|
createdAt: string;
|
|
1246
1285
|
updatedAt: string;
|
|
1247
1286
|
};
|
|
@@ -2178,6 +2217,11 @@ export type ManagementProcedures = {
|
|
|
2178
2217
|
value: string;
|
|
2179
2218
|
};
|
|
2180
2219
|
}>;
|
|
2220
|
+
signIn?: {
|
|
2221
|
+
connects: "shared" | "endUser";
|
|
2222
|
+
scope?: string;
|
|
2223
|
+
credential?: string;
|
|
2224
|
+
};
|
|
2181
2225
|
include?: Array<string>;
|
|
2182
2226
|
exclude?: Array<string>;
|
|
2183
2227
|
audience: "all" | {
|
|
@@ -2185,7 +2229,7 @@ export type ManagementProcedures = {
|
|
|
2185
2229
|
} | {
|
|
2186
2230
|
agents: Array<string>;
|
|
2187
2231
|
};
|
|
2188
|
-
overridable?: Array<"displayName" | "include" | "exclude" | "headers">;
|
|
2232
|
+
overridable?: Array<"displayName" | "include" | "exclude" | "headers" | "signIn">;
|
|
2189
2233
|
}>;
|
|
2190
2234
|
webhooks?: Array<{
|
|
2191
2235
|
name: string;
|
|
@@ -2713,6 +2757,11 @@ export type ManagementProcedures = {
|
|
|
2713
2757
|
value: string;
|
|
2714
2758
|
};
|
|
2715
2759
|
}>;
|
|
2760
|
+
signIn?: {
|
|
2761
|
+
connects: "shared" | "endUser";
|
|
2762
|
+
scope?: string;
|
|
2763
|
+
credential?: string;
|
|
2764
|
+
};
|
|
2716
2765
|
include?: Array<string>;
|
|
2717
2766
|
exclude?: Array<string>;
|
|
2718
2767
|
audience: "all" | {
|
|
@@ -2720,7 +2769,7 @@ export type ManagementProcedures = {
|
|
|
2720
2769
|
} | {
|
|
2721
2770
|
agents: Array<string>;
|
|
2722
2771
|
};
|
|
2723
|
-
overridable?: Array<"displayName" | "include" | "exclude" | "headers">;
|
|
2772
|
+
overridable?: Array<"displayName" | "include" | "exclude" | "headers" | "signIn">;
|
|
2724
2773
|
}>;
|
|
2725
2774
|
webhooks?: Array<{
|
|
2726
2775
|
name: string;
|
|
@@ -2816,7 +2865,30 @@ export type ManagementProcedures = {
|
|
|
2816
2865
|
id: string;
|
|
2817
2866
|
};
|
|
2818
2867
|
};
|
|
2819
|
-
/**
|
|
2868
|
+
/** Find how Cortex would sign in to the MCP server with OAuth, from its metadata: the authorization server's issuer, the method Cortex would use (`preRegistered`, `clientIdDocument`, `dynamicRegistration`, or null for none), the scopes on offer, whether PKCE with S256 is supported, and the resource tokens are for. A pre-registered app for another issuer is refused as `sign_in_issuer_mismatch`. */
|
|
2869
|
+
"mcpServers.detectSignIn": {
|
|
2870
|
+
input: {
|
|
2871
|
+
id: string;
|
|
2872
|
+
};
|
|
2873
|
+
output: {
|
|
2874
|
+
issuer: string;
|
|
2875
|
+
method: "preRegistered" | "clientIdDocument" | "dynamicRegistration" | null;
|
|
2876
|
+
scopes: Array<string>;
|
|
2877
|
+
pkce: boolean;
|
|
2878
|
+
resource: string;
|
|
2879
|
+
};
|
|
2880
|
+
};
|
|
2881
|
+
/** Forget every connection Cortex holds to the MCP server: the shared one, or every end user's. Its turns report it not connected until someone signs in again. The tokens are not revoked at the authorization server. */
|
|
2882
|
+
"mcpServers.disconnect": {
|
|
2883
|
+
input: {
|
|
2884
|
+
id: string;
|
|
2885
|
+
};
|
|
2886
|
+
output: {
|
|
2887
|
+
id: string;
|
|
2888
|
+
name: string;
|
|
2889
|
+
};
|
|
2890
|
+
};
|
|
2891
|
+
/** One MCP server by id with its url, headers, sign-in, tool filters, and sharing; its shared connection, never its tokens, with `stepUp` once its server refused a call for lack of scope (the scopes it lacks, and whether a sign-in may still ask for them); how many end users are connected or must sign in again; and `signInUrl`, where a browser form-posts an operator token and the server's id to connect it (null while the engine or the console has no public URL). */
|
|
2820
2892
|
"mcpServers.get": {
|
|
2821
2893
|
input: {
|
|
2822
2894
|
id: string;
|
|
@@ -2855,6 +2927,11 @@ export type ManagementProcedures = {
|
|
|
2855
2927
|
value: string;
|
|
2856
2928
|
};
|
|
2857
2929
|
}>;
|
|
2930
|
+
signIn: {
|
|
2931
|
+
connects: "shared" | "endUser";
|
|
2932
|
+
scope?: string;
|
|
2933
|
+
credential?: string;
|
|
2934
|
+
} | null;
|
|
2858
2935
|
include: Array<string>;
|
|
2859
2936
|
exclude: Array<string>;
|
|
2860
2937
|
status: "draft" | "published" | "disabled";
|
|
@@ -2881,6 +2958,22 @@ export type ManagementProcedures = {
|
|
|
2881
2958
|
type: "string" | "number" | "boolean";
|
|
2882
2959
|
computed: boolean;
|
|
2883
2960
|
}>;
|
|
2961
|
+
connection: {
|
|
2962
|
+
status: "connected" | "needs_scope" | "broken";
|
|
2963
|
+
issuer: string;
|
|
2964
|
+
scope: string;
|
|
2965
|
+
expiresAt: string | null;
|
|
2966
|
+
updatedAt: string;
|
|
2967
|
+
stepUp: {
|
|
2968
|
+
missing: string;
|
|
2969
|
+
grantable: boolean;
|
|
2970
|
+
} | null;
|
|
2971
|
+
} | null;
|
|
2972
|
+
endUsers: {
|
|
2973
|
+
connected: number;
|
|
2974
|
+
broken: number;
|
|
2975
|
+
};
|
|
2976
|
+
signInUrl: string | null;
|
|
2884
2977
|
};
|
|
2885
2978
|
};
|
|
2886
2979
|
/** List MCP servers with pagination and search. */
|
|
@@ -3010,7 +3103,7 @@ export type ManagementProcedures = {
|
|
|
3010
3103
|
unchanged: boolean;
|
|
3011
3104
|
};
|
|
3012
3105
|
};
|
|
3013
|
-
/** Update an MCP server's draft: which of its tools agents may call (`include`, empty for every tool, and `exclude`, tool names as the server spells them). Saved as a draft; call publish to make it live. The url
|
|
3106
|
+
/** Update an MCP server's draft: which of its tools agents may call (`include`, empty for every tool, and `exclude`, tool names as the server spells them). Saved as a draft; call publish to make it live. The url, headers (each with a prefix and its value from a credential, the run token, a variable or a constant) and `signIn` apply live without a publish. `signIn` (null for none) has Cortex sign in with OAuth: `connects` is `shared` (an operator connects once for every agent) or `endUser` (each end user connects their own), `scope` what to ask for, and `credential` names the app registered with the authorization server beforehand, an `oauth2-client-credentials` credential naming its `issuer`. A server that signs in declares no Authorization header. */
|
|
3014
3107
|
"mcpServers.update": {
|
|
3015
3108
|
input: {
|
|
3016
3109
|
url: string;
|
|
@@ -3030,6 +3123,11 @@ export type ManagementProcedures = {
|
|
|
3030
3123
|
value: string;
|
|
3031
3124
|
};
|
|
3032
3125
|
}>;
|
|
3126
|
+
signIn: {
|
|
3127
|
+
connects: "shared" | "endUser";
|
|
3128
|
+
scope?: string;
|
|
3129
|
+
credential?: string;
|
|
3130
|
+
} | null;
|
|
3033
3131
|
include: Array<string>;
|
|
3034
3132
|
exclude: Array<string>;
|
|
3035
3133
|
id: string;
|
|
@@ -3754,7 +3852,7 @@ export type ManagementProcedures = {
|
|
|
3754
3852
|
displayName: Partial<Record<"en" | "ar", string>>;
|
|
3755
3853
|
}>;
|
|
3756
3854
|
};
|
|
3757
|
-
/** Replace a secret's value in place. */
|
|
3855
|
+
/** Replace a secret's value in place; a `private_key_jwt` credential's generated key is refused (`signing_key_secret`), since only `credentials.rotateKey` rewrites it. */
|
|
3758
3856
|
"secrets.rotate": {
|
|
3759
3857
|
input: {
|
|
3760
3858
|
id: string;
|
|
@@ -1196,24 +1196,68 @@ export const mcpToolNamesSchema = z
|
|
|
1196
1196
|
)
|
|
1197
1197
|
.max(200);
|
|
1198
1198
|
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
),
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
exclude: mcpToolNamesSchema.default([]),
|
|
1214
|
-
audience: audienceSchema,
|
|
1215
|
-
overridable: overridable(["displayName", "include", "exclude", "headers"]),
|
|
1199
|
+
/** Who holds an MCP server's sign-in: one connection an operator makes for every agent, or one per end user of each agent. */
|
|
1200
|
+
export const MCP_CONNECTION_OWNERS = ["shared", "endUser"] as const;
|
|
1201
|
+
|
|
1202
|
+
/**
|
|
1203
|
+
* How Cortex signs in to an MCP server that takes OAuth: who connects, the
|
|
1204
|
+
* scope it asks for (absent, what the server advertises), and, for an
|
|
1205
|
+
* authorization server that takes neither a client ID document nor dynamic
|
|
1206
|
+
* registration, the credential holding the app registered there beforehand:
|
|
1207
|
+
* an OAuth2 client credentials one naming that server as its issuer.
|
|
1208
|
+
*/
|
|
1209
|
+
export const mcpSignInSchema = z.strictObject({
|
|
1210
|
+
connects: z.enum(MCP_CONNECTION_OWNERS),
|
|
1211
|
+
scope: z.string().trim().min(1).max(1_000).optional(),
|
|
1212
|
+
credential: nameSchema.optional(),
|
|
1216
1213
|
});
|
|
1214
|
+
export type McpSignIn = z.infer<typeof mcpSignInSchema>;
|
|
1215
|
+
|
|
1216
|
+
/**
|
|
1217
|
+
* The index of the Authorization header a server that signs in declares
|
|
1218
|
+
* beside its sign-in, whose token travels under that header; -1 when it
|
|
1219
|
+
* signs in without one, or declares one without signing in.
|
|
1220
|
+
*/
|
|
1221
|
+
export function authorizationBesideSignIn(server: {
|
|
1222
|
+
signIn?: McpSignIn | null;
|
|
1223
|
+
headers: ReadonlyArray<{ header: string }>;
|
|
1224
|
+
}) {
|
|
1225
|
+
return server.signIn
|
|
1226
|
+
? server.headers.findIndex((entry) => entry.header.toLowerCase() === "authorization")
|
|
1227
|
+
: -1;
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
const manifestMcpServerSchema = z
|
|
1231
|
+
.strictObject({
|
|
1232
|
+
/** Prefixes every tool the server contributes: scripts call `tools.<name>_<tool>()`. */
|
|
1233
|
+
name: identifierSchema,
|
|
1234
|
+
displayName: displayNameSchema.default({}),
|
|
1235
|
+
url: upstreamUrlSchema,
|
|
1236
|
+
headers: declaredHeadersSchema(
|
|
1237
|
+
presentedHeaderSchema.refine(
|
|
1238
|
+
(name) => !mcpTransportHeaders.has(name.toLowerCase()),
|
|
1239
|
+
"a header the MCP transport sets",
|
|
1240
|
+
),
|
|
1241
|
+
headerSourceSchema,
|
|
1242
|
+
),
|
|
1243
|
+
/** Absent, every request carries only its headers. */
|
|
1244
|
+
signIn: mcpSignInSchema.optional(),
|
|
1245
|
+
/** The server's own tool names to offer; empty offers every tool. */
|
|
1246
|
+
include: mcpToolNamesSchema.default([]),
|
|
1247
|
+
exclude: mcpToolNamesSchema.default([]),
|
|
1248
|
+
audience: audienceSchema,
|
|
1249
|
+
overridable: overridable(["displayName", "include", "exclude", "headers", "signIn"]),
|
|
1250
|
+
})
|
|
1251
|
+
.superRefine((server, context) => {
|
|
1252
|
+
const index = authorizationBesideSignIn(server);
|
|
1253
|
+
if (index >= 0) {
|
|
1254
|
+
context.addIssue({
|
|
1255
|
+
code: "custom",
|
|
1256
|
+
path: ["headers", index, "header"],
|
|
1257
|
+
message: "the sign-in's token travels under Authorization",
|
|
1258
|
+
});
|
|
1259
|
+
}
|
|
1260
|
+
});
|
|
1217
1261
|
export type ManifestMcpServer = z.input<typeof manifestMcpServerSchema>;
|
|
1218
1262
|
|
|
1219
1263
|
/**
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { REQUEST_LOCATION_TOOL } from "../client-tools/index";
|
|
2
|
+
import { CONNECT_ACCOUNT_TOOL, REQUEST_LOCATION_TOOL } from "../client-tools/index";
|
|
3
3
|
import { backendErrorSchema } from "../errors/index";
|
|
4
4
|
import { pendingApprovalSchema, TRIGGER_ANSWER_TOOL_NAME } from "../triggers/index";
|
|
5
5
|
import { isRecord, readJsonPointer } from "../json/index";
|
|
@@ -139,6 +139,7 @@ export const BUILT_IN_TOOL_NAMES = [
|
|
|
139
139
|
"searchRegistries",
|
|
140
140
|
"searchThreadHistory",
|
|
141
141
|
REQUEST_LOCATION_TOOL,
|
|
142
|
+
CONNECT_ACCOUNT_TOOL,
|
|
142
143
|
TRIGGER_ANSWER_TOOL_NAME,
|
|
143
144
|
] as const;
|
|
144
145
|
|
|
@@ -164,6 +164,47 @@ export type ClientToolBinding = {
|
|
|
164
164
|
embedOrigin: string;
|
|
165
165
|
};
|
|
166
166
|
|
|
167
|
+
/**
|
|
168
|
+
* How the widget connects an end user's account for a `connectAccount`
|
|
169
|
+
* call, stamped on the call: where the engine starts a sign-in, at the URL
|
|
170
|
+
* browsers reach it, the agent it is for, and each server the call may
|
|
171
|
+
* name, by name: its display name, and the scope it still needs when the
|
|
172
|
+
* account is connected but lacks access.
|
|
173
|
+
*/
|
|
174
|
+
export type ConnectAccountBinding = {
|
|
175
|
+
startUrl: string;
|
|
176
|
+
agent: string;
|
|
177
|
+
servers: Record<string, { displayName: Partial<LocalizedText>; scope?: string }>;
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Response of `GET /connections/:server?call=<toolCallId>`: where the
|
|
182
|
+
* caller's own account stands with the agent's MCP server of that name, for
|
|
183
|
+
* the card of that `connectAccount` call. `connected` with all a sign-in
|
|
184
|
+
* may ask for; `pending` while it is not connected, or lacks access a
|
|
185
|
+
* sign-in may still grant; `refused` once the card's own sign-in came back
|
|
186
|
+
* without it, or no sign-in may ask again.
|
|
187
|
+
*/
|
|
188
|
+
export type AccountConnection = {
|
|
189
|
+
status: "connected" | "pending" | "refused";
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* The form `POST /oauth/start` takes, posted into the window a sign-in runs
|
|
194
|
+
* in with its bearer in the body, so no link can start one: the console's
|
|
195
|
+
* operator token and the server's id for the shared connection.
|
|
196
|
+
*/
|
|
197
|
+
export type SharedSignInStart = { operatorToken: string; mcpServerId: string };
|
|
198
|
+
|
|
199
|
+
/** An end user's own start: their token, their agent, the server's name, and the `connectAccount` call it answers. */
|
|
200
|
+
export type EndUserSignInStart = {
|
|
201
|
+
token: string;
|
|
202
|
+
agent: string;
|
|
203
|
+
server: string;
|
|
204
|
+
threadId: string;
|
|
205
|
+
toolCallId: string;
|
|
206
|
+
};
|
|
207
|
+
|
|
167
208
|
/**
|
|
168
209
|
* What Cortex offers this agent's clients, fetched once at widget startup.
|
|
169
210
|
* The widget hides UI for anything absent; Cortex enforces each.
|
|
@@ -263,6 +304,8 @@ export type ToolCallMetadata = {
|
|
|
263
304
|
answeredBy?: "client";
|
|
264
305
|
/** Set beside `answeredBy` on a call of a client tool with an embedded page: the widget mounts the embed for it. */
|
|
265
306
|
embed?: ClientToolBinding;
|
|
307
|
+
/** Set beside `answeredBy` on a `connectAccount` call: the widget starts the sign-in with it. */
|
|
308
|
+
connect?: ConnectAccountBinding;
|
|
266
309
|
};
|
|
267
310
|
|
|
268
311
|
/** The `metadata.cortex` a tool call carries, when Cortex stamped one. */
|
package/src/scaffold/files.ts
CHANGED
|
@@ -28,7 +28,7 @@ import { applyPorts, type Ports } from "@/scaffold/ports";
|
|
|
28
28
|
* `@m6d/cortex` range below are stamped by scripts/sync-package-versions.ts
|
|
29
29
|
* before the CLI publishes, so a scaffold pins what was current then.
|
|
30
30
|
*/
|
|
31
|
-
const CORTEX_VERSION = "0.
|
|
31
|
+
const CORTEX_VERSION = "0.21.0";
|
|
32
32
|
|
|
33
33
|
/** This machine as Cortex's containers name it: the one private host the stack's egress policy admits. */
|
|
34
34
|
export const DOCKER_HOST = "host.docker.internal";
|
|
@@ -147,7 +147,7 @@ function packageJson({ name, datasets, setup }: Project) {
|
|
|
147
147
|
verify: "bun run format:check && bun run lint && bun run spellcheck && bun run check && bun run audit",
|
|
148
148
|
},
|
|
149
149
|
dependencies: {
|
|
150
|
-
"@m6d/cortex": "~0.
|
|
150
|
+
"@m6d/cortex": "~0.9.0",
|
|
151
151
|
// drizzle 1.0 is in beta; this is the build `@m6d/cortex/drizzle` is tested against.
|
|
152
152
|
...(datasets ? { "drizzle-orm": "1.0.0-beta.15-859cf75" } : {}),
|
|
153
153
|
hono: "^4.12.3",
|