Skip to main content

MCP server for executing Python via Thonny's isolated environment

Project description

Thonny MCP ๐Ÿ”’๐Ÿ

Your AI Analysis Partner for Private, Local Data Science

Use AI agents to analyze your data without it ever leaving your computer. Thonny MCP connects your AI assistant to a clean, isolated Python environment where you maintain complete control.

"Your data stays yours. Your AI acts as a collaborative partner, not a service provider."


๐ŸŽฏ Who Is This For?

  • Data Analysts working with sensitive business data
  • Scientists handling confidential research data
  • Researchers with privacy constraints (HIPAA, GDPR, etc.)
  • Students learning data science with AI guidance
  • Anyone who wants AI assistance without cloud dependencies

๐Ÿ”’ Privacy-First Design

Traditional Cloud Tools Thonny MCP
โŒ Data uploaded to AI company servers โœ… Data never leaves your computer
โŒ Queries logged and analyzed โœ… Your analysis is private
โŒ Internet connection required โœ… Works completely offline
โŒ Trust required โœ… You control everything

Your AI becomes your coding friend and analysis partner - not a data processor.


โœจ Why Thonny MCP?

For Data Analysts:

  • ๐Ÿ“Š Load CSVs, Excel files, databases - all locally
  • ๐Ÿ“ˆ Generate charts and visualizations on your machine
  • ๐Ÿ” Explore data with pandas, numpy, scipy
  • ๐Ÿ“‰ Run statistical analysis without cloud exposure

For Scientists:

  • ๐Ÿงฌ Process sensitive research data securely
  • ๐Ÿ“š Use specialized libraries (biopython, astropy, etc.)
  • ๐Ÿ”ฌ Reproducible analysis with version-controlled code
  • ๐Ÿ“ Generate reports while maintaining data confidentiality

For Everyone:

  • โœ… No Python setup headaches - Thonny just works
  • โœ… Isolated environment - Won't conflict with system
  • โœ… Open Thonny IDE - Debug and modify AI-generated code
  • โœ… Install any package - Full PyPI access via pip

๐Ÿš€ Quick Start

Step 1: Install Thonny (One Time)

  1. Visit https://thonny.org
  2. Download thonny-xx.x.exe (~30MB)
  3. Run installer (takes ~30 seconds)
  4. Done! ๐ŸŽ‰

Step 2: Install MCP Server (Inside Thonny)

Open Thonny, then:

Via Thonny's Shell (View โ†’ Shell or press Ctrl+`):

%pip install thonny-mcp

This installs the MCP server inside Thonny's isolated environment.

Step 3: Connect Your AI Assistant

Find your Thonny Python path: In Thonny's Shell, run:

import sys
print(sys.executable)

Copy that path (e.g., C:/Users/You/AppData/Local/Programs/Thonny/python.exe)

Configure Claude Desktop:

Option A - Via Settings (Easiest):

  1. Open Claude Desktop
  2. Go to Settings โ†’ Developer โ†’ Edit Config
  3. Add the configuration below

Option B - Manual: Edit %APPDATA%\Claude\claude_desktop_config.json:

{
  "mcpServers": {
    "thonny-python": {
      "command": "C:/Users/YOUR_USERNAME/AppData/Local/Programs/Thonny/python.exe",
      "args": ["-m", "thonny_mcp.server"]
    }
  }
}

Replace YOUR_USERNAME with your Windows username.

Restart Claude Desktop. You're ready!


Alternative - OpenCode CLI (Recommended for Privacy!)

OpenCode CLI is a fantastic choice for private, local AI assistance:

  1. Install OpenCode CLI:

    npm install -g @opencode/cli
    
  2. Get OpenCode Go (cheap & effective):

    • Visit OpenCode pricing
    • Go plan offers affordable access to powerful AI models
    • Perfect for data analysis workloads
    • Private and secure
  3. Configure OpenCode with Thonny MCP:

    Edit your OpenCode config file:

    • Windows: %USERPROFILE%\.config\opencode\opencode.json
    • Project-specific: opencode.json in your project folder

    Add this configuration:

    {
      "mcp": {
        "thonny-python": {
          "type": "local",
          "command": [
            "C:/Users/YOUR_USERNAME/AppData/Local/Programs/Thonny/python.exe",
            "-m",
            "thonny_mcp.server"
          ],
          "enabled": true
        }
      }
    }
    
  4. Start analyzing:

    opencode
    

Why OpenCode CLI + Go Plan?

  • ๐Ÿ’ฐ Affordable - Much cheaper than other AI services
  • ๐Ÿ”’ Private - Your data stays local
  • โšก Fast - Direct connection to your Thonny environment
  • ๐ŸŽฏ Focused - Purpose-built for coding and analysis

