@malloy-publisher/server 0.0.235 → 0.0.237
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app/api-doc.yaml +83 -27
- package/dist/app/assets/{EnvironmentPage-BsAnavYN.js → EnvironmentPage-DaP6CpgK.js} +1 -1
- package/dist/app/assets/{HomePage-CADE138j.js → HomePage-C9yNw3iu.js} +1 -1
- package/dist/app/assets/{LightMode-Cfh7KzN8.js → LightMode-Be4-G-dX.js} +1 -1
- package/dist/app/assets/{MainPage-CO3pRlnV.js → MainPage-J_rU2SgI.js} +2 -2
- package/dist/app/assets/{MaterializationsPage-p9YjkRXZ.js → MaterializationsPage-DHgPI4MO.js} +1 -1
- package/dist/app/assets/ModelPage-BzXfjTu5.js +1 -0
- package/dist/app/assets/{PackagePage-e4kN75YR.js → PackagePage-ByoSTLJ4.js} +1 -1
- package/dist/app/assets/RouteError-CrSeKN2r.js +1 -0
- package/dist/app/assets/{ThemeEditorPage-CciagFTq.js → ThemeEditorPage-Btxnmhtk.js} +1 -1
- package/dist/app/assets/{WorkbookPage-DNWmkCXa.js → WorkbookPage-D6JLPnWO.js} +1 -1
- package/dist/app/assets/{core-Rj_4rRnA.es-BZyvITuO.js → core-BSRgt1mG.es-Bc_D2Lq4.js} +1 -1
- package/dist/app/assets/{index-CH2AcDzc.js → index-BRijWj6K.js} +15 -15
- package/dist/app/assets/{index-DQa463gC.js → index-BX4D94xw.js} +1 -1
- package/dist/app/assets/{index-VBbcc8s6.js → index-Cvy0xtb7.js} +1 -1
- package/dist/app/assets/{index-DQpV7MyA.js → index-DMAhIJNC.js} +1 -1
- package/dist/app/index.html +1 -1
- package/dist/package_load_worker.mjs +59 -20
- package/dist/server.mjs +1342 -616
- package/package.json +8 -2
- package/.eslintrc.json +0 -44
- package/.prettierignore +0 -1
- package/.prettierrc +0 -4
- package/build.ts +0 -97
- package/dist/app/assets/ModelPage-C1OSTv-x.js +0 -1
- package/dist/app/assets/RouteError-CzbfOkng.js +0 -1
- package/eslint.config.mjs +0 -76
- package/k6-tests/README.md +0 -144
- package/k6-tests/bun.lock +0 -797
- package/k6-tests/load-test/load-test-crud-connections.ts +0 -263
- package/k6-tests/load-test/load-test-crud-packages.ts +0 -461
- package/k6-tests/load-test/load-test-crud-projects.ts +0 -160
- package/k6-tests/load-test/load-test-crud.ts +0 -142
- package/k6-tests/load-test/load-test.ts +0 -784
- package/k6-tests/package.json +0 -27
- package/k6-tests/scripts/run-with-otel.sh +0 -50
- package/k6-tests/smoke-test/smoke-test.ts +0 -158
- package/k6-tests/tsconfig.json +0 -29
- package/k6-tests/utils/client_factory.ts +0 -128
- package/k6-tests/utils/common.ts +0 -489
- package/k6-tests/utils/logger.ts +0 -62
- package/k6-tests/utils/types.d.ts +0 -35
- package/malloy_mcp.dxt +0 -0
- package/publisher.config.example.bigquery.json +0 -33
- package/publisher.config.example.duckdb.json +0 -23
- package/publisher.config.json +0 -23
- package/scripts/bake-duckdb-extensions.js +0 -110
- package/src/config.spec.ts +0 -1535
- package/src/config.theme.spec.ts +0 -178
- package/src/config.ts +0 -1142
- package/src/constants.ts +0 -105
- package/src/controller/compile.controller.ts +0 -40
- package/src/controller/connection.controller.spec.ts +0 -849
- package/src/controller/connection.controller.ts +0 -901
- package/src/controller/database.controller.ts +0 -24
- package/src/controller/materialization.controller.spec.ts +0 -293
- package/src/controller/materialization.controller.ts +0 -319
- package/src/controller/model.controller.ts +0 -164
- package/src/controller/package.controller.spec.ts +0 -273
- package/src/controller/package.controller.ts +0 -306
- package/src/controller/query.controller.ts +0 -152
- package/src/controller/theme.controller.ts +0 -83
- package/src/controller/watch-mode.controller.ts +0 -243
- package/src/data_styles.ts +0 -71
- package/src/default-publisher.config.json +0 -23
- package/src/ducklake_version.spec.ts +0 -163
- package/src/ducklake_version.ts +0 -153
- package/src/errors.spec.ts +0 -119
- package/src/errors.ts +0 -237
- package/src/filter_deprecation.spec.ts +0 -64
- package/src/filter_deprecation.ts +0 -42
- package/src/health.spec.ts +0 -123
- package/src/health.ts +0 -233
- package/src/heap_check.spec.ts +0 -144
- package/src/heap_check.ts +0 -144
- package/src/instrumentation.ts +0 -214
- package/src/json_utils.spec.ts +0 -51
- package/src/json_utils.ts +0 -33
- package/src/logger.spec.ts +0 -210
- package/src/logger.ts +0 -251
- package/src/malloy_pin_prereqs.spec.ts +0 -25
- package/src/materialization_metrics.spec.ts +0 -169
- package/src/materialization_metrics.ts +0 -346
- package/src/mcp/error_messages.spec.ts +0 -35
- package/src/mcp/error_messages.ts +0 -183
- package/src/mcp/handler_utils.spec.ts +0 -108
- package/src/mcp/handler_utils.ts +0 -251
- package/src/mcp/mcp_constants.ts +0 -6
- package/src/mcp/query_envelope.spec.ts +0 -229
- package/src/mcp/query_envelope.ts +0 -240
- package/src/mcp/server.protocol.spec.ts +0 -250
- package/src/mcp/server.ts +0 -87
- package/src/mcp/skills/build_skills_bundle.spec.ts +0 -51
- package/src/mcp/skills/build_skills_bundle.ts +0 -176
- package/src/mcp/skills/skills_bundle.json +0 -1
- package/src/mcp/skills/skills_bundle.spec.ts +0 -171
- package/src/mcp/tool_response.spec.ts +0 -108
- package/src/mcp/tool_response.ts +0 -138
- package/src/mcp/tools/compile_tool.spec.ts +0 -315
- package/src/mcp/tools/compile_tool.ts +0 -208
- package/src/mcp/tools/docs_search/build_docs_index.ts +0 -132
- package/src/mcp/tools/docs_search/malloy_docs_index.json +0 -1
- package/src/mcp/tools/docs_search_tool.spec.ts +0 -32
- package/src/mcp/tools/docs_search_tool.ts +0 -128
- package/src/mcp/tools/embedding_index.spec.ts +0 -1236
- package/src/mcp/tools/embedding_index.ts +0 -808
- package/src/mcp/tools/execute_query_tool.spec.ts +0 -294
- package/src/mcp/tools/execute_query_tool.ts +0 -316
- package/src/mcp/tools/get_context_eval.ts +0 -275
- package/src/mcp/tools/get_context_tool.spec.ts +0 -592
- package/src/mcp/tools/get_context_tool.ts +0 -585
- package/src/mcp/tools/reload_package_tool.spec.ts +0 -232
- package/src/mcp/tools/reload_package_tool.ts +0 -132
- package/src/mcp_config.spec.ts +0 -919
- package/src/mcp_config.ts +0 -425
- package/src/oom_guards.integration.spec.ts +0 -269
- package/src/package_load/package_load_pool.spec.ts +0 -252
- package/src/package_load/package_load_pool.ts +0 -931
- package/src/package_load/package_load_worker.ts +0 -1034
- package/src/package_load/protocol.ts +0 -364
- package/src/package_load/rpc_wait_accountant.spec.ts +0 -109
- package/src/package_load/rpc_wait_accountant.ts +0 -76
- package/src/package_load_metrics.spec.ts +0 -114
- package/src/package_load_metrics.ts +0 -127
- package/src/path_safety.spec.ts +0 -158
- package/src/path_safety.ts +0 -146
- package/src/pg_helpers.spec.ts +0 -223
- package/src/pg_helpers.ts +0 -52
- package/src/query_cap_metrics.spec.ts +0 -89
- package/src/query_cap_metrics.ts +0 -116
- package/src/query_concurrency.spec.ts +0 -247
- package/src/query_concurrency.ts +0 -237
- package/src/query_metadata_metrics.ts +0 -49
- package/src/query_param_utils.ts +0 -29
- package/src/query_timeout.spec.ts +0 -224
- package/src/query_timeout.ts +0 -179
- package/src/runtime/publisher.js +0 -328
- package/src/server-old.ts +0 -988
- package/src/server.ts +0 -2059
- package/src/service/annotations.spec.ts +0 -118
- package/src/service/annotations.ts +0 -91
- package/src/service/authorize.spec.ts +0 -154
- package/src/service/authorize.ts +0 -498
- package/src/service/authorize_integration.spec.ts +0 -2041
- package/src/service/build_plan.spec.ts +0 -685
- package/src/service/build_plan.ts +0 -797
- package/src/service/compile_authorize.spec.ts +0 -85
- package/src/service/compile_fragment_techniques.spec.ts +0 -156
- package/src/service/connection.spec.ts +0 -2360
- package/src/service/connection.ts +0 -2187
- package/src/service/connection_config.spec.ts +0 -734
- package/src/service/connection_config.ts +0 -899
- package/src/service/connection_federation.spec.ts +0 -184
- package/src/service/connection_fingerprint.spec.ts +0 -102
- package/src/service/connection_service.spec.ts +0 -711
- package/src/service/connection_service.ts +0 -291
- package/src/service/cron_evaluator.spec.ts +0 -79
- package/src/service/cron_evaluator.ts +0 -105
- package/src/service/db_utils.spec.ts +0 -1085
- package/src/service/db_utils.ts +0 -1502
- package/src/service/duckdb_instance_isolation.spec.ts +0 -137
- package/src/service/ducklake_lazy_attach.spec.ts +0 -110
- package/src/service/embedding_provider.spec.ts +0 -329
- package/src/service/embedding_provider.ts +0 -236
- package/src/service/environment.ts +0 -2191
- package/src/service/environment_admission.spec.ts +0 -344
- package/src/service/environment_compile.spec.ts +0 -197
- package/src/service/environment_store.spec.ts +0 -2274
- package/src/service/environment_store.ts +0 -2434
- package/src/service/environment_store_anchoring.spec.ts +0 -107
- package/src/service/environment_store_clone.spec.ts +0 -350
- package/src/service/explore_visibility.spec.ts +0 -434
- package/src/service/exports_probe.spec.ts +0 -107
- package/src/service/extension_fetch_policy.spec.ts +0 -256
- package/src/service/filter.spec.ts +0 -458
- package/src/service/filter.ts +0 -341
- package/src/service/filter_bypass.spec.ts +0 -418
- package/src/service/filter_integration.spec.ts +0 -1004
- package/src/service/freshness.spec.ts +0 -183
- package/src/service/freshness.ts +0 -112
- package/src/service/gcs_s3_utils.ts +0 -427
- package/src/service/given.ts +0 -105
- package/src/service/givens_integration.spec.ts +0 -219
- package/src/service/manifest_loader.spec.ts +0 -189
- package/src/service/manifest_loader.ts +0 -156
- package/src/service/materialization_build_session.spec.ts +0 -435
- package/src/service/materialization_build_session.ts +0 -681
- package/src/service/materialization_config_validation.spec.ts +0 -99
- package/src/service/materialization_config_validation.ts +0 -120
- package/src/service/materialization_eligibility.spec.ts +0 -158
- package/src/service/materialization_eligibility.ts +0 -305
- package/src/service/materialization_schedule_surface.spec.ts +0 -256
- package/src/service/materialization_scheduler.spec.ts +0 -289
- package/src/service/materialization_scheduler.ts +0 -304
- package/src/service/materialization_serve_transform.spec.ts +0 -1003
- package/src/service/materialization_serve_transform.ts +0 -779
- package/src/service/materialization_service.spec.ts +0 -2373
- package/src/service/materialization_service.ts +0 -2342
- package/src/service/materialization_test_fixtures.ts +0 -249
- package/src/service/model.spec.ts +0 -1157
- package/src/service/model.ts +0 -3370
- package/src/service/model_limits.spec.ts +0 -209
- package/src/service/model_limits.ts +0 -131
- package/src/service/model_storage_serve.spec.ts +0 -193
- package/src/service/model_storage_serve_joins.spec.ts +0 -193
- package/src/service/package.spec.ts +0 -612
- package/src/service/package.ts +0 -1799
- package/src/service/package_manifest.spec.ts +0 -230
- package/src/service/package_manifest.ts +0 -241
- package/src/service/package_memory_governor.spec.ts +0 -173
- package/src/service/package_memory_governor.ts +0 -233
- package/src/service/package_quote_bound_tables.spec.ts +0 -171
- package/src/service/package_race.spec.ts +0 -208
- package/src/service/package_reload_safety.spec.ts +0 -193
- package/src/service/package_rollback.spec.ts +0 -190
- package/src/service/package_worker_path.spec.ts +0 -645
- package/src/service/path_injection.spec.ts +0 -39
- package/src/service/persist_annotation_validation.spec.ts +0 -89
- package/src/service/persist_annotation_validation.ts +0 -59
- package/src/service/persistence_policy.spec.ts +0 -374
- package/src/service/proxy.spec.ts +0 -414
- package/src/service/proxy.ts +0 -248
- package/src/service/query_boundary.spec.ts +0 -470
- package/src/service/query_metadata.spec.ts +0 -408
- package/src/service/query_metadata.ts +0 -492
- package/src/service/query_metadata_identity.spec.ts +0 -149
- package/src/service/query_text.spec.ts +0 -79
- package/src/service/query_text.ts +0 -43
- package/src/service/quoting.spec.ts +0 -135
- package/src/service/quoting.ts +0 -78
- package/src/service/resolve_environment.ts +0 -15
- package/src/service/restricted_mode.spec.ts +0 -299
- package/src/service/source_extraction.ts +0 -225
- package/src/service/theme_store.ts +0 -199
- package/src/storage/DatabaseInterface.ts +0 -198
- package/src/storage/StorageManager.ts +0 -113
- package/src/storage/duckdb/ConnectionRepository.ts +0 -143
- package/src/storage/duckdb/DuckDBConnection.ts +0 -123
- package/src/storage/duckdb/DuckDBRepository.ts +0 -208
- package/src/storage/duckdb/EnvironmentRepository.ts +0 -148
- package/src/storage/duckdb/MaterializationRepository.spec.ts +0 -191
- package/src/storage/duckdb/MaterializationRepository.ts +0 -336
- package/src/storage/duckdb/PackageRepository.ts +0 -135
- package/src/storage/duckdb/schema.ts +0 -229
- package/src/stream_helpers.spec.ts +0 -280
- package/src/stream_helpers.ts +0 -162
- package/src/telemetry.ts +0 -20
- package/src/test_helpers/metrics_harness.ts +0 -166
- package/src/theme_key_parity.spec.ts +0 -57
- package/src/utils.ts +0 -29
- package/tests/fixtures/authorize-compile/model.malloy +0 -9
- package/tests/fixtures/authorize-compile/publisher.json +0 -4
- package/tests/fixtures/html-pages-nopublic/model.malloy +0 -1
- package/tests/fixtures/html-pages-nopublic/publisher.json +0 -5
- package/tests/fixtures/html-pages-test/data.csv +0 -3
- package/tests/fixtures/html-pages-test/public/assets/app.css +0 -3
- package/tests/fixtures/html-pages-test/public/barehtml.html +0 -4
- package/tests/fixtures/html-pages-test/public/bodymeta.html +0 -8
- package/tests/fixtures/html-pages-test/public/data.json +0 -1
- package/tests/fixtures/html-pages-test/public/fitcomment.html +0 -11
- package/tests/fixtures/html-pages-test/public/index.html +0 -9
- package/tests/fixtures/html-pages-test/public/nohead.html +0 -8
- package/tests/fixtures/html-pages-test/public/notfit.html +0 -13
- package/tests/fixtures/html-pages-test/public/slides.html +0 -12
- package/tests/fixtures/html-pages-test/public/sub/page2.html +0 -9
- package/tests/fixtures/html-pages-test/public/unterminated.html +0 -10
- package/tests/fixtures/html-pages-test/publisher.json +0 -5
- package/tests/fixtures/html-pages-test/report.malloy +0 -1
- package/tests/fixtures/persist-multi-level/data/orders.csv +0 -5
- package/tests/fixtures/persist-multi-level/multi_level.malloy +0 -18
- package/tests/fixtures/persist-multi-level/publisher.json +0 -5
- package/tests/fixtures/persist-schedule-test/data/orders.csv +0 -5
- package/tests/fixtures/persist-schedule-test/persist_schedule_test.malloy +0 -11
- package/tests/fixtures/persist-schedule-test/publisher.json +0 -7
- package/tests/fixtures/persist-schedule-warn-test/data/orders.csv +0 -5
- package/tests/fixtures/persist-schedule-warn-test/persist_warn_test.malloy +0 -11
- package/tests/fixtures/persist-schedule-warn-test/publisher.json +0 -6
- package/tests/fixtures/persist-test/data/orders.csv +0 -5
- package/tests/fixtures/persist-test/persist_test.malloy +0 -11
- package/tests/fixtures/persist-test/publisher.json +0 -5
- package/tests/fixtures/publisher.config.json +0 -15
- package/tests/fixtures/query-givens/data/orders.csv +0 -7
- package/tests/fixtures/query-givens/model.malloy +0 -34
- package/tests/fixtures/query-givens/publisher.json +0 -5
- package/tests/fixtures/xlsx/database.xlsx +0 -0
- package/tests/harness/mcp_test_setup.ts +0 -332
- package/tests/harness/rest_e2e.ts +0 -68
- package/tests/integration/authorize/compile_authorize_http.integration.spec.ts +0 -92
- package/tests/integration/concurrent_package/concurrent_package.integration.spec.ts +0 -280
- package/tests/integration/duckdb_storage/duckdb_storage.integration.spec.ts +0 -138
- package/tests/integration/first_boot/readiness_line.integration.spec.ts +0 -177
- package/tests/integration/html_pages/html_pages.integration.spec.ts +0 -440
- package/tests/integration/legacy_routes/legacy_routes.integration.spec.ts +0 -259
- package/tests/integration/materialization/environment_list.integration.spec.ts +0 -133
- package/tests/integration/materialization/freshness_gate.integration.spec.ts +0 -292
- package/tests/integration/materialization/manifest_binding.integration.spec.ts +0 -430
- package/tests/integration/materialization/materialization_lifecycle.integration.spec.ts +0 -441
- package/tests/integration/materialization/orchestrated_rebind.integration.spec.ts +0 -150
- package/tests/integration/materialization/reference_manifest.integration.spec.ts +0 -251
- package/tests/integration/materialization/schedule_edit.integration.spec.ts +0 -126
- package/tests/integration/materialization/scheduler.integration.spec.ts +0 -200
- package/tests/integration/materialization/scheduler_recovery.integration.spec.ts +0 -172
- package/tests/integration/materialization/scheduler_transitions.integration.spec.ts +0 -256
- package/tests/integration/materialization/scheduler_wiring.integration.spec.ts +0 -143
- package/tests/integration/mcp/mcp_execute_query_tool.integration.spec.ts +0 -438
- package/tests/integration/mcp/mcp_get_context_semantic.integration.spec.ts +0 -235
- package/tests/integration/mcp/mcp_transport.integration.spec.ts +0 -98
- package/tests/integration/query_givens/query_givens.integration.spec.ts +0 -146
- package/tests/integration/query_givens/query_givens_authorize.integration.spec.ts +0 -121
- package/tests/integration/sdk_givens/sdk_givens.integration.spec.ts +0 -110
- package/tests/integration/watch-mode/watch_mode.integration.spec.ts +0 -421
- package/tests/unit/controller/theme.controller.spec.ts +0 -141
- package/tests/unit/duckdb/attached_databases.test.ts +0 -1243
- package/tests/unit/duckdb/duckdb_connection.test.ts +0 -181
- package/tests/unit/duckdb/legacy_schema_migration.test.ts +0 -197
- package/tests/unit/duckdb/repositories.test.ts +0 -208
- package/tests/unit/ducklake/ducklake.test.ts +0 -952
- package/tests/unit/service/theme_store.spec.ts +0 -139
- package/tsconfig.json +0 -32
package/src/mcp_config.ts
DELETED
|
@@ -1,425 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Put this server in the host's MCP config, so an agent opened here finds the
|
|
3
|
-
* `malloy_*` tools without being told how.
|
|
4
|
-
*
|
|
5
|
-
* An MCP client only knows a server exists if something registered it: a
|
|
6
|
-
* `.mcp.json` in the directory the session starts in, or a manual
|
|
7
|
-
* `claude mcp add`. A running server is otherwise invisible to it, and starting
|
|
8
|
-
* the server first does not help, because registration and ordering are separate
|
|
9
|
-
* requirements. The repo ships a committed `.mcp.json` and the scaffolder writes
|
|
10
|
-
* one, so `npx @malloy-publisher/server` was the only way in that did not
|
|
11
|
-
* register itself, which is the only reason getting-started had to teach
|
|
12
|
-
* `claude mcp add` before a reader could ask a question.
|
|
13
|
-
*
|
|
14
|
-
* Creates only, never edits, and never reads. The file may hold other servers
|
|
15
|
-
* and their credentials, reading it can block when it is a FIFO, and a boot
|
|
16
|
-
* that rewrites it would churn version-controlled files. The scaffolder can
|
|
17
|
-
* afford to merge because it runs once at the user's request; a server boot
|
|
18
|
-
* cannot.
|
|
19
|
-
*
|
|
20
|
-
* A symlink named `.mcp.json` is never written through. `wx` (`O_EXCL`) refuses
|
|
21
|
-
* one on POSIX whether or not its target exists, but Windows resolves the
|
|
22
|
-
* reparse point first, so a DANGLING link there gets its target created. So the
|
|
23
|
-
* symlink is rejected explicitly, using the same `lstat` that reports a stale
|
|
24
|
-
* config, and `wx` stays as the race-free backstop for everything else.
|
|
25
|
-
*/
|
|
26
|
-
import * as fs from "fs";
|
|
27
|
-
import * as os from "os";
|
|
28
|
-
import * as path from "path";
|
|
29
|
-
import { parseBoolEnv } from "./config";
|
|
30
|
-
import { logger } from "./logger";
|
|
31
|
-
|
|
32
|
-
export const MCP_CONFIG_FILENAME = ".mcp.json";
|
|
33
|
-
|
|
34
|
-
/** What happened, so the caller can log it and tests can assert on it. */
|
|
35
|
-
export type McpConfigOutcome =
|
|
36
|
-
| {
|
|
37
|
-
action: "skipped-home";
|
|
38
|
-
dir: string;
|
|
39
|
-
endpoint: string;
|
|
40
|
-
staleConfig: string | undefined;
|
|
41
|
-
}
|
|
42
|
-
| {
|
|
43
|
-
action: "skipped-git";
|
|
44
|
-
dir: string;
|
|
45
|
-
/** The directory holding `.git`, which is often far above `dir`. */
|
|
46
|
-
gitRoot: string;
|
|
47
|
-
/** A `.mcp.json` already up-tree, named so the reader can go look. */
|
|
48
|
-
rootConfig: string | undefined;
|
|
49
|
-
endpoint: string;
|
|
50
|
-
staleConfig: string | undefined;
|
|
51
|
-
}
|
|
52
|
-
| {
|
|
53
|
-
action: "skipped-root";
|
|
54
|
-
dir: string;
|
|
55
|
-
endpoint: string;
|
|
56
|
-
staleConfig: string | undefined;
|
|
57
|
-
}
|
|
58
|
-
| {
|
|
59
|
-
action: "skipped-unstable-port";
|
|
60
|
-
dir: string;
|
|
61
|
-
endpoint: string;
|
|
62
|
-
requestedPort: number;
|
|
63
|
-
boundPort: number;
|
|
64
|
-
staleConfig: string | undefined;
|
|
65
|
-
}
|
|
66
|
-
| { action: "created"; file: string }
|
|
67
|
-
| { action: "exists"; file: string; endpoint: string }
|
|
68
|
-
| { action: "failed"; file: string; problem: string; endpoint: string };
|
|
69
|
-
|
|
70
|
-
/** Pinned rather than `Record<string, string>`, so `type` cannot drift. */
|
|
71
|
-
type McpServerEntry = { type: "http"; url: string };
|
|
72
|
-
|
|
73
|
-
function malloyServer(endpoint: string): McpServerEntry {
|
|
74
|
-
return { type: "http", url: endpoint };
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* The port to write, given what was asked for and what the OS handed back.
|
|
79
|
-
*
|
|
80
|
-
* Extracted from the listen callback so it can be tested: this is the piece
|
|
81
|
-
* that makes "no ports to know" true, and a silent fallback to the requested
|
|
82
|
-
* value would write a config naming a port nothing is on.
|
|
83
|
-
*/
|
|
84
|
-
export function resolveBoundPort(
|
|
85
|
-
address: ReturnType<import("net").Server["address"]>,
|
|
86
|
-
requestedPort: number,
|
|
87
|
-
): number {
|
|
88
|
-
return typeof address === "object" && address ? address.port : requestedPort;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* The host an agent on this machine should dial to reach the bound socket. A
|
|
93
|
-
* wildcard becomes the loopback literal of its own family; a specific bind
|
|
94
|
-
* address is written as-is, since when the server is bound only to some address
|
|
95
|
-
* that address is the only one that reaches it.
|
|
96
|
-
*
|
|
97
|
-
* Not `localhost`, which is ambiguous: it resolves to both `127.0.0.1` and
|
|
98
|
-
* `::1`, while the server binds exactly one family (`0.0.0.0` is the IPv4
|
|
99
|
-
* wildcard and does not cover IPv6). With the server on IPv4, `::1` is free, any
|
|
100
|
-
* local process can bind the same port there, and clients using `fetch` prefer
|
|
101
|
-
* the IPv6 answer, so that process receives the traffic. Reproduced 3 times out
|
|
102
|
-
* of 3 before this was changed.
|
|
103
|
-
*
|
|
104
|
-
* This is unambiguous naming, not isolation. On macOS a process can still bind
|
|
105
|
-
* the more specific `127.0.0.1:PORT` while this server holds the wildcard, and
|
|
106
|
-
* any attacker able to do that already runs as this user and could rewrite the
|
|
107
|
-
* file directly. `displayHost` in `service/environment_store.ts` answers the
|
|
108
|
-
* same question with `localhost`, as do the committed root `.mcp.json` and the
|
|
109
|
-
* scaffolder. Reconciling all three is its own change: one feeds a documented
|
|
110
|
-
* line scripts parse, and another ships from a different package.
|
|
111
|
-
*/
|
|
112
|
-
export function resolveClientHost(
|
|
113
|
-
address: ReturnType<import("net").Server["address"]>,
|
|
114
|
-
fallbackHost: string,
|
|
115
|
-
): string {
|
|
116
|
-
// fallbackHost is the one user-controlled input here (everything else is
|
|
117
|
-
// OS-derived), and it is only consulted on a path no runtime reaches today.
|
|
118
|
-
// Anything that is not an IP literal is discarded rather than interpolated:
|
|
119
|
-
// a hostname would reintroduce the ambiguity this function exists to remove,
|
|
120
|
-
// and a quote would break out of the quoting in addCommand, in a line the
|
|
121
|
-
// docs tell people to paste.
|
|
122
|
-
const usableFallback = /^[0-9a-fA-F:.]+$/.test(fallbackHost)
|
|
123
|
-
? fallbackHost
|
|
124
|
-
: "";
|
|
125
|
-
const fromSocket =
|
|
126
|
-
typeof address === "object" && address ? address.address : undefined;
|
|
127
|
-
// Both runtimes populate `address` for a TCP socket. This runs in a listen
|
|
128
|
-
// callback where a throw kills an already-bound server, so a missing or
|
|
129
|
-
// non-string value falls back rather than throwing, and both no-address
|
|
130
|
-
// shapes fall back the same way.
|
|
131
|
-
const raw =
|
|
132
|
-
typeof fromSocket === "string" && fromSocket
|
|
133
|
-
? fromSocket
|
|
134
|
-
: usableFallback;
|
|
135
|
-
if (raw === "0.0.0.0" || raw === "") return "127.0.0.1";
|
|
136
|
-
if (raw === "::" || raw === "::0") return "[::1]";
|
|
137
|
-
if (!raw.includes(":")) return raw;
|
|
138
|
-
// Bracket an IPv6 literal so it is a legal URL authority, and drop any zone
|
|
139
|
-
// index first: node keeps `%en0` in address() where bun strips it, and no
|
|
140
|
-
// HTTP client can dial a zone anyway, so keeping it writes a URL that fails
|
|
141
|
-
// to parse rather than one that merely cannot connect.
|
|
142
|
-
return `[${raw.split("%")[0]}]`;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/** The URL to write into the config and to print in advice. */
|
|
146
|
-
export function mcpEndpoint(host: string, port: number): string {
|
|
147
|
-
return `http://${host}:${port}/mcp`;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* Is this directory inside a git working tree?
|
|
152
|
-
*
|
|
153
|
-
* A checkout belongs to somebody, and dropping an untracked file into one is both
|
|
154
|
-
* a surprise in `git status` and a thing that gets committed by accident. A clone
|
|
155
|
-
* of this repo is the sharp case: the documented `bun run start` chdirs into
|
|
156
|
-
* `packages/server`, so without this the file landed there, untracked, and not at
|
|
157
|
-
* the root where a session actually reads it.
|
|
158
|
-
*
|
|
159
|
-
* This is deliberately blunt: one `.git` anywhere above the working directory is
|
|
160
|
-
* enough, so it also covers a user's own project, which is a normal place to run
|
|
161
|
-
* the server. That case gets no file, so the caller logs the manual command
|
|
162
|
-
* rather than leaving them with nothing.
|
|
163
|
-
*
|
|
164
|
-
* Walks up rather than shelling out to git, and treats `.git` as present whether
|
|
165
|
-
* it is a directory or the file that worktrees and submodules use.
|
|
166
|
-
*/
|
|
167
|
-
function findGitWorkTreeRoot(dir: string): string | undefined {
|
|
168
|
-
let current = path.resolve(dir);
|
|
169
|
-
for (;;) {
|
|
170
|
-
if (fs.existsSync(path.join(current, ".git"))) return current;
|
|
171
|
-
const parent = path.dirname(current);
|
|
172
|
-
if (parent === current) return undefined;
|
|
173
|
-
current = parent;
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
* Is the feature on?
|
|
179
|
-
*
|
|
180
|
-
* `parseBoolEnv`, not a bespoke set: `config.ts` already owns boolean env
|
|
181
|
-
* parsing for this server and throws on an unrecognised value, so a typo in a
|
|
182
|
-
* manifest is loud rather than silently flipping a filesystem write. An earlier
|
|
183
|
-
* version of this function invented its own spellings, including stripping
|
|
184
|
-
* surrounding quotes, which is a problem every flag here would share and so
|
|
185
|
-
* belongs in the shared helper if it belongs anywhere.
|
|
186
|
-
*/
|
|
187
|
-
export function mcpConfigEnabled(): boolean {
|
|
188
|
-
return !(parseBoolEnv("PUBLISHER_NO_MCP_CONFIG") ?? false);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
/**
|
|
192
|
-
* Create `<dir>/.mcp.json` if this directory should have one and does not.
|
|
193
|
-
*
|
|
194
|
-
* Never throws. A convenience file must not be able to stop a server booting, so
|
|
195
|
-
* every failure path returns an outcome instead.
|
|
196
|
-
*/
|
|
197
|
-
export function ensureMcpConfig(options: {
|
|
198
|
-
dir: string;
|
|
199
|
-
/** The URL an agent on this machine should dial, from `mcpEndpoint`. */
|
|
200
|
-
endpoint: string;
|
|
201
|
-
/**
|
|
202
|
-
* What was asked for, and what the OS actually gave. Required as a pair: when
|
|
203
|
-
* only one was supplied the guard below silently did nothing, which is the
|
|
204
|
-
* defect it exists to prevent.
|
|
205
|
-
*/
|
|
206
|
-
requestedPort: number;
|
|
207
|
-
boundPort: number;
|
|
208
|
-
/**
|
|
209
|
-
* The home directory to stay out of. Defaults to `os.homedir()`; injected by
|
|
210
|
-
* tests so they never depend on the developer's real one, where a regression
|
|
211
|
-
* in guard ordering would write the file instead of going red.
|
|
212
|
-
*/
|
|
213
|
-
homeDir?: string;
|
|
214
|
-
}): McpConfigOutcome {
|
|
215
|
-
const { dir, endpoint, requestedPort, boundPort, homeDir } = options;
|
|
216
|
-
|
|
217
|
-
const file = path.join(dir, MCP_CONFIG_FILENAME);
|
|
218
|
-
try {
|
|
219
|
-
// Probed before the guards, not just via EEXIST on the write. Every skip
|
|
220
|
-
// returns before writing, so without this a stale config sitting in this
|
|
221
|
-
// very directory goes unmentioned in exactly the cases where it is the
|
|
222
|
-
// most useful thing to say.
|
|
223
|
-
// lstat, not existsSync: they disagree on a dangling symlink, which is
|
|
224
|
-
// the case this module's header calls out, and the write path (via
|
|
225
|
-
// EEXIST) counts that as present. Reporting and writing must agree.
|
|
226
|
-
const existing = (():
|
|
227
|
-
| { path: string; isSymlink: boolean }
|
|
228
|
-
| undefined => {
|
|
229
|
-
try {
|
|
230
|
-
return {
|
|
231
|
-
path: file,
|
|
232
|
-
isSymlink: fs.lstatSync(file).isSymbolicLink(),
|
|
233
|
-
};
|
|
234
|
-
} catch {
|
|
235
|
-
return undefined;
|
|
236
|
-
}
|
|
237
|
-
})();
|
|
238
|
-
const staleConfig = existing?.path;
|
|
239
|
-
|
|
240
|
-
// Only write a port that will still be this port next boot. Comparing
|
|
241
|
-
// bound against requested catches every way they diverge with one
|
|
242
|
-
// condition: `--mcp_port 0` asks for any free port, and a non-numeric
|
|
243
|
-
// MCP_PORT becomes NaN, which bun binds ephemerally too (k8s injects
|
|
244
|
-
// MCP_PORT=tcp://... into every pod in a namespace with a Service named
|
|
245
|
-
// `mcp`). Create-never-edit means the first boot's file is never
|
|
246
|
-
// corrected, so a file naming a port that moves is permanently wrong.
|
|
247
|
-
if (boundPort !== requestedPort) {
|
|
248
|
-
return {
|
|
249
|
-
action: "skipped-unstable-port",
|
|
250
|
-
dir,
|
|
251
|
-
endpoint,
|
|
252
|
-
requestedPort,
|
|
253
|
-
boundPort,
|
|
254
|
-
staleConfig,
|
|
255
|
-
};
|
|
256
|
-
}
|
|
257
|
-
// Inside the try because os.homedir() throws when HOME is unset and the uid
|
|
258
|
-
// has no passwd entry, which is the ordinary distroless container shape.
|
|
259
|
-
// realpath, not resolve: `resolve` normalises `.` and `..` but not
|
|
260
|
-
// symlinks, while process.cwd() is always fully resolved. With HOME on a
|
|
261
|
-
// symlinked path (an automounted /home is the common one) the two forms
|
|
262
|
-
// never matched and the file landed in the home directory this guard
|
|
263
|
-
// exists to protect. Falls back to the unresolved path when it does not
|
|
264
|
-
// exist, since realpathSync throws on a missing directory.
|
|
265
|
-
const realish = (p: string) => {
|
|
266
|
-
try {
|
|
267
|
-
return fs.realpathSync(p);
|
|
268
|
-
} catch {
|
|
269
|
-
return path.resolve(p);
|
|
270
|
-
}
|
|
271
|
-
};
|
|
272
|
-
if (realish(dir) === realish(homeDir ?? os.homedir())) {
|
|
273
|
-
return { action: "skipped-home", dir, endpoint, staleConfig };
|
|
274
|
-
}
|
|
275
|
-
// The filesystem root is the other directory nobody opens an agent in,
|
|
276
|
-
// and it is where a process manager puts you by default: systemd gives
|
|
277
|
-
// system units a working directory of `/`.
|
|
278
|
-
if (path.resolve(dir) === path.parse(path.resolve(dir)).root) {
|
|
279
|
-
return { action: "skipped-root", dir, endpoint, staleConfig };
|
|
280
|
-
}
|
|
281
|
-
const gitRoot = findGitWorkTreeRoot(dir);
|
|
282
|
-
if (gitRoot !== undefined) {
|
|
283
|
-
// Existence only, never a read. Reported rather than acted on: it tells
|
|
284
|
-
// the reader where to look, and cannot tell us whether that file names
|
|
285
|
-
// this server or some unrelated one, so it must not decide whether to
|
|
286
|
-
// stay quiet.
|
|
287
|
-
const rootCandidate = path.join(gitRoot, MCP_CONFIG_FILENAME);
|
|
288
|
-
return {
|
|
289
|
-
action: "skipped-git",
|
|
290
|
-
dir,
|
|
291
|
-
gitRoot,
|
|
292
|
-
rootConfig: fs.existsSync(rootCandidate)
|
|
293
|
-
? rootCandidate
|
|
294
|
-
: undefined,
|
|
295
|
-
endpoint,
|
|
296
|
-
staleConfig,
|
|
297
|
-
};
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
// Refuse a symlink before attempting the write, rather than relying on
|
|
301
|
-
// `wx` to refuse it. `O_EXCL` does that on POSIX whether or not the
|
|
302
|
-
// target exists, but Windows resolves the reparse point first, so
|
|
303
|
-
// CREATE_NEW on a DANGLING link creates the target: the write-through
|
|
304
|
-
// this module exists to prevent, on the one platform where `wx` does not
|
|
305
|
-
// prevent it. Caught by the cross-platform CI run, not by reasoning.
|
|
306
|
-
// `wx` stays as the race-free backstop for the non-symlink case.
|
|
307
|
-
if (existing?.isSymlink) return { action: "exists", file, endpoint };
|
|
308
|
-
|
|
309
|
-
const body =
|
|
310
|
-
JSON.stringify(
|
|
311
|
-
{ mcpServers: { malloy: malloyServer(endpoint) } },
|
|
312
|
-
null,
|
|
313
|
-
2,
|
|
314
|
-
) + "\n";
|
|
315
|
-
fs.writeFileSync(file, body, { encoding: "utf8", flag: "wx" });
|
|
316
|
-
return { action: "created", file };
|
|
317
|
-
} catch (error) {
|
|
318
|
-
const code = (error as NodeJS.ErrnoException)?.code;
|
|
319
|
-
// EEXIST covers both an ordinary existing file and a dangling symlink, and
|
|
320
|
-
// the response to each is the same: leave it alone, unread.
|
|
321
|
-
if (code === "EEXIST") return { action: "exists", file, endpoint };
|
|
322
|
-
return {
|
|
323
|
-
action: "failed",
|
|
324
|
-
file,
|
|
325
|
-
problem: error instanceof Error ? error.message : String(error),
|
|
326
|
-
endpoint,
|
|
327
|
-
};
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
/**
|
|
332
|
-
* The command to register this server by hand, at the DEFAULT (local) scope.
|
|
333
|
-
*
|
|
334
|
-
* Not `-s user`. Claude Code resolves a duplicate name by precedence, local then
|
|
335
|
-
* project then user, and takes the winning entry whole rather than merging. Every
|
|
336
|
-
* branch that prints this either has a `.mcp.json` in the directory or may have
|
|
337
|
-
* one up-tree, and that file is project scope, so a user-scoped registration is
|
|
338
|
-
* silently shadowed by the very file that caused the message. Local outranks it
|
|
339
|
-
* and works. `-s user` still belongs in the README's separate "register once for
|
|
340
|
-
* every directory" instruction, where nothing shadows it.
|
|
341
|
-
*/
|
|
342
|
-
export function addCommand(endpoint: string): string {
|
|
343
|
-
// Quoted because an IPv6 endpoint contains brackets, which zsh (the macOS
|
|
344
|
-
// default) treats as a glob and refuses outright: `no matches found`. The
|
|
345
|
-
// docs tell the reader to paste this line as printed.
|
|
346
|
-
return `claude mcp add --transport http malloy '${endpoint}'`;
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
/**
|
|
350
|
-
* Say whether an agent opened here will find this server, and what to do when it
|
|
351
|
-
* will not.
|
|
352
|
-
*
|
|
353
|
-
* Every branch that leaves the user without a file says so at `info`, with the
|
|
354
|
-
* endpoint and the command. Noise is preferred to silence on purpose: a skip
|
|
355
|
-
* that says nothing is indistinguishable from a broken install, and `debug` is
|
|
356
|
-
* not a middle ground, because it disappears entirely at `LOG_LEVEL=info`.
|
|
357
|
-
*/
|
|
358
|
-
/** The stale-file warning, appended to whichever skip fired. */
|
|
359
|
-
function staleNote(
|
|
360
|
-
staleConfig: string | undefined,
|
|
361
|
-
alreadyNamed?: string,
|
|
362
|
-
): string {
|
|
363
|
-
// `alreadyNamed` is the git branch's rootConfig: when cwd is the repo root
|
|
364
|
-
// the two are the same path, and naming it twice reads like two files.
|
|
365
|
-
return staleConfig === undefined || staleConfig === alreadyNamed
|
|
366
|
-
? ""
|
|
367
|
-
: `. Note that ${staleConfig} is already here and was not written by this run, so an agent started here uses whatever that names.`;
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
export function logMcpConfigOutcome(outcome: McpConfigOutcome): void {
|
|
371
|
-
switch (outcome.action) {
|
|
372
|
-
case "created":
|
|
373
|
-
logger.info(
|
|
374
|
-
`Wrote ${outcome.file} so an agent started in this directory finds this server. Disable with --no-mcp-config.`,
|
|
375
|
-
);
|
|
376
|
-
return;
|
|
377
|
-
case "exists":
|
|
378
|
-
// States the endpoint rather than speculating that the file is wrong.
|
|
379
|
-
// This fires on every boot of a scaffolded package, where the file is
|
|
380
|
-
// almost always correct, so it must not read as a warning.
|
|
381
|
-
logger.info(
|
|
382
|
-
`Left the existing ${outcome.file} alone, unread. This server is at ${outcome.endpoint}. If an agent started here reaches a different Publisher than you expect, ask it to run malloy_getContext, which names the environment and packages it is actually talking to. To point it here: ${addCommand(outcome.endpoint)}`,
|
|
383
|
-
);
|
|
384
|
-
return;
|
|
385
|
-
case "failed":
|
|
386
|
-
// info, not warn: nothing is broken. The server is serving, and a
|
|
387
|
-
// convenience file was not created. On a read-only filesystem this
|
|
388
|
-
// fires on every pod of every rollout, where a warning would be picked
|
|
389
|
-
// up by alerting and read by someone who cannot act on it.
|
|
390
|
-
logger.info(
|
|
391
|
-
`Could not write ${outcome.file} (${outcome.problem}). An agent started here will not find this server on its own. To connect one, run: ${addCommand(outcome.endpoint)}`,
|
|
392
|
-
);
|
|
393
|
-
return;
|
|
394
|
-
case "skipped-git":
|
|
395
|
-
logger.info(
|
|
396
|
-
outcome.rootConfig !== undefined
|
|
397
|
-
? // Named, not acted on: it may register an unrelated server, or
|
|
398
|
-
// name a port this run is not on, and we never read it to find
|
|
399
|
-
// out. So the endpoint and the command are still given.
|
|
400
|
-
`Did not write ${MCP_CONFIG_FILENAME} into ${outcome.dir} because it is inside the git working tree at ${outcome.gitRoot}, which already has ${outcome.rootConfig}. This server is at ${outcome.endpoint}; if your agent does not list malloy, run this from the directory you start it in: ${addCommand(outcome.endpoint)}${staleNote(outcome.staleConfig, outcome.rootConfig)}`
|
|
401
|
-
: `Did not write ${MCP_CONFIG_FILENAME} into ${outcome.dir} because it is inside the git working tree at ${outcome.gitRoot}. To connect an agent, run this from the directory you start it in: ${addCommand(outcome.endpoint)}${staleNote(outcome.staleConfig)}`,
|
|
402
|
-
);
|
|
403
|
-
return;
|
|
404
|
-
case "skipped-home":
|
|
405
|
-
logger.info(
|
|
406
|
-
`Did not write ${MCP_CONFIG_FILENAME} into your home directory (${outcome.dir}). To connect an agent, run this from the directory you start it in: ${addCommand(outcome.endpoint)}${staleNote(outcome.staleConfig)}`,
|
|
407
|
-
);
|
|
408
|
-
return;
|
|
409
|
-
case "skipped-unstable-port":
|
|
410
|
-
logger.info(
|
|
411
|
-
`Did not write ${MCP_CONFIG_FILENAME}: this server asked for MCP port ${outcome.requestedPort} and got ${outcome.boundPort}, so the port changes from run to run and a saved config would be wrong next boot. To connect an agent to this run: ${addCommand(outcome.endpoint)}. That registration outlives this run, and the port will have moved by the next one, so undo it with claude mcp remove malloy${staleNote(outcome.staleConfig)}`,
|
|
412
|
-
);
|
|
413
|
-
return;
|
|
414
|
-
case "skipped-root":
|
|
415
|
-
logger.info(
|
|
416
|
-
`Did not write ${MCP_CONFIG_FILENAME} into the filesystem root (${outcome.dir}). To connect an agent, run this from the directory you start it in: ${addCommand(outcome.endpoint)}${staleNote(outcome.staleConfig)}`,
|
|
417
|
-
);
|
|
418
|
-
return;
|
|
419
|
-
default: {
|
|
420
|
-
// A new variant must be handled above rather than logging nothing.
|
|
421
|
-
const exhaustive: never = outcome;
|
|
422
|
-
return exhaustive;
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
}
|