@protobase/server 0.67.9 → 0.67.11
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/create-admin.js +5 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.js +4 -1
- package/dist/mcp/mcp-tools.d.ts +8 -2
- package/dist/mcp/mcp-tools.js +10 -3
- package/dist/package.js +1 -1
- package/dist/webhooks/attempt.js +2 -2
- package/dist/webhooks/table/webhook-table.js +1 -1
- package/dist/workflows/mcp/merge-patch.js +15 -0
- package/dist/workflows/mcp/type-catalog.js +144 -0
- package/dist/workflows/mcp/workflow-instructions.d.ts +7 -0
- package/dist/workflows/mcp/workflow-instructions.js +77 -0
- package/dist/workflows/mcp/workflow-read-tools.d.ts +6 -0
- package/dist/workflows/mcp/workflow-read-tools.js +209 -0
- package/dist/workflows/mcp/workflow-tool-args.js +35 -0
- package/dist/workflows/mcp/workflow-write-tools.d.ts +7 -0
- package/dist/workflows/mcp/workflow-write-tools.js +149 -0
- package/dist/workflows/save-hook.js +22 -3
- package/dist/workflows/step-types.js +3 -10
- package/dist/workflows/store.js +9 -6
- package/dist/workflows/workflow-engine.js +1 -1
- package/package.json +5 -5
package/dist/create-admin.js
CHANGED
|
@@ -38,8 +38,10 @@ import { changesSchema } from "./changes/events-options.js";
|
|
|
38
38
|
import { mcpToolContext } from "./mcp/mcp-context.js";
|
|
39
39
|
import { mcpHandler } from "./mcp/mcp-handler.js";
|
|
40
40
|
import { closedMcpRoutes, mcpRoutes } from "./mcp/mcp-routes.js";
|
|
41
|
+
import { mcpInstructions } from "./mcp/mcp-instructions.js";
|
|
41
42
|
import { mcpServer } from "./mcp/mcp-server.js";
|
|
42
43
|
import { mcpTools } from "./mcp/mcp-tools.js";
|
|
44
|
+
import { workflowInstructions } from "./workflows/mcp/workflow-instructions.js";
|
|
43
45
|
import { withoutSessionTokens } from "./better-auth/without-session-tokens.js";
|
|
44
46
|
import { organizationRoutes } from "./better-auth/organizations/admin-routes.js";
|
|
45
47
|
import { statusRoute } from "./better-auth/status-route.js";
|
|
@@ -384,8 +386,10 @@ const createAdmin = ({ resources: appResources, views: appViews = [], userTables
|
|
|
384
386
|
sql: options.mcp !== false && options.mcp?.sql === true,
|
|
385
387
|
writes,
|
|
386
388
|
commented,
|
|
389
|
+
workflows: Boolean(engine),
|
|
387
390
|
own: options.mcp !== false && options.mcp?.tools || []
|
|
388
391
|
});
|
|
392
|
+
const instructions = engine ? `${mcpInstructions}\n${workflowInstructions}` : mcpInstructions;
|
|
389
393
|
const approvals = mcpApprovals();
|
|
390
394
|
const { resource } = auth.mcp;
|
|
391
395
|
const info = {
|
|
@@ -400,6 +404,7 @@ const createAdmin = ({ resources: appResources, views: appViews = [], userTables
|
|
|
400
404
|
});
|
|
401
405
|
return mcpServer({
|
|
402
406
|
info,
|
|
407
|
+
instructions,
|
|
403
408
|
tools: tools(session),
|
|
404
409
|
context,
|
|
405
410
|
report,
|
package/dist/index.d.ts
CHANGED
|
@@ -152,6 +152,9 @@ import { McpServerInput, mcpServer } from "./mcp/mcp-server.js";
|
|
|
152
152
|
import { mcpIcons } from "./web-app/mcp-icons.js";
|
|
153
153
|
import { mcpInstructions } from "./mcp/mcp-instructions.js";
|
|
154
154
|
import { McpToolsInput, mcpTools } from "./mcp/mcp-tools.js";
|
|
155
|
+
import { workflowReadTools } from "./workflows/mcp/workflow-read-tools.js";
|
|
156
|
+
import { workflowWriteTools } from "./workflows/mcp/workflow-write-tools.js";
|
|
157
|
+
import { workflowInstructions } from "./workflows/mcp/workflow-instructions.js";
|
|
155
158
|
import { McpGateInput, mcpGate, sessionOf } from "./mcp/mcp-gate.js";
|
|
156
159
|
import { McpHandlerInput, mcpHandler } from "./mcp/mcp-handler.js";
|
|
157
160
|
import { McpRoutesInput, mcpRoutes } from "./mcp/mcp-routes.js";
|
|
@@ -192,4 +195,4 @@ import { DrainResult, JobsRuntime, StartJobsOptions, startJobs } from "./jobs/ru
|
|
|
192
195
|
import { jobsDrainApp } from "./jobs/drain-route.js";
|
|
193
196
|
import { checkJobsSchema, jobsSchema, jobsSchemaMigration } from "./jobs/schema.js";
|
|
194
197
|
import { AdminAuth, CreateAuthOptions, UserCreatedHook, normalizeRoles } from "./better-auth/create-auth.js";
|
|
195
|
-
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 BaseToolContext, type BatchOperation, type BatchResult, type BetterAuthAuthenticator, type BetterAuthAuthenticatorOptions, type CallBackend, type CallMember, type Caller, type CallerOptions, type CallsAccess, type CallsOptions, type CapturedRow, type CascadingKey, type ChannelBroadcast, type ChannelSender, 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 LiveKitBackend, type LocalFilesOptions, type MailAttachment, type MailMessage, MailServerTimeoutError, 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 PdfComponent, type PdfOptions, type PdfTemplate, PersonalMailError, type PersonalMailErrorCode, type PersonalMailProviderId, 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 StartWorkflowOptions, type StatusOptions, 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 UserTables, type VapidSettings, type ViewSource, type WebPushFields, type WebPushOptions, type WebhooksOptions, type WidgetOptions, type WorkflowsOptions, 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, callsMigration, capSocketMessages, changesMigration, channelSendsMigration, checkAi, checkAuthSchema, checkCalls, checkChanges, checkChannelSends, checkComments, checkComputed, checkJobsSchema, checkNotificationsSchema, checkQueryRunner, checkRealtimeRelay, checkResourcesOutsideAuthStore, checkSourceNames, checkStatusSchema, checkUserTablesSchema, checkViewQueries, checkWebhooksSchema, checkWorkflowsSchema, coalesce, commentTools, commentsMigration, computedMigration, configExports, consoleAuditQueue, conversationKey, countRecordsTool, createAdmin, createConversations, createEncryptionTools, createFileCleanup, createOrganization, createRecordTool, createUser, defaultAppName, defaultHistoryPage, defaultSignInPolicy, defineEmail, defineFunction, defineJob, definePdf, 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, onDoNotDisturb, ownedDefinitionsMigration, parseColumns, parseRoles, passwordLength, pluginOptions, pluginsBeforeSignIn, pluginsFor, pluginsVariable, pngIcons, poolWaitMs, postgresDeviceStore, postgresHistoryStore, processor, pushMessage, queryRunnerMigration, readDbConnections, readDefinitionsVersion, readJson, readOnlyQueryTool, readOperatorSettings, readOutbox, readPlatformSignIn, readRecordTools, readVapidSettings, readWriteQueryTool, readableResources, realtimeLimits, realtimeRelayMigration, recordOf, refusalOf, refused, relayed, reloadingAdmin, 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, sqlSends, startErrorReporting, startJobs, startWorkflow, statusMigration, statusNames, storageCleanupSchedule, storageProvider, storeFile, storeUsage, streamChatCompletion, subscription, supabasePath, thumbHash, unauthorized, updateRecordTool, urgencies, userTableResources, userTablesMigration, vapidPrivateKeyVariable, vapidPublicKeyVariable, vapidSubjectVariable, variantPath, verifySignature, verifyStandardWebhook, waitUntil, wantsLongQueries, webManifestPath, webPushSender, webauthnPath, webhooksMigration, widget, withCacheBreakpoints, withTrustedApps, workflowsMigration, writeRecordTools };
|
|
198
|
+
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 BaseToolContext, type BatchOperation, type BatchResult, type BetterAuthAuthenticator, type BetterAuthAuthenticatorOptions, type CallBackend, type CallMember, type Caller, type CallerOptions, type CallsAccess, type CallsOptions, type CapturedRow, type CascadingKey, type ChannelBroadcast, type ChannelSender, 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 LiveKitBackend, type LocalFilesOptions, type MailAttachment, type MailMessage, MailServerTimeoutError, 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 PdfComponent, type PdfOptions, type PdfTemplate, PersonalMailError, type PersonalMailErrorCode, type PersonalMailProviderId, 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 StartWorkflowOptions, type StatusOptions, 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 UserTables, type VapidSettings, type ViewSource, type WebPushFields, type WebPushOptions, type WebhooksOptions, type WidgetOptions, type WorkflowsOptions, 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, callsMigration, capSocketMessages, changesMigration, channelSendsMigration, checkAi, checkAuthSchema, checkCalls, checkChanges, checkChannelSends, checkComments, checkComputed, checkJobsSchema, checkNotificationsSchema, checkQueryRunner, checkRealtimeRelay, checkResourcesOutsideAuthStore, checkSourceNames, checkStatusSchema, checkUserTablesSchema, checkViewQueries, checkWebhooksSchema, checkWorkflowsSchema, coalesce, commentTools, commentsMigration, computedMigration, configExports, consoleAuditQueue, conversationKey, countRecordsTool, createAdmin, createConversations, createEncryptionTools, createFileCleanup, createOrganization, createRecordTool, createUser, defaultAppName, defaultHistoryPage, defaultSignInPolicy, defineEmail, defineFunction, defineJob, definePdf, 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, onDoNotDisturb, ownedDefinitionsMigration, parseColumns, parseRoles, passwordLength, pluginOptions, pluginsBeforeSignIn, pluginsFor, pluginsVariable, pngIcons, poolWaitMs, postgresDeviceStore, postgresHistoryStore, processor, pushMessage, queryRunnerMigration, readDbConnections, readDefinitionsVersion, readJson, readOnlyQueryTool, readOperatorSettings, readOutbox, readPlatformSignIn, readRecordTools, readVapidSettings, readWriteQueryTool, readableResources, realtimeLimits, realtimeRelayMigration, recordOf, refusalOf, refused, relayed, reloadingAdmin, 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, sqlSends, startErrorReporting, startJobs, startWorkflow, statusMigration, statusNames, storageCleanupSchedule, storageProvider, storeFile, storeUsage, streamChatCompletion, subscription, supabasePath, thumbHash, unauthorized, updateRecordTool, urgencies, userTableResources, userTablesMigration, vapidPrivateKeyVariable, vapidPublicKeyVariable, vapidSubjectVariable, variantPath, verifySignature, verifyStandardWebhook, waitUntil, wantsLongQueries, webManifestPath, webPushSender, webauthnPath, webhooksMigration, widget, withCacheBreakpoints, withTrustedApps, workflowInstructions, workflowReadTools, workflowWriteTools, workflowsMigration, writeRecordTools };
|
package/dist/index.js
CHANGED
|
@@ -55,7 +55,10 @@ import { deleteRecordTool } from "./assistant/delete-record-tool.js";
|
|
|
55
55
|
import { createRecordTool, updateRecordTool, writeRecordTools } from "./assistant/write-record-tools.js";
|
|
56
56
|
import { batchWriteTool } from "./assistant/batch-write-tool.js";
|
|
57
57
|
import { addCommentTool, commentTools, listCommentsTool } from "./assistant/comment-tools.js";
|
|
58
|
+
import { workflowReadTools } from "./workflows/mcp/workflow-read-tools.js";
|
|
59
|
+
import { workflowWriteTools } from "./workflows/mcp/workflow-write-tools.js";
|
|
58
60
|
import { mcpTools } from "./mcp/mcp-tools.js";
|
|
61
|
+
import { workflowInstructions } from "./workflows/mcp/workflow-instructions.js";
|
|
59
62
|
import { addAppRoleToOwners, addMember, createOrganization, listOrganizations, removeMember, setMemberRoles } from "./better-auth/organizations/host.js";
|
|
60
63
|
import { createUser, deleteUser, hasUsers, listUsers, roleChoices, setUserBanned, setUserRole } from "./better-auth/users.js";
|
|
61
64
|
import { coalesce } from "./changes/coalesce.js";
|
|
@@ -142,4 +145,4 @@ import { defineEmail } from "./mail/define-email.js";
|
|
|
142
145
|
import { definePdf } from "./pdf/define-pdf.js";
|
|
143
146
|
import { startJobs } from "./jobs/runtime.js";
|
|
144
147
|
import { jobsDrainApp } from "./jobs/drain-route.js";
|
|
145
|
-
export { ConnectionsBusy, DeviceStoreError, FatalJobError, HistoryStoreError, HttpProblem, MailServerTimeoutError, MissingCredentials, ModelError, NotificationSendError, PersonalMailError, PushMessageError, addAppRoleToOwners, addCommentTool, addMember, apiKeyCaller, apiKeySettings, apiKeyVariables, appNameOver, aspectRatio, assistantProtocolRoutes, assistantRoles, assistantSettings, assistantVariables, authSchemaDrift, authSchemaMigration, authStoreTables, backgroundSettled, badRequest, batchWriteTool, beforeSignInPluginsVariable, betterAuthTokens, callsMigration, capSocketMessages, changesMigration, channelSendsMigration, checkAi, checkAuthSchema, checkCalls, checkChanges, checkChannelSends, checkComments, checkComputed, checkJobsSchema, checkNotificationsSchema, checkQueryRunner, checkRealtimeRelay, checkResourcesOutsideAuthStore, checkSourceNames, checkStatusSchema, checkUserTablesSchema, checkViewQueries, checkWebhooksSchema, checkWorkflowsSchema, coalesce, commentTools, commentsMigration, computedMigration, configExports, consoleAuditQueue, conversationKey, countRecordsTool, createAdmin, createConversations, createEncryptionTools, createFileCleanup, createOrganization, createRecordTool, createUser, defaultAppName, defaultHistoryPage, defaultSignInPolicy, defineEmail, defineFunction, defineJob, definePdf, 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, onDoNotDisturb, ownedDefinitionsMigration, parseColumns, parseRoles, passwordLength, pluginOptions, pluginsBeforeSignIn, pluginsFor, pluginsVariable, pngIcons, poolWaitMs, postgresDeviceStore, postgresHistoryStore, processor, pushMessage, queryRunnerMigration, readDbConnections, readDefinitionsVersion, readJson, readOnlyQueryTool, readOperatorSettings, readOutbox, readPlatformSignIn, readRecordTools, readVapidSettings, readWriteQueryTool, readableResources, realtimeLimits, realtimeRelayMigration, recordOf, refusalOf, refused, relayed, reloadingAdmin, 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, sqlSends, startErrorReporting, startJobs, startWorkflow, statusMigration, statusNames, storageCleanupSchedule, storageProvider, storeFile, storeUsage, streamChatCompletion, subscription, supabasePath, thumbHash, unauthorized, updateRecordTool, urgencies, userTableResources, userTablesMigration, vapidPrivateKeyVariable, vapidPublicKeyVariable, vapidSubjectVariable, variantPath, verifySignature, verifyStandardWebhook, waitUntil, wantsLongQueries, webManifestPath, webPushSender, webauthnPath, webhooksMigration, widget, withCacheBreakpoints, withTrustedApps, workflowsMigration, writeRecordTools };
|
|
148
|
+
export { ConnectionsBusy, DeviceStoreError, FatalJobError, HistoryStoreError, HttpProblem, MailServerTimeoutError, MissingCredentials, ModelError, NotificationSendError, PersonalMailError, PushMessageError, addAppRoleToOwners, addCommentTool, addMember, apiKeyCaller, apiKeySettings, apiKeyVariables, appNameOver, aspectRatio, assistantProtocolRoutes, assistantRoles, assistantSettings, assistantVariables, authSchemaDrift, authSchemaMigration, authStoreTables, backgroundSettled, badRequest, batchWriteTool, beforeSignInPluginsVariable, betterAuthTokens, callsMigration, capSocketMessages, changesMigration, channelSendsMigration, checkAi, checkAuthSchema, checkCalls, checkChanges, checkChannelSends, checkComments, checkComputed, checkJobsSchema, checkNotificationsSchema, checkQueryRunner, checkRealtimeRelay, checkResourcesOutsideAuthStore, checkSourceNames, checkStatusSchema, checkUserTablesSchema, checkViewQueries, checkWebhooksSchema, checkWorkflowsSchema, coalesce, commentTools, commentsMigration, computedMigration, configExports, consoleAuditQueue, conversationKey, countRecordsTool, createAdmin, createConversations, createEncryptionTools, createFileCleanup, createOrganization, createRecordTool, createUser, defaultAppName, defaultHistoryPage, defaultSignInPolicy, defineEmail, defineFunction, defineJob, definePdf, 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, onDoNotDisturb, ownedDefinitionsMigration, parseColumns, parseRoles, passwordLength, pluginOptions, pluginsBeforeSignIn, pluginsFor, pluginsVariable, pngIcons, poolWaitMs, postgresDeviceStore, postgresHistoryStore, processor, pushMessage, queryRunnerMigration, readDbConnections, readDefinitionsVersion, readJson, readOnlyQueryTool, readOperatorSettings, readOutbox, readPlatformSignIn, readRecordTools, readVapidSettings, readWriteQueryTool, readableResources, realtimeLimits, realtimeRelayMigration, recordOf, refusalOf, refused, relayed, reloadingAdmin, 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, sqlSends, startErrorReporting, startJobs, startWorkflow, statusMigration, statusNames, storageCleanupSchedule, storageProvider, storeFile, storeUsage, streamChatCompletion, subscription, supabasePath, thumbHash, unauthorized, updateRecordTool, urgencies, userTableResources, userTablesMigration, vapidPrivateKeyVariable, vapidPublicKeyVariable, vapidSubjectVariable, variantPath, verifySignature, verifyStandardWebhook, waitUntil, wantsLongQueries, webManifestPath, webPushSender, webauthnPath, webhooksMigration, widget, withCacheBreakpoints, withTrustedApps, workflowInstructions, workflowReadTools, workflowWriteTools, workflowsMigration, writeRecordTools };
|
package/dist/mcp/mcp-tools.d.ts
CHANGED
|
@@ -18,14 +18,20 @@ 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
|
+
/**
|
|
22
|
+
* Offers the workflow tools, for an app with `workflows`: reading workflows, their types and runs, and checking
|
|
23
|
+
* definitions; with writes on, `create_workflow`, `update_workflow` and `run_workflow` too. Off by default.
|
|
24
|
+
*/
|
|
25
|
+
workflows?: boolean;
|
|
21
26
|
/** The app's own tools, offered to every caller after the built-in ones. */
|
|
22
27
|
own?: AssistantTool[];
|
|
23
28
|
};
|
|
24
29
|
/**
|
|
25
30
|
* 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
|
|
31
|
+
* takes comments, the workflow tools with `workflows`, read-only SQL when `sql` is on and the caller sees the
|
|
32
|
+
* assistant, then the app's own tools. The
|
|
27
33
|
* tools are built once and only chosen per session: the MCP server compiles each tool's schema, and a schema object made
|
|
28
34
|
* per request would be compiled again.
|
|
29
35
|
*/
|
|
30
|
-
export declare const mcpTools: ({ db, sql, writes, commented, own }: McpToolsInput) => (session: Session) => AssistantTool[];
|
|
36
|
+
export declare const mcpTools: ({ db, sql, writes, commented, workflows, own }: McpToolsInput) => (session: Session) => AssistantTool[];
|
|
31
37
|
//#endregion
|
package/dist/mcp/mcp-tools.js
CHANGED
|
@@ -5,13 +5,17 @@ import { readRecordTools } from "../assistant/record-tools.js";
|
|
|
5
5
|
import { writeRecordTools } from "../assistant/write-record-tools.js";
|
|
6
6
|
import { batchWriteTool } from "../assistant/batch-write-tool.js";
|
|
7
7
|
import { commentTools } from "../assistant/comment-tools.js";
|
|
8
|
+
import { workflowReadTools } from "../workflows/mcp/workflow-read-tools.js";
|
|
9
|
+
import { workflowWriteTools } from "../workflows/mcp/workflow-write-tools.js";
|
|
8
10
|
//#region src/mcp/mcp-tools.ts
|
|
9
11
|
const writeTools = (input) => [...writeRecordTools(input), batchWriteTool(input)];
|
|
10
12
|
const builtInNames = (db) => new Set([
|
|
11
13
|
...readRecordTools(),
|
|
12
14
|
...writeTools({}),
|
|
13
15
|
...commentTools({ commented: ["comments"] }, {}),
|
|
14
|
-
readOnlyQueryTool(db)
|
|
16
|
+
readOnlyQueryTool(db),
|
|
17
|
+
...workflowReadTools(),
|
|
18
|
+
...workflowWriteTools()
|
|
15
19
|
].map((tool) => tool.name));
|
|
16
20
|
/** The app's own tools; one whose name is taken twice, or by a built-in tool, stops the server at startup. */
|
|
17
21
|
const checkedOwnTools = (db, own) => {
|
|
@@ -22,21 +26,24 @@ const checkedOwnTools = (db, own) => {
|
|
|
22
26
|
};
|
|
23
27
|
/**
|
|
24
28
|
* 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
|
|
29
|
+
* takes comments, the workflow tools with `workflows`, read-only SQL when `sql` is on and the caller sees the
|
|
30
|
+
* assistant, then the app's own tools. The
|
|
26
31
|
* tools are built once and only chosen per session: the MCP server compiles each tool's schema, and a schema object made
|
|
27
32
|
* per request would be compiled again.
|
|
28
33
|
*/
|
|
29
|
-
const mcpTools = ({ db, sql, writes, commented = [], own = [] }) => {
|
|
34
|
+
const mcpTools = ({ db, sql, writes, commented = [], workflows = false, own = [] }) => {
|
|
30
35
|
const ownTools = checkedOwnTools(db, own);
|
|
31
36
|
const read = readRecordTools();
|
|
32
37
|
const limits = writes === false ? false : writes === true || !writes.resources ? {} : { resources: writes.resources };
|
|
33
38
|
const write = limits === false ? [] : writeTools(limits);
|
|
34
39
|
const comments = commentTools({ commented }, limits);
|
|
40
|
+
const flows = workflows ? [...workflowReadTools(), ...limits === false ? [] : workflowWriteTools(limits)] : [];
|
|
35
41
|
const query = sql ? [readOnlyQueryTool(db)] : [];
|
|
36
42
|
return (session) => [
|
|
37
43
|
...read,
|
|
38
44
|
...write,
|
|
39
45
|
...comments,
|
|
46
|
+
...flows,
|
|
40
47
|
...seesAssistant(session.user.roles) ? query : [],
|
|
41
48
|
...ownTools
|
|
42
49
|
];
|
package/dist/package.js
CHANGED
package/dist/webhooks/attempt.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
+
import { AddressRefusedError, pinnedRequest } from "./table/pinned-request.js";
|
|
2
|
+
import { readLimited } from "./table/read-limited.js";
|
|
1
3
|
import { invokeFunction } from "../functions/define-function.js";
|
|
2
4
|
import { retryAfterMs } from "./retry-schedule.js";
|
|
3
5
|
import { standardWebhookHeaders } from "./standard-webhooks.js";
|
|
4
6
|
import { subscriberSession } from "./subscriber-view.js";
|
|
5
|
-
import { AddressRefusedError, pinnedRequest } from "./table/pinned-request.js";
|
|
6
|
-
import { readLimited } from "./table/read-limited.js";
|
|
7
7
|
//#region src/webhooks/attempt.ts
|
|
8
8
|
/** How long a receiver has to answer. */
|
|
9
9
|
const attemptTimeoutMs = 15e3;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { betterAuthOwnerRoles } from "./owner-roles.js";
|
|
2
1
|
import { checkWebhookUrl } from "./address-check.js";
|
|
2
|
+
import { betterAuthOwnerRoles } from "./owner-roles.js";
|
|
3
3
|
import { webhooksSchema } from "./options.js";
|
|
4
4
|
import { tableSubscriptions } from "./table-subscriptions.js";
|
|
5
5
|
//#region src/webhooks/table/webhook-table.ts
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
//#region src/workflows/mcp/merge-patch.ts
|
|
2
|
+
const isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3
|
+
/**
|
|
4
|
+
* `patch` applied to `target` as a JSON Merge Patch (RFC 7396): an object merges into the object at its place, a member
|
|
5
|
+
* set to null is removed, and any other value, a list included, replaces what was there. Neither input is changed.
|
|
6
|
+
*/
|
|
7
|
+
const mergePatch = (target, patch) => {
|
|
8
|
+
if (!isObject(patch)) return patch;
|
|
9
|
+
const merged = isObject(target) ? { ...target } : {};
|
|
10
|
+
for (const [name, value] of Object.entries(patch)) if (value === null) delete merged[name];
|
|
11
|
+
else merged[name] = mergePatch(merged[name], value);
|
|
12
|
+
return merged;
|
|
13
|
+
};
|
|
14
|
+
//#endregion
|
|
15
|
+
export { mergePatch };
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
//#region src/workflows/mcp/type-catalog.ts
|
|
2
|
+
const needs = (takes) => ({
|
|
3
|
+
required: true,
|
|
4
|
+
takes
|
|
5
|
+
});
|
|
6
|
+
const may = (takes) => ({
|
|
7
|
+
required: false,
|
|
8
|
+
takes
|
|
9
|
+
});
|
|
10
|
+
const resource = needs("the resource's name, as list_resources gives it");
|
|
11
|
+
const key = needs("the record's key");
|
|
12
|
+
const refused = ["Records.Refused: the API refused it, its status and message in the cause", "Records.InvalidArguments: an argument has the wrong type, named in the cause"];
|
|
13
|
+
/** The step types a Task names as its `Resource`, as the workflows reference describes them. */
|
|
14
|
+
const stepTypeCatalog = [
|
|
15
|
+
{
|
|
16
|
+
resource: "app:http",
|
|
17
|
+
does: "Sends one HTTP request to a public address and reads the answer.",
|
|
18
|
+
arguments: {
|
|
19
|
+
url: needs("an http or https URL"),
|
|
20
|
+
method: may("GET (default), POST, PUT, PATCH or DELETE"),
|
|
21
|
+
headers: may("an object of text values"),
|
|
22
|
+
query: may("an object of text values, added to the URL's query"),
|
|
23
|
+
body: may("text sent as it is, a file value sent as its bytes, any other value sent as JSON; a GET sends none"),
|
|
24
|
+
response: may("how the answer's body is read: json (default; empty is null), text, or file (kept as a file value of the run)")
|
|
25
|
+
},
|
|
26
|
+
result: "{ status, headers, body }",
|
|
27
|
+
errors: [
|
|
28
|
+
"Http.Status: a status outside 2xx; the cause names it with the start of the body",
|
|
29
|
+
"Http.Network: no answer",
|
|
30
|
+
"Http.InvalidArguments: an argument has the wrong type, named in the cause",
|
|
31
|
+
"Http.InvalidBody: response json, and the body is not JSON",
|
|
32
|
+
"Http.AddressRefused: the host is not a public address",
|
|
33
|
+
"Http.ResponseTooLarge: a body over 10 MiB"
|
|
34
|
+
]
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
resource: "app:records.create",
|
|
38
|
+
does: "Creates records of the app's resources, as the workflow's last editor.",
|
|
39
|
+
arguments: {
|
|
40
|
+
resource,
|
|
41
|
+
data: needs("an object of field values, or a list of them, created in one batch: all or none")
|
|
42
|
+
},
|
|
43
|
+
result: "the created record, or the list of them",
|
|
44
|
+
errors: refused
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
resource: "app:records.update",
|
|
48
|
+
does: "Updates one record, as the workflow's last editor.",
|
|
49
|
+
arguments: {
|
|
50
|
+
resource,
|
|
51
|
+
key,
|
|
52
|
+
data: needs("an object of the field values to change")
|
|
53
|
+
},
|
|
54
|
+
result: "the updated record",
|
|
55
|
+
errors: refused
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
resource: "app:records.get",
|
|
59
|
+
does: "Reads one record, as the workflow's last editor.",
|
|
60
|
+
arguments: {
|
|
61
|
+
resource,
|
|
62
|
+
key
|
|
63
|
+
},
|
|
64
|
+
result: "the record",
|
|
65
|
+
errors: refused
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
resource: "app:records.list",
|
|
69
|
+
does: "Lists records, as the workflow's last editor.",
|
|
70
|
+
arguments: {
|
|
71
|
+
resource,
|
|
72
|
+
filter: may("a filter in the API's syntax, such as status = \"open\""),
|
|
73
|
+
order_by: may("such as \"created_at desc\""),
|
|
74
|
+
limit: may("how many at most")
|
|
75
|
+
},
|
|
76
|
+
result: "{ records }",
|
|
77
|
+
errors: refused
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
resource: "app:mail.send",
|
|
81
|
+
does: "Sends a mail from the app's own sender.",
|
|
82
|
+
arguments: {
|
|
83
|
+
to: needs("one address, or a list of at most 50"),
|
|
84
|
+
subject: needs("one line of text"),
|
|
85
|
+
text: needs("the body as text"),
|
|
86
|
+
html: may("the body as HTML too"),
|
|
87
|
+
attachments: may("a list of file values the run keeps, at most 10 MiB together")
|
|
88
|
+
},
|
|
89
|
+
result: "{ message_id }",
|
|
90
|
+
errors: ["Mail.InvalidArguments: an argument has the wrong type, named in the cause", "States.TaskFailed: the app sends no mail, or the mailer refused it"]
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
resource: "app:piece",
|
|
94
|
+
does: "Runs an action of a connector. Not implemented yet: a run fails it.",
|
|
95
|
+
arguments: {
|
|
96
|
+
piece: needs("the connector's name"),
|
|
97
|
+
action: needs("one of its actions"),
|
|
98
|
+
connection: needs("the connection holding its sign-in"),
|
|
99
|
+
props: may("the action's arguments, by name")
|
|
100
|
+
},
|
|
101
|
+
result: "what the action returns",
|
|
102
|
+
errors: ["Piece.Error: always, until connectors are implemented"]
|
|
103
|
+
}
|
|
104
|
+
];
|
|
105
|
+
/** The triggers a definition's `trigger` holds exactly one of. */
|
|
106
|
+
const triggerCatalog = [
|
|
107
|
+
{
|
|
108
|
+
trigger: "manual",
|
|
109
|
+
example: { manual: {} },
|
|
110
|
+
starts: "when someone runs the workflow, such as with run_workflow",
|
|
111
|
+
input: "the input they give"
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
trigger: "webhook",
|
|
115
|
+
example: { webhook: {} },
|
|
116
|
+
starts: "on each POST to the workflow's webhook address",
|
|
117
|
+
input: "{ body, headers, query }"
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
trigger: "schedule",
|
|
121
|
+
example: { schedule: {
|
|
122
|
+
cron: "0 6 * * 1-5",
|
|
123
|
+
timezone: "Europe/Amsterdam"
|
|
124
|
+
} },
|
|
125
|
+
starts: "on a 5-field cron, in an IANA time zone (default UTC)",
|
|
126
|
+
input: "{ scheduled_at }, ISO 8601 with the zone's offset"
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
trigger: "mail",
|
|
130
|
+
example: { mail: { mailbox: "support" } },
|
|
131
|
+
starts: "on each mail arriving in that mailbox",
|
|
132
|
+
input: "{ from, to, subject, text, html, received_at, attachments }"
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
trigger: "record",
|
|
136
|
+
example: { record: { events: ["orders.created"] } },
|
|
137
|
+
starts: "on each of those record events, <resource>.<event>",
|
|
138
|
+
input: "{ event, resource, key, after, before, changed }"
|
|
139
|
+
}
|
|
140
|
+
];
|
|
141
|
+
/** Only manual runs start for now: a save takes the other triggers, which start nothing yet. */
|
|
142
|
+
const triggerNote = "Runs start only by hand (run_workflow) for now: a workflow saves with any trigger, and the other triggers start no runs yet.";
|
|
143
|
+
//#endregion
|
|
144
|
+
export { stepTypeCatalog, triggerCatalog, triggerNote };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
//#region src/workflows/mcp/workflow-instructions.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* What the MCP endpoint tells a client about workflows, beside reading records: the language, in the named-reads
|
|
4
|
+
* profile of the States Language with JSONata, and how the workflow tools find, make, change and test-run them.
|
|
5
|
+
*/
|
|
6
|
+
export declare const workflowInstructions: string;
|
|
7
|
+
//#endregion
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { limits } from "@protobase/workflows";
|
|
2
|
+
/**
|
|
3
|
+
* What the MCP endpoint tells a client about workflows, beside reading records: the language, in the named-reads
|
|
4
|
+
* profile of the States Language with JSONata, and how the workflow tools find, make, change and test-run them.
|
|
5
|
+
*/
|
|
6
|
+
const workflowInstructions = [
|
|
7
|
+
"Workflows: list_workflow_types names the step types and triggers with their arguments and inputs; list_workflows and get_workflow read workflows; validate_workflow checks a definition without saving it; create_workflow and update_workflow save one; run_workflow starts a run with an input, and get_workflow_run reads it with each step's input, output, logs and error. A run goes on in the background: read it again until its status is succeeded or failed, then fix the step that failed with update_workflow and run it again.",
|
|
8
|
+
"A definition is one JSON document: { \"trigger\": <exactly one trigger>, \"stateMachine\": { \"QueryLanguage\": \"JSONata\", \"StartAt\": \"<state>\", \"States\": { \"<state>\": <state>, ... } } }, the States Language with JSONata expressions, in a profile where every value is read by name. It holds no layout.",
|
|
9
|
+
"States: Task { Resource: <step type>, Arguments, TimeoutSeconds, Retry, Catch }; Pass { Output, Assign }; Choice { Choices: [{ Condition, Next, Output?, Assign? }], Default }; Map { Items, ItemProcessor: { StartAt, States }, MaxConcurrency }; Wait { Seconds | Timestamp }; Succeed { Output }; Fail { Error, Cause }. Every state may have Comment, Output and Assign. Task, Pass, Map and Wait have exactly one of Next (a state name) and End: true. State names are unique across the document, Maps included, and every state must be reachable from StartAt. InputPath, Parameters, ResultPath and the other JSONPath fields, and Parallel, are not part of the profile.",
|
|
10
|
+
"Expressions: a string that is whole \"{% ... %}\" is a JSONata expression whose value, of any type, the field takes; any other string is literal text. Build text inside one expression with &: \"{% 'Order ' & $order.id %}\". Expressions sit at any depth in Arguments, Output and Assign, and in Items, Condition, Seconds, Timestamp, Error and Cause.",
|
|
11
|
+
"Reading values by name, the only reads allowed: $states.context.Execution.Input (the run's input, in every state), $states.context.Execution.StartTime, $states.context.Secrets.<NAME> (a secret the app lists), $<variable> (assigned by a state that runs before), $states.result (a Task's or Map's result, in its Output and Assign), $states.errorOutput ({ Error, Cause }, in a Catch rule's Output and Assign), $states.input (the item, only in the first state of a Map's ItemProcessor). Never a bare field name, $ or $$ on its own, or $eval. Keep a result for later states with \"Assign\": { \"order\": \"{% $states.result %}\" }, read as $order from the next state on; a state inside a Map may not assign a name an outer state assigns.",
|
|
12
|
+
`Errors: a failing Task or Map fails with an error name, its step type's own (list_workflow_types names them), States.Timeout, States.TaskFailed, or States.QueryEvaluationError for an expression that failed. Retry: [{ ErrorEquals: ["States.ALL"], IntervalSeconds, MaxAttempts (retries after the first try, at most ${limits.retryAttempts}), BackoffRate }]; Catch: [{ ErrorEquals, Next, Assign }] goes on to Next once no retry is left; without a Catch the run fails. A Choice whose rules all fail and that has no Default fails with States.NoChoiceMatched.`,
|
|
13
|
+
`Limits: ${limits.states} states, a Map's MaxConcurrency ${limits.mapConcurrency}, a Task's TimeoutSeconds ${limits.taskTimeoutSeconds}.`,
|
|
14
|
+
`A saved or validated definition that does not fit is refused with problems, each { state, field, message }: the state (a Map's inner state as Map/State), the field such as Arguments.url, and what was expected. Fix those fields and try again; nothing was saved. An example: ${JSON.stringify({
|
|
15
|
+
trigger: { manual: {} },
|
|
16
|
+
stateMachine: {
|
|
17
|
+
QueryLanguage: "JSONata",
|
|
18
|
+
StartAt: "GetQuote",
|
|
19
|
+
States: {
|
|
20
|
+
GetQuote: {
|
|
21
|
+
Type: "Task",
|
|
22
|
+
Resource: "app:records.get",
|
|
23
|
+
Arguments: {
|
|
24
|
+
resource: "quotes",
|
|
25
|
+
key: "{% $states.context.Execution.Input.quote_id %}"
|
|
26
|
+
},
|
|
27
|
+
Assign: { quote: "{% $states.result %}" },
|
|
28
|
+
Next: "IsDraft"
|
|
29
|
+
},
|
|
30
|
+
IsDraft: {
|
|
31
|
+
Type: "Choice",
|
|
32
|
+
Choices: [{
|
|
33
|
+
Condition: "{% $quote.status = 'draft' %}",
|
|
34
|
+
Next: "Notify"
|
|
35
|
+
}],
|
|
36
|
+
Default: "NotADraft"
|
|
37
|
+
},
|
|
38
|
+
NotADraft: {
|
|
39
|
+
Type: "Fail",
|
|
40
|
+
Error: "Quote.NotDraft",
|
|
41
|
+
Cause: "{% 'Quote ' & $quote.id & ' is not a draft' %}"
|
|
42
|
+
},
|
|
43
|
+
Notify: {
|
|
44
|
+
Type: "Task",
|
|
45
|
+
Resource: "app:http",
|
|
46
|
+
Arguments: {
|
|
47
|
+
method: "POST",
|
|
48
|
+
url: "https://hooks.example.com/quotes",
|
|
49
|
+
body: {
|
|
50
|
+
id: "{% $quote.id %}",
|
|
51
|
+
total: "{% $quote.total %}"
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
Retry: [{
|
|
55
|
+
ErrorEquals: ["States.ALL"],
|
|
56
|
+
IntervalSeconds: 10,
|
|
57
|
+
MaxAttempts: 3
|
|
58
|
+
}],
|
|
59
|
+
Catch: [{
|
|
60
|
+
ErrorEquals: ["States.ALL"],
|
|
61
|
+
Next: "Failed",
|
|
62
|
+
Assign: { notifyError: "{% $states.errorOutput %}" }
|
|
63
|
+
}],
|
|
64
|
+
Output: "{% $states.result.body %}",
|
|
65
|
+
End: true
|
|
66
|
+
},
|
|
67
|
+
Failed: {
|
|
68
|
+
Type: "Fail",
|
|
69
|
+
Error: "Notify.Failed",
|
|
70
|
+
Cause: "{% $notifyError.Cause %}"
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
})}`
|
|
75
|
+
].join("\n");
|
|
76
|
+
//#endregion
|
|
77
|
+
export { workflowInstructions };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { AssistantTool, ToolContext } from "../../assistant/tools.js";
|
|
2
|
+
import "../../index.js";
|
|
3
|
+
//#region src/workflows/mcp/workflow-read-tools.d.ts
|
|
4
|
+
/** The tools that read workflows, their types and runs, and check definitions. */
|
|
5
|
+
export declare const workflowReadTools: () => AssistantTool<ToolContext>[];
|
|
6
|
+
//#endregion
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { defineTool } from "../../assistant/tools.js";
|
|
2
|
+
import { refused } from "../../assistant/record-tool-args.js";
|
|
3
|
+
import { stepTypeNames } from "../step-types.js";
|
|
4
|
+
import { mergePatch } from "./merge-patch.js";
|
|
5
|
+
import { stepTypeCatalog, triggerCatalog, triggerNote } from "./type-catalog.js";
|
|
6
|
+
import { definitionParameter, editParameter, idOf, idParameter, objectOf, pageSizeOf, textOf } from "./workflow-tool-args.js";
|
|
7
|
+
import { checkDefinition } from "@protobase/workflows";
|
|
8
|
+
//#region src/workflows/mcp/workflow-read-tools.ts
|
|
9
|
+
const reads = {
|
|
10
|
+
readOnlyHint: true,
|
|
11
|
+
openWorldHint: false
|
|
12
|
+
};
|
|
13
|
+
const listed = [
|
|
14
|
+
"id",
|
|
15
|
+
"name",
|
|
16
|
+
"description",
|
|
17
|
+
"version",
|
|
18
|
+
"updated_at"
|
|
19
|
+
];
|
|
20
|
+
/** The step types and triggers a definition may name, with their arguments, results, errors and run inputs. */
|
|
21
|
+
const listWorkflowTypesTool = defineTool({
|
|
22
|
+
name: "list_workflow_types",
|
|
23
|
+
description: "Lists what a workflow definition may name, as JSON ({ step_types, triggers }): each step type a Task names as its Resource, with its Arguments (required or not, and what each takes), its result and the errors it fails with; and each trigger, how the definition holds it and the input a run gets from it.",
|
|
24
|
+
parameters: {
|
|
25
|
+
type: "object",
|
|
26
|
+
properties: {}
|
|
27
|
+
},
|
|
28
|
+
annotations: reads,
|
|
29
|
+
run: async () => JSON.stringify({
|
|
30
|
+
step_types: stepTypeCatalog,
|
|
31
|
+
triggers: triggerCatalog,
|
|
32
|
+
note: triggerNote
|
|
33
|
+
})
|
|
34
|
+
});
|
|
35
|
+
/** The workflows the caller may read, a page at a time, without their definitions. */
|
|
36
|
+
const listWorkflowsTool = defineTool({
|
|
37
|
+
name: "list_workflows",
|
|
38
|
+
description: "Lists the workflows you may read, newest change first, as JSON ({ items, next_page_token }), each with its id, name, description and version but not its definition: get_workflow reads that. filter uses AIP-160 on name, such as name = \"Daily report\"; pass next_page_token back as page_token for the next page.",
|
|
39
|
+
parameters: {
|
|
40
|
+
type: "object",
|
|
41
|
+
properties: {
|
|
42
|
+
filter: {
|
|
43
|
+
type: "string",
|
|
44
|
+
description: "Such as name = \"Daily report\"."
|
|
45
|
+
},
|
|
46
|
+
page_size: {
|
|
47
|
+
type: "integer",
|
|
48
|
+
minimum: 1,
|
|
49
|
+
maximum: 100,
|
|
50
|
+
description: "Workflows per page, 20 by default."
|
|
51
|
+
},
|
|
52
|
+
page_token: {
|
|
53
|
+
type: "string",
|
|
54
|
+
description: "The next_page_token of the page before."
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
annotations: reads,
|
|
59
|
+
run: async (args, context) => {
|
|
60
|
+
const params = {
|
|
61
|
+
fields: listed,
|
|
62
|
+
order_by: "updated_at desc",
|
|
63
|
+
page_size: pageSizeOf(args.page_size, 20, 100),
|
|
64
|
+
...textOf(args.filter) && { filter: textOf(args.filter) },
|
|
65
|
+
...textOf(args.page_token) && { page_token: textOf(args.page_token) }
|
|
66
|
+
};
|
|
67
|
+
return context.records.list("workflows", params).then((page) => JSON.stringify({
|
|
68
|
+
items: page.items.map(({ etag: _etag, permissions: _permissions, ...item }) => item),
|
|
69
|
+
next_page_token: page.next_page_token
|
|
70
|
+
}), refused);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
/** One workflow with its definition and the etag an update can pass. */
|
|
74
|
+
const getWorkflowTool = defineTool({
|
|
75
|
+
name: "get_workflow",
|
|
76
|
+
description: "Reads one workflow by its id, as JSON ({ workflow, etag }): its name, description, version and whole definition. Pass the etag to update_workflow to fail when someone changed it since.",
|
|
77
|
+
parameters: {
|
|
78
|
+
type: "object",
|
|
79
|
+
properties: { id: idParameter },
|
|
80
|
+
required: ["id"]
|
|
81
|
+
},
|
|
82
|
+
annotations: reads,
|
|
83
|
+
run: async (args, context) => {
|
|
84
|
+
const id = idOf(args);
|
|
85
|
+
if (!id) return "Pass the workflow's id as { \"id\": \"<id>\" }.";
|
|
86
|
+
return context.records.get("workflows", id).then(({ record, etag }) => JSON.stringify({
|
|
87
|
+
workflow: record,
|
|
88
|
+
etag
|
|
89
|
+
}), refused);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
const checked = (definition) => {
|
|
93
|
+
const problems = checkDefinition(definition, { resources: stepTypeNames });
|
|
94
|
+
return JSON.stringify(problems.length > 0 ? {
|
|
95
|
+
valid: false,
|
|
96
|
+
problems
|
|
97
|
+
} : { valid: true });
|
|
98
|
+
};
|
|
99
|
+
/** Checks a definition, or a saved one with an edit, without saving anything. */
|
|
100
|
+
const validateWorkflowTool = defineTool({
|
|
101
|
+
name: "validate_workflow",
|
|
102
|
+
description: "Checks a definition as a save checks it, and saves nothing: pass a whole definition, or a workflow's id with an edit to check that edit of its saved definition. Returns JSON { valid: true }, or { valid: false, problems }, each problem { state, field, message } naming the state, the field and what was expected.",
|
|
103
|
+
parameters: {
|
|
104
|
+
type: "object",
|
|
105
|
+
properties: {
|
|
106
|
+
definition: definitionParameter,
|
|
107
|
+
id: idParameter,
|
|
108
|
+
edit: editParameter
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
annotations: reads,
|
|
112
|
+
run: async (args, context) => {
|
|
113
|
+
const id = idOf(args);
|
|
114
|
+
const edit = objectOf(args.edit);
|
|
115
|
+
if (args.definition !== void 0 && id === void 0 && edit === void 0) return checked(args.definition);
|
|
116
|
+
if (args.definition === void 0 && id && edit) return context.records.get("workflows", id).then(({ record }) => checked(mergePatch(record.definition, edit)), refused);
|
|
117
|
+
return "Pass either { \"definition\": { ... } } or { \"id\": \"<id>\", \"edit\": { ... } }.";
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
const runFields = [
|
|
121
|
+
"id",
|
|
122
|
+
"workflow_id",
|
|
123
|
+
"version",
|
|
124
|
+
"status",
|
|
125
|
+
"input",
|
|
126
|
+
"output",
|
|
127
|
+
"error",
|
|
128
|
+
"cause",
|
|
129
|
+
"resume_at",
|
|
130
|
+
"created_at",
|
|
131
|
+
"finished_at",
|
|
132
|
+
"duration_ms"
|
|
133
|
+
];
|
|
134
|
+
const stepFields = [
|
|
135
|
+
"state",
|
|
136
|
+
"scope",
|
|
137
|
+
"seq",
|
|
138
|
+
"type",
|
|
139
|
+
"attempt",
|
|
140
|
+
"status",
|
|
141
|
+
"input",
|
|
142
|
+
"output",
|
|
143
|
+
"assigned",
|
|
144
|
+
"logs",
|
|
145
|
+
"error",
|
|
146
|
+
"cause",
|
|
147
|
+
"next",
|
|
148
|
+
"resume_at",
|
|
149
|
+
"duration_ms"
|
|
150
|
+
];
|
|
151
|
+
/** A run with its steps in the order they ran, a page at a time. */
|
|
152
|
+
const getWorkflowRunTool = defineTool({
|
|
153
|
+
name: "get_workflow_run",
|
|
154
|
+
description: "Reads a run by its id, as JSON ({ run, steps, next_page_token }): its status (queued, running, waiting, succeeded or failed), input, output, error and cause, and its steps in the order they ran, each with its state, scope (empty, or the Map item it ran in), try, status (succeeded, retrying, caught or failed), input, output, assigned variables, logs, error and cause. A run goes on in the background: read it again while it is queued, running or waiting. Pass state to read only that state's steps; pass next_page_token back as page_token for more steps.",
|
|
155
|
+
parameters: {
|
|
156
|
+
type: "object",
|
|
157
|
+
properties: {
|
|
158
|
+
run_id: {
|
|
159
|
+
type: "string",
|
|
160
|
+
description: "The run's id, as run_workflow gives it."
|
|
161
|
+
},
|
|
162
|
+
state: {
|
|
163
|
+
type: "string",
|
|
164
|
+
description: "Only this state's steps."
|
|
165
|
+
},
|
|
166
|
+
page_size: {
|
|
167
|
+
type: "integer",
|
|
168
|
+
minimum: 1,
|
|
169
|
+
maximum: 100,
|
|
170
|
+
description: "Steps per page, 20 by default."
|
|
171
|
+
},
|
|
172
|
+
page_token: {
|
|
173
|
+
type: "string",
|
|
174
|
+
description: "The next_page_token of the page before."
|
|
175
|
+
}
|
|
176
|
+
},
|
|
177
|
+
required: ["run_id"]
|
|
178
|
+
},
|
|
179
|
+
annotations: reads,
|
|
180
|
+
run: async (args, context) => {
|
|
181
|
+
const id = textOf(args.run_id);
|
|
182
|
+
if (!id) return "Pass the run's id as { \"run_id\": \"<id>\" }.";
|
|
183
|
+
const run = await context.records.get("workflow_runs", id, { fields: runFields }).then(({ record }) => record, refused);
|
|
184
|
+
if (typeof run === "string") return run;
|
|
185
|
+
const state = textOf(args.state);
|
|
186
|
+
const params = {
|
|
187
|
+
filter: `run_id = ${JSON.stringify(id)}${state ? ` AND state = ${JSON.stringify(state)}` : ""}`,
|
|
188
|
+
fields: stepFields,
|
|
189
|
+
order_by: "id",
|
|
190
|
+
page_size: pageSizeOf(args.page_size, 20, 100),
|
|
191
|
+
...textOf(args.page_token) && { page_token: textOf(args.page_token) }
|
|
192
|
+
};
|
|
193
|
+
return context.records.list("workflow_steps", params).then((page) => JSON.stringify({
|
|
194
|
+
run,
|
|
195
|
+
steps: page.items.map(({ etag: _etag, permissions: _permissions, ...step }) => step),
|
|
196
|
+
next_page_token: page.next_page_token
|
|
197
|
+
}), refused);
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
/** The tools that read workflows, their types and runs, and check definitions. */
|
|
201
|
+
const workflowReadTools = () => [
|
|
202
|
+
listWorkflowTypesTool,
|
|
203
|
+
listWorkflowsTool,
|
|
204
|
+
getWorkflowTool,
|
|
205
|
+
validateWorkflowTool,
|
|
206
|
+
getWorkflowRunTool
|
|
207
|
+
];
|
|
208
|
+
//#endregion
|
|
209
|
+
export { getWorkflowRunTool, getWorkflowTool, listWorkflowTypesTool, listWorkflowsTool, validateWorkflowTool, workflowReadTools };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { problemOf } from "../../error-handler.js";
|
|
2
|
+
import { refused } from "../../assistant/record-tool-args.js";
|
|
3
|
+
//#region src/workflows/mcp/workflow-tool-args.ts
|
|
4
|
+
const idParameter = {
|
|
5
|
+
type: "string",
|
|
6
|
+
description: "The workflow's id, as list_workflows gives it."
|
|
7
|
+
};
|
|
8
|
+
const definitionParameter = {
|
|
9
|
+
type: "object",
|
|
10
|
+
description: "The whole definition: { \"trigger\": { ... }, \"stateMachine\": { \"QueryLanguage\": \"JSONata\", \"StartAt\": ..., \"States\": { ... } } }, as the server's instructions describe it."
|
|
11
|
+
};
|
|
12
|
+
const editParameter = {
|
|
13
|
+
type: "object",
|
|
14
|
+
description: "A small edit of the saved definition as a JSON Merge Patch (RFC 7396): only what changes, at its place in the document. An object merges into the object there, null removes a member, and any other value, a list included, replaces it. Such as { \"stateMachine\": { \"States\": { \"Fetch\": { \"Arguments\": { \"url\": \"https://...\" } }, \"Old\": null } } }."
|
|
15
|
+
};
|
|
16
|
+
const idOf = (args) => typeof args.id === "string" && args.id ? args.id : void 0;
|
|
17
|
+
const objectOf = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
18
|
+
const textOf = (value) => typeof value === "string" ? value : void 0;
|
|
19
|
+
/** A page size given as a whole number, cut to `max`; `fallback` when none is given. */
|
|
20
|
+
const pageSizeOf = (value, fallback, max) => typeof value === "number" && Number.isInteger(value) && value > 0 ? Math.min(value, max) : fallback;
|
|
21
|
+
/**
|
|
22
|
+
* A save the workflow check refused, as `{ error, message, problems }`, each problem naming the state and field and
|
|
23
|
+
* what was expected; any other refusal as `refused` words it.
|
|
24
|
+
*/
|
|
25
|
+
const workflowRefused = (error) => {
|
|
26
|
+
const problem = problemOf(error);
|
|
27
|
+
if (problem?.slug === "invalid-workflow") return JSON.stringify({
|
|
28
|
+
error: problem.slug,
|
|
29
|
+
message: "The definition does not fit the language; nothing was saved. Fix each problem and try again.",
|
|
30
|
+
problems: problem.extras.problems
|
|
31
|
+
});
|
|
32
|
+
return refused(error);
|
|
33
|
+
};
|
|
34
|
+
//#endregion
|
|
35
|
+
export { definitionParameter, editParameter, idOf, idParameter, objectOf, pageSizeOf, textOf, workflowRefused };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { AssistantTool, ToolContext } from "../../assistant/tools.js";
|
|
2
|
+
import { WriteRecordToolsInput } from "../../assistant/write-tool-limits.js";
|
|
3
|
+
import "../../index.js";
|
|
4
|
+
//#region src/workflows/mcp/workflow-write-tools.d.ts
|
|
5
|
+
/** The tools that save and run workflows, on the resources `input` allows. */
|
|
6
|
+
export declare const workflowWriteTools: (input?: WriteRecordToolsInput) => AssistantTool<ToolContext>[];
|
|
7
|
+
//#endregion
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { notFound } from "../../problem.js";
|
|
2
|
+
import { defineTool } from "../../assistant/tools.js";
|
|
3
|
+
import { refused } from "../../assistant/record-tool-args.js";
|
|
4
|
+
import { createApproval, updateApproval } from "../../assistant/write-prompts.js";
|
|
5
|
+
import { approvalTexts, recordBefore } from "../../assistant/record-before.js";
|
|
6
|
+
import { notApproved, onlyNote, outside } from "../../assistant/write-tool-limits.js";
|
|
7
|
+
import { mergePatch } from "./merge-patch.js";
|
|
8
|
+
import { definitionParameter, editParameter, idOf, idParameter, objectOf, textOf, workflowRefused } from "./workflow-tool-args.js";
|
|
9
|
+
//#region src/workflows/mcp/workflow-write-tools.ts
|
|
10
|
+
/**
|
|
11
|
+
* `create_workflow`, `update_workflow` and `run_workflow`: saving and running workflows as the caller, through the
|
|
12
|
+
* resources' access rules and the save check, each after the user approves it in their client, as the record write
|
|
13
|
+
* tools ask. A refused definition comes back with its problems, each naming the state and field.
|
|
14
|
+
*/
|
|
15
|
+
const writes = {
|
|
16
|
+
readOnlyHint: false,
|
|
17
|
+
destructiveHint: false,
|
|
18
|
+
openWorldHint: false
|
|
19
|
+
};
|
|
20
|
+
const nameParameter = {
|
|
21
|
+
type: "string",
|
|
22
|
+
description: "A name, 1 to 100 characters."
|
|
23
|
+
};
|
|
24
|
+
const descriptionParameter = {
|
|
25
|
+
type: "string",
|
|
26
|
+
description: "What it does, for people; at most 2000 characters."
|
|
27
|
+
};
|
|
28
|
+
const saved = (record, etag) => JSON.stringify({
|
|
29
|
+
id: record.id,
|
|
30
|
+
version: record.version,
|
|
31
|
+
etag
|
|
32
|
+
});
|
|
33
|
+
/** Creates a workflow once the user approves it; a definition the check refuses saves nothing. */
|
|
34
|
+
const createWorkflowTool = (input = {}) => defineTool({
|
|
35
|
+
name: "create_workflow",
|
|
36
|
+
description: `Creates a workflow from its name and whole definition, checked as validate_workflow checks it, and returns JSON { id, version, etag }. A definition that does not fit is refused with { error, problems }, each problem naming the state, the field and what was expected; nothing is saved then. The user approves each save in their client first.${onlyNote(input)}`,
|
|
37
|
+
parameters: {
|
|
38
|
+
type: "object",
|
|
39
|
+
properties: {
|
|
40
|
+
name: nameParameter,
|
|
41
|
+
description: descriptionParameter,
|
|
42
|
+
definition: definitionParameter
|
|
43
|
+
},
|
|
44
|
+
required: ["name", "definition"]
|
|
45
|
+
},
|
|
46
|
+
annotations: writes,
|
|
47
|
+
run: async (args, context) => {
|
|
48
|
+
const name = textOf(args.name);
|
|
49
|
+
const definition = objectOf(args.definition);
|
|
50
|
+
if (!name || !definition) return "Pass { \"name\": \"<name>\", \"definition\": { \"trigger\": ..., \"stateMachine\": ... } }.";
|
|
51
|
+
const refusal = outside(input, "workflows");
|
|
52
|
+
if (refusal) return refusal;
|
|
53
|
+
const data = {
|
|
54
|
+
name,
|
|
55
|
+
definition,
|
|
56
|
+
...textOf(args.description) !== void 0 && { description: textOf(args.description) }
|
|
57
|
+
};
|
|
58
|
+
const { text, described } = await approvalTexts(context, "workflows");
|
|
59
|
+
const answer = await context.approve(createApproval(text, described, data));
|
|
60
|
+
if (answer !== "approved") return notApproved(answer);
|
|
61
|
+
return context.records.create("workflows", data).then(({ record, etag }) => saved(record, etag), workflowRefused);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
/** Changes a workflow's definition by an edit or a whole new one, or its name or description, once the user approves it. */
|
|
65
|
+
const updateWorkflowTool = (input = {}) => defineTool({
|
|
66
|
+
name: "update_workflow",
|
|
67
|
+
description: `Changes a workflow: its definition by a small edit (edit, a JSON Merge Patch of the saved definition) or whole (definition), and its name or description. The changed definition is checked as validate_workflow checks it and saved as the workflow's next version; runs started before keep theirs. Returns JSON { id, version, etag }. A definition that does not fit is refused with { error, problems }, each naming the state, the field and what was expected; nothing is saved then. Pass the etag get_workflow gave to fail when someone changed it since. The user approves each save in their client first.${onlyNote(input)}`,
|
|
68
|
+
parameters: {
|
|
69
|
+
type: "object",
|
|
70
|
+
properties: {
|
|
71
|
+
id: idParameter,
|
|
72
|
+
edit: editParameter,
|
|
73
|
+
definition: definitionParameter,
|
|
74
|
+
name: nameParameter,
|
|
75
|
+
description: descriptionParameter,
|
|
76
|
+
etag: {
|
|
77
|
+
type: "string",
|
|
78
|
+
description: "The etag get_workflow gave, to fail when the workflow changed since."
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
required: ["id"]
|
|
82
|
+
},
|
|
83
|
+
annotations: writes,
|
|
84
|
+
run: async (args, context) => {
|
|
85
|
+
const id = idOf(args);
|
|
86
|
+
const edit = objectOf(args.edit);
|
|
87
|
+
const whole = objectOf(args.definition);
|
|
88
|
+
if (!id || edit && whole) return "Pass { \"id\": \"<id>\" } with either an edit or a whole definition, and any name or description to change.";
|
|
89
|
+
const refusal = outside(input, "workflows");
|
|
90
|
+
if (refusal) return refusal;
|
|
91
|
+
const before = await recordBefore(context, "workflows", id, textOf(args.etag) || void 0);
|
|
92
|
+
if (typeof before === "string") return before;
|
|
93
|
+
if (edit && !before) return refused(notFound("No workflows with this key that you may read"));
|
|
94
|
+
const definition = edit ? mergePatch(before.record.definition, edit) : whole;
|
|
95
|
+
const data = {
|
|
96
|
+
...definition !== void 0 && { definition },
|
|
97
|
+
...textOf(args.name) !== void 0 && { name: textOf(args.name) },
|
|
98
|
+
...textOf(args.description) !== void 0 && { description: textOf(args.description) }
|
|
99
|
+
};
|
|
100
|
+
if (Object.keys(data).length === 0) return "Pass an edit, a definition, a name or a description to change; nothing was saved.";
|
|
101
|
+
const { text, described } = await approvalTexts(context, "workflows");
|
|
102
|
+
const answer = await context.approve(updateApproval(text, described, id, before?.record ?? {}, data));
|
|
103
|
+
if (answer !== "approved") return notApproved(answer);
|
|
104
|
+
const etag = textOf(args.etag) || (edit ? before.etag : void 0);
|
|
105
|
+
return context.records.update("workflows", id, data, { ...etag && { etag } }).then(({ record, etag: next }) => saved(record, next), workflowRefused);
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
/** Starts a run of a workflow with an input once the user approves it. */
|
|
109
|
+
const runWorkflowTool = (input = {}) => defineTool({
|
|
110
|
+
name: "run_workflow",
|
|
111
|
+
description: `Starts a run of a workflow's current version with an input, which its states read as $states.context.Execution.Input, and returns JSON { run_id, status }. The run goes on in the background, doing what its steps do (requests, mail, record writes) as the workflow's last editor: read it with get_workflow_run until its status is succeeded or failed. The user approves each run in their client first.${onlyNote(input)}`,
|
|
112
|
+
parameters: {
|
|
113
|
+
type: "object",
|
|
114
|
+
properties: {
|
|
115
|
+
id: idParameter,
|
|
116
|
+
input: { description: "The run's input, any JSON value, at most 1 MiB; an empty object by default." }
|
|
117
|
+
},
|
|
118
|
+
required: ["id"]
|
|
119
|
+
},
|
|
120
|
+
annotations: {
|
|
121
|
+
...writes,
|
|
122
|
+
openWorldHint: true
|
|
123
|
+
},
|
|
124
|
+
run: async (args, context) => {
|
|
125
|
+
const id = idOf(args);
|
|
126
|
+
if (!id) return "Pass { \"id\": \"<id>\", \"input\": { ... } }.";
|
|
127
|
+
const refusal = outside(input, "workflow_runs");
|
|
128
|
+
if (refusal) return refusal;
|
|
129
|
+
const data = {
|
|
130
|
+
workflow_id: id,
|
|
131
|
+
input: args.input ?? {}
|
|
132
|
+
};
|
|
133
|
+
const { text, described } = await approvalTexts(context, "workflow_runs");
|
|
134
|
+
const answer = await context.approve(createApproval(text, described, data));
|
|
135
|
+
if (answer !== "approved") return notApproved(answer);
|
|
136
|
+
return context.records.create("workflow_runs", data).then(({ record }) => JSON.stringify({
|
|
137
|
+
run_id: record.id,
|
|
138
|
+
status: record.status
|
|
139
|
+
}), refused);
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
/** The tools that save and run workflows, on the resources `input` allows. */
|
|
143
|
+
const workflowWriteTools = (input = {}) => [
|
|
144
|
+
createWorkflowTool(input),
|
|
145
|
+
updateWorkflowTool(input),
|
|
146
|
+
runWorkflowTool(input)
|
|
147
|
+
];
|
|
148
|
+
//#endregion
|
|
149
|
+
export { createWorkflowTool, runWorkflowTool, updateWorkflowTool, workflowWriteTools };
|
|
@@ -1,18 +1,37 @@
|
|
|
1
1
|
import { badRequest } from "../problem.js";
|
|
2
|
+
import { requiredStepInputs } from "@protobase/schema/internal";
|
|
2
3
|
import { sql } from "kysely";
|
|
3
4
|
import { checkDefinition, problemText } from "@protobase/workflows";
|
|
4
5
|
//#region src/workflows/save-hook.ts
|
|
5
6
|
/**
|
|
6
7
|
* Checks a workflow's definition on every save that changes it, against the language's schema and the named-reads
|
|
7
8
|
* profile, with the step types the app has, and keeps it as the version the table's trigger numbered, saved by the
|
|
8
|
-
* caller. A refusal is a 400 problem naming each state and field, also listed as `problems
|
|
9
|
+
* caller. A refusal is a 400 problem naming each state and field, also listed as `problems`, and as the `definition`
|
|
10
|
+
* field's `errors` with the state and field in their `params`, for a form to show each on its step; the write rolls back.
|
|
9
11
|
*/
|
|
10
12
|
const workflowSaveHook = (schema, stepTypes) => async (event, trx) => {
|
|
11
13
|
if (event.resource.name !== "workflows" || !event.after) return;
|
|
12
14
|
if (event.operation !== "create" && event.operation !== "update") return;
|
|
13
15
|
if (event.operation === "update" && event.after.version === event.before?.version) return;
|
|
14
|
-
const problems = checkDefinition(event.after.definition, {
|
|
15
|
-
|
|
16
|
+
const problems = checkDefinition(event.after.definition, {
|
|
17
|
+
resources: stepTypes,
|
|
18
|
+
required: requiredStepInputs
|
|
19
|
+
});
|
|
20
|
+
if (problems.length > 0) {
|
|
21
|
+
const errors = problems.map((problem) => ({
|
|
22
|
+
code: "invalid-workflow",
|
|
23
|
+
field: "definition",
|
|
24
|
+
message: problemText(problem),
|
|
25
|
+
params: {
|
|
26
|
+
state: problem.state,
|
|
27
|
+
field: problem.field
|
|
28
|
+
}
|
|
29
|
+
}));
|
|
30
|
+
throw badRequest("invalid-workflow", `The workflow cannot be saved: ${problems.map(problemText).join("; ")}`, {
|
|
31
|
+
problems,
|
|
32
|
+
errors
|
|
33
|
+
});
|
|
34
|
+
}
|
|
16
35
|
await sql`insert into ${sql.id(schema, "versions")} (workflow_id, version, definition, saved_by, tenant_id)
|
|
17
36
|
select w.id, w.version, w.definition, ${String(event.user.id)}, w.tenant_id from ${sql.id(schema, "workflows")} w where w.id = ${String(event.after.id)}`.execute(trx);
|
|
18
37
|
};
|
|
@@ -2,17 +2,10 @@ import { httpStep } from "./http-step.js";
|
|
|
2
2
|
import { mailStep } from "./mail-step.js";
|
|
3
3
|
import { pieceStep } from "./piece-step.js";
|
|
4
4
|
import { recordsSteps } from "./records-step.js";
|
|
5
|
+
import { workflowStepTypes } from "@protobase/schema/internal";
|
|
5
6
|
//#region src/workflows/step-types.ts
|
|
6
|
-
/** The step types a Task names as its `Resource`, as the
|
|
7
|
-
const stepTypeNames =
|
|
8
|
-
"app:http",
|
|
9
|
-
"app:records.create",
|
|
10
|
-
"app:records.update",
|
|
11
|
-
"app:records.get",
|
|
12
|
-
"app:records.list",
|
|
13
|
-
"app:mail.send",
|
|
14
|
-
"app:piece"
|
|
15
|
-
];
|
|
7
|
+
/** The step types a Task names as its `Resource`, as the editor offers them. */
|
|
8
|
+
const stepTypeNames = Object.keys(workflowStepTypes);
|
|
16
9
|
/** The step types of one segment, which sends mail with the mailer of the job that runs it. */
|
|
17
10
|
const stepTypes = ({ deps, schema, roles, allowPrivateAddresses }, mail) => {
|
|
18
11
|
const files = () => deps().files;
|
package/dist/workflows/store.js
CHANGED
|
@@ -17,15 +17,18 @@ const postgresRunStore = (schema) => {
|
|
|
17
17
|
from ${steps} where run_id = ${id} order by scope, seq`.execute(db);
|
|
18
18
|
return rows;
|
|
19
19
|
},
|
|
20
|
-
save: async (db, id,
|
|
20
|
+
save: async (db, id, records) => {
|
|
21
|
+
const row = (step) => sql`(${step.scope}, ${step.seq}::integer, ${step.state}, ${step.type}, ${step.status}, ${step.attempt}::integer,
|
|
22
|
+
${json(step.input)}::jsonb, ${json(step.output)}::jsonb, ${json(step.assigned)}::jsonb, ${step.next}, ${step.resumeAt ?? null}::timestamptz,
|
|
23
|
+
${step.error ?? null}, ${step.cause ?? null}, ${JSON.stringify(step.logs)}::jsonb, ${step.startedAt}::timestamptz, ${step.finishedAt}::timestamptz)`;
|
|
21
24
|
const { rows } = await sql`insert into ${steps}
|
|
22
25
|
(run_id, tenant_id, scope, seq, state, type, status, attempt, input, output, assigned, next, resume_at, error, cause, logs, started_at, finished_at)
|
|
23
|
-
select r.id, r.tenant_id,
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
select r.id, r.tenant_id, s.*
|
|
27
|
+
from ${runs} r, (values ${sql.join(records.map(row))})
|
|
28
|
+
as s (scope, seq, state, type, status, attempt, input, output, assigned, next, resume_at, error, cause, logs, started_at, finished_at)
|
|
29
|
+
where r.id = ${id}
|
|
27
30
|
on conflict (run_id, scope, seq) do nothing returning id`.execute(db);
|
|
28
|
-
return rows.length
|
|
31
|
+
return rows.length === records.length;
|
|
29
32
|
},
|
|
30
33
|
update: async (db, id, change) => {
|
|
31
34
|
const ended = change.status === "succeeded" || change.status === "failed";
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
+
import { stepTypeNames, stepTypes } from "./step-types.js";
|
|
1
2
|
import { betterAuthOwnerRoles } from "../webhooks/table/owner-roles.js";
|
|
2
3
|
import { runInputCheck } from "./input-limits.js";
|
|
3
4
|
import { workflowSecrets, workflowsSchema } from "./options.js";
|
|
4
5
|
import { workflowSaveHook } from "./save-hook.js";
|
|
5
6
|
import { workflowsRunner } from "./segment-job.js";
|
|
6
7
|
import { runStartHook } from "./start-hook.js";
|
|
7
|
-
import { stepTypeNames, stepTypes } from "./step-types.js";
|
|
8
8
|
//#region src/workflows/workflow-engine.ts
|
|
9
9
|
/**
|
|
10
10
|
* What `createAdmin` needs for `workflows`: the hooks that check saves and start runs, the check of a run's input before
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@protobase/server",
|
|
3
|
-
"version": "0.67.
|
|
3
|
+
"version": "0.67.11",
|
|
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": {
|
|
@@ -44,10 +44,10 @@
|
|
|
44
44
|
"@better-auth/sso": "^1.7.12",
|
|
45
45
|
"@livekit/protocol": "1.53.1",
|
|
46
46
|
"@modelcontextprotocol/server": "^2.3.1",
|
|
47
|
-
"@protobase/layout": "0.67.
|
|
48
|
-
"@protobase/query": "0.67.
|
|
49
|
-
"@protobase/schema": "0.67.
|
|
50
|
-
"@protobase/workflows": "0.67.
|
|
47
|
+
"@protobase/layout": "0.67.11",
|
|
48
|
+
"@protobase/query": "0.67.11",
|
|
49
|
+
"@protobase/schema": "0.67.11",
|
|
50
|
+
"@protobase/workflows": "0.67.11",
|
|
51
51
|
"@react-email/render": "^2.1.0",
|
|
52
52
|
"@react-pdf/primitives": "^4.4.0",
|
|
53
53
|
"@react-pdf/renderer": "^4.9.0",
|