iflow-mcp_alaturqua-mcp-trino-python 0.7.1__py3-none-any.whl
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.
- iflow_mcp_alaturqua_mcp_trino_python-0.7.1.dist-info/METADATA +446 -0
- iflow_mcp_alaturqua_mcp_trino_python-0.7.1.dist-info/RECORD +9 -0
- iflow_mcp_alaturqua_mcp_trino_python-0.7.1.dist-info/WHEEL +4 -0
- iflow_mcp_alaturqua_mcp_trino_python-0.7.1.dist-info/entry_points.txt +2 -0
- iflow_mcp_alaturqua_mcp_trino_python-0.7.1.dist-info/licenses/LICENSE +201 -0
- src/__init__.py +0 -0
- src/config.py +39 -0
- src/server.py +617 -0
- src/trino_client.py +596 -0
src/config.py
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Configuration module for Trino connection settings."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
|
|
6
|
+
import trino
|
|
7
|
+
from dotenv import load_dotenv
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class TrinoConfig:
|
|
12
|
+
"""Configuration class for Trino connection settings."""
|
|
13
|
+
|
|
14
|
+
host: str
|
|
15
|
+
port: int
|
|
16
|
+
user: str
|
|
17
|
+
catalog: str | None = None
|
|
18
|
+
schema: str | None = None
|
|
19
|
+
http_scheme: str = "http"
|
|
20
|
+
auth: trino.auth.BasicAuthentication | None = None
|
|
21
|
+
source: str = "mcp-trino-python"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def load_config() -> TrinoConfig:
|
|
25
|
+
"""Load Trino configuration from environment variables."""
|
|
26
|
+
load_dotenv(override=True)
|
|
27
|
+
|
|
28
|
+
return TrinoConfig(
|
|
29
|
+
host=os.getenv("TRINO_HOST", "localhost"),
|
|
30
|
+
port=int(os.getenv("TRINO_PORT", "8080")),
|
|
31
|
+
user=os.getenv("TRINO_USER", os.getenv("USER", "trino")),
|
|
32
|
+
catalog=os.getenv("TRINO_CATALOG"),
|
|
33
|
+
schema=os.getenv("TRINO_SCHEMA"),
|
|
34
|
+
http_scheme=os.getenv("TRINO_HTTP_SCHEME", "http"),
|
|
35
|
+
auth=None
|
|
36
|
+
if os.getenv("TRINO_PASSWORD", None) is None
|
|
37
|
+
else trino.auth.BasicAuthentication(os.getenv("TRINO_USER", None), os.getenv("TRINO_PASSWORD", None)),
|
|
38
|
+
source="mcp-trino-python",
|
|
39
|
+
)
|
src/server.py
ADDED
|
@@ -0,0 +1,617 @@
|
|
|
1
|
+
"""Model Context Protocol server for Trino.
|
|
2
|
+
|
|
3
|
+
This module provides a Model Context Protocol (MCP) server that exposes Trino
|
|
4
|
+
functionality through resources and tools, with special support for Iceberg tables.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
|
|
9
|
+
from mcp.server.fastmcp import FastMCP
|
|
10
|
+
from mcp.server.fastmcp.prompts import base
|
|
11
|
+
from pydantic import Field
|
|
12
|
+
|
|
13
|
+
from config import load_config
|
|
14
|
+
from trino_client import TrinoClient
|
|
15
|
+
|
|
16
|
+
# Initialize the MCP server and Trino client
|
|
17
|
+
config = load_config()
|
|
18
|
+
client = TrinoClient(config)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# Initialize the MCP server with recommended settings for all transports
|
|
22
|
+
mcp = FastMCP(
|
|
23
|
+
name="Trino Explorer",
|
|
24
|
+
instructions="This Model Context Protocol (MCP) server provides access to Trino Query Engine.",
|
|
25
|
+
# HTTP transport settings (used by streamable-http and sse)
|
|
26
|
+
host=os.getenv("MCP_HOST", "127.0.0.1"),
|
|
27
|
+
port=int(os.getenv("MCP_PORT", "8000")),
|
|
28
|
+
# Recommended for production HTTP deployments
|
|
29
|
+
stateless_http=True,
|
|
30
|
+
json_response=True,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
# Tools
|
|
35
|
+
@mcp.tool(description="List all available catalogs")
|
|
36
|
+
def show_catalogs() -> str:
|
|
37
|
+
"""List all available catalogs."""
|
|
38
|
+
return client.list_catalogs()
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@mcp.tool(description="List all schemas in a catalog")
|
|
42
|
+
def show_schemas(catalog: str = Field(description="The name of the catalog")) -> str:
|
|
43
|
+
"""List all schemas in a catalog.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
catalog: The name of the catalog
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
str: List of schemas in the specified catalog
|
|
50
|
+
"""
|
|
51
|
+
return client.list_schemas(catalog)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@mcp.tool(description="List all tables in a schema")
|
|
55
|
+
def show_tables(
|
|
56
|
+
catalog: str = Field(description="The name of the catalog"),
|
|
57
|
+
schema_name: str = Field(description="The name of the schema"),
|
|
58
|
+
) -> str:
|
|
59
|
+
"""List all tables in a schema.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
catalog: The name of the catalog
|
|
63
|
+
schema_name: The name of the schema
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
str: List of tables in the specified schema
|
|
67
|
+
"""
|
|
68
|
+
return client.list_tables(catalog, schema_name)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@mcp.tool(description="Describe a table")
|
|
72
|
+
def describe_table(
|
|
73
|
+
catalog: str = Field(description="The catalog name"),
|
|
74
|
+
schema_name: str = Field(description="The schema name"),
|
|
75
|
+
table: str = Field(description="The name of the table"),
|
|
76
|
+
) -> str:
|
|
77
|
+
"""Describe a table.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
catalog (str): The catalog name
|
|
81
|
+
schema_name (str): The schema name
|
|
82
|
+
table (str): The name of the table
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
str: Table description in JSON format
|
|
86
|
+
"""
|
|
87
|
+
return client.describe_table(catalog, schema_name, table)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@mcp.tool(description="Show the CREATE TABLE statement for a specific table")
|
|
91
|
+
def show_create_table(
|
|
92
|
+
catalog: str = Field(description="catalog name "),
|
|
93
|
+
schema_name: str = Field(description="schema name "),
|
|
94
|
+
table: str = Field(description="The name of the table"),
|
|
95
|
+
) -> str:
|
|
96
|
+
"""Show the CREATE TABLE statement for a table.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
catalog: catalog name
|
|
100
|
+
schema_name: schema name
|
|
101
|
+
table: The name of the table
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
str: The CREATE TABLE statement
|
|
105
|
+
"""
|
|
106
|
+
return client.show_create_table(catalog, schema_name, table)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@mcp.tool(description="Show the CREATE VIEW statement for a specific view")
|
|
110
|
+
def show_create_view(
|
|
111
|
+
catalog: str = Field(description="catalog name "),
|
|
112
|
+
schema_name: str = Field(description="schema name "),
|
|
113
|
+
view: str = Field(description="The name of the view"),
|
|
114
|
+
) -> str:
|
|
115
|
+
"""Show the CREATE VIEW statement for a view.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
catalog: catalog name
|
|
119
|
+
schema_name: schema name
|
|
120
|
+
view: The name of the view
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
str: The CREATE VIEW statement
|
|
124
|
+
"""
|
|
125
|
+
return client.show_create_view(catalog, schema_name, view)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
@mcp.tool(description="Execute a SQL query and return results in a readable format")
|
|
129
|
+
def execute_query(query: str = Field(description="The SQL query to execute")) -> str:
|
|
130
|
+
"""Execute a SQL query and return formatted results.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
query: The SQL query to execute
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
str: Query results formatted as a JSON string
|
|
137
|
+
"""
|
|
138
|
+
return client.execute_query(query)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
@mcp.tool(description="Optimize an Iceberg table's data files")
|
|
142
|
+
def optimize(
|
|
143
|
+
catalog: str = Field(description="catalog name "),
|
|
144
|
+
schema_name: str = Field(description="schema name "),
|
|
145
|
+
table: str = Field(description="The name of the table to optimize"),
|
|
146
|
+
) -> str:
|
|
147
|
+
"""Optimize an Iceberg table by compacting small files.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
catalog: catalog name
|
|
151
|
+
schema_name: schema name
|
|
152
|
+
table: The name of the table to optimize
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
str: Confirmation message
|
|
156
|
+
"""
|
|
157
|
+
return client.optimize(catalog, schema_name, table)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
@mcp.tool(description="Optimize manifest files for an Iceberg table")
|
|
161
|
+
def optimize_manifests(
|
|
162
|
+
catalog: str = Field(description="catalog name "),
|
|
163
|
+
schema_name: str = Field(description="schema name "),
|
|
164
|
+
table: str = Field(description="The name of the table"),
|
|
165
|
+
) -> str:
|
|
166
|
+
"""Optimize manifest files for an Iceberg table.
|
|
167
|
+
|
|
168
|
+
Args:
|
|
169
|
+
catalog: catalog name
|
|
170
|
+
schema_name: schema name
|
|
171
|
+
table: The name of the table
|
|
172
|
+
|
|
173
|
+
Returns:
|
|
174
|
+
str: Confirmation message
|
|
175
|
+
"""
|
|
176
|
+
return client.optimize_manifests(catalog, schema_name, table)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
@mcp.tool(description="Remove old snapshots from an Iceberg table")
|
|
180
|
+
def expire_snapshots(
|
|
181
|
+
catalog: str = Field(description="catalog name "),
|
|
182
|
+
schema_name: str = Field(description="schema name "),
|
|
183
|
+
retention_threshold: str = Field(
|
|
184
|
+
description="Age threshold for snapshot removal (e.g., '7d', '30d')", default="7d"
|
|
185
|
+
),
|
|
186
|
+
table: str = Field(description="The name of the table"),
|
|
187
|
+
) -> str:
|
|
188
|
+
"""Remove old snapshots from an Iceberg table.
|
|
189
|
+
|
|
190
|
+
Args:
|
|
191
|
+
catalog: catalog name
|
|
192
|
+
schema_name: schema name
|
|
193
|
+
table: The name of the table
|
|
194
|
+
retention_threshold: Age threshold for snapshot removal (e.g., "7d", "30d")
|
|
195
|
+
|
|
196
|
+
Returns:
|
|
197
|
+
str: Confirmation message
|
|
198
|
+
"""
|
|
199
|
+
return client.expire_snapshots(catalog, schema_name, table, retention_threshold)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
@mcp.tool(description="Show statistics for a table")
|
|
203
|
+
def show_stats(
|
|
204
|
+
catalog: str = Field(description="catalog name "),
|
|
205
|
+
schema_name: str = Field(description="schema name "),
|
|
206
|
+
table: str = Field(description="The name of the table"),
|
|
207
|
+
) -> str:
|
|
208
|
+
"""Show statistics for a table.
|
|
209
|
+
|
|
210
|
+
Args:
|
|
211
|
+
catalog: catalog name
|
|
212
|
+
schema_name: schema name
|
|
213
|
+
table: The name of the table
|
|
214
|
+
|
|
215
|
+
Returns:
|
|
216
|
+
str: Table statistics in JSON format
|
|
217
|
+
"""
|
|
218
|
+
return client.show_stats(catalog, schema_name, table)
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
@mcp.tool(name="show_query_history", description="Get the history of executed queries")
|
|
222
|
+
def show_query_history(
|
|
223
|
+
limit: int = Field(description="maximum number of history entries to return", default=None),
|
|
224
|
+
) -> str:
|
|
225
|
+
"""Get the history of executed queries.
|
|
226
|
+
|
|
227
|
+
Args:
|
|
228
|
+
limit: maximum number of history entries to return.
|
|
229
|
+
If None, returns all entries.
|
|
230
|
+
|
|
231
|
+
Returns:
|
|
232
|
+
str: JSON-formatted string containing query history.
|
|
233
|
+
"""
|
|
234
|
+
return client.get_query_history(limit)
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
@mcp.tool(description="Show a hierarchical tree view of catalogs, schemas, and tables")
|
|
238
|
+
def show_catalog_tree() -> str:
|
|
239
|
+
"""Get a hierarchical tree view showing the full structure of catalogs, schemas, and tables.
|
|
240
|
+
|
|
241
|
+
Returns:
|
|
242
|
+
str: A formatted string showing the catalog > schema > table hierarchy with visual indicators
|
|
243
|
+
"""
|
|
244
|
+
return client.show_catalog_tree()
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
@mcp.tool(description="Show Iceberg table properties")
|
|
248
|
+
def show_table_properties(
|
|
249
|
+
catalog: str = Field(description="catalog name "),
|
|
250
|
+
schema_name: str = Field(description="schema name "),
|
|
251
|
+
table: str = Field(description="The name of the table"),
|
|
252
|
+
) -> str:
|
|
253
|
+
"""Show Iceberg table properties.
|
|
254
|
+
|
|
255
|
+
Args:
|
|
256
|
+
catalog: catalog name
|
|
257
|
+
schema_name: schema name
|
|
258
|
+
table: The name of the table
|
|
259
|
+
|
|
260
|
+
Returns:
|
|
261
|
+
str: JSON-formatted table properties
|
|
262
|
+
"""
|
|
263
|
+
return client.show_table_properties(catalog, schema_name, table)
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
@mcp.tool(description="Show Iceberg table history/changelog")
|
|
267
|
+
def show_table_history(
|
|
268
|
+
catalog: str = Field(description="catalog name "),
|
|
269
|
+
schema_name: str = Field(description="schema name "),
|
|
270
|
+
table: str = Field(description="The name of the table"),
|
|
271
|
+
) -> str:
|
|
272
|
+
"""Show Iceberg table history/changelog.
|
|
273
|
+
|
|
274
|
+
The history contains:
|
|
275
|
+
- made_current_at: When snapshot became active
|
|
276
|
+
- snapshot_id: Identifier of the snapshot
|
|
277
|
+
- parent_id: Identifier of the parent snapshot
|
|
278
|
+
- is_current_ancestor: Whether snapshot is an ancestor of current
|
|
279
|
+
|
|
280
|
+
Args:
|
|
281
|
+
catalog: catalog name
|
|
282
|
+
schema_name: schema name
|
|
283
|
+
table: The name of the table
|
|
284
|
+
|
|
285
|
+
Returns:
|
|
286
|
+
str: JSON-formatted table history
|
|
287
|
+
"""
|
|
288
|
+
return client.show_table_history(catalog, schema_name, table)
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
@mcp.tool(description="Show metadata for the table")
|
|
292
|
+
def show_metadata_log_entries(
|
|
293
|
+
catalog: str = Field(description="catalog name "),
|
|
294
|
+
schema_name: str = Field(description="schema name "),
|
|
295
|
+
table: str = Field(description="The name of the table"),
|
|
296
|
+
) -> str:
|
|
297
|
+
"""Show Iceberg table metadata log entries.
|
|
298
|
+
|
|
299
|
+
The metadata log contains:
|
|
300
|
+
- timestamp: When metadata was created
|
|
301
|
+
- file: Location of the metadata file
|
|
302
|
+
- latest_snapshot_id: ID of latest snapshot when metadata was updated
|
|
303
|
+
- latest_schema_id: ID of latest schema when metadata was updated
|
|
304
|
+
- latest_sequence_number: Data sequence number of metadata file
|
|
305
|
+
|
|
306
|
+
Args:
|
|
307
|
+
catalog: catalog name
|
|
308
|
+
schema_name: schema name
|
|
309
|
+
table: The name of the table
|
|
310
|
+
|
|
311
|
+
Returns:
|
|
312
|
+
str: JSON-formatted metadata log entries
|
|
313
|
+
"""
|
|
314
|
+
return client.show_metadata_log_entries(catalog, schema_name, table)
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
@mcp.tool(description="Show Iceberg table snapshots")
|
|
318
|
+
def show_snapshots(
|
|
319
|
+
catalog: str = Field(description="catalog name "),
|
|
320
|
+
schema_name: str = Field(description="schema name "),
|
|
321
|
+
table: str = Field(description="The name of the table"),
|
|
322
|
+
) -> str:
|
|
323
|
+
"""Show Iceberg table snapshots.
|
|
324
|
+
|
|
325
|
+
The snapshots table contains:
|
|
326
|
+
- committed_at: When snapshot became active
|
|
327
|
+
- snapshot_id: Identifier for the snapshot
|
|
328
|
+
- parent_id: Identifier for the parent snapshot
|
|
329
|
+
- operation: Type of operation (append/replace/overwrite/delete)
|
|
330
|
+
- manifest_list: List of Avro manifest files
|
|
331
|
+
- summary: Summary of changes from previous snapshot
|
|
332
|
+
|
|
333
|
+
Args:
|
|
334
|
+
catalog: catalog name
|
|
335
|
+
schema_name: schema name
|
|
336
|
+
table: The name of the table
|
|
337
|
+
|
|
338
|
+
Returns:
|
|
339
|
+
str: JSON-formatted table snapshots
|
|
340
|
+
"""
|
|
341
|
+
return client.show_snapshots(catalog, schema_name, table)
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
@mcp.tool(description="Show Iceberg table manifests")
|
|
345
|
+
def show_manifests(
|
|
346
|
+
catalog: str = Field(description="catalog name"),
|
|
347
|
+
schema_name: str = Field(description="schema name"),
|
|
348
|
+
table: str = Field(description="The name of the table"),
|
|
349
|
+
all_snapshots: bool = False,
|
|
350
|
+
) -> str:
|
|
351
|
+
"""Show Iceberg table manifests for current or all snapshots.
|
|
352
|
+
|
|
353
|
+
The manifests table contains:
|
|
354
|
+
- path: Manifest file location
|
|
355
|
+
- length: Manifest file length
|
|
356
|
+
- partition_spec_id: ID of partition spec used
|
|
357
|
+
- added_snapshot_id: ID of snapshot when manifest was added
|
|
358
|
+
- added_data_files_count: Number of data files with status ADDED
|
|
359
|
+
- added_rows_count: Total rows in ADDED files
|
|
360
|
+
- existing_data_files_count: Number of EXISTING files
|
|
361
|
+
- existing_rows_count: Total rows in EXISTING files
|
|
362
|
+
- deleted_data_files_count: Number of DELETED files
|
|
363
|
+
- deleted_rows_count: Total rows in DELETED files
|
|
364
|
+
- partition_summaries: Partition range metadata
|
|
365
|
+
|
|
366
|
+
Args:
|
|
367
|
+
catalog: catalog name
|
|
368
|
+
schema_name: schema name
|
|
369
|
+
table: The name of the table
|
|
370
|
+
all_snapshots: If True, show manifests from all snapshots
|
|
371
|
+
|
|
372
|
+
Returns:
|
|
373
|
+
str: JSON-formatted table manifests
|
|
374
|
+
"""
|
|
375
|
+
return client.show_manifests(catalog, schema_name, table, all_snapshots)
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
@mcp.tool(description="Show Iceberg table partitions")
|
|
379
|
+
def show_partitions(
|
|
380
|
+
catalog: str = Field(description="catalog name "),
|
|
381
|
+
schema_name: str = Field(description="schema name "),
|
|
382
|
+
table: str = Field(description="The name of the table"),
|
|
383
|
+
) -> str:
|
|
384
|
+
"""Show Iceberg table partitions.
|
|
385
|
+
|
|
386
|
+
The partitions table contains:
|
|
387
|
+
- partition: Mapping of partition column names to values
|
|
388
|
+
- record_count: Number of records in partition
|
|
389
|
+
- file_count: Number of files in partition
|
|
390
|
+
- total_size: Total size of files in partition
|
|
391
|
+
- data: Partition range metadata with min/max values and null/nan counts
|
|
392
|
+
|
|
393
|
+
Args:
|
|
394
|
+
catalog: catalog name
|
|
395
|
+
schema_name: schema name
|
|
396
|
+
table: The name of the table
|
|
397
|
+
|
|
398
|
+
Returns:
|
|
399
|
+
str: JSON-formatted table partitions
|
|
400
|
+
"""
|
|
401
|
+
return client.show_partitions(catalog, schema_name, table)
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
@mcp.tool(description="Show Iceberg table data files")
|
|
405
|
+
def show_files(
|
|
406
|
+
catalog: str = Field(description="catalog name "),
|
|
407
|
+
schema_name: str = Field(description="schema name "),
|
|
408
|
+
table: str = Field(description="The name of the table"),
|
|
409
|
+
) -> str:
|
|
410
|
+
"""Show Iceberg table data files in current snapshot.
|
|
411
|
+
|
|
412
|
+
The files table contains:
|
|
413
|
+
- content: Type of content (0=DATA, 1=POSITION_DELETES, 2=EQUALITY_DELETES)
|
|
414
|
+
- file_path: Data file location
|
|
415
|
+
- file_format: Format of the data file
|
|
416
|
+
- record_count: Number of records in file
|
|
417
|
+
- file_size_in_bytes: File size
|
|
418
|
+
- column_sizes: Column ID to size mapping
|
|
419
|
+
- value_counts: Column ID to value count mapping
|
|
420
|
+
- null_value_counts: Column ID to null count mapping
|
|
421
|
+
- nan_value_counts: Column ID to NaN count mapping
|
|
422
|
+
- lower_bounds: Column ID to lower bound mapping
|
|
423
|
+
- upper_bounds: Column ID to upper bound mapping
|
|
424
|
+
- key_metadata: Encryption key metadata
|
|
425
|
+
- split_offsets: Recommended split locations
|
|
426
|
+
- equality_ids: Field IDs for equality deletes
|
|
427
|
+
|
|
428
|
+
Args:
|
|
429
|
+
catalog: catalog name
|
|
430
|
+
schema_name: schema name
|
|
431
|
+
table: The name of the table
|
|
432
|
+
|
|
433
|
+
Returns:
|
|
434
|
+
str: JSON-formatted table files info
|
|
435
|
+
"""
|
|
436
|
+
return client.show_files(catalog, schema_name, table)
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
@mcp.tool(description="Show Iceberg table manifest entries")
|
|
440
|
+
def show_entries(
|
|
441
|
+
catalog: str = Field(description="catalog name "),
|
|
442
|
+
schema_name: str = Field(description="schema name "),
|
|
443
|
+
table: str = Field(description="The name of the table"),
|
|
444
|
+
all_snapshots: bool = False,
|
|
445
|
+
) -> str:
|
|
446
|
+
"""Show Iceberg table manifest entries for current or all snapshots.
|
|
447
|
+
|
|
448
|
+
The entries table contains:
|
|
449
|
+
- status: Status of entry (0=EXISTING, 1=ADDED, 2=DELETED)
|
|
450
|
+
- snapshot_id: ID of the snapshot
|
|
451
|
+
- sequence_number: Data sequence number
|
|
452
|
+
- file_sequence_number: File sequence number
|
|
453
|
+
- data_file: File metadata including path, format, size etc
|
|
454
|
+
- readable_metrics: Human-readable file metrics
|
|
455
|
+
|
|
456
|
+
Args:
|
|
457
|
+
catalog: catalog name
|
|
458
|
+
schema_name: schema name
|
|
459
|
+
table: The name of the table
|
|
460
|
+
all_snapshots: If True, show entries from all snapshots
|
|
461
|
+
|
|
462
|
+
Returns:
|
|
463
|
+
str: JSON-formatted manifest entries
|
|
464
|
+
"""
|
|
465
|
+
return client.show_entries(catalog, schema_name, table, all_snapshots)
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
@mcp.tool(description="Show Iceberg table references (branches and tags)")
|
|
469
|
+
def show_refs(
|
|
470
|
+
catalog: str = Field(description="catalog name "),
|
|
471
|
+
schema_name: str = Field(description="schema name "),
|
|
472
|
+
table: str = Field(description="The name of the table"),
|
|
473
|
+
) -> str:
|
|
474
|
+
"""Show Iceberg table references (branches and tags).
|
|
475
|
+
|
|
476
|
+
The refs table contains:
|
|
477
|
+
- name: Name of the reference
|
|
478
|
+
- type: Type of reference (BRANCH or TAG)
|
|
479
|
+
- snapshot_id: ID of referenced snapshot
|
|
480
|
+
- max_reference_age_in_ms: Max age before reference expiry
|
|
481
|
+
- min_snapshots_to_keep: Min snapshots to keep (branches only)
|
|
482
|
+
- max_snapshot_age_in_ms: Max snapshot age in branch
|
|
483
|
+
|
|
484
|
+
Args:
|
|
485
|
+
catalog: catalog name
|
|
486
|
+
schema_name: schema name
|
|
487
|
+
table: The name of the table
|
|
488
|
+
|
|
489
|
+
Returns:
|
|
490
|
+
str: JSON-formatted table references
|
|
491
|
+
"""
|
|
492
|
+
return client.show_refs(catalog, schema_name, table)
|
|
493
|
+
|
|
494
|
+
|
|
495
|
+
# Prompts
|
|
496
|
+
@mcp.prompt(title="Explore Trino Data")
|
|
497
|
+
def explore_data(catalog: str, schema_name: str) -> list[base.Message]:
|
|
498
|
+
"""Interactive prompt to explore Trino data.
|
|
499
|
+
|
|
500
|
+
Args:
|
|
501
|
+
catalog: The name of the catalog to explore
|
|
502
|
+
schema_name: The name of the schema to explore
|
|
503
|
+
|
|
504
|
+
Returns:
|
|
505
|
+
list[base.Message]: A list of messages to guide the conversation
|
|
506
|
+
"""
|
|
507
|
+
messages = [
|
|
508
|
+
base.AssistantMessage(
|
|
509
|
+
"I'll help you explore data in Trino. I can show you available catalogs, "
|
|
510
|
+
"schemas, and tables, and help you query the data."
|
|
511
|
+
)
|
|
512
|
+
]
|
|
513
|
+
|
|
514
|
+
if catalog and schema_name:
|
|
515
|
+
messages.append(
|
|
516
|
+
base.UserMessage(
|
|
517
|
+
f"Show me what tables are available in the {catalog}.{schema_name} schema and help me query them."
|
|
518
|
+
)
|
|
519
|
+
)
|
|
520
|
+
elif catalog:
|
|
521
|
+
messages.append(base.UserMessage(f"Show me what schemas are available in the {catalog} catalog."))
|
|
522
|
+
else:
|
|
523
|
+
messages.append(base.UserMessage("Show me what catalogs are available."))
|
|
524
|
+
|
|
525
|
+
return messages
|
|
526
|
+
|
|
527
|
+
|
|
528
|
+
@mcp.prompt(title="Maintain Iceberg Table")
|
|
529
|
+
def maintain_iceberg(table: str, catalog: str, schema_name: str) -> list[base.Message]:
|
|
530
|
+
"""Interactive prompt for Iceberg table maintenance."""
|
|
531
|
+
return [
|
|
532
|
+
base.AssistantMessage(
|
|
533
|
+
"I'll help you maintain an Iceberg table. I can help with optimization, "
|
|
534
|
+
"cleaning up snapshots and orphan files, and viewing table metadata."
|
|
535
|
+
),
|
|
536
|
+
base.UserMessage(
|
|
537
|
+
f"What maintenance operations should we perform on the Iceberg table "
|
|
538
|
+
f"{catalog + '.' if catalog else ''}{schema_name + '.' if schema_name else ''}{table}?"
|
|
539
|
+
),
|
|
540
|
+
]
|
|
541
|
+
|
|
542
|
+
|
|
543
|
+
if __name__ == "__main__":
|
|
544
|
+
import argparse
|
|
545
|
+
import sys
|
|
546
|
+
|
|
547
|
+
from loguru import logger
|
|
548
|
+
|
|
549
|
+
# Configure logging
|
|
550
|
+
logger.remove()
|
|
551
|
+
logger.add(sys.stderr, level="INFO", format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {message}")
|
|
552
|
+
|
|
553
|
+
parser = argparse.ArgumentParser(
|
|
554
|
+
description="MCP Trino Server - Model Context Protocol connector for Trino",
|
|
555
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
556
|
+
epilog="""
|
|
557
|
+
Transport modes:
|
|
558
|
+
stdio Standard I/O (default) - for local MCP clients like VS Code
|
|
559
|
+
streamable-http HTTP with streaming support (recommended for remote/web access)
|
|
560
|
+
sse Server-Sent Events (legacy HTTP transport)
|
|
561
|
+
|
|
562
|
+
Examples:
|
|
563
|
+
# Run with stdio (default, for VS Code integration)
|
|
564
|
+
python server.py
|
|
565
|
+
|
|
566
|
+
# Run with Streamable HTTP on port 8000
|
|
567
|
+
python server.py --transport streamable-http --host 0.0.0.0 --port 8000
|
|
568
|
+
|
|
569
|
+
# Run with SSE transport
|
|
570
|
+
python server.py --transport sse --host 127.0.0.1 --port 8001
|
|
571
|
+
|
|
572
|
+
Environment variables:
|
|
573
|
+
MCP_HOST Default host for HTTP transports (default: 127.0.0.1)
|
|
574
|
+
MCP_PORT Default port for HTTP transports (default: 8000)
|
|
575
|
+
""",
|
|
576
|
+
)
|
|
577
|
+
parser.add_argument(
|
|
578
|
+
"--host",
|
|
579
|
+
default=None,
|
|
580
|
+
help="Host to bind to (default: 127.0.0.1, use 0.0.0.0 for all interfaces)",
|
|
581
|
+
)
|
|
582
|
+
parser.add_argument(
|
|
583
|
+
"--port",
|
|
584
|
+
type=int,
|
|
585
|
+
default=None,
|
|
586
|
+
help="Port to listen on (default: 8000)",
|
|
587
|
+
)
|
|
588
|
+
parser.add_argument(
|
|
589
|
+
"--transport",
|
|
590
|
+
choices=["stdio", "streamable-http", "sse"],
|
|
591
|
+
default="stdio",
|
|
592
|
+
help="Transport type (default: stdio)",
|
|
593
|
+
)
|
|
594
|
+
args = parser.parse_args()
|
|
595
|
+
|
|
596
|
+
# Update settings if CLI args provided (override env vars)
|
|
597
|
+
if args.host:
|
|
598
|
+
mcp.settings.host = args.host
|
|
599
|
+
if args.port:
|
|
600
|
+
mcp.settings.port = args.port
|
|
601
|
+
|
|
602
|
+
logger.info(f"Starting Trino MCP server with {args.transport} transport")
|
|
603
|
+
|
|
604
|
+
if args.transport == "stdio":
|
|
605
|
+
logger.info("Using stdio transport for local MCP communication")
|
|
606
|
+
mcp.run(transport="stdio")
|
|
607
|
+
elif args.transport == "streamable-http":
|
|
608
|
+
logger.info(f"Starting Streamable HTTP server on http://{mcp.settings.host}:{mcp.settings.port}/mcp")
|
|
609
|
+
mcp.run(transport="streamable-http")
|
|
610
|
+
elif args.transport == "sse":
|
|
611
|
+
logger.info(f"Starting SSE server on http://{mcp.settings.host}:{mcp.settings.port}/sse")
|
|
612
|
+
mcp.run(transport="sse")
|
|
613
|
+
|
|
614
|
+
|
|
615
|
+
def main():
|
|
616
|
+
"""Entry point for the MCP Trino server."""
|
|
617
|
+
mcp.run(transport="stdio")
|