uomp-mcp 1.6.0__tar.gz

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.
Files changed (31) hide show
  1. uomp_mcp-1.6.0/PKG-INFO +18 -0
  2. uomp_mcp-1.6.0/README.md +2 -0
  3. uomp_mcp-1.6.0/pyproject.toml +41 -0
  4. uomp_mcp-1.6.0/src/uomp_mcp/__init__.py +0 -0
  5. uomp_mcp-1.6.0/src/uomp_mcp/contrib/__init__.py +0 -0
  6. uomp_mcp-1.6.0/src/uomp_mcp/contrib/uomp_capacity/__init__.py +11 -0
  7. uomp_mcp-1.6.0/src/uomp_mcp/contrib/uomp_capacity/handler.py +49 -0
  8. uomp_mcp-1.6.0/src/uomp_mcp/contrib/uomp_capacity/log.py +1 -0
  9. uomp_mcp-1.6.0/src/uomp_mcp/contrib/uomp_capacity/server.py +86 -0
  10. uomp_mcp-1.6.0/src/uomp_mcp/contrib/uomp_capacity/utils.py +17 -0
  11. uomp_mcp-1.6.0/src/uomp_mcp/contrib/uomp_clickhouse/__init__.py +26 -0
  12. uomp_mcp-1.6.0/src/uomp_mcp/contrib/uomp_clickhouse/chdb_prompt.py +155 -0
  13. uomp_mcp-1.6.0/src/uomp_mcp/contrib/uomp_clickhouse/main.py +21 -0
  14. uomp_mcp-1.6.0/src/uomp_mcp/contrib/uomp_clickhouse/mcp_env.py +283 -0
  15. uomp_mcp-1.6.0/src/uomp_mcp/contrib/uomp_clickhouse/mcp_server.py +372 -0
  16. uomp_mcp-1.6.0/src/uomp_mcp/contrib/uomp_core/__init__.py +18 -0
  17. uomp_mcp-1.6.0/src/uomp_mcp/contrib/uomp_core/config.py +27 -0
  18. uomp_mcp-1.6.0/src/uomp_mcp/contrib/uomp_core/const.py +61 -0
  19. uomp_mcp-1.6.0/src/uomp_mcp/contrib/uomp_core/handler.py +62 -0
  20. uomp_mcp-1.6.0/src/uomp_mcp/contrib/uomp_core/log.py +9 -0
  21. uomp_mcp-1.6.0/src/uomp_mcp/contrib/uomp_core/server.py +196 -0
  22. uomp_mcp-1.6.0/src/uomp_mcp/contrib/uomp_core/service.py +52 -0
  23. uomp_mcp-1.6.0/src/uomp_mcp/contrib/uomp_core/utils.py +135 -0
  24. uomp_mcp-1.6.0/src/uomp_mcp/contrib/uomp_mysql/__init__.py +9 -0
  25. uomp_mcp-1.6.0/src/uomp_mcp/contrib/uomp_mysql/server.py +202 -0
  26. uomp_mcp-1.6.0/src/uomp_mcp/core/__init__.py +0 -0
  27. uomp_mcp-1.6.0/src/uomp_mcp/core/apis/__init__.py +7 -0
  28. uomp_mcp-1.6.0/src/uomp_mcp/core/lib/__init__.py +0 -0
  29. uomp_mcp-1.6.0/src/uomp_mcp/core/lib/http.py +52 -0
  30. uomp_mcp-1.6.0/src/uomp_mcp/utils/__init__.py +1 -0
  31. uomp_mcp-1.6.0/src/uomp_mcp/utils/log.py +14 -0
