@raybase/core 0.0.1 → 0.3.3
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 +21 -0
- package/dist/.admin-build/AdminApp.css +2 -0
- package/dist/.admin-build/AdminApp.js +77 -0
- package/dist/.admin-docs-build/AdminDocsApp.css +2 -0
- package/dist/.admin-docs-build/AdminDocsApp.js +45 -0
- package/dist/actions.d.ts +347 -0
- package/dist/actions.d.ts.map +1 -0
- package/dist/actions.js +661 -0
- package/dist/actions.js.map +1 -0
- package/dist/admin-assets/admin.7738f50720a8ed7b.css +2 -0
- package/dist/admin-assets/admin.8678cb61db34dc81.js +77 -0
- package/dist/admin-boot.d.ts +94 -0
- package/dist/admin-boot.d.ts.map +1 -0
- package/dist/admin-boot.js +139 -0
- package/dist/admin-boot.js.map +1 -0
- package/dist/admin-docs-content.d.ts +95 -0
- package/dist/admin-docs-content.d.ts.map +1 -0
- package/dist/admin-docs-content.js +463 -0
- package/dist/admin-docs-content.js.map +1 -0
- package/dist/admin-docs-ui.d.ts +11 -0
- package/dist/admin-docs-ui.d.ts.map +1 -0
- package/dist/admin-docs-ui.js +23 -0
- package/dist/admin-docs-ui.js.map +1 -0
- package/dist/admin-kit.d.ts +29 -0
- package/dist/admin-kit.js +1557 -0
- package/dist/admin-role.d.ts +8 -0
- package/dist/admin-role.d.ts.map +1 -0
- package/dist/admin-role.js +22 -0
- package/dist/admin-role.js.map +1 -0
- package/dist/admin-ui.d.ts +3 -0
- package/dist/admin-ui.d.ts.map +1 -0
- package/dist/admin-ui.js +16 -0
- package/dist/admin-ui.js.map +1 -0
- package/dist/agent-ai-chat.d.ts +127 -0
- package/dist/agent-ai-chat.d.ts.map +1 -0
- package/dist/agent-ai-chat.js +5975 -0
- package/dist/agent-ai-chat.js.map +1 -0
- package/dist/agent-chapter-archive.d.ts +214 -0
- package/dist/agent-chapter-archive.d.ts.map +1 -0
- package/dist/agent-chapter-archive.js +696 -0
- package/dist/agent-chapter-archive.js.map +1 -0
- package/dist/agent-chapter-search.d.ts +116 -0
- package/dist/agent-chapter-search.d.ts.map +1 -0
- package/dist/agent-chapter-search.js +605 -0
- package/dist/agent-chapter-search.js.map +1 -0
- package/dist/agent-chapter-tools.d.ts +45 -0
- package/dist/agent-chapter-tools.d.ts.map +1 -0
- package/dist/agent-chapter-tools.js +344 -0
- package/dist/agent-chapter-tools.js.map +1 -0
- package/dist/agent-chapters.d.ts +228 -0
- package/dist/agent-chapters.d.ts.map +1 -0
- package/dist/agent-chapters.js +737 -0
- package/dist/agent-chapters.js.map +1 -0
- package/dist/agent-data-parts.d.ts +167 -0
- package/dist/agent-data-parts.d.ts.map +1 -0
- package/dist/agent-data-parts.js +68 -0
- package/dist/agent-data-parts.js.map +1 -0
- package/dist/agent-errors.d.ts +41 -0
- package/dist/agent-errors.d.ts.map +1 -0
- package/dist/agent-errors.js +191 -0
- package/dist/agent-errors.js.map +1 -0
- package/dist/agent-follow-up-store.d.ts +273 -0
- package/dist/agent-follow-up-store.d.ts.map +1 -0
- package/dist/agent-follow-up-store.js +703 -0
- package/dist/agent-follow-up-store.js.map +1 -0
- package/dist/agent-follow-ups.d.ts +314 -0
- package/dist/agent-follow-ups.d.ts.map +1 -0
- package/dist/agent-follow-ups.js +840 -0
- package/dist/agent-follow-ups.js.map +1 -0
- package/dist/agent-memory.d.ts +323 -0
- package/dist/agent-memory.d.ts.map +1 -0
- package/dist/agent-memory.js +1270 -0
- package/dist/agent-memory.js.map +1 -0
- package/dist/agent-message-parts.d.ts +58 -0
- package/dist/agent-message-parts.d.ts.map +1 -0
- package/dist/agent-message-parts.js +163 -0
- package/dist/agent-message-parts.js.map +1 -0
- package/dist/agent-remote-mcp-credentials.d.ts +154 -0
- package/dist/agent-remote-mcp-credentials.d.ts.map +1 -0
- package/dist/agent-remote-mcp-credentials.js +287 -0
- package/dist/agent-remote-mcp-credentials.js.map +1 -0
- package/dist/agent-remote-mcp-crypto.d.ts +32 -0
- package/dist/agent-remote-mcp-crypto.d.ts.map +1 -0
- package/dist/agent-remote-mcp-crypto.js +101 -0
- package/dist/agent-remote-mcp-crypto.js.map +1 -0
- package/dist/agent-remote-mcp-install.d.ts +299 -0
- package/dist/agent-remote-mcp-install.d.ts.map +1 -0
- package/dist/agent-remote-mcp-install.js +785 -0
- package/dist/agent-remote-mcp-install.js.map +1 -0
- package/dist/agent-remote-mcp-oauth.d.ts +73 -0
- package/dist/agent-remote-mcp-oauth.d.ts.map +1 -0
- package/dist/agent-remote-mcp-oauth.js +344 -0
- package/dist/agent-remote-mcp-oauth.js.map +1 -0
- package/dist/agent-remote-mcp-transport.d.ts +46 -0
- package/dist/agent-remote-mcp-transport.d.ts.map +1 -0
- package/dist/agent-remote-mcp-transport.js +225 -0
- package/dist/agent-remote-mcp-transport.js.map +1 -0
- package/dist/agent-remote-mcp.d.ts +147 -0
- package/dist/agent-remote-mcp.d.ts.map +1 -0
- package/dist/agent-remote-mcp.js +681 -0
- package/dist/agent-remote-mcp.js.map +1 -0
- package/dist/agent-room-redaction.d.ts +66 -0
- package/dist/agent-room-redaction.d.ts.map +1 -0
- package/dist/agent-room-redaction.js +264 -0
- package/dist/agent-room-redaction.js.map +1 -0
- package/dist/agent-rooms.d.ts +209 -0
- package/dist/agent-rooms.d.ts.map +1 -0
- package/dist/agent-rooms.js +244 -0
- package/dist/agent-rooms.js.map +1 -0
- package/dist/agent-runtime.d.ts +541 -0
- package/dist/agent-runtime.d.ts.map +1 -0
- package/dist/agent-runtime.js +4260 -0
- package/dist/agent-runtime.js.map +1 -0
- package/dist/agent-skill-archive.d.ts +20 -0
- package/dist/agent-skill-archive.d.ts.map +1 -0
- package/dist/agent-skill-archive.js +316 -0
- package/dist/agent-skill-archive.js.map +1 -0
- package/dist/agent-skill-discovery.d.ts +96 -0
- package/dist/agent-skill-discovery.d.ts.map +1 -0
- package/dist/agent-skill-discovery.js +464 -0
- package/dist/agent-skill-discovery.js.map +1 -0
- package/dist/agent-skill-format.d.ts +57 -0
- package/dist/agent-skill-format.d.ts.map +1 -0
- package/dist/agent-skill-format.js +238 -0
- package/dist/agent-skill-format.js.map +1 -0
- package/dist/agent-skill-import.d.ts +64 -0
- package/dist/agent-skill-import.d.ts.map +1 -0
- package/dist/agent-skill-import.js +295 -0
- package/dist/agent-skill-import.js.map +1 -0
- package/dist/agent-skill-publish.d.ts +51 -0
- package/dist/agent-skill-publish.d.ts.map +1 -0
- package/dist/agent-skill-publish.js +136 -0
- package/dist/agent-skill-publish.js.map +1 -0
- package/dist/agent-skill-store.d.ts +252 -0
- package/dist/agent-skill-store.d.ts.map +1 -0
- package/dist/agent-skill-store.js +514 -0
- package/dist/agent-skill-store.js.map +1 -0
- package/dist/agent-skills.d.ts +85 -0
- package/dist/agent-skills.d.ts.map +1 -0
- package/dist/agent-skills.js +291 -0
- package/dist/agent-skills.js.map +1 -0
- package/dist/agent-transcript-pager.d.ts +77 -0
- package/dist/agent-transcript-pager.d.ts.map +1 -0
- package/dist/agent-transcript-pager.js +210 -0
- package/dist/agent-transcript-pager.js.map +1 -0
- package/dist/agent-user-data-parts.d.ts +20 -0
- package/dist/agent-user-data-parts.d.ts.map +1 -0
- package/dist/agent-user-data-parts.js +161 -0
- package/dist/agent-user-data-parts.js.map +1 -0
- package/dist/agent-user-transcript.d.ts +46 -0
- package/dist/agent-user-transcript.d.ts.map +1 -0
- package/dist/agent-user-transcript.js +106 -0
- package/dist/agent-user-transcript.js.map +1 -0
- package/dist/agent-voice-notes.d.ts +15 -0
- package/dist/agent-voice-notes.d.ts.map +1 -0
- package/dist/agent-voice-notes.js +17 -0
- package/dist/agent-voice-notes.js.map +1 -0
- package/dist/agent-wakes.d.ts +213 -0
- package/dist/agent-wakes.d.ts.map +1 -0
- package/dist/agent-wakes.js +534 -0
- package/dist/agent-wakes.js.map +1 -0
- package/dist/agent.d.ts +1780 -0
- package/dist/agent.d.ts.map +1 -0
- package/dist/agent.js +3123 -0
- package/dist/agent.js.map +1 -0
- package/dist/ai.d.ts +583 -0
- package/dist/ai.d.ts.map +1 -0
- package/dist/ai.js +1858 -0
- package/dist/ai.js.map +1 -0
- package/dist/app-manifest.d.ts +255 -0
- package/dist/app-manifest.d.ts.map +1 -0
- package/dist/app-manifest.js +372 -0
- package/dist/app-manifest.js.map +1 -0
- package/dist/app-users.d.ts +30 -0
- package/dist/app-users.d.ts.map +1 -0
- package/dist/app-users.js +91 -0
- package/dist/app-users.js.map +1 -0
- package/dist/audit-archive.d.ts +85 -0
- package/dist/audit-archive.d.ts.map +1 -0
- package/dist/audit-archive.js +178 -0
- package/dist/audit-archive.js.map +1 -0
- package/dist/audit.d.ts +54 -0
- package/dist/audit.d.ts.map +1 -0
- package/dist/audit.js +95 -0
- package/dist/audit.js.map +1 -0
- package/dist/auth-logger.d.ts +28 -0
- package/dist/auth-logger.d.ts.map +1 -0
- package/dist/auth-logger.js +53 -0
- package/dist/auth-logger.js.map +1 -0
- package/dist/auth-password-secret.d.ts +59 -0
- package/dist/auth-password-secret.d.ts.map +1 -0
- package/dist/auth-password-secret.js +409 -0
- package/dist/auth-password-secret.js.map +1 -0
- package/dist/auth-schema.d.ts +120 -0
- package/dist/auth-schema.d.ts.map +1 -0
- package/dist/auth-schema.js +285 -0
- package/dist/auth-schema.js.map +1 -0
- package/dist/auth.d.ts +321 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +1562 -0
- package/dist/auth.js.map +1 -0
- package/dist/backup-archive.d.ts +257 -0
- package/dist/backup-archive.d.ts.map +1 -0
- package/dist/backup-archive.js +2068 -0
- package/dist/backup-archive.js.map +1 -0
- package/dist/backup-crypto.d.ts +313 -0
- package/dist/backup-crypto.d.ts.map +1 -0
- package/dist/backup-crypto.js +693 -0
- package/dist/backup-crypto.js.map +1 -0
- package/dist/backup-format.d.ts +200 -0
- package/dist/backup-format.d.ts.map +1 -0
- package/dist/backup-format.js +1247 -0
- package/dist/backup-format.js.map +1 -0
- package/dist/cli.d.ts +9 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +15 -0
- package/dist/cli.js.map +1 -0
- package/dist/clock.d.ts +35 -0
- package/dist/clock.d.ts.map +1 -0
- package/dist/clock.js +83 -0
- package/dist/clock.js.map +1 -0
- package/dist/data-client.d.ts +187 -0
- package/dist/data-client.d.ts.map +1 -0
- package/dist/data-client.js +590 -0
- package/dist/data-client.js.map +1 -0
- package/dist/data-secrets.d.ts +164 -0
- package/dist/data-secrets.d.ts.map +1 -0
- package/dist/data-secrets.js +383 -0
- package/dist/data-secrets.js.map +1 -0
- package/dist/db.d.ts +453 -0
- package/dist/db.d.ts.map +1 -0
- package/dist/db.js +324 -0
- package/dist/db.js.map +1 -0
- package/dist/dev-activity.d.ts +48 -0
- package/dist/dev-activity.d.ts.map +1 -0
- package/dist/dev-activity.js +50 -0
- package/dist/dev-activity.js.map +1 -0
- package/dist/do.d.ts +429 -0
- package/dist/do.d.ts.map +1 -0
- package/dist/do.js +4526 -0
- package/dist/do.js.map +1 -0
- package/dist/email.d.ts +137 -0
- package/dist/email.d.ts.map +1 -0
- package/dist/email.js +527 -0
- package/dist/email.js.map +1 -0
- package/dist/generated/admin-assets.d.ts +16 -0
- package/dist/generated/admin-assets.d.ts.map +1 -0
- package/dist/generated/admin-assets.js +16 -0
- package/dist/generated/admin-assets.js.map +1 -0
- package/dist/generated/admin-docs-assets.d.ts +3 -0
- package/dist/generated/admin-docs-assets.d.ts.map +1 -0
- package/dist/generated/admin-docs-assets.js +3 -0
- package/dist/generated/admin-docs-assets.js.map +1 -0
- package/dist/handoff-consumer.d.ts +79 -0
- package/dist/handoff-consumer.d.ts.map +1 -0
- package/dist/handoff-consumer.js +300 -0
- package/dist/handoff-consumer.js.map +1 -0
- package/dist/handoff.d.ts +70 -0
- package/dist/handoff.d.ts.map +1 -0
- package/dist/handoff.js +228 -0
- package/dist/handoff.js.map +1 -0
- package/dist/host-auth.d.ts +47 -0
- package/dist/host-auth.d.ts.map +1 -0
- package/dist/host-auth.js +59 -0
- package/dist/host-auth.js.map +1 -0
- package/dist/host-user.d.ts +15 -0
- package/dist/host-user.d.ts.map +1 -0
- package/dist/host-user.js +23 -0
- package/dist/host-user.js.map +1 -0
- package/dist/html.d.ts +35 -0
- package/dist/html.d.ts.map +1 -0
- package/dist/html.js +634 -0
- package/dist/html.js.map +1 -0
- package/dist/http-error.d.ts +13 -0
- package/dist/http-error.d.ts.map +1 -0
- package/dist/http-error.js +15 -0
- package/dist/http-error.js.map +1 -0
- package/dist/imports.d.ts +219 -0
- package/dist/imports.d.ts.map +1 -0
- package/dist/imports.js +571 -0
- package/dist/imports.js.map +1 -0
- package/dist/index.d.ts +78 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +76 -0
- package/dist/index.js.map +1 -0
- package/dist/inference-tape.d.ts +64 -0
- package/dist/inference-tape.d.ts.map +1 -0
- package/dist/inference-tape.js +278 -0
- package/dist/inference-tape.js.map +1 -0
- package/dist/internal.d.ts +15 -0
- package/dist/internal.d.ts.map +1 -0
- package/dist/internal.js +48 -0
- package/dist/internal.js.map +1 -0
- package/dist/jobs.d.ts +796 -0
- package/dist/jobs.d.ts.map +1 -0
- package/dist/jobs.js +538 -0
- package/dist/jobs.js.map +1 -0
- package/dist/json-schema.d.ts +91 -0
- package/dist/json-schema.d.ts.map +1 -0
- package/dist/json-schema.js +884 -0
- package/dist/json-schema.js.map +1 -0
- package/dist/knowledge.d.ts +33 -0
- package/dist/knowledge.d.ts.map +1 -0
- package/dist/knowledge.js +348 -0
- package/dist/knowledge.js.map +1 -0
- package/dist/local-dev-chrome.d.ts +29 -0
- package/dist/local-dev-chrome.d.ts.map +1 -0
- package/dist/local-dev-chrome.js +514 -0
- package/dist/local-dev-chrome.js.map +1 -0
- package/dist/local-dev-personas.d.ts +15 -0
- package/dist/local-dev-personas.d.ts.map +1 -0
- package/dist/local-dev-personas.js +46 -0
- package/dist/local-dev-personas.js.map +1 -0
- package/dist/mcp.d.ts +567 -0
- package/dist/mcp.d.ts.map +1 -0
- package/dist/mcp.js +2480 -0
- package/dist/mcp.js.map +1 -0
- package/dist/modules.d.ts +145 -0
- package/dist/modules.d.ts.map +1 -0
- package/dist/modules.js +164 -0
- package/dist/modules.js.map +1 -0
- package/dist/notification-client.d.ts +94 -0
- package/dist/notification-client.d.ts.map +1 -0
- package/dist/notification-client.js +127 -0
- package/dist/notification-client.js.map +1 -0
- package/dist/notifications.d.ts +447 -0
- package/dist/notifications.d.ts.map +1 -0
- package/dist/notifications.js +1625 -0
- package/dist/notifications.js.map +1 -0
- package/dist/operator-data-query.d.ts +117 -0
- package/dist/operator-data-query.d.ts.map +1 -0
- package/dist/operator-data-query.js +431 -0
- package/dist/operator-data-query.js.map +1 -0
- package/dist/operator-mcp.d.ts +63 -0
- package/dist/operator-mcp.d.ts.map +1 -0
- package/dist/operator-mcp.js +2828 -0
- package/dist/operator-mcp.js.map +1 -0
- package/dist/outbound-https.d.ts +24 -0
- package/dist/outbound-https.d.ts.map +1 -0
- package/dist/outbound-https.js +172 -0
- package/dist/outbound-https.js.map +1 -0
- package/dist/pitr.d.ts +60 -0
- package/dist/pitr.d.ts.map +1 -0
- package/dist/pitr.js +94 -0
- package/dist/pitr.js.map +1 -0
- package/dist/platform.d.ts +199 -0
- package/dist/platform.d.ts.map +1 -0
- package/dist/platform.js +196 -0
- package/dist/platform.js.map +1 -0
- package/dist/policies.d.ts +115 -0
- package/dist/policies.d.ts.map +1 -0
- package/dist/policies.js +158 -0
- package/dist/policies.js.map +1 -0
- package/dist/principal.d.ts +143 -0
- package/dist/principal.d.ts.map +1 -0
- package/dist/principal.js +174 -0
- package/dist/principal.js.map +1 -0
- package/dist/realtime.d.ts +280 -0
- package/dist/realtime.d.ts.map +1 -0
- package/dist/realtime.js +578 -0
- package/dist/realtime.js.map +1 -0
- package/dist/retrieval.d.ts +263 -0
- package/dist/retrieval.d.ts.map +1 -0
- package/dist/retrieval.js +602 -0
- package/dist/retrieval.js.map +1 -0
- package/dist/runtime-error-client.d.ts +10 -0
- package/dist/runtime-error-client.d.ts.map +1 -0
- package/dist/runtime-error-client.js +70 -0
- package/dist/runtime-error-client.js.map +1 -0
- package/dist/runtime-errors.d.ts +180 -0
- package/dist/runtime-errors.d.ts.map +1 -0
- package/dist/runtime-errors.js +572 -0
- package/dist/runtime-errors.js.map +1 -0
- package/dist/schema.d.ts +434 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +232 -0
- package/dist/schema.js.map +1 -0
- package/dist/search.d.ts +216 -0
- package/dist/search.d.ts.map +1 -0
- package/dist/search.js +785 -0
- package/dist/search.js.map +1 -0
- package/dist/settings.d.ts +191 -0
- package/dist/settings.d.ts.map +1 -0
- package/dist/settings.js +478 -0
- package/dist/settings.js.map +1 -0
- package/dist/sql.d.ts +7 -0
- package/dist/sql.d.ts.map +1 -0
- package/dist/sql.js +30 -0
- package/dist/sql.js.map +1 -0
- package/dist/storage-client.d.ts +69 -0
- package/dist/storage-client.d.ts.map +1 -0
- package/dist/storage-client.js +469 -0
- package/dist/storage-client.js.map +1 -0
- package/dist/storage.d.ts +21 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/storage.js +82 -0
- package/dist/storage.js.map +1 -0
- package/dist/subject-domains.d.ts +15 -0
- package/dist/subject-domains.d.ts.map +1 -0
- package/dist/subject-domains.js +38 -0
- package/dist/subject-domains.js.map +1 -0
- package/dist/support.d.ts +132 -0
- package/dist/support.d.ts.map +1 -0
- package/dist/support.js +483 -0
- package/dist/support.js.map +1 -0
- package/dist/system-tables.d.ts +12 -0
- package/dist/system-tables.d.ts.map +1 -0
- package/dist/system-tables.js +225 -0
- package/dist/system-tables.js.map +1 -0
- package/dist/test.d.ts +57 -0
- package/dist/test.d.ts.map +1 -0
- package/dist/test.js +454 -0
- package/dist/test.js.map +1 -0
- package/dist/vite.d.ts +82 -0
- package/dist/vite.d.ts.map +1 -0
- package/dist/vite.js +82 -0
- package/dist/vite.js.map +1 -0
- package/dist/webpush-crypto.d.ts +39 -0
- package/dist/webpush-crypto.d.ts.map +1 -0
- package/dist/webpush-crypto.js +213 -0
- package/dist/webpush-crypto.js.map +1 -0
- package/dist/webpush-private.d.ts +11 -0
- package/dist/webpush-private.d.ts.map +1 -0
- package/dist/webpush-private.js +11 -0
- package/dist/webpush-private.js.map +1 -0
- package/dist/webpush.d.ts +86 -0
- package/dist/webpush.d.ts.map +1 -0
- package/dist/webpush.js +375 -0
- package/dist/webpush.js.map +1 -0
- package/dist/worker.d.ts +185 -0
- package/dist/worker.d.ts.map +1 -0
- package/dist/worker.js +13021 -0
- package/dist/worker.js.map +1 -0
- package/package.json +161 -8
- package/README.md +0 -3
|
@@ -0,0 +1,785 @@
|
|
|
1
|
+
import { agentRemoteMcpRuntimeTools, agentRemoteMcpToolBindings, remoteMcpModelToolName, remoteMcpSecretNamePattern, validateRemoteMcpEndpoint, validateRemoteMcpToolName } from "./agent-remote-mcp.js";
|
|
2
|
+
import { RemoteMcpCredentialStore, remoteMcpKeyFromEnv } from "./agent-remote-mcp-credentials.js";
|
|
3
|
+
import { remoteMcpRequest } from "./agent-remote-mcp-transport.js";
|
|
4
|
+
import { sha256Hex, stableJson } from "./internal.js";
|
|
5
|
+
import { role } from "./policies.js";
|
|
6
|
+
import { nowIso, q, randomId } from "./sql.js";
|
|
7
|
+
export const raybaseRemoteMcpInstallLimits = {
|
|
8
|
+
/** Descriptors Core will pin from one `tools/list`. */
|
|
9
|
+
maxTools: 64,
|
|
10
|
+
/** Bytes Core will read from one `tools/list` response. */
|
|
11
|
+
maxDiscoveryBytes: 256_000,
|
|
12
|
+
maxDescriptionChars: 4_000,
|
|
13
|
+
maxInputSchemaBytes: 32_000,
|
|
14
|
+
discoveryTimeoutMs: 10_000
|
|
15
|
+
};
|
|
16
|
+
const defaultRemoteMcpToolPolicy = { kind: "role", role: "user" };
|
|
17
|
+
/* -------------------------------------------------------------------------- */
|
|
18
|
+
/* Discovery */
|
|
19
|
+
/* -------------------------------------------------------------------------- */
|
|
20
|
+
/**
|
|
21
|
+
* Reads a server's `tools/list` once over stateless Streamable HTTP. This is
|
|
22
|
+
* the only moment Core talks to an installed server outside a tool call, and
|
|
23
|
+
* it only ever happens on an operator's install or rediscover.
|
|
24
|
+
*/
|
|
25
|
+
export async function discoverRemoteMcpTools(input) {
|
|
26
|
+
validateRemoteMcpEndpoint(input.endpoint);
|
|
27
|
+
const credential = input.credential ?? { type: "none" };
|
|
28
|
+
validateInstalledCredential(credential);
|
|
29
|
+
try {
|
|
30
|
+
const store = credential.type === "stored"
|
|
31
|
+
? new RemoteMcpCredentialStore(requiredControl(input.control), remoteMcpKeyFromEnv(input.env))
|
|
32
|
+
: null;
|
|
33
|
+
const payload = await remoteMcpRequest({
|
|
34
|
+
serverName: input.serverName ?? "discovery",
|
|
35
|
+
endpoint: input.endpoint,
|
|
36
|
+
transport: input.transport,
|
|
37
|
+
method: "tools/list",
|
|
38
|
+
params: {},
|
|
39
|
+
authorization: async () => {
|
|
40
|
+
if (store) {
|
|
41
|
+
const resolved = await store.resolveAuthorization(input.serverName ?? "");
|
|
42
|
+
return { header: resolved.header.reveal(), generation: resolved.generation };
|
|
43
|
+
}
|
|
44
|
+
return { header: installedCredentialHeader(credential, input.env), generation: 0 };
|
|
45
|
+
},
|
|
46
|
+
coordinator: input.coordinator,
|
|
47
|
+
fetcher: input.fetcher,
|
|
48
|
+
timeoutMs: input.timeoutMs ?? raybaseRemoteMcpInstallLimits.discoveryTimeoutMs,
|
|
49
|
+
maxBytes: raybaseRemoteMcpInstallLimits.maxDiscoveryBytes
|
|
50
|
+
});
|
|
51
|
+
const listed = payload?.tools;
|
|
52
|
+
if (!Array.isArray(listed))
|
|
53
|
+
throw new Error("Remote MCP tools/list did not return a tools array");
|
|
54
|
+
if (listed.length > raybaseRemoteMcpInstallLimits.maxTools) {
|
|
55
|
+
throw new Error(`Remote MCP server listed ${listed.length} tools; Core pins at most ${raybaseRemoteMcpInstallLimits.maxTools}`);
|
|
56
|
+
}
|
|
57
|
+
const tools = {};
|
|
58
|
+
const rejected = [];
|
|
59
|
+
for (const entry of listed) {
|
|
60
|
+
const record = entry && typeof entry === "object" && !Array.isArray(entry) ? entry : null;
|
|
61
|
+
const name = record && typeof record.name === "string" ? record.name : "";
|
|
62
|
+
try {
|
|
63
|
+
if (!record)
|
|
64
|
+
throw new Error("descriptor must be an object");
|
|
65
|
+
validateRemoteMcpToolName(name, "(discovered)");
|
|
66
|
+
if (tools[name])
|
|
67
|
+
throw new Error("duplicate tool name");
|
|
68
|
+
tools[name] = pinnedToolFrom(record);
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
rejected.push({ name: name || "(unnamed)", reason: error instanceof Error ? error.message : String(error) });
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (!Object.keys(tools).length)
|
|
75
|
+
throw new Error("Remote MCP server listed no tool Core is willing to pin");
|
|
76
|
+
return {
|
|
77
|
+
endpointHost: endpointHost(input.endpoint),
|
|
78
|
+
tools,
|
|
79
|
+
hash: await pinnedToolsHash(tools),
|
|
80
|
+
discoveredAt: nowIso(),
|
|
81
|
+
rejected
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
throw error instanceof Error ? error : new Error(String(error));
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
function pinnedToolFrom(record) {
|
|
89
|
+
const description = typeof record.description === "string" ? record.description.trim() : "";
|
|
90
|
+
if (!description)
|
|
91
|
+
throw new Error("description is required");
|
|
92
|
+
if (description.length > raybaseRemoteMcpInstallLimits.maxDescriptionChars)
|
|
93
|
+
throw new Error("description is too long");
|
|
94
|
+
const schema = record.inputSchema;
|
|
95
|
+
if (!schema || typeof schema !== "object" || Array.isArray(schema))
|
|
96
|
+
throw new Error("inputSchema must be a JSON-schema object");
|
|
97
|
+
const schemaRecord = schema;
|
|
98
|
+
if (schemaRecord.type !== undefined && schemaRecord.type !== "object")
|
|
99
|
+
throw new Error("inputSchema must describe an object");
|
|
100
|
+
if (JSON.stringify(schemaRecord).length > raybaseRemoteMcpInstallLimits.maxInputSchemaBytes)
|
|
101
|
+
throw new Error("inputSchema is too large");
|
|
102
|
+
const annotations = record.annotations && typeof record.annotations === "object" && !Array.isArray(record.annotations)
|
|
103
|
+
? record.annotations
|
|
104
|
+
: undefined;
|
|
105
|
+
const hints = annotations
|
|
106
|
+
? {
|
|
107
|
+
...(typeof annotations.readOnlyHint === "boolean" ? { readOnlyHint: annotations.readOnlyHint } : {}),
|
|
108
|
+
...(typeof annotations.destructiveHint === "boolean" ? { destructiveHint: annotations.destructiveHint } : {}),
|
|
109
|
+
...(typeof annotations.idempotentHint === "boolean" ? { idempotentHint: annotations.idempotentHint } : {})
|
|
110
|
+
}
|
|
111
|
+
: undefined;
|
|
112
|
+
return {
|
|
113
|
+
description,
|
|
114
|
+
inputSchema: { ...schemaRecord, type: "object" },
|
|
115
|
+
...(hints && Object.keys(hints).length ? { annotations: hints } : {})
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
export async function pinnedToolsHash(tools) {
|
|
119
|
+
return sha256Hex(stableJson(tools));
|
|
120
|
+
}
|
|
121
|
+
export function remoteMcpDiscoveryDiff(current, next) {
|
|
122
|
+
const added = Object.keys(next).filter((name) => !current[name]).sort();
|
|
123
|
+
const removed = Object.keys(current).filter((name) => !next[name]).sort();
|
|
124
|
+
const shared = Object.keys(next).filter((name) => current[name]);
|
|
125
|
+
const changed = shared.filter((name) => stableJson(current[name]) !== stableJson(next[name])).sort();
|
|
126
|
+
const unchanged = shared.filter((name) => !changed.includes(name)).sort();
|
|
127
|
+
return { added, removed, changed, unchanged };
|
|
128
|
+
}
|
|
129
|
+
/* -------------------------------------------------------------------------- */
|
|
130
|
+
/* Store */
|
|
131
|
+
/* -------------------------------------------------------------------------- */
|
|
132
|
+
export async function listInstalledRemoteMcpServerRows(control) {
|
|
133
|
+
return control.all(`SELECT * FROM ${q("_agent_remote_mcp_servers")} ORDER BY "name"`);
|
|
134
|
+
}
|
|
135
|
+
export async function findInstalledRemoteMcpServerRow(control, name) {
|
|
136
|
+
return control.first(`SELECT * FROM ${q("_agent_remote_mcp_servers")} WHERE "name"=?`, [name]);
|
|
137
|
+
}
|
|
138
|
+
export async function listAgentRemoteMcpBindingRows(control, agentName) {
|
|
139
|
+
return agentName
|
|
140
|
+
? control.all(`SELECT * FROM ${q("_agent_remote_mcp_bindings")} WHERE "agentName"=? ORDER BY "serverName"`, [agentName])
|
|
141
|
+
: control.all(`SELECT * FROM ${q("_agent_remote_mcp_bindings")} ORDER BY "agentName","serverName"`);
|
|
142
|
+
}
|
|
143
|
+
export async function installRemoteMcpServer(input) {
|
|
144
|
+
const name = input.name;
|
|
145
|
+
validateRemoteMcpEndpoint(input.endpoint);
|
|
146
|
+
const credential = input.credential ?? { type: "none" };
|
|
147
|
+
validateInstalledCredential(credential);
|
|
148
|
+
if ((input.reservedServerNames ?? []).includes(name)) {
|
|
149
|
+
throw new Error(`Remote MCP server ${name} is already declared in code; installed and code-declared servers share one namespace`);
|
|
150
|
+
}
|
|
151
|
+
if (await findInstalledRemoteMcpServerRow(input.control, name)) {
|
|
152
|
+
throw new Error(`Remote MCP server ${name} is already installed; rediscover or disable it instead`);
|
|
153
|
+
}
|
|
154
|
+
const discovery = input.oauthDiscovery || input.deferDiscovery ? null : await discoverRemoteMcpTools({
|
|
155
|
+
serverName: name, endpoint: input.endpoint, credential, control: input.control,
|
|
156
|
+
transport: input.transport, env: input.env, fetcher: input.fetcher
|
|
157
|
+
});
|
|
158
|
+
const access = resolveToolAccess(discovery?.tools ?? {}, input.access);
|
|
159
|
+
const now = input.now ?? nowIso();
|
|
160
|
+
const row = {
|
|
161
|
+
id: randomId("remote_mcp_server"),
|
|
162
|
+
name,
|
|
163
|
+
endpoint: input.endpoint,
|
|
164
|
+
credentialRefJson: JSON.stringify(credential),
|
|
165
|
+
transport: input.transport ?? "stateless",
|
|
166
|
+
discoveryState: input.oauthDiscovery || input.deferDiscovery ? "awaiting_credential" : "pinned",
|
|
167
|
+
oauthMetadataJson: input.oauthDiscovery ? JSON.stringify(input.oauthDiscovery) : null,
|
|
168
|
+
serverRevision: 1,
|
|
169
|
+
toolsJson: JSON.stringify(discovery?.tools ?? {}),
|
|
170
|
+
policyJson: JSON.stringify(access),
|
|
171
|
+
discoveredAt: discovery?.discoveredAt ?? now,
|
|
172
|
+
discoveryHash: discovery?.hash ?? await pinnedToolsHash({}),
|
|
173
|
+
status: "installed",
|
|
174
|
+
createdBy: input.actor,
|
|
175
|
+
createdAt: now,
|
|
176
|
+
updatedAt: now
|
|
177
|
+
};
|
|
178
|
+
// Validate through the same definition path a code-declared server takes,
|
|
179
|
+
// so an installed row can never produce a shape v1 would have refused.
|
|
180
|
+
const server = installedRemoteMcpServerFor(row);
|
|
181
|
+
await input.control.run(`INSERT INTO ${q("_agent_remote_mcp_servers")} ("id","name","endpoint","credentialRefJson","transport","discoveryState","oauthMetadataJson","serverRevision","toolsJson","policyJson","discoveredAt","discoveryHash","status","createdBy","createdAt","updatedAt") VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`, [row.id, row.name, row.endpoint, row.credentialRefJson, row.transport, row.discoveryState, row.oauthMetadataJson, row.serverRevision, row.toolsJson, row.policyJson, row.discoveredAt, row.discoveryHash, row.status, row.createdBy, row.createdAt, row.updatedAt]);
|
|
182
|
+
return {
|
|
183
|
+
row,
|
|
184
|
+
server: remoteMcpServerView(row, server),
|
|
185
|
+
discovery: discovery ?? { endpointHost: endpointHost(input.endpoint), tools: {}, hash: row.discoveryHash, discoveredAt: now, rejected: [] },
|
|
186
|
+
secretName: credential.type === "secret" ? credential.name : null
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
export async function rediscoverRemoteMcpServer(input) {
|
|
190
|
+
const row = await requireServerRow(input.control, input.name);
|
|
191
|
+
const credential = parseCredentialRef(row.credentialRefJson);
|
|
192
|
+
const discovery = await discoverRemoteMcpTools({
|
|
193
|
+
serverName: row.name, endpoint: row.endpoint, credential, control: input.control,
|
|
194
|
+
transport: row.transport === "sessioned" ? "sessioned" : "stateless", env: input.env, fetcher: input.fetcher
|
|
195
|
+
});
|
|
196
|
+
const current = parsePinnedTools(row.toolsJson);
|
|
197
|
+
const diff = remoteMcpDiscoveryDiff(current, discovery.tools);
|
|
198
|
+
if (!input.confirm)
|
|
199
|
+
return { diff, applied: false, discovery, disabledBindings: [] };
|
|
200
|
+
// The operator's access rules survive rediscovery for every tool that
|
|
201
|
+
// survived it; a new tool starts at the closed default.
|
|
202
|
+
const previousAccess = parseToolAccess(row.policyJson);
|
|
203
|
+
const access = resolveToolAccess(discovery.tools, Object.fromEntries(Object.entries(previousAccess).filter(([toolName]) => discovery.tools[toolName])));
|
|
204
|
+
const now = input.now ?? nowIso();
|
|
205
|
+
const next = {
|
|
206
|
+
...row,
|
|
207
|
+
toolsJson: JSON.stringify(discovery.tools),
|
|
208
|
+
policyJson: JSON.stringify(access),
|
|
209
|
+
discoveredAt: discovery.discoveredAt,
|
|
210
|
+
discoveryHash: discovery.hash,
|
|
211
|
+
discoveryState: "pinned",
|
|
212
|
+
serverRevision: Number(row.serverRevision || 0) + 1,
|
|
213
|
+
updatedAt: now
|
|
214
|
+
};
|
|
215
|
+
const server = installedRemoteMcpServerFor(next);
|
|
216
|
+
await input.control.run(`UPDATE ${q("_agent_remote_mcp_servers")} SET "toolsJson"=?,"policyJson"=?,"discoveredAt"=?,"discoveryHash"=?,"discoveryState"='pinned',"serverRevision"=?,"updatedAt"=? WHERE "id"=?`, [next.toolsJson, next.policyJson, next.discoveredAt, next.discoveryHash, next.serverRevision, next.updatedAt, next.id]);
|
|
217
|
+
const disabledBindings = [];
|
|
218
|
+
if (diff.removed.length) {
|
|
219
|
+
for (const binding of await listAgentRemoteMcpBindingRows(input.control)) {
|
|
220
|
+
if (binding.serverName !== row.name || binding.status !== "active")
|
|
221
|
+
continue;
|
|
222
|
+
const bound = parseToolNames(binding.toolNamesJson);
|
|
223
|
+
const missing = bound.filter((toolName) => diff.removed.includes(toolName));
|
|
224
|
+
if (!missing.length)
|
|
225
|
+
continue;
|
|
226
|
+
const reason = `Pinned tool(s) ${missing.join(", ")} disappeared at rediscovery on ${now}`;
|
|
227
|
+
await input.control.run(`UPDATE ${q("_agent_remote_mcp_bindings")} SET "status"='disabled',"disabledReason"=? WHERE "id"=?`, [reason, binding.id]);
|
|
228
|
+
disabledBindings.push({ agentName: binding.agentName, serverName: binding.serverName, reason });
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return { diff, applied: true, discovery, disabledBindings, server: remoteMcpServerView(next, server) };
|
|
232
|
+
}
|
|
233
|
+
export async function setInstalledRemoteMcpServerStatus(input) {
|
|
234
|
+
const row = await requireServerRow(input.control, input.name);
|
|
235
|
+
const now = input.now ?? nowIso();
|
|
236
|
+
await input.control.run(`UPDATE ${q("_agent_remote_mcp_servers")} SET "status"=?,"updatedAt"=? WHERE "id"=?`, [input.status, now, row.id]);
|
|
237
|
+
return { ...row, status: input.status, updatedAt: now };
|
|
238
|
+
}
|
|
239
|
+
export async function bindAgentRemoteMcpServer(input) {
|
|
240
|
+
const row = await requireServerRow(input.control, input.serverName);
|
|
241
|
+
if (row.status !== "installed")
|
|
242
|
+
throw new Error(`Remote MCP server ${input.serverName} is disabled`);
|
|
243
|
+
if ((row.discoveryState ?? "pinned") !== "pinned")
|
|
244
|
+
throw new Error(`Remote MCP server ${input.serverName} must finish credential setup and tool discovery before binding`);
|
|
245
|
+
const pinned = parsePinnedTools(row.toolsJson);
|
|
246
|
+
const toolNames = [...new Set(input.toolNames.map((value) => value.trim()).filter(Boolean))];
|
|
247
|
+
if (!toolNames.length)
|
|
248
|
+
throw new Error("A remote MCP binding must allow at least one tool");
|
|
249
|
+
for (const toolName of toolNames) {
|
|
250
|
+
if (!pinned[toolName])
|
|
251
|
+
throw new Error(`Remote MCP server ${input.serverName} has no pinned tool ${toolName}`);
|
|
252
|
+
}
|
|
253
|
+
const reserved = new Set(input.reservedModelToolNames ?? []);
|
|
254
|
+
for (const toolName of toolNames) {
|
|
255
|
+
const modelToolName = remoteMcpModelToolName(input.serverName, toolName);
|
|
256
|
+
if (reserved.has(modelToolName)) {
|
|
257
|
+
throw new Error(`Agent ${input.agentName} already offers runtime tool "${modelToolName}"; refusing a duplicate remote MCP binding`);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
const now = input.now ?? nowIso();
|
|
261
|
+
const existing = await input.control.first(`SELECT * FROM ${q("_agent_remote_mcp_bindings")} WHERE "agentName"=? AND "serverName"=?`, [input.agentName, input.serverName]);
|
|
262
|
+
if (existing) {
|
|
263
|
+
await input.control.run(`UPDATE ${q("_agent_remote_mcp_bindings")} SET "toolNamesJson"=?,"status"='active',"disabledReason"=NULL WHERE "id"=?`, [JSON.stringify(toolNames), existing.id]);
|
|
264
|
+
return { ...existing, toolNamesJson: JSON.stringify(toolNames), status: "active", disabledReason: null };
|
|
265
|
+
}
|
|
266
|
+
const binding = {
|
|
267
|
+
id: randomId("remote_mcp_binding"),
|
|
268
|
+
agentName: input.agentName,
|
|
269
|
+
serverName: input.serverName,
|
|
270
|
+
toolNamesJson: JSON.stringify(toolNames),
|
|
271
|
+
status: "active",
|
|
272
|
+
disabledReason: null,
|
|
273
|
+
createdBy: input.actor,
|
|
274
|
+
createdAt: now
|
|
275
|
+
};
|
|
276
|
+
await input.control.run(`INSERT INTO ${q("_agent_remote_mcp_bindings")} ("id","agentName","serverName","toolNamesJson","status","disabledReason","createdBy","createdAt") VALUES (?,?,?,?,?,?,?,?)`, [binding.id, binding.agentName, binding.serverName, binding.toolNamesJson, binding.status, binding.disabledReason, binding.createdBy, binding.createdAt]);
|
|
277
|
+
return binding;
|
|
278
|
+
}
|
|
279
|
+
export async function unbindAgentRemoteMcpServer(input) {
|
|
280
|
+
const existing = await input.control.first(`SELECT * FROM ${q("_agent_remote_mcp_bindings")} WHERE "agentName"=? AND "serverName"=?`, [input.agentName, input.serverName]);
|
|
281
|
+
if (!existing)
|
|
282
|
+
return false;
|
|
283
|
+
await input.control.run(`DELETE FROM ${q("_agent_remote_mcp_bindings")} WHERE "id"=?`, [existing.id]);
|
|
284
|
+
return true;
|
|
285
|
+
}
|
|
286
|
+
/* -------------------------------------------------------------------------- */
|
|
287
|
+
/* Projection */
|
|
288
|
+
/* -------------------------------------------------------------------------- */
|
|
289
|
+
/**
|
|
290
|
+
* The `RaybaseRemoteMcpServer` an installed row stands for — exactly the shape
|
|
291
|
+
* a code-declared server has, so `callRemoteMcpTool` and the event recorder
|
|
292
|
+
* are unchanged.
|
|
293
|
+
*/
|
|
294
|
+
export function installedRemoteMcpServerFor(row) {
|
|
295
|
+
const pinned = parsePinnedTools(row.toolsJson);
|
|
296
|
+
const access = parseToolAccess(row.policyJson);
|
|
297
|
+
const credential = parseCredentialRef(row.credentialRefJson);
|
|
298
|
+
const tools = {};
|
|
299
|
+
for (const [toolName, descriptor] of Object.entries(pinned)) {
|
|
300
|
+
const rule = access[toolName];
|
|
301
|
+
tools[toolName] = {
|
|
302
|
+
description: descriptor.description,
|
|
303
|
+
inputSchema: descriptor.inputSchema,
|
|
304
|
+
...(descriptor.annotations ? { annotations: descriptor.annotations } : {}),
|
|
305
|
+
...(descriptor.timeoutMs !== undefined ? { timeoutMs: descriptor.timeoutMs } : {}),
|
|
306
|
+
...(descriptor.maxResultBytes !== undefined ? { maxResultBytes: descriptor.maxResultBytes } : {}),
|
|
307
|
+
...(descriptor.redactionKeys ? { redactionKeys: descriptor.redactionKeys } : {}),
|
|
308
|
+
policy: parsePolicyExpression(rule?.policy ?? defaultRemoteMcpToolPolicy),
|
|
309
|
+
audience: rule?.audience ?? "signed-in"
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
validateRemoteMcpEndpoint(row.endpoint);
|
|
313
|
+
validateInstalledCredential(credential);
|
|
314
|
+
return {
|
|
315
|
+
name: row.name,
|
|
316
|
+
endpoint: row.endpoint,
|
|
317
|
+
transport: row.transport === "sessioned" ? "sessioned" : "stateless",
|
|
318
|
+
...(credential.type === "none" ? {} : credential.type === "stored"
|
|
319
|
+
? { credentials: { type: "stored", serverName: row.name } }
|
|
320
|
+
: { credentials: credential }),
|
|
321
|
+
tools
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
export function remoteMcpServerView(row, server = installedRemoteMcpServerFor(row), credentialStatus = null) {
|
|
325
|
+
const access = parseToolAccess(row.policyJson);
|
|
326
|
+
const credential = parseCredentialRef(row.credentialRefJson);
|
|
327
|
+
return {
|
|
328
|
+
name: row.name,
|
|
329
|
+
endpoint: row.endpoint,
|
|
330
|
+
endpointHost: endpointHost(row.endpoint),
|
|
331
|
+
status: row.status === "disabled" ? "disabled" : "installed",
|
|
332
|
+
credential,
|
|
333
|
+
secretName: credential.type === "secret" ? credential.name : null,
|
|
334
|
+
transport: row.transport === "sessioned" ? "sessioned" : "stateless",
|
|
335
|
+
discoveryState: row.discoveryState === "awaiting_credential" ? "awaiting_credential" : row.discoveryState === "rediscovery_required" ? "rediscovery_required" : "pinned",
|
|
336
|
+
credentialStatus,
|
|
337
|
+
oauth: parseOAuthMetadata(row.oauthMetadataJson),
|
|
338
|
+
tools: Object.entries(server.tools).map(([toolName, descriptor]) => ({
|
|
339
|
+
name: toolName,
|
|
340
|
+
modelToolName: remoteMcpModelToolName(row.name, toolName),
|
|
341
|
+
description: descriptor.description,
|
|
342
|
+
policy: access[toolName]?.policy ?? defaultRemoteMcpToolPolicy,
|
|
343
|
+
audience: descriptor.audience ?? "signed-in",
|
|
344
|
+
...(descriptor.annotations ? { annotations: descriptor.annotations } : {}),
|
|
345
|
+
...(descriptor.timeoutMs !== undefined ? { timeoutMs: descriptor.timeoutMs } : {}),
|
|
346
|
+
...(descriptor.maxResultBytes !== undefined ? { maxResultBytes: descriptor.maxResultBytes } : {})
|
|
347
|
+
})).sort((left, right) => left.name.localeCompare(right.name)),
|
|
348
|
+
discoveredAt: row.discoveredAt,
|
|
349
|
+
discoveryHash: row.discoveryHash,
|
|
350
|
+
createdBy: row.createdBy,
|
|
351
|
+
createdAt: row.createdAt,
|
|
352
|
+
updatedAt: row.updatedAt
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
export function remoteMcpBindingView(row) {
|
|
356
|
+
const toolNames = parseToolNames(row.toolNamesJson);
|
|
357
|
+
return {
|
|
358
|
+
agentName: row.agentName,
|
|
359
|
+
serverName: row.serverName,
|
|
360
|
+
toolNames,
|
|
361
|
+
modelToolNames: toolNames.map((toolName) => remoteMcpModelToolName(row.serverName, toolName)),
|
|
362
|
+
status: row.status === "disabled" ? "disabled" : "active",
|
|
363
|
+
disabledReason: row.disabledReason ?? null,
|
|
364
|
+
createdBy: row.createdBy,
|
|
365
|
+
createdAt: row.createdAt
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
/** Installed servers and bindings, projected for admin/doctor/operator surfaces. */
|
|
369
|
+
export async function installedRemoteMcpOverview(control) {
|
|
370
|
+
const rows = await listInstalledRemoteMcpServerRows(control);
|
|
371
|
+
let statuses = new Map();
|
|
372
|
+
try {
|
|
373
|
+
// Status reads are safe by construction and do not decrypt envelopes.
|
|
374
|
+
const raw = await control.all(`SELECT "serverName","kind","state","scheme","revision","generation","expiresAt","scopesJson","subject","createdBy","createdAt","updatedAt" FROM ${q("_agent_remote_mcp_credentials")}`);
|
|
375
|
+
statuses = new Map(raw.map((row) => [String(row.serverName), {
|
|
376
|
+
serverName: String(row.serverName), kind: row.kind === "oauth" ? "oauth" : "static",
|
|
377
|
+
state: row.state === "reauth_required" ? "reauth_required" : row.state === "cleared" ? "cleared" : "ready",
|
|
378
|
+
scheme: row.scheme === "raw" ? "raw" : "bearer", revision: Number(row.revision), generation: Number(row.generation),
|
|
379
|
+
expiresAt: typeof row.expiresAt === "string" ? row.expiresAt : null,
|
|
380
|
+
scopes: parseStringArray(typeof row.scopesJson === "string" ? row.scopesJson : null),
|
|
381
|
+
subject: typeof row.subject === "string" ? row.subject : null, createdBy: String(row.createdBy), createdAt: String(row.createdAt), updatedAt: String(row.updatedAt)
|
|
382
|
+
}]));
|
|
383
|
+
}
|
|
384
|
+
catch { /* tables may not have reconciled yet */ }
|
|
385
|
+
const servers = [];
|
|
386
|
+
for (const row of rows) {
|
|
387
|
+
let view;
|
|
388
|
+
try {
|
|
389
|
+
view = remoteMcpServerView(row, undefined, statuses.get(row.name) ?? null);
|
|
390
|
+
}
|
|
391
|
+
catch (error) {
|
|
392
|
+
throw new Error(`Installed remote MCP server ${row.name} is invalid: ${error instanceof Error ? error.message : String(error)}`);
|
|
393
|
+
}
|
|
394
|
+
view.toolsHash = await pinnedToolsHash(parsePinnedTools(row.toolsJson));
|
|
395
|
+
servers.push(view);
|
|
396
|
+
}
|
|
397
|
+
const bindings = (await listAgentRemoteMcpBindingRows(control)).map(remoteMcpBindingView);
|
|
398
|
+
return { servers, bindings };
|
|
399
|
+
}
|
|
400
|
+
/** Summaries in the same shape the code-declared surfaces use, for admin/doctor. */
|
|
401
|
+
export async function installedRemoteMcpServerSummaries(control) {
|
|
402
|
+
const { servers } = await installedRemoteMcpOverview(control);
|
|
403
|
+
return servers.map((server) => ({
|
|
404
|
+
name: server.name,
|
|
405
|
+
endpointHost: server.endpointHost,
|
|
406
|
+
toolNames: server.tools.map((tool) => tool.name).sort((left, right) => left.localeCompare(right)),
|
|
407
|
+
credentialRefs: server.secretName ? [server.secretName] : []
|
|
408
|
+
}));
|
|
409
|
+
}
|
|
410
|
+
export async function installedAgentRemoteMcpBindingSummaries(control) {
|
|
411
|
+
return (await listAgentRemoteMcpBindingRows(control))
|
|
412
|
+
.filter((row) => row.status !== "disabled")
|
|
413
|
+
.map((row) => {
|
|
414
|
+
const view = remoteMcpBindingView(row);
|
|
415
|
+
return {
|
|
416
|
+
agentName: view.agentName,
|
|
417
|
+
serverName: view.serverName,
|
|
418
|
+
toolNames: [...view.toolNames].sort((left, right) => left.localeCompare(right)),
|
|
419
|
+
modelToolNames: [...view.modelToolNames].sort((left, right) => left.localeCompare(right))
|
|
420
|
+
};
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
/* -------------------------------------------------------------------------- */
|
|
424
|
+
/* Runtime resolution */
|
|
425
|
+
/* -------------------------------------------------------------------------- */
|
|
426
|
+
const installedBindingCache = new WeakMap();
|
|
427
|
+
/**
|
|
428
|
+
* The installed bindings this agent may use, read once per invocation. Rows are
|
|
429
|
+
* read straight from the database and never re-validated against the server:
|
|
430
|
+
* the model's tool list is exactly the set the operator pinned.
|
|
431
|
+
*/
|
|
432
|
+
export async function resolveInstalledAgentRemoteMcpToolBindings(input) {
|
|
433
|
+
if (!input.control)
|
|
434
|
+
return [];
|
|
435
|
+
const key = `${input.agent.name}:${input.conversationId}:${input.runId || input.requestId}`;
|
|
436
|
+
let requestCache = input.request ? installedBindingCache.get(input.request) : undefined;
|
|
437
|
+
if (input.request && !requestCache) {
|
|
438
|
+
requestCache = new Map();
|
|
439
|
+
installedBindingCache.set(input.request, requestCache);
|
|
440
|
+
}
|
|
441
|
+
const existing = requestCache?.get(key);
|
|
442
|
+
if (existing)
|
|
443
|
+
return existing;
|
|
444
|
+
const pending = resolveInstalledAgentRemoteMcpToolBindingsUncached(input);
|
|
445
|
+
requestCache?.set(key, pending);
|
|
446
|
+
return pending;
|
|
447
|
+
}
|
|
448
|
+
async function resolveInstalledAgentRemoteMcpToolBindingsUncached(input) {
|
|
449
|
+
let bindingRows;
|
|
450
|
+
try {
|
|
451
|
+
bindingRows = await listAgentRemoteMcpBindingRows(input.control, input.agent.name);
|
|
452
|
+
}
|
|
453
|
+
catch {
|
|
454
|
+
// A newly deployed Durable Object may not have applied the manifest tables
|
|
455
|
+
// yet. An unreadable install table means no installed tools, never a
|
|
456
|
+
// failed turn.
|
|
457
|
+
return [];
|
|
458
|
+
}
|
|
459
|
+
const active = bindingRows.filter((row) => row.status !== "disabled");
|
|
460
|
+
if (!active.length)
|
|
461
|
+
return [];
|
|
462
|
+
const reserved = new Set();
|
|
463
|
+
try {
|
|
464
|
+
for (const binding of agentRemoteMcpToolBindings(input.agent))
|
|
465
|
+
reserved.add(binding.modelToolName);
|
|
466
|
+
}
|
|
467
|
+
catch {
|
|
468
|
+
// Code-declared validation failures surface elsewhere; they must not stop
|
|
469
|
+
// installed tools from resolving.
|
|
470
|
+
}
|
|
471
|
+
const bindings = [];
|
|
472
|
+
for (const row of active) {
|
|
473
|
+
const serverRow = await findInstalledRemoteMcpServerRow(input.control, row.serverName);
|
|
474
|
+
if (!serverRow || serverRow.status !== "installed")
|
|
475
|
+
continue;
|
|
476
|
+
let server;
|
|
477
|
+
try {
|
|
478
|
+
server = installedRemoteMcpServerFor(serverRow);
|
|
479
|
+
}
|
|
480
|
+
catch (error) {
|
|
481
|
+
console.warn(`Raybase agent ${input.agent.name} skipped installed remote MCP server ${row.serverName}: ${error instanceof Error ? error.message : String(error)}`);
|
|
482
|
+
continue;
|
|
483
|
+
}
|
|
484
|
+
for (const toolName of parseToolNames(row.toolNamesJson)) {
|
|
485
|
+
const descriptor = server.tools[toolName];
|
|
486
|
+
if (!descriptor)
|
|
487
|
+
continue;
|
|
488
|
+
const modelToolName = remoteMcpModelToolName(server.name, toolName);
|
|
489
|
+
if (reserved.has(modelToolName)) {
|
|
490
|
+
console.warn(`Raybase agent ${input.agent.name} skipped installed remote MCP tool ${modelToolName}: the name is already taken`);
|
|
491
|
+
continue;
|
|
492
|
+
}
|
|
493
|
+
reserved.add(modelToolName);
|
|
494
|
+
bindings.push({ server, toolName, descriptor, modelToolName });
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
return bindings;
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* Code-declared plus installed bindings, merged. `agentRemoteMcpToolBindings`
|
|
501
|
+
* stays synchronous for definition-time validation.
|
|
502
|
+
*/
|
|
503
|
+
export async function resolveAgentRemoteMcpToolBindings(input) {
|
|
504
|
+
return [...agentRemoteMcpToolBindings(input.agent), ...(await resolveInstalledAgentRemoteMcpToolBindings(input))];
|
|
505
|
+
}
|
|
506
|
+
/** Model-facing tools for the installed half of the merged set. */
|
|
507
|
+
export async function installedAgentRemoteMcpRuntimeTools(input) {
|
|
508
|
+
const bindings = await resolveInstalledAgentRemoteMcpToolBindings(input);
|
|
509
|
+
if (!bindings.length)
|
|
510
|
+
return [];
|
|
511
|
+
return agentRemoteMcpRuntimeTools(input, bindings);
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* What an app may show a member about an agent's installed servers: names,
|
|
515
|
+
* host and tool descriptions. No endpoint path, no credential reference, no
|
|
516
|
+
* policy internals. Members never install.
|
|
517
|
+
*/
|
|
518
|
+
export async function agentRemoteMcpCapabilities(control, agentName) {
|
|
519
|
+
const bindings = (await listAgentRemoteMcpBindingRows(control, agentName)).filter((row) => row.status !== "disabled");
|
|
520
|
+
const capabilities = [];
|
|
521
|
+
for (const binding of bindings) {
|
|
522
|
+
const row = await findInstalledRemoteMcpServerRow(control, binding.serverName);
|
|
523
|
+
if (!row || row.status !== "installed")
|
|
524
|
+
continue;
|
|
525
|
+
const pinned = parsePinnedTools(row.toolsJson);
|
|
526
|
+
const tools = parseToolNames(binding.toolNamesJson)
|
|
527
|
+
.filter((toolName) => pinned[toolName])
|
|
528
|
+
.map((toolName) => ({ name: toolName, description: pinned[toolName].description }));
|
|
529
|
+
if (!tools.length)
|
|
530
|
+
continue;
|
|
531
|
+
capabilities.push({ serverName: row.name, endpointHost: endpointHost(row.endpoint), tools });
|
|
532
|
+
}
|
|
533
|
+
return capabilities.sort((left, right) => left.serverName.localeCompare(right.serverName));
|
|
534
|
+
}
|
|
535
|
+
/* -------------------------------------------------------------------------- */
|
|
536
|
+
/* Doctor */
|
|
537
|
+
/* -------------------------------------------------------------------------- */
|
|
538
|
+
/**
|
|
539
|
+
* Findings an operator can act on. `env` is the worker environment when the
|
|
540
|
+
* caller has one: a deployed app can see whether the secret is bound, a static
|
|
541
|
+
* caller cannot and gets the "check wrangler secret list" advice instead.
|
|
542
|
+
*/
|
|
543
|
+
export async function remoteMcpInstallFindings(input) {
|
|
544
|
+
const { servers, bindings } = await installedRemoteMcpOverview(input.control);
|
|
545
|
+
const findings = [];
|
|
546
|
+
const canReadEnv = input.canReadEnv ?? input.env !== undefined;
|
|
547
|
+
const byName = new Map(servers.map((server) => [server.name, server]));
|
|
548
|
+
for (const server of servers) {
|
|
549
|
+
if (server.credential.type === "stored") {
|
|
550
|
+
try {
|
|
551
|
+
remoteMcpKeyFromEnv(input.env);
|
|
552
|
+
}
|
|
553
|
+
catch {
|
|
554
|
+
findings.push({
|
|
555
|
+
level: "error", check: "remote-mcp-store-key", serverName: server.name,
|
|
556
|
+
message: `Installed remote MCP server ${server.name} uses runtime credential storage, but RAYBASE_REMOTE_MCP_KEY is missing or invalid.`,
|
|
557
|
+
fix: "Run raybase mcp keys enable, then configure the same Worker secret before deployment."
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
if (!server.credentialStatus || server.credentialStatus.state === "cleared") {
|
|
561
|
+
findings.push({ level: "warning", check: "remote-mcp-credential", serverName: server.name, message: `Installed remote MCP server ${server.name} has no usable stored credential.`, fix: `Set a credential or connect OAuth for ${server.name}.` });
|
|
562
|
+
}
|
|
563
|
+
else if (server.credentialStatus.state === "reauth_required") {
|
|
564
|
+
findings.push({ level: "error", check: "remote-mcp-reauth", serverName: server.name, message: `Installed remote MCP server ${server.name} needs OAuth reconnection.`, fix: `Run raybase mcp connect ${server.name}.` });
|
|
565
|
+
}
|
|
566
|
+
else if (server.credentialStatus.kind === "oauth" && server.credentialStatus.expiresAt && Date.parse(server.credentialStatus.expiresAt) <= Date.now()) {
|
|
567
|
+
findings.push({ level: "warning", check: "remote-mcp-oauth-expired", serverName: server.name, message: `Installed remote MCP server ${server.name} has an expired OAuth access token.`, fix: `Call the server to trigger refresh, or run raybase mcp connect ${server.name} if refresh is unavailable.` });
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
if (server.secretName && canReadEnv) {
|
|
571
|
+
const value = input.env && typeof input.env === "object" ? input.env[server.secretName] : undefined;
|
|
572
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
573
|
+
findings.push({
|
|
574
|
+
level: "error",
|
|
575
|
+
check: "remote-mcp-secret",
|
|
576
|
+
serverName: server.name,
|
|
577
|
+
secretName: server.secretName,
|
|
578
|
+
message: `Installed remote MCP server ${server.name} needs ${server.secretName}, which is not set in this environment.`,
|
|
579
|
+
fix: `Run wrangler secret put ${server.secretName} for the deployed worker, or add ${server.secretName} to .dev.vars locally.`
|
|
580
|
+
});
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
if (server.status === "installed" && server.discoveryHash !== server.toolsHash) {
|
|
584
|
+
findings.push({
|
|
585
|
+
level: "error",
|
|
586
|
+
check: "remote-mcp-discovery-hash",
|
|
587
|
+
serverName: server.name,
|
|
588
|
+
message: `Installed remote MCP server ${server.name} has pinned descriptors that no longer match its last discovery hash.`,
|
|
589
|
+
fix: `Run raybase mcp rediscover ${server.name} and confirm the diff so the pinned set and its hash agree again.`
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
try {
|
|
594
|
+
const stalePending = await input.control.first(`SELECT COUNT(*) AS "count" FROM ${q("_agent_remote_mcp_oauth_pending")} WHERE "expiresAt"<=?`, [nowIso()]);
|
|
595
|
+
const staleUploads = await input.control.first(`SELECT COUNT(*) AS "count" FROM ${q("_agent_remote_mcp_credential_uploads")} WHERE "expiresAt"<=?`, [nowIso()]);
|
|
596
|
+
const count = Number(stalePending?.count ?? 0) + Number(staleUploads?.count ?? 0);
|
|
597
|
+
if (count)
|
|
598
|
+
findings.push({ level: "info", check: "remote-mcp-stale-credentials", message: `${count} expired remote MCP OAuth state or upload ticket row(s) await pruning.`, fix: "A credential lifecycle operation prunes these rows opportunistically; no secret values are stored in tickets." });
|
|
599
|
+
}
|
|
600
|
+
catch { /* newly reconciled tables may not exist yet */ }
|
|
601
|
+
for (const binding of bindings) {
|
|
602
|
+
const server = byName.get(binding.serverName);
|
|
603
|
+
if (!server) {
|
|
604
|
+
findings.push({
|
|
605
|
+
level: "error",
|
|
606
|
+
check: "remote-mcp-binding",
|
|
607
|
+
serverName: binding.serverName,
|
|
608
|
+
message: `Agent ${binding.agentName} is bound to remote MCP server ${binding.serverName}, which is not installed.`,
|
|
609
|
+
fix: `Run raybase mcp unbind ${binding.agentName} ${binding.serverName}, or install the server again.`
|
|
610
|
+
});
|
|
611
|
+
continue;
|
|
612
|
+
}
|
|
613
|
+
if (server.status === "disabled") {
|
|
614
|
+
findings.push({
|
|
615
|
+
level: "warning",
|
|
616
|
+
check: "remote-mcp-binding",
|
|
617
|
+
serverName: binding.serverName,
|
|
618
|
+
message: `Agent ${binding.agentName} is bound to remote MCP server ${binding.serverName}, which is disabled; its tools are not offered.`,
|
|
619
|
+
fix: `Re-enable the server, or run raybase mcp unbind ${binding.agentName} ${binding.serverName}.`
|
|
620
|
+
});
|
|
621
|
+
}
|
|
622
|
+
if (binding.status === "disabled") {
|
|
623
|
+
findings.push({
|
|
624
|
+
level: "warning",
|
|
625
|
+
check: "remote-mcp-binding",
|
|
626
|
+
serverName: binding.serverName,
|
|
627
|
+
message: `Agent ${binding.agentName}'s binding to ${binding.serverName} is disabled: ${binding.disabledReason ?? "a pinned tool disappeared"}.`,
|
|
628
|
+
fix: `Run raybase mcp bind ${binding.agentName} ${binding.serverName} --tools … with the tools that still exist.`
|
|
629
|
+
});
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
return findings;
|
|
633
|
+
}
|
|
634
|
+
/* -------------------------------------------------------------------------- */
|
|
635
|
+
/* Parsing helpers */
|
|
636
|
+
/* -------------------------------------------------------------------------- */
|
|
637
|
+
export function parsePinnedTools(value) {
|
|
638
|
+
const parsed = safeJson(value);
|
|
639
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
640
|
+
return {};
|
|
641
|
+
return parsed;
|
|
642
|
+
}
|
|
643
|
+
export function parseToolAccess(value) {
|
|
644
|
+
const parsed = safeJson(value);
|
|
645
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
646
|
+
return {};
|
|
647
|
+
return parsed;
|
|
648
|
+
}
|
|
649
|
+
export function parseCredentialRef(value) {
|
|
650
|
+
const parsed = safeJson(value);
|
|
651
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
652
|
+
throw new Error("Installed remote MCP credential reference is malformed");
|
|
653
|
+
const record = parsed;
|
|
654
|
+
if (record.type === "none")
|
|
655
|
+
return { type: "none" };
|
|
656
|
+
if (record.type === "stored")
|
|
657
|
+
return { type: "stored" };
|
|
658
|
+
if (record.type !== "secret")
|
|
659
|
+
throw new Error("Installed remote MCP credential reference type is unsupported");
|
|
660
|
+
return {
|
|
661
|
+
type: "secret",
|
|
662
|
+
name: String(record.name ?? ""),
|
|
663
|
+
...(record.scheme === "raw" ? { scheme: "raw" } : record.scheme === "bearer" ? { scheme: "bearer" } : {})
|
|
664
|
+
};
|
|
665
|
+
}
|
|
666
|
+
export function parseToolNames(value) {
|
|
667
|
+
const parsed = safeJson(value);
|
|
668
|
+
return Array.isArray(parsed) ? parsed.filter((item) => typeof item === "string") : [];
|
|
669
|
+
}
|
|
670
|
+
/**
|
|
671
|
+
* `role:user`, `operator`, `deny`, or `public`. `public` is the only spelling
|
|
672
|
+
* that reaches the public principal, and it forces the read-only hint v1
|
|
673
|
+
* requires for public Agent use.
|
|
674
|
+
*/
|
|
675
|
+
export function parseRemoteMcpAccessSpec(spec) {
|
|
676
|
+
const value = spec.trim();
|
|
677
|
+
if (value === "operator")
|
|
678
|
+
return { policy: { kind: "operator" }, audience: "signed-in" };
|
|
679
|
+
if (value === "deny")
|
|
680
|
+
return { policy: { kind: "denyAll" }, audience: "signed-in" };
|
|
681
|
+
if (value === "public")
|
|
682
|
+
return { policy: { kind: "role", role: "user" }, audience: ["signed-in", "public"] };
|
|
683
|
+
const match = /^role:([A-Za-z][A-Za-z0-9_-]*)$/.exec(value);
|
|
684
|
+
if (match)
|
|
685
|
+
return { policy: { kind: "role", role: match[1] }, audience: "signed-in" };
|
|
686
|
+
throw new Error(`Unsupported remote MCP access rule "${spec}". Use role:<name>, operator, deny, or public.`);
|
|
687
|
+
}
|
|
688
|
+
export function validateInstalledCredential(credential) {
|
|
689
|
+
if (credential.type === "none" || credential.type === "stored")
|
|
690
|
+
return;
|
|
691
|
+
if (credential.type !== "secret")
|
|
692
|
+
throw new Error("Installed remote MCP credentials must be stored, a secret reference, or none");
|
|
693
|
+
if (!remoteMcpSecretNamePattern.test(credential.name)) {
|
|
694
|
+
throw new Error(`Remote MCP secret name ${credential.name} must match RAYBASE_MCP_<NAME>_TOKEN`);
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
function installedCredentialHeader(credential, env) {
|
|
698
|
+
if (credential.type !== "secret")
|
|
699
|
+
return undefined;
|
|
700
|
+
const value = env && typeof env === "object" ? env[credential.name] : undefined;
|
|
701
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
702
|
+
throw new Error(`Remote MCP credential ${credential.name} is not configured in this environment`);
|
|
703
|
+
}
|
|
704
|
+
return credential.scheme === "raw" ? value.trim() : `Bearer ${value.trim()}`;
|
|
705
|
+
}
|
|
706
|
+
function requiredControl(control) {
|
|
707
|
+
if (!control)
|
|
708
|
+
throw new Error("Remote MCP stored credentials require the control database");
|
|
709
|
+
return control;
|
|
710
|
+
}
|
|
711
|
+
function parseOAuthMetadata(value) {
|
|
712
|
+
if (!value)
|
|
713
|
+
return null;
|
|
714
|
+
const parsed = safeJson(value);
|
|
715
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
716
|
+
}
|
|
717
|
+
function parseStringArray(value) {
|
|
718
|
+
if (!value)
|
|
719
|
+
return [];
|
|
720
|
+
const parsed = safeJson(value);
|
|
721
|
+
return Array.isArray(parsed) ? parsed.filter((item) => typeof item === "string") : [];
|
|
722
|
+
}
|
|
723
|
+
function resolveToolAccess(tools, access) {
|
|
724
|
+
const resolved = {};
|
|
725
|
+
// "*" is the operator's rule for every tool without one of its own.
|
|
726
|
+
const fallback = access?.["*"];
|
|
727
|
+
for (const [toolName, rule] of Object.entries(access ?? {})) {
|
|
728
|
+
if (toolName === "*")
|
|
729
|
+
continue;
|
|
730
|
+
if (!tools[toolName])
|
|
731
|
+
throw new Error(`Access rule references unpinned tool ${toolName}`);
|
|
732
|
+
resolved[toolName] = { policy: rule.policy ?? defaultRemoteMcpToolPolicy, ...(rule.audience ? { audience: rule.audience } : {}) };
|
|
733
|
+
}
|
|
734
|
+
for (const toolName of Object.keys(tools)) {
|
|
735
|
+
if (resolved[toolName])
|
|
736
|
+
continue;
|
|
737
|
+
resolved[toolName] = fallback
|
|
738
|
+
? { policy: fallback.policy ?? defaultRemoteMcpToolPolicy, ...(fallback.audience ? { audience: fallback.audience } : {}) }
|
|
739
|
+
: { policy: defaultRemoteMcpToolPolicy, audience: "signed-in" };
|
|
740
|
+
}
|
|
741
|
+
return resolved;
|
|
742
|
+
}
|
|
743
|
+
async function requireServerRow(control, name) {
|
|
744
|
+
const row = await findInstalledRemoteMcpServerRow(control, name);
|
|
745
|
+
if (!row)
|
|
746
|
+
throw new Error(`Remote MCP server ${name} is not installed`);
|
|
747
|
+
return row;
|
|
748
|
+
}
|
|
749
|
+
function parsePolicyExpression(value) {
|
|
750
|
+
switch (value?.kind) {
|
|
751
|
+
case "allowAll":
|
|
752
|
+
case "denyAll":
|
|
753
|
+
case "operator":
|
|
754
|
+
return { kind: value.kind };
|
|
755
|
+
case "role":
|
|
756
|
+
return role(String(value.role));
|
|
757
|
+
case "servicePrincipal":
|
|
758
|
+
return { kind: "servicePrincipal", service: String(value.service) };
|
|
759
|
+
case "anyOf":
|
|
760
|
+
case "allOf":
|
|
761
|
+
return { kind: value.kind, policies: (value.policies ?? []).map(parsePolicyExpression) };
|
|
762
|
+
case "not":
|
|
763
|
+
return { kind: "not", policy: parsePolicyExpression(value.policy) };
|
|
764
|
+
default:
|
|
765
|
+
// `owns` needs a row and cannot describe a remote tool call.
|
|
766
|
+
throw new Error(`Unsupported remote MCP access rule kind "${String(value?.kind)}"`);
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
function safeJson(value) {
|
|
770
|
+
try {
|
|
771
|
+
return JSON.parse(value);
|
|
772
|
+
}
|
|
773
|
+
catch {
|
|
774
|
+
return null;
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
function endpointHost(endpoint) {
|
|
778
|
+
try {
|
|
779
|
+
return new URL(endpoint).hostname.toLowerCase();
|
|
780
|
+
}
|
|
781
|
+
catch {
|
|
782
|
+
return "";
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
//# sourceMappingURL=agent-remote-mcp-install.js.map
|