๐Ÿ’ก How It Works

You                     AI Agent (Claude/OpenCode)      Thonny MCP              Your Data
โ”‚                               โ”‚                               โ”‚                       โ”‚
โ”‚ "Analyze this CSV for me"     โ”‚                               โ”‚                       โ”‚
โ”‚โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€>โ”‚                               โ”‚                       โ”‚
โ”‚                               โ”‚                               โ”‚                       โ”‚
โ”‚                               โ”‚  "I'll load it with pandas"   โ”‚                       โ”‚
โ”‚                               โ”‚โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€>โ”‚                       โ”‚
โ”‚                               โ”‚                               โ”‚                       โ”‚
โ”‚                               โ”‚                               โ”‚  Load CSV locally     โ”‚
โ”‚                               โ”‚                               โ”‚โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€>โ”‚
โ”‚                               โ”‚                               โ”‚                       โ”‚
โ”‚                               โ”‚                               โ”‚  Return results       โ”‚
โ”‚                               โ”‚                               โ”‚<โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”‚
โ”‚                               โ”‚                               โ”‚                       โ”‚
โ”‚                               โ”‚  "Found interesting patterns" โ”‚                       โ”‚
โ”‚<โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”‚                               โ”‚                       โ”‚

The AI suggests. You approve. The code runs locally on your machine.


๐Ÿ“Š Real-World Example: NYC Taxi Data Analysis

Here's exactly how you'd work with your AI agent to analyze real data:

Step 1: Download Data & Setup

You: "Let's analyze NYC taxi data. Can you download the January 2023 dataset from the NYC TLC website?"

AI Agent (via Thonny MCP):

import urllib.request
import os

# Create data directory
data_dir = "C:/Users/You/data_analysis/nyc_taxi"
os.makedirs(data_dir, exist_ok=True)

# Download NYC taxi data (January 2023)
url = "https://d37ci6vzurychx.cloudfront.net/trip-data/yellow_tripdata_2023-01.parquet"
file_path = os.path.join(data_dir, "yellow_tripdata_2023-01.parquet")

print(f"Downloading from {url}...")
urllib.request.urlretrieve(url, file_path)
print(f"โœ… Downloaded to {file_path}")
print(f"File size: {os.path.getsize(file_path) / (1024*1024):.1f} MB")

Step 2: Explore the Data

You: "Great! Now let's explore what's in this dataset."

AI Agent: "I'll help you analyze this data. First, let me make sure we have the necessary tools installed, then we'll load and explore the dataset."

# First, let's install the packages we need
install_package("pandas")
install_package("pyarrow")  # For reading parquet files

AI: "โœ… Packages installed! Now let's load the data:"

import pandas as pd

# Load the data
df = pd.read_parquet("C:/Users/You/data_analysis/nyc_taxi/yellow_tripdata_2023-01.parquet")

print(f"Dataset shape: {df.shape[0]:,} rows ร— {df.shape[1]} columns")
print("\nColumn names:")
print(df.columns.tolist())
print("\nFirst few rows:")
print(df.head())

AI responds: "The dataset has 3,066,766 taxi trips with 19 columns including pickup/dropoff times, passenger counts, trip distances, and fares."

Step 3: Data Quality Check

You: "Are there any data quality issues we should know about?"

AI Agent:

print("=== Data Quality Report ===\n")

# Check for missing values
missing = df.isnull().sum()
missing_pct = (missing / len(df)) * 100
print("Missing values:")
print(missing[missing > 0])
print()

# Check data types
print("Data types:")
print(df.dtypes)
print()

# Basic statistics for numeric columns
print("Numeric columns summary:")
print(df.describe())

Step 4: Analysis

You: "What's the average trip distance and fare? Any patterns by time of day?"

AI Agent:

import matplotlib.pyplot as plt
import numpy as np

# Calculate trip duration in minutes
df['trip_duration'] = (df['tpep_dropoff_datetime'] - df['tpep_pickup_datetime']).dt.total_seconds() / 60

# Filter out obvious outliers
df_clean = df[
    (df['trip_distance'] > 0) & 
    (df['trip_distance'] < 100) &  # Less than 100 miles
    (df['fare_amount'] > 0) &
    (df['fare_amount'] < 500) &    # Less than $500
    (df['trip_duration'] > 0) &
    (df['trip_duration'] < 180)     # Less than 3 hours
].copy()

print(f"Cleaned dataset: {len(df_clean):,} trips (removed {len(df) - len(df_clean):,} outliers)")
print()

