@softure-ai/mcp-access 0.2.8 → 0.3.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.
Files changed (153) hide show
  1. package/CHANGELOG.md +71 -0
  2. package/README.md +270 -19
  3. package/dist/cli/run.d.ts +1 -1
  4. package/dist/cli/run.d.ts.map +1 -1
  5. package/dist/cli/run.js +2 -2
  6. package/dist/cli/run.js.map +1 -1
  7. package/dist/contract.d.ts +48 -2
  8. package/dist/contract.d.ts.map +1 -1
  9. package/dist/index.d.ts +96 -3
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +26 -5
  12. package/dist/index.js.map +1 -1
  13. package/dist/jobs/index.d.ts +2 -0
  14. package/dist/jobs/index.d.ts.map +1 -1
  15. package/dist/jobs/index.js +5 -2
  16. package/dist/jobs/index.js.map +1 -1
  17. package/dist/jobs/operation-state.d.ts +58 -0
  18. package/dist/jobs/operation-state.d.ts.map +1 -0
  19. package/dist/jobs/operation-state.js +68 -0
  20. package/dist/jobs/operation-state.js.map +1 -0
  21. package/dist/jobs/operation-tasks.d.ts +61 -0
  22. package/dist/jobs/operation-tasks.d.ts.map +1 -0
  23. package/dist/jobs/operation-tasks.js +161 -0
  24. package/dist/jobs/operation-tasks.js.map +1 -0
  25. package/dist/jobs/operations.d.ts +68 -0
  26. package/dist/jobs/operations.d.ts.map +1 -0
  27. package/dist/jobs/operations.js +110 -0
  28. package/dist/jobs/operations.js.map +1 -0
  29. package/dist/messages/en.d.ts +57 -1
  30. package/dist/messages/en.d.ts.map +1 -1
  31. package/dist/messages/en.js +57 -1
  32. package/dist/messages/en.js.map +1 -1
  33. package/dist/messages/index.d.ts +56 -1
  34. package/dist/messages/index.d.ts.map +1 -1
  35. package/dist/next/issue-token.d.ts +11 -2
  36. package/dist/next/issue-token.d.ts.map +1 -1
  37. package/dist/next/issue-token.js +56 -7
  38. package/dist/next/issue-token.js.map +1 -1
  39. package/dist/next/oauth-page.d.ts +6 -1
  40. package/dist/next/oauth-page.d.ts.map +1 -1
  41. package/dist/next/oauth-page.js +6 -5
  42. package/dist/next/oauth-page.js.map +1 -1
  43. package/dist/next/token-manager-props.d.ts +3 -2
  44. package/dist/next/token-manager-props.d.ts.map +1 -1
  45. package/dist/next/token-manager-props.js +12 -2
  46. package/dist/next/token-manager-props.js.map +1 -1
  47. package/dist/options.d.ts +47 -0
  48. package/dist/options.d.ts.map +1 -1
  49. package/dist/options.js +53 -0
  50. package/dist/options.js.map +1 -1
  51. package/dist/schema.d.ts +214 -0
  52. package/dist/schema.d.ts.map +1 -1
  53. package/dist/schema.js +21 -3
  54. package/dist/schema.js.map +1 -1
  55. package/dist/server/api-tokens.d.ts +64 -0
  56. package/dist/server/api-tokens.d.ts.map +1 -0
  57. package/dist/server/api-tokens.js +176 -0
  58. package/dist/server/api-tokens.js.map +1 -0
  59. package/dist/server/confirmation.d.ts +69 -0
  60. package/dist/server/confirmation.d.ts.map +1 -0
  61. package/dist/server/confirmation.js +220 -0
  62. package/dist/server/confirmation.js.map +1 -0
  63. package/dist/server/endpoint.d.ts.map +1 -1
  64. package/dist/server/endpoint.js +6 -4
  65. package/dist/server/endpoint.js.map +1 -1
  66. package/dist/server/health.d.ts.map +1 -1
  67. package/dist/server/health.js +3 -1
  68. package/dist/server/health.js.map +1 -1
  69. package/dist/server/index.d.ts +5 -2
  70. package/dist/server/index.d.ts.map +1 -1
  71. package/dist/server/index.js +4 -1
  72. package/dist/server/index.js.map +1 -1
  73. package/dist/server/oauth-http.d.ts +5 -0
  74. package/dist/server/oauth-http.d.ts.map +1 -1
  75. package/dist/server/oauth-http.js +11 -7
  76. package/dist/server/oauth-http.js.map +1 -1
  77. package/dist/server/privacy.d.ts +4 -0
  78. package/dist/server/privacy.d.ts.map +1 -1
  79. package/dist/server/privacy.js +5 -1
  80. package/dist/server/privacy.js.map +1 -1
  81. package/dist/server/prune.d.ts +5 -1
  82. package/dist/server/prune.d.ts.map +1 -1
  83. package/dist/server/prune.js +14 -4
  84. package/dist/server/prune.js.map +1 -1
  85. package/dist/server/strict-input.d.ts +3 -1
  86. package/dist/server/strict-input.d.ts.map +1 -1
  87. package/dist/server/strict-input.js +54 -8
  88. package/dist/server/strict-input.js.map +1 -1
  89. package/dist/server/tokens.d.ts +46 -11
  90. package/dist/server/tokens.d.ts.map +1 -1
  91. package/dist/server/tokens.js +46 -24
  92. package/dist/server/tokens.js.map +1 -1
  93. package/dist/server/tools.d.ts +8 -0
  94. package/dist/server/tools.d.ts.map +1 -1
  95. package/dist/server/tools.js +1 -0
  96. package/dist/server/tools.js.map +1 -1
  97. package/dist/server/writes-switch.d.ts +8 -0
  98. package/dist/server/writes-switch.d.ts.map +1 -0
  99. package/dist/server/writes-switch.js +25 -0
  100. package/dist/server/writes-switch.js.map +1 -0
  101. package/dist/stdio/serve.d.ts.map +1 -1
  102. package/dist/stdio/serve.js +5 -7
  103. package/dist/stdio/serve.js.map +1 -1
  104. package/dist/tokens/index.d.ts +6 -0
  105. package/dist/tokens/index.d.ts.map +1 -0
  106. package/dist/tokens/index.js +8 -0
  107. package/dist/tokens/index.js.map +1 -0
  108. package/dist/ui/consent-form.d.ts +2 -2
  109. package/dist/ui/consent-form.d.ts.map +1 -1
  110. package/dist/ui/consent-form.js +3 -2
  111. package/dist/ui/consent-form.js.map +1 -1
  112. package/dist/ui/index.d.ts +1 -1
  113. package/dist/ui/index.d.ts.map +1 -1
  114. package/dist/ui/index.js.map +1 -1
  115. package/dist/ui/token-manager.d.ts +18 -2
  116. package/dist/ui/token-manager.d.ts.map +1 -1
  117. package/dist/ui/token-manager.js +28 -3
  118. package/dist/ui/token-manager.js.map +1 -1
  119. package/migrations/0004_create_used_confirmations.sql +13 -0
  120. package/migrations/0005_add_api_tokens.sql +15 -0
  121. package/migrations/0006_create_operation_tasks.sql +18 -0
  122. package/module.json +10 -4
  123. package/package.json +7 -2
  124. package/src/cli/run.ts +2 -2
  125. package/src/contract.ts +60 -2
  126. package/src/index.ts +39 -4
  127. package/src/jobs/index.ts +41 -2
  128. package/src/jobs/operation-state.ts +119 -0
  129. package/src/jobs/operation-tasks.ts +229 -0
  130. package/src/jobs/operations.ts +213 -0
  131. package/src/messages/en.ts +58 -1
  132. package/src/next/issue-token.ts +64 -9
  133. package/src/next/oauth-page.tsx +12 -6
  134. package/src/next/token-manager-props.ts +15 -4
  135. package/src/options.ts +62 -0
  136. package/src/schema.ts +23 -3
  137. package/src/server/api-tokens.ts +224 -0
  138. package/src/server/confirmation.ts +316 -0
  139. package/src/server/endpoint.ts +6 -4
  140. package/src/server/health.ts +3 -1
  141. package/src/server/index.ts +33 -2
  142. package/src/server/oauth-http.ts +16 -7
  143. package/src/server/privacy.ts +9 -1
  144. package/src/server/prune.ts +17 -4
  145. package/src/server/strict-input.ts +62 -13
  146. package/src/server/tokens.ts +73 -27
  147. package/src/server/tools.ts +9 -0
  148. package/src/server/writes-switch.ts +24 -0
  149. package/src/stdio/serve.ts +5 -7
  150. package/src/tokens/index.ts +22 -0
  151. package/src/ui/consent-form.tsx +12 -8
  152. package/src/ui/index.ts +1 -0
  153. package/src/ui/token-manager.tsx +140 -3
