@protobase/server 0.53.0 → 0.53.2

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.
@@ -1,7 +1,5 @@
1
1
  import { HttpProblem } from "../problem.js";
2
- import { forcedJson } from "./result-budget.js";
3
2
  import { defineTool } from "./tools.js";
4
- import { cutRecordNote } from "./budgeted-page.js";
5
3
  import { writeText } from "./write-messages.js";
6
4
  import { describedResource } from "./write-prompts.js";
7
5
  import { fieldsParameter } from "./write-echo.js";
@@ -127,7 +125,7 @@ const batchWriteTool = (input = {}) => defineTool({
127
125
  const answer = await context.approve(approval);
128
126
  if (answer !== "approved") return `The user did not approve this batch (${answer}); nothing was written.`;
129
127
  const befores = await recordsBefore(context, operations);
130
- return context.records.batch(operations).then(async (results) => forcedJson({ results: await batchEcho(context, operations, results, befores, fields) }, cutRecordNote), refused);
128
+ return context.records.batch(operations).then(async (results) => JSON.stringify({ results: await batchEcho(context, operations, results, befores, fields) }), refused);
131
129
  }
132
130
  });
133
131
  //#endregion
@@ -0,0 +1,9 @@
1
+ import { listColumnNames } from "@protobase/schema/internal";
2
+ //#region src/assistant/list-fields.ts
3
+ /**
4
+ * The fields `list_records` returns when it is not told which: the key, then the columns of the resource's table as
5
+ * the caller's view lists them, leaving out the related columns, which are not fields.
6
+ */
7
+ const listFields = (model, view) => [.../* @__PURE__ */ new Set([...model.primaryKey, ...listColumnNames(model, view)])].filter((name) => Object.hasOwn(model.fields, name) && !model.fields[name].writeOnly);
8
+ //#endregion
9
+ export { listFields };
@@ -1,7 +1,7 @@
1
- import { budgetedQueryResult } from "./query-result-budget.js";
2
1
  import { runReadOnlyQuery, runReadWriteQuery } from "./run-query.js";
3
2
  import { defineTool } from "./tools.js";
4
3
  //#region src/assistant/query-tools.ts