# Key metrics
print("=== Key Metrics ===")
print(f"Average trip distance: {df_clean['trip_distance'].mean():.2f} miles")
print(f"Average fare: ${df_clean['fare_amount'].mean():.2f}")
print(f"Average trip duration: {df_clean['trip_duration'].mean():.1f} minutes")
print(f"Average speed: {(df_clean['trip_distance'] / (df_clean['trip_duration']/60)).mean():.1f} mph")
print()

# Extract hour for time-of-day analysis
df_clean['pickup_hour'] = df_clean['tpep_pickup_datetime'].dt.hour

# Trips by hour
hourly_trips = df_clean.groupby('pickup_hour').size()
print("=== Trips by Hour of Day ===")
for hour, count in hourly_trips.items():
    bar = "โ–ˆ" * int(count / hourly_trips.max() * 30)
    print(f"{hour:2d}:00 {bar} {count:,}")

Step 5: Visualization

You: "Can you create some visualizations and save them locally?"

AI Agent:

# Create visualizations
fig, axes = plt.subplots(2, 2, figsize=(14, 10))

# 1. Trips by hour
hourly_trips.plot(kind='bar', ax=axes[0,0], color='steelblue')
axes[0,0].set_title('NYC Taxi Trips by Hour of Day (January 2023)')
axes[0,0].set_xlabel('Hour')
axes[0,0].set_ylabel('Number of Trips')
axes[0,0].tick_params(axis='x', rotation=0)

# 2. Trip distance distribution
axes[0,1].hist(df_clean['trip_distance'], bins=50, color='coral', edgecolor='black')
axes[0,1].set_title('Distribution of Trip Distances')
axes[0,1].set_xlabel('Distance (miles)')
axes[0,1].set_ylabel('Frequency')
axes[0,1].set_xlim(0, 20)

# 3. Fare vs Distance scatter
sample = df_clean.sample(n=5000)  # Sample for performance
axes[1,0].scatter(sample['trip_distance'], sample['fare_amount'], alpha=0.5, s=1)
axes[1,0].set_title('Fare Amount vs Trip Distance')
axes[1,0].set_xlabel('Trip Distance (miles)')
axes[1,0].set_ylabel('Fare Amount ($)')
axes[1,0].set_xlim(0, 25)
axes[1,0].set_ylim(0, 100)

# 4. Payment type distribution
payment_counts = df_clean['payment_type'].value_counts()
payment_labels = {1: 'Credit Card', 2: 'Cash', 3: 'No Charge', 4: 'Dispute', 5: 'Unknown'}
payment_counts.index = payment_counts.index.map(payment_labels)
axes[1,1].pie(payment_counts.values, labels=payment_counts.index, autopct='%1.1f%%')
axes[1,1].set_title('Payment Type Distribution')

plt.tight_layout()

# Save locally
output_path = "C:/Users/You/data_analysis/nyc_taxi/analysis.png"
plt.savefig(output_path, dpi=300, bbox_inches='tight')
print(f"โœ… Visualization saved to: {output_path}")

# Show correlation
correlation = df_clean['trip_distance'].corr(df_clean['fare_amount'])
print(f"\nCorrelation between distance and fare: {correlation:.3f}")

Step 6: Insights Summary

AI Agent provides summary:

"Based on the analysis of 3+ million NYC taxi trips in January 2023:**

  1. Peak Hours: 6-8 PM sees the highest volume (~200k trips/hour)
  2. Average Trip: 3.1 miles, $18.42 fare, 16 minutes
  3. Payment: 72% use credit cards, 23% cash
  4. Outliers: Removed 45k trips with unrealistic distances/fares
  5. Correlation: Strong correlation (0.87) between distance and fare

The visualizations are saved locally for your reference."


๐ŸŽ“ Learning With AI: Building a Predictive Model

You: "I'm trying to understand Gradient Boosting Machines and how to build a predictive model. Can you walk me through it using this taxi data?"

AI Agent: "Absolutely! Gradient Boosting is like having a team of experts where each one learns from the mistakes of the previous. Let me explain while we build a model to predict taxi fares."

# First, let's install the ML libraries we need
install_package("scikit-learn")
install_package("xgboost")
install_package("matplotlib")

AI: "โœ… Great! Now let's build the model step by step:"

import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.ensemble import GradientBoostingRegressor
from sklearn.metrics import mean_absolute_error, r2_score
import matplotlib.pyplot as plt

# Load a sample of the data (for faster learning)
df = pd.read_parquet("C:/Users/You/data_analysis/nyc_taxi/yellow_tripdata_2023-01.parquet")
df_sample = df.sample(n=10000, random_state=42)  # Small sample for learning

