@read-frog/api-contract 0.15.0 → 0.16.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": "@read-frog/api-contract",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
4
4
  "private": false,
5
5
  "repository": {
6
6
  "type": "git",
@@ -26,7 +26,7 @@
26
26
  "dependencies": {
27
27
  "@orpc/contract": "^1.14.6",
28
28
  "zod": "^4.5.4",
29
- "@read-frog/definitions": "0.5.0"
29
+ "@read-frog/definitions": "0.5.1"
30
30
  },
31
31
  "devDependencies": {
32
32
  "@types/node": "^26.1.0",
@@ -0,0 +1,26 @@
1
+ import { oc } from "@orpc/contract";
2
+ import {
3
+ mcpConnectionListOutputSchema,
4
+ mcpConnectionRevokeInputSchema,
5
+ mcpConnectionRevokeOutputSchema,
6
+ } from "#/schemas/mcp-connection";
7
+
8
+ export const mcpConnectionContract = {
9
+ list: oc
10
+ .route({
11
+ method: "GET",
12
+ path: "/mcp-connections",
13
+ summary: "List the OAuth clients the caller has authorized",
14
+ tags: ["MCP Connections"],
15
+ })
16
+ .output(mcpConnectionListOutputSchema),
17
+ revoke: oc
18
+ .route({
19
+ method: "POST",
20
+ path: "/mcp-connections/revoke",
21
+ summary: "Withdraw a client's authorization and revoke its tokens",
22
+ tags: ["MCP Connections"],
23
+ })
24
+ .input(mcpConnectionRevokeInputSchema)
25
+ .output(mcpConnectionRevokeOutputSchema),
26
+ };
package/src/index.ts CHANGED
@@ -5,6 +5,7 @@ import { cardContract } from "./contracts/card";
5
5
  import { cardTemplateContract } from "./contracts/card-template";
6
6
  import { ebookContract } from "./contracts/ebook";
7
7
  import { hostedAiContract } from "./contracts/hosted-ai";
8
+ import { mcpConnectionContract } from "./contracts/mcp-connection";
8
9
  import { notebaseContract } from "./contracts/notebase";
9
10
  import { notebaseColumnContract } from "./contracts/notebase-column";
10
11
  import { notebaseRowContract } from "./contracts/notebase-row";
@@ -20,6 +21,7 @@ export const contract = {
20
21
  cardTemplate: cardTemplateContract,
21
22
  ebook: ebookContract,
22
23
  hostedAi: hostedAiContract,
24
+ mcpConnection: mcpConnectionContract,
23
25
  notebase: notebaseContract,
24
26
  notebaseColumn: notebaseColumnContract,
25
27
  notebaseRow: notebaseRowContract,
@@ -38,6 +40,7 @@ export * from "./schemas/billing";
38
40
  export * from "./schemas/card";
39
41
  export * from "./schemas/ebook";
40
42
  export * from "./schemas/hosted-ai";
43
+ export * from "./schemas/mcp-connection";
41
44
  export * from "./schemas/notebase";
42
45
  export * from "./schemas/notebase-column";
43
46
  export * from "./schemas/notebase-row";
@@ -18,6 +18,32 @@ export type BillingSubscriptionStatus = z.infer<typeof billingSubscriptionStatus
18
18
  export const billingPlanSchema = z.enum(["free", "pro", "ultra"]);
19
19
  export type BillingPlan = z.infer<typeof billingPlanSchema>;
20
20
 
21
+ /**
22
+ * Whether a plan may use a feature, given the plans that feature allows.
23
+ *
24
+ * An explicit set rather than a minimum tier: there is no rank table to keep in
25
+ * step with the plan enum, and any combination is expressible by writing it
26
+ * down. The cost is that nothing derives from it automatically -- a feature's
27
+ * pricing-card placement is a companion edit, noted at each `*_ALLOWED_PLANS`.
28
+ */
29
+ export function isPlanAllowed(plan: BillingPlan, allowed: readonly BillingPlan[]): boolean {
30
+ return allowed.includes(plan);
31
+ }
32
+
33
+ /**
34
+ * Plans that may use the remote MCP server.
35
+ *
36
+ * Read by the server-side gate AND by the website's copy, so the page cannot
37
+ * promise access the API refuses. `satisfies` keeps the literal tuple type so
38
+ * the UI can render the plan names from this constant instead of hardcoding
39
+ * them.
40
+ *
41
+ * Companion edit when this changes: the feature bullet in
42
+ * `apps/www/src/routes/pricing.tsx` lives on the lowest allowed plan's card
43
+ * (higher cards inherit it through their "everything in <lower plan>" line).
44
+ */
45
+ export const MCP_ALLOWED_PLANS = ["ultra"] as const satisfies readonly BillingPlan[];
46
+
21
47
  // Plans a user can actively check out into (the free tier has no price).
22
48
  export const billingCheckoutPlanSchema = z.enum(["pro", "ultra"]);
23
49
  export type BillingCheckoutPlan = z.infer<typeof billingCheckoutPlanSchema>;
@@ -0,0 +1,28 @@
1
+ import { z } from "zod";
2
+
3
+ export const mcpConnectionSchema = z
4
+ .object({
5
+ /** OAuth client identifier. For a CIMD client this is an https URL. */
6
+ clientId: z.string(),
7
+ /** The client's registered display name, absent when it never gave one. */
8
+ clientName: z.string().nullable(),
9
+ scopes: z.array(z.string()),
10
+ requestedUserInfoClaims: z.array(z.string()),
11
+ grantedAt: z.date().nullable(),
12
+ updatedAt: z.date().nullable(),
13
+ /**
14
+ * When this client last obtained an access token — the closest honest
15
+ * proxy for "last used" the OAuth tables record. A token is issued on
16
+ * connect and on every refresh, so an idle client's value stops advancing.
17
+ */
18
+ lastAuthorizedAt: z.date().nullable(),
19
+ })
20
+ .strict();
21
+ export type McpConnection = z.infer<typeof mcpConnectionSchema>;
22
+
23
+ export const mcpConnectionListOutputSchema = z.array(mcpConnectionSchema);
24
+
25
+ export const mcpConnectionRevokeInputSchema = z.object({ clientId: z.string().min(1) }).strict();
26
+ export type McpConnectionRevokeInput = z.infer<typeof mcpConnectionRevokeInputSchema>;
27
+
28
+ export const mcpConnectionRevokeOutputSchema = z.object({ clientId: z.string() }).strict();