@anchrd/intel 0.59.0
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/README.md +496 -0
- package/bin/intel.mjs +134 -0
- package/dist/adapters/cloudflare/cloudflare-flow-workflow.d.ts +42 -0
- package/dist/adapters/cloudflare/cloudflare-flow-workflow.js +89 -0
- package/dist/adapters/cloudflare/cloudflare.d.ts +29 -0
- package/dist/adapters/cloudflare/cloudflare.js +416 -0
- package/dist/adapters/cloudflare/cloudflare.types.d.ts +31 -0
- package/dist/adapters/cloudflare/cloudflare.types.js +1 -0
- package/dist/adapters/content/content.d.ts +3 -0
- package/dist/adapters/content/content.js +128 -0
- package/dist/adapters/content/content.types.d.ts +38 -0
- package/dist/adapters/content/content.types.js +1 -0
- package/dist/adapters/db/db-audit.d.ts +62 -0
- package/dist/adapters/db/db-audit.js +150 -0
- package/dist/adapters/db/db-boards.d.ts +6 -0
- package/dist/adapters/db/db-boards.js +324 -0
- package/dist/adapters/db/db-feed.d.ts +30 -0
- package/dist/adapters/db/db-feed.js +276 -0
- package/dist/adapters/db/db-flows.d.ts +42 -0
- package/dist/adapters/db/db-flows.js +1311 -0
- package/dist/adapters/db/db-grants.d.ts +133 -0
- package/dist/adapters/db/db-grants.js +269 -0
- package/dist/adapters/db/db-indexing.d.ts +3 -0
- package/dist/adapters/db/db-indexing.js +200 -0
- package/dist/adapters/db/db-oauth.d.ts +3 -0
- package/dist/adapters/db/db-oauth.js +26 -0
- package/dist/adapters/db/db-prompts.d.ts +46 -0
- package/dist/adapters/db/db-prompts.js +144 -0
- package/dist/adapters/db/db.d.ts +6 -0
- package/dist/adapters/db/db.js +1687 -0
- package/dist/adapters/db/db.types.d.ts +16 -0
- package/dist/adapters/db/db.types.js +1 -0
- package/dist/adapters/document-converter/document-converter.d.ts +2 -0
- package/dist/adapters/document-converter/document-converter.js +26 -0
- package/dist/adapters/document-converter/document-converter.types.d.ts +17 -0
- package/dist/adapters/document-converter/document-converter.types.js +1 -0
- package/dist/adapters/flow-runtime/flow-runtime.d.ts +3 -0
- package/dist/adapters/flow-runtime/flow-runtime.js +22 -0
- package/dist/adapters/flow-runtime/flow-runtime.types.d.ts +19 -0
- package/dist/adapters/flow-runtime/flow-runtime.types.js +1 -0
- package/dist/adapters/index-queue/index-queue.d.ts +10 -0
- package/dist/adapters/index-queue/index-queue.js +12 -0
- package/dist/adapters/index-queue/index-queue.types.d.ts +11 -0
- package/dist/adapters/index-queue/index-queue.types.js +1 -0
- package/dist/adapters/json-schema/json-schema.d.ts +2 -0
- package/dist/adapters/json-schema/json-schema.js +77 -0
- package/dist/adapters/openid/openid.d.ts +5 -0
- package/dist/adapters/openid/openid.js +301 -0
- package/dist/adapters/portal-tokens/portal-tokens.d.ts +8 -0
- package/dist/adapters/portal-tokens/portal-tokens.js +79 -0
- package/dist/adapters/remote-tools/remote-tools.d.ts +5 -0
- package/dist/adapters/remote-tools/remote-tools.js +129 -0
- package/dist/adapters/semantic-index/semantic-index.d.ts +2 -0
- package/dist/adapters/semantic-index/semantic-index.js +139 -0
- package/dist/adapters/semantic-index/semantic-index.types.d.ts +56 -0
- package/dist/adapters/semantic-index/semantic-index.types.js +1 -0
- package/dist/adapters/session-cookie/session-cookie.d.ts +5 -0
- package/dist/adapters/session-cookie/session-cookie.js +87 -0
- package/dist/adapters/tool-source-policy/tool-source-policy.d.ts +1 -0
- package/dist/adapters/tool-source-policy/tool-source-policy.js +22 -0
- package/dist/audit/audit.d.ts +2 -0
- package/dist/audit/audit.js +87 -0
- package/dist/audit/audit.types.d.ts +23 -0
- package/dist/audit/audit.types.js +1 -0
- package/dist/auth/auth.d.ts +2 -0
- package/dist/auth/auth.js +284 -0
- package/dist/auth/auth.types.d.ts +108 -0
- package/dist/auth/auth.types.js +1 -0
- package/dist/boards/boards.d.ts +44 -0
- package/dist/boards/boards.js +447 -0
- package/dist/boards/boards.types.d.ts +117 -0
- package/dist/boards/boards.types.js +1 -0
- package/dist/build/build.d.ts +2 -0
- package/dist/build/build.js +192 -0
- package/dist/build/build.types.d.ts +17 -0
- package/dist/build/build.types.js +1 -0
- package/dist/bundle/bundle.d.ts +4 -0
- package/dist/bundle/bundle.js +1194 -0
- package/dist/bundle/bundle.types.d.ts +33 -0
- package/dist/bundle/bundle.types.js +1 -0
- package/dist/cli/cli.d.ts +4 -0
- package/dist/cli/cli.js +262 -0
- package/dist/cli/cli.types.d.ts +14 -0
- package/dist/cli/cli.types.js +1 -0
- package/dist/contract/audit.d.ts +50 -0
- package/dist/contract/audit.js +72 -0
- package/dist/contract/board.d.ts +150 -0
- package/dist/contract/board.js +262 -0
- package/dist/contract/bundle.d.ts +147 -0
- package/dist/contract/bundle.js +139 -0
- package/dist/contract/contract.d.ts +112 -0
- package/dist/contract/contract.js +143 -0
- package/dist/contract/feed.d.ts +126 -0
- package/dist/contract/feed.js +149 -0
- package/dist/contract/flow-run.d.ts +346 -0
- package/dist/contract/flow-run.js +194 -0
- package/dist/contract/flow.d.ts +1057 -0
- package/dist/contract/flow.js +552 -0
- package/dist/contract/node.d.ts +551 -0
- package/dist/contract/node.js +456 -0
- package/dist/contract/share.d.ts +216 -0
- package/dist/contract/share.js +134 -0
- package/dist/contract/table.d.ts +171 -0
- package/dist/contract/table.js +117 -0
- package/dist/contract/tool.d.ts +122 -0
- package/dist/contract/tool.js +172 -0
- package/dist/feed/feed.d.ts +2 -0
- package/dist/feed/feed.js +77 -0
- package/dist/feed/feed.types.d.ts +23 -0
- package/dist/feed/feed.types.js +1 -0
- package/dist/flows/flows.d.ts +41 -0
- package/dist/flows/flows.js +2216 -0
- package/dist/flows/flows.types.d.ts +339 -0
- package/dist/flows/flows.types.js +1 -0
- package/dist/http/http.d.ts +9 -0
- package/dist/http/http.js +874 -0
- package/dist/http/http.types.d.ts +22 -0
- package/dist/http/http.types.js +1 -0
- package/dist/indexing/indexing.d.ts +6 -0
- package/dist/indexing/indexing.js +239 -0
- package/dist/indexing/indexing.types.d.ts +11 -0
- package/dist/indexing/indexing.types.js +1 -0
- package/dist/intel/intel.d.ts +3 -0
- package/dist/intel/intel.js +194 -0
- package/dist/intel/intel.types.d.ts +24 -0
- package/dist/intel/intel.types.js +1 -0
- package/dist/mcp/mcp.d.ts +2 -0
- package/dist/mcp/mcp.js +1292 -0
- package/dist/mcp/mcp.types.d.ts +25 -0
- package/dist/mcp/mcp.types.js +1 -0
- package/dist/nodes/document-links/document-links.d.ts +20 -0
- package/dist/nodes/document-links/document-links.js +57 -0
- package/dist/nodes/nodes.d.ts +2 -0
- package/dist/nodes/nodes.js +1667 -0
- package/dist/nodes/nodes.types.d.ts +511 -0
- package/dist/nodes/nodes.types.js +1 -0
- package/dist/prepare/prepare.d.ts +4 -0
- package/dist/prepare/prepare.js +16 -0
- package/dist/prepare/prepare.types.d.ts +9 -0
- package/dist/prepare/prepare.types.js +1 -0
- package/dist/prompts/prompts.d.ts +2 -0
- package/dist/prompts/prompts.js +65 -0
- package/dist/prompts/prompts.types.d.ts +71 -0
- package/dist/prompts/prompts.types.js +1 -0
- package/dist/shared/callers/callers.d.ts +28 -0
- package/dist/shared/callers/callers.js +30 -0
- package/dist/shared/csv/csv.d.ts +13 -0
- package/dist/shared/csv/csv.js +85 -0
- package/dist/shared/document-text/document-text.d.ts +21 -0
- package/dist/shared/document-text/document-text.js +31 -0
- package/dist/shared/gate-authorization/gate-authorization.d.ts +5 -0
- package/dist/shared/gate-authorization/gate-authorization.js +26 -0
- package/dist/shared/grant-expiry/grant-expiry.d.ts +19 -0
- package/dist/shared/grant-expiry/grant-expiry.js +26 -0
- package/dist/shared/intel-error/intel-error.d.ts +22 -0
- package/dist/shared/intel-error/intel-error.js +27 -0
- package/dist/shared/plain-title/plain-title.d.ts +16 -0
- package/dist/shared/plain-title/plain-title.js +45 -0
- package/dist/shared/problem-details/problem-details.d.ts +30 -0
- package/dist/shared/problem-details/problem-details.js +18 -0
- package/dist/shared/report-unexpected-error/report-unexpected-error.d.ts +1 -0
- package/dist/shared/report-unexpected-error/report-unexpected-error.js +37 -0
- package/dist/shared/safe-return-path/safe-return-path.d.ts +2 -0
- package/dist/shared/safe-return-path/safe-return-path.js +11 -0
- package/dist/shared/sha256/sha256.d.ts +2 -0
- package/dist/shared/sha256/sha256.js +14 -0
- package/dist/tools/tool-servers/tool-servers.d.ts +46 -0
- package/dist/tools/tool-servers/tool-servers.js +177 -0
- package/dist/tools/tools.d.ts +2 -0
- package/dist/tools/tools.js +207 -0
- package/dist/tools/tools.types.d.ts +56 -0
- package/dist/tools/tools.types.js +1 -0
- package/examples/branding/favicon.svg +5 -0
- package/examples/branding/logo.svg +5 -0
- package/examples/branding/theme.css +16 -0
- package/examples/dev.vars.example +7 -0
- package/examples/intel.json +9 -0
- package/migrations/0000_intel_foundation.sql +203 -0
- package/migrations/0001_portal_native_tools.sql +21 -0
- package/migrations/0002_flows_in_the_knowledge_tree.sql +34 -0
- package/migrations/0003_folder_permissions.sql +211 -0
- package/migrations/0004_subflow_runs.sql +14 -0
- package/migrations/0005_flow_node_cleanup.sql +28 -0
- package/migrations/0005_tables_in_the_knowledge_tree.sql +68 -0
- package/migrations/0006_links_are_written_in_the_text.sql +20 -0
- package/migrations/0007_no_node_waits.sql +69 -0
- package/migrations/0008_three_layers.sql +130 -0
- package/migrations/0009_no_context_policy.sql +48 -0
- package/migrations/0010_no_run_waits_a_year.sql +27 -0
- package/migrations/0011_one_name_for_the_tree.sql +53 -0
- package/migrations/0012_table_snapshots.sql +29 -0
- package/migrations/0017_a_vector_per_card.sql +38 -0
- package/migrations/0018_no_context_policy_at_last.sql +97 -0
- package/migrations/0019_one_name_for_the_grants.sql +52 -0
- package/migrations/0020_cascade_purge_replay.sql +11 -0
- package/migrations/0021_a_flow_carries_its_own_grant.sql +44 -0
- package/migrations/0022_a_cursor_over_the_journal.sql +14 -0
- package/migrations/0023_a_board_and_its_tasks.sql +116 -0
- package/migrations/0024_the_archive_is_a_column.sql +33 -0
- package/migrations/0025_archiving_takes_the_subtree.sql +29 -0
- package/migrations/0026_one_feed_over_two_kinds.sql +23 -0
- package/migrations/0027_the_runs_of_every_flow.sql +27 -0
- package/migrations/0028_a_prompt_name_over_two_kinds.sql +70 -0
- package/package.json +118 -0
- package/src/contract/CLAUDE.md +152 -0
- package/src/contract/README.md +102 -0
- package/src/contract/audit.ts +85 -0
- package/src/contract/board.ts +306 -0
- package/src/contract/bundle.ts +157 -0
- package/src/contract/contract.ts +161 -0
- package/src/contract/feed.ts +165 -0
- package/src/contract/flow-run.ts +241 -0
- package/src/contract/flow.ts +675 -0
- package/src/contract/node.ts +583 -0
- package/src/contract/share.ts +196 -0
- package/src/contract/table.ts +178 -0
- package/src/contract/tool.ts +202 -0
- package/ui/README.md +110 -0
- package/ui/components.json +21 -0
- package/ui/index.html +13 -0
- package/ui/package.json +11 -0
- package/ui/scripts/lint-tokens.mjs +187 -0
- package/ui/src/access-summary/access-summary.tsx +385 -0
- package/ui/src/app/action-slot/action-slot.tsx +27 -0
- package/ui/src/app/app-sidebar/app-sidebar.tsx +71 -0
- package/ui/src/app/app-tree/app-tree.tsx +1175 -0
- package/ui/src/app/app.tsx +87 -0
- package/ui/src/app/header-search/header-search.tsx +294 -0
- package/ui/src/app/reindex-dialog/reindex-dialog.tsx +79 -0
- package/ui/src/app/settings-dialog/settings-dialog.tsx +135 -0
- package/ui/src/app/sidebar-preferences/sidebar-preferences.ts +68 -0
- package/ui/src/app/sidebar-preferences/sidebar-preferences.types.ts +15 -0
- package/ui/src/app/sidebar-resize-handle/sidebar-resize-handle.tsx +86 -0
- package/ui/src/app/tree-expansion/tree-expansion.ts +107 -0
- package/ui/src/app/tree-expansion/tree-expansion.types.ts +31 -0
- package/ui/src/app/tree-move/tree-move.tsx +427 -0
- package/ui/src/app/tree-reveal/tree-reveal.ts +64 -0
- package/ui/src/app/tree-reveal/tree-reveal.types.ts +16 -0
- package/ui/src/app/user-footer/user-footer.tsx +188 -0
- package/ui/src/app/view-menu/view-menu.tsx +115 -0
- package/ui/src/app-root/app-root.tsx +109 -0
- package/ui/src/app-root/app-root.types.ts +27 -0
- package/ui/src/archive/archive.tsx +417 -0
- package/ui/src/attachment-detail/attachment-detail.ts +52 -0
- package/ui/src/attachment-viewer/attachment-viewer.tsx +166 -0
- package/ui/src/blocknote-view/blocknote-view.tsx +30 -0
- package/ui/src/board/board-assignee/board-assignee-picker.tsx +132 -0
- package/ui/src/board/board-assignee/board-assignee.ts +48 -0
- package/ui/src/board/board-assignee/board-assignee.tsx +75 -0
- package/ui/src/board/board-chip/board-chip.tsx +46 -0
- package/ui/src/board/board-crumbs/board-crumbs.ts +56 -0
- package/ui/src/board/board-crumbs/board-crumbs.tsx +111 -0
- package/ui/src/board/board-data/board-data.ts +195 -0
- package/ui/src/board/board-data/board-data.types.ts +38 -0
- package/ui/src/board/board-dates/board-dates.ts +40 -0
- package/ui/src/board/board-dates/board-dates.tsx +105 -0
- package/ui/src/board/board-kanban/board-kanban.ts +162 -0
- package/ui/src/board/board-kanban/board-kanban.tsx +680 -0
- package/ui/src/board/board-open-task/board-open-task.ts +32 -0
- package/ui/src/board/board-panel/board-panel.tsx +259 -0
- package/ui/src/board/board-settings/board-settings.tsx +226 -0
- package/ui/src/board/board-status/board-status.tsx +60 -0
- package/ui/src/board/board-stripes/board-stripes.ts +128 -0
- package/ui/src/board/board-table/board-table.ts +53 -0
- package/ui/src/board/board-table/board-table.tsx +610 -0
- package/ui/src/board/board-task/board-task.ts +108 -0
- package/ui/src/board/board-task/board-task.tsx +335 -0
- package/ui/src/board/board-title-row/board-title-row.tsx +66 -0
- package/ui/src/branding/branding.tsx +17 -0
- package/ui/src/branding/custom-logo.ts +1 -0
- package/ui/src/branding/favicon.default.svg +5 -0
- package/ui/src/branding/favicon.svg +5 -0
- package/ui/src/components/ui/avatar.tsx +39 -0
- package/ui/src/components/ui/button.tsx +64 -0
- package/ui/src/components/ui/collapsible.tsx +20 -0
- package/ui/src/components/ui/command.tsx +160 -0
- package/ui/src/components/ui/dialog.tsx +143 -0
- package/ui/src/components/ui/dropdown-menu.tsx +187 -0
- package/ui/src/components/ui/input.tsx +21 -0
- package/ui/src/components/ui/popover.tsx +41 -0
- package/ui/src/components/ui/select.tsx +163 -0
- package/ui/src/components/ui/separator.tsx +26 -0
- package/ui/src/components/ui/sheet.tsx +136 -0
- package/ui/src/components/ui/sidebar.tsx +693 -0
- package/ui/src/components/ui/skeleton.tsx +13 -0
- package/ui/src/components/ui/switch.tsx +25 -0
- package/ui/src/components/ui/table.tsx +82 -0
- package/ui/src/components/ui/tabs.tsx +52 -0
- package/ui/src/components/ui/tooltip.tsx +51 -0
- package/ui/src/data/intel-data-provider/intel-data-provider.ts +685 -0
- package/ui/src/data/intel-data-provider/intel-data-provider.types.ts +266 -0
- package/ui/src/data/query-client.ts +18 -0
- package/ui/src/data/request-refusal/refusal-notice.tsx +36 -0
- package/ui/src/data/request-refusal/request-refusal.ts +51 -0
- package/ui/src/data/sign-in/sign-in.ts +97 -0
- package/ui/src/data/sign-in/sign-in.types.ts +15 -0
- package/ui/src/document-link/document-link.tsx +114 -0
- package/ui/src/document-markdown/document-markdown.ts +195 -0
- package/ui/src/editor-schema/editor-schema.ts +23 -0
- package/ui/src/entry-picker/entry-picker.tsx +238 -0
- package/ui/src/feed/feed.tsx +344 -0
- package/ui/src/file-preview/file-preview-view.tsx +496 -0
- package/ui/src/file-preview/file-preview.tsx +28 -0
- package/ui/src/file-preview/pdf-file-preview.tsx +5 -0
- package/ui/src/file-preview/presentation-file-preview.tsx +21 -0
- package/ui/src/file-preview/spreadsheet-file-preview.tsx +5 -0
- package/ui/src/file-preview/word-file-preview.tsx +5 -0
- package/ui/src/flow-runs/flow-runs.tsx +238 -0
- package/ui/src/flows/flows.tsx +1646 -0
- package/ui/src/flows/node-icon/node-icon.ts +34 -0
- package/ui/src/flows/node-palette/node-palette.tsx +214 -0
- package/ui/src/flows/node-palette/node-palette.types.ts +15 -0
- package/ui/src/folder-contents/folder-contents.tsx +321 -0
- package/ui/src/folder-index/folder-index.ts +55 -0
- package/ui/src/frontmatter/frontmatter.tsx +193 -0
- package/ui/src/graph-pane/graph-pane.tsx +53 -0
- package/ui/src/hooks/use-mobile.ts +19 -0
- package/ui/src/i18n/custom.json +4 -0
- package/ui/src/i18n/de.json +609 -0
- package/ui/src/i18n/en.json +609 -0
- package/ui/src/i18n/es.json +609 -0
- package/ui/src/i18n/i18n-context.tsx +65 -0
- package/ui/src/i18n/i18n-languages/i18n-languages.ts +83 -0
- package/ui/src/i18n/i18n.ts +32 -0
- package/ui/src/i18n/i18n.types.ts +49 -0
- package/ui/src/kind-icon.ts +97 -0
- package/ui/src/lib/utils.ts +6 -0
- package/ui/src/main.tsx +68 -0
- package/ui/src/modal/modal.tsx +53 -0
- package/ui/src/node-details/node-details.tsx +545 -0
- package/ui/src/node-editor/node-editor.tsx +304 -0
- package/ui/src/node-graph/graph-notice.tsx +21 -0
- package/ui/src/node-graph/node-graph.ts +214 -0
- package/ui/src/node-graph/node-graph.tsx +295 -0
- package/ui/src/node-import/node-import.tsx +418 -0
- package/ui/src/node-save-error.ts +46 -0
- package/ui/src/node-source/node-source.tsx +128 -0
- package/ui/src/node-table/node-table.tsx +107 -0
- package/ui/src/nodes/nodes.tsx +240 -0
- package/ui/src/rename-resource/rename-resource.ts +80 -0
- package/ui/src/resolved-names/resolved-names.tsx +67 -0
- package/ui/src/resource-error.ts +33 -0
- package/ui/src/resource-menu/resource-menu.tsx +1156 -0
- package/ui/src/router/router-context.ts +6 -0
- package/ui/src/router/router.tsx +67 -0
- package/ui/src/router/router.types.ts +8 -0
- package/ui/src/router/selection-search.ts +88 -0
- package/ui/src/save-button/save-button.tsx +94 -0
- package/ui/src/section-hint/section-hint.tsx +40 -0
- package/ui/src/sign-in-refused/sign-in-refused.tsx +35 -0
- package/ui/src/styles.css +189 -0
- package/ui/src/table-columns/table-columns.tsx +379 -0
- package/ui/src/theme/custom.css +1 -0
- package/ui/src/theme/theme-context.tsx +89 -0
- package/ui/src/theme/theme.ts +70 -0
- package/ui/src/time/relative-time.tsx +41 -0
- package/ui/src/time/time-context.tsx +78 -0
- package/ui/src/time/time.ts +165 -0
- package/ui/src/title-row/title-row.tsx +185 -0
- package/ui/src/tools/tools.tsx +546 -0
- package/ui/src/user-name/user-name.ts +61 -0
- package/ui/src/verbatim/verbatim.tsx +68 -0
- package/ui/tsconfig.json +17 -0
- package/ui/vite.config.ts +129 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { type ToolServer } from "../../contract/tool.js";
|
|
2
|
+
/**
|
|
3
|
+
* The portal's own directory tool. It is an ordinary entry in `tools/list`, so it is reached the
|
|
4
|
+
* same way every other tool is: `tools/call` with the asking user's portal token.
|
|
5
|
+
*
|
|
6
|
+
* ⚠️ Its presence in the live list is also the permission check. A deployment whose portal does not
|
|
7
|
+
* offer it, or a user whose Access policies hide it, gets no server list at all — and therefore
|
|
8
|
+
* cannot delegate anything. That is deliberate: the alternative would be inventing the server list
|
|
9
|
+
* out of tool names, which is exactly the guess this module exists to avoid.
|
|
10
|
+
*/
|
|
11
|
+
export declare const ServerDirectoryTool = "portal_list_servers";
|
|
12
|
+
export interface ToolServerDirectoryAnswer {
|
|
13
|
+
isError: boolean;
|
|
14
|
+
content: unknown[];
|
|
15
|
+
structuredContent?: unknown;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* The servers this user reaches: the portal's directory, confirmed against the portal's live tool
|
|
19
|
+
* list.
|
|
20
|
+
*
|
|
21
|
+
* Two sets come out of one answer, and keeping them apart is the whole of this function:
|
|
22
|
+
*
|
|
23
|
+
* - **`declared`** — every identifier the directory mentions at all, disabled rows included. It is
|
|
24
|
+
* the universe `serverOf` attributes against, and it must be as WIDE as possible. ⚠️ Narrowing it
|
|
25
|
+
* widens the delegation: drop `wiki_extra` because the portal marked it disabled, and the live
|
|
26
|
+
* tool `wiki_extra__read` falls back onto `wiki` — a tool of a server nobody delegated, handed to
|
|
27
|
+
* an agent that was given `wiki`. Filtering therefore happens after attribution, never before it.
|
|
28
|
+
* - **the returned list** — what may actually be delegated: enabled, and confirmed by at least one
|
|
29
|
+
* live tool. Intel cannot prove that a directory identifier is the same string the namespace uses,
|
|
30
|
+
* so it only ever offers handles it has just seen work.
|
|
31
|
+
*
|
|
32
|
+
* ⚠️ What remains outside Intel's reach: a directory that omits a server whose tools ARE in
|
|
33
|
+
* `tools/list`. Nothing in the answer distinguishes that from a tool of the enclosing namespace, and
|
|
34
|
+
* the portal is the authority on its own servers — so an incomplete directory widens by exactly one
|
|
35
|
+
* namespace level, and it is a portal defect rather than something this code can detect.
|
|
36
|
+
*/
|
|
37
|
+
export interface ToolServerDirectory {
|
|
38
|
+
/** Every identifier the directory mentioned. Attribute against this, never against `servers`. */
|
|
39
|
+
declared: string[];
|
|
40
|
+
/** What may be offered and delegated: enabled, and confirmed by a live tool. */
|
|
41
|
+
servers: ToolServer[];
|
|
42
|
+
}
|
|
43
|
+
export declare function toolServersFrom(input: {
|
|
44
|
+
directory: ToolServerDirectoryAnswer;
|
|
45
|
+
toolNames: string[];
|
|
46
|
+
}): ToolServerDirectory | null;
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { serverOf } from "../../contract/tool.js";
|
|
3
|
+
/**
|
|
4
|
+
* The portal's own directory tool. It is an ordinary entry in `tools/list`, so it is reached the
|
|
5
|
+
* same way every other tool is: `tools/call` with the asking user's portal token.
|
|
6
|
+
*
|
|
7
|
+
* ⚠️ Its presence in the live list is also the permission check. A deployment whose portal does not
|
|
8
|
+
* offer it, or a user whose Access policies hide it, gets no server list at all — and therefore
|
|
9
|
+
* cannot delegate anything. That is deliberate: the alternative would be inventing the server list
|
|
10
|
+
* out of tool names, which is exactly the guess this module exists to avoid.
|
|
11
|
+
*/
|
|
12
|
+
export const ServerDirectoryTool = "portal_list_servers";
|
|
13
|
+
/**
|
|
14
|
+
* What the portal answers about one server, read as tolerantly as possible.
|
|
15
|
+
*
|
|
16
|
+
* ⚠️ Only the shape is assumed, never the vocabulary. The portal is somebody else's product and its
|
|
17
|
+
* payload is not part of any contract Intel owns, so every field is optional and the answer is
|
|
18
|
+
* accepted from `structuredContent`, from `{ servers: [...] }`, from a JSON text block, or — the
|
|
19
|
+
* form the live portal actually uses — from the prose block `proseRows` reads. What makes a row
|
|
20
|
+
* usable is not that it parsed but that its identifier is confirmed against the live tool list
|
|
21
|
+
* below.
|
|
22
|
+
*/
|
|
23
|
+
const DirectoryRow = z.looseObject({
|
|
24
|
+
id: z.string().optional(),
|
|
25
|
+
name: z.string().optional(),
|
|
26
|
+
enabled: z.boolean().optional(),
|
|
27
|
+
});
|
|
28
|
+
const DirectoryRows = z.array(DirectoryRow);
|
|
29
|
+
const DirectoryEnvelope = z.looseObject({ servers: DirectoryRows });
|
|
30
|
+
/** A line the portal meant as a directory entry: `- <display name> (<handle>): <state>`. */
|
|
31
|
+
const ProseRow = /^\s*[-*•]\s+(.+?)\s*\(([^()\s]+)\)\s*:\s*(.*)$/;
|
|
32
|
+
/** What claims to be an entry at all. Everything else in the block is a heading or a hint. */
|
|
33
|
+
const ProseBullet = /^\s*[-*•]\s/;
|
|
34
|
+
/**
|
|
35
|
+
* ⚠️ Never the ✓. A tick is decoration: a different locale, a plain-text client or a later version
|
|
36
|
+
* of the portal writes the same fact another way, and a reading that hangs on one code point would
|
|
37
|
+
* silently switch every server off. What is read is the word, and the word alone.
|
|
38
|
+
*
|
|
39
|
+
* It has to be the WHOLE state, not a word inside it, because "disabled" contains "enabled" and
|
|
40
|
+
* "not enabled" is built from it — a substring test would read both as switched on, and that is the
|
|
41
|
+
* one direction this must never get wrong. Anything else the portal might write ("aktiviert",
|
|
42
|
+
* "enabled (3 tools)") is therefore not offerable, and stays declared.
|
|
43
|
+
*/
|
|
44
|
+
function readsAsEnabled(state) {
|
|
45
|
+
const words = state.toLowerCase().match(/\p{L}+/gu);
|
|
46
|
+
return words !== null && words.length === 1 && words[0] === "enabled";
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* The portal's prose form: a heading, one bullet per server, a closing hint. It is what the live
|
|
50
|
+
* portal actually answers (#233), and it is read only after every JSON reading in `rows` has failed.
|
|
51
|
+
*
|
|
52
|
+
* ⚠️ This reading hangs on somebody else's wording and can break without warning. The portal owes
|
|
53
|
+
* Intel no format at all, so a reworded state, a translated page or a changed layout is a normal
|
|
54
|
+
* event here rather than a defect. Two things keep that from becoming a quiet widening:
|
|
55
|
+
*
|
|
56
|
+
* - **A bullet that does not read as a row refuses the whole answer.** Skipping it would hand back
|
|
57
|
+
* a directory with one server missing, and a missing row is not a smaller list — it lets that
|
|
58
|
+
* server's tools fall back onto the shorter handle that encloses them, which is exactly the
|
|
59
|
+
* widening the split between `declared` and `servers` exists to prevent. The price is that a
|
|
60
|
+
* portal which one day bullets its closing hint fails loudly with the same closed
|
|
61
|
+
* `tool_servers_unavailable` that #233 found; that is the intended direction.
|
|
62
|
+
* - **Nothing read here can invent a server.** A handle no live tool confirms is never offered, so
|
|
63
|
+
* a misread line costs an offer, never a delegation.
|
|
64
|
+
*/
|
|
65
|
+
function proseRows(answer) {
|
|
66
|
+
const parsed = [];
|
|
67
|
+
for (const block of answer.content) {
|
|
68
|
+
if (typeof block !== "object" || block === null || !("text" in block))
|
|
69
|
+
continue;
|
|
70
|
+
const text = block.text;
|
|
71
|
+
if (typeof text !== "string")
|
|
72
|
+
continue;
|
|
73
|
+
for (const line of text.split("\n")) {
|
|
74
|
+
if (!ProseBullet.test(line))
|
|
75
|
+
continue;
|
|
76
|
+
const [, name, handle, state] = ProseRow.exec(line) ?? [];
|
|
77
|
+
if (name === undefined || handle === undefined)
|
|
78
|
+
return null;
|
|
79
|
+
// `id` carries the handle and `name` the display name, but neither is believed on its own:
|
|
80
|
+
// both stay candidates in `toolServersFrom`, and the live tool list decides which one
|
|
81
|
+
// actually namespaces.
|
|
82
|
+
parsed.push({ id: handle, name, enabled: readsAsEnabled(state ?? "") });
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return parsed.length > 0 ? parsed : null;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Every place the portal could reasonably have put its list, tried in order. An MCP tool result is
|
|
89
|
+
* either structured or text, and a server that answers with `{ servers: [...] }` is as likely as
|
|
90
|
+
* one that answers with a bare array.
|
|
91
|
+
*
|
|
92
|
+
* ⚠️ The prose reading comes last and is ADDITIONAL, never replacing. The portal is somebody else's
|
|
93
|
+
* product: it answers in prose today and may answer structured tomorrow, and a structured answer
|
|
94
|
+
* must keep winning on the day it arrives beside a human sentence.
|
|
95
|
+
*/
|
|
96
|
+
function rows(answer) {
|
|
97
|
+
const candidates = [answer.structuredContent];
|
|
98
|
+
for (const block of answer.content) {
|
|
99
|
+
if (typeof block === "object" && block !== null && "text" in block) {
|
|
100
|
+
const text = block.text;
|
|
101
|
+
if (typeof text !== "string")
|
|
102
|
+
continue;
|
|
103
|
+
try {
|
|
104
|
+
candidates.push(JSON.parse(text));
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
// A text block that is not JSON may still be the directory — see `proseRows` above. It is
|
|
108
|
+
// not fed to the JSON readings here, and it does not make them fail either: the portal may
|
|
109
|
+
// well add a human sentence beside the payload.
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
for (const candidate of candidates) {
|
|
114
|
+
const bare = DirectoryRows.safeParse(candidate);
|
|
115
|
+
if (bare.success)
|
|
116
|
+
return bare.data;
|
|
117
|
+
const wrapped = DirectoryEnvelope.safeParse(candidate);
|
|
118
|
+
if (wrapped.success)
|
|
119
|
+
return wrapped.data.servers;
|
|
120
|
+
}
|
|
121
|
+
return proseRows(answer);
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* ⚠️ The rule itself — longest match against declared handles, never a cut at an underscore — is
|
|
125
|
+
* `serverOf` in the contract (`src/contract`), because the tools screen has to read a name exactly the
|
|
126
|
+
* way the delegation does (#212). What stays here is what the rule is USED for: a tool whose prefix
|
|
127
|
+
* matches no declared handle belongs to no server and can never be delegated, which is what keeps
|
|
128
|
+
* the portal's own management tools (`portal_list_servers`, `portal_toggle_servers`) out of an
|
|
129
|
+
* agent's reach — the portal does not list itself as an upstream server.
|
|
130
|
+
*/
|
|
131
|
+
/**
|
|
132
|
+
* ⚠️ A handle that would carry the portal's OWN tools is never a server anybody can be given.
|
|
133
|
+
*
|
|
134
|
+
* The portal does not list itself as an upstream, so in practice `portal` never appears in the
|
|
135
|
+
* directory — but "in practice" is not a guard. One row called `portal` (an upstream a customer
|
|
136
|
+
* happens to name that way, a future portal that describes itself) would make `portal_toggle_servers`
|
|
137
|
+
* a delegable tool, and an agent could then switch its delegator's MCP servers on and off. The rule
|
|
138
|
+
* is written as a question about reach rather than as a blocklist of one string: if delegating this
|
|
139
|
+
* handle would hand over `portal_list_servers`, the handle is refused.
|
|
140
|
+
*/
|
|
141
|
+
function ownsTheDirectory(handle) {
|
|
142
|
+
return ServerDirectoryTool.startsWith(`${handle}_`);
|
|
143
|
+
}
|
|
144
|
+
export function toolServersFrom(input) {
|
|
145
|
+
const parsed = rows(input.directory);
|
|
146
|
+
if (parsed === null)
|
|
147
|
+
return null;
|
|
148
|
+
// `id` and `name` are both offered as candidates because the portal's directory names them
|
|
149
|
+
// separately and only one of them is the namespace. The live list decides which.
|
|
150
|
+
const display = new Map();
|
|
151
|
+
const offerable = new Set();
|
|
152
|
+
for (const row of parsed) {
|
|
153
|
+
for (const candidate of [row.name, row.id]) {
|
|
154
|
+
if (candidate === undefined || candidate.length === 0)
|
|
155
|
+
continue;
|
|
156
|
+
if (ownsTheDirectory(candidate))
|
|
157
|
+
continue;
|
|
158
|
+
if (!display.has(candidate))
|
|
159
|
+
display.set(candidate, row.name ?? row.id ?? candidate);
|
|
160
|
+
if (row.enabled !== false)
|
|
161
|
+
offerable.add(candidate);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
const counts = new Map();
|
|
165
|
+
for (const toolName of input.toolNames) {
|
|
166
|
+
const handle = serverOf(toolName, display.keys());
|
|
167
|
+
if (handle === null || !offerable.has(handle))
|
|
168
|
+
continue;
|
|
169
|
+
counts.set(handle, (counts.get(handle) ?? 0) + 1);
|
|
170
|
+
}
|
|
171
|
+
return {
|
|
172
|
+
declared: [...display.keys()],
|
|
173
|
+
servers: [...counts.entries()]
|
|
174
|
+
.map(([handle, toolCount]) => ({ handle, name: display.get(handle) ?? handle, toolCount }))
|
|
175
|
+
.sort((a, b) => a.handle.localeCompare(b.handle)),
|
|
176
|
+
};
|
|
177
|
+
}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { IntelError } from "../shared/intel-error/intel-error.js";
|
|
2
|
+
import { reportUnexpectedError } from "../shared/report-unexpected-error/report-unexpected-error.js";
|
|
3
|
+
import { ServerDirectoryTool, toolServersFrom, } from "./tool-servers/tool-servers.js";
|
|
4
|
+
const CallTimeoutMs = 15_000;
|
|
5
|
+
const MaxTools = 1_000;
|
|
6
|
+
const MaxResultBytes = 1_000_000;
|
|
7
|
+
// Refresh slightly early so a call cannot start with a token that expires mid-flight.
|
|
8
|
+
const RefreshWindowMs = 30_000;
|
|
9
|
+
/*
|
|
10
|
+
* ⚠️ Until #390 this file answered two kinds of caller: a person, and an agent acting on somebody
|
|
11
|
+
* else's portal connection (D30). The second one is gone with the agents, and with it the
|
|
12
|
+
* delegation lookup, the cut against the portal's server directory, and the audit row that named
|
|
13
|
+
* both principals. What is left is the rule the delegation was an exception to: the token is read
|
|
14
|
+
* per actor and never shared — one operator token for everybody would make every catalog the same
|
|
15
|
+
* one and the portal's Access policies decorative (ADR-0003).
|
|
16
|
+
*/
|
|
17
|
+
export function createTools(deps) {
|
|
18
|
+
function portal() {
|
|
19
|
+
if (!deps.portalUrl || !deps.sourceAllowed(deps.portalUrl)) {
|
|
20
|
+
throw new IntelError(503, "portal_not_configured", "No MCP portal is configured for this deployment");
|
|
21
|
+
}
|
|
22
|
+
return deps.portalUrl;
|
|
23
|
+
}
|
|
24
|
+
// Authorization for tools lives entirely in the portal, so "may this user act" reduces to "does
|
|
25
|
+
// this user have a usable portal token".
|
|
26
|
+
async function accessToken(actor) {
|
|
27
|
+
const stored = await deps.tokens.read(actor.id);
|
|
28
|
+
if (!stored) {
|
|
29
|
+
throw new IntelError(401, "portal_not_connected", "The portal has not signed this user in yet");
|
|
30
|
+
}
|
|
31
|
+
if (stored.expiresAt > deps.now().getTime() + RefreshWindowMs)
|
|
32
|
+
return stored.accessToken;
|
|
33
|
+
const refreshed = stored.refreshToken ? await deps.refresh(stored) : null;
|
|
34
|
+
if (!refreshed) {
|
|
35
|
+
// Losing a connection is a visible event for the person and an invisible one for everybody
|
|
36
|
+
// else: the screen simply asks them to sign in again, and no trace says why. The two reasons
|
|
37
|
+
// need telling apart — a portal that issues no refresh token at all is a configuration
|
|
38
|
+
// problem, a refresh that gets rejected is a different one (#97).
|
|
39
|
+
reportUnexpectedError(new Error(`portal token dropped: ${stored.refreshToken ? "refresh was rejected" : "no refresh token was ever issued"}`));
|
|
40
|
+
// A token that cannot be renewed is dropped: leaving it would keep failing every call with a
|
|
41
|
+
// stale credential. The browser answers this by signing in silently again (#60); an MCP
|
|
42
|
+
// client sees the code and repeats its own authorization.
|
|
43
|
+
await deps.tokens.clear(actor.id);
|
|
44
|
+
throw new IntelError(401, "portal_reconnect_required", "The portal sign-in for this user has expired");
|
|
45
|
+
}
|
|
46
|
+
await deps.tokens.write(actor.id, refreshed);
|
|
47
|
+
return refreshed.accessToken;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* ⚠️ Whatever the portal answers, the caller hears which side is at fault — never a bare 500.
|
|
51
|
+
*
|
|
52
|
+
* Measured against the live installation (#308): adding ONE server to the portal made
|
|
53
|
+
* `/api/v1/tools` answer `500 internal_error`, and with it every tool of every OTHER server
|
|
54
|
+
* disappeared — an empty tools screen, an agent profile that can offer nothing, and no hint
|
|
55
|
+
* anywhere which of the four servers caused it. The failure is total by nature (there is one
|
|
56
|
+
* `tools/list` for the whole portal, and the MCP client either parses it or does not), but the
|
|
57
|
+
* SILENCE was ours: the throw travelled up unhandled.
|
|
58
|
+
*
|
|
59
|
+
* `tool_catalog_unreadable` is therefore not a nicer word for the same outage. It is the
|
|
60
|
+
* difference between "Intel is broken" and "the portal answered something Intel could not read,
|
|
61
|
+
* and the reason is in the log" — the same rule `adapters/json-schema` learned the hard way in
|
|
62
|
+
* #229, where a catch-all that renamed the failure hid it for weeks.
|
|
63
|
+
*/
|
|
64
|
+
async function remoteTools(actor) {
|
|
65
|
+
const token = await accessToken(actor);
|
|
66
|
+
const remote = await deps.remote
|
|
67
|
+
.list(portal(), token, AbortSignal.timeout(CallTimeoutMs))
|
|
68
|
+
.catch((error) => {
|
|
69
|
+
// An IntelError on this path is a decision somebody already made (an expired sign-in, a
|
|
70
|
+
// refusal). Renaming it would throw away the one message that names the actual cause.
|
|
71
|
+
if (error instanceof IntelError)
|
|
72
|
+
throw error;
|
|
73
|
+
reportUnexpectedError(error);
|
|
74
|
+
// ⚠️ Deliberately says nothing about WHY. This one catch covers a portal that answered
|
|
75
|
+
// something unparseable, a portal that did not answer at all (the deadline above), and a
|
|
76
|
+
// portal that refused — and a message naming one of them would be wrong for the other two.
|
|
77
|
+
// That is the #229 mistake in miniature: a sentence more specific than the evidence sends
|
|
78
|
+
// the reader looking in the wrong place. The evidence is in the log.
|
|
79
|
+
throw new IntelError(502, "tool_catalog_unreadable", "The MCP portal's tool list could not be read; the reason was logged");
|
|
80
|
+
});
|
|
81
|
+
if (remote.length > MaxTools) {
|
|
82
|
+
throw new IntelError(502, "tool_catalog_too_large", "The portal returned too many tools");
|
|
83
|
+
}
|
|
84
|
+
return remote;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* The servers the portal says this connection reaches, confirmed against its live tool list.
|
|
88
|
+
*
|
|
89
|
+
* ⚠️ Fails rather than answering "none" when the portal keeps no directory. An empty list would
|
|
90
|
+
* be indistinguishable from "you have no servers", and the difference decides whether a screen
|
|
91
|
+
* says "connect something" or "this portal cannot be delegated from".
|
|
92
|
+
*/
|
|
93
|
+
async function serverList(actor, toolNames) {
|
|
94
|
+
if (!toolNames.includes(ServerDirectoryTool)) {
|
|
95
|
+
throw new IntelError(502, "tool_servers_unavailable", `The portal does not offer ${ServerDirectoryTool}, so its servers cannot be listed`);
|
|
96
|
+
}
|
|
97
|
+
const answer = await deps.remote.call({
|
|
98
|
+
url: portal(),
|
|
99
|
+
accessToken: await accessToken(actor),
|
|
100
|
+
name: ServerDirectoryTool,
|
|
101
|
+
arguments: {},
|
|
102
|
+
signal: AbortSignal.timeout(CallTimeoutMs),
|
|
103
|
+
});
|
|
104
|
+
const directory = answer.isError ? null : toolServersFrom({ directory: answer, toolNames });
|
|
105
|
+
if (directory === null) {
|
|
106
|
+
throw new IntelError(502, "tool_servers_unavailable", "The portal did not answer with a server list this Intel can read");
|
|
107
|
+
}
|
|
108
|
+
return directory;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* One live tools/list is both the catalog and the authorization answer: the portal only returns
|
|
112
|
+
* what this user may reach. Nothing here is cached as a permission.
|
|
113
|
+
*/
|
|
114
|
+
async function capabilities(actor) {
|
|
115
|
+
const remote = await remoteTools(actor);
|
|
116
|
+
const items = [];
|
|
117
|
+
for (const tool of remote) {
|
|
118
|
+
items.push({
|
|
119
|
+
...tool,
|
|
120
|
+
fingerprint: await deps.fingerprint({
|
|
121
|
+
name: tool.name,
|
|
122
|
+
inputSchema: tool.inputSchema,
|
|
123
|
+
outputSchema: tool.outputSchema,
|
|
124
|
+
annotations: tool.annotations,
|
|
125
|
+
}),
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
return items;
|
|
129
|
+
}
|
|
130
|
+
async function requireCapability(actor, name) {
|
|
131
|
+
const found = (await capabilities(actor)).find((capability) => capability.name === name);
|
|
132
|
+
if (!found) {
|
|
133
|
+
throw new IntelError(404, "tool_not_available", `Tool ${name} is not available to you`);
|
|
134
|
+
}
|
|
135
|
+
return found;
|
|
136
|
+
}
|
|
137
|
+
async function call(actor, input) {
|
|
138
|
+
if (!actor.canExecute) {
|
|
139
|
+
throw new IntelError(403, "tool_execute_forbidden", "Tool execution permission is required");
|
|
140
|
+
}
|
|
141
|
+
const capability = await requireCapability(actor, input.name);
|
|
142
|
+
const validation = deps.validate(capability.inputSchema, input.arguments);
|
|
143
|
+
if (!validation.valid) {
|
|
144
|
+
throw new IntelError(400, "tool_arguments_invalid", validation.detail ?? "Invalid arguments");
|
|
145
|
+
}
|
|
146
|
+
const result = await deps.remote.call({
|
|
147
|
+
url: portal(),
|
|
148
|
+
accessToken: await accessToken(actor),
|
|
149
|
+
name: capability.name,
|
|
150
|
+
arguments: input.arguments,
|
|
151
|
+
signal: AbortSignal.timeout(CallTimeoutMs),
|
|
152
|
+
});
|
|
153
|
+
if (new TextEncoder().encode(JSON.stringify(result)).byteLength > MaxResultBytes) {
|
|
154
|
+
throw new IntelError(502, "tool_result_too_large", "Tool result exceeded the Intel limit");
|
|
155
|
+
}
|
|
156
|
+
return result;
|
|
157
|
+
}
|
|
158
|
+
// A token that is gone or beyond renewal is the same answer as never having signed in, so reading
|
|
159
|
+
// a catalog reports it as a state. Only a portal that does not answer stays an error — the view
|
|
160
|
+
// has to tell "sign in again" apart from "the portal failed", and a 401 here would otherwise look
|
|
161
|
+
// like an expired Intel session to the browser.
|
|
162
|
+
function disconnected(error) {
|
|
163
|
+
return (error instanceof IntelError &&
|
|
164
|
+
(error.code === "portal_not_connected" || error.code === "portal_reconnect_required"));
|
|
165
|
+
}
|
|
166
|
+
return {
|
|
167
|
+
async catalog(actor) {
|
|
168
|
+
const stored = await deps.tokens.read(actor.id);
|
|
169
|
+
// No portal sign-in yet is a normal state, not an error: the browser answers it by running
|
|
170
|
+
// the silent sign-in and asking again (#60).
|
|
171
|
+
if (!stored)
|
|
172
|
+
return { portalConnected: false, items: [] };
|
|
173
|
+
try {
|
|
174
|
+
return { portalConnected: true, items: await capabilities(actor) };
|
|
175
|
+
}
|
|
176
|
+
catch (error) {
|
|
177
|
+
if (disconnected(error))
|
|
178
|
+
return { portalConnected: false, items: [] };
|
|
179
|
+
throw error;
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
async servers(actor) {
|
|
183
|
+
const stored = await deps.tokens.read(actor.id);
|
|
184
|
+
if (!stored)
|
|
185
|
+
return { portalConnected: false, items: [] };
|
|
186
|
+
try {
|
|
187
|
+
const remote = await remoteTools(actor);
|
|
188
|
+
const { servers } = await serverList(actor, remote.map((tool) => tool.name));
|
|
189
|
+
return { portalConnected: true, items: servers };
|
|
190
|
+
}
|
|
191
|
+
catch (error) {
|
|
192
|
+
if (disconnected(error))
|
|
193
|
+
return { portalConnected: false, items: [] };
|
|
194
|
+
throw error;
|
|
195
|
+
}
|
|
196
|
+
},
|
|
197
|
+
execute: async (actor, input) => await call(actor, input),
|
|
198
|
+
async test(actor, input) {
|
|
199
|
+
const capability = await requireCapability(actor, input.name);
|
|
200
|
+
if (capability.annotations.readOnlyHint !== true ||
|
|
201
|
+
capability.annotations.destructiveHint === true) {
|
|
202
|
+
throw new IntelError(409, "tool_test_unsafe", "Only explicitly read-only, non-destructive tools can run in the test surface");
|
|
203
|
+
}
|
|
204
|
+
return await call(actor, input);
|
|
205
|
+
},
|
|
206
|
+
};
|
|
207
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { ExecuteToolInput, TestToolInput, ToolCapability, ToolCatalog, ToolServerCatalog, ToolTestResult } from "../contract/tool.js";
|
|
2
|
+
export interface ToolActor {
|
|
3
|
+
id: string;
|
|
4
|
+
email: string;
|
|
5
|
+
canExecute: boolean;
|
|
6
|
+
}
|
|
7
|
+
export interface PortalToken {
|
|
8
|
+
accessToken: string;
|
|
9
|
+
refreshToken: string | null;
|
|
10
|
+
expiresAt: number;
|
|
11
|
+
issuer: string;
|
|
12
|
+
clientId: string;
|
|
13
|
+
resource: string;
|
|
14
|
+
}
|
|
15
|
+
export interface PortalTokenStore {
|
|
16
|
+
read(userId: string): Promise<PortalToken | null>;
|
|
17
|
+
write(userId: string, token: PortalToken): Promise<void>;
|
|
18
|
+
clear(userId: string): Promise<void>;
|
|
19
|
+
}
|
|
20
|
+
export interface RemoteTool {
|
|
21
|
+
name: string;
|
|
22
|
+
title: string | null;
|
|
23
|
+
description: string | null;
|
|
24
|
+
inputSchema: Record<string, unknown>;
|
|
25
|
+
outputSchema: Record<string, unknown> | null;
|
|
26
|
+
annotations: ToolCapability["annotations"];
|
|
27
|
+
}
|
|
28
|
+
export interface RemoteTools {
|
|
29
|
+
list(url: string, accessToken: string, signal: AbortSignal): Promise<RemoteTool[]>;
|
|
30
|
+
call(input: {
|
|
31
|
+
url: string;
|
|
32
|
+
accessToken: string;
|
|
33
|
+
name: string;
|
|
34
|
+
arguments: Record<string, unknown>;
|
|
35
|
+
signal: AbortSignal;
|
|
36
|
+
}): Promise<ToolTestResult>;
|
|
37
|
+
}
|
|
38
|
+
export interface ToolDeps {
|
|
39
|
+
portalUrl: string | null;
|
|
40
|
+
remote: RemoteTools;
|
|
41
|
+
tokens: PortalTokenStore;
|
|
42
|
+
refresh(token: PortalToken): Promise<PortalToken | null>;
|
|
43
|
+
now(): Date;
|
|
44
|
+
fingerprint(value: unknown): Promise<string>;
|
|
45
|
+
sourceAllowed(url: string): boolean;
|
|
46
|
+
validate(schema: Record<string, unknown>, value: unknown): {
|
|
47
|
+
valid: boolean;
|
|
48
|
+
detail?: string;
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
export interface ToolService {
|
|
52
|
+
catalog(actor: ToolActor): Promise<ToolCatalog>;
|
|
53
|
+
servers(actor: ToolActor): Promise<ToolServerCatalog>;
|
|
54
|
+
execute(actor: ToolActor, input: ExecuteToolInput): Promise<ToolTestResult>;
|
|
55
|
+
test(actor: ToolActor, input: TestToolInput): Promise<ToolTestResult>;
|
|
56
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
|
2
|
+
<title>Customer Intel</title>
|
|
3
|
+
<rect width="32" height="32" rx="8" fill="oklch(0.5 0.18 250)"/>
|
|
4
|
+
<path d="M10 10h12v12H10zM7 14h3m12 0h3M14 7v3m4-3v3M14 22v3m4-3v3" fill="none" stroke="white" stroke-width="2" stroke-linecap="round"/>
|
|
5
|
+
</svg>
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
|
2
|
+
<title>Customer Intel</title>
|
|
3
|
+
<rect width="32" height="32" rx="8" fill="oklch(0.5 0.18 250)"/>
|
|
4
|
+
<path d="M10 10h12v12H10zM7 14h3m12 0h3M14 7v3m4-3v3M14 22v3m4-3v3" fill="none" stroke="white" stroke-width="2" stroke-linecap="round"/>
|
|
5
|
+
</svg>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
--radius: 0.75rem;
|
|
3
|
+
--primary: oklch(0.5 0.18 250);
|
|
4
|
+
--primary-foreground: oklch(0.98 0.01 250);
|
|
5
|
+
--sidebar-primary: oklch(0.5 0.18 250);
|
|
6
|
+
--sidebar-primary-foreground: oklch(0.98 0.01 250);
|
|
7
|
+
--ring: oklch(0.62 0.14 250);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.dark {
|
|
11
|
+
--primary: oklch(0.72 0.14 250);
|
|
12
|
+
--primary-foreground: oklch(0.16 0.03 250);
|
|
13
|
+
--sidebar-primary: oklch(0.72 0.14 250);
|
|
14
|
+
--sidebar-primary-foreground: oklch(0.16 0.03 250);
|
|
15
|
+
--ring: oklch(0.68 0.13 250);
|
|
16
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
GATE_URL=http://localhost:8787
|
|
2
|
+
INTEL_URL=http://localhost:8788
|
|
3
|
+
MCP_PORTAL_URL=https://portal.example.com/mcp
|
|
4
|
+
TOOL_SOURCE_ORIGINS=https://portal.example.com
|
|
5
|
+
ALLOW_INSECURE_OAUTH=true
|
|
6
|
+
GATE_SERVICE_KEY=replace-with-local-gate-service-key
|
|
7
|
+
INTEL_SESSION_SECRET=replace-with-at-least-32-random-bytes
|