Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ pnpm start
python -m venv .venv
source .venv/bin/activate
pip install -r pizzaz_server_python/requirements.txt
uvicorn pizzaz_server_python.main:app --port 8000
uv run uvicorn pizzaz_server_python.main:app --port 8000
```

### Solar system Python server
Expand All @@ -101,10 +101,10 @@ uvicorn pizzaz_server_python.main:app --port 8000
python -m venv .venv
source .venv/bin/activate
pip install -r solar-system_server_python/requirements.txt
uvicorn solar-system_server_python.main:app --port 8000
uv run uvicorn solar-system_server_python.main:app --port 8000
```

You can reuse the same virtual environment for all Python servers—install the dependencies once and run whichever entry point you need.
**Important**: Python servers must be run from the project root directory using the module path format (e.g., `pizzaz_server_python.main:app`), not from within their own directories. You can reuse the same virtual environment for all Python servers—install the dependencies once and run whichever entry point you need.

## Testing in ChatGPT

Expand Down
9 changes: 2 additions & 7 deletions build-all.mts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ import react from "@vitejs/plugin-react";
import fg from "fast-glob";
import path from "path";
import fs from "fs";
import crypto from "crypto";
import pkg from "./package.json" with { type: "json" };
import { getVersionHash } from "./src/version.js";
import tailwindcss from "@tailwindcss/vite";

const entries = fg.sync("src/**/index.{tsx,jsx}");
Expand Down Expand Up @@ -145,11 +144,7 @@ const outputs = fs

const renamed = [];

const h = crypto
.createHash("sha256")
.update(pkg.version, "utf8")
.digest("hex")
.slice(0, 4);
const h = getVersionHash();

console.group("Hashing outputs");
for (const out of outputs) {
Expand Down
31 changes: 6 additions & 25 deletions pizzaz_server_node/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
type Tool
} from "@modelcontextprotocol/sdk/types.js";
import { z } from "zod";
import { generateWidgetHtml } from "./version.js";