package/dist/schema.d.ts CHANGED
@@ -623,6 +623,69 @@ export declare const accessTokens: import("drizzle-orm/pg-core").PgTableWithColu
623
623
  identity: undefined;
624
624
  generated: undefined;
625
625
  }, {}, {}>;
626
+ kind: import("drizzle-orm/pg-core").PgColumn<{
627
+ name: "kind";
628
+ tableName: "access_tokens";
629
+ dataType: "string";
630
+ columnType: "PgText";
631
+ data: "mcp" | "api";
632
+ driverParam: string;
633
+ notNull: true;
634
+ hasDefault: true;
635
+ isPrimaryKey: false;
636
+ isAutoincrement: false;
637
+ hasRuntimeDefault: false;
638
+ enumValues: [string, ...string[]];
639
+ baseColumn: never;
640
+ identity: undefined;
641
+ generated: undefined;
642
+ }, {}, {
643
+ $type: "mcp" | "api";
644
+ }>;
645
+ scopes: import("drizzle-orm/pg-core").PgColumn<{
646
+ name: "scopes";
647
+ tableName: "access_tokens";
648
+ dataType: "array";
649
+ columnType: "PgArray";
650
+ data: string[];
651
+ driverParam: string | string[];
652
+ notNull: true;
653
+ hasDefault: true;
654
+ isPrimaryKey: false;
655
+ isAutoincrement: false;
656
+ hasRuntimeDefault: false;
657
+ enumValues: [string, ...string[]];
658
+ baseColumn: import("drizzle-orm").Column<{
659
+ name: "scopes";
660
+ tableName: "access_tokens";
661
+ dataType: "string";
662
+ columnType: "PgText";
663
+ data: string;
664
+ driverParam: string;
665
+ notNull: false;
666
+ hasDefault: false;
667
+ isPrimaryKey: false;
668
+ isAutoincrement: false;
669
+ hasRuntimeDefault: false;
670
+ enumValues: [string, ...string[]];
671
+ baseColumn: never;
672
+ identity: undefined;
673
+ generated: undefined;
674
+ }, {}, {}>;
675
+ identity: undefined;
676
+ generated: undefined;
677
+ }, {}, {
678
+ baseBuilder: import("drizzle-orm/pg-core").PgColumnBuilder<{
679
+ name: "scopes";
680
+ dataType: "string";
681
+ columnType: "PgText";
682
+ data: string;
683
+ enumValues: [string, ...string[]];
684
+ driverParam: string;
685
+ }, {}, {}, import("drizzle-orm").ColumnBuilderExtraConfig>;
686
+ size: undefined;
687
+ $type: string[];
688
+ }>;
626
689
  };