4
+ const maxResultLength = 2e4;
5
5
  const sqlParameter = (description) => ({
6
6
  type: "string",
7
7
  description
@@ -11,7 +11,7 @@ const refused = (error) => {
11
11
  if (isDatabaseError(error)) return `Postgres refused the statement: ${error.message}`;
12
12
  throw error;
13
13
  };
14
- const shown = (result, statement, context, more) => {
14
+ const shown = (result, statement, context) => {
15
15
  context.show({
16
16
  type: "table",
17
17
  id: crypto.randomUUID(),
@@ -20,9 +20,9 @@ const shown = (result, statement, context, more) => {
20
20
  caption: statement,
21
21
  ...result.truncated && { truncated: true }
22
22
  });
23
- return budgetedQueryResult(result, more);
23
+ const json = JSON.stringify(result);
24
+ return json.length > maxResultLength ? `${json.slice(0, maxResultLength)}... (cut off)` : json;
24
25
  };
25
- const readOn = (count) => `; add OFFSET ${count} to the statement to read on`;
26
26
  const statementOf = (args) => typeof args.sql === "string" && args.sql.trim() ? args.sql : void 0;
27
27
  /**
28
28
  * Lets the model read the app's database: one SELECT per call, in a read-only transaction (see `runReadOnlyQuery`).
@@ -31,7 +31,7 @@ const statementOf = (args) => typeof args.sql === "string" && args.sql.trim() ?
31
31
  */
32
32
  const readOnlyQueryTool = (db, options = {}) => defineTool({
33
33
  name: "run_read_only_query",
34
- description: `Runs one PostgreSQL SELECT on the app's database in a read-only transaction and returns up to ${options.rowLimit ?? 200} rows as JSON ({ columns, rows, truncated }); a result too large for one answer holds fewer rows, with long values cut and named in cut_fields, and a note on reading the rest. The user sees the rows as a table. Use it to answer questions about the data. It reads the app's resource tables only. It cannot change anything.`,
34
+ description: `Runs one PostgreSQL SELECT on the app's database in a read-only transaction and returns up to ${options.rowLimit ?? 200} rows as JSON ({ columns, rows, truncated }). The user sees the rows as a table. Use it to answer questions about the data. It reads the app's resource tables only. It cannot change anything.`,
35
35
  parameters: {
36
36
  type: "object",
37
37
  properties: { sql: sqlParameter("One SELECT statement, without a trailing semicolon.") },
@@ -44,7 +44,7 @@ const readOnlyQueryTool = (db, options = {}) => defineTool({
44
44
  run: async (args, context) => {
45
45
  const statement = statementOf(args);
46
46
  if (!statement) return "Pass the statement as { \"sql\": \"select ...\" }.";
47
- return runReadOnlyQuery(db, statement, options).then((result) => shown(result, statement, context, readOn), refused);
47
+ return runReadOnlyQuery(db, statement, options).then((result) => shown(result, statement, context), refused);
48
48
  }
49
49
  });
50
50
  /**
@@ -79,7 +79,7 @@ const readWriteQueryTool = (db, options = {}) => defineTool({
79
79
  code: statement
80
80
  });
81
81
  if (answer !== "approved") return `The user did not approve the statement (${answer}); nothing ran.`;
82
- return runReadWriteQuery(db, statement, options).then((result) => shown(result, statement, context, () => ""), refused);
82
+ return runReadWriteQuery(db, statement, options).then((result) => shown(result, statement, context), refused);
83
83
  }
84
84
  });
85
85
  //#endregion
@@ -0,0 +1,13 @@
1
+ import { pickFields } from "../rows.js";
2
+ import { selectedFields } from "../params.js";
3
+ //#region src/assistant/record-fields.ts
4
+ /** `record` with only `fields`, each one of those it holds, which are those the caller may read; all of it without. */
5
+ const recordFields = (model, record, fields) => {
6
+ const readable = Object.fromEntries(Object.keys(record).map((name) => [name, model.fields[name]]));
7
+ return pickFields(record, selectedFields({
8
+ ...model,
9
+ fields: readable
10
+ }, fields));
11
+ };
12
+ //#endregion
13
+ export { recordFields };
@@ -1,9 +1,7 @@
1
1
  import { notFound } from "../problem.js";
2
- import { forcedJson } from "./result-budget.js";
3
2
  import { defineTool } from "./tools.js";
3
+ import { listFields } from "./list-fields.js";
4
4
  import { invalidShowDeleted, keyOf, keyParameter, refused, resourceOf, resourceParameter, showDeletedOf, showDeletedParameter } from "./record-tool-args.js";
5
- import { budgetedPage, cutRecordNote } from "./budgeted-page.js";
6
- import { fieldPiece, pieceParameters, readsPiece } from "./record-field-piece.js";
7
5
  import { countRecordsTool } from "./count-records-tool.js";
8
6
  //#region src/assistant/record-tools.ts
9
7
  const readOnly = {
@@ -39,6 +37,7 @@ const resourceSummary = (model, view) => ({
39
37
  const resourceDetail = (model, view) => ({
40
38
  ...resourceSummary(model, view),
41
39
  ...view?.help && { help: view.help },
40
+ list_fields: listFields(model, view),
42
41
  fields: Object.values(model.fields).map((field) => fieldEntry(field, view))
43
42
  });
44
43
  const viewOf = ({ views }, model) => views.find((view) => view.resource === model.name);
@@ -59,7 +58,7 @@ const listResourcesTool = () => defineTool({
59
58
  /** Describes one resource the caller may read, with its fields, their labels, help and values, and which fields filters and sorts take. */
60
59
  const describeResourceTool = () => defineTool({
61
60
  name: "describe_resource",
62
- description: "Describes one resource you may read, as JSON: its name, label, help, key fields, whether it soft deletes (soft_delete) and fields (name, type, label, help, enum values, the resource a relation links to, and whether it is optional, read-only, filterable and sortable). Filters and order_by in list_records take only fields marked filterable or sortable.",
61
+ description: "Describes one resource you may read, as JSON: its name, label, help, key fields, whether it soft deletes (soft_delete) and fields (name, type, label, help, enum values, the resource a relation links to, and whether it is optional, read-only, filterable and sortable), and list_fields, the fields list_records returns when not told which. Filters and order_by in list_records take only fields marked filterable or sortable.",
63
62
  parameters: {
64
63
  type: "object",
65
64
  properties: { resource: resourceParameter },
@@ -75,14 +74,42 @@ const describeResourceTool = () => defineTool({
75
74
  return JSON.stringify(resourceDetail(model, viewOf(resources, model)));
76
75
  }
77
76
  });
77
+ const fieldsParameter = (what) => ({
78
+ type: "array",
79
+ items: { type: "string" },
80
+ minItems: 1,
81
+ description: `The fields to return, such as ["id", "status"], or ["*"] for every field; ${what}.`
82
+ });
83
+ /** The fields named, `*` when they ask for every field, or `undefined` when none are. */
84
+ const fieldsOf = (value) => {
85
+ if (!Array.isArray(value) || value.length === 0 || !value.every((name) => typeof name === "string")) return void 0;
86
+ return value.includes("*") ? "*" : value;
87
+ };
88
+ /** Without `fields`, the resource's list fields, the page naming the others in `omitted_fields`; `*` asks for all. */
89
+ const listedFields = async (resource, value, context) => {
90
+ const named = fieldsOf(value);
91
+ if (named === "*") return {};
92
+ if (named) return { fields: named };
93
+ const readable = await context.resources();
94
+ const model = readable.models.find((each) => each.name === resource);
95
+ if (!model) return {};
96
+ const fields = listFields(model, viewOf(readable, model));
97
+ const omitted = Object.keys(model.fields).filter((name) => !fields.includes(name));
98
+ if (omitted.length === 0) return {};
99
+ return {
100
+ fields,
101
+ omitted
102
+ };
103
+ };
78
104
  /** Finds records of a resource with the REST API's filters, sorting and pages, under the caller's access rules. */
79
105
  const listRecordsTool = () => defineTool({
80
106
  name: "list_records",
81
- description: `Finds records of a resource and returns a page of them as JSON ({ items, next_page_token, total_size_estimate }). filter uses AIP-160 syntax, such as status = "open" AND amount > 10, and order_by a comma-separated list of fields, each optionally followed by desc. Pass next_page_token back as page_token for the next page; it is empty on the last. A page too large for one result holds fewer records, with long values cut and named in cut_fields, and a note on reading the rest. show_deleted: true also returns soft-deleted records.`,
107
+ description: `Finds records of a resource and returns a page of them as JSON ({ items, next_page_token, total_size_estimate }). Name the fields you need in fields, such as ["id", "status"]; without it each record has its resource's list_fields, the columns of its table in the app, and the page names the others in omitted_fields: pass ["*"] for every field. filter uses AIP-160 syntax on filterable fields, such as status = "open" AND amount > 10, and order_by a comma-separated list of sortable fields, each optionally followed by desc. total_size_estimate is an estimate, not an exact count. Keep page_size small and pass next_page_token back as page_token for the next page; it is empty on the last. show_deleted: true also returns soft-deleted records.`,
82
108
  parameters: {
83
109
  type: "object",
84
110
  properties: {
85
111
  resource: resourceParameter,
112
+ fields: fieldsParameter("without it, the list_fields describe_resource names"),
86
113
  filter: {
87
114
  type: "string",
88
115
  description: "An AIP-160 filter on filterable fields, such as status = \"open\" AND amount > 10."
@@ -111,28 +138,32 @@ const listRecordsTool = () => defineTool({
111
138
  if (!resource) return "Pass the resource as { \"resource\": \"<name>\" }.";
112
139
  const showDeleted = showDeletedOf(args);
113
140
  if (showDeleted === void 0) return invalidShowDeleted;
141
+ const { fields, omitted } = await listedFields(resource, args.fields, context);
114
142
  const params = {
115
143
  ...typeof args.filter === "string" && { filter: args.filter },
116
144
  ...typeof args.order_by === "string" && { order_by: args.order_by },
117
145
  ...typeof args.page_token === "string" && { page_token: args.page_token },
146
+ ...fields && { fields },
118
147
  page_size: typeof args.page_size === "number" ? Math.min(args.page_size, maxPageSize) : defaultPageSize,
119
148
  ...showDeleted && { show_deleted: true }
120
149
  };
121
- const list = (paged) => context.records.list(resource, paged);
122
- return list(params).then((page) => budgetedPage(page, params, list)).catch(refused);
150
+ return context.records.list(resource, params).then((page) => JSON.stringify({
151
+ ...page,
152
+ ...omitted && { omitted_fields: omitted }
153
+ }), refused);
123
154
  }
124
155
  });
125
156
  /** Reads one record by its key, under the caller's access rules. */
126
157
  const getRecordTool = () => defineTool({
127
158
  name: "get_record",
128
- description: "Reads one record of a resource by its key and returns it as JSON ({ record, etag }), with long values cut and named in cut_fields when it is too large for one result. field reads one field alone, in pieces from offset. show_deleted: true also finds a soft-deleted record.",
159
+ description: "Reads one record of a resource by its key and returns it as JSON ({ record, etag }). Name the fields you need in fields, such as [\"id\", \"status\"]; without it, or with [\"*\"], the record has every field, long texts included. show_deleted: true also finds a soft-deleted record.",
129
160
  parameters: {
130
161
  type: "object",
131
162
  properties: {
132
163
  resource: resourceParameter,
133
164
  key: keyParameter,
134
- show_deleted: showDeletedParameter,
135
- ...pieceParameters
165
+ fields: fieldsParameter("without it, every field"),
166
+ show_deleted: showDeletedParameter
136
167
  },
137
168
  required: ["resource", "key"]
138
169
  },
@@ -143,8 +174,11 @@ const getRecordTool = () => defineTool({
143
174
  if (!resource || key === void 0) return "Pass the resource and key as { \"resource\": \"<name>\", \"key\": \"<key>\" }.";
144
175
  const showDeleted = showDeletedOf(args);
145
176
  if (showDeleted === void 0) return invalidShowDeleted;
146
- const shown = (found) => readsPiece(args) ? fieldPiece(found, args) : forcedJson(found, cutRecordNote);
147
- return context.records.get(resource, key, { show_deleted: showDeleted }).then(shown, refused);
177
+ const fields = fieldsOf(args.fields);
178
+ return context.records.get(resource, key, {
179
+ show_deleted: showDeleted,
180
+ ...Array.isArray(fields) && { fields }
181
+ }).then((record) => JSON.stringify(record), refused);
148
182
  }
149
183
  });
150
184
  /**
@@ -61,9 +61,13 @@ export type ToolRecords = {
61
61
  list: (resource: string, params?: ListParams) => Promise<RecordPage>;
62
62
  /** Counts per value of a field, as `GET /{resource}:aggregate` does, under the same access rules as `list`. */
63
63
  aggregate: (resource: string, params: AggregateParams) => Promise<RecordGroups>;
64
- /** With `show_deleted`, also finds a soft-deleted record, as the REST API's `show_deleted` does. */
64
+ /**
65
+ * With `show_deleted`, also finds a soft-deleted record, as the REST API's `show_deleted` does. With `fields`, the
66
+ * record holds those fields only, each checked against the fields the caller may read.
67
+ */
65
68
  get: (resource: string, key: RecordKeyInput, options?: {
66
69
  show_deleted?: boolean;
70
+ fields?: string[];
67
71
  }) => Promise<ToolRecord>;
68
72
  create: (resource: string, data: Row) => Promise<ToolRecord>;
69
73
  /** With `etag`, fails with 412 when the record changed since it was read; without, it updates any version. */
@@ -5,6 +5,7 @@ import { decodeKeyText } from "../key.js";
5
5
  import { runBatch } from "../batch/run-batch.js";
6
6
  import { listRecords } from "../list-service.js";
7
7
  import { readRecord } from "../read-service.js";
8
+ import { recordFields } from "./record-fields.js";
8
9
  import { encodeKey } from "@protobase/schema/internal";
9
10
  //#region src/assistant/records.ts
10
11
  const filterLength = 2e5;
@@ -29,11 +30,11 @@ const assistantRecords = (deps, session) => {
29
30
  return {
30
31
  list: async (resource, params = {}) => (await listRecords(deps, entryOf(resource), session, params, filterLength)).body,
31
32
  aggregate: async (resource, params) => (await aggregate(deps, entryOf(resource), session, params)).body,
32
- get: async (resource, input, { show_deleted } = {}) => {
33
+ get: async (resource, input, { show_deleted, fields } = {}) => {
33
34
  const { entry, key } = keyOf(resource, input);
34
35
  const { record, etag } = await readRecord(deps, entry, session, key, show_deleted);
35
36
  return {
36
- record,
37
+ record: recordFields(entry.model, record, fields),
37
38
  etag
38
39
  };
39
40
  },
@@ -1,7 +1,5 @@
1
- import { forcedJson } from "./result-budget.js";
2
1
  import { defineTool } from "./tools.js";
3
2
  import { keyOf, keyParameter, refused, resourceOf } from "./record-tool-args.js";
4
- import { cutRecordNote } from "./budgeted-page.js";
5
3
  import { createApproval, updateApproval } from "./write-prompts.js";
6
4
  import { approvalTexts, recordBefore } from "./record-before.js";
7
5
  import { forbiddenText, notApproved, onlyNote, outside, resourceParameterFor } from "./write-tool-limits.js";
@@ -43,7 +41,7 @@ const createRecordTool = (input = {}) => defineTool({
43
41
  const { text, described } = await approvalTexts(context, resource);
44
42
  const answer = await context.approve(createApproval(text, described, data));
45
43
  if (answer !== "approved") return notApproved(answer);
46
- return context.records.create(resource, data).then((written) => forcedJson(writeEcho(described.model, written, data, void 0, fields), cutRecordNote), refused);
44
+ return context.records.create(resource, data).then((written) => JSON.stringify(writeEcho(described.model, written, data, void 0, fields)), refused);
47
45
  }
48
46
  });
49
47
  /** Updates one record by its key once the user approves it, under the caller's access rules. */
@@ -90,7 +88,7 @@ const updateRecordTool = (input = {}) => defineTool({
90
88
  const { text, described } = await approvalTexts(context, resource);
91
89
  const answer = await context.approve(updateApproval(text, described, key, before?.record ?? {}, data));
92
90
  if (answer !== "approved") return notApproved(answer);
93
- return context.records.update(resource, key, data, { etag }).then((written) => forcedJson(writeEcho(described.model, written, data, before?.record, fields), cutRecordNote), refused);
91
+ return context.records.update(resource, key, data, { etag }).then((written) => JSON.stringify(writeEcho(described.model, written, data, before?.record, fields)), refused);
94
92
  }
95
93
  });
96
94
  /** `create_record`, `update_record` and `delete_record`, on the resources `input` allows. */
package/dist/index.d.ts CHANGED
@@ -130,6 +130,7 @@ import { CommentToolsInput, addCommentTool, commentTools, listCommentsTool } fro
130
130
  import { McpApprovals, mcpApprovals } from "./mcp/mcp-approval-store.js";
131
131
  import { McpServerInput, mcpServer } from "./mcp/mcp-server.js";
132
132
  import { mcpIcons } from "./web-app/mcp-icons.js";
133
+ import { mcpInstructions } from "./mcp/mcp-instructions.js";
133
134
  import { McpToolsInput, mcpTools } from "./mcp/mcp-tools.js";
134
135
  import { McpGateInput, mcpGate, sessionOf } from "./mcp/mcp-gate.js";
135
136
  import { McpHandlerInput, mcpHandler } from "./mcp/mcp-handler.js";
@@ -170,4 +171,4 @@ import { MobileRelease, ReleaseFile, ServedReleaseFile, composeRelease, releaseM
170
171
  import { MobileReleaseAppInput, OfferedRelease, mobileReleaseApp } from "./mobile-release/release-app.js";
171
172
  import { mobileSigningKeyVariable, readSigningKey } from "./mobile-release/signing-key.js";
172
173
  import { AdminAuth, CreateAuthOptions, UserCreatedHook, normalizeRoles } from "./better-auth/create-auth.js";
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 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, 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 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 ReasoningDetail, type ReasoningEffort, type RebindOption, type RecordGroups, 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 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, composeRelease, 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, mcpRoutes, mcpServer, mcpToolContext, mcpTools, migrationCommand, mobileReleaseApp, mobileSigningKeyVariable, 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, readSigningKey, readVapidSettings, readWriteQueryTool, readableResources, realtimeLimits, realtimeRelayMigration, recordOf, relayed, releaseManifest, 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 };
174
+ 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, 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 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 ReasoningDetail, type ReasoningEffort, type RebindOption, type RecordGroups, 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 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, composeRelease, 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, mobileReleaseApp, mobileSigningKeyVariable, 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, readSigningKey, readVapidSettings, readWriteQueryTool, readableResources, realtimeLimits, realtimeRelayMigration, recordOf, relayed, releaseManifest, 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/index.js CHANGED
@@ -44,6 +44,7 @@ import { mcpToolContext } from "./mcp/mcp-context.js";
44
44
  import { mcpGate, sessionOf } from "./mcp/mcp-gate.js";
45
45
  import { mcpHandler } from "./mcp/mcp-handler.js";
46
46
  import { mcpRoutes } from "./mcp/mcp-routes.js";
47
+ import { mcpInstructions } from "./mcp/mcp-instructions.js";
47
48
  import { mcpServer } from "./mcp/mcp-server.js";
48
49
  import { countRecordsTool } from "./assistant/count-records-tool.js";
49
50
  import { describeResourceTool, getRecordTool, listRecordsTool, listResourcesTool, readRecordTools } from "./assistant/record-tools.js";
@@ -128,4 +129,4 @@ import { jobsDrainApp } from "./jobs/drain-route.js";
128
129
  import { composeRelease, releaseManifest } from "./mobile-release/release.js";
129
130
  import { mobileReleaseApp } from "./mobile-release/release-app.js";
130
131
  import { mobileSigningKeyVariable, readSigningKey } from "./mobile-release/signing-key.js";
131
- export { ConnectionsBusy, DeviceStoreError, FatalJobError, HistoryStoreError, HttpProblem, MissingCredentials, ModelError, NotificationSendError, PushMessageError, 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, composeRelease, 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, mcpRoutes, mcpServer, mcpToolContext, mcpTools, migrationCommand, mobileReleaseApp, mobileSigningKeyVariable, 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, readSigningKey, readVapidSettings, readWriteQueryTool, readableResources, realtimeLimits, realtimeRelayMigration, recordOf, relayed, releaseManifest, 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 };
132
+ export { ConnectionsBusy, DeviceStoreError, FatalJobError, HistoryStoreError, HttpProblem, MissingCredentials, ModelError, NotificationSendError, PushMessageError, 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, composeRelease, 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, mobileReleaseApp, mobileSigningKeyVariable, 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, readSigningKey, readVapidSettings, readWriteQueryTool, readableResources, realtimeLimits, realtimeRelayMigration, recordOf, relayed, releaseManifest, 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 };
@@ -0,0 +1,4 @@
1
+ //#region src/mcp/mcp-instructions.d.ts
2
+ /** What `mcpServer` tells a client when it connects: how to read the app's records without pulling more than it needs. */
3
+ export declare const mcpInstructions: string;
4
+ //#endregion
@@ -0,0 +1,10 @@
1
+ //#region src/mcp/mcp-instructions.ts
2
+ /** What `mcpServer` tells a client when it connects: how to read the app's records without pulling more than it needs. */
3
+ const mcpInstructions = [
4
+ "Call list_resources first to find the resources, then describe_resource for one: it names the resource's fields, which ones filters and sorts take, and its list_fields.",
5
+ "Name the fields you need in fields, such as [\"id\", \"status\"], on list_records and get_record: a record can hold long texts. Without fields, list_records returns the list_fields, the columns of the resource's table in the app, and names the others in omitted_fields; [\"*\"] asks for every field. get_record returns every field unless told which.",
6
+ "Filter on fields marked filterable, the indexed ones, rather than reading pages to search them, and keep page_size small: follow next_page_token only when you need more. total_size_estimate is an estimate, not an exact count: count_records counts.",
7
+ "For example, the open tasks, newest first, ten at a time: list_records { \"resource\": \"tasks\", \"fields\": [\"id\", \"status\", \"title\"], \"filter\": \"status = \\\"open\\\"\", \"order_by\": \"created_at desc\", \"page_size\": 10 }, then get_record { \"resource\": \"tasks\", \"key\": \"42\", \"fields\": [\"brief\"] } for one long text."
8
+ ].join("\n");
9
+ //#endregion
10
+ export { mcpInstructions };
@@ -10,6 +10,8 @@ export type McpServerInput = {
10
10
  title?: string;
11
11
  icons?: Icon[];
12
12
  };
13
+ /** What the server tells clients about using it when they connect; `mcpInstructions`, on reading records, by default. */
14
+ instructions?: string;
13
15
  tools: AssistantTool[];
14
16
  /** What each tool gets as its context, for the caller of the request; `approve` comes from each call's own answers. */
15
17
  context: Omit<ToolContext, 'approve'>;
@@ -26,5 +28,5 @@ export type McpServerInput = {
26
28
  * counts only for the call it was asked for, once, while it lasts; for a user holding one of `rolesWithoutApproval` it
27
29
  * is approved at once, and the client is never asked.
28
30
  */
29
- export declare const mcpServer: ({ info, tools, context, report, rolesWithoutApproval, approvals }: McpServerInput) => McpServer;
31
+ export declare const mcpServer: ({ info, instructions, tools, context, report, rolesWithoutApproval, approvals }: McpServerInput) => McpServer;
30
32
  //#endregion
@@ -2,6 +2,7 @@ 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
4
  import { callHash, callScope, scopeHash } from "./mcp-call-hash.js";
5
+ import { mcpInstructions } from "./mcp-instructions.js";
5
6
  import { CLIENT_CAPABILITIES_META_KEY, McpServer, fromJsonSchema } from "@modelcontextprotocol/server";
6
7
  //#region src/mcp/mcp-server.ts
7
8
  const capabilitiesOf = (envelope) => envelope?.[CLIENT_CAPABILITIES_META_KEY];
@@ -33,8 +34,8 @@ const stopped = (tool) => ({
33
34
  * counts only for the call it was asked for, once, while it lasts; for a user holding one of `rolesWithoutApproval` it
34
35
  * is approved at once, and the client is never asked.
35
36
  */
36
- const mcpServer = ({ info, tools, context, report, rolesWithoutApproval = [], approvals }) => {
37
- const server = new McpServer(info);
37
+ const mcpServer = ({ info, instructions = mcpInstructions, tools, context, report, rolesWithoutApproval = [], approvals }) => {
38
+ const server = new McpServer(info, { instructions });
38
39
  const unasked = writesUnasked(rolesWithoutApproval, context.session.user.roles);
39
40
  for (const tool of checkedTools(tools)) {
40
41
  const config = {
package/dist/package.js CHANGED
@@ -1,4 +1,4 @@
1
1
  //#region package.json
2
- var version = "0.53.0";
2
+ var version = "0.53.2";
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.53.0",
3
+ "version": "0.53.2",
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": {
@@ -40,9 +40,9 @@
40
40
  "@better-auth/passkey": "^1.7.12",
41
41
  "@better-auth/sso": "^1.7.12",
42
42
  "@modelcontextprotocol/server": "^2.3.1",
43
- "@protobase/layout": "0.53.0",
44
- "@protobase/query": "0.53.0",
45
- "@protobase/schema": "0.53.0",
43
+ "@protobase/layout": "0.53.2",
44
+ "@protobase/query": "0.53.2",
45
+ "@protobase/schema": "0.53.2",
46
46
  "@react-email/render": "^2.1.0",
47
47
  "@scalar/hono-api-reference": "^0.12.10",
48
48
  "@sentry/core": "^11.7.0",
@@ -1,30 +0,0 @@
1
- import { budgetedJson, forcedJson, leadingFit } from "./result-budget.js";
2
- //#region src/assistant/budgeted-page.ts
3
- /** How a record tool's result says to read a field it cut. */
4
- const cutRecordNote = "The fields in cut_fields were cut to fit the size limit, each with its full length; read the rest of one with get_record and its field, offset and max_chars arguments.";
5
- const fewerNote = (count) => `Only ${count} records fit the size limit; pass next_page_token as page_token for the next ones. ${cutRecordNote}`;
6
- /**
7
- * A page of records as JSON within the size budget: its strings trimmed, then, when that does not fit, the page
8
- * listed again with as many records as fit, so its next_page_token goes on from the last one returned.
9
- */
10
- const budgetedPage = async (page, params, list) => {
11
- const json = budgetedJson(page, cutRecordNote);
12
- if (json) return json;
13
- if (page.items.length <= 1) return forcedJson(page, cutRecordNote);
14
- const fits = (count) => budgetedJson({
15
- ...page,
16
- items: page.items.slice(0, count),
17
- note: fewerNote(count)
18
- }, fewerNote(count)) !== void 0;
19
- const count = leadingFit(page.items.length - 1, fits);
20
- const fewer = await list({
21
- ...params,
22
- page_size: count
23
- });
24
- return forcedJson({
25
- ...fewer,
26
- note: fewerNote(fewer.items.length)
27
- }, fewerNote(fewer.items.length));
28
- };
29
- //#endregion
30
- export { budgetedPage, cutRecordNote };
@@ -1,32 +0,0 @@
1
- import { budgetedJson, forcedJson, leadingFit } from "./result-budget.js";
2
- //#region src/assistant/query-result-budget.ts
3
- const cutCellNote = "The values in cut_fields were cut to fit the size limit, each with its full length (rows[r][c] is row r, column c); read the rest of one with substring(<column> from <start>), counting characters from 1.";
4
- const fewerRowsNote = (count, more) => `Only the first ${count} rows fit the size limit${more}. ${cutCellNote}`;
5
- /**
6
- * A query's result as JSON within the size budget: its strings trimmed, then, when that does not fit, only as many
7
- * leading rows as fit, marked truncated. `more` ends the note on the rows left out, such as how to read on.
8
- */
9
- const budgetedQueryResult = (result, more) => {
10
- const json = budgetedJson(result, cutCellNote);
11
- if (json) return json;
12
- if (result.rows.length <= 1) return forcedJson(result, cutCellNote);
13
- const leading = (count) => {
14
- const note = fewerRowsNote(count, more(count));
15
- return {
16
- result: {
17
- ...result,
18
- rows: result.rows.slice(0, count),
19
- truncated: true,
20
- note
21
- },
22
- note
23
- };
24
- };
25
- const { result: fewer, note } = leading(leadingFit(result.rows.length - 1, (count) => {
26
- const { result, note } = leading(count);
27
- return budgetedJson(result, note) !== void 0;
28
- }));
29
- return forcedJson(fewer, note);
30
- };
31
- //#endregion
32
- export { budgetedQueryResult };
@@ -1,54 +0,0 @@
1
- import { forcedJson } from "./result-budget.js";
2
- //#region src/assistant/record-field-piece.ts
3
- /** get_record's arguments that read one field in pieces, such as a field a result cut to fit its size limit. */
4
- const pieceParameters = {
5
- field: {
6
- type: "string",
7
- description: "One field to read alone, in pieces: max_chars characters of its value from offset. A value that is not text reads as its JSON."
8
- },
9
- offset: {
10
- type: "integer",
11
- minimum: 0,
12
- description: "With field: the character to start at, 0 by default."
13
- },
14
- max_chars: {
15
- type: "integer",
16
- minimum: 1,
17
- maximum: 3e4,
18
- description: "With field: the characters to read, 20000 by default."
19
- }
20
- };
21
- const defaultChars = 2e4;
22
- const maxChars = 3e4;
23
- const invalid = (message) => JSON.stringify({
24
- error: "invalid-parameter",
25
- message
26
- });
27
- const countOf = (value, fallback) => value === void 0 ? fallback : typeof value === "number" && Number.isInteger(value) && value >= 0 ? value : void 0;
28
- /** Whether the call reads one field in pieces rather than the whole record. */
29
- const readsPiece = (args) => args.field !== void 0;
30
- /**
31
- * The piece of `args.field` of `found` the call asks for, as JSON ({ field, offset, length, value, next_offset }), where
32
- * length is the whole value's and next_offset is absent once the piece reaches its end.
33
- */
34
- const fieldPiece = (found, args) => {
35
- const field = args.field;
36
- if (typeof field !== "string" || !Object.hasOwn(found.record, field)) return invalid(`Invalid parameter field: pass one of ${Object.keys(found.record).join(", ")}`);
37
- const offset = countOf(args.offset, 0);
38
- if (offset === void 0) return invalid("Invalid parameter offset: pass a whole number from 0");
39
- const chars = countOf(args.max_chars, defaultChars);
40
- if (!chars) return invalid(`Invalid parameter max_chars: pass a whole number from 1 to ${maxChars}`);
41
- const raw = found.record[field];
42
- const text = typeof raw === "string" ? raw : JSON.stringify(raw) ?? "null";
43
- const end = offset + Math.min(chars, maxChars);
44
- const piece = {
45
- field,
46
- offset,
47
- length: text.length,
48
- value: text.slice(offset, end),
49
- ...end < text.length && { next_offset: end }
50
- };
51
- return forcedJson(piece, "The value was cut to fit the size limit; pass a smaller max_chars.");
52
- };
53
- //#endregion
54
- export { fieldPiece, pieceParameters, readsPiece };
@@ -1,72 +0,0 @@
1
- //#region src/assistant/result-budget.ts
2
- /**
3
- * Keeps a tool result within what a model's client takes: over the budget, its longest string values are trimmed, each
4
- * named in `cut_fields` with its full length, and a `note` says how to read the rest. A tool with a list returns fewer
5
- * items when trimming alone does not fit (see `leadingFit`).
6
- */
7
- /** The characters of JSON one tool result may hold. */
8
- const resultBudget = 4e4;
9
- const stringFloor = 500;
10
- const neverCut = /* @__PURE__ */ new Set(["next_page_token", "etag"]);
11
- const pathOf = (path, key) => path ? `${path}.${key}` : key;
12
- const trimmed = (value, cap, path, cuts) => {
13
- if (typeof value === "string") {
14
- if (value.length <= cap) return value;
15
- cuts.push({
16
- field: path,
17
- length: value.length
18
- });
19
- return `${value.slice(0, cap)}…`;
20
- }
21
- if (Array.isArray(value)) return value.map((item, index) => trimmed(item, cap, `${path}[${index}]`, cuts));
22
- if (value !== null && typeof value === "object") return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, !path && neverCut.has(key) ? item : trimmed(item, cap, pathOf(path, key), cuts)]));
23
- return value;
24
- };
25
- const longestString = (value) => {
26
- if (typeof value === "string") return value.length;
27
- if (value !== null && typeof value === "object") return Math.max(0, ...Object.values(value).map(longestString));
28
- return 0;
29
- };
30
- const cutAt = (result, cap, note) => {
31
- const cuts = [];
32
- const value = trimmed(result, cap, "", cuts);
33
- return JSON.stringify(cuts.length ? {
34
- ...value,
35
- cut_fields: cuts,
36
- note
37
- } : value);
38
- };
39
- /**
40
- * `result` as JSON within `budget`, its strings trimmed as little as fits but to no fewer than `floor` characters;
41
- * undefined when even that does not fit. `note` says how to read a cut field's rest.
42
- */
43
- const budgetedJson = (result, note, budget = resultBudget, floor = stringFloor) => {
44
- const json = JSON.stringify(result);
45
- if (json.length <= budget) return json;
46
- const plain = JSON.parse(json);
47
- let low = floor;
48
- let high = longestString(plain);
49
- if (low >= high) return void 0;
50
- if (cutAt(plain, low, note).length > budget) return void 0;
51
- while (high - low > 1) {
52
- const cap = Math.floor((low + high) / 2);
53
- if (cutAt(plain, cap, note).length <= budget) low = cap;
54
- else high = cap;
55
- }
56
- return cutAt(plain, low, note);
57
- };
58
- /** `result` as JSON within `budget` if it can be, its strings trimmed down to nothing when they must; never undefined. */
59
- const forcedJson = (result, note, budget = resultBudget) => budgetedJson(result, note, budget) ?? budgetedJson(result, note, budget, 0) ?? cutAt(JSON.parse(JSON.stringify(result)), 0, note);
60
- /** The most leading items of `count`, at least 1, for which `fits` holds, `fits` holding for fewer when it does for more. */
61
- const leadingFit = (count, fits) => {
62
- let low = 1;
63
- let high = count;
64
- while (low < high) {
65
- const middle = Math.ceil((low + high) / 2);
66
- if (fits(middle)) low = middle;
67
- else high = middle - 1;
68
- }
69
- return low;
70
- };
71
- //#endregion
72
- export { budgetedJson, forcedJson, leadingFit, resultBudget };