datris-mcp-server 1.2.0__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.
@@ -0,0 +1,124 @@
1
+ Metadata-Version: 2.4
2
+ Name: datris-mcp-server
3
+ Version: 1.2.0
4
+ Summary: MCP server for the Datris AI Agent-Native Data Platform
5
+ Project-URL: Homepage, https://datris.ai
6
+ Project-URL: Repository, https://github.com/datris/datris-platform-oss
7
+ Project-URL: Documentation, https://github.com/datris/datris-platform-oss/blob/main/docs/mcp.md
8
+ Project-URL: Issues, https://github.com/datris/datris-platform-oss/issues
9
+ Author-email: Datris <info@datris.ai>
10
+ License: Apache-2.0
11
+ Keywords: ai-agent,data-platform,datris,mcp,model-context-protocol
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: Apache Software License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Database
20
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
21
+ Classifier: Topic :: Software Development :: Libraries
22
+ Requires-Python: >=3.10
23
+ Requires-Dist: mcp[cli]
24
+ Requires-Dist: python-dotenv
25
+ Requires-Dist: requests
26
+ Requires-Dist: starlette
27
+ Requires-Dist: uvicorn
28
+ Description-Content-Type: text/markdown
29
+
30
+ # Datris MCP Server
31
+
32
+ MCP (Model Context Protocol) server for the [Datris AI Agent-Native Data Platform](https://datris.ai). Enables AI agents (Claude Desktop, Claude Code, Cursor, and custom frameworks) to natively interact with the platform — discover data, create pipelines, upload files, monitor jobs, search vector databases, query structured data, and answer questions with AI.
33
+
34
+ <!-- mcp-name: io.github.datris/datris -->
35
+
36
+ ## Install
37
+
38
+ ```bash
39
+ pip install datris-mcp-server
40
+ ```
41
+
42
+ ## Usage
43
+
44
+ ### stdio mode (Claude Desktop / Claude Code)
45
+
46
+ ```bash
47
+ PIPELINE_URL=http://localhost:8080 datris-mcp-server
48
+ ```
49
+
50
+ Or run directly:
51
+
52
+ ```bash
53
+ PIPELINE_URL=http://localhost:8080 python server.py
54
+ ```
55
+
56
+ ### SSE mode (Docker / remote agents)
57
+
58
+ ```bash
59
+ PIPELINE_URL=http://localhost:8080 datris-mcp-server --sse --port 3000
60
+ ```
61
+
62
+ ### Docker
63
+
64
+ The MCP server starts automatically with `docker compose up` in SSE mode on port 3000.
65
+
66
+ ## Configuration
67
+
68
+ ### Claude Desktop
69
+
70
+ Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
71
+
72
+ ```json
73
+ {
74
+ "mcpServers": {
75
+ "datris": {
76
+ "command": "datris-mcp-server",
77
+ "env": {
78
+ "PIPELINE_URL": "http://localhost:8080"
79
+ }
80
+ }
81
+ }
82
+ }
83
+ ```
84
+
85
+ ### Claude Code
86
+
87
+ Add to `.mcp.json` in your project root:
88
+
89
+ ```json
90
+ {
91
+ "mcpServers": {
92
+ "datris": {
93
+ "command": "datris-mcp-server",
94
+ "env": {
95
+ "PIPELINE_URL": "http://localhost:8080"
96
+ }
97
+ }
98
+ }
99
+ }
100
+ ```
101
+
102
+ ## Environment Variables
103
+
104
+ | Variable | Default | Description |
105
+ |----------|---------|-------------|
106
+ | `PIPELINE_URL` | `http://localhost:8080` | Datris pipeline server URL |
107
+ | `PIPELINE_API_KEY` | (empty) | API key if pipeline has key validation enabled |
108
+
109
+ ## Tools
110
+
111
+ 30+ tools across these categories:
112
+
113
+ - **Pipeline Management** — create, list, get, delete pipelines; upload files; monitor jobs
114
+ - **Vector Search** — semantic search across Qdrant, Weaviate, Milvus, Chroma, pgvector
115
+ - **Database Query** — read-only SQL queries (PostgreSQL) and MongoDB queries
116
+ - **Metadata Discovery** — explore databases, schemas, tables, columns, collections
117
+ - **AI** — RAG-powered question answering
118
+ - **System** — health checks, version info
119
+
120
+ See the full documentation at [docs/mcp.md](https://github.com/datris/datris-platform-oss/blob/main/docs/mcp.md).
121
+
122
+ ## License
123
+
124
+ Apache 2.0
@@ -0,0 +1,5 @@
1
+ server.py,sha256=HlC6y79o67MhyFDn6LWRozn7JhHaEpHenmZj3x9IwKU,44245
2
+ datris_mcp_server-1.2.0.dist-info/METADATA,sha256=3fWcS34zczHBleEEajduUDLzrpNkd5iXrzlElPJOcS4,3537
3
+ datris_mcp_server-1.2.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
4
+ datris_mcp_server-1.2.0.dist-info/entry_points.txt,sha256=sKsVHwslKpo51XRmkc94nvCyRVeHmQ9cbI0YE-6hN8Y,50
5
+ datris_mcp_server-1.2.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.29.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ datris-mcp-server = server:main
server.py ADDED
@@ -0,0 +1,1211 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Datris MCP Server
4
+
5
+ A thin REST API client that exposes the Datris platform as MCP tools so AI agents
6
+ (Claude, Cursor, etc.) can natively interact with the platform — discover data,
7
+ create pipelines, upload files, monitor jobs, search vector databases, query
8
+ structured data, and answer questions with AI.
9
+
10
+ Usage:
11
+ pip install -r requirements.txt
12
+
13
+ # stdio mode (for Claude Desktop / Claude Code)
14
+ python server.py
15
+
16
+ # SSE mode (for Docker / remote agents)
17
+ python server.py --sse --port 3000
18
+ """
19
+
20
+ import argparse
21
+ import json
22
+ import os
23
+ from typing import Any
24
+
25
+ import requests
26
+ from dotenv import load_dotenv
27
+ from mcp.server import Server
28
+ from mcp.types import Resource, Tool, TextContent
29
+
30
+ load_dotenv()
31
+
32
+ PIPELINE_URL = os.getenv("PIPELINE_URL", "http://localhost:8080")
33
+ PIPELINE_API_KEY = os.getenv("PIPELINE_API_KEY", "")
34
+
35
+ server = Server("datris", instructions="""\
36
+ Datris is the first AI Agent-Native Data Platform. It ingests, validates, transforms, and routes data to databases, message queues, and vector stores — all driven by pipeline configurations that AI agents can create and manage programmatically.
37
+
38
+ A pipeline defines a complete data processing flow:
39
+ - Source: file format (CSV, JSON, XML), schema with field names and types
40
+ - Data Quality: validation rules including regex patterns, AI-powered rules, and JSON Schema validation
41
+ - Transformation: deduplication, trimming, AI transforms, and JavaScript row functions
42
+ - Preprocessor: optional REST endpoint called before processing
43
+ - Destination: PostgreSQL, MongoDB, Kafka, ActiveMQ, REST endpoint, or vector databases (Qdrant, Weaviate, Milvus, Chroma, pgvector) with embedding configuration
44
+
45
+ Recommended workflow for agents:
46
+ 1. Discover existing data: use metadata tools (list_postgres_databases, list_postgres_tables, etc.) to explore what's available
47
+ 2. Create a pipeline: use generate_schema to auto-create from a sample file, or create_pipeline manually
48
+ 3. Profile data: use profile_data to get AI-suggested data quality rules before ingestion
49
+ 4. Ingest data: use upload_file to process files through the pipeline
50
+ 5. Monitor: use get_job_status to track processing (status: RUNNING, COMPLETED, FAILED, CANCELLED)
51
+ 6. Query & search: use query tools for structured data, search tools for semantic/vector search
52
+ 7. RAG: combine search results with ai_answer for AI-powered question answering
53
+ """)
54
+
55
+
56
+ def _headers():
57
+ """Build request headers."""
58
+ h = {"Content-Type": "application/json"}
59
+ if PIPELINE_API_KEY:
60
+ h["x-api-key"] = PIPELINE_API_KEY
61
+ return h
62
+
63
+
64
+ def _call(method, path, **kwargs):
65
+ """Make an HTTP request to the pipeline API."""
66
+ url = f"{PIPELINE_URL}{path}"
67
+ try:
68
+ resp = getattr(requests, method)(url, headers=_headers(), timeout=300, **kwargs)
69
+ return resp.text
70
+ except requests.RequestException as e:
71
+ return json.dumps({"error": str(e)})
72
+
73
+
74
+ def _upload(path, file_path, data=None):
75
+ """Upload a file via multipart POST to the pipeline API."""
76
+ with open(file_path, "rb") as f:
77
+ files = {"file": (os.path.basename(file_path), f)}
78
+ h = {}
79
+ if PIPELINE_API_KEY:
80
+ h["x-api-key"] = PIPELINE_API_KEY
81
+ resp = requests.post(
82
+ f"{PIPELINE_URL}{path}",
83
+ headers=h,
84
+ files=files,
85
+ data=data or {},
86
+ timeout=300
87
+ )
88
+ return resp.text
89
+
90
+
91
+ # ---------------------------------------------------------------------------
92
+ # MCP Resources
93
+ # ---------------------------------------------------------------------------
94
+
95
+ PIPELINE_CONFIG_REFERENCE = """\
96
+ # Datris Pipeline Configuration Reference
97
+
98
+ A pipeline defines a complete data processing flow. The config JSON has these top-level sections:
99
+
100
+ ```json
101
+ {
102
+ "name": "pipeline_name",
103
+ "source": { ... },
104
+ "preprocessor": { ... },
105
+ "dataQuality": { ... },
106
+ "transformation": { ... },
107
+ "destination": { ... }
108
+ }
109
+ ```
110
+
111
+ ## Source
112
+
113
+ Defines the input file format and schema.
114
+
115
+ ### fileAttributes (choose one)
116
+
117
+ **csvAttributes** — for CSV files:
118
+ ```json
119
+ "csvAttributes": {
120
+ "delimiter": ",",
121
+ "header": true,
122
+ "encoding": "UTF-8"
123
+ }
124
+ ```
125
+
126
+ **jsonAttributes** — for JSON files:
127
+ ```json
128
+ "jsonAttributes": {
129
+ "everyRowContainsObject": false,
130
+ "encoding": "UTF-8"
131
+ }
132
+ ```
133
+
134
+ **xmlAttributes** — for XML files:
135
+ ```json
136
+ "xmlAttributes": {
137
+ "everyRowContainsObject": false,
138
+ "encoding": "UTF-8"
139
+ }
140
+ ```
141
+
142
+ **xlsAttributes** — for Excel files:
143
+ ```json
144
+ "xlsAttributes": {
145
+ "worksheet": 0,
146
+ "tempCsvFileDelimiter": ","
147
+ }
148
+ ```
149
+
150
+ **unstructuredAttributes** — for PDFs, DOCX, TXT (used with vector DB destinations):
151
+ ```json
152
+ "unstructuredAttributes": {
153
+ "fileExtension": "pdf",
154
+ "preserveFilename": true
155
+ }
156
+ ```
157
+
158
+ ### schemaProperties (required for structured data, omit for unstructured)
159
+
160
+ ```json
161
+ "schemaProperties": {
162
+ "fields": [
163
+ {"name": "column_name", "type": "string"},
164
+ {"name": "price", "type": "double"},
165
+ {"name": "quantity", "type": "int"}
166
+ ]
167
+ }
168
+ ```
169
+
170
+ Supported field types: `string`, `int`, `bigint`, `float`, `double`, `boolean`, `date`, `timestamp`
171
+
172
+ ### streamAttributes (optional, for streaming sources like Kafka)
173
+
174
+ ```json
175
+ "streamAttributes": {
176
+ "type": "kafka"
177
+ }
178
+ ```
179
+
180
+ ### databaseAttributes (optional, for database pull sources)
181
+
182
+ ```json
183
+ "databaseAttributes": {
184
+ "type": "postgres",
185
+ "postgresSecretsName": "oss/postgres",
186
+ "database": "mydb",
187
+ "schema": "public",
188
+ "table": "source_table",
189
+ "cronExpression": "0 0 * * *",
190
+ "timestampFieldName": "updated_at",
191
+ "includeFields": ["col1", "col2"],
192
+ "sqlOverride": "SELECT * FROM source_table WHERE active = true"
193
+ }
194
+ ```
195
+
196
+ ## Preprocessor (optional)
197
+
198
+ A REST endpoint called before processing each file. Use for custom validation or enrichment.
199
+
200
+ ```json
201
+ "preprocessor": {
202
+ "endpoint": "https://my-service.example.com/preprocess",
203
+ "bearerToken": "token123",
204
+ "apiKey": "key123",
205
+ "timeoutMs": 300000,
206
+ "async": false
207
+ }
208
+ ```
209
+
210
+ ## Data Quality (optional)
211
+
212
+ Validation rules applied to data before transformation and destination.
213
+
214
+ ### columnRules — regex or function-based validation per column
215
+
216
+ ```json
217
+ "columnRules": [
218
+ {
219
+ "columnName": "email",
220
+ "function": "regex",
221
+ "parameter": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\\\.[a-zA-Z]{2,}$",
222
+ "onFailureIsError": true,
223
+ "description": "Must be a valid email"
224
+ },
225
+ {
226
+ "columnName": "price",
227
+ "function": "regex",
228
+ "parameter": "^[0-9]+(\\\\.[0-9]+)?$",
229
+ "onFailureIsError": false,
230
+ "description": "Must be a positive number"
231
+ }
232
+ ]
233
+ ```
234
+
235
+ ### aiRule — AI-powered validation using natural language
236
+
237
+ ```json
238
+ "aiRule": {
239
+ "instruction": "All price columns must be positive and not exceed $1,000,000. Volume must be a positive integer.",
240
+ "onFailureIsError": false,
241
+ "sample": true,
242
+ "sampleSize": 200
243
+ }
244
+ ```
245
+
246
+ ### validationSchema — JSON Schema file reference
247
+
248
+ ```json
249
+ "validationSchema": "my-schema.json"
250
+ ```
251
+
252
+ Upload the schema file first using the `upload_config` tool with type "validation-schema".
253
+
254
+ ### validateFileHeader — check CSV headers match schema
255
+
256
+ ```json
257
+ "validateFileHeader": true
258
+ ```
259
+
260
+ ## Transformation (optional)
261
+
262
+ Transform data after validation, before writing to destination.
263
+
264
+ ### Basic transformations
265
+
266
+ ```json
267
+ "transformation": {
268
+ "trimColumnWhitespace": true,
269
+ "deduplicate": true
270
+ }
271
+ ```
272
+
273
+ ### rowFunctions — JavaScript-based row transformations
274
+
275
+ ```json
276
+ "rowFunctions": [
277
+ {
278
+ "function": "javascript",
279
+ "parameters": ["transform.js"]
280
+ }
281
+ ]
282
+ ```
283
+
284
+ Upload the JS file first using the `upload_config` tool with type "javascript".
285
+
286
+ ### rowFunctions — REST endpoint transformations
287
+
288
+ ```json
289
+ "rowFunctions": [
290
+ {
291
+ "function": "restEndpoint",
292
+ "parameters": ["http://my-service:5600/transform", "row", "30000", "", ""]
293
+ }
294
+ ]
295
+ ```
296
+
297
+ Parameters: [endpoint URL, mode ("row" or "batch"), timeout ms, bearer token, API key].
298
+ In row mode, the endpoint receives `{"pipelineName": "...", "pipelineToken": "...", "row": {...}}` and returns `{"status": "success", "row": {...}}` (or `"row": null` to remove the row).
299
+ In batch mode, it receives `{"rows": [...]}` and returns `{"status": "success", "rows": [...]}` (null entries are removed).
300
+
301
+ ### aiTransformation — AI-powered data transformation
302
+
303
+ ```json
304
+ "aiTransformation": {
305
+ "instruction": "Convert all date values from MM/DD/YYYY to YYYY-MM-DD format",
306
+ "sample": true,
307
+ "sampleSize": 200
308
+ }
309
+ ```
310
+
311
+ ## Destination (required — choose one)
312
+
313
+ ### database — PostgreSQL
314
+
315
+ ```json
316
+ "destination": {
317
+ "database": {
318
+ "dbName": "datris",
319
+ "schema": "public",
320
+ "table": "my_table",
321
+ "usePostgres": true,
322
+ "keyFields": ["id"],
323
+ "truncateBeforeWrite": false,
324
+ "manageTableManually": false
325
+ }
326
+ }
327
+ ```
328
+
329
+ ### database — MongoDB
330
+
331
+ ```json
332
+ "destination": {
333
+ "database": {
334
+ "dbName": "datris",
335
+ "table": "my_collection",
336
+ "useMongoDB": true
337
+ }
338
+ }
339
+ ```
340
+
341
+ ### objectStore — S3/MinIO
342
+
343
+ ```json
344
+ "destination": {
345
+ "objectStore": {
346
+ "prefixKey": "data/output/",
347
+ "fileFormat": "parquet",
348
+ "writeMode": "overwrite",
349
+ "partitionBy": ["date", "region"]
350
+ }
351
+ }
352
+ ```
353
+
354
+ File formats: `parquet`, `csv`, `json`, `orc`
355
+ Write modes: `overwrite`, `append`, `ignore`, `error`
356
+
357
+ ### kafka
358
+
359
+ ```json
360
+ "destination": {
361
+ "kafka": {
362
+ "topic": "my-topic",
363
+ "keyField": "id"
364
+ }
365
+ }
366
+ ```
367
+
368
+ ### activeMQ
369
+
370
+ ```json
371
+ "destination": {
372
+ "activeMQ": {
373
+ "queueName": "my-queue"
374
+ }
375
+ }
376
+ ```
377
+
378
+ ### restEndpoint
379
+
380
+ ```json
381
+ "destination": {
382
+ "restEndpoint": {
383
+ "endpoint": "https://my-service.example.com/ingest",
384
+ "bearerToken": "token123",
385
+ "timeoutMs": 300000
386
+ }
387
+ }
388
+ ```
389
+
390
+ ### Vector databases (for RAG / semantic search)
391
+
392
+ All vector DB destinations share a common structure with chunking and embedding config. Use with unstructured source files (PDF, DOCX, TXT).
393
+
394
+ **qdrant:**
395
+ ```json
396
+ "destination": {
397
+ "qdrant": {
398
+ "collectionName": "my_documents",
399
+ "chunking": {"strategy": "recursive", "chunkSize": 500, "chunkOverlap": 50},
400
+ "metadata": {"source": "annual_reports", "year": "2026"},
401
+ "embeddingSecretName": "oss/embedding",
402
+ "qdrantSecretName": "oss/qdrant"
403
+ }
404
+ }
405
+ ```
406
+
407
+ **weaviate:**
408
+ ```json
409
+ "destination": {
410
+ "weaviate": {
411
+ "className": "MyDocuments",
412
+ "chunking": {"strategy": "recursive", "chunkSize": 500, "chunkOverlap": 50},
413
+ "metadata": {"source": "annual_reports"},
414
+ "embeddingSecretName": "oss/embedding",
415
+ "weaviateSecretName": "oss/weaviate"
416
+ }
417
+ }
418
+ ```
419
+
420
+ **milvus:**
421
+ ```json
422
+ "destination": {
423
+ "milvus": {
424
+ "collectionName": "my_documents",
425
+ "chunking": {"strategy": "recursive", "chunkSize": 500, "chunkOverlap": 50},
426
+ "metadata": {"source": "annual_reports"},
427
+ "embeddingSecretName": "oss/embedding",
428
+ "milvusSecretName": "oss/milvus"
429
+ }
430
+ }
431
+ ```
432
+
433
+ **chroma:**
434
+ ```json
435
+ "destination": {
436
+ "chroma": {
437
+ "collectionName": "my_documents",
438
+ "chunking": {"strategy": "recursive", "chunkSize": 500, "chunkOverlap": 50},
439
+ "metadata": {"source": "annual_reports"},
440
+ "embeddingSecretName": "oss/embedding",
441
+ "chromaSecretName": "oss/chroma"
442
+ }
443
+ }
444
+ ```
445
+
446
+ **pgvector:**
447
+ ```json
448
+ "destination": {
449
+ "pgvector": {
450
+ "tableName": "my_documents",
451
+ "schemaName": "public",
452
+ "chunking": {"strategy": "recursive", "chunkSize": 500, "chunkOverlap": 50},
453
+ "metadata": {"source": "annual_reports"},
454
+ "embeddingSecretName": "oss/embedding",
455
+ "postgresSecretName": "oss/pgvector"
456
+ }
457
+ }
458
+ ```
459
+
460
+ ## Complete Examples
461
+
462
+ ### CSV → PostgreSQL with AI data quality
463
+
464
+ ```json
465
+ {
466
+ "name": "stock_prices",
467
+ "source": {
468
+ "fileAttributes": {
469
+ "csvAttributes": {"delimiter": ",", "header": true, "encoding": "UTF-8"}
470
+ },
471
+ "schemaProperties": {
472
+ "fields": [
473
+ {"name": "symbol", "type": "string"},
474
+ {"name": "date", "type": "string"},
475
+ {"name": "close", "type": "double"},
476
+ {"name": "volume", "type": "int"}
477
+ ]
478
+ }
479
+ },
480
+ "dataQuality": {
481
+ "aiRule": {
482
+ "instruction": "All price columns must be positive. Volume must be a positive integer.",
483
+ "onFailureIsError": false
484
+ }
485
+ },
486
+ "destination": {
487
+ "database": {
488
+ "dbName": "datris",
489
+ "schema": "public",
490
+ "table": "stock_prices",
491
+ "usePostgres": true
492
+ }
493
+ }
494
+ }
495
+ ```
496
+
497
+ ### PDF → pgvector for RAG
498
+
499
+ ```json
500
+ {
501
+ "name": "financial_docs",
502
+ "source": {
503
+ "fileAttributes": {
504
+ "unstructuredAttributes": {"fileExtension": "pdf", "preserveFilename": true}
505
+ }
506
+ },
507
+ "destination": {
508
+ "pgvector": {
509
+ "tableName": "financial_documents",
510
+ "schemaName": "public",
511
+ "chunking": {"strategy": "recursive", "chunkSize": 500, "chunkOverlap": 50},
512
+ "metadata": {"company": "Acme Corp", "document_type": "10-K"},
513
+ "embeddingSecretName": "oss/embedding",
514
+ "postgresSecretName": "oss/pgvector"
515
+ }
516
+ }
517
+ }
518
+ ```
519
+
520
+ ### JSON → MongoDB
521
+
522
+ ```json
523
+ {
524
+ "name": "events",
525
+ "source": {
526
+ "fileAttributes": {
527
+ "jsonAttributes": {"everyRowContainsObject": false, "encoding": "UTF-8"}
528
+ },
529
+ "schemaProperties": {
530
+ "fields": [{"name": "_json", "type": "string"}]
531
+ }
532
+ },
533
+ "destination": {
534
+ "database": {
535
+ "dbName": "datris",
536
+ "table": "events",
537
+ "useMongoDB": true
538
+ }
539
+ }
540
+ }
541
+ ```
542
+ """
543
+
544
+
545
+ @server.list_resources()
546
+ async def list_resources():
547
+ return [
548
+ Resource(
549
+ uri="datris://pipeline-config-reference",
550
+ name="Pipeline Configuration Reference",
551
+ description="Complete reference for building Datris pipeline configurations. Covers all source types (CSV, JSON, XML, PDF), data quality rules (regex, AI), transformations, and all destination types (PostgreSQL, MongoDB, Kafka, vector databases). Read this before using create_pipeline.",
552
+ mimeType="text/plain",
553
+ )
554
+ ]
555
+
556
+
557
+ @server.read_resource()
558
+ async def read_resource(uri):
559
+ if str(uri) == "datris://pipeline-config-reference":
560
+ return PIPELINE_CONFIG_REFERENCE
561
+ raise ValueError(f"Unknown resource: {uri}")
562
+
563
+
564
+ # ---------------------------------------------------------------------------
565
+ # MCP Tools
566
+ # ---------------------------------------------------------------------------
567
+
568
+ @server.list_tools()
569
+ async def list_tools():
570
+ return [
571
+ # --- Pipeline Management ---
572
+ Tool(
573
+ name="list_pipelines",
574
+ description="List all registered pipeline configurations. Each pipeline defines a complete data processing flow: source format and schema, data quality rules (regex + AI), transformations, and destination (database, message queue, or vector store).",
575
+ inputSchema={
576
+ "type": "object",
577
+ "properties": {},
578
+ }
579
+ ),
580
+ Tool(
581
+ name="get_pipeline",
582
+ description="Get a specific pipeline configuration by name. Returns the full JSON config including source, dataQuality, transformation, preprocessor, and destination sections.",
583
+ inputSchema={
584
+ "type": "object",
585
+ "properties": {
586
+ "pipeline": {
587
+ "type": "string",
588
+ "description": "Pipeline name"
589
+ }
590
+ },
591
+ "required": ["pipeline"]
592
+ }
593
+ ),
594
+ Tool(
595
+ name="create_pipeline",
596
+ description="Create or update a pipeline configuration. The config JSON must include 'name' and typically includes: source (fileAttributes, schemaProperties with field definitions), dataQuality (column rules with regex, AI rules, schema validation), transformation (deduplication, trimming, AI transforms, row functions), and destination (database, objectStore, kafka, activeMQ, restEndpoint, or vector DB like qdrant/weaviate/milvus/chroma/pgvector). Read the 'Pipeline Configuration Reference' resource for full field details and examples. Use generate_schema to auto-create a config from a sample file.",
597
+ inputSchema={
598
+ "type": "object",
599
+ "properties": {
600
+ "config": {
601
+ "type": "object",
602
+ "description": "Full pipeline configuration JSON including name, source, destination, dataQuality, transformation"
603
+ }
604
+ },
605
+ "required": ["config"]
606
+ }
607
+ ),
608
+ Tool(
609
+ name="delete_pipeline",
610
+ description="Delete a registered pipeline configuration by name. This removes the config but does not delete any data already processed by the pipeline.",
611
+ inputSchema={
612
+ "type": "object",
613
+ "properties": {
614
+ "pipeline": {
615
+ "type": "string",
616
+ "description": "Pipeline name to delete"
617
+ }
618
+ },
619
+ "required": ["pipeline"]
620
+ }
621
+ ),
622
+ Tool(
623
+ name="upload_file",
624
+ description="Upload a data file (CSV, JSON, XML, or compressed archive) to a registered pipeline for processing. The pipeline's rules are applied: schema validation, data quality checks, transformations, then routing to the configured destination. Returns a pipeline token for tracking job status via get_job_status.",
625
+ inputSchema={
626
+ "type": "object",
627
+ "properties": {
628
+ "file_path": {
629
+ "type": "string",
630
+ "description": "Absolute path to the file to upload"
631
+ },
632
+ "pipeline": {
633
+ "type": "string",
634
+ "description": "Pipeline name to process the file with"
635
+ }
636
+ },
637
+ "required": ["file_path", "pipeline"]
638
+ }
639
+ ),
640
+ Tool(
641
+ name="get_job_status",
642
+ description="Get job status. Query by pipeline_token (from upload_file) for detailed status of a specific job, or by pipeline_name for a paginated summary of all jobs for that pipeline. Status values: RUNNING, COMPLETED, FAILED, CANCELLED.",
643
+ inputSchema={
644
+ "type": "object",
645
+ "properties": {
646
+ "pipeline_token": {
647
+ "type": "string",
648
+ "description": "Pipeline token returned from upload_file"
649
+ },
650
+ "pipeline_name": {
651
+ "type": "string",
652
+ "description": "Pipeline name to get latest status for"
653
+ },
654
+ "page": {
655
+ "type": "integer",
656
+ "description": "Page number for paginated results (default: 1)"
657
+ }
658
+ }
659
+ }
660
+ ),
661
+ Tool(
662
+ name="kill_job",
663
+ description="Kill a running pipeline job by its pipeline token. The job thread will be interrupted and the job marked as cancelled.",
664
+ inputSchema={
665
+ "type": "object",
666
+ "properties": {
667
+ "pipeline_token": {
668
+ "type": "string",
669
+ "description": "Pipeline token of the running job to kill"
670
+ }
671
+ },
672
+ "required": ["pipeline_token"]
673
+ }
674
+ ),
675
+ Tool(
676
+ name="generate_schema",
677
+ description="Upload a sample data file and use AI to automatically generate a complete pipeline configuration with inferred field names, data types, and schema. Supports CSV, JSON, and XML. This is the fastest way to create a new pipeline — generate the config, review/modify it, then register it with create_pipeline.",
678
+ inputSchema={
679
+ "type": "object",
680
+ "properties": {
681
+ "file_path": {
682
+ "type": "string",
683
+ "description": "Absolute path to the file to analyze"
684
+ },
685
+ "pipeline": {
686
+ "type": "string",
687
+ "description": "Pipeline name for the generated configuration"
688
+ },
689
+ "delimiter": {
690
+ "type": "string",
691
+ "description": "CSV delimiter (default: comma)"
692
+ },
693
+ "header": {
694
+ "type": "boolean",
695
+ "description": "Whether CSV has a header row (default: true)"
696
+ }
697
+ },
698
+ "required": ["file_path", "pipeline"]
699
+ }
700
+ ),
701
+ Tool(
702
+ name="profile_data",
703
+ description="Upload a data file and use AI to generate a comprehensive data profile: summary statistics per column, data quality issues detected, and suggested validation rules (regex patterns and AI-powered rules). Use the suggested rules when building a pipeline's dataQuality section.",
704
+ inputSchema={
705
+ "type": "object",
706
+ "properties": {
707
+ "file_path": {
708
+ "type": "string",
709
+ "description": "Absolute path to the file to profile"
710
+ },
711
+ "delimiter": {
712
+ "type": "string",
713
+ "description": "CSV delimiter (default: comma)"
714
+ },
715
+ "header": {
716
+ "type": "boolean",
717
+ "description": "Whether CSV has a header row (default: true)"
718
+ },
719
+ "sample_size": {
720
+ "type": "integer",
721
+ "description": "Number of rows to sample for profiling (default: 200)"
722
+ }
723
+ },
724
+ "required": ["file_path"]
725
+ }
726
+ ),
727
+ Tool(
728
+ name="get_version",
729
+ description="Get the Datris server version.",
730
+ inputSchema={
731
+ "type": "object",
732
+ "properties": {},
733
+ }
734
+ ),
735
+ Tool(
736
+ name="check_service_health",
737
+ description="Check which backend services are up, down, or not configured. Returns the health status of PostgreSQL, MongoDB, MinIO, ActiveMQ, Kafka, and any configured vector databases (Qdrant, Weaviate, Milvus, Chroma, pgvector). Call this before attempting search or query operations to know which services are available.",
738
+ inputSchema={
739
+ "type": "object",
740
+ "properties": {},
741
+ }
742
+ ),
743
+ # --- Vector Database Search Tools ---
744
+ Tool(
745
+ name="search_qdrant",
746
+ description="Semantic search across a Qdrant vector database collection. Takes a natural language query, generates an embedding, and returns the most similar document chunks with similarity scores. For RAG: pass the returned text to ai_answer with the user's question.",
747
+ inputSchema={
748
+ "type": "object",
749
+ "properties": {
750
+ "query": {"type": "string", "description": "Natural language search query"},
751
+ "collection": {"type": "string", "description": "Qdrant collection name (default: financial_documents)"},
752
+ "top_k": {"type": "integer", "description": "Number of results to return (default: 5)"},
753
+ },
754
+ "required": ["query"]
755
+ }
756
+ ),
757
+ Tool(
758
+ name="search_weaviate",
759
+ description="Semantic search across a Weaviate vector database class. Takes a natural language query, generates an embedding, and returns the most similar document chunks with similarity scores. For RAG: pass the returned text to ai_answer with the user's question.",
760
+ inputSchema={
761
+ "type": "object",
762
+ "properties": {
763
+ "query": {"type": "string", "description": "Natural language search query"},
764
+ "class_name": {"type": "string", "description": "Weaviate class name (default: FinancialDocuments)"},
765
+ "top_k": {"type": "integer", "description": "Number of results to return (default: 5)"},
766
+ },
767
+ "required": ["query"]
768
+ }
769
+ ),
770
+ Tool(
771
+ name="search_milvus",
772
+ description="Semantic search across a Milvus vector database collection. Takes a natural language query, generates an embedding, and returns the most similar document chunks with similarity scores. For RAG: pass the returned text to ai_answer with the user's question.",
773
+ inputSchema={
774
+ "type": "object",
775
+ "properties": {
776
+ "query": {"type": "string", "description": "Natural language search query"},
777
+ "collection": {"type": "string", "description": "Milvus collection name (default: financial_documents)"},
778
+ "top_k": {"type": "integer", "description": "Number of results to return (default: 5)"},
779
+ },
780
+ "required": ["query"]
781
+ }
782
+ ),
783
+ Tool(
784
+ name="search_pgvector",
785
+ description="Semantic search across a PostgreSQL pgvector table using cosine distance. Takes a natural language query, generates an embedding, and returns the most similar document chunks with similarity scores. Use list_postgres_tables with vector_only=true to discover available pgvector tables. For RAG: pass the returned text to ai_answer.",
786
+ inputSchema={
787
+ "type": "object",
788
+ "properties": {
789
+ "query": {"type": "string", "description": "Natural language search query"},
790
+ "table": {"type": "string", "description": "Table name (default: financial_documents)"},
791
+ "schema": {"type": "string", "description": "PostgreSQL schema (default: public)"},
792
+ "top_k": {"type": "integer", "description": "Number of results to return (default: 5)"},
793
+ },
794
+ "required": ["query"]
795
+ }
796
+ ),
797
+ Tool(
798
+ name="search_chroma",
799
+ description="Semantic search across a Chroma vector database collection. Takes a natural language query, generates an embedding, and returns the most similar document chunks with similarity scores. For RAG: pass the returned text to ai_answer with the user's question.",
800
+ inputSchema={
801
+ "type": "object",
802
+ "properties": {
803
+ "query": {"type": "string", "description": "Natural language search query"},
804
+ "collection": {"type": "string", "description": "Chroma collection name (default: financial_documents)"},
805
+ "top_k": {"type": "integer", "description": "Number of results to return (default: 5)"},
806
+ },
807
+ "required": ["query"]
808
+ }
809
+ ),
810
+ # --- Database Query Tools ---
811
+ Tool(
812
+ name="query_postgres",
813
+ description="Execute a read-only SQL SELECT query against PostgreSQL. Use the metadata discovery tools (list_postgres_databases, list_postgres_schemas, list_postgres_tables, list_postgres_columns) first to explore available data before constructing queries. Only SELECT is allowed; LIMIT is auto-appended if missing.",
814
+ inputSchema={
815
+ "type": "object",
816
+ "properties": {
817
+ "sql": {"type": "string", "description": "SQL SELECT query to execute"},
818
+ "limit": {"type": "integer", "description": "Maximum rows to return (default: 100)"},
819
+ },
820
+ "required": ["sql"]
821
+ }
822
+ ),
823
+ Tool(
824
+ name="query_mongodb",
825
+ description="Query a MongoDB collection with optional filter and projection. Use list_mongodb_databases and list_mongodb_collections first to discover available data. Returns matching documents as JSON.",
826
+ inputSchema={
827
+ "type": "object",
828
+ "properties": {
829
+ "collection": {"type": "string", "description": "MongoDB collection name"},
830
+ "filter": {"type": "object", "description": "MongoDB query filter (default: {})"},
831
+ "projection": {"type": "object", "description": "Fields to include/exclude (default: all fields)"},
832
+ "limit": {"type": "integer", "description": "Maximum documents to return (default: 20)"},
833
+ },
834
+ "required": ["collection"]
835
+ }
836
+ ),
837
+ # --- Metadata Discovery Tools ---
838
+ Tool(
839
+ name="list_postgres_databases",
840
+ description="List all PostgreSQL databases available in the Datris platform. Use this as the first step when exploring what data has been ingested into PostgreSQL destinations.",
841
+ inputSchema={
842
+ "type": "object",
843
+ "properties": {},
844
+ }
845
+ ),
846
+ Tool(
847
+ name="list_postgres_schemas",
848
+ description="List all schemas in a PostgreSQL database. Schemas organize tables within a database (e.g., 'public', 'analytics'). Use after list_postgres_databases to drill into a specific database.",
849
+ inputSchema={
850
+ "type": "object",
851
+ "properties": {
852
+ "database": {"type": "string", "description": "Database name (default: datris)"},
853
+ },
854
+ }
855
+ ),
856
+ Tool(
857
+ name="list_postgres_tables",
858
+ description="List all tables in a PostgreSQL schema. Set vector_only=true to show only pgvector embedding tables, or false (default) to show regular data tables. Use after list_postgres_schemas.",
859
+ inputSchema={
860
+ "type": "object",
861
+ "properties": {
862
+ "database": {"type": "string", "description": "Database name (default: datris)"},
863
+ "schema": {"type": "string", "description": "Schema name (default: public)"},
864
+ "vector_only": {"type": "boolean", "description": "If true, only return tables with an embedding column (pgvector tables). Default: false"},
865
+ },
866
+ }
867
+ ),
868
+ Tool(
869
+ name="list_postgres_columns",
870
+ description="List all columns and their data types for a specific PostgreSQL table. Use this to understand table structure before writing a query_postgres SQL query.",
871
+ inputSchema={
872
+ "type": "object",
873
+ "properties": {
874
+ "database": {"type": "string", "description": "Database name (default: datris)"},
875
+ "schema": {"type": "string", "description": "Schema name (default: public)"},
876
+ "table": {"type": "string", "description": "Table name"},
877
+ },
878
+ "required": ["table"]
879
+ }
880
+ ),
881
+ Tool(
882
+ name="list_mongodb_databases",
883
+ description="List all MongoDB databases available in the Datris platform. Use this as the first step when exploring what data has been ingested into MongoDB destinations.",
884
+ inputSchema={
885
+ "type": "object",
886
+ "properties": {},
887
+ }
888
+ ),
889
+ Tool(
890
+ name="list_mongodb_collections",
891
+ description="List MongoDB collections. If database is specified, lists collections in that database. If omitted, lists all collections across all databases in 'db.collection' format.",
892
+ inputSchema={
893
+ "type": "object",
894
+ "properties": {
895
+ "database": {"type": "string", "description": "Database name (optional; omit to list from all databases)"},
896
+ },
897
+ }
898
+ ),
899
+ # --- Vector Store Metadata ---
900
+ Tool(
901
+ name="list_qdrant_collections",
902
+ description="List all collections in the Qdrant vector database. Use this to discover available collections before running search_qdrant.",
903
+ inputSchema={
904
+ "type": "object",
905
+ "properties": {},
906
+ }
907
+ ),
908
+ Tool(
909
+ name="list_weaviate_classes",
910
+ description="List all classes in the Weaviate vector database. Use this to discover available classes before running search_weaviate.",
911
+ inputSchema={
912
+ "type": "object",
913
+ "properties": {},
914
+ }
915
+ ),
916
+ Tool(
917
+ name="list_milvus_collections",
918
+ description="List all collections in the Milvus vector database. Use this to discover available collections before running search_milvus.",
919
+ inputSchema={
920
+ "type": "object",
921
+ "properties": {},
922
+ }
923
+ ),
924
+ Tool(
925
+ name="list_chroma_collections",
926
+ description="List all collections in the Chroma vector database. Use this to discover available collections before running search_chroma.",
927
+ inputSchema={
928
+ "type": "object",
929
+ "properties": {},
930
+ }
931
+ ),
932
+ Tool(
933
+ name="list_pgvector_collections",
934
+ description="List all pgvector tables (tables with an embedding column) in PostgreSQL. Use this to discover available collections before running search_pgvector.",
935
+ inputSchema={
936
+ "type": "object",
937
+ "properties": {},
938
+ }
939
+ ),
940
+ # --- AI Tools ---
941
+ Tool(
942
+ name="ai_answer",
943
+ description="Ask the Datris AI to answer a question based on provided context. Ideal for RAG workflows: first retrieve relevant chunks using a search tool (search_qdrant, search_pgvector, etc.), then pass the retrieved text as context along with the user's question to get a synthesized answer.",
944
+ inputSchema={
945
+ "type": "object",
946
+ "properties": {
947
+ "query": {"type": "string", "description": "The question to answer"},
948
+ "context": {"type": "string", "description": "Context text to base the answer on (e.g., retrieved document chunks)"},
949
+ },
950
+ "required": ["query", "context"]
951
+ }
952
+ ),
953
+ # --- Configuration Tools ---
954
+ Tool(
955
+ name="upload_config",
956
+ description="Upload a configuration file to the Datris platform. Supports two types: 'validation-schema' (JSON Schema files used in pipeline dataQuality schema validation) and 'javascript' (JS files used in pipeline transformation row functions). The file is stored and can be referenced by pipeline configurations.",
957
+ inputSchema={
958
+ "type": "object",
959
+ "properties": {
960
+ "file_path": {"type": "string", "description": "Absolute path to the configuration file to upload"},
961
+ "type": {"type": "string", "enum": ["validation-schema", "javascript"], "description": "Config file type: 'validation-schema' for JSON Schema or 'javascript' for transformation scripts"},
962
+ },
963
+ "required": ["file_path", "type"]
964
+ }
965
+ ),
966
+ ]
967
+
968
+
969
+ @server.call_tool()
970
+ async def call_tool(name: str, arguments: dict[str, Any]) -> list[TextContent]:
971
+ try:
972
+ result = _dispatch(name, arguments)
973
+ return [TextContent(type="text", text=result)]
974
+ except Exception as e:
975
+ return [TextContent(type="text", text=json.dumps({"error": str(e)}))]
976
+
977
+
978
+ def _dispatch(name: str, args: dict) -> str:
979
+ # --- Pipeline Management ---
980
+ if name == "list_pipelines":
981
+ return _call("get", "/api/v1/pipelines")
982
+
983
+ elif name == "get_pipeline":
984
+ return _call("get", "/api/v1/pipeline", params={"pipeline": args["pipeline"]})
985
+
986
+ elif name == "create_pipeline":
987
+ return _call("post", "/api/v1/pipeline", json=args["config"])
988
+
989
+ elif name == "delete_pipeline":
990
+ return _call("delete", "/api/v1/pipeline", params={"pipeline": args["pipeline"]})
991
+
992
+ elif name == "upload_file":
993
+ data = {"pipeline": args["pipeline"]}
994
+ return _upload("/api/v1/pipeline/upload", args["file_path"], data)
995
+
996
+ elif name == "get_job_status":
997
+ params = {}
998
+ if args.get("pipeline_token"):
999
+ params["pipelinetoken"] = args["pipeline_token"]
1000
+ if args.get("pipeline_name"):
1001
+ params["pipelinename"] = args["pipeline_name"]
1002
+ if args.get("page"):
1003
+ params["page"] = args["page"]
1004
+ return _call("get", "/api/v1/pipeline/status", params=params)
1005
+
1006
+ elif name == "kill_job":
1007
+ payload = {"pipelineToken": args["pipeline_token"]}
1008
+ return _call("post", "/api/v1/job/kill", json=payload)
1009
+
1010
+ elif name == "generate_schema":
1011
+ data = {"pipeline": args["pipeline"]}
1012
+ if args.get("delimiter"):
1013
+ data["delimiter"] = args["delimiter"]
1014
+ if args.get("header") is not None:
1015
+ data["header"] = str(args["header"]).lower()
1016
+ return _upload("/api/v1/pipeline/generate", args["file_path"], data)
1017
+
1018
+ elif name == "profile_data":
1019
+ data = {}
1020
+ if args.get("delimiter"):
1021
+ data["delimiter"] = args["delimiter"]
1022
+ if args.get("header") is not None:
1023
+ data["header"] = str(args["header"]).lower()
1024
+ if args.get("sample_size"):
1025
+ data["sampleSize"] = str(args["sample_size"])
1026
+ return _upload("/api/v1/pipeline/profile", args["file_path"], data)
1027
+
1028
+ elif name == "get_version":
1029
+ return _call("get", "/api/v1/version")
1030
+
1031
+ elif name == "check_service_health":
1032
+ return _call("get", "/api/v1/health/services")
1033
+
1034
+ # --- Vector Database Search (via REST API) ---
1035
+ elif name == "search_qdrant":
1036
+ payload = {"query": args["query"]}
1037
+ if args.get("collection"):
1038
+ payload["collection"] = args["collection"]
1039
+ if args.get("top_k"):
1040
+ payload["topK"] = args["top_k"]
1041
+ return _call("post", "/api/v1/search/qdrant", json=payload)
1042
+
1043
+ elif name == "search_weaviate":
1044
+ payload = {"query": args["query"]}
1045
+ if args.get("class_name"):
1046
+ payload["className"] = args["class_name"]
1047
+ if args.get("top_k"):
1048
+ payload["topK"] = args["top_k"]
1049
+ return _call("post", "/api/v1/search/weaviate", json=payload)
1050
+
1051
+ elif name == "search_milvus":
1052
+ payload = {"query": args["query"]}
1053
+ if args.get("collection"):
1054
+ payload["collection"] = args["collection"]
1055
+ if args.get("top_k"):
1056
+ payload["topK"] = args["top_k"]
1057
+ return _call("post", "/api/v1/search/milvus", json=payload)
1058
+
1059
+ elif name == "search_pgvector":
1060
+ payload = {"query": args["query"]}
1061
+ if args.get("table"):
1062
+ payload["table"] = args["table"]
1063
+ if args.get("schema"):
1064
+ payload["schema"] = args["schema"]
1065
+ if args.get("top_k"):
1066
+ payload["topK"] = args["top_k"]
1067
+ return _call("post", "/api/v1/search/pgvector", json=payload)
1068
+
1069
+ elif name == "search_chroma":
1070
+ payload = {"query": args["query"]}
1071
+ if args.get("collection"):
1072
+ payload["collection"] = args["collection"]
1073
+ if args.get("top_k"):
1074
+ payload["topK"] = args["top_k"]
1075
+ return _call("post", "/api/v1/search/chroma", json=payload)
1076
+
1077
+ # --- Database Queries (via REST API) ---
1078
+ elif name == "query_postgres":
1079
+ payload = {"sql": args["sql"]}
1080
+ if args.get("limit"):
1081
+ payload["limit"] = args["limit"]
1082
+ return _call("post", "/api/v1/query/postgres", json=payload)
1083
+
1084
+ elif name == "query_mongodb":
1085
+ payload = {"collection": args["collection"]}
1086
+ if args.get("filter"):
1087
+ payload["filter"] = args["filter"]
1088
+ if args.get("projection"):
1089
+ payload["projection"] = args["projection"]
1090
+ if args.get("limit"):
1091
+ payload["limit"] = args["limit"]
1092
+ return _call("post", "/api/v1/query/mongodb", json=payload)
1093
+
1094
+ # --- Metadata Discovery (via REST API) ---
1095
+ elif name == "list_postgres_databases":
1096
+ return _call("get", "/api/v1/metadata/postgres/databases")
1097
+
1098
+ elif name == "list_postgres_schemas":
1099
+ params = {}
1100
+ if args.get("database"):
1101
+ params["database"] = args["database"]
1102
+ return _call("get", "/api/v1/metadata/postgres/schemas", params=params)
1103
+
1104
+ elif name == "list_postgres_tables":
1105
+ params = {}
1106
+ if args.get("database"):
1107
+ params["database"] = args["database"]
1108
+ if args.get("schema"):
1109
+ params["schema"] = args["schema"]
1110
+ if args.get("vector_only") is not None:
1111
+ params["vectorOnly"] = str(args["vector_only"]).lower()
1112
+ return _call("get", "/api/v1/metadata/postgres/tables", params=params)
1113
+
1114
+ elif name == "list_postgres_columns":
1115
+ params = {"table": args["table"]}
1116
+ if args.get("database"):
1117
+ params["database"] = args["database"]
1118
+ if args.get("schema"):
1119
+ params["schema"] = args["schema"]
1120
+ return _call("get", "/api/v1/metadata/postgres/columns", params=params)
1121
+
1122
+ elif name == "list_mongodb_databases":
1123
+ return _call("get", "/api/v1/metadata/mongodb/databases")
1124
+
1125
+ elif name == "list_mongodb_collections":
1126
+ params = {}
1127
+ if args.get("database"):
1128
+ params["database"] = args["database"]
1129
+ return _call("get", "/api/v1/metadata/mongodb/collections", params=params)
1130
+
1131
+ # --- Vector Store Metadata ---
1132
+ elif name == "list_qdrant_collections":
1133
+ return _call("get", "/api/v1/metadata/qdrant/collections")
1134
+
1135
+ elif name == "list_weaviate_classes":
1136
+ return _call("get", "/api/v1/metadata/weaviate/classes")
1137
+
1138
+ elif name == "list_milvus_collections":
1139
+ return _call("get", "/api/v1/metadata/milvus/collections")
1140
+
1141
+ elif name == "list_chroma_collections":
1142
+ return _call("get", "/api/v1/metadata/chroma/collections")
1143
+
1144
+ elif name == "list_pgvector_collections":
1145
+ return _call("get", "/api/v1/metadata/postgres/tables", params={"vectorOnly": "true"})
1146
+
1147
+ # --- AI ---
1148
+ elif name == "ai_answer":
1149
+ payload = {"query": args["query"], "context": args["context"]}
1150
+ return _call("post", "/api/v1/ai/answer", json=payload)
1151
+
1152
+ # --- Config ---
1153
+ elif name == "upload_config":
1154
+ data = {"type": args["type"]}
1155
+ return _upload("/api/v1/config/upload", args["file_path"], data)
1156
+
1157
+ else:
1158
+ return json.dumps({"error": f"Unknown tool: {name}"})
1159
+
1160
+
1161
+ # ---------------------------------------------------------------------------
1162
+ # Main
1163
+ # ---------------------------------------------------------------------------
1164
+
1165
+ async def run_stdio():
1166
+ from mcp.server.stdio import stdio_server
1167
+ async with stdio_server() as (read_stream, write_stream):
1168
+ await server.run(read_stream, write_stream, server.create_initialization_options())
1169
+
1170
+
1171
+ async def run_sse(port: int):
1172
+ from mcp.server.sse import SseServerTransport
1173
+ from starlette.applications import Starlette
1174
+ from starlette.routing import Route
1175
+ import uvicorn
1176
+
1177
+ sse = SseServerTransport("/messages")
1178
+
1179
+ async def handle_sse(request):
1180
+ async with sse.connect_sse(request.scope, request.receive, request._send) as streams:
1181
+ await server.run(streams[0], streams[1], server.create_initialization_options())
1182
+
1183
+ async def handle_messages(request):
1184
+ await sse.handle_post_message(request.scope, request.receive, request._send)
1185
+
1186
+ app = Starlette(routes=[
1187
+ Route("/sse", endpoint=handle_sse),
1188
+ Route("/messages", endpoint=handle_messages, methods=["POST"]),
1189
+ ])
1190
+
1191
+ config = uvicorn.Config(app, host="0.0.0.0", port=port)
1192
+ srv = uvicorn.Server(config)
1193
+ await srv.serve()
1194
+
1195
+
1196
+ def main():
1197
+ import asyncio
1198
+
1199
+ parser = argparse.ArgumentParser(description="Datris MCP Server")
1200
+ parser.add_argument("--sse", action="store_true", help="Run in SSE mode (default: stdio)")
1201
+ parser.add_argument("--port", type=int, default=3000, help="SSE port (default: 3000)")
1202
+ args = parser.parse_args()
1203
+
1204
+ if args.sse:
1205
+ asyncio.run(run_sse(args.port))
1206
+ else:
1207
+ asyncio.run(run_stdio())
1208
+
1209
+
1210
+ if __name__ == "__main__":
1211
+ main()