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
@@ -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,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.27.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ pgsql-mcp-server = pgsql_mcp_server.app:serve