merge: incorporate Power BI community PR #4341

This commit is contained in:
Timothy
2026-03-03 12:27:06 -08:00
3 changed files with 329 additions and 0 deletions
@@ -0,0 +1,77 @@
# Power BI Tool
Power BI integration for automated dataset refresh, report export, and workspace management.
## Features
- **Workspace Management**: List all available Power BI workspaces
- **Dataset Operations**: List and refresh datasets programmatically
- **Report Management**: List reports and export to PDF/PowerPoint
- **OAuth2 Authentication**: Secure credential storage via Hive credential system
## Available Functions
### `power_bi_get_workspaces()`
Get list of all Power BI workspaces accessible to the authenticated user.
### `power_bi_get_datasets(workspace_id)`
List all datasets in a specific workspace.
**Args:**
- `workspace_id`: The Power BI workspace ID
### `power_bi_get_reports(workspace_id)`
List all reports in a specific workspace.
**Args:**
- `workspace_id`: The Power BI workspace ID
### `power_bi_refresh_dataset(workspace_id, dataset_id)`
Trigger a dataset refresh.
**Args:**
- `workspace_id`: The Power BI workspace ID
- `dataset_id`: The dataset ID to refresh
### `power_bi_export_report(workspace_id, report_id, format)`
Export a report to PDF or PowerPoint.
**Args:**
- `workspace_id`: The Power BI workspace ID
- `report_id`: The report ID to export
- `format`: Export format - "PDF" or "PPTX" (default: "PDF")
## Authentication
Requires Power BI access token obtained via OAuth2.
**Via Credential Store:**
```python
# Token stored securely in Hive credential store under key "power_bi"
```
**Via Environment Variable:**
```bash
export POWER_BI_ACCESS_TOKEN="your-token-here"
```
## Example Use Cases
**Business Analytics Agent:**
```
1. Monthly sales analysis completes
2. Agent calls power_bi_refresh_dataset() to update dashboard
3. Agent calls power_bi_export_report() to generate PDF
4. Report emailed to stakeholders automatically
```
**Marketing Agent:**
```
1. Identifies trending products from data analysis
2. Calls power_bi_refresh_dataset() on marketing dashboard
3. Team receives updated insights in real-time
```
## API Reference
Based on Power BI REST API v1.0: https://learn.microsoft.com/en-us/rest/api/power-bi/
@@ -0,0 +1,5 @@
"""Power BI Tool - Dataset refresh and report export."""
from .power_bi_tool import register_tools
__all__ = ["register_tools"]
@@ -0,0 +1,247 @@
cat > power_bi_tool.py << 'EOF'
"""
Power BI Tool - Dataset Refresh and Report Export
Supports Power BI REST API for:
- Refreshing datasets
- Exporting reports
- Managing workspaces
API Reference: https://learn.microsoft.com/en-us/rest/api/power-bi/
"""
from __future__ import annotations
import os
from typing import TYPE_CHECKING, Any
import httpx
from fastmcp import FastMCP
if TYPE_CHECKING:
from aden_tools.credentials import CredentialStoreAdapter
POWER_BI_API_BASE = "https://api.powerbi.com/v1.0/myorg"
class _PowerBIClient:
"""Internal client wrapping Power BI REST API calls."""
def __init__(self, access_token: str):
self._token = access_token
@property
def _headers(self) -> dict[str, str]:
return {
"Authorization": f"Bearer {self._token}",
"Content-Type": "application/json",
}
def _handle_response(self, response: httpx.Response) -> dict[str, Any]:
"""Handle common HTTP error codes."""
if response.status_code == 401:
return {"error": "Invalid or expired Power BI access token"}
if response.status_code == 403:
return {"error": "Insufficient permissions"}
if response.status_code == 404:
return {"error": "Resource not found"}
if response.status_code == 429:
return {"error": "Rate limit exceeded"}
if response.status_code == 202:
return {
"status": "accepted",
"request_id": response.headers.get("x-ms-request-id")
}
if response.status_code >= 400:
try:
detail = response.json().get("message", response.text)
except Exception:
detail = response.text
return {"error": f"Power BI API error (HTTP {response.status_code}): {detail}"}
return response.json()
def get_workspaces(self) -> dict[str, Any]:
"""Get list of Power BI workspaces."""
response = httpx.get(
f"{POWER_BI_API_BASE}/groups",
headers=self._headers,
timeout=30.0,
)
return self._handle_response(response)
def refresh_dataset(self, workspace_id: str, dataset_id: str) -> dict[str, Any]:
"""Trigger a dataset refresh."""
response = httpx.post(
f"{POWER_BI_API_BASE}/groups/{workspace_id}/datasets/{dataset_id}/refreshes",
headers=self._headers,
json={"notifyOption": "NoNotification"},
timeout=30.0,
)
return self._handle_response(response)
def get_datasets(self, workspace_id: str) -> dict[str, Any]:
"""Get list of datasets in a workspace."""
response = httpx.get(
f"{POWER_BI_API_BASE}/groups/{workspace_id}/datasets",
headers=self._headers,
timeout=30.0,
)
return self._handle_response(response)
def get_reports(self, workspace_id: str) -> dict[str, Any]:
"""Get list of reports in a workspace."""
response = httpx.get(
f"{POWER_BI_API_BASE}/groups/{workspace_id}/reports",
headers=self._headers,
timeout=30.0,
)
return self._handle_response(response)
def export_report(
self, workspace_id: str, report_id: str, export_format: str = "PDF"
) -> dict[str, Any]:
"""Export a report to PDF, PPTX, or PNG."""
if export_format.upper() not in ["PDF", "PPTX", "PNG"]:
return {"error": f"Invalid format: {export_format}. Must be PDF, PPTX, or PNG"}
response = httpx.post(
f"{POWER_BI_API_BASE}/groups/{workspace_id}/reports/{report_id}/ExportTo",
headers=self._headers,
json={"format": export_format.upper()},
timeout=30.0,
)
return self._handle_response(response)
def register_tools(
mcp: FastMCP,
credentials: CredentialStoreAdapter | None = None,
) -> None:
"""Register Power BI tools with the MCP server."""
def _get_token() -> str | None:
"""Get Power BI access token from credential manager or environment."""
if credentials is not None:
token = credentials.get("power_bi")
if token is not None and not isinstance(token, str):
raise TypeError(f"Expected string from credentials, got {type(token).__name__}")
return token
return os.getenv("POWER_BI_ACCESS_TOKEN")
def _get_client() -> _PowerBIClient | dict[str, str]:
"""Get a Power BI client, or return an error dict if no credentials."""
token = _get_token()
if not token:
return {
"error": "Power BI credentials not configured",
"help": "Set POWER_BI_ACCESS_TOKEN environment variable or configure via credential store",
}
return _PowerBIClient(token)
@mcp.tool()
def power_bi_get_workspaces() -> dict:
"""
Get list of Power BI workspaces.
Returns:
Dict with list of workspaces or error
"""
client = _get_client()
if isinstance(client, dict):
return client
try:
return client.get_workspaces()
except httpx.TimeoutException:
return {"error": "Request timed out"}
except httpx.RequestError as e:
return {"error": f"Network error: {e}"}
@mcp.tool()
def power_bi_refresh_dataset(workspace_id: str, dataset_id: str) -> dict:
"""
Refresh a Power BI dataset.
Args:
workspace_id: The Power BI workspace ID
dataset_id: The dataset ID to refresh
Returns:
Dict with success status or error
"""
client = _get_client()
if isinstance(client, dict):
return client
try:
return client.refresh_dataset(workspace_id, dataset_id)
except httpx.TimeoutException:
return {"error": "Request timed out"}
except httpx.RequestError as e:
return {"error": f"Network error: {e}"}
@mcp.tool()
def power_bi_get_datasets(workspace_id: str) -> dict:
"""
Get list of datasets in a Power BI workspace.
Args:
workspace_id: The Power BI workspace ID
Returns:
Dict with list of datasets or error
"""
client = _get_client()
if isinstance(client, dict):
return client
try:
return client.get_datasets(workspace_id)
except httpx.TimeoutException:
return {"error": "Request timed out"}
except httpx.RequestError as e:
return {"error": f"Network error: {e}"}
@mcp.tool()
def power_bi_get_reports(workspace_id: str) -> dict:
"""
Get list of reports in a Power BI workspace.
Args:
workspace_id: The Power BI workspace ID
Returns:
Dict with list of reports or error
"""
client = _get_client()
if isinstance(client, dict):
return client
try:
return client.get_reports(workspace_id)
except httpx.TimeoutException:
return {"error": "Request timed out"}
except httpx.RequestError as e:
return {"error": f"Network error: {e}"}
@mcp.tool()
def power_bi_export_report(
workspace_id: str, report_id: str, export_format: str = "PDF"
) -> dict:
"""
Export a Power BI report to PDF, PPTX, or PNG.
Args:
workspace_id: The Power BI workspace ID
report_id: The report ID to export
export_format: Export format - "PDF", "PPTX", or "PNG" (default: "PDF")
Returns:
Dict with export result or error
"""
client = _get_client()
if isinstance(client, dict):
return client
try:
return client.export_report(workspace_id, report_id, export_format)
except httpx.TimeoutException:
return {"error": "Request timed out"}
except httpx.RequestError as e:
return {"error": f"Network error: {e}"}
EOF