pgsql-mcp-server 0.1.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.
|
File without changes
|
pgsql_mcp_server/app.py
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from contextlib import asynccontextmanager
|
|
3
|
+
from textwrap import dedent
|
|
4
|
+
from typing import AsyncIterator, Optional
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
import click
|
|
7
|
+
from sqlmodel import create_engine, Session, text
|
|
8
|
+
from sqlalchemy.engine import Engine
|
|
9
|
+
from mcp.server.fastmcp import FastMCP, Context
|
|
10
|
+
from sqlalchemy import inspect, Result
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class AppContext:
|
|
15
|
+
engine: Engine
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@asynccontextmanager
|
|
19
|
+
async def app_lifespan(server: FastMCP) -> AsyncIterator[AppContext]:
|
|
20
|
+
_dsn = os.getenv("DATABASE_URL")
|
|
21
|
+
engine = create_engine(_dsn, echo=False)
|
|
22
|
+
|
|
23
|
+
try:
|
|
24
|
+
yield AppContext(engine=engine)
|
|
25
|
+
finally:
|
|
26
|
+
engine.dispose()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
mcp = FastMCP("PostgreSQL Explorer", lifespan=app_lifespan)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@mcp.tool()
|
|
33
|
+
def get_schema_names(ctx: Context) -> str:
|
|
34
|
+
"""Get all schema names."""
|
|
35
|
+
engine = ctx.request_context.lifespan_context.engine
|
|
36
|
+
inspector = inspect(engine)
|
|
37
|
+
|
|
38
|
+
schema_names = inspector.get_schema_names()
|
|
39
|
+
if not inspector.get_schema_names():
|
|
40
|
+
return "No schemas found."
|
|
41
|
+
else:
|
|
42
|
+
return f"All schemas: {', '.join(schema_names)}"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@mcp.tool()
|
|
46
|
+
def get_tables(ctx: Context, schema_name: Optional[str] = "public") -> str:
|
|
47
|
+
"""Get all tables in a schema.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
schema_name: The name of the schema to get tables from, defaults to "public".
|
|
51
|
+
"""
|
|
52
|
+
try:
|
|
53
|
+
schema_name = schema_name or "public"
|
|
54
|
+
engine = ctx.request_context.lifespan_context.engine
|
|
55
|
+
inspector = inspect(engine)
|
|
56
|
+
|
|
57
|
+
table_names = inspector.get_table_names(schema=schema_name)
|
|
58
|
+
if table_names:
|
|
59
|
+
return f"All tables from schema {schema_name}: {', '.join(table_names)}"
|
|
60
|
+
else:
|
|
61
|
+
return f"No tables found in schema {schema_name}."
|
|
62
|
+
|
|
63
|
+
except Exception as e:
|
|
64
|
+
return f"查询表时发生错误: {str(e)}"
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@mcp.tool()
|
|
68
|
+
def get_columns(ctx: Context, table: str, schema_name: Optional[str] = "public") -> str:
|
|
69
|
+
"""Get all columns in a table.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
table: The name of the table to get columns from.
|
|
73
|
+
schema_name: The name of the schema to get columns from, defaults to "public".
|
|
74
|
+
"""
|
|
75
|
+
try:
|
|
76
|
+
schema_name = schema_name or "public"
|
|
77
|
+
engine = ctx.request_context.lifespan_context.engine
|
|
78
|
+
inspector = inspect(engine)
|
|
79
|
+
columns = inspector.get_columns(table, schema=schema_name)
|
|
80
|
+
|
|
81
|
+
if not columns:
|
|
82
|
+
return "No columns found in table."
|
|
83
|
+
else:
|
|
84
|
+
result = []
|
|
85
|
+
for column in columns:
|
|
86
|
+
col = dedent(f"""\
|
|
87
|
+
column_name: {column["name"]}
|
|
88
|
+
type: {column["type"]}
|
|
89
|
+
nullable: {column["nullable"]}
|
|
90
|
+
default: {column.get("default")}
|
|
91
|
+
autoincrement: {column.get("autoincrement")}
|
|
92
|
+
comment: {column.get("comment")} \
|
|
93
|
+
""")
|
|
94
|
+
result.append(col)
|
|
95
|
+
return "\n\n".join(result)
|
|
96
|
+
|
|
97
|
+
except Exception as e:
|
|
98
|
+
return f"查询表时发生错误: {str(e)}"
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@mcp.tool()
|
|
102
|
+
def get_indexes(ctx: Context, table: str, schema_name: Optional[str] = "public") -> str:
|
|
103
|
+
"""Get all indexes in a table.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
table: The name of the table to get indexes from.
|
|
107
|
+
schema_name: The name of the schema to get indexes from, defaults to "public".
|
|
108
|
+
"""
|
|
109
|
+
try:
|
|
110
|
+
schema_name = schema_name or "public"
|
|
111
|
+
engine = ctx.request_context.lifespan_context.engine
|
|
112
|
+
inspector = inspect(engine)
|
|
113
|
+
indexes = inspector.get_indexes(table, schema=schema_name)
|
|
114
|
+
|
|
115
|
+
if not indexes:
|
|
116
|
+
return "No indexes found in table."
|
|
117
|
+
else:
|
|
118
|
+
return str(indexes)
|
|
119
|
+
|
|
120
|
+
except Exception as e:
|
|
121
|
+
return f"查询表时发生错误: {str(e)}"
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
@mcp.tool()
|
|
125
|
+
def run_dql_query(ctx: Context, raw_sql_query: str) -> str:
|
|
126
|
+
"""Run a raw DQL SQL query, like SELECT, SHOW, DESCRIBE, EXPLAIN, etc.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
raw_sql_query: The raw SQL query to run.
|
|
130
|
+
"""
|
|
131
|
+
engine = ctx.request_context.lifespan_context.engine
|
|
132
|
+
with Session(engine) as session:
|
|
133
|
+
raw_sql_select = text(raw_sql_query)
|
|
134
|
+
result: Result = session.execute(raw_sql_select)
|
|
135
|
+
rows = result.fetchall()
|
|
136
|
+
return str(rows)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
@click.command()
|
|
140
|
+
@click.option("--dsn", "-d", type=str, help="Database connection string")
|
|
141
|
+
@click.option("-v", "--verbose", count=True)
|
|
142
|
+
def serve(dsn: str, verbose: bool):
|
|
143
|
+
os.environ["DATABASE_URL"] = dsn
|
|
144
|
+
mcp.run()
|
|
145
|
+
|
|
146
|
+
if __name__ == "__main__":
|
|
147
|
+
serve()
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pgsql-mcp-server
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: PostgreSQL MCP Server
|
|
5
|
+
Requires-Python: >=3.12
|
|
6
|
+
Requires-Dist: click>=8.1.8
|
|
7
|
+
Requires-Dist: httpx>=0.28.1
|
|
8
|
+
Requires-Dist: mcp[cli]>=1.6.0
|
|
9
|
+
Requires-Dist: psycopg2-binary>=2.9.10
|
|
10
|
+
Requires-Dist: python-dotenv>=1.1.0
|
|
11
|
+
Requires-Dist: sqlmodel>=0.0.24
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
pgsql_mcp_server/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
pgsql_mcp_server/app.py,sha256=I6YOGNyLebNnm424lbDD0eO3iGBV6pi03U2BNC40kKQ,4437
|
|
3
|
+
pgsql_mcp_server-0.1.0.dist-info/METADATA,sha256=msd5Alk8xe7ru4j14T7GWvEaAMyP8N2_xUjTQlFfQic,310
|
|
4
|
+
pgsql_mcp_server-0.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
5
|
+
pgsql_mcp_server-0.1.0.dist-info/entry_points.txt,sha256=Z3-MsUmk3TG-aQ6uTFcc7jLCjAa38msknabm0kF4axI,64
|
|
6
|
+
pgsql_mcp_server-0.1.0.dist-info/RECORD,,
|