627
690
  dialect: "pg";
628
691
  }>;
@@ -684,6 +747,157 @@ export declare const oauthRegistrationWindow: import("drizzle-orm/pg-core").PgTa
684
747
  };
685
748
  dialect: "pg";
686
749
  }>;
750
+ export declare const usedConfirmations: import("drizzle-orm/pg-core").PgTableWithColumns<{
751
+ name: "used_confirmations";
752
+ schema: "mcp";
753
+ columns: {
754
+ tokenHash: import("drizzle-orm/pg-core").PgColumn<{
755
+ name: "token_hash";
756
+ tableName: "used_confirmations";
757
+ dataType: "string";
758
+ columnType: "PgText";
759
+ data: string;
760
+ driverParam: string;
761
+ notNull: true;
762
+ hasDefault: false;
763
+ isPrimaryKey: true;
764
+ isAutoincrement: false;
765
+ hasRuntimeDefault: false;
766
+ enumValues: [string, ...string[]];
767
+ baseColumn: never;
768
+ identity: undefined;
769
+ generated: undefined;
770
+ }, {}, {}>;
771
+ usedAt: import("drizzle-orm/pg-core").PgColumn<{
772
+ name: "used_at";
773
+ tableName: "used_confirmations";
774
+ dataType: "date";
775
+ columnType: "PgTimestamp";
776
+ data: Date;
777
+ driverParam: string;
778
+ notNull: true;
779
+ hasDefault: false;
780
+ isPrimaryKey: false;
781
+ isAutoincrement: false;
782
+ hasRuntimeDefault: false;
783
+ enumValues: undefined;
784
+ baseColumn: never;
785
+ identity: undefined;
786
+ generated: undefined;
787
+ }, {}, {}>;
788
+ expiresAt: import("drizzle-orm/pg-core").PgColumn<{
789
+ name: "expires_at";
790
+ tableName: "used_confirmations";
791
+ dataType: "date";
792
+ columnType: "PgTimestamp";
793
+ data: Date;
794
+ driverParam: string;
795
+ notNull: true;
796
+ hasDefault: false;
797
+ isPrimaryKey: false;
798
+ isAutoincrement: false;
799
+ hasRuntimeDefault: false;
800
+ enumValues: undefined;
801
+ baseColumn: never;
802
+ identity: undefined;
803
+ generated: undefined;
804
+ }, {}, {}>;
805
+ };
806
+ dialect: "pg";
807
+ }>;
808
+ /** MCP Tasks over long operations (#977): a random task id per account and job, until `expiresAt`. */
809
+ export declare const operationTasks: import("drizzle-orm/pg-core").PgTableWithColumns<{
810
+ name: "operation_tasks";
811
+ schema: "mcp";
812
+ columns: {
813
+ taskId: import("drizzle-orm/pg-core").PgColumn<{
814
+ name: "task_id";
815
+ tableName: "operation_tasks";
816
+ dataType: "string";
817
+ columnType: "PgText";
818
+ data: string;
819
+ driverParam: string;
820
+ notNull: true;
821
+ hasDefault: false;
822
+ isPrimaryKey: true;
823
+ isAutoincrement: false;
824
+ hasRuntimeDefault: false;
825
+ enumValues: [string, ...string[]];
826
+ baseColumn: never;
827
+ identity: undefined;
828
+ generated: undefined;
829
+ }, {}, {}>;
830
+ userId: import("drizzle-orm/pg-core").PgColumn<{
831
+ name: "user_id";
832
+ tableName: "operation_tasks";
833
+ dataType: "string";
834
+ columnType: "PgUUID";
835
+ data: string;
836
+ driverParam: string;
837
+ notNull: true;
838
+ hasDefault: false;
839
+ isPrimaryKey: false;
840
+ isAutoincrement: false;
841
+ hasRuntimeDefault: false;
842
+ enumValues: undefined;
843
+ baseColumn: never;
844
+ identity: undefined;
845
+ generated: undefined;
846
+ }, {}, {}>;
847
+ jobId: import("drizzle-orm/pg-core").PgColumn<{
848
+ name: "job_id";
849
+ tableName: "operation_tasks";
850
+ dataType: "bigint";
851
+ columnType: "PgBigInt64";
852
+ data: bigint;
853
+ driverParam: string;
854
+ notNull: true;
855
+ hasDefault: false;
856
+ isPrimaryKey: false;
857
+ isAutoincrement: false;
858
+ hasRuntimeDefault: false;
859
+ enumValues: undefined;
860
+ baseColumn: never;
861
+ identity: undefined;
862
+ generated: undefined;
863
+ }, {}, {}>;
864
+ createdAt: import("drizzle-orm/pg-core").PgColumn<{
865
+ name: "created_at";
866
+ tableName: "operation_tasks";
867
+ dataType: "date";
868
+ columnType: "PgTimestamp";
869
+ data: Date;
870
+ driverParam: string;
871
+ notNull: true;
872
+ hasDefault: false;
873
+ isPrimaryKey: false;
874
+ isAutoincrement: false;
875
+ hasRuntimeDefault: false;
876
+ enumValues: undefined;
877
+ baseColumn: never;
878
+ identity: undefined;
879
+ generated: undefined;
880
+ }, {}, {}>;
881
+ expiresAt: import("drizzle-orm/pg-core").PgColumn<{
882
+ name: "expires_at";
883
+ tableName: "operation_tasks";
884
+ dataType: "date";
885
+ columnType: "PgTimestamp";
886
+ data: Date;
887
+ driverParam: string;
888
+ notNull: true;
889
+ hasDefault: false;
890
+ isPrimaryKey: false;
891
+ isAutoincrement: false;
892
+ hasRuntimeDefault: false;
893
+ enumValues: undefined;
894
+ baseColumn: never;
895
+ identity: undefined;
896
+ generated: undefined;
897
+ }, {}, {}>;
898
+ };
899
+ dialect: "pg";
900
+ }>;
687
901
  export type OAuthClientRow = typeof oauthClients.$inferSelect;