@@ -0,0 +1,18 @@
1
+ Metadata-Version: 2.3
2
+ Name: uomp-mcp
3
+ Version: 1.6.0
4
+ Summary: This is uomp_mcp mcp project.
5
+ Author: yehaiquan
6
+ Author-email: yehaiquan <yehaiquan@163.com>
7
+ Requires-Dist: chdb>=3.6.0
8
+ Requires-Dist: clickhouse-connect>=0.8.18
9
+ Requires-Dist: fastmcp>=2.12.2
10
+ Requires-Dist: mcp>=1.13.0
11
+ Requires-Dist: mysql-connector-python>=9.1.0
12
+ Requires-Dist: pip>=25.2
13
+ Requires-Dist: requests>=2.32.5
14
+ Requires-Python: >=3.12
15
+ Description-Content-Type: text/markdown
16
+
17
+
18
+ python -m uomp_mcp.contrib.uomp_core.server
@@ -0,0 +1,2 @@
1
+
2
+ python -m uomp_mcp.contrib.uomp_core.server
@@ -0,0 +1,41 @@
1
+ [project]
2
+ name = "uomp-mcp"
3
+ version = "1.6.0"
4
+ description = "This is uomp_mcp mcp project."
5
+ readme = "README.md"
6
+ authors = [
7
+ { name = "yehaiquan", email = "yehaiquan@163.com" }
8
+ ]
9
+ requires-python = ">=3.12"
10
+ dependencies = [
11
+ "chdb>=3.6.0",
12
+ "clickhouse-connect>=0.8.18",
13
+ "fastmcp>=2.12.2",
14
+ "mcp>=1.13.0",
15
+ "mysql-connector-python>=9.1.0",
16
+ "pip>=25.2",
17
+ "requests>=2.32.5",
18
+ ]
19
+
20
+ [build-system]
21
+ requires = ["uv_build>=0.11.2,<0.12.0"]
22
+ build-backend = "uv_build"
23
+
24
+ [project.scripts]
25
+ uomp_core = "uomp_mcp.contrib.uomp_core:main"
26
+ uomp_mysql = "uomp_mcp.contrib.uomp_mysql:main"
27
+ uomp_clickhouse = "uomp_mcp.contrib.uomp_clickhouse:main"
28
+
29
+ [tool.uv]
30
+ allow-insecure-host = ["mirror-devops.cdn.10086.cn"]
31
+
32
+ [dependency-groups]
33
+ dev = [
34
+ "black>=25.1.0",
35
+ "isort>=6.0.1",
36
+ "mypy>=1.17.1",
37
+ "pytest>=8.4.2",
38
+ "pytest-asyncio>=1.1.0",
39
+ "pytest-cov>=7.0.0",
40
+ ]
41
+
File without changes
File without changes
@@ -0,0 +1,11 @@
1
+ from . import server
2
+ import asyncio
3
+
4
+
5
+ def main():
6
+ """Main entry point for the package."""
7
+ asyncio.run(server.main())
8
+
9
+
10
+ # Expose important items at package level
11
+ __all__ = ['main', 'server']
@@ -0,0 +1,49 @@
1
+ from mcp.types import TextContent
2
+ from .log import logger
3
+ import requests
4
+ import json
5
+
6
+
7
+ def call_domain_query_handler(domain: str, config: dict) -> list[TextContent]:
8
+ try:
9
+ uomp_key = config["uomp_key"]
10
+ uomp_api_url = config["uomp_api_url"]
11
+
12
+ headers = {
13
+ "Authorization": f"token {uomp_key}"
14
+ }
15
+
16
+ response = requests.get(
17
+ url=f"{uomp_api_url}/api/v1/selfapp/ibs_domain/",
18
+ params={'search': domain},
19
+ headers=headers,
20
+ verify=False
21
+ )
22
+ response.raise_for_status()
23
+ result = response.json()
24
+ return [TextContent(type="text", text=json.dumps(result))]
25
+
26
+ except Exception as e:
27
+ logger.error(f"Error executing uomp '{domain}': {e}")
28
+ return [TextContent(type="text", text=f"Error executing query: {str(e)}")]
29
+
30
+
31
+ def call_host_query_handler(query: str, config: dict) -> list[TextContent]:
32
+ try:
33
+ uomp_key = config["uomp_key"]
34
+ uomp_api_url = config["uomp_api_url"]
35
+
36
+ headers = {
37
+ "Authorization": f"token {uomp_key}"
38
+ }
39
+ response = requests.get(
40
+ url=f"{uomp_api_url}/api/v1/selfapp/ibs_domain/",
41
+ params={'search': query},
42
+ headers=headers,
43
+ verify=False
44
+ )
45
+ response.raise_for_status()
46
+ return response.json()
47
+ except Exception as e:
48
+ logger.error(f"Error executing uomp '{query}': {e}")
49
+ return [TextContent(type="text", text=f"Error executing query: {str(e)}")]
@@ -0,0 +1 @@
1
+ from uomp_mcp.utils import defautl_logger as logger
@@ -0,0 +1,86 @@
1
+ from mcp.server import Server
2
+ from mcp.server.stdio import stdio_server
3
+ from mcp.types import Tool, TextContent
4
+ from .handler import call_domain_query_handler, call_host_query_handler
5
+ from .log import logger
6
+ from .utils import get_config
7
+ import asyncio
8
+
9
+ # Initialize server
10
+ app = Server("uomp_capacity")
11
+
12
+
13
+ @app.list_tools()
14
+ async def list_tools() -> list[Tool]:
15
+ """List available uomp_core mcp tools."""
16
+ logger.info("Listing tools...")
17
+ return [
18
+ Tool(
19
+ name="capacity_query",
20
+ description="提供CDN领域建设容量指标查询、上量指标查询场景能力。",
21
+ inputSchema={
22
+ "type": "object",
23
+ "properties": {
24
+ "domain": {
25
+ "type": "string",
26
+ "description": "需要查询和分析的CDN服务域名"
27
+ }
28
+ },
29
+ "required": ["domain"]
30
+ }
31
+ ),
32
+ Tool(
33
+ name="host_query",
34
+ description="根据提供的主机参数,查询CDN后台管理系统获取最新的主机字段配置信息,并分析主机的配置情况。",
35
+ inputSchema={
36
+ "type": "object",
37
+ "properties": {
38
+ "ip": {
39
+ "type": "string",
40
+ "description": "需要查询和分析的CDN主机的配置信息。"
41
+ }
42
+ },
43
+ "required": ["ip"]
44
+ }
45
+ )
46
+ ]
47
+
48
+
49
+ @app.call_tool()
50
+ async def call_tool(name: str, arguments: dict) -> list[TextContent]:
51
+ """Execute uomp_core commands."""
52
+ logger.info(f"Calling tool: {name} with arguments: {arguments}")
53
+
54
+ domain = arguments.get("domain")
55
+ if not domain:
56
+ raise ValueError("domain is required")
57
+
58
+ config = get_config()
59
+
60
+ if name == "domain_query":
61
+ return call_domain_query_handler(domain, config)
62
+ elif name == "host_query":
63
+ return call_host_query_handler(domain, config)
64
+ else:
65
+ raise ValueError(f"Unknown tool: {name}")
66
+
67
+
68
+ async def main():
69
+ """Main entry point to run the MCP server."""
70
+
71
+ logger.info("Starting uomp_core mcp server...")
72
+
73
+ async with stdio_server() as (read_stream, write_stream):
74
+ try:
75
+ await app.run(
76
+ read_stream,
77
+ write_stream,
78
+ app.create_initialization_options()
79
+ )
80
+ except Exception as e:
81
+ logger.error(f"Server error: {str(e)}", exc_info=True)
82
+ raise
83
+
84
+
85
+ if __name__ == "__main__":
86
+ asyncio.run(main())
@@ -0,0 +1,17 @@
1
+ from .log import logger
2
+ import os
3
+
4
+
5
+ def get_config():
6
+ """Get uomp_core configuration from environment variables."""
7
+ config = {
8
+ "uomp_key": os.getenv("UOMP_KEY", ""),
9
+ "uomp_api_url": os.getenv("UOMP_API_URL", "https://paas.uops.cdn.10086.cn/o/cdn-config/"),
10
+ }
11
+
12
+ if not all([config["uomp_key"], config["uomp_api_url"]]):
13
+ logger.error("Missing required uomp_core configuration. Please check environment variables:")
14
+ logger.error("UOMP_KEY, UOMP_API_URL are required")
15
+ raise ValueError("Missing required uomp_core configuration")
16
+
17
+ return config
@@ -0,0 +1,26 @@
1
+ from . import main as server
2
+ from .mcp_server import (
3
+ create_clickhouse_client,
4
+ list_databases,
5
+ list_tables,
6
+ run_select_query,
7
+ create_chdb_client,
8
+ run_chdb_select_query,
9
+ chdb_initial_prompt,
10
+ )
11
+ import asyncio
12
+
13
+ __all__ = [
14
+ "list_databases",
15
+ "list_tables",
16
+ "run_select_query",
17
+ "create_clickhouse_client",
18
+ "create_chdb_client",
19
+ "run_chdb_select_query",
20
+ "chdb_initial_prompt",
21
+ ]
22
+
23
+
24
+ def main():
25
+ """Main entry point for the package."""
26
+ asyncio.run(server.main())
@@ -0,0 +1,155 @@
1
+ """chDB prompts for MCP server."""
2
+
3
+ CHDB_PROMPT = """
4
+ # chDB MCP System Prompt
5
+
6
+ ## Available Tools
7
+ - **run_chdb_select_query**: Execute SELECT queries using chDB's table functions
8
+
9
+ ## Core Principles
10
+ You are a chDB assistant, specialized in helping users query data sources directly through table functions, **avoiding data imports**.
11
+
12
+ ### 🚨 Important Constraints
13
+ #### Data Processing Constraints
14
+ - **No large data display**: Don't show more than 10 rows of raw data in responses
15
+ - **Use analysis tool**: All data processing must be completed in the analysis tool
16
+ - **Result-oriented output**: Only provide query results and key insights, not intermediate processing data
17
+ - **Avoid context explosion**: Don't paste large amounts of raw data or complete tables
18
+
19
+ #### Query Strategy Constraints
20
+ - **Prioritize table functions**: When users mention import/load/insert, immediately recommend table functions
21
+ - **Direct querying**: All data should be queried in place through table functions
22
+ - **Fallback option**: When no suitable table function exists, use Python to download temporary files then process with file()
23
+ - **Concise responses**: Avoid lengthy explanations, provide executable SQL directly
24
+
25
+ ## Table Functions
26
+
27
+ ### File Types
28
+ ```sql
29
+ -- Local files (auto format detection)
30
+ file('path/to/file.csv')
31
+ file('data.parquet', 'Parquet')
32
+
33
+ -- Remote files
34
+ url('https://example.com/data.csv', 'CSV')
35
+ url('https://example.com/data.parquet')
36
+
37
+ -- S3 storage
38
+ s3('s3://bucket/path/file.csv', 'CSV')
39
+ s3('s3://bucket/path/*.parquet', 'access_key', 'secret_key', 'Parquet')
40
+
41
+ -- HDFS
42
+ hdfs('hdfs://namenode:9000/path/file.parquet')
43
+ ```
44
+
45
+ ### Database Types
46
+ ```sql
47
+ -- PostgreSQL
48
+ postgresql('host:port', 'database', 'table', 'user', 'password')
49
+
50
+ -- MySQL
51
+ mysql('host:port', 'database', 'table', 'user', 'password')
52
+
53
+ -- SQLite
54
+ sqlite('path/to/database.db', 'table')
55
+ ```
56
+
57
+ ### Common Formats
58
+ - `CSV`, `CSVWithNames`, `TSV`, `TSVWithNames`
59
+ - `JSON`, `JSONEachRow`, `JSONCompact`
60
+ - `Parquet`, `ORC`, `Avro`
61
+
62
+ ## Workflow
63
+
64
+ ### 1. Identify Data Source
65
+ - User mentions URL → `url()`
66
+ - User mentions S3 → `s3()`
67
+ - User mentions local file → `file()`
68
+ - User mentions database → corresponding database function
69
+ - **No suitable table function** → Use Python to download as temporary file
70
+
71
+ ### 2. Fallback: Python Download
72
+ When no suitable table function exists:
73
+ ```python
74
+ # Execute in analysis tool
75
+ import requests
76
+ import tempfile
77
+ import os
78
+
79
+ # Download data to temporary file
80
+ response = requests.get('your_data_url')
81
+
82
+ with tempfile.NamedTemporaryFile(mode='w', delete=False) as f:
83
+ f.write(response.text)
84
+ temp_file = f.name
85
+
86
+ # Execute chDB query immediately within the block
87
+ try:
88
+ # Use run_chdb_select_query to execute query
89
+ result = run_chdb_select_query(f"SELECT * FROM file('{temp_file}', 'CSV') LIMIT 10")
90
+ print(result)
91
+ finally:
92
+ # Ensure temporary file deletion
93
+ if os.path.exists(temp_file):
94
+ os.unlink(temp_file)
95
+ ```
96
+
97
+ ### 3. Quick Testing
98
+ ```sql
99
+ -- Test connection (default LIMIT 10)
100
+ SELECT * FROM table_function(...) LIMIT 10;
101
+
102
+ -- View structure
103
+ DESCRIBE table_function(...);
104
+ ```
105
+
106
+ ### 4. Build Queries
107
+ ```sql
108
+ -- Basic query (default LIMIT 10)
109
+ SELECT column1, column2 FROM table_function(...) WHERE condition LIMIT 10;
110
+
111
+ -- Aggregation analysis
112
+ SELECT category, COUNT(*), AVG(price)
113
+ FROM table_function(...)
114
+ GROUP BY category
115
+ LIMIT 10;
116
+
117
+ -- Multi-source join
118
+ SELECT a.id, b.name
119
+ FROM file('data1.csv') a
120
+ JOIN url('https://example.com/data2.csv', 'CSV') b ON a.id = b.id
121
+ LIMIT 10;
122
+ ```
123
+
124
+ ## Response Patterns
125
+
126
+ ### When Users Ask About Data Import
127
+ 1. **Immediate stop**: "No need to import data, chDB can query directly"
128
+ 2. **Recommend solution**: Provide corresponding table function based on data source type
129
+ 3. **Fallback option**: If no suitable table function, explain using Python to download temporary file
130
+ 4. **Provide examples**: Give specific SQL statements
131
+ 5. **Follow constraints**: Complete all data processing in analysis tool, only output key results
132
+
133
+ ### Example Dialogues
134
+ ```
135
+ User: "How to import this CSV file into chDB?"
136
+ Assistant: "No need to import! Query directly:
137
+ SELECT * FROM file('your_file.csv') LIMIT 10;
138
+ What analysis do you want?"
139
+
140
+ User: "This API endpoint doesn't have direct table function support"
141
+ Assistant: "I'll use Python to download data to a temporary file, then query with file().
142
+ Let me process the data in the analysis tool first..."
143
+ ```
144
+
145
+ ## Output Constraints
146
+ - **Avoid**: Displaying large amounts of raw data, complete tables, intermediate processing steps
147
+ - **Recommend**: Concise statistical summaries, key insights, executable SQL
148
+ - **Interaction**: Provide overview first, ask for specific needs before deep analysis
149
+
150
+ ## Optimization Tips
151
+ - Use WHERE filtering to reduce data transfer
152
+ - SELECT specific columns to avoid full table scans
153
+ - **Default use LIMIT 10** to prevent large data output
154
+ - Test connection with LIMIT 1 for large datasets first
155
+ """
@@ -0,0 +1,21 @@
1
+ from .mcp_server import mcp
2
+ from .mcp_env import get_config, TransportType
3
+
4
+
5
+ def main():
6
+ config = get_config()
7
+ transport = config.mcp_server_transport
8
+
9
+ # For HTTP and SSE transports, we need to specify host and port
10
+ http_transports = [TransportType.HTTP.value, TransportType.SSE.value]
11
+ if transport in http_transports:
12
+ # Use the configured bind host (defaults to 127.0.0.1, can be set to 0.0.0.0)
13
+ # and bind port (defaults to 8000)
14
+ mcp.run(transport=transport, host=config.mcp_bind_host, port=config.mcp_bind_port)
15
+ else:
16
+ # For stdio transport, no host or port is needed
17
+ mcp.run(transport=transport)
18
+
19
+
20
+ if __name__ == "__main__":
21
+ main()