type PizzazWidget = {
id: string;
Expand Down Expand Up @@ -47,11 +48,7 @@ const widgets: PizzazWidget[] = [
templateUri: "ui://widget/pizza-map.html",
invoking: "Hand-tossing a map",
invoked: "Served a fresh map",
html: `
<div id="pizzaz-root"></div>
<link rel="stylesheet" href="https://persistent.oaistatic.com/ecosystem-built-assets/pizzaz-0038.css">
<script type="module" src="https://persistent.oaistatic.com/ecosystem-built-assets/pizzaz-0038.js"></script>
`.trim(),
html: generateWidgetHtml("pizzaz"),
responseText: "Rendered a pizza map!"
},
{
Expand All @@ -60,11 +57,7 @@ const widgets: PizzazWidget[] = [
templateUri: "ui://widget/pizza-carousel.html",
invoking: "Carousel some spots",
invoked: "Served a fresh carousel",
html: `
<div id="pizzaz-carousel-root"></div>
<link rel="stylesheet" href="https://persistent.oaistatic.com/ecosystem-built-assets/pizzaz-carousel-0038.css">
<script type="module" src="https://persistent.oaistatic.com/ecosystem-built-assets/pizzaz-carousel-0038.js"></script>
`.trim(),
html: generateWidgetHtml("pizzaz-carousel"),
responseText: "Rendered a pizza carousel!"
},
{
Expand All @@ -73,11 +66,7 @@ const widgets: PizzazWidget[] = [
templateUri: "ui://widget/pizza-albums.html",
invoking: "Hand-tossing an album",
invoked: "Served a fresh album",
html: `
<div id="pizzaz-albums-root"></div>
<link rel="stylesheet" href="https://persistent.oaistatic.com/ecosystem-built-assets/pizzaz-albums-0038.css">
<script type="module" src="https://persistent.oaistatic.com/ecosystem-built-assets/pizzaz-albums-0038.js"></script>
`.trim(),
html: generateWidgetHtml("pizzaz-albums"),
responseText: "Rendered a pizza album!"
},
{
Expand All @@ -86,11 +75,7 @@ const widgets: PizzazWidget[] = [
templateUri: "ui://widget/pizza-list.html",
invoking: "Hand-tossing a list",
invoked: "Served a fresh list",
html: `
<div id="pizzaz-list-root"></div>
<link rel="stylesheet" href="https://persistent.oaistatic.com/ecosystem-built-assets/pizzaz-list-0038.css">
<script type="module" src="https://persistent.oaistatic.com/ecosystem-built-assets/pizzaz-list-0038.js"></script>
`.trim(),
html: generateWidgetHtml("pizzaz-list"),
responseText: "Rendered a pizza list!"
},
{
Expand All @@ -99,11 +84,7 @@ const widgets: PizzazWidget[] = [
templateUri: "ui://widget/pizza-video.html",
invoking: "Hand-tossing a video",
invoked: "Served a fresh video",
html: `
<div id="pizzaz-video-root"></div>
<link rel="stylesheet" href="https://persistent.oaistatic.com/ecosystem-built-assets/pizzaz-video-0038.css">
<script type="module" src="https://persistent.oaistatic.com/ecosystem-built-assets/pizzaz-video-0038.js"></script>
`.trim(),
html: generateWidgetHtml("pizzaz-video"),
responseText: "Rendered a pizza video!"
}
];
Expand Down
80 changes: 80 additions & 0 deletions pizzaz_server_node/src/version.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import fs from "fs";
import path from "path";
import crypto from "crypto";

/**
* Centralized version management for the Apps SDK examples
* This module provides a single source of truth for version hashes
* and asset URLs to avoid hardcoded version references across the codebase.
*/

// Read package.json version
const packageJsonPath = path.resolve("../package.json");
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
const version = packageJson.version;

// Generate version hash (same logic as build-all.mts)
export function getVersionHash(): string {
return crypto
.createHash("sha256")
.update(version, "utf8")
.digest("hex")
.slice(0, 4);
}

// Asset URL configuration
export interface AssetConfig {
/** Base URL for built assets */
baseUrl: string;

/** Whether to use local development mode */
isDevelopment?: boolean;
}

// Default configuration
export const DEFAULT_CONFIG: AssetConfig = {
baseUrl: "https://persistent.oaistatic.com/ecosystem-built-assets",
isDevelopment: false
};

/**
* Get asset URL for a specific widget
*/
export function getAssetUrl(widgetName: string, assetType: "css" | "js" | "html", config: AssetConfig = DEFAULT_CONFIG): string {
const versionHash = getVersionHash();
const filename = `${widgetName}-${versionHash}.${assetType}`;

if (config.isDevelopment) {
// For local development, use relative paths
return `/assets/${filename}`;
}

// For production, use the configured base URL
return `${config.baseUrl}/${filename}`;
}

/**
* Generate HTML markup for a widget
*/
export function generateWidgetHtml(widgetName: string, config: AssetConfig = DEFAULT_CONFIG): string {
const cssUrl = getAssetUrl(widgetName, "css", config);
const jsUrl = getAssetUrl(widgetName, "js", config);
const rootId = `${widgetName}-root`;

return `
<div id="${rootId}"></div>
<link rel="stylesheet" href="${cssUrl}">
<script type="module" src="${jsUrl}"></script>
`.trim();
}

/**
* Get current version information
*/
export function getVersionInfo() {
return {
version,
hash: getVersionHash(),
timestamp: new Date().toISOString()
};
}
48 changes: 13 additions & 35 deletions pizzaz_server_python/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,14 @@
import mcp.types as types
from mcp.server.fastmcp import FastMCP
from pydantic import BaseModel, ConfigDict, Field, ValidationError
try:
from version import generate_widget_html
except ImportError:
# Fallback for when running from the pizzaz_server_python directory
import sys
import os
sys.path.insert(0, os.path.dirname(__file__))
from version import generate_widget_html


@dataclass(frozen=True)
Expand All @@ -36,13 +44,7 @@ class PizzazWidget:
template_uri="ui://widget/pizza-map.html",
invoking="Hand-tossing a map",
invoked="Served a fresh map",
html=(
"<div id=\"pizzaz-root\"></div>\n"
"<link rel=\"stylesheet\" href=\"https://persistent.oaistatic.com/"
"ecosystem-built-assets/pizzaz-0038.css\">\n"
"<script type=\"module\" src=\"https://persistent.oaistatic.com/"
"ecosystem-built-assets/pizzaz-0038.js\"></script>"
),
html=generate_widget_html("pizzaz"),
response_text="Rendered a pizza map!",
),
PizzazWidget(
Expand All @@ -51,13 +53,7 @@ class PizzazWidget:
template_uri="ui://widget/pizza-carousel.html",
invoking="Carousel some spots",
invoked="Served a fresh carousel",
html=(
"<div id=\"pizzaz-carousel-root\"></div>\n"
"<link rel=\"stylesheet\" href=\"https://persistent.oaistatic.com/"
"ecosystem-built-assets/pizzaz-carousel-0038.css\">\n"
"<script type=\"module\" src=\"https://persistent.oaistatic.com/"
"ecosystem-built-assets/pizzaz-carousel-0038.js\"></script>"
),
html=generate_widget_html("pizzaz-carousel"),
response_text="Rendered a pizza carousel!",
),
PizzazWidget(
Expand All @@ -66,13 +62,7 @@ class PizzazWidget:
template_uri="ui://widget/pizza-albums.html",
invoking="Hand-tossing an album",
invoked="Served a fresh album",
html=(
"<div id=\"pizzaz-albums-root\"></div>\n"
"<link rel=\"stylesheet\" href=\"https://persistent.oaistatic.com/"
"ecosystem-built-assets/pizzaz-albums-0038.css\">\n"
"<script type=\"module\" src=\"https://persistent.oaistatic.com/"
"ecosystem-built-assets/pizzaz-albums-0038.js\"></script>"
),
html=generate_widget_html("pizzaz-albums"),
response_text="Rendered a pizza album!",
),
PizzazWidget(
Expand All @@ -81,13 +71,7 @@ class PizzazWidget:
template_uri="ui://widget/pizza-list.html",
invoking="Hand-tossing a list",
invoked="Served a fresh list",
html=(
"<div id=\"pizzaz-list-root\"></div>\n"
"<link rel=\"stylesheet\" href=\"https://persistent.oaistatic.com/"
"ecosystem-built-assets/pizzaz-list-0038.css\">\n"
"<script type=\"module\" src=\"https://persistent.oaistatic.com/"
"ecosystem-built-assets/pizzaz-list-0038.js\"></script>"
),
html=generate_widget_html("pizzaz-list"),
response_text="Rendered a pizza list!",
),
PizzazWidget(
Expand All @@ -96,13 +80,7 @@ class PizzazWidget:
template_uri="ui://widget/pizza-video.html",
invoking="Hand-tossing a video",
invoked="Served a fresh video",
html=(
"<div id=\"pizzaz-video-root\"></div>\n"
"<link rel=\"stylesheet\" href=\"https://persistent.oaistatic.com/"
"ecosystem-built-assets/pizzaz-video-0038.css\">\n"
"<script type=\"module\" src=\"https://persistent.oaistatic.com/"
"ecosystem-built-assets/pizzaz-video-0038.js\"></script>"
),
html=generate_widget_html("pizzaz-video"),
response_text="Rendered a pizza video!",
),
]
Expand Down
84 changes: 84 additions & 0 deletions pizzaz_server_python/version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
"""
Centralized version management for the Apps SDK examples
This module provides a single source of truth for version hashes
and asset URLs to avoid hardcoded version references across the codebase.
"""

import hashlib
import json
import os
from pathlib import Path
from typing import Dict, Literal, Optional


# Read package.json version
package_json_path = Path(__file__).parent.parent / "package.json"
with open(package_json_path, "r") as f:
package_json = json.load(f)
version = package_json["version"]


# Generate version hash (same logic as build-all.mts)
def get_version_hash() -> str:
"""Generate version hash from package.json version."""
return hashlib.sha256(version.encode("utf-8")).hexdigest()[:4]


# Asset URL configuration
class AssetConfig:
"""Configuration for asset URLs."""

def __init__(
self,
base_url: str = "https://persistent.oaistatic.com/ecosystem-built-assets",
is_development: bool = False,
):
self.base_url = base_url
self.is_development = is_development


# Default configuration
DEFAULT_CONFIG = AssetConfig()


def get_asset_url(
widget_name: str,
asset_type: Literal["css", "js", "html"],
config: AssetConfig = DEFAULT_CONFIG,
) -> str:
"""Get asset URL for a specific widget."""
version_hash = get_version_hash()
filename = f"{widget_name}-{version_hash}.{asset_type}"

if config.is_development:
# For local development, use relative paths
return f"/assets/{filename}"

# For production, use the configured base URL
return f"{config.base_url}/{filename}"


def generate_widget_html(
widget_name: str, config: AssetConfig = DEFAULT_CONFIG
) -> str:
"""Generate HTML markup for a widget."""
css_url = get_asset_url(widget_name, "css", config)
js_url = get_asset_url(widget_name, "js", config)
root_id = f"{widget_name}-root"

return f'''
<div id="{root_id}"></div>
<link rel="stylesheet" href="{css_url}">
<script type="module" src="{js_url}"></script>
'''.strip()


def get_version_info() -> Dict[str, str]:
"""Get current version information."""
from datetime import datetime

return {
"version": version,
"hash": get_version_hash(),
"timestamp": datetime.now().isoformat(),
}
Loading