688
902
  export type OAuthTokenEndpointAuthMethod = OAuthClientRow["tokenEndpointAuthMethod"];
689
903
  //# sourceMappingURL=schema.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,SAAS,+CAAkB,CAAC;AAEzC,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAQvB,CAAC;AAEH,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAelC,CAAC;AAEH,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EActB,CAAC;AAEH,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAYvB,CAAC;AAEH,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAIlC,CAAC;AAEH,MAAM,MAAM,cAAc,GAAG,OAAO,YAAY,CAAC,YAAY,CAAC;AAC9D,MAAM,MAAM,4BAA4B,GAAG,cAAc,CAAC,yBAAyB,CAAC,CAAC"}
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,SAAS,+CAAkB,CAAC;AAEzC,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAQvB,CAAC;AAEH,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAelC,CAAC;AAEH,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EActB,CAAC;AAEH,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAevB,CAAC;AAEH,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAIlC,CAAC;AAEH,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAI5B,CAAC;AAEH,sGAAsG;AACtG,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAQzB,CAAC;AAEH,MAAM,MAAM,cAAc,GAAG,OAAO,YAAY,CAAC,YAAY,CAAC;AAC9D,MAAM,MAAM,4BAA4B,GAAG,cAAc,CAAC,yBAAyB,CAAC,CAAC"}
package/dist/schema.js CHANGED
@@ -1,8 +1,8 @@
1
1
  // Drizzle view of the module's tables (migrations/0001_create_access_tokens.sql,
2
- // 0002_create_oauth_grants.sql and 0003_create_oauth_registration_window.sql). The migrations are the
3
- // source of truth; this file only types the queries.
2
+ // 0002_create_oauth_grants.sql, 0003_create_oauth_registration_window.sql, 0004_create_used_confirmations.sql,
3
+ // 0005_add_api_tokens.sql and 0006_create_operation_tasks.sql). The migrations are the source of truth; this file only types the queries.
4
4
  import { users } from "@softure-ai/auth";
5
- import { boolean, jsonb, pgSchema, smallint, text, timestamp, uuid } from "drizzle-orm/pg-core";
5
+ import { bigint, boolean, jsonb, pgSchema, smallint, text, timestamp, uuid } from "drizzle-orm/pg-core";
6
6
  export const mcpSchema = pgSchema("mcp");
