@malloy-publisher/server 0.0.382-dev → 0.2.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.docker.md +145 -20
- package/README.md +65 -12
- package/dist/app/api-doc.html +5 -0
- package/dist/app/api-doc.yaml +3903 -583
- package/dist/app/assets/EnvironmentPage-DiUgLuCH.js +1 -0
- package/dist/app/assets/HomePage-MhhjhnFj.js +1 -0
- package/dist/app/assets/LightMode-BEyvNUzT.js +1 -0
- package/dist/app/assets/MainPage-D4Cfee9o.js +2 -0
- package/dist/app/assets/MaterializationsPage-nGgGqwSN.js +1 -0
- package/dist/app/assets/ModelPage-DLAcEw-F.js +1 -0
- package/dist/app/assets/PackagePage-Df0ikH7w.js +1 -0
- package/dist/app/assets/RouteError-BFQwtroH.js +1 -0
- package/dist/app/assets/ThemeEditorPage-OoskoQYN.js +1 -0
- package/dist/app/assets/WorkbookPage-CpFgvUDC.js +1 -0
- package/dist/app/assets/{core-CjeTkq8O.es-BqRc6yhC.js → core-lb28vQkR.es-BoVSOc75.js} +1 -1
- package/dist/app/assets/github-dark-DenFmJkN.es-DHJKELXO.js +1 -0
- package/dist/app/assets/index-5eLCcNmP.css +1 -0
- package/dist/app/assets/index-6ypuFH6g.js +1774 -0
- package/dist/app/assets/index-B8pnU_V_.js +615 -0
- package/dist/app/assets/index-BImcHmVP.js +1 -0
- package/dist/app/assets/index-DSoCUF-f.js +2725 -0
- package/dist/app/assets/index-DfqzSItA.js +2 -0
- package/dist/app/assets/index-w_0OQJgZ.js +23 -0
- package/dist/app/index.html +8 -4
- package/dist/cpufeatures-1yrn0vtw.node +0 -0
- package/dist/default-publisher.config.json +23 -0
- package/dist/{instrumentation.js → instrumentation.mjs} +10631 -10573
- package/dist/package_load_worker.mjs +15219 -0
- package/dist/runtime/publisher.js +331 -0
- package/dist/{server.js → server.mjs} +115548 -49590
- package/dist/sshcrypto-vd2k5hq9.node +0 -0
- package/package.json +41 -28
- package/publisher.config.example.bigquery.json +33 -0
- package/publisher.config.example.duckdb.json +23 -0
- package/.eslintrc.json +0 -44
- package/.prettierignore +0 -1
- package/.prettierrc +0 -4
- package/build.ts +0 -38
- package/dist/app/assets/HomePage-Dn3E4CuB.js +0 -1
- package/dist/app/assets/MainPage-BzB3yoqi.js +0 -2
- package/dist/app/assets/ModelPage-C9O_sAXT.js +0 -1
- package/dist/app/assets/PackagePage-DcxKEjBX.js +0 -1
- package/dist/app/assets/ProjectPage-BDj307rF.js +0 -1
- package/dist/app/assets/RouteError-DAShbVCG.js +0 -1
- package/dist/app/assets/WorkbookPage-Cs_XYEaB.js +0 -1
- package/dist/app/assets/index-15BOvhp0.js +0 -456
- package/dist/app/assets/index-Bb2jqquW.js +0 -676
- package/dist/app/assets/index-CMlGQMcl.css +0 -1
- package/dist/app/assets/index-D68X76-7.js +0 -1276
- package/dist/app/assets/index.umd-DGBekgSu.js +0 -1145
- package/dxt/malloy_bridge.py +0 -354
- package/dxt/manifest.json +0 -22
- 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.json +0 -33
- package/src/config.spec.ts +0 -850
- package/src/config.ts +0 -305
- package/src/constants.ts +0 -10
- package/src/controller/compile.controller.ts +0 -35
- package/src/controller/connection.controller.ts +0 -515
- package/src/controller/database.controller.ts +0 -21
- package/src/controller/manifest.controller.ts +0 -29
- package/src/controller/materialization.controller.ts +0 -125
- package/src/controller/model.controller.ts +0 -107
- package/src/controller/package.controller.ts +0 -182
- package/src/controller/query.controller.ts +0 -60
- package/src/controller/watch-mode.controller.ts +0 -98
- package/src/data_styles.ts +0 -71
- package/src/dto/connection.dto.spec.ts +0 -131
- package/src/dto/connection.dto.ts +0 -174
- package/src/dto/index.ts +0 -2
- package/src/dto/package.dto.spec.ts +0 -42
- package/src/dto/package.dto.ts +0 -12
- package/src/dto/validate.spec.ts +0 -76
- package/src/dto/validate.ts +0 -31
- package/src/errors.ts +0 -116
- package/src/health.ts +0 -189
- package/src/instrumentation.ts +0 -148
- package/src/logger.ts +0 -154
- package/src/mcp/error_messages.ts +0 -199
- package/src/mcp/handler_utils.ts +0 -239
- package/src/mcp/mcp_constants.ts +0 -22
- package/src/mcp/prompts/handlers.ts +0 -84
- package/src/mcp/prompts/index.ts +0 -11
- package/src/mcp/prompts/prompt_definitions.ts +0 -160
- package/src/mcp/prompts/prompt_service.ts +0 -67
- package/src/mcp/prompts/utils.ts +0 -62
- package/src/mcp/resource_metadata.ts +0 -47
- package/src/mcp/resources/model_resource.ts +0 -153
- package/src/mcp/resources/notebook_resource.ts +0 -137
- package/src/mcp/resources/package_resource.ts +0 -370
- package/src/mcp/resources/project_resource.ts +0 -184
- package/src/mcp/resources/query_resource.ts +0 -117
- package/src/mcp/resources/source_resource.ts +0 -141
- package/src/mcp/resources/view_resource.ts +0 -136
- package/src/mcp/server.ts +0 -62
- package/src/mcp/tools/discovery_tools.ts +0 -258
- package/src/mcp/tools/execute_query_tool.ts +0 -265
- package/src/server.ts +0 -1195
- package/src/service/connection.spec.ts +0 -1331
- package/src/service/connection.ts +0 -1511
- package/src/service/connection_service.spec.ts +0 -641
- package/src/service/connection_service.ts +0 -181
- package/src/service/db_utils.spec.ts +0 -714
- package/src/service/db_utils.ts +0 -1227
- package/src/service/filter.spec.ts +0 -447
- package/src/service/filter.ts +0 -337
- package/src/service/filter_integration.spec.ts +0 -825
- package/src/service/gcs_s3_utils.ts +0 -427
- package/src/service/manifest_service.spec.ts +0 -201
- package/src/service/manifest_service.ts +0 -106
- package/src/service/materialization_service.spec.ts +0 -648
- package/src/service/materialization_service.ts +0 -929
- package/src/service/materialized_table_gc.spec.ts +0 -383
- package/src/service/materialized_table_gc.ts +0 -279
- package/src/service/model.spec.ts +0 -257
- package/src/service/model.ts +0 -1069
- package/src/service/package.spec.ts +0 -236
- package/src/service/package.ts +0 -463
- package/src/service/project.ts +0 -681
- package/src/service/project_compile.spec.ts +0 -197
- package/src/service/project_store.spec.ts +0 -859
- package/src/service/project_store.ts +0 -1531
- package/src/service/quoting.ts +0 -41
- package/src/service/resolve_project.ts +0 -13
- package/src/storage/BaseRepository.ts +0 -31
- package/src/storage/DatabaseInterface.ts +0 -180
- package/src/storage/StorageManager.mock.ts +0 -59
- package/src/storage/StorageManager.ts +0 -210
- package/src/storage/duckdb/ConnectionRepository.ts +0 -141
- package/src/storage/duckdb/DuckDBConnection.ts +0 -177
- package/src/storage/duckdb/DuckDBManifestStore.ts +0 -70
- package/src/storage/duckdb/DuckDBRepository.ts +0 -224
- package/src/storage/duckdb/ManifestRepository.ts +0 -119
- package/src/storage/duckdb/MaterializationRepository.ts +0 -249
- package/src/storage/duckdb/PackageRepository.ts +0 -135
- package/src/storage/duckdb/ProjectRepository.ts +0 -148
- package/src/storage/duckdb/manifest_store.spec.ts +0 -133
- package/src/storage/duckdb/schema.ts +0 -147
- package/src/storage/ducklake/DuckLakeManifestStore.ts +0 -146
- package/src/utils.ts +0 -13
- 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/harness/e2e.ts +0 -96
- package/tests/harness/mcp_test_setup.ts +0 -344
- package/tests/harness/mocks.ts +0 -37
- package/tests/harness/rest_e2e.ts +0 -68
- package/tests/harness/uris.ts +0 -31
- package/tests/integration/materialization/materialization_lifecycle.integration.spec.ts +0 -470
- package/tests/integration/mcp/mcp_execute_query_tool.integration.spec.ts +0 -434
- package/tests/integration/mcp/mcp_resource.integration.spec.ts +0 -663
- package/tests/integration/mcp/mcp_transport.integration.spec.ts +0 -122
- package/tests/integration/mcp/setup.spec.ts +0 -5
- package/tests/unit/duckdb/attached_databases.test.ts +0 -1132
- package/tests/unit/ducklake/ducklake.test.ts +0 -950
- package/tests/unit/mcp/prompt_definitions.test.ts +0 -102
- package/tests/unit/mcp/prompt_happy.test.ts +0 -51
- package/tsconfig.json +0 -32
package/dxt/malloy_bridge.py
DELETED
|
@@ -1,354 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
import sys
|
|
3
|
-
import json
|
|
4
|
-
import urllib.request
|
|
5
|
-
import urllib.parse
|
|
6
|
-
import urllib.error
|
|
7
|
-
import logging
|
|
8
|
-
import time
|
|
9
|
-
import signal
|
|
10
|
-
from typing import Dict, Any, Optional
|
|
11
|
-
|
|
12
|
-
# Set up logging with more detailed format
|
|
13
|
-
logging.basicConfig(
|
|
14
|
-
filename='/tmp/malloy_bridge.log',
|
|
15
|
-
level=logging.DEBUG,
|
|
16
|
-
format='%(asctime)s - %(levelname)s - %(funcName)s:%(lineno)d - %(message)s'
|
|
17
|
-
)
|
|
18
|
-
|
|
19
|
-
class ImprovedMalloyMCPBridge:
|
|
20
|
-
def __init__(self):
|
|
21
|
-
self.mcp_url = "http://localhost:4040/mcp"
|
|
22
|
-
# No tool name mapping needed - server already uses underscore format
|
|
23
|
-
|
|
24
|
-
# Set stdin/stdout to line buffered mode for better responsiveness
|
|
25
|
-
try:
|
|
26
|
-
sys.stdin.reconfigure(line_buffering=True)
|
|
27
|
-
sys.stdout.reconfigure(line_buffering=True)
|
|
28
|
-
except AttributeError:
|
|
29
|
-
# reconfigure not available in older Python versions
|
|
30
|
-
pass
|
|
31
|
-
|
|
32
|
-
# Set up signal handlers for graceful shutdown
|
|
33
|
-
signal.signal(signal.SIGTERM, self.signal_handler)
|
|
34
|
-
signal.signal(signal.SIGINT, self.signal_handler)
|
|
35
|
-
logging.info("Bridge initialized with improved stdio handling")
|
|
36
|
-
|
|
37
|
-
def signal_handler(self, signum, frame):
|
|
38
|
-
logging.info(f"Received signal {signum}, shutting down gracefully")
|
|
39
|
-
sys.exit(0)
|
|
40
|
-
|
|
41
|
-
def parse_sse_response(self, response, request_id: Optional[Any] = None) -> Dict[str, Any]:
|
|
42
|
-
"""Parse Server-Sent Events response format with improved error handling"""
|
|
43
|
-
logging.debug(f"Starting SSE parsing for request {request_id}")
|
|
44
|
-
|
|
45
|
-
try:
|
|
46
|
-
# Read response with size limit to prevent memory issues
|
|
47
|
-
max_size = 1024 * 1024 # 1MB limit
|
|
48
|
-
response_text = ""
|
|
49
|
-
bytes_read = 0
|
|
50
|
-
|
|
51
|
-
# Read response in chunks to avoid memory issues
|
|
52
|
-
while True:
|
|
53
|
-
chunk = response.read(8192) # 8KB chunks
|
|
54
|
-
if not chunk:
|
|
55
|
-
break
|
|
56
|
-
bytes_read += len(chunk)
|
|
57
|
-
if bytes_read > max_size:
|
|
58
|
-
logging.warning(f"Response too large ({bytes_read} bytes), truncating")
|
|
59
|
-
break
|
|
60
|
-
response_text += chunk.decode('utf-8')
|
|
61
|
-
|
|
62
|
-
logging.debug(f"Read {bytes_read} bytes from SSE response")
|
|
63
|
-
|
|
64
|
-
lines = response_text.strip().split('\n')
|
|
65
|
-
data_line = None
|
|
66
|
-
|
|
67
|
-
# Look for data line in SSE format
|
|
68
|
-
for line in lines:
|
|
69
|
-
if line.startswith('data: '):
|
|
70
|
-
data_line = line[6:] # Remove 'data: ' prefix
|
|
71
|
-
break
|
|
72
|
-
|
|
73
|
-
# If no SSE format, try parsing the entire response as JSON
|
|
74
|
-
if not data_line:
|
|
75
|
-
data_line = response_text.strip()
|
|
76
|
-
|
|
77
|
-
if data_line:
|
|
78
|
-
try:
|
|
79
|
-
parsed_data = json.loads(data_line)
|
|
80
|
-
# Ensure the response has a proper ID
|
|
81
|
-
if 'id' not in parsed_data or parsed_data['id'] is None:
|
|
82
|
-
parsed_data['id'] = request_id
|
|
83
|
-
|
|
84
|
-
logging.debug(f"Successfully parsed response for request {request_id}")
|
|
85
|
-
return parsed_data
|
|
86
|
-
|
|
87
|
-
except json.JSONDecodeError as e:
|
|
88
|
-
logging.error(f"JSON decode error for request {request_id}: {e}")
|
|
89
|
-
logging.error(f"Raw data that failed to parse: {data_line[:200]}...")
|
|
90
|
-
return {
|
|
91
|
-
"jsonrpc": "2.0",
|
|
92
|
-
"error": {
|
|
93
|
-
"code": -32603,
|
|
94
|
-
"message": f"Failed to parse response: {str(e)}"
|
|
95
|
-
},
|
|
96
|
-
"id": request_id
|
|
97
|
-
}
|
|
98
|
-
else:
|
|
99
|
-
logging.error(f"No data found in response for request {request_id}")
|
|
100
|
-
return {
|
|
101
|
-
"jsonrpc": "2.0",
|
|
102
|
-
"error": {
|
|
103
|
-
"code": -32603,
|
|
104
|
-
"message": "No data found in response"
|
|
105
|
-
},
|
|
106
|
-
"id": request_id
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
except Exception as e:
|
|
110
|
-
logging.error(f"Error reading SSE response for request {request_id}: {e}")
|
|
111
|
-
return {
|
|
112
|
-
"jsonrpc": "2.0",
|
|
113
|
-
"error": {
|
|
114
|
-
"code": -32603,
|
|
115
|
-
"message": f"SSE parsing error: {str(e)}"
|
|
116
|
-
},
|
|
117
|
-
"id": request_id
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
def send_request(self, request: Dict[str, Any]) -> Dict[str, Any]:
|
|
121
|
-
"""Send a JSON-RPC request to the Malloy MCP endpoint with improved timeout handling"""
|
|
122
|
-
request_id = request.get("id")
|
|
123
|
-
method = request.get("method", "unknown")
|
|
124
|
-
start_time = time.time()
|
|
125
|
-
|
|
126
|
-
logging.debug(f"Sending request {request_id} ({method}) to Malloy server")
|
|
127
|
-
|
|
128
|
-
# Log ALL method calls for debugging
|
|
129
|
-
logging.info(f"METHOD CALL - Request {request_id}: method='{method}'")
|
|
130
|
-
if method == "tools/call":
|
|
131
|
-
tool_name = request.get("params", {}).get("name", "unknown")
|
|
132
|
-
logging.info(f"TOOL CALL - Request {request_id}: tool_name='{tool_name}'")
|
|
133
|
-
logging.info(f"FULL REQUEST - {json.dumps(request)}")
|
|
134
|
-
|
|
135
|
-
try:
|
|
136
|
-
# Ensure the request has proper structure
|
|
137
|
-
if "jsonrpc" not in request:
|
|
138
|
-
request["jsonrpc"] = "2.0"
|
|
139
|
-
|
|
140
|
-
# Prepare the request with longer timeout for tools/list
|
|
141
|
-
timeout = 10 if method == "tools/list" else 3
|
|
142
|
-
data = json.dumps(request).encode('utf-8')
|
|
143
|
-
req = urllib.request.Request(
|
|
144
|
-
self.mcp_url,
|
|
145
|
-
data=data,
|
|
146
|
-
headers={
|
|
147
|
-
'Content-Type': 'application/json',
|
|
148
|
-
'Accept': 'application/json, text/event-stream',
|
|
149
|
-
'Connection': 'close' # Don't keep connections open
|
|
150
|
-
}
|
|
151
|
-
)
|
|
152
|
-
|
|
153
|
-
logging.debug(f"Using timeout of {timeout}s for method {method}")
|
|
154
|
-
|
|
155
|
-
with urllib.request.urlopen(req, timeout=timeout) as response:
|
|
156
|
-
# Handle SSE response directly from the response object
|
|
157
|
-
content_type = response.headers.get('Content-Type', '')
|
|
158
|
-
|
|
159
|
-
if 'text/event-stream' in content_type:
|
|
160
|
-
logging.debug("Handling SSE response")
|
|
161
|
-
parsed_response = self.parse_sse_response(response, request_id)
|
|
162
|
-
else:
|
|
163
|
-
logging.debug("Handling JSON response")
|
|
164
|
-
response_text = response.read().decode('utf-8')
|
|
165
|
-
try:
|
|
166
|
-
parsed_response = json.loads(response_text)
|
|
167
|
-
if 'id' not in parsed_response or parsed_response['id'] is None:
|
|
168
|
-
parsed_response['id'] = request_id
|
|
169
|
-
except json.JSONDecodeError as e:
|
|
170
|
-
logging.error(f"Failed to parse JSON response: {e}")
|
|
171
|
-
parsed_response = {
|
|
172
|
-
"jsonrpc": "2.0",
|
|
173
|
-
"error": {
|
|
174
|
-
"code": -32603,
|
|
175
|
-
"message": f"Invalid JSON response: {str(e)}"
|
|
176
|
-
},
|
|
177
|
-
"id": request_id
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
# Log server response for debugging
|
|
181
|
-
if "error" in parsed_response:
|
|
182
|
-
logging.error(f"SERVER ERROR - Request {request_id} ({method}): {parsed_response}")
|
|
183
|
-
elif method == "tools/call":
|
|
184
|
-
logging.info(f"TOOL CALL - Server response: {str(parsed_response)[:500]}...")
|
|
185
|
-
|
|
186
|
-
elapsed = time.time() - start_time
|
|
187
|
-
logging.debug(f"Request {request_id} completed in {elapsed:.2f}s")
|
|
188
|
-
return parsed_response
|
|
189
|
-
|
|
190
|
-
except urllib.error.HTTPError as e:
|
|
191
|
-
elapsed = time.time() - start_time
|
|
192
|
-
error_message = f"HTTP {e.code}: {e.reason}"
|
|
193
|
-
try:
|
|
194
|
-
error_body = e.read().decode('utf-8')
|
|
195
|
-
error_message += f" - {error_body}"
|
|
196
|
-
except:
|
|
197
|
-
pass
|
|
198
|
-
|
|
199
|
-
logging.error(f"HTTP error for request {request_id} after {elapsed:.2f}s: {error_message}")
|
|
200
|
-
return {
|
|
201
|
-
"jsonrpc": "2.0",
|
|
202
|
-
"error": {
|
|
203
|
-
"code": e.code,
|
|
204
|
-
"message": error_message
|
|
205
|
-
},
|
|
206
|
-
"id": request_id
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
except urllib.error.URLError as e:
|
|
210
|
-
elapsed = time.time() - start_time
|
|
211
|
-
error_msg = f"Connection error: {str(e)}"
|
|
212
|
-
logging.error(f"URL error for request {request_id} after {elapsed:.2f}s: {error_msg}")
|
|
213
|
-
return {
|
|
214
|
-
"jsonrpc": "2.0",
|
|
215
|
-
"error": {
|
|
216
|
-
"code": -32603,
|
|
217
|
-
"message": error_msg
|
|
218
|
-
},
|
|
219
|
-
"id": request_id
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
except Exception as e:
|
|
223
|
-
elapsed = time.time() - start_time
|
|
224
|
-
error_msg = f"Unexpected error: {str(e)}"
|
|
225
|
-
logging.error(f"Unexpected error for request {request_id} after {elapsed:.2f}s: {error_msg}")
|
|
226
|
-
return {
|
|
227
|
-
"jsonrpc": "2.0",
|
|
228
|
-
"error": {
|
|
229
|
-
"code": -32603,
|
|
230
|
-
"message": error_msg
|
|
231
|
-
},
|
|
232
|
-
"id": request_id
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
def safe_print(self, data):
|
|
236
|
-
"""Print with improved error handling and immediate flushing"""
|
|
237
|
-
try:
|
|
238
|
-
output = json.dumps(data)
|
|
239
|
-
print(output, flush=True) # Use flush=True for immediate output
|
|
240
|
-
logging.debug(f"Successfully sent response: {len(output)} chars")
|
|
241
|
-
|
|
242
|
-
except BrokenPipeError:
|
|
243
|
-
logging.error("Broken pipe error - client disconnected")
|
|
244
|
-
sys.exit(0)
|
|
245
|
-
|
|
246
|
-
except Exception as e:
|
|
247
|
-
logging.error(f"Print error: {e}")
|
|
248
|
-
# Don't exit on print errors, just log them
|
|
249
|
-
|
|
250
|
-
def process_request(self, line: str) -> None:
|
|
251
|
-
"""Process a single request line with improved error handling"""
|
|
252
|
-
try:
|
|
253
|
-
request = json.loads(line)
|
|
254
|
-
request_id = request.get("id", "unknown")
|
|
255
|
-
method = request.get("method", "unknown")
|
|
256
|
-
|
|
257
|
-
logging.debug(f"Processing request {request_id}: {method}")
|
|
258
|
-
|
|
259
|
-
# Validate required fields
|
|
260
|
-
if not isinstance(request, dict):
|
|
261
|
-
raise ValueError("Request must be a JSON object")
|
|
262
|
-
|
|
263
|
-
if "method" not in request:
|
|
264
|
-
raise ValueError("Request must have a 'method' field")
|
|
265
|
-
|
|
266
|
-
# Handle notifications/initialized locally (Malloy server doesn't support it)
|
|
267
|
-
if method == "notifications/initialized":
|
|
268
|
-
logging.info(f"Handling notifications/initialized locally for request {request_id}")
|
|
269
|
-
# For notifications, we don't send a response (notifications are one-way)
|
|
270
|
-
return
|
|
271
|
-
|
|
272
|
-
# Ensure ID is present and valid
|
|
273
|
-
if "id" not in request:
|
|
274
|
-
request["id"] = 1 # Default ID
|
|
275
|
-
elif request["id"] is None:
|
|
276
|
-
request["id"] = 1 # Replace null with default
|
|
277
|
-
|
|
278
|
-
# Send request and get response
|
|
279
|
-
response = self.send_request(request)
|
|
280
|
-
|
|
281
|
-
# Send response immediately
|
|
282
|
-
self.safe_print(response)
|
|
283
|
-
|
|
284
|
-
except json.JSONDecodeError as e:
|
|
285
|
-
logging.error(f"JSON parse error: {e}")
|
|
286
|
-
error_response = {
|
|
287
|
-
"jsonrpc": "2.0",
|
|
288
|
-
"error": {
|
|
289
|
-
"code": -32700,
|
|
290
|
-
"message": f"Parse error: {str(e)}"
|
|
291
|
-
},
|
|
292
|
-
"id": None
|
|
293
|
-
}
|
|
294
|
-
self.safe_print(error_response)
|
|
295
|
-
|
|
296
|
-
except ValueError as e:
|
|
297
|
-
logging.error(f"Request validation error: {e}")
|
|
298
|
-
error_response = {
|
|
299
|
-
"jsonrpc": "2.0",
|
|
300
|
-
"error": {
|
|
301
|
-
"code": -32600,
|
|
302
|
-
"message": f"Invalid Request: {str(e)}"
|
|
303
|
-
},
|
|
304
|
-
"id": None
|
|
305
|
-
}
|
|
306
|
-
self.safe_print(error_response)
|
|
307
|
-
|
|
308
|
-
except Exception as e:
|
|
309
|
-
logging.error(f"Unexpected error processing request: {e}")
|
|
310
|
-
error_response = {
|
|
311
|
-
"jsonrpc": "2.0",
|
|
312
|
-
"error": {
|
|
313
|
-
"code": -32603,
|
|
314
|
-
"message": f"Internal error: {str(e)}"
|
|
315
|
-
},
|
|
316
|
-
"id": None
|
|
317
|
-
}
|
|
318
|
-
self.safe_print(error_response)
|
|
319
|
-
|
|
320
|
-
def run(self):
|
|
321
|
-
"""Main loop with improved stdin handling"""
|
|
322
|
-
logging.info("Starting main processing loop")
|
|
323
|
-
|
|
324
|
-
try:
|
|
325
|
-
# Process stdin line by line with immediate handling
|
|
326
|
-
for line in sys.stdin:
|
|
327
|
-
line = line.strip()
|
|
328
|
-
if not line:
|
|
329
|
-
continue
|
|
330
|
-
|
|
331
|
-
# Process each request immediately
|
|
332
|
-
self.process_request(line)
|
|
333
|
-
|
|
334
|
-
except KeyboardInterrupt:
|
|
335
|
-
logging.info("Received keyboard interrupt, shutting down")
|
|
336
|
-
sys.exit(0)
|
|
337
|
-
|
|
338
|
-
except BrokenPipeError:
|
|
339
|
-
logging.info("Broken pipe detected, client disconnected")
|
|
340
|
-
sys.exit(0)
|
|
341
|
-
|
|
342
|
-
except Exception as e:
|
|
343
|
-
logging.error(f"Fatal error in main loop: {e}")
|
|
344
|
-
sys.exit(1)
|
|
345
|
-
|
|
346
|
-
logging.info("Main loop completed")
|
|
347
|
-
|
|
348
|
-
if __name__ == "__main__":
|
|
349
|
-
try:
|
|
350
|
-
bridge = ImprovedMalloyMCPBridge()
|
|
351
|
-
bridge.run()
|
|
352
|
-
except Exception as e:
|
|
353
|
-
logging.error(f"Failed to start bridge: {e}")
|
|
354
|
-
sys.exit(1)
|
package/dxt/manifest.json
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"dxt_version": "0.1",
|
|
3
|
-
"name": "Malloy",
|
|
4
|
-
"version": "1.0.0",
|
|
5
|
-
"description": "Connect to Malloy Publisher, allowing you to write & run Malloy data queries.",
|
|
6
|
-
"author": {
|
|
7
|
-
"name": "Josh Sacks"
|
|
8
|
-
},
|
|
9
|
-
"server": {
|
|
10
|
-
"type": "python",
|
|
11
|
-
"entry_point": "malloy_bridge.py",
|
|
12
|
-
"mcp_config": {
|
|
13
|
-
"command": "python",
|
|
14
|
-
"args": ["${__dirname}/malloy_bridge.py"],
|
|
15
|
-
"env": {
|
|
16
|
-
"PYTHONPATH": "server/lib"
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
},
|
|
20
|
-
"keywords": ["sql", "malloy", "data", "queries", "charts"],
|
|
21
|
-
"license": "MIT"
|
|
22
|
-
}
|
package/eslint.config.mjs
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
import { fixupConfigRules, fixupPluginRules } from "@eslint/compat";
|
|
2
|
-
import { FlatCompat } from "@eslint/eslintrc";
|
|
3
|
-
import js from "@eslint/js";
|
|
4
|
-
import typescriptEslint from "@typescript-eslint/eslint-plugin";
|
|
5
|
-
import tsParser from "@typescript-eslint/parser";
|
|
6
|
-
import globals from "globals";
|
|
7
|
-
import path from "node:path";
|
|
8
|
-
import { fileURLToPath } from "node:url";
|
|
9
|
-
|
|
10
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
11
|
-
const __dirname = path.dirname(__filename);
|
|
12
|
-
const compat = new FlatCompat({
|
|
13
|
-
baseDirectory: __dirname,
|
|
14
|
-
recommendedConfig: js.configs.recommended,
|
|
15
|
-
allConfig: js.configs.all,
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
export default [
|
|
19
|
-
{
|
|
20
|
-
ignores: [
|
|
21
|
-
"**/node_modules/**",
|
|
22
|
-
"**/dist/**",
|
|
23
|
-
"**/build/**",
|
|
24
|
-
"**/.DS_Store",
|
|
25
|
-
"**/__pycache__/**",
|
|
26
|
-
"**/*.py[cod]",
|
|
27
|
-
"**/*.egg-info/**",
|
|
28
|
-
"**/.env",
|
|
29
|
-
"**/*.log",
|
|
30
|
-
],
|
|
31
|
-
},
|
|
32
|
-
...fixupConfigRules(
|
|
33
|
-
compat.extends(
|
|
34
|
-
"eslint:recommended",
|
|
35
|
-
"plugin:react/recommended",
|
|
36
|
-
"plugin:@typescript-eslint/recommended",
|
|
37
|
-
"plugin:@typescript-eslint/eslint-recommended",
|
|
38
|
-
"prettier",
|
|
39
|
-
"plugin:prettier/recommended",
|
|
40
|
-
"plugin:react-hooks/recommended",
|
|
41
|
-
"plugin:storybook/recommended",
|
|
42
|
-
),
|
|
43
|
-
),
|
|
44
|
-
{
|
|
45
|
-
plugins: {
|
|
46
|
-
"@typescript-eslint": fixupPluginRules(typescriptEslint),
|
|
47
|
-
},
|
|
48
|
-
|
|
49
|
-
languageOptions: {
|
|
50
|
-
globals: {
|
|
51
|
-
...globals.jest,
|
|
52
|
-
...globals.browser,
|
|
53
|
-
},
|
|
54
|
-
|
|
55
|
-
parser: tsParser,
|
|
56
|
-
},
|
|
57
|
-
|
|
58
|
-
settings: {
|
|
59
|
-
react: {
|
|
60
|
-
version: "detect",
|
|
61
|
-
},
|
|
62
|
-
},
|
|
63
|
-
|
|
64
|
-
rules: {
|
|
65
|
-
"react/react-in-jsx-scope": "off",
|
|
66
|
-
"@typescript-eslint/no-unused-vars": [
|
|
67
|
-
"error",
|
|
68
|
-
{
|
|
69
|
-
argsIgnorePattern: "^_",
|
|
70
|
-
varsIgnorePattern: "^_",
|
|
71
|
-
caughtErrorsIgnorePattern: "^_",
|
|
72
|
-
},
|
|
73
|
-
],
|
|
74
|
-
},
|
|
75
|
-
},
|
|
76
|
-
];
|
package/k6-tests/README.md
DELETED
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
# k6-tests
|
|
2
|
-
|
|
3
|
-
Performance and load testing suite for the Malloy Publisher API using k6.
|
|
4
|
-
|
|
5
|
-
## Installation
|
|
6
|
-
|
|
7
|
-
To install dependencies:
|
|
8
|
-
|
|
9
|
-
```bash
|
|
10
|
-
bun install
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
## Prerequisites
|
|
14
|
-
|
|
15
|
-
**Important:** Before running tests, ensure the publisher server is running with the following environment variables to limit log output:
|
|
16
|
-
|
|
17
|
-
```bash
|
|
18
|
-
DISABLE_RESPONSE_LOGGING=true
|
|
19
|
-
LOG_LEVEL=info # or LOG_LEVEL=warn
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
Start the server before running any k6 tests.
|
|
23
|
-
|
|
24
|
-
## Available Tests
|
|
25
|
-
|
|
26
|
-
### Smoke Test
|
|
27
|
-
|
|
28
|
-
Basic functionality verification under minimal load:
|
|
29
|
-
|
|
30
|
-
```bash
|
|
31
|
-
bun run smoke-test # Standard test
|
|
32
|
-
./scripts/run-with-otel.sh smoke-test/smoke-test.ts # With OpenTelemetry export
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
### Load Tests
|
|
36
|
-
|
|
37
|
-
#### CRUD Load Tests
|
|
38
|
-
|
|
39
|
-
Specific tests for CRUD (Create, Read, Update, Delete) operations on individual resource types:
|
|
40
|
-
|
|
41
|
-
```bash
|
|
42
|
-
bun run load-test-crud-projects # Projects CRUD load test
|
|
43
|
-
bun run load-test-crud-connections # Connections CRUD load test
|
|
44
|
-
bun run load-test-crud-packages # Packages CRUD load test
|
|
45
|
-
bun run load-test-crud # Run all three CRUD tests sequentially
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
Run CRUD tests with OpenTelemetry export:
|
|
49
|
-
|
|
50
|
-
```bash
|
|
51
|
-
./scripts/run-with-otel.sh load-test/load-test-crud-projects.ts
|
|
52
|
-
./scripts/run-with-otel.sh load-test/load-test-crud-connections.ts
|
|
53
|
-
./scripts/run-with-otel.sh load-test/load-test-crud-packages.ts
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
#### Comprehensive Load Test
|
|
57
|
-
|
|
58
|
-
Focused on comprehensive API read operations and query execution. Tests listing, getting, and querying various resources (projects, packages, models, notebooks, connections, databases, queries, SQL sources) under normal load:
|
|
59
|
-
|
|
60
|
-
```bash
|
|
61
|
-
bun run load-test # Standard test
|
|
62
|
-
./scripts/run-with-otel.sh load-test/load-test.ts # With OTEL export
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
## OpenTelemetry & Prometheus Integration
|
|
66
|
-
|
|
67
|
-
Export k6 metrics to Prometheus via OpenTelemetry for visualization in Grafana.
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
**Usage:**
|
|
71
|
-
Use the `run-with-otel.sh` script to run any test with OpenTelemetry export:
|
|
72
|
-
|
|
73
|
-
```bash
|
|
74
|
-
./scripts/run-with-otel.sh <test-file> [additional-k6-args...]
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
Examples:
|
|
78
|
-
```bash
|
|
79
|
-
./scripts/run-with-otel.sh smoke-test/smoke-test.ts
|
|
80
|
-
./scripts/run-with-otel.sh load-test/load-test.ts --duration 5m
|
|
81
|
-
./scripts/run-with-otel.sh load-test/load-test-crud-projects.ts
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
## Environment Variables
|
|
85
|
-
|
|
86
|
-
### Test Configuration
|
|
87
|
-
|
|
88
|
-
- `K6_PUBLISHER_URL` - Base URL of the publisher service (default: `http://localhost:4000`)
|
|
89
|
-
- `K6_PROJECT_NAME` - Project name for tests (default: `malloy-samples`)
|
|
90
|
-
- `K6_AUTH_TOKEN` - Authorization token for API requests
|
|
91
|
-
- `GOOGLE_APPLICATION_CREDENTIALS` - BigQuery service account JSON (for BigQuery tests)
|
|
92
|
-
- `K6_USE_VERSION_ID` - Set to `"true"` to use version IDs in API calls - uses "latest" as versionId when set to true
|
|
93
|
-
- `K6_MAX_VIEWS_PER_MODEL` - Maximum views to test per model (default: `5`)
|
|
94
|
-
- `K6_DEBUG` - Set to `"true"` for verbose logging
|
|
95
|
-
- `K6_WHITELISTED_PACKAGES` - JSON array of package names to test
|
|
96
|
-
- `K6_AVAILABLE_PACKAGES` - JSON array of available package names
|
|
97
|
-
|
|
98
|
-
### OpenTelemetry Export (when using `run-with-otel.sh`)
|
|
99
|
-
|
|
100
|
-
These environment variables are automatically set by `run-with-otel.sh` but can be overridden:
|
|
101
|
-
|
|
102
|
-
- `K6_OTEL_GRPC_EXPORTER_ENDPOINT` - OTEL collector gRPC endpoint (default: `localhost:4317`)
|
|
103
|
-
- `K6_OTEL_GRPC_EXPORTER_INSECURE` - Disable TLS for plain gRPC (default: `true` - set automatically by script)
|
|
104
|
-
- `K6_OTEL_SERVICE_NAME` - Service name for metrics (default: `k6-load-test`)
|
|
105
|
-
- `K6_OTEL_EXPORTER_PROTOCOL` - Exporter protocol: `grpc` or `http/protobuf` (default: `grpc`)
|
|
106
|
-
- `K6_OTEL_HTTP_EXPORTER_ENDPOINT` - OTEL collector HTTP endpoint (default: `localhost:4318`)
|
|
107
|
-
- `K6_OTEL_FLUSH_INTERVAL` - How often k6 flushes internal metrics (default: `1s`)
|
|
108
|
-
- `K6_OTEL_EXPORT_INTERVAL` - How often k6 exports to OTEL collector (default: `2s` in script, `10s` otherwise)
|
|
109
|
-
|
|
110
|
-
## Setup & Build
|
|
111
|
-
|
|
112
|
-
### Clone Malloy Samples
|
|
113
|
-
|
|
114
|
-
Clone the malloy-samples repository (required for tests):
|
|
115
|
-
|
|
116
|
-
```bash
|
|
117
|
-
bun run clone-malloy-samples
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
This clones the [malloy-samples](https://github.com/credibledata/malloy-samples) repository into `./packages` directory.
|
|
121
|
-
|
|
122
|
-
### Client Generation
|
|
123
|
-
|
|
124
|
-
To regenerate API clients from OpenAPI spec:
|
|
125
|
-
|
|
126
|
-
```bash
|
|
127
|
-
bun run generate-clients
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
### Build
|
|
131
|
-
|
|
132
|
-
Run the full build process (cleanup, clone samples, and generate clients):
|
|
133
|
-
|
|
134
|
-
```bash
|
|
135
|
-
bun run build
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
## Cleanup
|
|
139
|
-
|
|
140
|
-
Remove generated files and dependencies:
|
|
141
|
-
|
|
142
|
-
```bash
|
|
143
|
-
bun run clean
|
|
144
|
-
```
|