Spaces:
Running
Running
| """ | |
| PowerMCP Hugging Face Space - MCP Server Interface | |
| Provides a Gradio interface for power system analysis tools via MCP. | |
| """ | |
| import json | |
| import gradio as gr | |
| # ============= Import from PowerMCP ============= | |
| from pandapower_tools import ( | |
| PANDAPOWER_AVAILABLE, | |
| create_empty_network as pp_create_empty, | |
| create_test_network as pp_create_test, | |
| run_power_flow as pp_run_pf, | |
| run_dc_power_flow as pp_run_dc, | |
| get_network_info as pp_get_info, | |
| add_bus as pp_add_bus, | |
| add_line as pp_add_line, | |
| add_load as pp_add_load, | |
| add_generator as pp_add_gen, | |
| add_ext_grid as pp_add_ext_grid, | |
| run_contingency_analysis as pp_contingency, | |
| get_available_std_types as pp_std_types, | |
| ) | |
| from pypsa_tools import ( | |
| PYPSA_AVAILABLE, | |
| create_network as pypsa_create, | |
| get_network_info as pypsa_get_info, | |
| ) | |
| print(f"✓ pandapower {'loaded' if PANDAPOWER_AVAILABLE else 'not available'}") | |
| print(f"✓ pypsa {'loaded' if PYPSA_AVAILABLE else 'not available'}") | |
| # ============= Wrapper Functions for Gradio ============= | |
| # These wrap the PowerMCP functions to return JSON strings for Gradio display | |
| def pandapower_create_empty_network() -> str: | |
| """Create an empty pandapower network.""" | |
| return json.dumps(pp_create_empty(), indent=2, default=str) | |
| def pandapower_create_test_network(network_type: str = "case9") -> str: | |
| """Create a standard IEEE test network.""" | |
| return json.dumps(pp_create_test(network_type), indent=2, default=str) | |
| def pandapower_run_power_flow(algorithm: str = "nr", calculate_voltage_angles: bool = True, | |
| max_iteration: int = 50, tolerance_mva: float = 1e-8) -> str: | |
| """Run AC power flow analysis.""" | |
| return json.dumps(pp_run_pf(algorithm, calculate_voltage_angles, | |
| max_iteration, tolerance_mva), indent=2, default=str) | |
| def pandapower_run_dc_power_flow() -> str: | |
| """Run DC power flow analysis.""" | |
| return json.dumps(pp_run_dc(), indent=2, default=str) | |
| def pandapower_get_network_info() -> str: | |
| """Get network information.""" | |
| return json.dumps(pp_get_info(), indent=2, default=str) | |
| def pandapower_add_bus(name: str, vn_kv: float, bus_type: str = "b", | |
| in_service: bool = True, max_vm_pu: float = 1.1, | |
| min_vm_pu: float = 0.9) -> str: | |
| """Add a bus to the network.""" | |
| return json.dumps(pp_add_bus(name, vn_kv, bus_type, in_service, | |
| max_vm_pu, min_vm_pu), indent=2) | |
| def pandapower_add_line(from_bus: int, to_bus: int, length_km: float, | |
| std_type: str = "NAYY 4x50 SE", name: str = "") -> str: | |
| """Add a line to the network.""" | |
| return json.dumps(pp_add_line(int(from_bus), int(to_bus), length_km, | |
| std_type, name), indent=2) | |
| def pandapower_add_load(bus: int, p_mw: float, q_mvar: float = 0.0, name: str = "") -> str: | |
| """Add a load to the network.""" | |
| return json.dumps(pp_add_load(int(bus), p_mw, q_mvar, name), indent=2) | |
| def pandapower_add_generator(bus: int, p_mw: float, vm_pu: float = 1.0, | |
| name: str = "", controllable: bool = True) -> str: | |
| """Add a generator to the network.""" | |
| return json.dumps(pp_add_gen(int(bus), p_mw, vm_pu, name, controllable), indent=2) | |
| def pandapower_add_ext_grid(bus: int, vm_pu: float = 1.0, va_degree: float = 0.0, | |
| name: str = "External Grid") -> str: | |
| """Add an external grid (slack bus) to the network.""" | |
| return json.dumps(pp_add_ext_grid(int(bus), vm_pu, va_degree, name), indent=2) | |
| def pandapower_run_contingency_analysis(contingency_type: str = "line", | |
| element_indices: str = "") -> str: | |
| """Run N-1 contingency analysis.""" | |
| indices = None | |
| if element_indices.strip(): | |
| indices = [int(x.strip()) for x in element_indices.split(",")] | |
| return json.dumps(pp_contingency(contingency_type, indices), indent=2, default=str) | |
| def pandapower_get_available_std_types() -> str: | |
| """Get available standard types for lines and transformers.""" | |
| return json.dumps(pp_std_types(), indent=2) | |
| def pypsa_create_network(name: str = "PyPSA Network") -> str: | |
| """Create a new PyPSA network.""" | |
| return json.dumps(pypsa_create(name), indent=2) | |
| def pypsa_get_network_info() -> str: | |
| """Get PyPSA network information.""" | |
| return json.dumps(pypsa_get_info(), indent=2) | |
| def get_available_tools() -> str: | |
| """Get a list of all available tools.""" | |
| tools = { | |
| "pandapower_tools": { | |
| "available": PANDAPOWER_AVAILABLE, | |
| "tools": [ | |
| "pandapower_create_empty_network", | |
| "pandapower_create_test_network", | |
| "pandapower_run_power_flow", | |
| "pandapower_run_dc_power_flow", | |
| "pandapower_get_network_info", | |
| "pandapower_add_bus", | |
| "pandapower_add_line", | |
| "pandapower_add_load", | |
| "pandapower_add_generator", | |
| "pandapower_add_ext_grid", | |
| "pandapower_run_contingency_analysis", | |
| "pandapower_get_available_std_types" | |
| ] | |
| }, | |
| "pypsa_tools": { | |
| "available": PYPSA_AVAILABLE, | |
| "tools": [ | |
| "pypsa_create_network", | |
| "pypsa_get_network_info" | |
| ] | |
| } | |
| } | |
| return json.dumps(tools, indent=2) | |
| # ============= Gradio Interfaces ============= | |
| iface_pp_create_empty = gr.Interface( | |
| fn=pandapower_create_empty_network, | |
| inputs=[], | |
| outputs=gr.Textbox(label="Result"), | |
| title="Create Empty Pandapower Network", | |
| description="Create an empty pandapower network" | |
| ) | |
| iface_pp_create_test = gr.Interface( | |
| fn=pandapower_create_test_network, | |
| inputs=[gr.Textbox(label="Network Type", value="case9", | |
| info="Options: case9, case14, case30, case39, case57, case118, etc.")], | |
| outputs=gr.Textbox(label="Result"), | |
| title="Create Test Network", | |
| description="Create a standard IEEE test network" | |
| ) | |
| iface_pp_run_pf = gr.Interface( | |
| fn=pandapower_run_power_flow, | |
| inputs=[ | |
| gr.Textbox(label="Algorithm", value="nr", info="nr, bfsw, gs, fdbx, fdxb"), | |
| gr.Checkbox(label="Calculate Voltage Angles", value=True), | |
| gr.Number(label="Max Iterations", value=50), | |
| gr.Number(label="Tolerance (MVA)", value=1e-8) | |
| ], | |
| outputs=gr.Textbox(label="Result"), | |
| title="Run AC Power Flow", | |
| description="Run AC power flow analysis" | |
| ) | |
| iface_pp_run_dc = gr.Interface( | |
| fn=pandapower_run_dc_power_flow, | |
| inputs=[], | |
| outputs=gr.Textbox(label="Result"), | |
| title="Run DC Power Flow", | |
| description="Run DC power flow analysis" | |
| ) | |
| iface_pp_get_info = gr.Interface( | |
| fn=pandapower_get_network_info, | |
| inputs=[], | |
| outputs=gr.Textbox(label="Result"), | |
| title="Get Network Info", | |
| description="Get network information" | |
| ) | |
| iface_pp_add_bus = gr.Interface( | |
| fn=pandapower_add_bus, | |
| inputs=[ | |
| gr.Textbox(label="Bus Name"), | |
| gr.Number(label="Nominal Voltage (kV)", value=20.0), | |
| gr.Textbox(label="Bus Type", value="b"), | |
| gr.Checkbox(label="In Service", value=True), | |
| gr.Number(label="Max Voltage (pu)", value=1.1), | |
| gr.Number(label="Min Voltage (pu)", value=0.9) | |
| ], | |
| outputs=gr.Textbox(label="Result"), | |
| title="Add Bus", | |
| description="Add a bus to the network" | |
| ) | |
| iface_pp_add_line = gr.Interface( | |
| fn=pandapower_add_line, | |
| inputs=[ | |
| gr.Number(label="From Bus Index", precision=0), | |
| gr.Number(label="To Bus Index", precision=0), | |
| gr.Number(label="Length (km)", value=1.0), | |
| gr.Textbox(label="Standard Type", value="NAYY 4x50 SE"), | |
| gr.Textbox(label="Name", value="") | |
| ], | |
| outputs=gr.Textbox(label="Result"), | |
| title="Add Line", | |
| description="Add a line to the network" | |
| ) | |
| iface_pp_add_load = gr.Interface( | |
| fn=pandapower_add_load, | |
| inputs=[ | |
| gr.Number(label="Bus Index", precision=0), | |
| gr.Number(label="Active Power (MW)", value=1.0), | |
| gr.Number(label="Reactive Power (Mvar)", value=0.0), | |
| gr.Textbox(label="Name", value="") | |
| ], | |
| outputs=gr.Textbox(label="Result"), | |
| title="Add Load", | |
| description="Add a load to the network" | |
| ) | |
| iface_pp_add_gen = gr.Interface( | |
| fn=pandapower_add_generator, | |
| inputs=[ | |
| gr.Number(label="Bus Index", precision=0), | |
| gr.Number(label="Active Power (MW)", value=10.0), | |
| gr.Number(label="Voltage Setpoint (pu)", value=1.0), | |
| gr.Textbox(label="Name", value=""), | |
| gr.Checkbox(label="Controllable", value=True) | |
| ], | |
| outputs=gr.Textbox(label="Result"), | |
| title="Add Generator", | |
| description="Add a generator to the network" | |
| ) | |
| iface_pp_add_ext_grid = gr.Interface( | |
| fn=pandapower_add_ext_grid, | |
| inputs=[ | |
| gr.Number(label="Bus Index", precision=0), | |
| gr.Number(label="Voltage Magnitude (pu)", value=1.0), | |
| gr.Number(label="Voltage Angle (degrees)", value=0.0), | |
| gr.Textbox(label="Name", value="External Grid") | |
| ], | |
| outputs=gr.Textbox(label="Result"), | |
| title="Add External Grid", | |
| description="Add an external grid (slack bus)" | |
| ) | |
| iface_pp_contingency = gr.Interface( | |
| fn=pandapower_run_contingency_analysis, | |
| inputs=[ | |
| gr.Textbox(label="Contingency Type", value="line", info="line, trafo, or gen"), | |
| gr.Textbox(label="Element Indices", value="", info="Comma-separated, empty for all") | |
| ], | |
| outputs=gr.Textbox(label="Result"), | |
| title="Run Contingency Analysis", | |
| description="Run N-1 contingency analysis" | |
| ) | |
| iface_pp_std_types = gr.Interface( | |
| fn=pandapower_get_available_std_types, | |
| inputs=[], | |
| outputs=gr.Textbox(label="Result"), | |
| title="Get Standard Types", | |
| description="Get available standard types" | |
| ) | |
| iface_pypsa_create = gr.Interface( | |
| fn=pypsa_create_network, | |
| inputs=[gr.Textbox(label="Network Name", value="PyPSA Network")], | |
| outputs=gr.Textbox(label="Result"), | |
| title="Create PyPSA Network", | |
| description="Create a new PyPSA network" | |
| ) | |
| iface_pypsa_info = gr.Interface( | |
| fn=pypsa_get_network_info, | |
| inputs=[], | |
| outputs=gr.Textbox(label="Result"), | |
| title="Get PyPSA Network Info", | |
| description="Get PyPSA network information" | |
| ) | |
| iface_list_tools = gr.Interface( | |
| fn=get_available_tools, | |
| inputs=[], | |
| outputs=gr.Textbox(label="Available Tools"), | |
| title="List Available Tools", | |
| description="List all available tools" | |
| ) | |
| # ============= Main Gradio App ============= | |
| with gr.Blocks(title="PowerMCP Server") as demo: | |
| gr.Markdown(""" | |
| # ⚡ PowerMCP - Power System Analysis MCP Server | |
| This Space exposes power system analysis tools via the **Model Context Protocol (MCP)**. | |
| **Use this Space:** | |
| - 🤖 **As MCP tools** in Claude Desktop, Cursor, or other MCP clients | |
| - 🖥️ **Web interface** below for manual testing | |
| **Available Tools:** pandapower (power flow, contingency analysis), PyPSA (optimization) | |
| """) | |
| with gr.Tab("🔌 Quick Start"): | |
| gr.Markdown("### Create and Analyze a Test Network") | |
| with gr.Row(): | |
| with gr.Column(): | |
| test_network_type = gr.Dropdown( | |
| choices=["case9", "case14", "case30", "case39", "case57", "case118"], | |
| value="case9", | |
| label="Select Test Network" | |
| ) | |
| create_btn = gr.Button("🔧 Create Network", variant="primary") | |
| create_output = gr.Code(label="Creation Result", language="json", lines=8) | |
| with gr.Column(): | |
| run_pf_btn = gr.Button("⚡ Run Power Flow", variant="primary") | |
| pf_output = gr.Code(label="Power Flow Results", language="json", lines=8) | |
| with gr.Column(): | |
| contingency_btn = gr.Button("🔍 Run Contingency Analysis", variant="secondary") | |
| contingency_output = gr.Code(label="Contingency Results", language="json", lines=8) | |
| info_btn = gr.Button("ℹ️ Get Network Info", variant="secondary") | |
| info_output = gr.Code(label="Network Info", language="json", lines=10) | |
| create_btn.click(fn=pandapower_create_test_network, inputs=[test_network_type], outputs=[create_output]) | |
| run_pf_btn.click(fn=pandapower_run_power_flow, inputs=[], outputs=[pf_output]) | |
| contingency_btn.click(fn=pandapower_run_contingency_analysis, inputs=[], outputs=[contingency_output]) | |
| info_btn.click(fn=pandapower_get_network_info, inputs=[], outputs=[info_output]) | |
| with gr.Tab("🔧 Pandapower Tools"): | |
| gr.TabbedInterface( | |
| [iface_pp_create_empty, iface_pp_create_test, iface_pp_run_pf, iface_pp_run_dc, | |
| iface_pp_get_info, iface_pp_add_bus, iface_pp_add_line, iface_pp_add_load, | |
| iface_pp_add_gen, iface_pp_add_ext_grid, iface_pp_contingency, iface_pp_std_types], | |
| ["Create Empty", "Create Test", "AC Power Flow", "DC Power Flow", | |
| "Get Info", "Add Bus", "Add Line", "Add Load", "Add Gen", "Add Ext Grid", | |
| "Contingency", "Std Types"] | |
| ) | |
| with gr.Tab("⚡ PyPSA Tools"): | |
| gr.TabbedInterface( | |
| [iface_pypsa_create, iface_pypsa_info], | |
| ["Create Network", "Get Info"] | |
| ) | |
| with gr.Tab("📚 Documentation"): | |
| gr.Markdown(""" | |
| ## MCP Server Usage | |
| ### Connect via MCP Client | |
| Add this Space to your MCP client configuration: | |
| ```json | |
| { | |
| "mcpServers": { | |
| "powermcp": { | |
| "url": "https://your-space-url.hf.space/gradio_api/mcp/sse" | |
| } | |
| } | |
| } | |
| ``` | |
| ### Available Pandapower Tools | |
| | Tool | Description | | |
| |------|-------------| | |
| | `pandapower_create_empty_network` | Create an empty network | | |
| | `pandapower_create_test_network` | Create IEEE test networks | | |
| | `pandapower_run_power_flow` | Run AC power flow | | |
| | `pandapower_run_dc_power_flow` | Run DC power flow | | |
| | `pandapower_get_network_info` | Get network information | | |
| | `pandapower_add_bus` | Add a bus | | |
| | `pandapower_add_line` | Add a line | | |
| | `pandapower_add_load` | Add a load | | |
| | `pandapower_add_generator` | Add a generator | | |
| | `pandapower_add_ext_grid` | Add external grid | | |
| | `pandapower_run_contingency_analysis` | Run N-1 contingency | | |
| | `pandapower_get_available_std_types` | Get standard types | | |
| ### Available PyPSA Tools | |
| | Tool | Description | | |
| |------|-------------| | |
| | `pypsa_create_network` | Create a PyPSA network | | |
| | `pypsa_get_network_info` | Get network info | | |
| """) | |
| with gr.Tab("🛠️ Available Tools"): | |
| gr.Interface( | |
| fn=get_available_tools, | |
| inputs=[], | |
| outputs=gr.Code(label="Tools", language="json"), | |
| title="Available Tools", | |
| description="View all available power system tools" | |
| ) | |
| def main(): | |
| """Entry point for powermcp-app command""" | |
| # Try MCP server mode if available (requires gradio[mcp]) | |
| try: | |
| demo.launch(mcp_server=True, share=False) | |
| except TypeError: | |
| # Fallback for older Gradio versions without MCP support | |
| demo.launch(share=False) | |
| if __name__ == "__main__": | |
| main() | |