Spaces:
Sleeping
Sleeping
File size: 6,328 Bytes
7e5bd92 2e00e7a 7e5bd92 30c74cf 2e00e7a 30c74cf 2e00e7a 30c74cf 2e00e7a 30c74cf 2e00e7a 30c74cf 2e00e7a 30c74cf 2e00e7a 30c74cf 2e00e7a 30c74cf 2e00e7a 30c74cf 2e00e7a 7e5bd92 635b668 30c74cf 635b668 30c74cf 574d348 30c74cf 635b668 30c74cf 635b668 30c74cf 635b668 d478b93 30c74cf 7e5bd92 30c74cf 71b023d 30c74cf 33da02e | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 | # app.py
import json
import logging
from datetime import datetime, timezone
from typing import Dict
import gradio as gr
# --------------------------
# Logging setup (quiet + precise)
# --------------------------
class JsonFormatter(logging.Formatter):
def format(self, record: logging.LogRecord) -> str:
payload = {
"ts": datetime.now(timezone.utc).isoformat(timespec="milliseconds"),
"level": record.levelname,
"logger": record.name,
"message": record.getMessage(),
}
# Attach any extra dict passed via logger.extra
for k, v in getattr(record, "__dict__", {}).items():
if k not in payload and k not in (
"args", "asctime", "created", "exc_info", "exc_text", "filename",
"funcName", "levelno", "lineno", "module", "msecs", "msg",
"name", "pathname", "process", "processName", "relativeCreated",
"stack_info", "thread", "threadName"
):
payload[k] = v
return json.dumps(payload, ensure_ascii=False)
def setup_logging() -> logging.Logger:
root = logging.getLogger()
root.setLevel(logging.WARNING) # default higher; opt-in with our logger
# Silence noisy libs
logging.getLogger("httpx").setLevel(logging.ERROR) # suppress asset GETs
logging.getLogger("uvicorn").setLevel(logging.WARNING)
logging.getLogger("uvicorn.access").setLevel(logging.ERROR) # access logs off/noisy
handler = logging.StreamHandler()
handler.setFormatter(JsonFormatter())
app_logger = logging.getLogger("app")
app_logger.setLevel(logging.INFO)
# Avoid duplicate handlers if reloaded
if not any(isinstance(h, logging.StreamHandler) for h in app_logger.handlers):
app_logger.addHandler(handler)
app_logger.propagate = False
return app_logger
log = setup_logging()
# --------------------------
# Time function (LLM-friendly JSON)
# --------------------------
def _time_payload_utc() -> Dict[str, object]:
"""
Return multiple canonical time representations for LLMs/tools.
All times are UTC.
"""
now = datetime.now(timezone.utc)
iso8601 = now.isoformat(timespec="milliseconds").replace("+00:00", "Z")
# ISO week/date parts
iso_year, iso_week, iso_weekday = now.isocalendar()
yday = int(now.strftime("%j"))
return {
# Canonical machine formats
"utc_iso8601": iso8601, # e.g., 2025-11-11T04:15:30.123Z
"unix_seconds": int(now.timestamp()), # e.g., 1762824930
"unix_milliseconds": int(now.timestamp() * 1000),
# Human-friendly hints (still machine-parseable)
"date_utc": now.strftime("%Y-%m-%d"), # 2025-11-11
"time_utc": now.strftime("%H:%M:%S.%f")[:-3], # 04:15:30.123
"weekday_utc": now.strftime("%A"), # Tuesday
"rfc_1123_utc": now.strftime("%a, %d %b %Y %H:%M:%S GMT"),
# Components
"year": now.year,
"month": now.month,
"day": now.day,
"hour": now.hour,
"minute": now.minute,
"second": now.second,
"millisecond": int(now.microsecond / 1000),
# ISO calendar context
"iso_year": iso_year,
"iso_week": iso_week,
"iso_weekday": iso_weekday, # 1=Mon ... 7=Sun
"yearday": yday,
# Provenance
"timezone": "UTC",
"source": "Datetime MCP Server",
"note": "All values are UTC. Prefer 'utc_iso8601' for portability."
}
def now_utc():
"""
Return current time (UTC) as a rich JSON object.
Designed to be easily consumed by LLMs/tools.
"""
payload = _time_payload_utc()
# Application-level audit log (one line per query)
log.info("time_requested", extra={"route": "predict", "out_keys": list(payload.keys())})
return payload
# --------------------------
# Gradio Interface
# --------------------------
demo = gr.Interface(
fn=now_utc,
inputs=None,
outputs=gr.JSON(label="UTC time (LLM-friendly JSON)"),
title="Datetime MCP Server",
description="Returns the current UTC datetime in multiple canonical formats."
)
# --------------------------
# Middleware to log only meaningful hits
# --------------------------
def attach_selective_logging_middleware(app):
"""
Add a lightweight middleware that logs only when an API/predict-like
endpoint is hit; skips UI asset routes (/_app, /assets, /favicon, etc).
"""
from fastapi import Request
from starlette.responses import Response
ASSET_PREFIXES = ("/_app", "/assets", "/static", "/favicon", "/logo", "/file=")
INTERESTING_PREFIXES = ("/run", "/queue", "/predict", "/api", "/chat", "/submit")
@app.middleware("http")
async def selective_access_log(request: Request, call_next):
path = request.url.path or "/"
# Skip common asset paths
if path.startswith(ASSET_PREFIXES):
return await call_next(request)
# Log only interesting interactive endpoints
if path.startswith(INTERESTING_PREFIXES) or path == "/":
ua = request.headers.get("user-agent", "")
client_ip = getattr(request.client, "host", None)
log.info(
"incoming_request",
extra={
"method": request.method,
"path": path,
"client_ip": client_ip,
"user_agent": ua[:300],
},
)
response: Response = await call_next(request)
return response
# Attach middleware to the FastAPI app Gradio runs under
# Gradio 4.x exposes the FastAPI app at demo.server.app
try:
attach_selective_logging_middleware(demo.server.app) # type: ignore[attr-defined]
except Exception:
# Fallback for older/newer Gradio internals; safe no-op if unavailable.
pass
if __name__ == "__main__":
# quiet=True reduces banner/noise; mcp_server=True exposes the MCP SSE endpoint.
demo.launch(
mcp_server=True,
quiet=True, # suppresses gradio banner/extra logs
show_error=True, # still useful while developing
server_name="0.0.0.0",
server_port=None,
)
# https://nimo007-mcp-datetime-server.hf.space/gradio_api/mcp/
|