@protobase/server 0.45.22 → 0.46.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.
@@ -24,6 +24,7 @@ import { realtimePath, restPath, storagePath, supabaseFunctionsPath, supabasePat
24
24
  import { goTruePath } from "./gotrue/keys.js";
25
25
  import { authBasePath } from "./better-auth/auth-base-path.js";
26
26
  import { mcpDiscovery } from "./better-auth/mcp/discovery-routes.js";
27
+ import { mcpApprovals } from "./mcp/mcp-approval-store.js";
27
28
  import { checkMcpWrites, rolesWithoutApproval } from "./mcp/mcp-writes.js";
28
29
  import { mcpAuthenticator } from "./mcp/mcp-authenticator.js";
29
30
  import { mcpToolContext } from "./mcp/mcp-context.js";
@@ -320,6 +321,7 @@ const createAdmin = ({ resources: appResources, views: appViews = [], pages = []
320
321
  sql: options.mcp !== false && options.mcp?.sql === true,
321
322
  writes
322
323
  });
324
+ const approvals = mcpApprovals();
323
325
  const server = (session) => mcpServer({
324
326
  info: {
325
327
  name: appName,
@@ -328,7 +330,8 @@ const createAdmin = ({ resources: appResources, views: appViews = [], pages = []
328
330
  tools: tools(session),
329
331
  context: mcpToolContext(deps, meta, session),
330
332
  report,
331
- rolesWithoutApproval: rolesWithoutApproval(writes)
333
+ rolesWithoutApproval: rolesWithoutApproval(writes),
334
+ approvals
332
335
  });
333
336
  mcpRoutes(app, {
334
337
  path: auth.mcp.path,
package/dist/index.d.ts CHANGED
@@ -123,6 +123,7 @@ import { WriteRecordToolsInput } from "./assistant/write-tool-limits.js";
123
123
  import { createRecordTool, updateRecordTool, writeRecordTools } from "./assistant/write-record-tools.js";
124
124
  import { deleteRecordTool } from "./assistant/delete-record-tool.js";
125
125
  import { batchWriteTool } from "./assistant/batch-write-tool.js";
126
+ import { McpApprovals, mcpApprovals } from "./mcp/mcp-approval-store.js";
126
127
  import { McpServerInput, mcpServer } from "./mcp/mcp-server.js";
127
128
  import { McpToolsInput, mcpTools } from "./mcp/mcp-tools.js";
128
129
  import { McpGateInput, mcpGate, sessionOf } from "./mcp/mcp-gate.js";
@@ -160,4 +161,4 @@ import { checkJobsSchema, jobsSchema, jobsSchemaMigration } from "./jobs/schema.
160
161
  import { MobileRelease, ReleaseFile, ServedReleaseFile, composeRelease, releaseManifest } from "./mobile-release/release.js";
161
162
  import { MobileReleaseAppInput, OfferedRelease, mobileReleaseApp } from "./mobile-release/release-app.js";
162
163
  import { mobileSigningKeyVariable, readSigningKey } from "./mobile-release/signing-key.js";
163
- export { type Admin, type AdminAuth, type AdminOptions, type ApiFunction, type ApiKeys, type ApiKeysOptions, type ApnsEnvironment, type AppIcons, type AppIdentity, type AppPoolConfig, type ApplyOptions, type ApplyResult, type ApprovalAnswer, type ApprovalRequest, type AssistantBackend, type AssistantOptions, type AssistantSettings, type AssistantTool, type AuditEvent, type AuditQueue, type Authenticator, type BatchOperation, type BatchResult, type BetterAuthAuthenticator, type BetterAuthAuthenticatorOptions, type Caller, type CallerOptions, type CapturedRow, type CascadingKey, type ChatMessage, type ChatTextPart, type CheckAiInput, type CleanupEntry, type CleanupReport, type CleanupSchedule, type CoalescedChange, type CommentItem, type CommentUsers, type Completion, type CompletionOptions, ConnectionsBusy, type Conversation, type ConversationRecord, type ConversationStore, type ConversationsOptions, type CorsOptions, type CorsOrigins, type CreateAdminInput, type CreateAuthOptions, type Device, type DeviceColumn, type DeviceKind, type DeviceStore, DeviceStoreError, type DrainResult, type EmailComponent, type EmailOptions, type EmailTemplate, type EncryptionStatus, type EncryptionTools, type EncryptionToolsInput, type EnqueueOptions, type ErrorReporter, type ErrorReportingOptions, type ErrorReportingSettings, type EventData, type EventsOptions, FatalJobError, type FieldApplied, type FieldStatus, type FileCleanupInput, type FileLimits, type FileObject, type FileProvider, type FileRef, type FileStore, type FileVariantObject, type FilesOptions, type FunctionApp, type FunctionCaller, type FunctionContext, type FunctionEnv, type FunctionHandler, type FunctionOptions, type FunctionRecords, type FunctionSocket, type FunctionSource, type HistoryColumn, type HistoryOptions, type HistoryPosition, type HistoryStore, HistoryStoreError, HttpProblem, type IssueTokenOptions, type JobContext, type JobDefinition, type JobOptions, type JobsRuntime, type JsonObject, type JsonValue, type JwtOptions, type JwtVerifyOptions, type KeyFunctionContext, type LinkedAccount, type ListParams, type LocalFilesOptions, type MailMessage, type Mailer, type MapLayer, type MapOptions, type MapSettings, type McpGateInput, type McpHandlerInput, type McpRoutesInput, type McpServerInput, type McpToolsInput, type McpWrites, type MemberInput, MissingCredentials, type MobileConfig, type MobileRelease, type MobileReleaseAppInput, ModelError, type ModelSettings, type NewDevice, type NewNotification, type NewOrganization, type NewUser, type NotificationRecipients, NotificationSendError, type NotificationsOptions, type OfferedRelease, type OperatorProvider, type OrganizationRole, type OrganizationsOptions, type PgModule, type PipelineHook, type PlatformSignIn, type PluginOption, type PluginRef, type PngIcon, type ProjectConfig, type ProtectedFunctionOptions, type PublicFunctionContext, type PublicFunctionEnv, type PublicFunctionHandler, type PublicFunctionOptions, type PushAnswer, type PushMessage, PushMessageError, type PushSender, type QueryOptions, type QueryResult, type ReadableResources, type ReasoningDetail, type ReasoningEffort, type RebindOption, type RecordKeyInput, type RecordPage, type ReleaseFile, type RenderedEmail, type ResourceChange, type ResourceSource, type RoleDefinition, type RolesInput, type RunContext, type RuntimeOptions, type S3Bucket, type S3FilesOptions, type S3ListPage, type S3Object, type ScanGuardMode, type ScheduledDelete, type SendNotification, type ServedReleaseFile, type Session, type SessionOrganization, type Sharp, type SignInPolicy, type SignInRule, type SignatureOptions, type SocketEvents, type SourceOption, type SourceRef, type StaffAccessRule, type StaffSignIn, type StandardSchema, type StandardWebhookOptions, type StartJobsOptions, type StoreFileInput, type StoredMember, type StoredNotification, type StoredObject, type StoredOrganization, type StoredUser, type StrayValues, type SubscriptionDefinition, type SubscriptionEvent, type SubscriptionOptions, type TenantValue, type ToolAnnotations, type ToolCall, type ToolContext, type ToolRecord, type ToolRecords, type ToolSpec, type TrustedApp, type TurnOptions, type UnreadableEntry, type Urgency, type User, type UserCreatedHook, type VapidSettings, type ViewSource, type WebPushFields, type WebPushOptions, type WebhooksOptions, type WidgetOptions, type WriteEvent, type WriteOperation, type WriteRecordToolsInput, addAppRoleToOwners, addMember, apiKeyCaller, apiKeySettings, apiKeyVariables, appPool, aspectRatio, assistantProtocolRoutes, assistantRoles, assistantSettings, assistantVariables, authSchemaMigration, authStoreTables, backgroundSettled, badRequest, batchWriteTool, beforeSignInPluginsVariable, betterAuthAuthenticator, betterAuthTokens, capSocketMessages, changesMigration, checkAi, checkAuthSchema, checkChanges, checkComments, checkComputed, checkJobsSchema, checkNotificationsSchema, checkQueryRunner, checkSourceNames, checkViewQueries, checkWebhooksSchema, coalesce, commentsMigration, composeRelease, computedMigration, configExports, consoleAuditQueue, conversationKey, createAdmin, createAuth, createConversations, createEncryptionTools, createFileCleanup, createOrganization, createRecordTool, createUser, defaultAppName, defaultHistoryPage, defaultSignInPolicy, defineConfig, defineEmail, defineFunction, defineJob, defineTool, deleteRecordTool, deleteUser, deviceIdParam, deviceStore, devicesPath, errorReportingSettings, errorReportingVariable, eventMatcher, fileConversationStore, fileSize, fileType, forbidden, formatUri, functionApp, generateVapidKeys, getRecordTool, goTruePath, handleDroppedConnections, hasUsers, historyIdParam, historyIdPattern, historyLimits, historyPath, historyStore, honorSocketMessageCaps, imageSize, isBackgroundWork, isCaptured, isJobDefinition, isOAuthWellKnownPath, isSchedule, issueToken, jobDefinitions, jobsDrainApp, jobsSchema, jobsSchemaMigration, jwtAuthenticator, jwtVerifier, limitedFileStore, listOrganizations, listRecordsTool, listResourcesTool, listUsers, localFileStore, localFiles, longQueryPool, longQueryTimeoutMs, mapSettings, mapStyleUrl, mapStyleVariable, maxDevicesPerUser, maxHistoryPage, maxMessageBytes, maxRecipients, maxTokenTtlSeconds, maxTtlSeconds, maxUnreadCount, maxWidgetPropsBytes, mcpGate, mcpHandler, mcpRoutes, mcpServer, mcpToolContext, mcpTools, mergeConfig, migrationCommand, mobileReleaseApp, mobileSigningKeyVariable, newWebhookSecret, normalizeRoles, notFound, notificationSenders, notifications, notificationsMigration, oauthWellKnownPaths, objectKey, parseColumns, parseRoles, passwordLength, pluginOptions, pluginsBeforeSignIn, pluginsFor, pluginsVariable, pngIcons, poolWaitMs, postgresDeviceStore, postgresHistoryStore, processor, pushMessage, queryRunnerMigration, readDbConnections, readJson, readOnlyQueryTool, readOperatorSettings, readOutbox, readPlatformSignIn, readRecordTools, readSigningKey, readVapidSettings, readWriteQueryTool, readableResources, realtimeLimits, recordOf, releaseManifest, removeDevicesWithUsers, removeHistoryWithUsers, removeMember, renderVariant, requestApproval, requireEnv, resourceChange, roleChoices, runInBackground, runReadOnlyQuery, runReadWriteQuery, runTurn, runtimeUrl, runtimeVariable, s3FileStore, s3Files, seesAssistant, seesRuntime, sendNotification, sessionOf, setMemberRoles, setUserBanned, setUserRole, shownAppName, signInPolicyMethods, socketMessageCapOf, sourceOptions, sourcesFor, sourcesVariable, startErrorReporting, startJobs, storageCleanupSchedule, storageProvider, storeFile, storeUsage, streamChatCompletion, subscription, supabasePath, thumbHash, unauthorized, updateRecordTool, urgencies, vapidPrivateKeyVariable, vapidPublicKeyVariable, vapidSubjectVariable, variantPath, verifySignature, verifyStandardWebhook, waitUntil, wantsLongQueries, webManifestPath, webPushSender, webauthnPath, webhooks, webhooksMigration, widget, withCacheBreakpoints, withTrustedApps, writeRecordTools };
164
+ export { type Admin, type AdminAuth, type AdminOptions, type ApiFunction, type ApiKeys, type ApiKeysOptions, type ApnsEnvironment, type AppIcons, type AppIdentity, type AppPoolConfig, type ApplyOptions, type ApplyResult, type ApprovalAnswer, type ApprovalRequest, type AssistantBackend, type AssistantOptions, type AssistantSettings, type AssistantTool, type AuditEvent, type AuditQueue, type Authenticator, type BatchOperation, type BatchResult, type BetterAuthAuthenticator, type BetterAuthAuthenticatorOptions, type Caller, type CallerOptions, type CapturedRow, type CascadingKey, type ChatMessage, type ChatTextPart, type CheckAiInput, type CleanupEntry, type CleanupReport, type CleanupSchedule, type CoalescedChange, type CommentItem, type CommentUsers, type Completion, type CompletionOptions, ConnectionsBusy, type Conversation, type ConversationRecord, type ConversationStore, type ConversationsOptions, type CorsOptions, type CorsOrigins, type CreateAdminInput, type CreateAuthOptions, type Device, type DeviceColumn, type DeviceKind, type DeviceStore, DeviceStoreError, type DrainResult, type EmailComponent, type EmailOptions, type EmailTemplate, type EncryptionStatus, type EncryptionTools, type EncryptionToolsInput, type EnqueueOptions, type ErrorReporter, type ErrorReportingOptions, type ErrorReportingSettings, type EventData, type EventsOptions, FatalJobError, type FieldApplied, type FieldStatus, type FileCleanupInput, type FileLimits, type FileObject, type FileProvider, type FileRef, type FileStore, type FileVariantObject, type FilesOptions, type FunctionApp, type FunctionCaller, type FunctionContext, type FunctionEnv, type FunctionHandler, type FunctionOptions, type FunctionRecords, type FunctionSocket, type FunctionSource, type HistoryColumn, type HistoryOptions, type HistoryPosition, type HistoryStore, HistoryStoreError, HttpProblem, type IssueTokenOptions, type JobContext, type JobDefinition, type JobOptions, type JobsRuntime, type JsonObject, type JsonValue, type JwtOptions, type JwtVerifyOptions, type KeyFunctionContext, type LinkedAccount, type ListParams, type LocalFilesOptions, type MailMessage, type Mailer, type MapLayer, type MapOptions, type MapSettings, type McpApprovals, type McpGateInput, type McpHandlerInput, type McpRoutesInput, type McpServerInput, type McpToolsInput, type McpWrites, type MemberInput, MissingCredentials, type MobileConfig, type MobileRelease, type MobileReleaseAppInput, ModelError, type ModelSettings, type NewDevice, type NewNotification, type NewOrganization, type NewUser, type NotificationRecipients, NotificationSendError, type NotificationsOptions, type OfferedRelease, type OperatorProvider, type OrganizationRole, type OrganizationsOptions, type PgModule, type PipelineHook, type PlatformSignIn, type PluginOption, type PluginRef, type PngIcon, type ProjectConfig, type ProtectedFunctionOptions, type PublicFunctionContext, type PublicFunctionEnv, type PublicFunctionHandler, type PublicFunctionOptions, type PushAnswer, type PushMessage, PushMessageError, type PushSender, type QueryOptions, type QueryResult, type ReadableResources, type ReasoningDetail, type ReasoningEffort, type RebindOption, type RecordKeyInput, type RecordPage, type ReleaseFile, type RenderedEmail, type ResourceChange, type ResourceSource, type RoleDefinition, type RolesInput, type RunContext, type RuntimeOptions, type S3Bucket, type S3FilesOptions, type S3ListPage, type S3Object, type ScanGuardMode, type ScheduledDelete, type SendNotification, type ServedReleaseFile, type Session, type SessionOrganization, type Sharp, type SignInPolicy, type SignInRule, type SignatureOptions, type SocketEvents, type SourceOption, type SourceRef, type StaffAccessRule, type StaffSignIn, type StandardSchema, type StandardWebhookOptions, type StartJobsOptions, type StoreFileInput, type StoredMember, type StoredNotification, type StoredObject, type StoredOrganization, type StoredUser, type StrayValues, type SubscriptionDefinition, type SubscriptionEvent, type SubscriptionOptions, type TenantValue, type ToolAnnotations, type ToolCall, type ToolContext, type ToolRecord, type ToolRecords, type ToolSpec, type TrustedApp, type TurnOptions, type UnreadableEntry, type Urgency, type User, type UserCreatedHook, type VapidSettings, type ViewSource, type WebPushFields, type WebPushOptions, type WebhooksOptions, type WidgetOptions, type WriteEvent, type WriteOperation, type WriteRecordToolsInput, addAppRoleToOwners, addMember, apiKeyCaller, apiKeySettings, apiKeyVariables, appPool, aspectRatio, assistantProtocolRoutes, assistantRoles, assistantSettings, assistantVariables, authSchemaMigration, authStoreTables, backgroundSettled, badRequest, batchWriteTool, beforeSignInPluginsVariable, betterAuthAuthenticator, betterAuthTokens, capSocketMessages, changesMigration, checkAi, checkAuthSchema, checkChanges, checkComments, checkComputed, checkJobsSchema, checkNotificationsSchema, checkQueryRunner, checkSourceNames, checkViewQueries, checkWebhooksSchema, coalesce, commentsMigration, composeRelease, computedMigration, configExports, consoleAuditQueue, conversationKey, createAdmin, createAuth, createConversations, createEncryptionTools, createFileCleanup, createOrganization, createRecordTool, createUser, defaultAppName, defaultHistoryPage, defaultSignInPolicy, defineConfig, defineEmail, defineFunction, defineJob, defineTool, deleteRecordTool, deleteUser, deviceIdParam, deviceStore, devicesPath, errorReportingSettings, errorReportingVariable, eventMatcher, fileConversationStore, fileSize, fileType, forbidden, formatUri, functionApp, generateVapidKeys, getRecordTool, goTruePath, handleDroppedConnections, hasUsers, historyIdParam, historyIdPattern, historyLimits, historyPath, historyStore, honorSocketMessageCaps, imageSize, isBackgroundWork, isCaptured, isJobDefinition, isOAuthWellKnownPath, isSchedule, issueToken, jobDefinitions, jobsDrainApp, jobsSchema, jobsSchemaMigration, jwtAuthenticator, jwtVerifier, limitedFileStore, listOrganizations, listRecordsTool, listResourcesTool, listUsers, localFileStore, localFiles, longQueryPool, longQueryTimeoutMs, mapSettings, mapStyleUrl, mapStyleVariable, maxDevicesPerUser, maxHistoryPage, maxMessageBytes, maxRecipients, maxTokenTtlSeconds, maxTtlSeconds, maxUnreadCount, maxWidgetPropsBytes, mcpApprovals, mcpGate, mcpHandler, mcpRoutes, mcpServer, mcpToolContext, mcpTools, mergeConfig, migrationCommand, mobileReleaseApp, mobileSigningKeyVariable, newWebhookSecret, normalizeRoles, notFound, notificationSenders, notifications, notificationsMigration, oauthWellKnownPaths, objectKey, parseColumns, parseRoles, passwordLength, pluginOptions, pluginsBeforeSignIn, pluginsFor, pluginsVariable, pngIcons, poolWaitMs, postgresDeviceStore, postgresHistoryStore, processor, pushMessage, queryRunnerMigration, readDbConnections, readJson, readOnlyQueryTool, readOperatorSettings, readOutbox, readPlatformSignIn, readRecordTools, readSigningKey, readVapidSettings, readWriteQueryTool, readableResources, realtimeLimits, recordOf, releaseManifest, removeDevicesWithUsers, removeHistoryWithUsers, removeMember, renderVariant, requestApproval, requireEnv, resourceChange, roleChoices, runInBackground, runReadOnlyQuery, runReadWriteQuery, runTurn, runtimeUrl, runtimeVariable, s3FileStore, s3Files, seesAssistant, seesRuntime, sendNotification, sessionOf, setMemberRoles, setUserBanned, setUserRole, shownAppName, signInPolicyMethods, socketMessageCapOf, sourceOptions, sourcesFor, sourcesVariable, startErrorReporting, startJobs, storageCleanupSchedule, storageProvider, storeFile, storeUsage, streamChatCompletion, subscription, supabasePath, thumbHash, unauthorized, updateRecordTool, urgencies, vapidPrivateKeyVariable, vapidPublicKeyVariable, vapidSubjectVariable, variantPath, verifySignature, verifyStandardWebhook, waitUntil, wantsLongQueries, webManifestPath, webPushSender, webauthnPath, webhooks, webhooksMigration, widget, withCacheBreakpoints, withTrustedApps, writeRecordTools };
package/dist/index.js CHANGED
@@ -35,6 +35,7 @@ import { passwordLength } from "./better-auth/new-account-password.js";
35
35
  import { readOperatorSettings } from "./better-auth/operator-provider.js";
36
36
  import { readPlatformSignIn } from "./better-auth/platform-sign-in.js";
37
37
  import { createAuth, normalizeRoles } from "./better-auth/create-auth.js";
38
+ import { mcpApprovals } from "./mcp/mcp-approval-store.js";
38
39
  import { mcpToolContext } from "./mcp/mcp-context.js";
39
40
  import { mcpGate, sessionOf } from "./mcp/mcp-gate.js";
40
41
  import { mcpHandler } from "./mcp/mcp-handler.js";
@@ -122,4 +123,4 @@ import { jobsDrainApp } from "./jobs/drain-route.js";
122
123
  import { composeRelease, releaseManifest } from "./mobile-release/release.js";
123
124
  import { mobileReleaseApp } from "./mobile-release/release-app.js";
124
125
  import { mobileSigningKeyVariable, readSigningKey } from "./mobile-release/signing-key.js";
125
- export { ConnectionsBusy, DeviceStoreError, FatalJobError, HistoryStoreError, HttpProblem, MissingCredentials, ModelError, NotificationSendError, PushMessageError, addAppRoleToOwners, addMember, apiKeyCaller, apiKeySettings, apiKeyVariables, appPool, aspectRatio, assistantProtocolRoutes, assistantRoles, assistantSettings, assistantVariables, authSchemaMigration, authStoreTables, backgroundSettled, badRequest, batchWriteTool, beforeSignInPluginsVariable, betterAuthAuthenticator, betterAuthTokens, capSocketMessages, changesMigration, checkAi, checkAuthSchema, checkChanges, checkComments, checkComputed, checkJobsSchema, checkNotificationsSchema, checkQueryRunner, checkSourceNames, checkViewQueries, checkWebhooksSchema, coalesce, commentsMigration, composeRelease, computedMigration, configExports, consoleAuditQueue, conversationKey, createAdmin, createAuth, createConversations, createEncryptionTools, createFileCleanup, createOrganization, createRecordTool, createUser, defaultAppName, defaultHistoryPage, defaultSignInPolicy, defineConfig, defineEmail, defineFunction, defineJob, defineTool, deleteRecordTool, deleteUser, deviceIdParam, deviceStore, devicesPath, errorReportingSettings, errorReportingVariable, eventMatcher, fileConversationStore, fileSize, fileType, forbidden, formatUri, functionApp, generateVapidKeys, getRecordTool, goTruePath, handleDroppedConnections, hasUsers, historyIdParam, historyIdPattern, historyLimits, historyPath, historyStore, honorSocketMessageCaps, imageSize, isBackgroundWork, isCaptured, isJobDefinition, isOAuthWellKnownPath, isSchedule, issueToken, jobDefinitions, jobsDrainApp, jobsSchema, jobsSchemaMigration, jwtAuthenticator, jwtVerifier, limitedFileStore, listOrganizations, listRecordsTool, listResourcesTool, listUsers, localFileStore, localFiles, longQueryPool, longQueryTimeoutMs, mapSettings, mapStyleUrl, mapStyleVariable, maxDevicesPerUser, maxHistoryPage, maxMessageBytes, maxRecipients, maxTokenTtlSeconds, maxTtlSeconds, maxUnreadCount, maxWidgetPropsBytes, mcpGate, mcpHandler, mcpRoutes, mcpServer, mcpToolContext, mcpTools, mergeConfig, migrationCommand, mobileReleaseApp, mobileSigningKeyVariable, newWebhookSecret, normalizeRoles, notFound, notificationSenders, notifications, notificationsMigration, oauthWellKnownPaths, objectKey, parseColumns, parseRoles, passwordLength, pluginOptions, pluginsBeforeSignIn, pluginsFor, pluginsVariable, pngIcons, poolWaitMs, postgresDeviceStore, postgresHistoryStore, processor, pushMessage, queryRunnerMigration, readDbConnections, readJson, readOnlyQueryTool, readOperatorSettings, readOutbox, readPlatformSignIn, readRecordTools, readSigningKey, readVapidSettings, readWriteQueryTool, readableResources, realtimeLimits, recordOf, releaseManifest, removeDevicesWithUsers, removeHistoryWithUsers, removeMember, renderVariant, requestApproval, requireEnv, resourceChange, roleChoices, runInBackground, runReadOnlyQuery, runReadWriteQuery, runTurn, runtimeUrl, runtimeVariable, s3FileStore, s3Files, seesAssistant, seesRuntime, sendNotification, sessionOf, setMemberRoles, setUserBanned, setUserRole, shownAppName, signInPolicyMethods, socketMessageCapOf, sourceOptions, sourcesFor, sourcesVariable, startErrorReporting, startJobs, storageCleanupSchedule, storageProvider, storeFile, storeUsage, streamChatCompletion, subscription, supabasePath, thumbHash, unauthorized, updateRecordTool, urgencies, vapidPrivateKeyVariable, vapidPublicKeyVariable, vapidSubjectVariable, variantPath, verifySignature, verifyStandardWebhook, waitUntil, wantsLongQueries, webManifestPath, webPushSender, webauthnPath, webhooks, webhooksMigration, widget, withCacheBreakpoints, withTrustedApps, writeRecordTools };
126
+ export { ConnectionsBusy, DeviceStoreError, FatalJobError, HistoryStoreError, HttpProblem, MissingCredentials, ModelError, NotificationSendError, PushMessageError, addAppRoleToOwners, addMember, apiKeyCaller, apiKeySettings, apiKeyVariables, appPool, aspectRatio, assistantProtocolRoutes, assistantRoles, assistantSettings, assistantVariables, authSchemaMigration, authStoreTables, backgroundSettled, badRequest, batchWriteTool, beforeSignInPluginsVariable, betterAuthAuthenticator, betterAuthTokens, capSocketMessages, changesMigration, checkAi, checkAuthSchema, checkChanges, checkComments, checkComputed, checkJobsSchema, checkNotificationsSchema, checkQueryRunner, checkSourceNames, checkViewQueries, checkWebhooksSchema, coalesce, commentsMigration, composeRelease, computedMigration, configExports, consoleAuditQueue, conversationKey, createAdmin, createAuth, createConversations, createEncryptionTools, createFileCleanup, createOrganization, createRecordTool, createUser, defaultAppName, defaultHistoryPage, defaultSignInPolicy, defineConfig, defineEmail, defineFunction, defineJob, defineTool, deleteRecordTool, deleteUser, deviceIdParam, deviceStore, devicesPath, errorReportingSettings, errorReportingVariable, eventMatcher, fileConversationStore, fileSize, fileType, forbidden, formatUri, functionApp, generateVapidKeys, getRecordTool, goTruePath, handleDroppedConnections, hasUsers, historyIdParam, historyIdPattern, historyLimits, historyPath, historyStore, honorSocketMessageCaps, imageSize, isBackgroundWork, isCaptured, isJobDefinition, isOAuthWellKnownPath, isSchedule, issueToken, jobDefinitions, jobsDrainApp, jobsSchema, jobsSchemaMigration, jwtAuthenticator, jwtVerifier, limitedFileStore, listOrganizations, listRecordsTool, listResourcesTool, listUsers, localFileStore, localFiles, longQueryPool, longQueryTimeoutMs, mapSettings, mapStyleUrl, mapStyleVariable, maxDevicesPerUser, maxHistoryPage, maxMessageBytes, maxRecipients, maxTokenTtlSeconds, maxTtlSeconds, maxUnreadCount, maxWidgetPropsBytes, mcpApprovals, mcpGate, mcpHandler, mcpRoutes, mcpServer, mcpToolContext, mcpTools, mergeConfig, migrationCommand, mobileReleaseApp, mobileSigningKeyVariable, newWebhookSecret, normalizeRoles, notFound, notificationSenders, notifications, notificationsMigration, oauthWellKnownPaths, objectKey, parseColumns, parseRoles, passwordLength, pluginOptions, pluginsBeforeSignIn, pluginsFor, pluginsVariable, pngIcons, poolWaitMs, postgresDeviceStore, postgresHistoryStore, processor, pushMessage, queryRunnerMigration, readDbConnections, readJson, readOnlyQueryTool, readOperatorSettings, readOutbox, readPlatformSignIn, readRecordTools, readSigningKey, readVapidSettings, readWriteQueryTool, readableResources, realtimeLimits, recordOf, releaseManifest, removeDevicesWithUsers, removeHistoryWithUsers, removeMember, renderVariant, requestApproval, requireEnv, resourceChange, roleChoices, runInBackground, runReadOnlyQuery, runReadWriteQuery, runTurn, runtimeUrl, runtimeVariable, s3FileStore, s3Files, seesAssistant, seesRuntime, sendNotification, sessionOf, setMemberRoles, setUserBanned, setUserRole, shownAppName, signInPolicyMethods, socketMessageCapOf, sourceOptions, sourcesFor, sourcesVariable, startErrorReporting, startJobs, storageCleanupSchedule, storageProvider, storeFile, storeUsage, streamChatCompletion, subscription, supabasePath, thumbHash, unauthorized, updateRecordTool, urgencies, vapidPrivateKeyVariable, vapidPublicKeyVariable, vapidSubjectVariable, variantPath, verifySignature, verifyStandardWebhook, waitUntil, wantsLongQueries, webManifestPath, webPushSender, webauthnPath, webhooks, webhooksMigration, widget, withCacheBreakpoints, withTrustedApps, writeRecordTools };
@@ -0,0 +1,21 @@
1
+ //#region src/mcp/mcp-approval-store.d.ts
2
+ /** An approval asked for: the `requestState` the client echoes with the answer, and the nonce the answer is filed under. */
3
+ export type AskedApproval = {
4
+ state: string;
5
+ nonce: string;
6
+ };
7
+ /**
8
+ * The approvals an MCP server asked for and has not had answered: `ask` mints the state for the call `call` hashes to,
9
+ * in the scope `scope` hashes to, and `check` takes an echoed state back once, resolving to its nonce only when this
10
+ * store minted it for that very call and scope, it is unexpired and no other check took it first.
11
+ */
12
+ export type McpApprovals = {
13
+ ask: (scope: string, call: string) => Promise<AskedApproval>;
14
+ check: (scope: string, call: string, state: unknown) => Promise<string | undefined>;
15
+ };
16
+ /**
17
+ * The approvals of an app's MCP endpoint: built once at startup, with a key of its own no one else holds, drawn when
18
+ * it first asks, so a server that never asks draws nothing.
19
+ */
20
+ export declare const mcpApprovals: () => McpApprovals;
21
+ //#endregion
@@ -0,0 +1,93 @@
1
+ import { createRequestStateCodec } from "@modelcontextprotocol/server";
2
+ //#region src/mcp/mcp-approval-store.ts
3
+ /** How long an approval asked for can be answered: RFC 6749's longest life for a one-time code. */
4
+ const approvalLifetime = 6e5;
5
+ const purpose = "mcp.approve";
6
+ const isPayload = (value) => {
7
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
8
+ const { purpose: named, nonce, call, expiresAt } = value;
9
+ return named === purpose && typeof nonce === "string" && nonce !== "" && typeof call === "string" && /^[0-9a-f]{64}$/.test(call) && typeof expiresAt === "number" && Number.isFinite(expiresAt);
10
+ };
11
+ const newNonce = () => Buffer.from(crypto.getRandomValues(/* @__PURE__ */ new Uint8Array(16))).toString("base64url");
12
+ /** An approval store with the key given; `mcpApprovals` is the one a server uses. */
13
+ const approvalStore = ({ key, now = () => Date.now(), perScope = 50, total = 1e4 }) => {
14
+ const codec = createRequestStateCodec({
15
+ key,
16
+ ttlSeconds: approvalLifetime / 1e3
17
+ });
18
+ const scopes = /* @__PURE__ */ new Map();
19
+ let waiting = 0;
20
+ const dropExpired = () => {
21
+ const at = now();
22
+ for (const [scope, issued] of scopes) {
23
+ for (const [nonce, { expiresAt }] of issued) {
24
+ if (expiresAt > at) continue;
25
+ issued.delete(nonce);
26
+ waiting -= 1;
27
+ }
28
+ if (issued.size === 0) scopes.delete(scope);
29
+ }
30
+ };
31
+ const take = (scope, nonce) => {
32
+ const issued = scopes.get(scope);
33
+ const entry = issued?.get(nonce);
34
+ if (!issued || !entry) return void 0;
35
+ issued.delete(nonce);
36
+ waiting -= 1;
37
+ if (issued.size === 0) scopes.delete(scope);
38
+ return entry;
39
+ };
40
+ return {
41
+ ask: async (scope, call) => {
42
+ const nonce = newNonce();
43
+ const expiresAt = now() + approvalLifetime;
44
+ const state = await codec.mint({
45
+ purpose,
46
+ nonce,
47
+ call,
48
+ expiresAt
49
+ });
50
+ dropExpired();
51
+ if (waiting >= total) throw new Error("Too many approvals are waiting for an answer");
52
+ const issued = scopes.get(scope) ?? /* @__PURE__ */ new Map();
53
+ if (issued.size >= perScope) {
54
+ issued.delete(issued.keys().next().value);
55
+ waiting -= 1;
56
+ }
57
+ issued.set(nonce, {
58
+ call,
59
+ expiresAt
60
+ });
61
+ scopes.set(scope, issued);
62
+ waiting += 1;
63
+ return {
64
+ state,
65
+ nonce
66
+ };
67
+ },
68
+ check: async (scope, call, state) => {
69
+ if (typeof state !== "string") return void 0;
70
+ const payload = await codec.verify(state, void 0).catch(() => void 0);
71
+ if (!isPayload(payload)) return void 0;
72
+ const issued = take(scope, payload.nonce);
73
+ if (!issued) return void 0;
74
+ if (issued.call !== call || payload.call !== call || payload.expiresAt !== issued.expiresAt) return void 0;
75
+ if (now() >= issued.expiresAt) return void 0;
76
+ return payload.nonce;
77
+ }
78
+ };
79
+ };
80
+ /**
81
+ * The approvals of an app's MCP endpoint: built once at startup, with a key of its own no one else holds, drawn when
82
+ * it first asks, so a server that never asks draws nothing.
83
+ */
84
+ const mcpApprovals = () => {
85
+ let store;
86
+ const opened = () => store ??= approvalStore({ key: crypto.getRandomValues(/* @__PURE__ */ new Uint8Array(32)) });
87
+ return {
88
+ ask: (scope, call) => opened().ask(scope, call),
89
+ check: async (scope, call, state) => store ? store.check(scope, call, state) : void 0
90
+ };
91
+ };
92
+ //#endregion
93
+ export { approvalLifetime, approvalStore, mcpApprovals };
@@ -8,15 +8,21 @@ var ApprovalNeeded = class extends Error {
8
8
  this.request = request;
9
9
  }
10
10
  };
11
+ /** The key of the answer to the approval asked with `nonce`: an answer filed under any other counts for nothing. */
12
+ const approvalKey = (nonce) => `approve-${nonce}`;
11
13
  /**
12
- * `ToolContext.approve` over MCP, from the answers a retried call carries: approved only when the user accepted and
13
- * ticked `approve`, rejected on any other answer, and `ApprovalNeeded` when there is none yet.
14
+ * `ToolContext.approve` over MCP, from the answer a retried call carries to the approval asked for that very call:
15
+ * `ApprovalNeeded` when it carries no state that counts, or no answer under that state's key; approved only when the
16
+ * user accepted and ticked `approve`; rejected on any other answer.
14
17
  */
15
- const mcpApproval = (inputResponses) => (request) => {
16
- const answer = inputResponse(inputResponses, "approve");
17
- if (answer.kind === "missing") return Promise.reject(new ApprovalNeeded(request));
18
- if (answer.kind === "elicit" && answer.action === "accept" && answer.content?.approve === true) return Promise.resolve("approved");
19
- return Promise.resolve("rejected");
18
+ const mcpApproval = ({ inputResponses, requestState, check }) => async (request) => {
19
+ if (requestState === void 0) throw new ApprovalNeeded(request);
20
+ const nonce = await check(requestState);
21
+ if (nonce === void 0) throw new ApprovalNeeded(request);
22
+ const answer = inputResponse(inputResponses, approvalKey(nonce));
23
+ if (answer.kind === "missing") throw new ApprovalNeeded(request);
24
+ if (answer.kind === "elicit" && answer.action === "accept" && answer.content?.approve === true) return "approved";
25
+ return "rejected";
20
26
  };
21
27
  /**
22
28
  * Whether `roles`, the roles the signed-in user holds, include one of `exempt`, whose MCP writes need no approval: an
@@ -44,21 +50,26 @@ const cannotAsk = {
44
50
  };
45
51
  /**
46
52
  * The answer to a call whose tool asked for approval: a form asking the user to approve `request` when the client can
47
- * show one, else a refusal. A 2025-protocol request declares no capabilities per request, so it is always refused.
53
+ * show one, filed under the nonce of the state `ask` mints for the call, else a refusal. A 2025-protocol request
54
+ * declares no capabilities per request, so it is always refused.
48
55
  */
49
- const approvalAsked = (request, capabilities) => {
56
+ const approvalAsked = async (request, capabilities, ask) => {
50
57
  if (!asksForms(capabilities)) return cannotAsk;
51
- return inputRequired({ inputRequests: { approve: inputRequired.elicit({
52
- message: messageOf(request),
53
- requestedSchema: {
54
- type: "object",
55
- properties: { approve: {
56
- type: "boolean",
57
- title: request.approveLabel ?? "Approve"
58
- } },
59
- required: ["approve"]
60
- }
61
- }) } });
58
+ const { state, nonce } = await ask();
59
+ return inputRequired({
60
+ inputRequests: { [approvalKey(nonce)]: inputRequired.elicit({
61
+ message: messageOf(request),
62
+ requestedSchema: {
63
+ type: "object",
64
+ properties: { approve: {
65
+ type: "boolean",
66
+ title: request.approveLabel ?? "Approve"
67
+ } },
68
+ required: ["approve"]
69
+ }
70
+ }) },
71
+ requestState: state
72
+ });
62
73
  };
63
74
  //#endregion
64
- export { ApprovalNeeded, approvalAsked, approvedUnasked, mcpApproval, writesUnasked };
75
+ export { ApprovalNeeded, approvalAsked, approvalKey, approvedUnasked, mcpApproval, writesUnasked };
@@ -0,0 +1,47 @@
1
+ import { createHash } from "node:crypto";
2
+ //#region src/mcp/mcp-call-hash.ts
3
+ const maxDepth = 32;
4
+ const isPlainObject = (value) => {
5
+ const prototype = Object.getPrototypeOf(value);
6
+ return prototype === Object.prototype || prototype === null;
7
+ };
8
+ /**
9
+ * `value` as JSON with each object's keys sorted and arrays kept in order, written out rather than rebuilt, so an own
10
+ * `__proto__` key is encoded like any other. Throws on what JSON cannot hold and past `maxDepth` levels of nesting.
11
+ */
12
+ const canonicalJson = (value, depth = 0) => {
13
+ if (depth > maxDepth) throw new Error(`The call nests deeper than ${maxDepth} levels`);
14
+ if (value === null || typeof value === "boolean" || typeof value === "string") return JSON.stringify(value);
15
+ if (typeof value === "number") {
16
+ if (!Number.isFinite(value)) throw new Error("The call holds a number JSON cannot");
17
+ return JSON.stringify(value);
18
+ }
19
+ if (Array.isArray(value)) return `[${Array.from(value, (item) => canonicalJson(item, depth + 1)).join(",")}]`;
20
+ if (typeof value === "object" && isPlainObject(value)) return `{${Object.entries(value).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([key, item]) => `${JSON.stringify(key)}:${canonicalJson(item, depth + 1)}`).join(",")}}`;
21
+ throw new Error("The call holds a value JSON cannot");
22
+ };
23
+ const isUserId = (id) => typeof id === "string" && id !== "" || typeof id === "number" && Number.isFinite(id);
24
+ /** The scope of a call by `session` to `resource`; throws for a session without a user to bind an approval to. */
25
+ const callScope = (session, resource) => {
26
+ if (session.anonymous || !isUserId(session.user.id)) throw new Error("An approval needs a signed-in user");
27
+ if (!resource) throw new Error("An approval needs the MCP resource the token was verified for");
28
+ return {
29
+ resource: resource.href,
30
+ user: session.user.id,
31
+ tenant: session.tenant ?? null,
32
+ organization: session.organization?.id ?? null,
33
+ client: session.client?.id ?? null
34
+ };
35
+ };
36
+ const sha256 = (text) => createHash("sha256").update(text).digest("hex");
37
+ /** The SHA-256 of a tool call: its tool, its arguments and its scope. */
38
+ const callHash = (tool, args, scope) => sha256(canonicalJson([
39
+ "mcp.approve.call.v1",
40
+ tool,
41
+ args,
42
+ scope
43
+ ]));
44
+ /** The SHA-256 of `scope` alone, which the approvals waiting for an answer are kept under. */
45
+ const scopeHash = (scope) => sha256(canonicalJson(["mcp.approve.scope.v1", scope]));
46
+ //#endregion
47
+ export { callHash, callScope, canonicalJson, scopeHash };
@@ -1,4 +1,5 @@
1
1
  import { AssistantTool, ToolContext } from "../assistant/tools.js";
2
+ import { McpApprovals } from "./mcp-approval-store.js";
2
3
  import { McpServer } from "@modelcontextprotocol/server";
3
4
  //#region src/mcp/mcp-server.d.ts
4
5
  export type McpServerInput = {
@@ -15,11 +16,14 @@ export type McpServerInput = {
15
16
  report: (error: unknown) => void;
16
17
  /** The roles whose calls are approved without asking the client: a user holding one of them is never asked. None by default. */
17
18
  rolesWithoutApproval?: readonly string[];
19
+ /** The approvals asked for and not yet answered, `mcpApprovals()` built once for every server the endpoint builds. */
20
+ approvals: McpApprovals;
18
21
  };
19
22
  /**
20
23
  * An MCP server offering `tools` as MCP tools, each with its JSON Schema, its hints, and the text it returns. A tool
21
- * that asks for approval makes the call ask the client's user, and runs again from the start with their answer; for a
22
- * user holding one of `rolesWithoutApproval` it is approved at once, and the client is never asked.
24
+ * that asks for approval makes the call ask the client's user, and runs again from the start with their answer, which
25
+ * counts only for the call it was asked for, once, while it lasts; for a user holding one of `rolesWithoutApproval` it
26
+ * is approved at once, and the client is never asked.
23
27
  */
24
- export declare const mcpServer: ({ info, tools, context, report, rolesWithoutApproval }: McpServerInput) => McpServer;
28
+ export declare const mcpServer: ({ info, tools, context, report, rolesWithoutApproval, approvals }: McpServerInput) => McpServer;
25
29
  //#endregion
@@ -1,6 +1,7 @@
1
1
  import { isStatementTimeout } from "../statement-timeout.js";
2
2
  import { checkedTools } from "../assistant/checked-tools.js";
3
3
  import { ApprovalNeeded, approvalAsked, approvedUnasked, mcpApproval, writesUnasked } from "./mcp-approval.js";
4
+ import { callHash, callScope, scopeHash } from "./mcp-call-hash.js";
4
5
  import { CLIENT_CAPABILITIES_META_KEY, McpServer, fromJsonSchema } from "@modelcontextprotocol/server";
5
6
  //#region src/mcp/mcp-server.ts
6
7
  const capabilitiesOf = (envelope) => envelope?.[CLIENT_CAPABILITIES_META_KEY];
@@ -28,10 +29,11 @@ const stopped = (tool) => ({
28
29
  });
29
30
  /**
30
31
  * An MCP server offering `tools` as MCP tools, each with its JSON Schema, its hints, and the text it returns. A tool
31
- * that asks for approval makes the call ask the client's user, and runs again from the start with their answer; for a
32
- * user holding one of `rolesWithoutApproval` it is approved at once, and the client is never asked.
32
+ * that asks for approval makes the call ask the client's user, and runs again from the start with their answer, which
33
+ * counts only for the call it was asked for, once, while it lasts; for a user holding one of `rolesWithoutApproval` it
34
+ * is approved at once, and the client is never asked.
33
35
  */
34
- const mcpServer = ({ info, tools, context, report, rolesWithoutApproval = [] }) => {
36
+ const mcpServer = ({ info, tools, context, report, rolesWithoutApproval = [], approvals }) => {
35
37
  const server = new McpServer(info);
36
38
  const unasked = writesUnasked(rolesWithoutApproval, context.session.user.roles);
37
39
  for (const tool of checkedTools(tools)) {
@@ -40,17 +42,41 @@ const mcpServer = ({ info, tools, context, report, rolesWithoutApproval = [] })
40
42
  inputSchema: inputSchemaOf(tool.parameters),
41
43
  ...tool.annotations && { annotations: tool.annotations }
42
44
  };
43
- server.registerTool(tool.name, config, async (args, ctx) => {
45
+ const answer = async (args, ctx) => {
46
+ const bound = () => {
47
+ const scope = callScope(context.session, ctx.http?.authInfo?.resource);
48
+ return {
49
+ scope: scopeHash(scope),
50
+ call: callHash(tool.name, args, scope)
51
+ };
52
+ };
53
+ const check = (state) => {
54
+ const { scope, call } = bound();
55
+ return approvals.check(scope, call, state);
56
+ };
57
+ const approve = unasked ? approvedUnasked : mcpApproval({
58
+ inputResponses: ctx.mcpReq.inputResponses,
59
+ requestState: ctx.mcpReq.requestState(),
60
+ check
61
+ });
44
62
  try {
45
63
  return { content: [{
46
64
  type: "text",
47
- text: await tool.run(args, {
65
+ text: await tool.run(structuredClone(args), {
48
66
  ...context,
49
- approve: unasked ? approvedUnasked : mcpApproval(ctx.mcpReq.inputResponses)
67
+ approve
50
68
  })
51
69
  }] };
52
70
  } catch (error) {
53
- if (error instanceof ApprovalNeeded) return approvalAsked(error.request, capabilitiesOf(ctx.mcpReq.envelope));
71
+ if (!(error instanceof ApprovalNeeded)) throw error;
72
+ const { scope, call } = bound();
73
+ return approvalAsked(error.request, capabilitiesOf(ctx.mcpReq.envelope), () => approvals.ask(scope, call));
74
+ }
75
+ };
76
+ server.registerTool(tool.name, config, async (args, ctx) => {
77
+ try {
78
+ return await answer(args, ctx);
79
+ } catch (error) {
54
80
  report(error);
55
81
  return isStatementTimeout(error) ? stopped(tool.name) : failed;
56
82
  }
package/dist/package.js CHANGED
@@ -1,4 +1,4 @@
1
1
  //#region package.json
2
- var version = "0.45.22";
2
+ var version = "0.46.0";
3
3
  //#endregion
4
4
  export { version };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@protobase/server",
3
- "version": "0.45.22",
3
+ "version": "0.46.0",
4
4
  "description": "The Protobase REST API as a Hono app, with Better Auth sign-in.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -36,9 +36,9 @@
36
36
  "@better-auth/passkey": "^1.7.12",
37
37
  "@better-auth/sso": "^1.7.12",
38
38
  "@modelcontextprotocol/server": "^2.3.1",
39
- "@protobase/layout": "0.45.22",
40
- "@protobase/query": "0.45.22",
41
- "@protobase/schema": "0.45.22",
39
+ "@protobase/layout": "0.46.0",
40
+ "@protobase/query": "0.46.0",
41
+ "@protobase/schema": "0.46.0",
42
42
  "@react-email/render": "^2.1.0",
43
43
  "@scalar/hono-api-reference": "^0.12.10",
44
44
  "@sentry/core": "^11.7.0",