print(f"Working with {len(df_sample):,} trips for this tutorial")
print()

# Prepare features for our model
print("=== Step 1: Prepare Features ===")

# Convert datetime to useful features
df_sample['hour'] = df_sample['tpep_pickup_datetime'].dt.hour
df_sample['day_of_week'] = df_sample['tpep_pickup_datetime'].dt.dayofweek
df_sample['trip_duration'] = (
    df_sample['tpep_dropoff_datetime'] - df_sample['tpep_pickup_datetime']
).dt.total_seconds() / 60

# Select features and target
features = ['trip_distance', 'hour', 'day_of_week', 'passenger_count']
target = 'fare_amount'

# Clean data
df_model = df_sample[
    (df_sample[features].notna().all(axis=1)) &
    (df_sample[target].notna()) &
    (df_sample[target] > 0) &
    (df_sample[target] < 200) &  # Remove extreme outliers
    (df_sample['trip_distance'] > 0) &
    (df_sample['trip_distance'] < 50)
].copy()

X = df_model[features]
y = df_model[target]

print(f"Features we're using: {features}")
print(f"Target: {target}")
print(f"Training data: {len(X):,} trips")

AI explains: "We're preparing features that might predict fare: distance, time of day, day of week, and passenger count. This is the foundation of any ML model."

print("\n=== Step 2: Split Data ===")

# Split into training and testing sets
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42
)

print(f"Training set: {len(X_train):,} trips")
print(f"Test set: {len(X_test):,} trips")

AI explains: "We split the data so we can train on 80% and test on 20%. This tells us if our model actually learned patterns or just memorized the data."

print("\n=== Step 3: Train Gradient Boosting Model ===")

# Create and train the GBM model
model = GradientBoostingRegressor(
    n_estimators=100,      # Number of trees (experts)
    learning_rate=0.1,     # How much each tree learns from errors
    max_depth=3,           # How complex each tree can be
    random_state=42
)

print("Training the model...")
model.fit(X_train, y_train)
print("โœ… Model trained!")

print("\n=== Step 4: Evaluate ===")

# Make predictions
y_pred = model.predict(X_test)

# Calculate metrics
mae = mean_absolute_error(y_test, y_pred)
r2 = r2_score(y_test, y_pred)

print(f"Mean Absolute Error: ${mae:.2f}")
print(f"Rยฒ Score: {r2:.3f}")
print()
print("๐Ÿ’ก Interpretation:")
print(f"   - On average, our predictions are off by ${mae:.2f}")
print(f"   - The model explains {r2*100:.1f}% of fare variation")

AI explains: "The GBM works by building trees sequentially. Each new tree focuses on fixing the errors of all previous trees combined - that's the 'gradient' part. It's like iterative improvement!"

print("\n=== Step 5: Feature Importance ===")

# See which features matter most
importance = pd.DataFrame({
    'feature': features,
    'importance': model.feature_importances_
}).sort_values('importance', ascending=False)

print("Feature importance (what drives fare predictions):")
print(importance)

# Visualize
plt.figure(figsize=(8, 5))
plt.barh(importance['feature'], importance['importance'])
plt.xlabel('Importance')
plt.title('What Drives Taxi Fare Predictions?')
plt.tight_layout()
plt.savefig("C:/Users/You/data_analysis/feature_importance.png")
print("\n๐Ÿ“Š Visualization saved!")

AI summarizes: *"๐ŸŽฏ We just built a Gradient Boosting model! Key takeaways:

  1. GBM combines many simple decision trees
  2. Each tree learns from the mistakes of previous trees
  3. Feature importance shows distance matters most (no surprise!)
  4. Our model predicts fares within ~${:.2f} on average

You can now predict fares for new trips! Try: model.predict([[5.0, 14, 2, 1]]) for a 5-mile trip at 2 PM on Tuesday with 1 passenger."*

You: "That's amazing! Can you save this so I can experiment with it in Thonny?"

AI Agent:

# Save the complete ML tutorial
save_and_run("gbm_tutorial", """
# Gradient Boosting Tutorial - NYC Taxi Fare Prediction
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.ensemble import GradientBoostingRegressor
from sklearn.metrics import mean_absolute_error, r2_score

# Load and prepare data
df = pd.read_parquet("C:/Users/You/data_analysis/nyc_taxi/yellow_tripdata_2023-01.parquet")
df_sample = df.sample(n=10000, random_state=42)

# Feature engineering
df_sample['hour'] = df_sample['tpep_pickup_datetime'].dt.hour
df_sample['day_of_week'] = df_sample['tpep_pickup_datetime'].dt.dayofweek

features = ['trip_distance', 'hour', 'day_of_week', 'passenger_count']
target = 'fare_amount'

# Clean data
df_model = df_sample[
    (df_sample[features].notna().all(axis=1)) &
    (df_sample[target].notna()) &
    (df_sample[target] > 0) &
    (df_sample[target] < 200)
].copy()

X = df_model[features]
y = df_model[target]

# Split and train
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

model = GradientBoostingRegressor(n_estimators=100, learning_rate=0.1, max_depth=3)
model.fit(X_train, y_train)

# Make a prediction!
new_trip = [[5.0, 14, 2, 1]]  # 5 miles, 2 PM, Tuesday, 1 passenger
predicted_fare = model.predict(new_trip)[0]
print(f"Predicted fare: ${predicted_fare:.2f}")

# TODO: Try changing the values above and see how predictions change!
""")

# Open in Thonny for experimentation
open_in_thonny("C:/Users/You/AppData/Local/Programs/Thonny/user_scripts/gbm_tutorial.py")

Now you have a working ML model in Thonny to experiment with! Try changing the trip parameters and see how predictions change. This is how you learn! ๐Ÿš€


๐Ÿ› ๏ธ Available Tools

execute(code, timeout=60)

Run Python code in your isolated Thonny environment.

install_package(package, upgrade=False)

Install any Python package you need.

# Install analysis libraries
install_package("pandas")
install_package("matplotlib")
install_package("seaborn")
install_package("numpy")
install_package("pyarrow")  # For parquet files

list_packages()

See what's already installed.

save_and_run(script_name, code)

Save analysis scripts for later reuse.

open_in_thonny(file_path=None)

Open Thonny IDE to debug or modify code.

check_thonny_installed()

Verify everything is set up correctly.


๐Ÿ” Security Best Practices

  • โœ… Keep data on local drives - Don't use cloud-synced folders for sensitive data
  • โœ… Review AI suggestions - Understand what code will run before executing
  • โœ… Use Thonny's isolation - It can't access system files outside its environment
  • โœ… Audit packages - Only install packages you trust
  • โœ… Delete when done - Clean up downloaded data files after analysis

๐Ÿ’ฌ Philosophy

Thonny MCP is built on these principles:

  1. Your data is yours - We help you keep it that way
  2. AI as partner, not replacement - You maintain control and understanding
  3. Transparency - See exactly what code runs on your machine
  4. Accessibility - Data science shouldn't require IT department setup
  5. Education - Learn while you analyze

๐Ÿค Contributing

This is a community project to make private data analysis accessible to everyone.

  • Found a bug? Open an issue
  • Have an idea? Start a discussion
  • Want to help? Submit a PR

Together we can make AI-assisted data science privacy-preserving by default.


๐Ÿ“„ License

MIT License - Free for personal and commercial use. Keep your data private! ๐Ÿ”’


๐Ÿ™ Acknowledgments

  • Thonny - The Python IDE that makes this possible
  • MCP - Model Context Protocol by Anthropic
  • FastMCP - Python SDK
  • OpenCode - Affordable, private AI for developers
  • NYC Taxi & Limousine Commission - For the excellent public dataset
  • The data science community fighting to keep analysis local and private

Analyze freely. Keep data private. Learn continuously. ๐Ÿš€๐Ÿ”’๐Ÿ“Š

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

thonny_mcp-0.1.0.tar.gz (13.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

thonny_mcp-0.1.0-py3-none-any.whl (14.1 kB view details)

Uploaded Python 3

File details

Details for the file thonny_mcp-0.1.0.tar.gz.

File metadata

  • Download URL: thonny_mcp-0.1.0.tar.gz
  • Upload date:
  • Size: 13.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.10.12

File hashes

Hashes for thonny_mcp-0.1.0.tar.gz
Algorithm Hash digest
SHA256 210d495faf4e45cb4d933b8ab7999230f77c21239cfdd5b290a23a04f98bffca
MD5 683951bfba39d51f38910a8ec6ef20b3
BLAKE2b-256 4227e0dc4cdebbd5d2bc41d41302fdfef04302fbacf8ed22a6401795152219c2

See more details on using hashes here.

File details

Details for the file thonny_mcp-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: thonny_mcp-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 14.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.10.12

File hashes

Hashes for thonny_mcp-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 a77a24ad0e306b9c0d24fc2831b94f7b0e7d05aa38e928168ff8cb50ae904a0e
MD5 8cca1fb6dc741e7c7921c3a0dcdf47ed
BLAKE2b-256 45eeba26d27e9a11a4f455117cfcf49789ec22344de6d87a808cf1fd258c41ae

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page