7
7
  export const oauthClients = mcpSchema.table("oauth_clients", {
8
8
  id: uuid("id").primaryKey().defaultRandom(),
@@ -56,10 +56,28 @@ export const accessTokens = mcpSchema.table("access_tokens", {
56
56
  expiresAt: timestamp("expires_at", { withTimezone: true }).notNull(),
57
57
  lastUsedAt: timestamp("last_used_at", { withTimezone: true }),
58
58
  grantId: uuid("grant_id").references(() => oauthGrants.id, { onDelete: "cascade" }),
59
+ /** `mcp` opens the MCP endpoint; `api` passes `requireApiToken` with its `scopes` (migration 0005). */
60
+ kind: text("kind").$type().notNull().default("mcp"),
61
+ scopes: text("scopes").array().$type().notNull().default([]),
59
62
  });
60
63
  export const oauthRegistrationWindow = mcpSchema.table("oauth_registration_window", {
61
64
  id: smallint("id").primaryKey().default(1),
62
65
  openedAt: timestamp("opened_at", { withTimezone: true }).notNull(),
63
66
  openUntil: timestamp("open_until", { withTimezone: true }).notNull(),
64
67
  });
68
+ export const usedConfirmations = mcpSchema.table("used_confirmations", {
69
+ tokenHash: text("token_hash").primaryKey(),
70
+ usedAt: timestamp("used_at", { withTimezone: true }).notNull(),
71
+ expiresAt: timestamp("expires_at", { withTimezone: true }).notNull(),
72
+ });
73
+ /** MCP Tasks over long operations (#977): a random task id per account and job, until `expiresAt`. */
74
+ export const operationTasks = mcpSchema.table("operation_tasks", {
75
+ taskId: text("task_id").primaryKey(),
76
+ userId: uuid("user_id")
77
+ .notNull()
78
+ .references(() => users.id, { onDelete: "cascade" }),
79
+ jobId: bigint("job_id", { mode: "bigint" }).notNull(),
80
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull(),
81
+ expiresAt: timestamp("expires_at", { withTimezone: true }).notNull(),
82
+ });
65
83
  //# sourceMappingURL=schema.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"schema.js","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,64 @@
1
+ import { type Err, type Ok } from "@softure-ai/core";
2
+ import type { ApiTokenIdentity, ApiTokenRefusal } from "../contract.js";
3
+ import { type ApiTokenOptions } from "../options.js";
4
+ import { type McpAccessContext } from "./tokens.js";
5
+ /** The bucket counted per client address before the token is looked up. */
6
+ export declare const API_RATE_LIMIT_BUCKET = "api";
7
+ /** The bucket counted per verified token. */
8
+ export declare const API_TOKEN_RATE_LIMIT_BUCKET = "api-token";
9
+ /** The app's API token settings, or null while `apiTokens` is unset. */
10
+ export declare function getApiTokenOptions(ctx: Pick<McpAccessContext, "config">): ApiTokenOptions | null;
11
+ /** Whether a presented value has the shape of one of the app's API tokens: its prefix and 32 base64url bytes. */
12
+ export declare function isApiTokenShape(options: ApiTokenOptions, token: string): boolean;
13
+ export interface IssueApiTokenInput {
14
+ /** The auth user id of the owner, from the session. */
15
+ readonly userId: string;
16
+ readonly name: string;
17
+ /** Scope names from the form: any values; unknown ones are refused. */
18
+ readonly scopes: readonly string[];
19
+ }
20
+ export interface IssuedApiToken {
21
+ readonly id: string;
22
+ /** The plaintext: shown to the owner once, stored nowhere. */
23
+ readonly token: string;
24
+ readonly name: string;
25
+ /** The scopes granted, in the order the app declares them. */
26
+ readonly scopes: readonly string[];
27
+ readonly expiresAt: Date;
28
+ }
29
+ export type IssueApiTokenResult = Ok<IssuedApiToken> | Err<"mcp-access.name_required" | "mcp-access.name_too_long" | "mcp-access.token_limit_reached" | "mcp-access.api_disabled" | "mcp-access.scope_required" | "mcp-access.scope_unknown">;
30
+ /**
31
+ * Issues an API token with the asked scopes. A scope the app does not declare is refused; a write
32
+ * scope is granted only while `allowWrites` is on (left out otherwise), and a token needs at least
33
+ * one scope left. Shares the per-account limit and lock with MCP tokens. Database failures propagate.
34
+ */
35
+ export declare function issueApiToken(ctx: McpAccessContext, input: IssueApiTokenInput): Promise<IssueApiTokenResult>;
36
+ /** The identity behind a valid API token. */
37
+ export interface VerifiedApiToken extends ApiTokenIdentity {
38
+ readonly expiresAt: Date;
39
+ }
40
+ /**
41
+ * The identity behind an API token, or null for anything unknown, revoked, expired, an MCP token or
42
+ * any value without the app's prefix. Write scopes count only while writing is enabled (`allowWrites`
43
+ * and the `mcp-access.writes_enabled` switch). Records the use like an MCP token. Database failures propagate.
44
+ */
45
+ export declare function verifyApiToken(ctx: McpAccessContext, token: string): Promise<VerifiedApiToken | null>;
46
+ export interface RequireApiTokenOptions {
47
+ /** Every scope the route needs; each must be one the app declares in `apiTokens.scopes`. */
48
+ readonly scopes: readonly string[];
49
+ }
50
+ /**
51
+ * Checks a route's request for a valid API token with every scope in `scopes`, in this order:
52
+ * the client is identified, its request counted per address (before any lookup, so a flood of
53
+ * guesses costs nothing), the `Authorization: Bearer` token verified, the request counted per token,
54
+ * the scopes checked. Expected refusals are values, for `apiTokenErrorResponse`; a failed count or
55
+ * lookup is logged and answered `mcp-access.api_unavailable`. A scope the app does not declare, or a
56
+ * missing bucket, is a setup bug and throws.
57
+ */
58
+ export declare function requireApiToken(ctx: McpAccessContext, request: Request, { scopes: required }: RequireApiTokenOptions): Promise<Ok<ApiTokenIdentity> | ApiTokenRefusal>;
59
+ /**
60
+ * The HTTP answer to a refusal of `requireApiToken`: JSON `{ error }` (the code without its module), with
61
+ * `WWW-Authenticate` on 401 and 403 (RFC 6750 §3), `Retry-After` on 429, and never cached.
62
+ */
63
+ export declare function apiTokenErrorResponse(refusal: ApiTokenRefusal): Response;
64
+ //# sourceMappingURL=api-tokens.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-tokens.d.ts","sourceRoot":"","sources":["../../src/server/api-tokens.ts"],"names":[],"mappings":"AAKA,OAAO,EAAqC,KAAK,GAAG,EAAE,KAAK,EAAE,EAAE,MAAM,kBAAkB,CAAC;AAGxF,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACxE,OAAO,EAA8B,KAAK,eAAe,EAAE,MAAM,eAAe,CAAC;AAEjF,OAAO,EAAgD,KAAK,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAGlG,2EAA2E;AAC3E,eAAO,MAAM,qBAAqB,QAAQ,CAAC;AAC3C,6CAA6C;AAC7C,eAAO,MAAM,2BAA2B,cAAc,CAAC;AAKvD,wEAAwE;AACxE,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,IAAI,CAAC,gBAAgB,EAAE,QAAQ,CAAC,GAAG,eAAe,GAAG,IAAI,CAEhG;AAED,iHAAiH;AACjH,wBAAgB,eAAe,CAAC,OAAO,EAAE,eAAe,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAGhF;AAWD,MAAM,WAAW,kBAAkB;IACjC,uDAAuD;IACvD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,uEAAuE;IACvE,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;CACpC;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,8DAA8D;IAC9D,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,8DAA8D;IAC9D,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,QAAQ,CAAC,SAAS,EAAE,IAAI,CAAC;CAC1B;AAED,MAAM,MAAM,mBAAmB,GAC3B,EAAE,CAAC,cAAc,CAAC,GAClB,GAAG,CACC,0BAA0B,GAC1B,0BAA0B,GAC1B,gCAAgC,GAChC,yBAAyB,GACzB,2BAA2B,GAC3B,0BAA0B,CAC7B,CAAC;AAEN;;;;GAIG;AACH,wBAAsB,aAAa,CAAC,GAAG,EAAE,gBAAgB,EAAE,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAiBlH;AAED,6CAA6C;AAC7C,MAAM,WAAW,gBAAiB,SAAQ,gBAAgB;IACxD,QAAQ,CAAC,SAAS,EAAE,IAAI,CAAC;CAC1B;AAED;;;;GAIG;AACH,wBAAsB,cAAc,CAAC,GAAG,EAAE,gBAAgB,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAE3G;AAiBD,MAAM,WAAW,sBAAsB;IACrC,4FAA4F;IAC5F,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;CACpC;AAED;;;;;;;GAOG;AACH,wBAAsB,eAAe,CACnC,GAAG,EAAE,gBAAgB,EACrB,OAAO,EAAE,OAAO,EAChB,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,sBAAsB,GAC3C,OAAO,CAAC,EAAE,CAAC,gBAAgB,CAAC,GAAG,eAAe,CAAC,CAmCjD;AAYD;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,eAAe,GAAG,QAAQ,CAaxE"}
@@ -0,0 +1,176 @@
1
+ // Personal tokens for an app's plain HTTP routes (#961): a phone Shortcut, an automation tool or a
2
+ // script sends `Authorization: Bearer <prefix>…` and the route calls `requireApiToken`. They share the
3
+ // table, hashing, expiry, limit, privacy and prune with the MCP tokens (`tokens.ts`); the `kind`
4
+ // column keeps the two doors apart. Nothing here imports the MCP SDK: `@softure-ai/mcp-access/tokens`
5
+ // is for an app that has no MCP server.
6
+ import { err, errorLogLabel, getModule, ok } from "@softure-ai/core";
7
+ import { randomBytes } from "node:crypto";
8
+ import { consumeRateLimit, identifyClient } from "@softure-ai/security/server";
9
+ import { MAX_PRESENTED_TOKEN_LENGTH } from "../options.js";
10
+ import { getMcpAccessOptions } from "./options.js";
11
+ import { findValidToken, hashAccessToken, insertToken } from "./tokens.js";
12
+ import { isWritingEnabled } from "./writes-switch.js";
13
+ /** The bucket counted per client address before the token is looked up. */
14
+ export const API_RATE_LIMIT_BUCKET = "api";
15
+ /** The bucket counted per verified token. */
16
+ export const API_TOKEN_RATE_LIMIT_BUCKET = "api-token";
17
+ const BEARER_PREFIX = /^Bearer[ \t]+/i;
18
+ const NO_STORE = "no-store";
19
+ /** The app's API token settings, or null while `apiTokens` is unset. */
20
+ export function getApiTokenOptions(ctx) {
21
+ return getMcpAccessOptions(ctx.config).apiTokens ?? null;
22
+ }
23
+ /** Whether a presented value has the shape of one of the app's API tokens: its prefix and 32 base64url bytes. */
24
+ export function isApiTokenShape(options, token) {
25
+ if (token.length > MAX_PRESENTED_TOKEN_LENGTH || !token.startsWith(options.prefix))
26
+ return false;
27
+ return /^[A-Za-z0-9_-]{43}$/.test(token.slice(options.prefix.length));
28
+ }
29
+ /**
30
+ * A new API token: the app's prefix and 32 random bytes. The stored sha256 covers the random part
31
+ * alone (the secret); the prefix is a label that the shape check requires before any lookup.
32
+ */
33
+ function createApiSecret(prefix) {
34
+ const random = randomBytes(32).toString("base64url");
35
+ return { secret: `${prefix}${random}`, secretHash: hashAccessToken(random) };
36
+ }
37
+ /**
38
+ * Issues an API token with the asked scopes. A scope the app does not declare is refused; a write
39
+ * scope is granted only while `allowWrites` is on (left out otherwise), and a token needs at least
40
+ * one scope left. Shares the per-account limit and lock with MCP tokens. Database failures propagate.
41
+ */
42
+ export async function issueApiToken(ctx, input) {
43
+ const apiOptions = getApiTokenOptions(ctx);
44
+ if (apiOptions === null)
45
+ return err("mcp-access.api_disabled");
46
+ const asked = new Set(input.scopes);
47
+ const declared = new Set(apiOptions.scopes.map((scope) => scope.name));
48
+ if ([...asked].some((name) => !declared.has(name)))
49
+ return err("mcp-access.scope_unknown");
50
+ const { allowWrites } = getMcpAccessOptions(ctx.config);
51
+ const granted = apiOptions.scopes.filter((scope) => asked.has(scope.name) && (scope.access === "read" || allowWrites));
52
+ if (granted.length === 0)
53
+ return err("mcp-access.scope_required");
54
+ const scopes = granted.map((scope) => scope.name);
55
+ const canWrite = granted.some((scope) => scope.access === "write");
56
+ const result = await insertToken(ctx, { userId: input.userId, name: input.name, kind: "api", canWrite, scopes, secret: createApiSecret(apiOptions.prefix) });
57
+ if (!result.ok)
58
+ return result;
59
+ const { id, token, name, expiresAt } = result.value;
60
+ return ok({ id, token, name, scopes, expiresAt });
61
+ }
62
+ /**
63
+ * The identity behind an API token, or null for anything unknown, revoked, expired, an MCP token or
64
+ * any value without the app's prefix. Write scopes count only while writing is enabled (`allowWrites`
65
+ * and the `mcp-access.writes_enabled` switch). Records the use like an MCP token. Database failures propagate.
66
+ */
67
+ export async function verifyApiToken(ctx, token) {
68
+ return (await checkApiToken(ctx, token))?.verified ?? null;
69
+ }
70
+ /** A verified token and the write scopes it holds that the writes switch keeps from it now. */
71
+ async function checkApiToken(ctx, token) {
72
+ const apiOptions = getApiTokenOptions(ctx);
73
+ if (apiOptions === null || !isApiTokenShape(apiOptions, token))
74
+ return null;
75
+ const row = await findValidToken(ctx, hashAccessToken(token.slice(apiOptions.prefix.length)), "api");
76
+ if (row === null)
77
+ return null;
78
+ const isWriteAllowed = await isWritingEnabled(ctx);
79
+ const writeScopes = new Set(apiOptions.scopes.filter((scope) => scope.access === "write").map((scope) => scope.name));
80
+ const withheld = isWriteAllowed ? [] : row.scopes.filter((scope) => writeScopes.has(scope));
81
+ return {
82
+ verified: { userId: row.userId, tokenId: row.id, scopes: row.scopes.filter((scope) => !withheld.includes(scope)), expiresAt: row.expiresAt },
83
+ withheld,
84
+ };
85
+ }
86
+ /**
87
+ * Checks a route's request for a valid API token with every scope in `scopes`, in this order:
88
+ * the client is identified, its request counted per address (before any lookup, so a flood of
89
+ * guesses costs nothing), the `Authorization: Bearer` token verified, the request counted per token,
90
+ * the scopes checked. Expected refusals are values, for `apiTokenErrorResponse`; a failed count or
91
+ * lookup is logged and answered `mcp-access.api_unavailable`. A scope the app does not declare, or a
92
+ * missing bucket, is a setup bug and throws.
93
+ */
94
+ export async function requireApiToken(ctx, request, { scopes: required }) {
95
+ const apiOptions = getApiTokenOptions(ctx);
96
+ if (apiOptions === null) {
97
+ throw new Error("@softure-ai/mcp-access: requireApiToken needs apiTokens in mcpAccess({ ... })");
98
+ }
99
+ assertDeclaredScopes(apiOptions, required);
100
+ assertRateLimitBuckets(ctx);
101
+ const client = identifyClient(ctx, request.headers);
102
+ if (!client.ok)
103
+ return client;
104
+ try {
105
+ const byAddress = await consumeRateLimit(ctx, { bucket: API_RATE_LIMIT_BUCKET, key: client.value });
106
+ if (!byAddress.ok)
107
+ return { ...err("security.rate_limited"), retryAfterSeconds: byAddress.retryAfterSeconds };
108
+ const header = request.headers.get("authorization");
109
+ if (header === null || !BEARER_PREFIX.test(header))
110
+ return err("mcp-access.api_token_missing");
111
+ const checked = await checkApiToken(ctx, header.replace(BEARER_PREFIX, "").trim());
112
+ if (checked === null)
113
+ return err("mcp-access.api_token_invalid");
114
+ const { verified, withheld } = checked;
115
+ const byToken = await consumeRateLimit(ctx, { bucket: API_TOKEN_RATE_LIMIT_BUCKET, key: `token:${verified.tokenId}` });
116
+ if (!byToken.ok)
117
+ return { ...err("security.rate_limited"), retryAfterSeconds: byToken.retryAfterSeconds };
118
+ const missingScopes = required.filter((scope) => !verified.scopes.includes(scope));
119
+ if (missingScopes.length > 0) {
120
+ // Write scopes the token holds while writing is off: say so, the token itself is fine.
121
+ const code = missingScopes.every((scope) => withheld.includes(scope)) ? "mcp-access.api_writes_disabled" : "mcp-access.api_scope_missing";
122
+ return { ...err(code), missingScopes };
123
+ }
124
+ return ok({ userId: verified.userId, tokenId: verified.tokenId, scopes: verified.scopes });
125
+ }
126
+ catch (error) {
127
+ // The token is never logged; the operation and the failure label are.
128
+ console.error(`@softure-ai/mcp-access: checking an API token failed: ${errorLogLabel(error)}`);
129
+ return err("mcp-access.api_unavailable");
130
+ }
131
+ }
132
+ const STATUS_BY_CODE = {
133
+ "security.client_unidentified": 400,
134
+ "mcp-access.api_token_missing": 401,
135
+ "mcp-access.api_token_invalid": 401,
136
+ "mcp-access.api_scope_missing": 403,
137
+ "mcp-access.api_writes_disabled": 403,
138
+ "security.rate_limited": 429,
139
+ "mcp-access.api_unavailable": 503,
140
+ };
141
+ /**
142
+ * The HTTP answer to a refusal of `requireApiToken`: JSON `{ error }` (the code without its module), with
143
+ * `WWW-Authenticate` on 401 and 403 (RFC 6750 §3), `Retry-After` on 429, and never cached.
144
+ */
145
+ export function apiTokenErrorResponse(refusal) {
146
+ const status = STATUS_BY_CODE[refusal.error];
147
+ const error = refusal.error.slice(refusal.error.indexOf(".") + 1);
148
+ const headers = new Headers({ "cache-control": NO_STORE });
149
+ if (status === 401) {
150
+ headers.set("www-authenticate", refusal.error === "mcp-access.api_token_missing" ? "Bearer" : 'Bearer error="invalid_token"');
151
+ }
152
+ if ("missingScopes" in refusal) {
153
+ headers.set("www-authenticate", `Bearer error="insufficient_scope", scope="${refusal.missingScopes.join(" ")}"`);
154
+ return Response.json({ error, missingScopes: refusal.missingScopes }, { status, headers });
155
+ }
156
+ if ("retryAfterSeconds" in refusal)
157
+ headers.set("retry-after", String(refusal.retryAfterSeconds));
158
+ return Response.json({ error }, { status, headers });
159
+ }
160
+ function assertDeclaredScopes(apiOptions, required) {
161
+ const declared = new Set(apiOptions.scopes.map((scope) => scope.name));
162
+ const unknown = required.filter((scope) => !declared.has(scope));
163
+ if (unknown.length > 0) {
164
+ throw new Error(`@softure-ai/mcp-access: requireApiToken asks for scopes apiTokens.scopes does not declare: ${unknown.join(", ")}`);
165
+ }
166
+ }
167
+ /** A missing bucket is a setup bug: thrown, so it reaches the log by name instead of a quiet 503. */
168
+ function assertRateLimitBuckets(ctx) {
169
+ const options = getModule(ctx.config, "security")?.options;
170
+ for (const bucket of [API_RATE_LIMIT_BUCKET, API_TOKEN_RATE_LIMIT_BUCKET]) {
171
+ if (options?.buckets?.[bucket] === undefined) {
172
+ throw new Error(`@softure-ai/mcp-access: the security module has no "${bucket}" bucket; spread API_TOKEN_RATE_LIMIT_BUCKETS into security({ buckets })`);
173
+ }
174
+ }
175
+ }
176
+ //# sourceMappingURL=api-tokens.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-tokens.js","sourceRoot":"","sources":["../../src/server/api-tokens.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,69 @@
1
+ import { z } from "zod";
2
+ import type { McpServerIdentity } from "../contract.js";
3
+ import type { McpAccessContext } from "./tokens.js";
4
+ import { type ToolCaller, type ToolCallHook, type ToolErrorHints } from "./tools.js";
5
+ import type { CallToolResult } from "@modelcontextprotocol/server";
6
+ /** The argument that carries the token on the confirmed call. */
7
+ export declare const CONFIRMATION_FIELD = "confirmation";
8
+ /** Signs and verifies confirmation tokens. */
9
+ export declare const CONFIRMATION_SECRET_ENV = "MCP_CONFIRMATION_SECRET";
10
+ /** A shorter secret counts as missing: a guessable key lets anyone mint a confirmation. */
11
+ export declare const MIN_CONFIRMATION_SECRET_LENGTH = 32;
12
+ /** How long a confirmation stays valid by default: fifteen minutes. */
13
+ export declare const DEFAULT_CONFIRMATION_TTL_SECONDS = 900;
14
+ /** The `confirmation` field for the tool's input schema, so a strict server lists and accepts it. */
15
+ export declare const confirmationInput: z.ZodOptional<z.ZodString>;
16
+ /** The stable codes of a refused confirmed call. */
17
+ export type ConfirmationErrorCode = "input.confirmation_invalid" | "input.confirmation_other_tool" | "input.confirmation_other_account" | "input.confirmation_expired" | "input.confirmation_arguments_changed" | "input.confirmation_estimate_changed" | "input.confirmation_used";
18
+ /** What a call would do and cost: `total` is bound to the token; every other field is shown to the person as it is. */
19
+ export interface ConfirmationEstimate {
20
+ readonly total: number;
21
+ }
22
+ /** The answer of a call that needs the person's yes first. */
23
+ export interface ConfirmationRequest<Estimate extends ConfirmationEstimate = ConfirmationEstimate> {
24
+ readonly needsConfirmation: true;
25
+ readonly estimate: Estimate;
26
+ readonly confirmation: string;
27
+ /** ISO 8601. */
28
+ readonly expiresAt: string;
29
+ /** The fixed sentence that tells the assistant to ask the person before calling again. */
30
+ readonly message: string;
31
+ }
32
+ /** The tool's arguments as the SDK passes them, `confirmation` included. */
33
+ export type ConfirmedToolArgs<Args> = Args & {
34
+ readonly confirmation?: string | undefined;
35
+ };
36
+ export interface WithConfirmationOptions<Args, Estimate extends ConfirmationEstimate> {
37
+ /** The module context: the database for the used tokens, the clock for the expiry, the config for the copy. */
38
+ readonly ctx: McpAccessContext;
39
+ /** The request's identity, as the server factory got it: the token is bound to its account and access token. */
40
+ readonly identity: Pick<McpServerIdentity, "userId" | "tokenId">;
41
+ /** The tool's registered name, also its label in the log line and the `onToolCall` record. */
42
+ readonly tool: string;
43
+ /** What the call would do and cost, without doing it. Runs on both calls; the second must give the same total. */
44
+ readonly estimate: (args: Args) => Estimate | Promise<Estimate>;
45
+ /** How long the person has to agree. Default: 900 seconds. */
46
+ readonly ttlSeconds?: number;
47
+ /** Runs the handler at once, without asking, when this answers true, e.g. `(estimate) => estimate.total === 0`. */
48
+ readonly runWithoutConfirmationWhen?: (estimate: Estimate) => boolean;
49
+ /** Default: `process.env.MCP_CONFIRMATION_SECRET`, read on every call. */
50
+ readonly secret?: string;
51
+ /** Texts per error code, as in `withToolErrors`; they also replace the refusal texts of the dictionary. */
52
+ readonly hints?: ToolErrorHints;
53
+ /** Called once after each call with its record (`confirmed` included), as in `withToolErrors`. */
54
+ readonly onToolCall?: ToolCallHook;
55
+ /** Who called, for the record, as in `withToolErrors`. */
56
+ readonly caller?: ToolCaller;
57
+ }
58
+ /**
59
+ * The tool callback for `server.registerTool(name, { inputSchema: { ...fields, confirmation: confirmationInput } },
60
+ * withConfirmation(handler, options))`. Without `confirmation` it runs `estimate` only and answers a
61
+ * `ConfirmationRequest` (or runs the handler when `runWithoutConfirmationWhen` says so); with a valid one it runs
62
+ * the handler with the arguments without `confirmation`. A refusal answers its `input.confirmation_*` text.
63
+ */
64
+ export declare function withConfirmation<Args extends Readonly<Record<string, unknown>>, Estimate extends ConfirmationEstimate>(handler: (args: Args) => unknown, options: WithConfirmationOptions<Args, Estimate>): (args: ConfirmedToolArgs<Args>) => Promise<CallToolResult>;
65
+ /** Deletes the used tokens past their expiry; a daily job through `pruneMcpAccess`. */
66
+ export declare function pruneUsedConfirmations(ctx: McpAccessContext): Promise<number>;
67
+ /** JSON with every object's keys sorted and `undefined` fields left out, so equal arguments give equal text. */
68
+ export declare function toCanonicalJson(value: unknown): string;
69
+ //# sourceMappingURL=confirmation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"confirmation.d.ts","sourceRoot":"","sources":["../../src/server/confirmation.ts"],"names":[],"mappings":"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"}