@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@m6d/cortex-cli",
3
- "version": "0.21.0",
3
+ "version": "0.22.0",
4
4
  "description": "Scaffold Cortex projects and manage a Cortex deployment",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -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.
@@ -10,6 +10,7 @@ export {
10
10
  canCarrySecret,
11
11
  egressAllowedHosts,
12
12
  isEgressAllowedHost,
13
+ isLoopback,
13
14
  isPrivateAddress,
14
15
  normalizeHost,
15
16
  } from "./url";
@@ -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 named credential, with a display name (a text per locale, `{}` for none), over a stored secret: `static` presents the secret's value; `oauth2-client-credentials` exchanges it, as the client secret of `clientId`, for a bearer at `tokenUrl` (https, or http on loopback or on a host `EGRESS_ALLOWED_HOSTS` admits). A credential's name and kind never change. Needs `secrets:write` too: whatever presents the credential sends its secret. */
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
- secretName: string;
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
- secretName: string;
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, and how many rows name it with the first few (`usedBy`). */
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
- secretName: string;
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
- /** Change a credential's display name, the secret it composes and, for an OAuth2 one, its token URL, client id and scope. Applies to the next call of every tool, dataset host and trigger naming it. Changing the secret or the token URL needs `secrets:write` too (`secret_binding_forbidden`): either sends a stored secret somewhere new. */
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
- secretName: string;
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
- /** One MCP server by id with its url, headers, tool filters, and sharing. */
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 and headers (each with a prefix and its value from a credential, the run token, a variable or a constant) apply live without a publish. */
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
- const manifestMcpServerSchema = z.strictObject({
1200
- /** Prefixes every tool the server contributes: scripts call `tools.<name>_<tool>()`. */
1201
- name: identifierSchema,
1202
- displayName: displayNameSchema.default({}),
1203
- url: upstreamUrlSchema,
1204
- headers: declaredHeadersSchema(
1205
- presentedHeaderSchema.refine(
1206
- (name) => !mcpTransportHeaders.has(name.toLowerCase()),
1207
- "a header the MCP transport sets",
1208
- ),
1209
- headerSourceSchema,
1210
- ),
1211
- /** The server's own tool names to offer; empty offers every tool. */
1212
- include: mcpToolNamesSchema.default([]),
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. */
@@ -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.20.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.8.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",