@segmentstream/ontology 0.0.0-stage → 0.92.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/NOTICE +2 -0
- package/README.md +1655 -2
- package/dist/action.d.ts +193 -0
- package/dist/action.js +41 -0
- package/dist/agent-chat.d.ts +44 -0
- package/dist/agent-chat.js +91 -0
- package/dist/agent-task-contract.d.ts +64 -0
- package/dist/agent-task-contract.js +15 -0
- package/dist/agent-task.d.ts +39 -0
- package/dist/agent-task.js +112 -0
- package/dist/analyze.d.ts +215 -0
- package/dist/analyze.js +2438 -0
- package/dist/app-feedback.d.ts +167 -0
- package/dist/app-feedback.js +176 -0
- package/dist/authoring-input.d.ts +3 -0
- package/dist/authoring-input.js +107 -0
- package/dist/authoring-names.d.ts +4 -0
- package/dist/authoring-names.js +13 -0
- package/dist/authoring-records.d.ts +35 -0
- package/dist/authoring-records.js +264 -0
- package/dist/authoring.d.ts +15 -0
- package/dist/authoring.js +331 -0
- package/dist/bin/check.d.ts +30 -0
- package/dist/bin/check.js +99 -0
- package/dist/bin/load.d.ts +48 -0
- package/dist/bin/load.js +139 -0
- package/dist/bin/main.d.ts +31 -0
- package/dist/bin/main.js +121 -0
- package/dist/bin/migrate.d.ts +57 -0
- package/dist/bin/migrate.js +170 -0
- package/dist/bin/output.d.ts +31 -0
- package/dist/bin/output.js +84 -0
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +31 -0
- package/dist/blob.d.ts +52 -0
- package/dist/blob.js +87 -0
- package/dist/channel-session.d.ts +16 -0
- package/dist/channel-session.js +16 -0
- package/dist/chat.d.ts +43 -0
- package/dist/chat.js +34 -0
- package/dist/confirmation.d.ts +46 -0
- package/dist/confirmation.js +41 -0
- package/dist/connection.d.ts +213 -0
- package/dist/connection.js +289 -0
- package/dist/deployment.d.ts +35 -0
- package/dist/deployment.js +76 -0
- package/dist/diagnostics.d.ts +27 -0
- package/dist/diagnostics.js +106 -0
- package/dist/emails.d.ts +70 -0
- package/dist/emails.js +221 -0
- package/dist/entity.d.ts +331 -0
- package/dist/entity.js +153 -0
- package/dist/errors.d.ts +154 -0
- package/dist/errors.js +109 -0
- package/dist/feedback-config.d.ts +24 -0
- package/dist/feedback-config.js +126 -0
- package/dist/files.d.ts +39 -0
- package/dist/files.js +4 -0
- package/dist/groups.d.ts +95 -0
- package/dist/groups.js +280 -0
- package/dist/index.d.ts +109 -0
- package/dist/index.js +70 -0
- package/dist/internal/diagnostics.js +594 -0
- package/dist/legacy.d.ts +160 -0
- package/dist/legacy.js +270 -0
- package/dist/manifest-compatibility.d.ts +8 -0
- package/dist/manifest-compatibility.js +87 -0
- package/dist/manifest.d.ts +3329 -0
- package/dist/manifest.js +757 -0
- package/dist/me.d.ts +46 -0
- package/dist/me.js +109 -0
- package/dist/metric-filters.d.ts +48 -0
- package/dist/metric-filters.js +252 -0
- package/dist/metric-routes.d.ts +132 -0
- package/dist/metric-routes.js +498 -0
- package/dist/metric.d.ts +172 -0
- package/dist/metric.js +132 -0
- package/dist/migrate/apply.d.ts +94 -0
- package/dist/migrate/apply.js +526 -0
- package/dist/migrate/errors.d.ts +46 -0
- package/dist/migrate/errors.js +38 -0
- package/dist/migrate/index.d.ts +10 -0
- package/dist/migrate/index.js +7 -0
- package/dist/migrate/model.d.ts +2311 -0
- package/dist/migrate/model.js +148 -0
- package/dist/migrate/plan.d.ts +51 -0
- package/dist/migrate/plan.js +1856 -0
- package/dist/migrate/preview.d.ts +13 -0
- package/dist/migrate/preview.js +65 -0
- package/dist/migrate/storage.d.ts +226 -0
- package/dist/migrate/storage.js +774 -0
- package/dist/model-metrics.d.ts +22 -0
- package/dist/model-metrics.js +545 -0
- package/dist/names.d.ts +49 -0
- package/dist/names.js +110 -0
- package/dist/navigation-artifact.d.ts +13 -0
- package/dist/navigation-artifact.js +11 -0
- package/dist/navigation-catalog.d.ts +55 -0
- package/dist/navigation-catalog.js +297 -0
- package/dist/nitro-navigation.d.ts +3 -0
- package/dist/nitro-navigation.js +64 -0
- package/dist/nitro-workflows.d.ts +108 -0
- package/dist/nitro-workflows.js +286 -0
- package/dist/nitro.d.ts +40 -0
- package/dist/nitro.js +50 -0
- package/dist/ontology.d.ts +41 -0
- package/dist/ontology.js +46 -0
- package/dist/permissions.d.ts +198 -0
- package/dist/permissions.js +272 -0
- package/dist/property.d.ts +188 -0
- package/dist/property.js +301 -0
- package/dist/query.d.ts +245 -0
- package/dist/query.js +615 -0
- package/dist/relationship.d.ts +99 -0
- package/dist/relationship.js +33 -0
- package/dist/render.d.ts +81 -0
- package/dist/render.js +512 -0
- package/dist/replication/baseline.d.ts +51 -0
- package/dist/replication/baseline.js +215 -0
- package/dist/replication/bigquery.d.ts +37 -0
- package/dist/replication/bigquery.js +211 -0
- package/dist/replication/coverage.d.ts +14 -0
- package/dist/replication/coverage.js +53 -0
- package/dist/replication/evolution.d.ts +25 -0
- package/dist/replication/evolution.js +184 -0
- package/dist/replication/identities.d.ts +6 -0
- package/dist/replication/identities.js +47 -0
- package/dist/replication/legacy-baseline.d.ts +27 -0
- package/dist/replication/legacy-baseline.js +109 -0
- package/dist/replication/legacy-store.d.ts +19 -0
- package/dist/replication/legacy-store.js +63 -0
- package/dist/replication/lineage.d.ts +36 -0
- package/dist/replication/lineage.js +72 -0
- package/dist/replication/migrate.d.ts +21 -0
- package/dist/replication/migrate.js +84 -0
- package/dist/replication/model.d.ts +52 -0
- package/dist/replication/model.js +99 -0
- package/dist/replication/scans.d.ts +34 -0
- package/dist/replication/scans.js +158 -0
- package/dist/replication/schema.d.ts +11 -0
- package/dist/replication/schema.js +226 -0
- package/dist/replication/shadow-schema.d.ts +4 -0
- package/dist/replication/shadow-schema.js +61 -0
- package/dist/replication/shadow.d.ts +127 -0
- package/dist/replication/shadow.js +614 -0
- package/dist/replication/source-replacement.d.ts +33 -0
- package/dist/replication/source-replacement.js +76 -0
- package/dist/replication/store.d.ts +36 -0
- package/dist/replication/store.js +382 -0
- package/dist/replication/transaction.d.ts +23 -0
- package/dist/replication/transaction.js +88 -0
- package/dist/role.d.ts +61 -0
- package/dist/role.js +76 -0
- package/dist/runtime/action-deadlocks.d.ts +21 -0
- package/dist/runtime/action-deadlocks.js +130 -0
- package/dist/runtime/action-transactions.d.ts +35 -0
- package/dist/runtime/action-transactions.js +54 -0
- package/dist/runtime/agent-counters.d.ts +26 -0
- package/dist/runtime/agent-counters.js +98 -0
- package/dist/runtime/agent-tasks.d.ts +63 -0
- package/dist/runtime/agent-tasks.js +120 -0
- package/dist/runtime/agents.d.ts +15 -0
- package/dist/runtime/agents.js +457 -0
- package/dist/runtime/batch-diagnostics.d.ts +22 -0
- package/dist/runtime/batch-diagnostics.js +89 -0
- package/dist/runtime/batch.d.ts +52 -0
- package/dist/runtime/batch.js +534 -0
- package/dist/runtime/channel-session-revocation.d.ts +3 -0
- package/dist/runtime/channel-session-revocation.js +5 -0
- package/dist/runtime/channel-sessions.d.ts +4 -0
- package/dist/runtime/channel-sessions.js +61 -0
- package/dist/runtime/confirmations.d.ts +29 -0
- package/dist/runtime/confirmations.js +57 -0
- package/dist/runtime/cursor.d.ts +23 -0
- package/dist/runtime/cursor.js +100 -0
- package/dist/runtime/deployment.d.ts +7 -0
- package/dist/runtime/deployment.js +4 -0
- package/dist/runtime/errors.d.ts +118 -0
- package/dist/runtime/errors.js +109 -0
- package/dist/runtime/federation-tables.d.ts +2 -0
- package/dist/runtime/federation-tables.js +20 -0
- package/dist/runtime/federation.d.ts +42 -0
- package/dist/runtime/federation.js +222 -0
- package/dist/runtime/files.d.ts +39 -0
- package/dist/runtime/files.js +44 -0
- package/dist/runtime/generated-actions.d.ts +37 -0
- package/dist/runtime/generated-actions.js +307 -0
- package/dist/runtime/history.d.ts +91 -0
- package/dist/runtime/history.js +77 -0
- package/dist/runtime/ids.d.ts +6 -0
- package/dist/runtime/ids.js +39 -0
- package/dist/runtime/member-tables.d.ts +8 -0
- package/dist/runtime/member-tables.js +33 -0
- package/dist/runtime/member-writes.d.ts +78 -0
- package/dist/runtime/member-writes.js +202 -0
- package/dist/runtime/metric-query.d.ts +19 -0
- package/dist/runtime/metric-query.js +411 -0
- package/dist/runtime/model.d.ts +67 -0
- package/dist/runtime/model.js +150 -0
- package/dist/runtime/new-user-hook.d.ts +6 -0
- package/dist/runtime/new-user-hook.js +37 -0
- package/dist/runtime/oauth.d.ts +9 -0
- package/dist/runtime/oauth.js +299 -0
- package/dist/runtime/pipeline.d.ts +155 -0
- package/dist/runtime/pipeline.js +597 -0
- package/dist/runtime/postgres-tls.d.ts +10 -0
- package/dist/runtime/postgres-tls.js +14 -0
- package/dist/runtime/principals.d.ts +302 -0
- package/dist/runtime/principals.js +1307 -0
- package/dist/runtime/reads.d.ts +55 -0
- package/dist/runtime/reads.js +313 -0
- package/dist/runtime/records.d.ts +42 -0
- package/dist/runtime/records.js +245 -0
- package/dist/runtime/resolve-agent-identity.d.ts +6 -0
- package/dist/runtime/resolve-agent-identity.js +181 -0
- package/dist/runtime/service-binding.d.ts +18 -0
- package/dist/runtime/service-binding.js +266 -0
- package/dist/runtime/sign-in-links.d.ts +68 -0
- package/dist/runtime/sign-in-links.js +147 -0
- package/dist/runtime/sql-runtime.d.ts +131 -0
- package/dist/runtime/sql-runtime.js +362 -0
- package/dist/runtime/statement.d.ts +18 -0
- package/dist/runtime/statement.js +31 -0
- package/dist/runtime/user-identities.d.ts +66 -0
- package/dist/runtime/user-identities.js +106 -0
- package/dist/runtime/user-identity-sql.d.ts +86 -0
- package/dist/runtime/user-identity-sql.js +297 -0
- package/dist/runtime/user-roles.d.ts +16 -0
- package/dist/runtime/user-roles.js +32 -0
- package/dist/runtime/validation.d.ts +16 -0
- package/dist/runtime/validation.js +26 -0
- package/dist/runtime/where.d.ts +57 -0
- package/dist/runtime/where.js +404 -0
- package/dist/runtime/workflow-admission.d.ts +49 -0
- package/dist/runtime/workflow-admission.js +92 -0
- package/dist/runtime/workflow-argument-codec.d.ts +10 -0
- package/dist/runtime/workflow-argument-codec.js +120 -0
- package/dist/runtime/workflow-arguments.d.ts +17 -0
- package/dist/runtime/workflow-arguments.js +99 -0
- package/dist/runtime/workflow-starts.d.ts +203 -0
- package/dist/runtime/workflow-starts.js +493 -0
- package/dist/runtime/workflow-users.d.ts +9 -0
- package/dist/runtime/workflow-users.js +17 -0
- package/dist/runtime/workload.d.ts +25 -0
- package/dist/runtime/workload.js +24 -0
- package/dist/runtime/writes.d.ts +108 -0
- package/dist/runtime/writes.js +491 -0
- package/dist/runtime-source.d.ts +22 -0
- package/dist/runtime-source.js +28 -0
- package/dist/runtime.d.ts +310 -0
- package/dist/runtime.js +31 -0
- package/dist/server.d.ts +23 -0
- package/dist/server.js +107 -0
- package/dist/service/agent/attachments.d.ts +33 -0
- package/dist/service/agent/attachments.js +228 -0
- package/dist/service/agent/authentication.d.ts +22 -0
- package/dist/service/agent/authentication.js +58 -0
- package/dist/service/agent/confirmations.d.ts +5 -0
- package/dist/service/agent/confirmations.js +73 -0
- package/dist/service/agent/conversation-routes.d.ts +14 -0
- package/dist/service/agent/conversation-routes.js +45 -0
- package/dist/service/agent/conversations.d.ts +40 -0
- package/dist/service/agent/conversations.js +69 -0
- package/dist/service/agent/files.d.ts +12 -0
- package/dist/service/agent/files.js +69 -0
- package/dist/service/agent/protocol.d.ts +90 -0
- package/dist/service/agent/protocol.js +114 -0
- package/dist/service/agent/server.d.ts +28 -0
- package/dist/service/agent/server.js +374 -0
- package/dist/service/agent/task-caller.d.ts +21 -0
- package/dist/service/agent/task-caller.js +35 -0
- package/dist/service/agent/task-start.d.ts +53 -0
- package/dist/service/agent/task-start.js +161 -0
- package/dist/service/agent/tasks.d.ts +40 -0
- package/dist/service/agent/tasks.js +155 -0
- package/dist/service/api.d.ts +50 -0
- package/dist/service/api.js +288 -0
- package/dist/service/authentication.d.ts +227 -0
- package/dist/service/authentication.js +767 -0
- package/dist/service/caller-context.d.ts +24 -0
- package/dist/service/caller-context.js +6 -0
- package/dist/service/catalog.d.ts +42 -0
- package/dist/service/catalog.js +66 -0
- package/dist/service/channel/provider.d.ts +10 -0
- package/dist/service/channel/provider.js +1 -0
- package/dist/service/channel/providers/telegram.d.ts +18 -0
- package/dist/service/channel/providers/telegram.js +108 -0
- package/dist/service/channel/sessions.d.ts +5 -0
- package/dist/service/channel/sessions.js +53 -0
- package/dist/service/channel/sign-in.d.ts +7 -0
- package/dist/service/channel/sign-in.js +78 -0
- package/dist/service/chat/archive.d.ts +64 -0
- package/dist/service/chat/archive.js +62 -0
- package/dist/service/chat/attribution.d.ts +26 -0
- package/dist/service/chat/attribution.js +28 -0
- package/dist/service/chat/delivery.d.ts +18 -0
- package/dist/service/chat/delivery.js +251 -0
- package/dist/service/chat/diagnostics.d.ts +2 -0
- package/dist/service/chat/diagnostics.js +35 -0
- package/dist/service/chat/envelope.d.ts +96 -0
- package/dist/service/chat/envelope.js +39 -0
- package/dist/service/chat/idempotency.d.ts +19 -0
- package/dist/service/chat/idempotency.js +37 -0
- package/dist/service/chat/providers/telegram.d.ts +36 -0
- package/dist/service/chat/providers/telegram.js +133 -0
- package/dist/service/chat/reads.d.ts +25 -0
- package/dist/service/chat/reads.js +75 -0
- package/dist/service/chat/resolve.d.ts +34 -0
- package/dist/service/chat/resolve.js +150 -0
- package/dist/service/chat/routes.d.ts +23 -0
- package/dist/service/chat/routes.js +75 -0
- package/dist/service/chat/schemas.d.ts +43 -0
- package/dist/service/chat/schemas.js +31 -0
- package/dist/service/config.d.ts +218 -0
- package/dist/service/config.js +265 -0
- package/dist/service/connection-stream.d.ts +10 -0
- package/dist/service/connection-stream.js +191 -0
- package/dist/service/connections.d.ts +60 -0
- package/dist/service/connections.js +271 -0
- package/dist/service/decisions.d.ts +35 -0
- package/dist/service/decisions.js +63 -0
- package/dist/service/descriptions.d.ts +8 -0
- package/dist/service/descriptions.js +20 -0
- package/dist/service/discovery-schemas.d.ts +92 -0
- package/dist/service/discovery-schemas.js +59 -0
- package/dist/service/discovery-search.d.ts +30 -0
- package/dist/service/discovery-search.js +95 -0
- package/dist/service/discovery.d.ts +37 -0
- package/dist/service/discovery.js +436 -0
- package/dist/service/docs.d.ts +9 -0
- package/dist/service/docs.js +197 -0
- package/dist/service/errors.d.ts +145 -0
- package/dist/service/errors.js +704 -0
- package/dist/service/federation-provider.d.ts +36 -0
- package/dist/service/federation-provider.js +110 -0
- package/dist/service/federation-server.d.ts +20 -0
- package/dist/service/federation-server.js +249 -0
- package/dist/service/federation-status.d.ts +40 -0
- package/dist/service/federation-status.js +151 -0
- package/dist/service/handlers.d.ts +18 -0
- package/dist/service/handlers.js +287 -0
- package/dist/service/mcp/confirmation.d.ts +68 -0
- package/dist/service/mcp/confirmation.js +178 -0
- package/dist/service/mcp/find-input.d.ts +12 -0
- package/dist/service/mcp/find-input.js +122 -0
- package/dist/service/mcp/input-schema.d.ts +6 -0
- package/dist/service/mcp/input-schema.js +126 -0
- package/dist/service/mcp/listing.d.ts +26 -0
- package/dist/service/mcp/listing.js +92 -0
- package/dist/service/mcp/metric-tools.d.ts +85 -0
- package/dist/service/mcp/metric-tools.js +258 -0
- package/dist/service/mcp/request-log.d.ts +5 -0
- package/dist/service/mcp/request-log.js +53 -0
- package/dist/service/mcp/runtime-policy.d.ts +27 -0
- package/dist/service/mcp/runtime-policy.js +62 -0
- package/dist/service/mcp/server.d.ts +52 -0
- package/dist/service/mcp/server.js +861 -0
- package/dist/service/mcp/tools.d.ts +99 -0
- package/dist/service/mcp/tools.js +938 -0
- package/dist/service/member-file-routes.d.ts +22 -0
- package/dist/service/member-file-routes.js +52 -0
- package/dist/service/member-files.d.ts +245 -0
- package/dist/service/member-files.js +258 -0
- package/dist/service/metric-query-platform.d.ts +77 -0
- package/dist/service/metric-query-platform.js +198 -0
- package/dist/service/metric-query-scopes.d.ts +43 -0
- package/dist/service/metric-query-scopes.js +147 -0
- package/dist/service/metric-query.d.ts +48 -0
- package/dist/service/metric-query.js +200 -0
- package/dist/service/name-suggestions.d.ts +3 -0
- package/dist/service/name-suggestions.js +38 -0
- package/dist/service/navigation-schemas.d.ts +80 -0
- package/dist/service/navigation-schemas.js +49 -0
- package/dist/service/navigation.d.ts +20 -0
- package/dist/service/navigation.js +165 -0
- package/dist/service/oauth/protocol.d.ts +70 -0
- package/dist/service/oauth/protocol.js +157 -0
- package/dist/service/oauth/refresh-replay.d.ts +2 -0
- package/dist/service/oauth/refresh-replay.js +20 -0
- package/dist/service/oauth/server.d.ts +28 -0
- package/dist/service/oauth/server.js +769 -0
- package/dist/service/operations.d.ts +170 -0
- package/dist/service/operations.js +392 -0
- package/dist/service/plan.d.ts +57 -0
- package/dist/service/plan.js +96 -0
- package/dist/service/rate-limit.d.ts +22 -0
- package/dist/service/rate-limit.js +52 -0
- package/dist/service/record-reads.d.ts +21 -0
- package/dist/service/record-reads.js +155 -0
- package/dist/service/replication.d.ts +5 -0
- package/dist/service/replication.js +39 -0
- package/dist/service/runtime-contract.d.ts +508 -0
- package/dist/service/runtime-contract.js +18 -0
- package/dist/service/runtime-identity.d.ts +71 -0
- package/dist/service/runtime-identity.js +122 -0
- package/dist/service/schemas.d.ts +961 -0
- package/dist/service/schemas.js +639 -0
- package/dist/service/service.d.ts +21 -0
- package/dist/service/service.js +526 -0
- package/dist/service/warehouse-platform.d.ts +17 -0
- package/dist/service/warehouse-platform.js +122 -0
- package/dist/service/warehouse-query.d.ts +24 -0
- package/dist/service/warehouse-query.js +280 -0
- package/dist/service/webhooks.d.ts +106 -0
- package/dist/service/webhooks.js +233 -0
- package/dist/service/workflow-admission-provider.d.ts +31 -0
- package/dist/service/workflow-admission-provider.js +205 -0
- package/dist/service/workflow-argument-serialization.d.ts +2 -0
- package/dist/service/workflow-argument-serialization.js +14 -0
- package/dist/service/workflow-callback.d.ts +22 -0
- package/dist/service/workflow-callback.js +75 -0
- package/dist/service/workflow-deployment.d.ts +8 -0
- package/dist/service/workflow-deployment.js +131 -0
- package/dist/service/workflow-diagnostics.d.ts +23 -0
- package/dist/service/workflow-diagnostics.js +263 -0
- package/dist/service/workflow-errors.d.ts +26 -0
- package/dist/service/workflow-errors.js +181 -0
- package/dist/service/workflow-logging.d.ts +2 -0
- package/dist/service/workflow-logging.js +50 -0
- package/dist/service/workflow-queue-fence.d.ts +6 -0
- package/dist/service/workflow-queue-fence.js +66 -0
- package/dist/service/workflows.d.ts +200 -0
- package/dist/service/workflows.js +1549 -0
- package/dist/sign-in.d.ts +58 -0
- package/dist/sign-in.js +57 -0
- package/dist/source.d.ts +30 -0
- package/dist/source.js +107 -0
- package/dist/start-recovery-contract.d.ts +49 -0
- package/dist/start-recovery-contract.js +26 -0
- package/dist/start-recovery.d.ts +27 -0
- package/dist/start-recovery.js +81 -0
- package/dist/storage/api.d.ts +4 -0
- package/dist/storage/api.js +40 -0
- package/dist/storage/attachments.d.ts +146 -0
- package/dist/storage/attachments.js +733 -0
- package/dist/storage/authorization.d.ts +38 -0
- package/dist/storage/authorization.js +83 -0
- package/dist/storage/file-access.d.ts +17 -0
- package/dist/storage/file-access.js +8 -0
- package/dist/storage/limits.d.ts +12 -0
- package/dist/storage/limits.js +17 -0
- package/dist/storage/maintenance-auth.d.ts +20 -0
- package/dist/storage/maintenance-auth.js +24 -0
- package/dist/storage/maintenance.d.ts +11 -0
- package/dist/storage/maintenance.js +67 -0
- package/dist/storage/neon.d.ts +4 -0
- package/dist/storage/neon.js +318 -0
- package/dist/storage/operator.d.ts +11 -0
- package/dist/storage/operator.js +30 -0
- package/dist/storage/provider.d.ts +93 -0
- package/dist/storage/provider.js +36 -0
- package/dist/storage/sessions.d.ts +184 -0
- package/dist/storage/sessions.js +688 -0
- package/dist/storage/tables.d.ts +54 -0
- package/dist/storage/tables.js +205 -0
- package/dist/storage/transfers.d.ts +16 -0
- package/dist/storage/transfers.js +153 -0
- package/dist/storage/writes.d.ts +7 -0
- package/dist/storage/writes.js +88 -0
- package/dist/telegram.d.ts +35 -0
- package/dist/telegram.js +173 -0
- package/dist/user-identity.d.ts +28 -0
- package/dist/user-identity.js +106 -0
- package/dist/user.d.ts +75 -0
- package/dist/user.js +111 -0
- package/dist/warehouse-backed.d.ts +6 -0
- package/dist/warehouse-backed.js +495 -0
- package/dist/webhook-token.d.ts +11 -0
- package/dist/webhook-token.js +18 -0
- package/dist/webhooks.d.ts +40 -0
- package/dist/webhooks.js +55 -0
- package/dist/wire.d.ts +10 -0
- package/dist/wire.js +35 -0
- package/dist/workflow-runtime.d.ts +116 -0
- package/dist/workflow-runtime.js +109 -0
- package/dist/workflow-topics.d.ts +28 -0
- package/dist/workflow-topics.js +28 -0
- package/dist/workflow.d.ts +284 -0
- package/dist/workflow.js +259 -0
- package/dist/workflows.d.ts +24 -0
- package/dist/workflows.js +18 -0
- package/package.json +127 -4
|
@@ -0,0 +1,861 @@
|
|
|
1
|
+
import { Cause, Data, Effect, Layer, Option, Redacted, Schema, SchemaTransformation } from "effect";
|
|
2
|
+
import { HttpRouter, HttpServerRequest, HttpServerResponse } from "effect/unstable/http";
|
|
3
|
+
import { HttpApiBuilder, HttpApiSecurity } from "effect/unstable/httpapi";
|
|
4
|
+
import { ActionRejected } from "../../errors.js";
|
|
5
|
+
import { DeploymentAdmissionError } from "../../deployment.js";
|
|
6
|
+
import { effectivePermissions } from "../../permissions.js";
|
|
7
|
+
import { encodeManifest } from "../../manifest.js";
|
|
8
|
+
import { describeError, SCOPE_MISMATCH } from "../metric-query-platform.js";
|
|
9
|
+
import { gatewayCredential, gatewayDecisions } from "../decisions.js";
|
|
10
|
+
import { CurrentPrincipal, withPrincipal } from "../authentication.js";
|
|
11
|
+
import { currentCaller } from "../caller-context.js";
|
|
12
|
+
import { requireRuntimeOperation, runtimeManifest } from "./runtime-policy.js";
|
|
13
|
+
import { CONFIRMATION_PREVIEW_METHOD, previewLocale, withoutConfirmation } from "./confirmation.js";
|
|
14
|
+
import { consumeChatEnvelope, CurrentChatCall } from "../chat/envelope.js";
|
|
15
|
+
import { ChatList, ChatHistory, ChatPostWire, RuntimeChatPostWire } from "../chat/schemas.js";
|
|
16
|
+
import { ChatPostResult } from "../../chat.js";
|
|
17
|
+
import { chatReads } from "../chat/reads.js";
|
|
18
|
+
import { withChatMembership } from "../chat/resolve.js";
|
|
19
|
+
import { catalogFor } from "../catalog.js";
|
|
20
|
+
import { createDiscovery, DiscoveryError } from "../discovery.js";
|
|
21
|
+
import { suggestNames, suggestionDetail } from "../name-suggestions.js";
|
|
22
|
+
import { DiscoverInput } from "../discovery-schemas.js";
|
|
23
|
+
import { NavigationInput } from "../navigation-schemas.js";
|
|
24
|
+
import { createNavigation, NavigationError } from "../navigation.js";
|
|
25
|
+
import { ConversationSearch } from "../agent/conversations.js";
|
|
26
|
+
import { GetDimensionsInput, GetDimensionValuesInput, ListMetricsInput, listMetrics, metricDimensions, planDimensionValues, VALUES_DEFAULT, } from "./metric-tools.js";
|
|
27
|
+
import { RequestId, errorBodyForWire, expectedFailure, internalError, isStorageFailure, schemaIssues, storageUnavailable, } from "../errors.js";
|
|
28
|
+
import { publicOrigin } from "../oauth/protocol.js";
|
|
29
|
+
import { mcpChallenge, mcpResource } from "../oauth/server.js";
|
|
30
|
+
import { identityToken, makeOperations, toQuery } from "../operations.js";
|
|
31
|
+
import { OntologyBackend } from "../runtime-contract.js";
|
|
32
|
+
import { WarehouseQueries } from "../warehouse-platform.js";
|
|
33
|
+
import { runtimeCredential } from "../runtime-identity.js";
|
|
34
|
+
import { ActionResponse, HistoryPage, HistoryParams, IdParam, WarehouseIdParam, Me, } from "../schemas.js";
|
|
35
|
+
import { uploadFields } from "../../storage/attachments.js";
|
|
36
|
+
import { RECORDS_OR_METRIC_QUERIES } from "../../render.js";
|
|
37
|
+
import { memberFileOperations } from "../member-files.js";
|
|
38
|
+
import { ACTION_OPTIONS_ARGUMENT, mcpTools, RUNTIME_SOURCE_META, visibleTools, } from "./tools.js";
|
|
39
|
+
import { listTools, toolDefinition } from "./listing.js";
|
|
40
|
+
/** The URL path of the ontology's MCP server, outside `/api/ontology`: one per app. */
|
|
41
|
+
export const MCP_PATH = "/mcp";
|
|
42
|
+
/** The MCP revisions the server speaks, newest first. */
|
|
43
|
+
export const MCP_PROTOCOL_VERSIONS = ["2025-11-25", "2025-06-18"];
|
|
44
|
+
const PROTOCOL_VERSION_HEADER = "mcp-protocol-version";
|
|
45
|
+
/** JSON-RPC and MCP error codes. */
|
|
46
|
+
const PARSE_ERROR = -32700;
|
|
47
|
+
const INVALID_REQUEST = -32600;
|
|
48
|
+
const METHOD_NOT_FOUND = -32601;
|
|
49
|
+
const INVALID_PARAMS = -32602;
|
|
50
|
+
const RESOURCE_NOT_FOUND = -32002;
|
|
51
|
+
function isObject(value) {
|
|
52
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
53
|
+
}
|
|
54
|
+
function rpcResult(id, result) {
|
|
55
|
+
return HttpServerResponse.jsonUnsafe({ jsonrpc: "2.0", id, result });
|
|
56
|
+
}
|
|
57
|
+
function rpcError(id, code, message, status = 200, data) {
|
|
58
|
+
return HttpServerResponse.jsonUnsafe({ jsonrpc: "2.0", id, error: { code, message, ...(data === undefined ? {} : { data }) } }, { status });
|
|
59
|
+
}
|
|
60
|
+
/** A message that is not a request: a notification, or a response to a request of the server's. */
|
|
61
|
+
const ACCEPTED = HttpServerResponse.empty({ status: 202 });
|
|
62
|
+
const METHOD_NOT_ALLOWED = HttpServerResponse.setHeader(rpcError(null, INVALID_REQUEST, "Use GET with Accept: text/event-stream, or POST.", 405), "allow", "GET, POST");
|
|
63
|
+
/** Bearer clients may call from any origin; cookies never authorize this transport. */
|
|
64
|
+
const CORS = {
|
|
65
|
+
"access-control-allow-origin": "*",
|
|
66
|
+
"access-control-allow-methods": "GET, POST",
|
|
67
|
+
"access-control-allow-headers": "authorization, content-type, mcp-protocol-version, mcp-session-id, last-event-id",
|
|
68
|
+
"access-control-expose-headers": "www-authenticate, mcp-protocol-version, mcp-session-id, x-request-id",
|
|
69
|
+
};
|
|
70
|
+
const PREFLIGHT = HttpServerResponse.empty({
|
|
71
|
+
status: 204,
|
|
72
|
+
headers: { ...CORS, allow: "GET, POST" },
|
|
73
|
+
});
|
|
74
|
+
// The generated Vercel service enables Fluid without a maxDuration override. Stay below even
|
|
75
|
+
// a 60s function limit, leaving time for cold starts and authentication before the stream opens.
|
|
76
|
+
export const MCP_STREAM_MAX_DURATION_MS = 45_000;
|
|
77
|
+
const HEARTBEAT_MS = 15_000;
|
|
78
|
+
/** A stateless keepalive stream: comments and a reconnect hint, never JSON-RPC messages or IDs. */
|
|
79
|
+
function eventStream(signal, maxDurationMs) {
|
|
80
|
+
let cleanup = () => { };
|
|
81
|
+
const stream = new ReadableStream({
|
|
82
|
+
start(controller) {
|
|
83
|
+
const encoder = new TextEncoder();
|
|
84
|
+
let closed = false;
|
|
85
|
+
const finish = (closeStream) => {
|
|
86
|
+
if (closed) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
closed = true;
|
|
90
|
+
clearInterval(heartbeat);
|
|
91
|
+
clearTimeout(deadline);
|
|
92
|
+
signal.removeEventListener("abort", close);
|
|
93
|
+
if (closeStream) {
|
|
94
|
+
controller.close();
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
const close = () => finish(true);
|
|
98
|
+
cleanup = () => finish(false);
|
|
99
|
+
controller.enqueue(encoder.encode(": connected\nretry: 1000\n\n"));
|
|
100
|
+
const heartbeat = setInterval(() => {
|
|
101
|
+
// A slow reader needs no queued keepalives; bound memory until it reads or disconnects.
|
|
102
|
+
if ((controller.desiredSize ?? 0) > 0) {
|
|
103
|
+
controller.enqueue(encoder.encode(": heartbeat\n\n"));
|
|
104
|
+
}
|
|
105
|
+
}, HEARTBEAT_MS);
|
|
106
|
+
const deadline = setTimeout(close, maxDurationMs);
|
|
107
|
+
signal.addEventListener("abort", close, { once: true });
|
|
108
|
+
if (signal.aborted) {
|
|
109
|
+
close();
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
cancel() {
|
|
113
|
+
cleanup();
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
return HttpServerResponse.raw(stream, {
|
|
117
|
+
contentType: "text/event-stream",
|
|
118
|
+
headers: { "cache-control": "no-cache, no-transform", "x-accel-buffering": "no" },
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
function classify(body) {
|
|
122
|
+
if (!isObject(body) || body.jsonrpc !== "2.0") {
|
|
123
|
+
return { kind: "invalid", id: null };
|
|
124
|
+
}
|
|
125
|
+
const { id, method, params } = body;
|
|
126
|
+
const validId = typeof id === "string" || typeof id === "number";
|
|
127
|
+
if (typeof method !== "string") {
|
|
128
|
+
// A response to a request the server sent; this server sends none, so there is nothing to do.
|
|
129
|
+
return "result" in body || "error" in body ? { kind: "other" } : { kind: "invalid", id: null };
|
|
130
|
+
}
|
|
131
|
+
if (id === undefined) {
|
|
132
|
+
return { kind: "other" };
|
|
133
|
+
}
|
|
134
|
+
if (!validId || (params !== undefined && !isObject(params))) {
|
|
135
|
+
return { kind: "invalid", id: validId ? id : null };
|
|
136
|
+
}
|
|
137
|
+
return { kind: "request", id, method, params: params ?? {} };
|
|
138
|
+
}
|
|
139
|
+
/** Arguments a tool could not decode. Unlike a protocol error, the model reads it and corrects itself. */
|
|
140
|
+
class InvalidArguments extends Data.TaggedError("InvalidArguments") {
|
|
141
|
+
}
|
|
142
|
+
/** Decodes a tool's arguments; the service's schemas need no services. */
|
|
143
|
+
function decodeArguments(schema, args) {
|
|
144
|
+
return Schema.decodeUnknownEffect(schema)(args, {
|
|
145
|
+
errors: "all",
|
|
146
|
+
onExcessProperty: "error",
|
|
147
|
+
}).pipe(Effect.mapError((error) => new InvalidArguments({ issues: schemaIssues(error.issue) })));
|
|
148
|
+
}
|
|
149
|
+
/** The `_options` argument of an action tool that consumes or stages an upload. */
|
|
150
|
+
const McpActionOptions = Schema.Struct({
|
|
151
|
+
idempotency_key: Schema.optionalKey(Schema.String.check(Schema.isMinLength(1), Schema.isMaxLength(128))),
|
|
152
|
+
create_intent: Schema.optionalKey(Schema.String.check(Schema.isUUID())),
|
|
153
|
+
}).pipe(Schema.decodeTo(Schema.Struct({
|
|
154
|
+
idempotencyKey: Schema.optionalKey(Schema.String),
|
|
155
|
+
createIntent: Schema.optionalKey(Schema.String),
|
|
156
|
+
}), SchemaTransformation.transform({
|
|
157
|
+
decode: (value) => ({
|
|
158
|
+
...(value.idempotency_key === undefined ? {} : { idempotencyKey: value.idempotency_key }),
|
|
159
|
+
...(value.create_intent === undefined ? {} : { createIntent: value.create_intent }),
|
|
160
|
+
}),
|
|
161
|
+
encode: (value) => ({
|
|
162
|
+
...(value.idempotencyKey === undefined ? {} : { idempotency_key: value.idempotencyKey }),
|
|
163
|
+
...(value.createIntent === undefined ? {} : { create_intent: value.createIntent }),
|
|
164
|
+
}),
|
|
165
|
+
})));
|
|
166
|
+
/** What the service sends: a value encoded with the schema of the matching HTTP route. */
|
|
167
|
+
function encoded(schema, value) {
|
|
168
|
+
return Schema.encodeUnknownEffect(schema)(value).pipe(Effect.orDie);
|
|
169
|
+
}
|
|
170
|
+
const GetArguments = Schema.Struct({ id: IdParam });
|
|
171
|
+
const HistoryArguments = Schema.Struct({
|
|
172
|
+
id: IdParam,
|
|
173
|
+
...HistoryParams,
|
|
174
|
+
});
|
|
175
|
+
function structured(value) {
|
|
176
|
+
return { text: JSON.stringify(value), structured: value };
|
|
177
|
+
}
|
|
178
|
+
function errorText(body) {
|
|
179
|
+
const issues = Array.isArray(body.issues)
|
|
180
|
+
? body.issues.map(({ path, message }) => `\n- ${path.length > 0 ? `${path.join(".")}: ` : ""}${message}`)
|
|
181
|
+
: [];
|
|
182
|
+
return `${body.detail} (${body.error}, request ${body.requestId})${issues.join("")}`;
|
|
183
|
+
}
|
|
184
|
+
/** SegmentStream's refusal when its deployed manifest is not the one the service checked against. */
|
|
185
|
+
const VERSION_MISMATCH = "ontology_version_mismatch";
|
|
186
|
+
/** What the agent reads instead of SegmentStream's detail, for refusals it must act on itself. */
|
|
187
|
+
const AGENT_DETAILS = {
|
|
188
|
+
[VERSION_MISMATCH]: "The ontology changed; call list_metrics for the current metric names, then call " +
|
|
189
|
+
"query_metrics with them.",
|
|
190
|
+
[SCOPE_MISMATCH]: "SegmentStream would read entities this app's ontology service did not check, so it refused. " +
|
|
191
|
+
"Retrying will not help: ask the app's operator to update @segmentstream/ontology and " +
|
|
192
|
+
"redeploy.",
|
|
193
|
+
};
|
|
194
|
+
/**
|
|
195
|
+
* SegmentStream's answer to a metric query as the model reads it: the body as structured content, its
|
|
196
|
+
* warnings first in the text so they are not missed, and an error status as a tool error.
|
|
197
|
+
*/
|
|
198
|
+
function metricQueryOutput(answer) {
|
|
199
|
+
const { body } = answer;
|
|
200
|
+
if (answer.status >= 400) {
|
|
201
|
+
const code = typeof body.error === "string" ? body.error : `HTTP ${answer.status}`;
|
|
202
|
+
const reference = typeof body.requestId === "string" ? `, request ${body.requestId}` : "";
|
|
203
|
+
const detail = AGENT_DETAILS[code] ??
|
|
204
|
+
(typeof body.detail === "string" ? body.detail : "The metric query failed.");
|
|
205
|
+
return { text: `${detail} (${code}${reference})`, structured: body, isError: true };
|
|
206
|
+
}
|
|
207
|
+
return withWarnings(body, warningsOf(body));
|
|
208
|
+
}
|
|
209
|
+
/** The warnings of SegmentStream's answer to a metric query, such as a filter some entity missed. */
|
|
210
|
+
function warningsOf(body) {
|
|
211
|
+
const execution = body.execution;
|
|
212
|
+
return Array.isArray(execution?.warnings)
|
|
213
|
+
? execution.warnings.filter((warning) => typeof warning === "string")
|
|
214
|
+
: [];
|
|
215
|
+
}
|
|
216
|
+
/** Structured content as the model reads it, with the warnings first in the text. */
|
|
217
|
+
function withWarnings(value, warnings) {
|
|
218
|
+
const json = JSON.stringify(value);
|
|
219
|
+
return {
|
|
220
|
+
text: warnings.length === 0
|
|
221
|
+
? json
|
|
222
|
+
: `Warnings:\n${warnings.map((warning) => `- ${warning}`).join("\n")}\n\n${json}`,
|
|
223
|
+
structured: value,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* A failed tool call as the model reads it: `isError`, the REST route's error body as structured
|
|
228
|
+
* content, and the same in a sentence. Unexpected failures are logged with their cause and answered
|
|
229
|
+
* with the request id only.
|
|
230
|
+
*/
|
|
231
|
+
function toolFailure(cause, requestId) {
|
|
232
|
+
if (Cause.hasInterruptsOnly(cause)) {
|
|
233
|
+
return Effect.failCause(cause);
|
|
234
|
+
}
|
|
235
|
+
const typedFailure = Option.getOrUndefined(Cause.findErrorOption(cause));
|
|
236
|
+
const defect = typedFailure === undefined ? Cause.squash(cause) : undefined;
|
|
237
|
+
const error = typedFailure ?? (defect instanceof DeploymentAdmissionError ? defect : undefined);
|
|
238
|
+
const expected = error instanceof NavigationError
|
|
239
|
+
? {
|
|
240
|
+
error: error.error,
|
|
241
|
+
detail: error.detail,
|
|
242
|
+
requestId,
|
|
243
|
+
...(error.revision === undefined ? {} : { revision: error.revision }),
|
|
244
|
+
}
|
|
245
|
+
: error instanceof DiscoveryError
|
|
246
|
+
? {
|
|
247
|
+
error: error.error,
|
|
248
|
+
detail: error.detail,
|
|
249
|
+
requestId,
|
|
250
|
+
...(error.hash === undefined ? {} : { hash: error.hash }),
|
|
251
|
+
...(error.suggestions === undefined ? {} : { suggestions: error.suggestions }),
|
|
252
|
+
}
|
|
253
|
+
: error instanceof InvalidArguments
|
|
254
|
+
? {
|
|
255
|
+
error: "invalid_request",
|
|
256
|
+
detail: "The tool's arguments are invalid.",
|
|
257
|
+
requestId,
|
|
258
|
+
issues: error.issues,
|
|
259
|
+
}
|
|
260
|
+
: error === undefined
|
|
261
|
+
? undefined
|
|
262
|
+
: expectedFailure(error, requestId)?.body;
|
|
263
|
+
const failure = expected !== undefined
|
|
264
|
+
? Effect.succeed(expected)
|
|
265
|
+
: isStorageFailure(error)
|
|
266
|
+
? Effect.logError("ontology.storage.failed", cause).pipe(Effect.annotateLogs({ requestId }), Effect.as(storageUnavailable(requestId).body))
|
|
267
|
+
: Effect.logError("ontology.mcp.tool_failed", cause).pipe(Effect.annotateLogs({ requestId }), Effect.as(internalError(requestId).body));
|
|
268
|
+
const reported = error instanceof NavigationError && error.error === "page_catalog_unavailable"
|
|
269
|
+
? Effect.logError("ontology.navigation.unavailable", error.cause).pipe(Effect.annotateLogs({ requestId }), Effect.andThen(failure))
|
|
270
|
+
: failure;
|
|
271
|
+
return Effect.map(reported, (body) => ({
|
|
272
|
+
content: [{ type: "text", text: errorText(body) }],
|
|
273
|
+
structuredContent: errorBodyForWire(body),
|
|
274
|
+
isError: true,
|
|
275
|
+
}));
|
|
276
|
+
}
|
|
277
|
+
function runTool(server, tool, args, request, preview) {
|
|
278
|
+
const { built, operations, queryMetrics, conversations, discovery, memberFiles, chats } = server;
|
|
279
|
+
return Effect.gen(function* () {
|
|
280
|
+
const { target } = tool;
|
|
281
|
+
const caller = yield* currentCaller;
|
|
282
|
+
switch (target.kind) {
|
|
283
|
+
case "navigation": {
|
|
284
|
+
const principal = yield* CurrentPrincipal;
|
|
285
|
+
const input = yield* decodeArguments(NavigationInput, args);
|
|
286
|
+
const result = yield* Effect.try({
|
|
287
|
+
try: () => {
|
|
288
|
+
if (server.navigation === undefined) {
|
|
289
|
+
throw new Error("Navigation tool has no deployment catalog binding.");
|
|
290
|
+
}
|
|
291
|
+
return server.navigation(input, principal, caller, publicOrigin(request));
|
|
292
|
+
},
|
|
293
|
+
catch: (error) => error,
|
|
294
|
+
});
|
|
295
|
+
return structured(result);
|
|
296
|
+
}
|
|
297
|
+
case "discover": {
|
|
298
|
+
const principal = yield* CurrentPrincipal;
|
|
299
|
+
const input = yield* decodeArguments(DiscoverInput, args);
|
|
300
|
+
const model = built.plan.ontology.config.discovery?.model;
|
|
301
|
+
// Meaning ranks entities on one page; actions, metrics and later pages match words.
|
|
302
|
+
if (model !== undefined &&
|
|
303
|
+
input.after === undefined &&
|
|
304
|
+
(input.kind ?? "entity") === "entity") {
|
|
305
|
+
const credential = gatewayCredential(identityToken(request));
|
|
306
|
+
const decide = server.decide ?? gatewayDecisions({ credential: () => credential });
|
|
307
|
+
const ranked = yield* Effect.tryPromise({
|
|
308
|
+
try: () => discovery.discoverByMeaning(input, principal, caller, { decide, model }),
|
|
309
|
+
catch: (error) => error,
|
|
310
|
+
}).pipe(Effect.exit);
|
|
311
|
+
if (ranked._tag === "Success") {
|
|
312
|
+
return structured(ranked.value);
|
|
313
|
+
}
|
|
314
|
+
const failure = Cause.squash(ranked.cause);
|
|
315
|
+
if (failure instanceof DiscoveryError) {
|
|
316
|
+
return yield* Effect.fail(failure);
|
|
317
|
+
}
|
|
318
|
+
// A search never fails because of the model: word matching answers, and operators
|
|
319
|
+
// see why, without the gateway credential.
|
|
320
|
+
yield* Effect.logWarning("ontology.discover.ranking_failed").pipe(Effect.annotateLogs({
|
|
321
|
+
model,
|
|
322
|
+
error: describeError(failure, (text) => credential === undefined ? text : text.split(credential.token).join("[redacted]")),
|
|
323
|
+
}));
|
|
324
|
+
return structured({ ...discovery.discover(input, principal, caller), ranking: "words" });
|
|
325
|
+
}
|
|
326
|
+
const result = yield* Effect.try({
|
|
327
|
+
try: () => discovery.discover(input, principal, caller),
|
|
328
|
+
catch: (error) => error,
|
|
329
|
+
});
|
|
330
|
+
return structured(model === undefined ? result : { ...result, ranking: "words" });
|
|
331
|
+
}
|
|
332
|
+
case "query-metrics": {
|
|
333
|
+
// The route's facade, as the caller: the arguments are the request body.
|
|
334
|
+
const answer = yield* queryMetrics(args).pipe(Effect.catchTag("InvalidRequestBody", (error) => Effect.fail(new InvalidArguments({ issues: error.issues }))));
|
|
335
|
+
return metricQueryOutput(answer);
|
|
336
|
+
}
|
|
337
|
+
case "list-metrics": {
|
|
338
|
+
const { search } = yield* decodeArguments(ListMetricsInput, args);
|
|
339
|
+
const permissions = effectivePermissions(yield* CurrentPrincipal);
|
|
340
|
+
return structured({
|
|
341
|
+
metrics: listMetrics(built.plan.ontology.manifest, permissions, search),
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
case "get-dimensions": {
|
|
345
|
+
const { metrics, search } = yield* decodeArguments(GetDimensionsInput, args);
|
|
346
|
+
const permissions = effectivePermissions(yield* CurrentPrincipal);
|
|
347
|
+
const answer = metricDimensions(built.plan.ontology.manifest, permissions, metrics, search);
|
|
348
|
+
if ("reason" in answer) {
|
|
349
|
+
return yield* new InvalidArguments({
|
|
350
|
+
issues: [{ path: ["metrics"], message: answer.reason }],
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
return structured(answer);
|
|
354
|
+
}
|
|
355
|
+
case "get-dimension-values": {
|
|
356
|
+
const input = yield* decodeArguments(GetDimensionValuesInput, args);
|
|
357
|
+
const permissions = effectivePermissions(yield* CurrentPrincipal);
|
|
358
|
+
const { dimension } = input;
|
|
359
|
+
const plan = planDimensionValues(built.plan.ontology.manifest, permissions, dimension, input.metrics);
|
|
360
|
+
switch (plan.kind) {
|
|
361
|
+
case "refused":
|
|
362
|
+
return yield* new InvalidArguments({
|
|
363
|
+
issues: [{ path: ["dimension"], message: plan.reason }],
|
|
364
|
+
});
|
|
365
|
+
case "boolean":
|
|
366
|
+
return structured({ dimension, values: [true, false], truncated: false });
|
|
367
|
+
case "options": {
|
|
368
|
+
const options = plan.property.options ?? [];
|
|
369
|
+
return structured({
|
|
370
|
+
dimension,
|
|
371
|
+
values: options.map((option) => option.value),
|
|
372
|
+
options: options.map(({ value, label, description }) => ({
|
|
373
|
+
value,
|
|
374
|
+
label,
|
|
375
|
+
...(description === "" ? {} : { description }),
|
|
376
|
+
})),
|
|
377
|
+
truncated: false,
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
const answer = yield* queryMetrics({
|
|
382
|
+
metrics: plan.metrics,
|
|
383
|
+
dimensions: [dimension],
|
|
384
|
+
filters: [
|
|
385
|
+
...(input.filters ?? []),
|
|
386
|
+
...(input.search === undefined
|
|
387
|
+
? []
|
|
388
|
+
: [{ dimension, operator: "contains", value: input.search }]),
|
|
389
|
+
],
|
|
390
|
+
orderBy: [{ dimension, direction: "asc" }],
|
|
391
|
+
limit: input.limit ?? VALUES_DEFAULT,
|
|
392
|
+
}).pipe(Effect.catchTag("InvalidRequestBody", (error) => Effect.fail(new InvalidArguments({ issues: error.issues }))));
|
|
393
|
+
if (answer.status >= 400) {
|
|
394
|
+
return metricQueryOutput(answer);
|
|
395
|
+
}
|
|
396
|
+
const { body } = answer;
|
|
397
|
+
const requested = new Set(plan.metrics);
|
|
398
|
+
const fields = Array.isArray(body.fields) ? body.fields : [];
|
|
399
|
+
// A scoped dimension's parent comes first, marked implicit: the values are the requested
|
|
400
|
+
// dimension's, under its own name when the platform answers with it.
|
|
401
|
+
const grouped = fields.filter((candidate) => typeof candidate.name === "string" &&
|
|
402
|
+
!requested.has(candidate.name) &&
|
|
403
|
+
candidate.implicit !== true);
|
|
404
|
+
const field = (grouped.find((candidate) => candidate.name === dimension) ?? grouped[0])
|
|
405
|
+
?.name;
|
|
406
|
+
const rows = Array.isArray(body.rows) ? body.rows : [];
|
|
407
|
+
const values = [
|
|
408
|
+
...new Set(rows.map((row) => row[typeof field === "string" ? field : dimension] ?? null)),
|
|
409
|
+
];
|
|
410
|
+
// A filter that missed some metric's entity leaves its rows unfiltered: the warnings say so.
|
|
411
|
+
const warnings = warningsOf(body);
|
|
412
|
+
return withWarnings({
|
|
413
|
+
dimension,
|
|
414
|
+
values: values,
|
|
415
|
+
metrics: [...plan.metrics],
|
|
416
|
+
truncated: body.truncated === true,
|
|
417
|
+
...(warnings.length === 0 ? {} : { warnings: [...warnings] }),
|
|
418
|
+
}, warnings);
|
|
419
|
+
}
|
|
420
|
+
case "me":
|
|
421
|
+
return structured(yield* encoded(Me, yield* operations.caller));
|
|
422
|
+
case "chat-list":
|
|
423
|
+
return structured(yield* chats.list(yield* decodeArguments(ChatList, args)));
|
|
424
|
+
case "chat-history":
|
|
425
|
+
return structured(yield* chats.history(yield* decodeArguments(ChatHistory, args), identityToken(request)));
|
|
426
|
+
case "find": {
|
|
427
|
+
const schemas = built.schemas.get(target.planned.name);
|
|
428
|
+
const query = (yield* decodeArguments(schemas.query, args));
|
|
429
|
+
const page = yield* operations.find(target.planned, toQuery(query));
|
|
430
|
+
return structured(yield* encoded(schemas.findResult, page));
|
|
431
|
+
}
|
|
432
|
+
case "get": {
|
|
433
|
+
const schemas = built.schemas.get(target.planned.name);
|
|
434
|
+
const { id } = yield* decodeArguments(target.planned.resolved.entity.definition.source === undefined
|
|
435
|
+
? GetArguments
|
|
436
|
+
: Schema.Struct({ id: WarehouseIdParam }), args);
|
|
437
|
+
return structured(yield* encoded(schemas.record, yield* operations.get(target.planned, id)));
|
|
438
|
+
}
|
|
439
|
+
case "member-file":
|
|
440
|
+
return structured(yield* memberFiles(target.operation, args, identityToken(request)));
|
|
441
|
+
case "conversation-search": {
|
|
442
|
+
if (!conversations) {
|
|
443
|
+
return yield* new ActionRejected({
|
|
444
|
+
message: "The conversation archive is not configured.",
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
const search = yield* decodeArguments(ConversationSearch, args);
|
|
448
|
+
const principal = yield* CurrentPrincipal;
|
|
449
|
+
return structured(yield* conversations({ search, principal, identityToken: identityToken(request) }));
|
|
450
|
+
}
|
|
451
|
+
case "history": {
|
|
452
|
+
const { id, ...params } = yield* decodeArguments(HistoryArguments, args);
|
|
453
|
+
return structured(yield* encoded(HistoryPage, yield* operations.history(target.planned, id, params)));
|
|
454
|
+
}
|
|
455
|
+
case "action": {
|
|
456
|
+
if (target.planned.name === "agent_chat" && target.action.name === "post_message") {
|
|
457
|
+
yield* decodeArguments(caller.kind === "runtime" ? RuntimeChatPostWire : ChatPostWire, args);
|
|
458
|
+
const answer = yield* operations.runAction(target.planned, target.action, {
|
|
459
|
+
input: args,
|
|
460
|
+
validateOnly: false,
|
|
461
|
+
identityToken: identityToken(request),
|
|
462
|
+
});
|
|
463
|
+
return structured(yield* encoded(ChatPostResult, answer.result));
|
|
464
|
+
}
|
|
465
|
+
const { [ACTION_OPTIONS_ARGUMENT]: rawOptions, ...input } = args;
|
|
466
|
+
// What the person confirms is described from the business input alone, and nothing runs.
|
|
467
|
+
if (preview !== undefined) {
|
|
468
|
+
const answer = yield* operations.runAction(target.planned, target.action, {
|
|
469
|
+
input,
|
|
470
|
+
validateOnly: false,
|
|
471
|
+
confirm: preview,
|
|
472
|
+
identityToken: identityToken(request),
|
|
473
|
+
});
|
|
474
|
+
return structured({
|
|
475
|
+
confirmation: (answer.result ?? null),
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
const options = rawOptions === undefined ||
|
|
479
|
+
(uploadFields(target.action.action.input).length === 0 &&
|
|
480
|
+
(target.action.action.files?.length ?? 0) === 0)
|
|
481
|
+
? undefined
|
|
482
|
+
: yield* decodeArguments(McpActionOptions, rawOptions);
|
|
483
|
+
const answer = yield* operations.runAction(target.planned, target.action, {
|
|
484
|
+
input: options === undefined ? args : input,
|
|
485
|
+
validateOnly: false,
|
|
486
|
+
...(options === undefined ? {} : { options }),
|
|
487
|
+
identityToken: identityToken(request),
|
|
488
|
+
});
|
|
489
|
+
return structured(yield* encoded(ActionResponse, answer));
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* Calls a tool, or with `preview`, asks a confirmable action what its person confirms: a tool that
|
|
496
|
+
* describes no confirmation of its own is then refused, so a stale or mistaken preview runs nothing.
|
|
497
|
+
*/
|
|
498
|
+
function callTool(server, params, request, preview) {
|
|
499
|
+
return Effect.gen(function* () {
|
|
500
|
+
const requestId = yield* RequestId;
|
|
501
|
+
const caller = yield* currentCaller;
|
|
502
|
+
const principal = yield* CurrentPrincipal;
|
|
503
|
+
const call = params;
|
|
504
|
+
const tool = server.tools.find((candidate) => candidate.name === call.name);
|
|
505
|
+
const allowed = yield* requireRuntimeOperation({
|
|
506
|
+
...(typeof call.name === "string" ? { name: call.name } : {}),
|
|
507
|
+
...(tool === undefined ? {} : { target: tool.target }),
|
|
508
|
+
}).pipe(Effect.exit);
|
|
509
|
+
if (allowed._tag === "Failure") {
|
|
510
|
+
return { kind: "called", result: yield* toolFailure(allowed.cause, requestId) };
|
|
511
|
+
}
|
|
512
|
+
if (tool === undefined) {
|
|
513
|
+
const candidates = visibleTools(server.tools, principal, caller)
|
|
514
|
+
.filter((candidate) => candidate.target.kind === "action")
|
|
515
|
+
.map((candidate) => candidate.name);
|
|
516
|
+
const suggestions = typeof call.name === "string" ? suggestNames(call.name, candidates) : [];
|
|
517
|
+
return {
|
|
518
|
+
kind: "invalid",
|
|
519
|
+
message: suggestionDetail(`Unknown tool: ${String(call.name)}.`, suggestions),
|
|
520
|
+
data: suggestions.length === 0 ? undefined : { suggestions },
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
const args = call.arguments ?? {};
|
|
524
|
+
if (!isObject(args)) {
|
|
525
|
+
return {
|
|
526
|
+
kind: "invalid",
|
|
527
|
+
message: "The tool's arguments must be an object.",
|
|
528
|
+
data: undefined,
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
if (preview !== undefined && tool.confirmation?.preview !== true) {
|
|
532
|
+
return {
|
|
533
|
+
kind: "invalid",
|
|
534
|
+
message: `${tool.name} describes no confirmation of its own; nothing was run.`,
|
|
535
|
+
data: undefined,
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
const execute = Effect.gen(function* () {
|
|
539
|
+
const consumed = yield* consumeChatEnvelope(args, yield* currentCaller);
|
|
540
|
+
const run = runTool(server, tool, withoutConfirmation(consumed.input), request, preview);
|
|
541
|
+
return yield* consumed.envelope === undefined
|
|
542
|
+
? run
|
|
543
|
+
: run.pipe(Effect.provideService(CurrentChatCall, consumed.envelope));
|
|
544
|
+
});
|
|
545
|
+
const result = yield* execute.pipe(Effect.map(({ text, structured: content, isError }) => ({
|
|
546
|
+
content: [{ type: "text", text }],
|
|
547
|
+
...(content === undefined ? {} : { structuredContent: content }),
|
|
548
|
+
...(isError === true ? { isError } : {}),
|
|
549
|
+
})), Effect.catchCause((cause) => toolFailure(cause, requestId)));
|
|
550
|
+
return { kind: "called", result };
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
const CATALOG_URI = "ontology://catalog";
|
|
554
|
+
const TOOL_URI = "ontology://tools/";
|
|
555
|
+
/**
|
|
556
|
+
* One tool as this caller's `tools/list` lists it, for a client that holds only its name, such as
|
|
557
|
+
* an agent runtime confirming a call that another of its tools makes on the member's behalf.
|
|
558
|
+
*/
|
|
559
|
+
const RESOURCE_TEMPLATES = [
|
|
560
|
+
{
|
|
561
|
+
uriTemplate: `${TOOL_URI}{name}`,
|
|
562
|
+
name: "tool",
|
|
563
|
+
title: "One listed tool",
|
|
564
|
+
description: "The tool named name exactly as tools/list lists it to the caller, or not found when it does " +
|
|
565
|
+
"not list it.",
|
|
566
|
+
mimeType: "application/json",
|
|
567
|
+
},
|
|
568
|
+
];
|
|
569
|
+
const RESOURCES = [
|
|
570
|
+
{
|
|
571
|
+
uri: CATALOG_URI,
|
|
572
|
+
name: "catalog",
|
|
573
|
+
title: "The ontology's catalog",
|
|
574
|
+
description: "Explicit full manifest export, with current permission filtering. Can be large; the " +
|
|
575
|
+
"tools themselves describe what an agent needs.",
|
|
576
|
+
mimeType: "application/json",
|
|
577
|
+
},
|
|
578
|
+
];
|
|
579
|
+
function readResource({ built, tools }, uri) {
|
|
580
|
+
return Effect.gen(function* () {
|
|
581
|
+
const principal = yield* CurrentPrincipal;
|
|
582
|
+
const caller = yield* currentCaller;
|
|
583
|
+
const { manifest } = built.plan.ontology;
|
|
584
|
+
switch (uri) {
|
|
585
|
+
case CATALOG_URI:
|
|
586
|
+
return [
|
|
587
|
+
{
|
|
588
|
+
uri,
|
|
589
|
+
mimeType: "application/json",
|
|
590
|
+
text: JSON.stringify(encodeManifest(catalogFor(runtimeManifest(manifest, caller), effectivePermissions(principal)))),
|
|
591
|
+
},
|
|
592
|
+
];
|
|
593
|
+
default: {
|
|
594
|
+
const name = typeof uri === "string" && uri.startsWith(TOOL_URI) ? uri.slice(TOOL_URI.length) : "";
|
|
595
|
+
const tool = visibleTools(tools, principal, caller).find((candidate) => candidate.name === name);
|
|
596
|
+
return tool === undefined
|
|
597
|
+
? undefined
|
|
598
|
+
: [{ uri, mimeType: "application/json", text: JSON.stringify(toolDefinition(tool)) }];
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
});
|
|
602
|
+
}
|
|
603
|
+
/**
|
|
604
|
+
* When to offer feedback, for a caller who may send it. Platform guidance for the agent; the agent
|
|
605
|
+
* words the offer itself, in the conversation's language.
|
|
606
|
+
*/
|
|
607
|
+
export const FEEDBACK_INSTRUCTIONS = "Feedback: this app accepts feedback for its builders. Offer to send it when the user wants " +
|
|
608
|
+
"something no permitted tool, field or data provides, an operation is refused for lack of " +
|
|
609
|
+
"permission, an operation fails unexpectedly or a retryable failure persists, or the user says " +
|
|
610
|
+
"something works badly or asks for something new. Do not offer what you can do yourself; offer " +
|
|
611
|
+
"once per problem. Before submitting, look for the user's open app_feedback with the same " +
|
|
612
|
+
"subject. Submit with app_feedback_submit only after the user agrees to the title and summary you " +
|
|
613
|
+
"show in their language. At the " +
|
|
614
|
+
"start of a conversation, find the user's app_feedback with responded_at set and seen_at empty, " +
|
|
615
|
+
"tell them the builders' response, then call app_feedback_acknowledge with its responded_at.";
|
|
616
|
+
export const AUTOMATIC_FAILURE_REPORT_INSTRUCTIONS = "Feedback: During requested work, this workspace preauthorizes minimal technical failure reports. " +
|
|
617
|
+
"Automatically record unexpected or persistent tool/connection failures through app_feedback_submit; " +
|
|
618
|
+
"first reuse the caller's open report with the same subject. Include only the operation, safe technical " +
|
|
619
|
+
"reason and request_id/error code, never business contents, credentials or files. Explain the actual " +
|
|
620
|
+
"reason and that the report was recorded in one or two sentences; do not merely offer to report it, " +
|
|
621
|
+
"and never claim it was recorded before a successful submission or existing-item read. " +
|
|
622
|
+
"Feature/change requests and sharing task contents or files still require the member's agreement " +
|
|
623
|
+
"to the title and summary. At conversation start, read the caller's unseen builder responses and " +
|
|
624
|
+
"acknowledge only the response you have relayed.";
|
|
625
|
+
/**
|
|
626
|
+
* Fixed guidance keeps initialization independent of ontology size: the feedback paragraph depends
|
|
627
|
+
* only on whether the caller may run `app_feedback_submit`.
|
|
628
|
+
*/
|
|
629
|
+
function instructions(feedback, files, automaticFailureReports, byMeaning, metrics) {
|
|
630
|
+
return [
|
|
631
|
+
"This server is the app's ontology. Every change goes through an action, is checked " +
|
|
632
|
+
"against your current permissions, and is kept in history.",
|
|
633
|
+
"Each <entity>_find describes its entity: what its fields and their values mean, its " +
|
|
634
|
+
"related records and its metrics. Each action's tool says what it does and when it is " +
|
|
635
|
+
"refused." +
|
|
636
|
+
(metrics
|
|
637
|
+
? " list_metrics lists every metric you may compute, get_dimensions what they can be " +
|
|
638
|
+
"grouped by, get_dimension_values a dimension's values, and query_metrics computes them."
|
|
639
|
+
: "") +
|
|
640
|
+
(byMeaning
|
|
641
|
+
? " To learn which entities a request concerns, call discover with the request."
|
|
642
|
+
: ""),
|
|
643
|
+
"Call me for the caller and permissions. Metadata visibility does not grant record access. " +
|
|
644
|
+
"Stored entities use <entity>_find, <entity>_get, <entity>_history and permitted action tools.",
|
|
645
|
+
`For analytics, ${RECORDS_OR_METRIC_QUERIES}. Warehouse observations also support find/get and relationship reads.`,
|
|
646
|
+
"Use the authenticated tool annotations for confirmation: permitted actions classified as " +
|
|
647
|
+
"non-destructive and reversible need no extra approval. Destructive, irreversible or " +
|
|
648
|
+
"external actions (openWorldHint) require confirmation before preparation; idempotence " +
|
|
649
|
+
"alone does not waive it. Present confirmations in the conversation's language using " +
|
|
650
|
+
"action, entity, field and option labels and readable record titles, never tool names, ids or JSON.",
|
|
651
|
+
...(feedback
|
|
652
|
+
? [
|
|
653
|
+
(automaticFailureReports
|
|
654
|
+
? AUTOMATIC_FAILURE_REPORT_INSTRUCTIONS
|
|
655
|
+
: FEEDBACK_INSTRUCTIONS) +
|
|
656
|
+
(files ? " Add each screenshot or file with app_feedback_file_attach." : ""),
|
|
657
|
+
]
|
|
658
|
+
: []),
|
|
659
|
+
].join("\n\n");
|
|
660
|
+
}
|
|
661
|
+
function negotiate(requested) {
|
|
662
|
+
return MCP_PROTOCOL_VERSIONS.includes(requested)
|
|
663
|
+
? requested
|
|
664
|
+
: MCP_PROTOCOL_VERSIONS[0];
|
|
665
|
+
}
|
|
666
|
+
/** Answers one JSON-RPC request as the authenticated caller. */
|
|
667
|
+
function dispatch(server, message, request) {
|
|
668
|
+
return Effect.gen(function* () {
|
|
669
|
+
const { id, method, params } = message;
|
|
670
|
+
const principal = yield* CurrentPrincipal;
|
|
671
|
+
const caller = yield* currentCaller;
|
|
672
|
+
switch (method) {
|
|
673
|
+
case "initialize":
|
|
674
|
+
return rpcResult(id, {
|
|
675
|
+
protocolVersion: negotiate(params.protocolVersion),
|
|
676
|
+
capabilities: {
|
|
677
|
+
tools: {},
|
|
678
|
+
resources: {},
|
|
679
|
+
// The agent runtime learns that tool calls accept its chat source without listing
|
|
680
|
+
// the catalog. The `me` tool keeps its `_meta` marker for older agents.
|
|
681
|
+
experimental: { [RUNTIME_SOURCE_META]: {} },
|
|
682
|
+
},
|
|
683
|
+
serverInfo: {
|
|
684
|
+
name: "segmentstream-ontology",
|
|
685
|
+
title: "Ontology",
|
|
686
|
+
version: server.built.plan.ontology.manifest.hash,
|
|
687
|
+
},
|
|
688
|
+
instructions: instructions(visibleTools(server.tools, principal, caller).some((tool) => tool.name === "app_feedback_submit"), visibleTools(server.tools, principal, caller).some((tool) => tool.name === "app_feedback_file_attach"), server.built.plan.ontology.manifest.feedback?.automaticFailureReports === true, server.built.plan.ontology.config.discovery !== undefined, visibleTools(server.tools, principal, caller).some((tool) => tool.name === "query_metrics")),
|
|
689
|
+
});
|
|
690
|
+
case "ping":
|
|
691
|
+
return rpcResult(id, {});
|
|
692
|
+
case "tools/list": {
|
|
693
|
+
return yield* Effect.try({
|
|
694
|
+
try: () => listTools(server.tools, principal, caller, server.built.plan.ontology.manifest.hash, params.cursor),
|
|
695
|
+
catch: (error) => error,
|
|
696
|
+
}).pipe(Effect.map((result) => rpcResult(id, result)), Effect.catchIf((error) => error instanceof DiscoveryError, (error) => Effect.succeed(rpcError(id, INVALID_PARAMS, error.detail))));
|
|
697
|
+
}
|
|
698
|
+
case "tools/call":
|
|
699
|
+
case CONFIRMATION_PREVIEW_METHOD: {
|
|
700
|
+
const called = yield* callTool(server, params, request, method === CONFIRMATION_PREVIEW_METHOD
|
|
701
|
+
? { locale: previewLocale(params.locale) }
|
|
702
|
+
: undefined);
|
|
703
|
+
return called.kind === "invalid"
|
|
704
|
+
? rpcError(id, INVALID_PARAMS, called.message, 200, called.data)
|
|
705
|
+
: rpcResult(id, called.result);
|
|
706
|
+
}
|
|
707
|
+
case "resources/list":
|
|
708
|
+
return rpcResult(id, { resources: RESOURCES });
|
|
709
|
+
case "resources/templates/list":
|
|
710
|
+
return rpcResult(id, { resourceTemplates: RESOURCE_TEMPLATES });
|
|
711
|
+
case "resources/read": {
|
|
712
|
+
const contents = yield* readResource(server, params.uri);
|
|
713
|
+
return contents === undefined
|
|
714
|
+
? rpcError(id, RESOURCE_NOT_FOUND, `Unknown resource: ${String(params.uri)}.`)
|
|
715
|
+
: rpcResult(id, { contents });
|
|
716
|
+
}
|
|
717
|
+
default:
|
|
718
|
+
return rpcError(id, METHOD_NOT_FOUND, `Method not found: ${method}.`);
|
|
719
|
+
}
|
|
720
|
+
});
|
|
721
|
+
}
|
|
722
|
+
/** Whether the supplied Origin differs from this service's public host. */
|
|
723
|
+
function crossOrigin(request) {
|
|
724
|
+
const origin = request.headers.origin;
|
|
725
|
+
if (origin === undefined) {
|
|
726
|
+
return false;
|
|
727
|
+
}
|
|
728
|
+
return !URL.canParse(origin) || new URL(origin).host !== new URL(publicOrigin(request)).host;
|
|
729
|
+
}
|
|
730
|
+
/** GET and POST deliberately share bearer-only authentication and Origin policy. */
|
|
731
|
+
function withMcpPrincipal(request, verifyToken, verifyRuntime, backend, answer, workspaceId) {
|
|
732
|
+
const credential = HttpApiBuilder.securityDecode(HttpApiSecurity.bearer);
|
|
733
|
+
return Effect.gen(function* () {
|
|
734
|
+
const requestId = yield* RequestId;
|
|
735
|
+
const bearer = yield* Effect.provideService(credential, HttpServerRequest.HttpServerRequest, request);
|
|
736
|
+
// Explicit bearer credentials still pass through authentication below, even if invalid.
|
|
737
|
+
if (Redacted.value(bearer).trim().length === 0 && crossOrigin(request)) {
|
|
738
|
+
return HttpServerResponse.jsonUnsafe(errorBodyForWire({
|
|
739
|
+
error: "forbidden",
|
|
740
|
+
detail: "Requests from another origin are refused.",
|
|
741
|
+
requestId,
|
|
742
|
+
}), { status: 403 });
|
|
743
|
+
}
|
|
744
|
+
const origin = publicOrigin(request);
|
|
745
|
+
const runtime = runtimeCredential(verifyRuntime, request);
|
|
746
|
+
// With the OAuth tables, members' access tokens work here, and a 401 tells the client where
|
|
747
|
+
// the member authorizes it. The app's agent calls as the member it names, or, running a task a
|
|
748
|
+
// workflow handed it, as the workflow's user.
|
|
749
|
+
return yield* withPrincipal(bearer, verifyToken, backend, answer, {
|
|
750
|
+
...(runtime === undefined ? {} : { runtime }),
|
|
751
|
+
...(workspaceId === undefined ? {} : { agentTasks: { workspaceId } }),
|
|
752
|
+
...(backend.oauth === undefined
|
|
753
|
+
? {}
|
|
754
|
+
: {
|
|
755
|
+
oauthResource: mcpResource(origin),
|
|
756
|
+
challenge: (missing) => mcpChallenge(origin, missing),
|
|
757
|
+
}),
|
|
758
|
+
});
|
|
759
|
+
});
|
|
760
|
+
}
|
|
761
|
+
/** `POST /mcp`: one JSON-RPC message in, one JSON response out, nothing kept between requests. */
|
|
762
|
+
function handlePost(server, verifyToken, verifyRuntime, backend, workspaceId) {
|
|
763
|
+
return (request) => {
|
|
764
|
+
const answer = Effect.gen(function* () {
|
|
765
|
+
const text = yield* Effect.orDie(request.text);
|
|
766
|
+
let body;
|
|
767
|
+
try {
|
|
768
|
+
body = JSON.parse(text);
|
|
769
|
+
}
|
|
770
|
+
catch {
|
|
771
|
+
return rpcError(null, PARSE_ERROR, "The body is not valid JSON.", 400);
|
|
772
|
+
}
|
|
773
|
+
if (Array.isArray(body)) {
|
|
774
|
+
return rpcError(null, INVALID_REQUEST, "Batches are not supported.", 400);
|
|
775
|
+
}
|
|
776
|
+
const message = classify(body);
|
|
777
|
+
const version = request.headers[PROTOCOL_VERSION_HEADER];
|
|
778
|
+
// `initialize` negotiates the revision, so its header is not checked: a 400 there would make
|
|
779
|
+
// a client fall back to the legacy HTTP+SSE transport.
|
|
780
|
+
if (version !== undefined &&
|
|
781
|
+
!MCP_PROTOCOL_VERSIONS.includes(version) &&
|
|
782
|
+
!(message.kind === "request" && message.method === "initialize")) {
|
|
783
|
+
return rpcError(null, INVALID_REQUEST, `Unsupported MCP-Protocol-Version ${version}; this server speaks ` +
|
|
784
|
+
`${MCP_PROTOCOL_VERSIONS.join(" and ")}.`, 400);
|
|
785
|
+
}
|
|
786
|
+
switch (message.kind) {
|
|
787
|
+
case "invalid":
|
|
788
|
+
return rpcError(message.id, INVALID_REQUEST, "Invalid JSON-RPC request.", 400);
|
|
789
|
+
case "other":
|
|
790
|
+
return ACCEPTED;
|
|
791
|
+
case "request":
|
|
792
|
+
return yield* dispatch(server, message, request);
|
|
793
|
+
}
|
|
794
|
+
});
|
|
795
|
+
return withMcpPrincipal(request, verifyToken, verifyRuntime, backend, answer, workspaceId);
|
|
796
|
+
};
|
|
797
|
+
}
|
|
798
|
+
/** An optional, authenticated server stream; stateless instances have no messages to send. */
|
|
799
|
+
function handleGet(request, options, backend) {
|
|
800
|
+
const acceptsStream = request.headers.accept?.split(",").some((range) => {
|
|
801
|
+
const [type, ...parameters] = range.trim().toLowerCase().split(";");
|
|
802
|
+
return (type?.trim() === "text/event-stream" &&
|
|
803
|
+
!parameters.some((parameter) => /^q\s*=\s*0(?:\.0*)?$/.test(parameter.trim())));
|
|
804
|
+
});
|
|
805
|
+
if (!acceptsStream) {
|
|
806
|
+
return Effect.succeed(METHOD_NOT_ALLOWED);
|
|
807
|
+
}
|
|
808
|
+
return withMcpPrincipal(request, options.verifyToken, options.verifyRuntime, backend, Effect.gen(function* () {
|
|
809
|
+
const version = request.headers[PROTOCOL_VERSION_HEADER];
|
|
810
|
+
if (version !== undefined &&
|
|
811
|
+
!MCP_PROTOCOL_VERSIONS.includes(version)) {
|
|
812
|
+
return rpcError(null, INVALID_REQUEST, `Unsupported MCP-Protocol-Version ${version}.`, 400);
|
|
813
|
+
}
|
|
814
|
+
const webRequest = yield* HttpServerRequest.toWeb(request);
|
|
815
|
+
return eventStream(webRequest.signal, options.streamMaxDurationMs ?? MCP_STREAM_MAX_DURATION_MS);
|
|
816
|
+
}), options.workspaceId);
|
|
817
|
+
}
|
|
818
|
+
/**
|
|
819
|
+
* Streamable HTTP without sessions: POST answers JSON-RPC, GET keeps an idle SSE stream alive,
|
|
820
|
+
* and DELETE answers 405. Every instance can answer any authenticated request independently.
|
|
821
|
+
*/
|
|
822
|
+
export function mcpLayer(options) {
|
|
823
|
+
return Layer.effectDiscard(Effect.gen(function* () {
|
|
824
|
+
const router = yield* HttpRouter.HttpRouter;
|
|
825
|
+
const backend = yield* OntologyBackend;
|
|
826
|
+
const warehouseQueries = yield* Effect.serviceOption(WarehouseQueries);
|
|
827
|
+
const tools = mcpTools(options.built, options.conversations !== undefined, {
|
|
828
|
+
attachments: backend.attachments !== undefined,
|
|
829
|
+
files: backend.blobs !== undefined,
|
|
830
|
+
}, options.navigation !== undefined);
|
|
831
|
+
const operations = makeOperations(backend, options.built.plan, options.hooks, options.connections, options.replicate, options.chatDelivery);
|
|
832
|
+
const conversations = options.conversations && withChatMembership(options.conversations, backend);
|
|
833
|
+
const server = {
|
|
834
|
+
chats: chatReads(backend, conversations, options.built.plan.ontology.config.chat?.attribution),
|
|
835
|
+
built: options.built,
|
|
836
|
+
tools,
|
|
837
|
+
discovery: createDiscovery(options.built, tools),
|
|
838
|
+
...(options.navigation === undefined
|
|
839
|
+
? {}
|
|
840
|
+
: {
|
|
841
|
+
navigation: createNavigation(options.navigation, options.built.plan.ontology.manifest, options.appOrigins ?? []),
|
|
842
|
+
}),
|
|
843
|
+
operations,
|
|
844
|
+
memberFiles: memberFileOperations(backend, options.built.plan, operations),
|
|
845
|
+
queryMetrics: options.metricQueries.model,
|
|
846
|
+
conversations,
|
|
847
|
+
decide: options.decide,
|
|
848
|
+
};
|
|
849
|
+
const post = handlePost(server, options.verifyToken, options.verifyRuntime, backend, options.workspaceId);
|
|
850
|
+
yield* router.add("POST", MCP_PATH, (request) => {
|
|
851
|
+
const response = post(request);
|
|
852
|
+
const served = Option.isSome(warehouseQueries)
|
|
853
|
+
? response.pipe(Effect.provideService(WarehouseQueries, warehouseQueries.value))
|
|
854
|
+
: response;
|
|
855
|
+
return served.pipe(Effect.map((response) => HttpServerResponse.setHeaders(response, CORS)));
|
|
856
|
+
});
|
|
857
|
+
yield* router.add("OPTIONS", MCP_PATH, PREFLIGHT);
|
|
858
|
+
yield* router.add("GET", MCP_PATH, (request) => handleGet(request, options, backend).pipe(Effect.map((response) => HttpServerResponse.setHeaders(response, CORS))));
|
|
859
|
+
yield* router.add("DELETE", MCP_PATH, METHOD_NOT_ALLOWED);
|
|
860
|
+
}));
|
|
861
|
+
}
|