@protobase/server 0.57.6 → 0.58.1
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/dist/admin-options.d.ts +5 -0
- package/dist/assistant/tools.d.ts +2 -2
- package/dist/create-admin.js +6 -1
- package/dist/encryption/decrypt-field-types.d.ts +25 -0
- package/dist/encryption/decrypt-field.js +63 -0
- package/dist/encryption/envelope-open.js +1 -1
- package/dist/functions/define-function.d.ts +9 -1
- package/dist/functions/routes.js +12 -1
- package/dist/index.d.ts +2 -1
- package/dist/jobs/job-types.d.ts +6 -0
- package/dist/jobs/run-job.js +5 -1
- package/dist/jobs/runtime.d.ts +6 -3
- package/dist/jobs/runtime.js +5 -2
- package/dist/mcp/mcp-authenticator.js +3 -3
- package/dist/mcp/mcp-tools.d.ts +9 -1
- package/dist/mcp/mcp-tools.js +23 -5
- package/dist/package.js +1 -1
- package/dist/types.d.ts +14 -4
- package/package.json +4 -4
package/dist/admin-options.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { MapOptions } from "./maps/map-settings.js";
|
|
|
5
5
|
import { PluginOption } from "./plugins/plugin-settings.js";
|
|
6
6
|
import { SourceOption } from "./sources/source-settings.js";
|
|
7
7
|
import { CorsOrigins } from "./api-cors.js";
|
|
8
|
+
import { AssistantTool } from "./assistant/tools.js";
|
|
8
9
|
import { AssistantOptions } from "./assistant/assistant-settings.js";
|
|
9
10
|
import { LocaleOption } from "./locale/locale-option.js";
|
|
10
11
|
import { McpWrites } from "./mcp/mcp-writes.js";
|
|
@@ -75,12 +76,16 @@ export type AdminOptions = {
|
|
|
75
76
|
* `writes: { rolesWithoutApproval: ['service'] }` lets a user holding one of those roles, such as a service account
|
|
76
77
|
* with no person present, write without the approval; any other user is still asked. Each must be a role the app's
|
|
77
78
|
* users can hold.
|
|
79
|
+
* `tools`: the app's own tools, offered after the built-in ones whatever `writes` says, each run as the caller under
|
|
80
|
+
* their access rules. One that changes data asks through `approve`, which `rolesWithoutApproval` skips too; one that
|
|
81
|
+
* never calls it writes unasked. A name taken twice, or by a built-in tool, stops startup.
|
|
78
82
|
* `false`: the host serves its own MCP endpoint at `auth.mcp.path`, in front of this app. This app then answers 404
|
|
79
83
|
* there and needs no `forAudience`, while the OAuth provider's discovery stays.
|
|
80
84
|
*/
|
|
81
85
|
mcp?: {
|
|
82
86
|
sql?: boolean;
|
|
83
87
|
writes?: McpWrites;
|
|
88
|
+
tools?: AssistantTool[];
|
|
84
89
|
} | false;
|
|
85
90
|
/**
|
|
86
91
|
* The websites whose browser code may call the API (`/api/*` but `/api/auth/*`) besides the app's own origins (see
|
|
@@ -21,7 +21,7 @@ export type ToolContext = {
|
|
|
21
21
|
show: (part: AssistantPart) => void;
|
|
22
22
|
/** Asks the user to approve with a card in that message (see `requestApproval`). */
|
|
23
23
|
approve: (request: ApprovalRequest) => Promise<ApprovalAnswer>;
|
|
24
|
-
/** The app's resources as the session's user, through their access rules and write hooks
|
|
24
|
+
/** The app's resources as the session's user, through their access rules and write hooks. */
|
|
25
25
|
records: ToolRecords;
|
|
26
26
|
/** The resources the caller may read, each restricted to their fields, with their views. */
|
|
27
27
|
resources: () => Promise<ReadableResources>;
|
|
@@ -38,7 +38,7 @@ export type ToolAnnotations = {
|
|
|
38
38
|
};
|
|
39
39
|
/**
|
|
40
40
|
* A function the model may call during a turn. `parameters` is the JSON Schema of its arguments; `run` gets them
|
|
41
|
-
* parsed, but not
|
|
41
|
+
* parsed, checked against the schema over MCP but not in the dock, and returns what the model reads as the result.
|
|
42
42
|
*/
|
|
43
43
|
export type AssistantTool = {
|
|
44
44
|
name: string;
|
package/dist/create-admin.js
CHANGED
|
@@ -77,6 +77,7 @@ import { createFilesRuntime } from "./files/runtime.js";
|
|
|
77
77
|
import { fileRoutes } from "./files/routes.js";
|
|
78
78
|
import { formRoutes } from "./forms/routes.js";
|
|
79
79
|
import { createFormsRuntime } from "./forms/runtime.js";
|
|
80
|
+
import { fieldDecrypter } from "./encryption/decrypt-field.js";
|
|
80
81
|
import { createEncryptionRuntime } from "./encryption/runtime.js";
|
|
81
82
|
import { functionRoutes } from "./functions/routes.js";
|
|
82
83
|
import { supabaseFunctionRoutes } from "./functions/supabase-routes.js";
|
|
@@ -333,7 +334,8 @@ const createAdmin = ({ resources: appResources, views: appViews = [], pages = []
|
|
|
333
334
|
db,
|
|
334
335
|
sql: options.mcp !== false && options.mcp?.sql === true,
|
|
335
336
|
writes,
|
|
336
|
-
commented
|
|
337
|
+
commented,
|
|
338
|
+
own: options.mcp !== false && options.mcp?.tools || []
|
|
337
339
|
});
|
|
338
340
|
const approvals = mcpApprovals();
|
|
339
341
|
const { resource } = auth.mcp;
|
|
@@ -373,6 +375,7 @@ const createAdmin = ({ resources: appResources, views: appViews = [], pages = []
|
|
|
373
375
|
sessionToken: betterAuthSessionToken(auth)
|
|
374
376
|
}
|
|
375
377
|
});
|
|
378
|
+
const decryptField = fieldDecrypter(deps);
|
|
376
379
|
app.route("/", functionRoutes({
|
|
377
380
|
basePath: `${systemPath}/functions`,
|
|
378
381
|
functions: apiFunctions,
|
|
@@ -380,6 +383,7 @@ const createAdmin = ({ resources: appResources, views: appViews = [], pages = []
|
|
|
380
383
|
access: deps.accessOptions,
|
|
381
384
|
db,
|
|
382
385
|
records: (session) => callerRecords(deps, session),
|
|
386
|
+
decryptField,
|
|
383
387
|
onError,
|
|
384
388
|
wake: () => wakeDispatch(deps),
|
|
385
389
|
upgradeWebSocket,
|
|
@@ -469,6 +473,7 @@ const createAdmin = ({ resources: appResources, views: appViews = [], pages = []
|
|
|
469
473
|
});
|
|
470
474
|
return Object.assign(app, {
|
|
471
475
|
startRealtimePrune: prune,
|
|
476
|
+
decryptField,
|
|
472
477
|
jobs: [
|
|
473
478
|
...webhooks.jobs,
|
|
474
479
|
...withHistory && days !== void 0 ? [pruneHistoryJob(changes, days)] : [],
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
//#region src/encryption/decrypt-field-types.d.ts
|
|
2
|
+
/** One record's key: its value, or its parts in the order of the resource's primary key. */
|
|
3
|
+
export type RecordKey = string | number | readonly (string | number)[];
|
|
4
|
+
/** Reads one encrypted field of one record as its cleartext, or `null` when it is empty. */
|
|
5
|
+
export type DecryptField = (resource: string, key: RecordKey, field: string) => Promise<string | null>;
|
|
6
|
+
/**
|
|
7
|
+
* Who reads, as the audit names them: a job, or a function with its caller and the request's origin (`AuditOrigin`).
|
|
8
|
+
* Filled in by the runtime, never by app code.
|
|
9
|
+
*/
|
|
10
|
+
export type FieldReader = {
|
|
11
|
+
job: string;
|
|
12
|
+
} | {
|
|
13
|
+
function: string;
|
|
14
|
+
actor: {
|
|
15
|
+
id: string | number;
|
|
16
|
+
roles: string[];
|
|
17
|
+
};
|
|
18
|
+
origin: {
|
|
19
|
+
userAgent?: string;
|
|
20
|
+
forwardedFor?: string;
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
/** The app's server-side read of encrypted fields, for the reader the runtime names. */
|
|
24
|
+
export type FieldDecrypter = (reader: FieldReader) => DecryptField;
|
|
25
|
+
//#endregion
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { readTransaction } from "../transactions.js";
|
|
2
|
+
import { bindingOf } from "./binding.js";
|
|
3
|
+
import { fetchRecord } from "../records.js";
|
|
4
|
+
import { openText } from "./envelope-open.js";
|
|
5
|
+
import { encodeKey } from "@protobase/schema/internal";
|
|
6
|
+
//#region src/encryption/decrypt-field.ts
|
|
7
|
+
const eventOf = (reader, base) => {
|
|
8
|
+
if ("job" in reader) return {
|
|
9
|
+
...base,
|
|
10
|
+
reader: { job: reader.job },
|
|
11
|
+
origin: {}
|
|
12
|
+
};
|
|
13
|
+
return {
|
|
14
|
+
...base,
|
|
15
|
+
actor: reader.actor,
|
|
16
|
+
reader: { function: reader.function },
|
|
17
|
+
origin: reader.origin
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* The read `decryptField` of a job's and a protected function's context: privileged like their `db`, so no access rules
|
|
22
|
+
* or tenant scope, of a live (not soft-deleted) record. It opens the value as the reveal does, then publishes a
|
|
23
|
+
* `field.revealed` event naming the reader, and only returns the value once that is accepted: when publishing fails,
|
|
24
|
+
* so does the read.
|
|
25
|
+
*/
|
|
26
|
+
const fieldDecrypter = (deps) => (reader) => async (resource, key, field) => {
|
|
27
|
+
const entry = deps.registry.find(resource);
|
|
28
|
+
if (!entry) throw new Error(`Unknown resource "${resource}"`);
|
|
29
|
+
const { model } = entry;
|
|
30
|
+
const target = Object.hasOwn(model.fields, field) ? model.fields[field] : void 0;
|
|
31
|
+
if (!target?.encrypted) throw new Error(`"${resource}.${field}" is not an encrypted field`);
|
|
32
|
+
if (!deps.encryption) throw new Error(`"${resource}" has encrypted fields but no encryption runtime`);
|
|
33
|
+
const parts = Array.isArray(key) ? key : [key];
|
|
34
|
+
if (parts.length !== model.primaryKey.length) throw new Error(`A ${resource} key has ${model.primaryKey.length} parts, not ${parts.length}`);
|
|
35
|
+
const tenantField = model.tenant && !model.primaryKey.includes(model.tenant) ? [model.tenant] : [];
|
|
36
|
+
const fields = [
|
|
37
|
+
...model.primaryKey,
|
|
38
|
+
...tenantField,
|
|
39
|
+
field
|
|
40
|
+
].map((name) => model.fields[name]);
|
|
41
|
+
const found = await readTransaction(deps.db, deps.statementTimeoutMs, (trx) => fetchRecord(trx, entry, parts, void 0, {
|
|
42
|
+
deleted: "hide",
|
|
43
|
+
fields
|
|
44
|
+
}));
|
|
45
|
+
if (!found) throw new Error(`No ${resource} with key "${encodeKey(parts)}"`);
|
|
46
|
+
const stored = found.record[field] ?? null;
|
|
47
|
+
const value = stored === null ? null : openText(deps.encryption.keyring, bindingOf(model, target, found.record), String(stored), {
|
|
48
|
+
resource,
|
|
49
|
+
field
|
|
50
|
+
});
|
|
51
|
+
const tenant = model.tenant ? found.record[model.tenant] : void 0;
|
|
52
|
+
await deps.audit.publish(eventOf(reader, {
|
|
53
|
+
type: "field.revealed",
|
|
54
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
55
|
+
...tenant !== void 0 && tenant !== null && { tenant },
|
|
56
|
+
resource,
|
|
57
|
+
recordKey: encodeKey(parts),
|
|
58
|
+
field
|
|
59
|
+
}));
|
|
60
|
+
return value;
|
|
61
|
+
};
|
|
62
|
+
//#endregion
|
|
63
|
+
export { fieldDecrypter };
|
|
@@ -42,7 +42,7 @@ const decrypt = (aes, aad, bytes) => {
|
|
|
42
42
|
};
|
|
43
43
|
/**
|
|
44
44
|
* The plaintext of `stored`, sealed under `binding` with a key of `keyring`, or an `UnreadableValue` thrown with why it
|
|
45
|
-
* does not open. The only place, with the CLI's status and apply
|
|
45
|
+
* does not open. The only place, with the CLI's status and apply and the audited `decryptField`, that decrypts.
|
|
46
46
|
*/
|
|
47
47
|
const openText = (keyring, binding, stored, subject) => {
|
|
48
48
|
const parsed = parse(stored);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { DecryptField } from "../encryption/decrypt-field-types.js";
|
|
1
2
|
import { Session } from "../types.js";
|
|
2
3
|
import { Caller } from "../api-keys/resolve-caller.js";
|
|
3
4
|
import { SocketEvents } from "./function-socket.js";
|
|
@@ -29,7 +30,14 @@ export type FunctionCaller = {
|
|
|
29
30
|
records: FunctionRecords;
|
|
30
31
|
};
|
|
31
32
|
/** What a function that needs a signed-in caller gets: the caller resolved before it runs. */
|
|
32
|
-
export type FunctionContext = PublicFunctionContext & FunctionCaller
|
|
33
|
+
export type FunctionContext = PublicFunctionContext & FunctionCaller & {
|
|
34
|
+
/**
|
|
35
|
+
* The cleartext of an `.encrypted()` field of one record, or `null` when it is empty: privileged like `db`, with no
|
|
36
|
+
* access rules or tenant scope. Each read is audited as a reveal, naming this function and its caller, and fails when
|
|
37
|
+
* the audit does. Not on a public function's context: a public function that needs a value enqueues a job.
|
|
38
|
+
*/
|
|
39
|
+
decryptField: DecryptField;
|
|
40
|
+
};
|
|
33
41
|
/** What a function gets at `/api/sb/functions/v1/<name>`, where supabase-js calls it: the caller its keys and token make. */
|
|
34
42
|
export type KeyFunctionContext = {
|
|
35
43
|
name: string;
|
package/dist/functions/routes.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { forbidden, notFound } from "../problem.js";
|
|
2
2
|
import { namingTimeouts } from "../statement-timeout.js";
|
|
3
|
+
import { auditOrigin } from "../audit/origin.js";
|
|
3
4
|
import { invokeFunction } from "./define-function.js";
|
|
4
5
|
import { runInBackground } from "../background-work.js";
|
|
5
6
|
import { socketAcceptor } from "./accept-socket.js";
|
|
@@ -22,7 +23,7 @@ const isUpgrade = (request) => request.headers.get("upgrade")?.toLowerCase() ===
|
|
|
22
23
|
* A function may answer an upgrade with a socket, only once it returned. While it is open, its caller is checked again
|
|
23
24
|
* every minute: a protected function's always, a public one's once `caller()` resolved.
|
|
24
25
|
*/
|
|
25
|
-
const functionRoutes = ({ basePath, functions, authenticate, access, db, records, onError, wake, upgradeWebSocket, upgradeAuthenticate = authenticate, report }) => {
|
|
26
|
+
const functionRoutes = ({ basePath, functions, authenticate, access, db, records, decryptField, onError, wake, upgradeWebSocket, upgradeAuthenticate = authenticate, report }) => {
|
|
26
27
|
const app = new Hono().basePath(basePath);
|
|
27
28
|
app.onError(onError);
|
|
28
29
|
const sessionOf = (request) => isUpgrade(request) ? upgradeAuthenticate(request) : authenticate(request);
|
|
@@ -79,11 +80,21 @@ const functionRoutes = ({ basePath, functions, authenticate, access, db, records
|
|
|
79
80
|
report,
|
|
80
81
|
recheck
|
|
81
82
|
});
|
|
83
|
+
const { user } = caller.session;
|
|
84
|
+
const reader = {
|
|
85
|
+
function: name,
|
|
86
|
+
actor: {
|
|
87
|
+
id: user.id,
|
|
88
|
+
roles: [...user.roles]
|
|
89
|
+
},
|
|
90
|
+
origin: auditOrigin(c.req.raw.headers)
|
|
91
|
+
};
|
|
82
92
|
const context = {
|
|
83
93
|
name,
|
|
84
94
|
db,
|
|
85
95
|
...caller,
|
|
86
96
|
caller: async () => caller,
|
|
97
|
+
decryptField: decryptField(reader),
|
|
87
98
|
acceptWebSocket: acceptor.accept
|
|
88
99
|
};
|
|
89
100
|
return acceptor.answer(name, await invokeFunction(name, fn, request, context));
|
package/dist/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { AppIdentity } from "./app-identity.js";
|
|
|
3
3
|
import { AppIcons, PngIcon, pngIcons } from "./web-app/app-icons.js";
|
|
4
4
|
import { RealtimeOptions, relayed } from "./realtime/relay/realtime-options.js";
|
|
5
5
|
import { EventsOptions } from "./changes/events-options.js";
|
|
6
|
+
import { DecryptField, FieldDecrypter, FieldReader, RecordKey } from "./encryption/decrypt-field-types.js";
|
|
6
7
|
import { StandardSchema, readJson } from "./functions/read-json.js";
|
|
7
8
|
import { MailMessage, Mailer } from "./mail/smtp-mailer.js";
|
|
8
9
|
import { JsonObject, JsonValue } from "./notifications/json-value.js";
|
|
@@ -169,4 +170,4 @@ import { DrainResult, JobsRuntime, StartJobsOptions, startJobs } from "./jobs/ru
|
|
|
169
170
|
import { jobsDrainApp } from "./jobs/drain-route.js";
|
|
170
171
|
import { checkJobsSchema, jobsSchema, jobsSchemaMigration } from "./jobs/schema.js";
|
|
171
172
|
import { AdminAuth, CreateAuthOptions, UserCreatedHook, normalizeRoles } from "./better-auth/create-auth.js";
|
|
172
|
-
export { type Admin, type AdminAuth, type AdminOptions, type AggregateParams, type ApiFunction, type ApiKeys, type ApiKeysOptions, type ApnsEnvironment, type AppIcons, type AppIdentity, type AppPoolConfig, type ApplyOptions, type ApplyResult, type ApprovalAnswer, type ApprovalRequest, type ArgumentsPayload, type AssistantBackend, type AssistantOptions, type AssistantSettings, type AssistantTool, type AuditEvent, type AuditQueue, type AuthHooks, type AuthSettings, type AuthenticateSettings, 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 CommentToolsInput, 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 EnqueueJob, 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 FileProviderSettings, 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, type HookContext, HttpProblem, type IssueTokenOptions, type JobContext, type JobDeclaration, type JobDefinition, type JobName, type JobOptions, type JobPayload, type JobRun, 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, type Membership, MissingCredentials, type MobileConfig, ModelError, type ModelSettings, type NewDevice, type NewNotification, type NewOrganization, type NewUser, type NotificationRecipients, NotificationSendError, type NotificationsOptions, type OperatorProvider, type OrganizationRole, type OrganizationsOptions, type PgModule, type PipelineHook, type PlatformSignIn, type PluginOption, type PluginRef, type PngIcon, type ProtectedFunctionOptions, type PublicFunctionContext, type PublicFunctionEnv, type PublicFunctionHandler, type PublicFunctionOptions, type PushAnswer, type PushMessage, PushMessageError, type PushSender, type QueryOptions, type QueryResult, type ReadableResources, type RealtimeOptions, type RealtimePrune, type ReasoningDetail, type ReasoningEffort, type RebindOption, type RecordGroups, type RecordKeyInput, type RecordPage, 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 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 ToolComment, type ToolCommentPage, type ToolComments, 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, addCommentTool, addMember, apiKeyCaller, apiKeySettings, apiKeyVariables, appNameOver, aspectRatio, assistantProtocolRoutes, assistantRoles, assistantSettings, assistantVariables, authSchemaDrift, authSchemaMigration, authStoreTables, backgroundSettled, badRequest, batchWriteTool, beforeSignInPluginsVariable, betterAuthTokens, capSocketMessages, changesMigration, checkAi, checkAuthSchema, checkChanges, checkComments, checkComputed, checkJobsSchema, checkNotificationsSchema, checkQueryRunner, checkRealtimeRelay, checkResourcesOutsideAuthStore, checkSourceNames, checkViewQueries, checkWebhooksSchema, coalesce, commentTools, commentsMigration, computedMigration, configExports, consoleAuditQueue, conversationKey, countRecordsTool, createAdmin, createConversations, createEncryptionTools, createFileCleanup, createOrganization, createRecordTool, createUser, defaultAppName, defaultHistoryPage, defaultSignInPolicy, defineEmail, defineFunction, defineJob, defineTool, deleteRecordTool, deleteUser, describeResourceTool, deviceIdParam, deviceStore, devicesPath, enqueueJob, 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, listCommentsTool, listOrganizations, listRecordsTool, listResourcesTool, listUsers, localFileStore, longQueryTimeoutMs, mapSettings, mapStyleUrl, mapStyleVariable, maxDevicesPerUser, maxHistoryPage, maxMessageBytes, maxRecipients, maxTokenTtlSeconds, maxTtlSeconds, maxUnreadCount, maxWidgetPropsBytes, mcpApprovals, mcpGate, mcpHandler, mcpIcons, mcpInstructions, mcpRoutes, mcpServer, mcpToolContext, mcpTools, migrationCommand, newWebhookSecret, normalizeRoles, notFound, notificationSenders, notificationsMigration, oauthWellKnownPaths, objectKey, parseColumns, parseRoles, passwordLength, pluginOptions, pluginsBeforeSignIn, pluginsFor, pluginsVariable, pngIcons, poolWaitMs, postgresDeviceStore, postgresHistoryStore, processor, pushMessage, queryRunnerMigration, readDbConnections, readJson, readOnlyQueryTool, readOperatorSettings, readOutbox, readPlatformSignIn, readRecordTools, readVapidSettings, readWriteQueryTool, readableResources, realtimeLimits, realtimeRelayMigration, recordOf, relayed, removeDevicesWithUsers, removeHistoryWithUsers, removeMember, renderVariant, requestApproval, requireEnv, resourceChange, roleChoices, runInBackground, runReadOnlyQuery, runReadWriteQuery, runTurn, runtimeUrl, runtimeVariable, s3FileStore, 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, webhooksMigration, widget, withCacheBreakpoints, withTrustedApps, writeRecordTools };
|
|
173
|
+
export { type Admin, type AdminAuth, type AdminOptions, type AggregateParams, type ApiFunction, type ApiKeys, type ApiKeysOptions, type ApnsEnvironment, type AppIcons, type AppIdentity, type AppPoolConfig, type ApplyOptions, type ApplyResult, type ApprovalAnswer, type ApprovalRequest, type ArgumentsPayload, type AssistantBackend, type AssistantOptions, type AssistantSettings, type AssistantTool, type AuditEvent, type AuditQueue, type AuthHooks, type AuthSettings, type AuthenticateSettings, 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 CommentToolsInput, type CommentUsers, type Completion, type CompletionOptions, ConnectionsBusy, type Conversation, type ConversationRecord, type ConversationStore, type ConversationsOptions, type CorsOptions, type CorsOrigins, type CreateAdminInput, type CreateAuthOptions, type DecryptField, type Device, type DeviceColumn, type DeviceKind, type DeviceStore, DeviceStoreError, type DrainResult, type EmailComponent, type EmailOptions, type EmailTemplate, type EncryptionStatus, type EncryptionTools, type EncryptionToolsInput, type EnqueueJob, type EnqueueOptions, type ErrorReporter, type ErrorReportingOptions, type ErrorReportingSettings, type EventData, type EventsOptions, FatalJobError, type FieldApplied, type FieldDecrypter, type FieldReader, type FieldStatus, type FileCleanupInput, type FileLimits, type FileObject, type FileProvider, type FileProviderSettings, 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, type HookContext, HttpProblem, type IssueTokenOptions, type JobContext, type JobDeclaration, type JobDefinition, type JobName, type JobOptions, type JobPayload, type JobRun, 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, type Membership, MissingCredentials, type MobileConfig, ModelError, type ModelSettings, type NewDevice, type NewNotification, type NewOrganization, type NewUser, type NotificationRecipients, NotificationSendError, type NotificationsOptions, type OperatorProvider, type OrganizationRole, type OrganizationsOptions, type PgModule, type PipelineHook, type PlatformSignIn, type PluginOption, type PluginRef, type PngIcon, type ProtectedFunctionOptions, type PublicFunctionContext, type PublicFunctionEnv, type PublicFunctionHandler, type PublicFunctionOptions, type PushAnswer, type PushMessage, PushMessageError, type PushSender, type QueryOptions, type QueryResult, type ReadableResources, type RealtimeOptions, type RealtimePrune, type ReasoningDetail, type ReasoningEffort, type RebindOption, type RecordGroups, type RecordKey, type RecordKeyInput, type RecordPage, 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 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 ToolComment, type ToolCommentPage, type ToolComments, 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, addCommentTool, addMember, apiKeyCaller, apiKeySettings, apiKeyVariables, appNameOver, aspectRatio, assistantProtocolRoutes, assistantRoles, assistantSettings, assistantVariables, authSchemaDrift, authSchemaMigration, authStoreTables, backgroundSettled, badRequest, batchWriteTool, beforeSignInPluginsVariable, betterAuthTokens, capSocketMessages, changesMigration, checkAi, checkAuthSchema, checkChanges, checkComments, checkComputed, checkJobsSchema, checkNotificationsSchema, checkQueryRunner, checkRealtimeRelay, checkResourcesOutsideAuthStore, checkSourceNames, checkViewQueries, checkWebhooksSchema, coalesce, commentTools, commentsMigration, computedMigration, configExports, consoleAuditQueue, conversationKey, countRecordsTool, createAdmin, createConversations, createEncryptionTools, createFileCleanup, createOrganization, createRecordTool, createUser, defaultAppName, defaultHistoryPage, defaultSignInPolicy, defineEmail, defineFunction, defineJob, defineTool, deleteRecordTool, deleteUser, describeResourceTool, deviceIdParam, deviceStore, devicesPath, enqueueJob, 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, listCommentsTool, listOrganizations, listRecordsTool, listResourcesTool, listUsers, localFileStore, longQueryTimeoutMs, mapSettings, mapStyleUrl, mapStyleVariable, maxDevicesPerUser, maxHistoryPage, maxMessageBytes, maxRecipients, maxTokenTtlSeconds, maxTtlSeconds, maxUnreadCount, maxWidgetPropsBytes, mcpApprovals, mcpGate, mcpHandler, mcpIcons, mcpInstructions, mcpRoutes, mcpServer, mcpToolContext, mcpTools, migrationCommand, newWebhookSecret, normalizeRoles, notFound, notificationSenders, notificationsMigration, oauthWellKnownPaths, objectKey, parseColumns, parseRoles, passwordLength, pluginOptions, pluginsBeforeSignIn, pluginsFor, pluginsVariable, pngIcons, poolWaitMs, postgresDeviceStore, postgresHistoryStore, processor, pushMessage, queryRunnerMigration, readDbConnections, readJson, readOnlyQueryTool, readOperatorSettings, readOutbox, readPlatformSignIn, readRecordTools, readVapidSettings, readWriteQueryTool, readableResources, realtimeLimits, realtimeRelayMigration, recordOf, relayed, removeDevicesWithUsers, removeHistoryWithUsers, removeMember, renderVariant, requestApproval, requireEnv, resourceChange, roleChoices, runInBackground, runReadOnlyQuery, runReadWriteQuery, runTurn, runtimeUrl, runtimeVariable, s3FileStore, 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, webhooksMigration, widget, withCacheBreakpoints, withTrustedApps, writeRecordTools };
|
package/dist/jobs/job-types.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { DecryptField } from "../encryption/decrypt-field-types.js";
|
|
1
2
|
import { StandardSchema } from "../functions/read-json.js";
|
|
2
3
|
import { Mailer } from "../mail/smtp-mailer.js";
|
|
3
4
|
import { SendNotification } from "../notifications/send-input.js";
|
|
@@ -17,6 +18,11 @@ export type JobContext = {
|
|
|
17
18
|
};
|
|
18
19
|
/** Aborted when the job's timeout passes, or when the worker shuts down and its grace period ends. */
|
|
19
20
|
signal: AbortSignal;
|
|
21
|
+
/**
|
|
22
|
+
* The cleartext of an `.encrypted()` field of one record, or `null` when it is empty: privileged like `db`, with no
|
|
23
|
+
* access rules or tenant scope. Each read is audited as a reveal, naming this job, and fails when the audit does.
|
|
24
|
+
*/
|
|
25
|
+
decryptField: DecryptField;
|
|
20
26
|
/** Sends mail with the app's mailer: its auth/mailer.ts, else the SMTP server of `PROTOBASE_SMTP_URL` and `PROTOBASE_MAIL_FROM`; `undefined` when the app has no mail. */
|
|
21
27
|
mail?: Mailer;
|
|
22
28
|
/** Sends a notification as `sendNotification` does; `undefined` when the app has no `notifications`. */
|
package/dist/jobs/run-job.js
CHANGED
|
@@ -49,11 +49,14 @@ const namedTimeout = (definition, error) => {
|
|
|
49
49
|
if (!isStatementTimeout(error)) return error;
|
|
50
50
|
return new Error(statementTimeoutDetail(`Job "${definition.name}"`, { kind: definition.options.longQueries ? "long" : "database" }), { cause: error });
|
|
51
51
|
};
|
|
52
|
+
const noDecrypter = (name) => async () => {
|
|
53
|
+
throw new Error(`Job "${name}" cannot decrypt fields: startJobs was not given the app's decryptField`);
|
|
54
|
+
};
|
|
52
55
|
/**
|
|
53
56
|
* One try of one job: completed when `run` returns, failed (to be retried while attempts are left) when it throws, and
|
|
54
57
|
* failed for good on a `FatalJobError` or an invalid payload.
|
|
55
58
|
*/
|
|
56
|
-
const runJob = async (definition, job, db, { mail, sendNotification } = {}) => {
|
|
59
|
+
const runJob = async (definition, job, db, { mail, sendNotification, decryptField } = {}) => {
|
|
57
60
|
try {
|
|
58
61
|
const payload = await checkPayload(definition, job.data ?? void 0);
|
|
59
62
|
const context = {
|
|
@@ -66,6 +69,7 @@ const runJob = async (definition, job, db, { mail, sendNotification } = {}) => {
|
|
|
66
69
|
startedOn: job.startedOn
|
|
67
70
|
},
|
|
68
71
|
signal: job.signal,
|
|
72
|
+
decryptField: decryptField ? decryptField({ job: definition.name }) : noDecrypter(definition.name),
|
|
69
73
|
...mail && { mail },
|
|
70
74
|
...sendNotification && { sendNotification }
|
|
71
75
|
};
|
package/dist/jobs/runtime.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { FieldDecrypter } from "../encryption/decrypt-field-types.js";
|
|
1
2
|
import { Mailer } from "../mail/smtp-mailer.js";
|
|
2
3
|
import { JobDefinition } from "./job-types.js";
|
|
3
4
|
import { JobsEnqueuer } from "./current.js";
|
|
@@ -21,6 +22,8 @@ export type StartJobsOptions = {
|
|
|
21
22
|
* `PROTOBASE_MAIL_FROM`, when set; `false` turns mail off whatever the environment says.
|
|
22
23
|
*/
|
|
23
24
|
mailer?: Mailer | false;
|
|
25
|
+
/** The app's read of encrypted fields (`createAdmin(...).decryptField`), which each job's context binds to the job. */
|
|
26
|
+
decryptField?: FieldDecrypter;
|
|
24
27
|
};
|
|
25
28
|
/** What a drain left: whether no job is due or running any more, how many are, and when the next one is due. */
|
|
26
29
|
export type DrainResult = {
|
|
@@ -31,8 +34,8 @@ export type DrainResult = {
|
|
|
31
34
|
export type JobsRuntime = JobsEnqueuer & {
|
|
32
35
|
/** Whether this process runs the jobs, or only enqueues them. */
|
|
33
36
|
readonly works: boolean;
|
|
34
|
-
/** Takes the project's jobs again, as `protobase dev` does when their files change
|
|
35
|
-
update(jobs: JobDefinition[]): Promise<void>;
|
|
37
|
+
/** Takes the project's jobs again, as `protobase dev` does when their files change, with the rebuilt app's `decryptField`. */
|
|
38
|
+
update(jobs: JobDefinition[], decryptField?: FieldDecrypter): Promise<void>;
|
|
36
39
|
/**
|
|
37
40
|
* Runs the jobs that are due until none is due or running, or `seconds` have passed: for a host that wakes a sleeping
|
|
38
41
|
* app to run its jobs. Needs this process to run jobs.
|
|
@@ -48,5 +51,5 @@ export type JobsRuntime = JobsEnqueuer & {
|
|
|
48
51
|
* that retries jobs whose worker died. It reads the jobs schema, never changes it: check it first with
|
|
49
52
|
* `checkJobsSchema`. Enqueueing through `defineJob(...).enqueue` reaches the runtime started here.
|
|
50
53
|
*/
|
|
51
|
-
export declare const startJobs: ({ db, jobs, work, pollSeconds, shutdownSeconds, onError, now, mailer }: StartJobsOptions) => Promise<JobsRuntime>;
|
|
54
|
+
export declare const startJobs: ({ db, jobs, work, pollSeconds, shutdownSeconds, onError, now, mailer, decryptField }: StartJobsOptions) => Promise<JobsRuntime>;
|
|
52
55
|
//#endregion
|
package/dist/jobs/runtime.js
CHANGED
|
@@ -30,8 +30,9 @@ const createBoss = (db, work) => new PgBoss({
|
|
|
30
30
|
* that retries jobs whose worker died. It reads the jobs schema, never changes it: check it first with
|
|
31
31
|
* `checkJobsSchema`. Enqueueing through `defineJob(...).enqueue` reaches the runtime started here.
|
|
32
32
|
*/
|
|
33
|
-
const startJobs = async ({ db, jobs, work = true, pollSeconds = 2, shutdownSeconds = 30, onError, now = () => /* @__PURE__ */ new Date(), mailer }) => {
|
|
33
|
+
const startJobs = async ({ db, jobs, work = true, pollSeconds = 2, shutdownSeconds = 30, onError, now = () => /* @__PURE__ */ new Date(), mailer, decryptField }) => {
|
|
34
34
|
const mail = mailer === void 0 ? platformMailer() : mailer || void 0;
|
|
35
|
+
let decrypter = decryptField;
|
|
35
36
|
const database = busyDatabase(jobsDatabase(db));
|
|
36
37
|
const boss = createBoss(database.db, work);
|
|
37
38
|
boss.on("error", onError);
|
|
@@ -60,6 +61,7 @@ const startJobs = async ({ db, jobs, work = true, pollSeconds = 2, shutdownSecon
|
|
|
60
61
|
if (current.options.schedule !== void 0 && job.singletonKey === "protobase.schedule") await schedules.following(current, job.startAfter);
|
|
61
62
|
return [await runJob(current, job, db, {
|
|
62
63
|
mail,
|
|
64
|
+
...decrypter && { decryptField: decrypter },
|
|
63
65
|
...definitions.has("notifications.send") && { sendNotification }
|
|
64
66
|
})];
|
|
65
67
|
}, { longQueries: current.options.longQueries });
|
|
@@ -69,7 +71,8 @@ const startJobs = async ({ db, jobs, work = true, pollSeconds = 2, shutdownSecon
|
|
|
69
71
|
concurrency
|
|
70
72
|
});
|
|
71
73
|
};
|
|
72
|
-
const update = async (next) => {
|
|
74
|
+
const update = async (next, nextDecrypter) => {
|
|
75
|
+
if (nextDecrypter) decrypter = nextDecrypter;
|
|
73
76
|
for (const definition of next) await ensureQueue(definition);
|
|
74
77
|
for (const name of definitions.keys()) {
|
|
75
78
|
if (next.some((definition) => definition.name === name)) continue;
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { isMcpWrites } from "./mcp-writes.js";
|
|
2
2
|
//#region src/mcp/mcp-authenticator.ts
|
|
3
3
|
const hasAudiences = (authenticate) => typeof authenticate.forAudience === "function";
|
|
4
|
-
const isMcpOptions = (options) => options === void 0 || options === false || typeof options === "object" && options !== null && !Array.isArray(options) && Object.entries(options).every(([name, value]) => value === void 0 || name === "sql" && typeof value === "boolean" || name === "writes" && isMcpWrites(value));
|
|
4
|
+
const isMcpOptions = (options) => options === void 0 || options === false || typeof options === "object" && options !== null && !Array.isArray(options) && Object.entries(options).every(([name, value]) => value === void 0 || name === "sql" && typeof value === "boolean" || name === "writes" && isMcpWrites(value) || name === "tools" && Array.isArray(value));
|
|
5
5
|
/**
|
|
6
6
|
* The authenticator of the MCP endpoint's tokens, when MCP is on (`auth.mcp`); none when the host serves its
|
|
7
7
|
* own (`options.mcp: false`). Stops the server at startup when `options.mcp` is neither `false` nor
|
|
8
|
-
* `{ sql?: boolean; writes?: boolean | { resources?: string[]; rolesWithoutApproval?: string[] } }`, when it is set without MCP, or when `authenticate` cannot verify tokens for
|
|
8
|
+
* `{ sql?: boolean; writes?: boolean | { resources?: string[]; rolesWithoutApproval?: string[] }; tools?: AssistantTool[] }`, when it is set without MCP, or when `authenticate` cannot verify tokens for
|
|
9
9
|
* another audience.
|
|
10
10
|
*/
|
|
11
11
|
const mcpAuthenticator = (authenticate, mcp, options) => {
|
|
12
|
-
if (!isMcpOptions(options)) throw new Error("options.mcp takes false or { sql?: boolean, writes?: boolean | { resources?: [<resource>, ...], rolesWithoutApproval?: [<role>, ...] } }");
|
|
12
|
+
if (!isMcpOptions(options)) throw new Error("options.mcp takes false or { sql?: boolean, writes?: boolean | { resources?: [<resource>, ...], rolesWithoutApproval?: [<role>, ...] }, tools?: [<tool>, ...] }");
|
|
13
13
|
if (options === false) return void 0;
|
|
14
14
|
if (options && !mcp) throw new Error("mcpEndpoint needs auth.mcp in protobase.config.ts");
|
|
15
15
|
if (!mcp) return void 0;
|
package/dist/mcp/mcp-tools.d.ts
CHANGED
|
@@ -18,6 +18,14 @@ export type McpToolsInput = {
|
|
|
18
18
|
* those of them that `writes` lists when it lists some. None by default.
|
|
19
19
|
*/
|
|
20
20
|
commented?: string[];
|
|
21
|
+
/** The app's own tools, offered to every caller after the built-in ones. */
|
|
22
|
+
own?: AssistantTool[];
|
|
21
23
|
};
|
|
22
|
-
|
|
24
|
+
/**
|
|
25
|
+
* The MCP tools for a caller: the record tools, the write tools when `writes` is on, the comment tools when a resource
|
|
26
|
+
* takes comments, read-only SQL when `sql` is on and the caller sees the assistant, then the app's own tools. The
|
|
27
|
+
* tools are built once and only chosen per session: the MCP server compiles each tool's schema, and a schema object made
|
|
28
|
+
* per request would be compiled again.
|
|
29
|
+
*/
|
|
30
|
+
export declare const mcpTools: ({ db, sql, writes, commented, own }: McpToolsInput) => (session: Session) => AssistantTool[];
|
|
23
31
|
//#endregion
|
package/dist/mcp/mcp-tools.js
CHANGED
|
@@ -1,16 +1,33 @@
|
|
|
1
1
|
import { seesAssistant } from "../assistant/assistant-settings.js";
|
|
2
|
+
import { checkedTools } from "../assistant/checked-tools.js";
|
|
2
3
|
import { readOnlyQueryTool } from "../assistant/query-tools.js";
|
|
3
4
|
import { readRecordTools } from "../assistant/record-tools.js";
|
|
4
5
|
import { writeRecordTools } from "../assistant/write-record-tools.js";
|
|
5
6
|
import { batchWriteTool } from "../assistant/batch-write-tool.js";
|
|
6
7
|
import { commentTools } from "../assistant/comment-tools.js";
|
|
7
8
|
//#region src/mcp/mcp-tools.ts
|
|
9
|
+
const writeTools = (input) => [...writeRecordTools(input), batchWriteTool(input)];
|
|
10
|
+
const builtInNames = (db) => new Set([
|
|
11
|
+
...readRecordTools(),
|
|
12
|
+
...writeTools({}),
|
|
13
|
+
...commentTools({ commented: ["comments"] }, {}),
|
|
14
|
+
readOnlyQueryTool(db)
|
|
15
|
+
].map((tool) => tool.name));
|
|
16
|
+
/** The app's own tools; one whose name is taken twice, or by a built-in tool, stops the server at startup. */
|
|
17
|
+
const checkedOwnTools = (db, own) => {
|
|
18
|
+
const builtIn = builtInNames(db);
|
|
19
|
+
const taken = own.find((tool) => builtIn.has(tool.name));
|
|
20
|
+
if (taken) throw new Error(`The MCP tool "${taken.name}" has the name of a built-in MCP tool; give it another name`);
|
|
21
|
+
return checkedTools(own);
|
|
22
|
+
};
|
|
8
23
|
/**
|
|
9
|
-
* The
|
|
10
|
-
*
|
|
24
|
+
* The MCP tools for a caller: the record tools, the write tools when `writes` is on, the comment tools when a resource
|
|
25
|
+
* takes comments, read-only SQL when `sql` is on and the caller sees the assistant, then the app's own tools. The
|
|
26
|
+
* tools are built once and only chosen per session: the MCP server compiles each tool's schema, and a schema object made
|
|
27
|
+
* per request would be compiled again.
|
|
11
28
|
*/
|
|
12
|
-
const
|
|
13
|
-
const
|
|
29
|
+
const mcpTools = ({ db, sql, writes, commented = [], own = [] }) => {
|
|
30
|
+
const ownTools = checkedOwnTools(db, own);
|
|
14
31
|
const read = readRecordTools();
|
|
15
32
|
const limits = writes === false ? false : writes === true || !writes.resources ? {} : { resources: writes.resources };
|
|
16
33
|
const write = limits === false ? [] : writeTools(limits);
|
|
@@ -20,7 +37,8 @@ const mcpTools = ({ db, sql, writes, commented = [] }) => {
|
|
|
20
37
|
...read,
|
|
21
38
|
...write,
|
|
22
39
|
...comments,
|
|
23
|
-
...seesAssistant(session.user.roles) ? query : []
|
|
40
|
+
...seesAssistant(session.user.roles) ? query : [],
|
|
41
|
+
...ownTools
|
|
24
42
|
];
|
|
25
43
|
};
|
|
26
44
|
//#endregion
|
package/dist/package.js
CHANGED
package/dist/types.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { FieldDecrypter } from "./encryption/decrypt-field-types.js";
|
|
1
2
|
import { JobDefinition } from "./jobs/job-types.js";
|
|
2
3
|
import { RealtimePrune } from "./realtime/realtime-prune.js";
|
|
3
4
|
import { Hono } from "hono";
|
|
@@ -95,11 +96,18 @@ export type FieldRevealedEvent = {
|
|
|
95
96
|
type: 'field.revealed';
|
|
96
97
|
/** ISO 8601, when the server answered. */
|
|
97
98
|
at: string;
|
|
98
|
-
|
|
99
|
+
/** The caller; absent when a job read the value (`reader.job`). */
|
|
100
|
+
actor?: {
|
|
99
101
|
id: string | number;
|
|
100
102
|
roles: string[];
|
|
101
103
|
};
|
|
102
|
-
/** The
|
|
104
|
+
/** The job or API function that read the value with `decryptField`; absent for a reveal. */
|
|
105
|
+
reader?: {
|
|
106
|
+
job: string;
|
|
107
|
+
} | {
|
|
108
|
+
function: string;
|
|
109
|
+
};
|
|
110
|
+
/** The caller's tenant (organization), or the record's for `decryptField`, when the resource has one. */
|
|
103
111
|
tenant?: TenantValue;
|
|
104
112
|
resource: string;
|
|
105
113
|
/** The record's key as its URL has it (composite keys joined with a comma). */
|
|
@@ -137,11 +145,13 @@ export type PageSource = {
|
|
|
137
145
|
toModel(): PageModel;
|
|
138
146
|
};
|
|
139
147
|
/**
|
|
140
|
-
* The admin API, the jobs it needs a worker to run (the delivery of resource events),
|
|
141
|
-
* tables a serving process starts once it listens and stops as it closes
|
|
148
|
+
* The admin API, the jobs it needs a worker to run (the delivery of resource events), the pruning of the realtime
|
|
149
|
+
* tables a serving process starts once it listens and stops as it closes, and the read of encrypted fields that
|
|
150
|
+
* `startJobs` gives the jobs' context.
|
|
142
151
|
*/
|
|
143
152
|
export type Admin = Hono<AdminEnv> & {
|
|
144
153
|
jobs: JobDefinition[];
|
|
145
154
|
startRealtimePrune: () => RealtimePrune;
|
|
155
|
+
decryptField: FieldDecrypter;
|
|
146
156
|
};
|
|
147
157
|
//#endregion
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@protobase/server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.58.1",
|
|
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": {
|
|
@@ -41,9 +41,9 @@
|
|
|
41
41
|
"@better-auth/passkey": "^1.7.12",
|
|
42
42
|
"@better-auth/sso": "^1.7.12",
|
|
43
43
|
"@modelcontextprotocol/server": "^2.3.1",
|
|
44
|
-
"@protobase/layout": "0.
|
|
45
|
-
"@protobase/query": "0.
|
|
46
|
-
"@protobase/schema": "0.
|
|
44
|
+
"@protobase/layout": "0.58.1",
|
|
45
|
+
"@protobase/query": "0.58.1",
|
|
46
|
+
"@protobase/schema": "0.58.1",
|
|
47
47
|
"@react-email/render": "^2.1.0",
|
|
48
48
|
"@scalar/hono-api-reference": "^0.12.10",
|
|
49
49
|
"@sentry/core": "^11.7.0",
|