tina4-python 3.13.49__tar.gz → 3.13.51__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {tina4_python-3.13.49 → tina4_python-3.13.51}/PKG-INFO +1 -1
- {tina4_python-3.13.49 → tina4_python-3.13.51}/pyproject.toml +1 -1
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/__init__.py +1 -1
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/dev_admin/__init__.py +93 -31
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/mcp/__init__.py +173 -34
- {tina4_python-3.13.49 → tina4_python-3.13.51}/.gitignore +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/README.md +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/CLAUDE.md +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/HtmlElement.py +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/Testing.py +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/ai/__init__.py +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/api/__init__.py +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/auth/__init__.py +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/cache/__init__.py +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/cli/__init__.py +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/container/__init__.py +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/core/__init__.py +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/core/cache.py +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/core/constants.py +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/core/events.py +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/core/middleware.py +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/core/rate_limiter.py +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/core/request.py +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/core/response.py +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/core/router.py +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/core/server.py +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/crud/__init__.py +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/database/__init__.py +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/database/adapter.py +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/database/connection.py +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/database/firebird.py +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/database/mongodb.py +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/database/mssql.py +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/database/mysql.py +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/database/odbc.py +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/database/postgres.py +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/database/sqlite.py +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/debug/__init__.py +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/debug/error_overlay.py +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/dev_admin/metrics.py +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/dev_admin/plan.py +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/dev_admin/project_index.py +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/docs.py +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/docstore/__init__.py +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/dotenv/__init__.py +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/env.py +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/frond/FROND.md +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/frond/__init__.py +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/frond/engine.py +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/gallery/auth/meta.json +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/gallery/auth/src/routes/api/gallery_auth.py +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/gallery/database/meta.json +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/gallery/database/src/routes/api/gallery_db.py +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/gallery/error-overlay/meta.json +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/gallery/error-overlay/src/routes/api/gallery_crash.py +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/gallery/orm/meta.json +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/gallery/orm/src/orm/Product.py +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/gallery/orm/src/routes/api/gallery_products.py +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/gallery/queue/meta.json +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/gallery/queue/src/routes/api/gallery_queue.py +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/gallery/rest-api/meta.json +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/gallery/rest-api/src/routes/api/gallery_hello.py +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/gallery/templates/meta.json +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/gallery/templates/src/routes/gallery_page.py +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/gallery/templates/src/templates/gallery_page.twig +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/graphql/__init__.py +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/i18n/__init__.py +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/mcp/protocol.py +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/mcp/tools.py +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/messenger/__init__.py +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/migration/__init__.py +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/migration/runner.py +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/orm/__init__.py +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/orm/fields.py +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/orm/model.py +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/public/__feedback/widget.js +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/public/css/tina4.css +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/public/css/tina4.min.css +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/public/favicon.ico +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/public/images/logo.svg +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/public/images/tina4-logo-icon.webp +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/public/js/frond.js +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/public/js/frond.min.js +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/public/js/tina4-dev-admin.js +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/public/js/tina4-dev-admin.min.js +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/public/js/tina4.min.js +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/public/js/tina4js.min.js +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/public/swagger/index.html +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/public/swagger/oauth2-redirect.html +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/query_builder/__init__.py +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/queue/__init__.py +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/queue/job.py +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/queue/kafka_backend.py +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/queue/lite_backend.py +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/queue/mongo_backend.py +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/queue/rabbitmq_backend.py +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/queue_backends/__init__.py +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/queue_backends/kafka_backend.py +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/queue_backends/mongo_backend.py +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/queue_backends/rabbitmq_backend.py +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/scss/__init__.py +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/scss/tina4css/_alerts.scss +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/scss/tina4css/_badges.scss +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/scss/tina4css/_buttons.scss +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/scss/tina4css/_cards.scss +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/scss/tina4css/_forms.scss +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/scss/tina4css/_grid.scss +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/scss/tina4css/_modals.scss +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/scss/tina4css/_nav.scss +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/scss/tina4css/_reset.scss +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/scss/tina4css/_tables.scss +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/scss/tina4css/_typography.scss +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/scss/tina4css/_utilities.scss +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/scss/tina4css/_variables.scss +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/scss/tina4css/base.scss +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/scss/tina4css/colors.scss +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/scss/tina4css/tina4.scss +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/seeder/__init__.py +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/service/__init__.py +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/session/__init__.py +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/session_handlers/__init__.py +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/session_handlers/mongodb_handler.py +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/session_handlers/redis_handler.py +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/session_handlers/valkey_handler.py +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/swagger/__init__.py +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/templates/components/crud.twig +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/templates/docker/distroless/Dockerfile +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/templates/docker/poetry/Dockerfile +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/templates/docker/python/Dockerfile +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/templates/docker/uv/Dockerfile +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/templates/errors/302.twig +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/templates/errors/401.twig +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/templates/errors/403.twig +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/templates/errors/404.twig +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/templates/errors/500.twig +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/templates/errors/502.twig +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/templates/errors/503.twig +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/templates/errors/base.twig +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/templates/frontend/README.md +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/templates/readme.md +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/test/__init__.py +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/test_client/__init__.py +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/translations/af/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/translations/af/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/translations/en/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/translations/en/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/translations/es/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/translations/es/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/translations/fr/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/translations/fr/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/translations/ja/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/translations/ja/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/translations/zh/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/translations/zh/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/validator/__init__.py +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/websocket/__init__.py +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/websocket/backplane.py +0 -0
- {tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/wsdl/__init__.py +0 -0
|
@@ -320,8 +320,9 @@ def write_mcp_discovery_file() -> None:
|
|
|
320
320
|
expected = {
|
|
321
321
|
"mcpServers": {
|
|
322
322
|
"tina4-live-docs": {
|
|
323
|
-
"
|
|
324
|
-
"
|
|
323
|
+
"type": "http",
|
|
324
|
+
"url": f"http://localhost:{port}/__dev/mcp",
|
|
325
|
+
"description": "Live API docs + dev tools for this Tina4 project (framework + user code)",
|
|
325
326
|
}
|
|
326
327
|
}
|
|
327
328
|
}
|
|
@@ -485,11 +486,13 @@ def get_api_handlers() -> dict:
|
|
|
485
486
|
# @mcp_tool decorator appear in both immediately.
|
|
486
487
|
"/__dev/api/mcp/tools": ("GET", _api_mcp_tools),
|
|
487
488
|
"/__dev/api/mcp/call": ("POST", _api_mcp_call),
|
|
488
|
-
#
|
|
489
|
-
# Same registry as the REST shim above
|
|
490
|
-
#
|
|
491
|
-
|
|
492
|
-
|
|
489
|
+
# MCP transport surface for real clients (Claude Code / Desktop).
|
|
490
|
+
# Same registry as the REST shim above. /__dev/mcp is the Streamable
|
|
491
|
+
# HTTP endpoint (POST message + DELETE session; "*" so one handler
|
|
492
|
+
# switches on the method); /message + /sse are the legacy HTTP+SSE
|
|
493
|
+
# transport, kept working for older SSE-only clients.
|
|
494
|
+
"/__dev/mcp": ("*", _api_mcp_endpoint),
|
|
495
|
+
"/__dev/mcp/message": ("POST", _api_mcp_message),
|
|
493
496
|
"/__dev/mcp/sse": ("GET", _api_mcp_sse),
|
|
494
497
|
# ── Scaffold REST shim ──
|
|
495
498
|
# Wraps the tina4python CLI's `generate <kind> <name>` so the
|
|
@@ -2964,43 +2967,102 @@ async def _api_mcp_call(request, response):
|
|
|
2964
2967
|
return response({"ok": False, "name": name, "error": str(exc)}, 500)
|
|
2965
2968
|
|
|
2966
2969
|
|
|
2967
|
-
# ─── MCP
|
|
2970
|
+
# ─── MCP transport endpoint ────────────────────────────────────────
|
|
2968
2971
|
#
|
|
2969
|
-
# The protocol surface real MCP clients (Claude
|
|
2970
|
-
#
|
|
2971
|
-
#
|
|
2972
|
-
#
|
|
2973
|
-
#
|
|
2974
|
-
#
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2972
|
+
# The protocol surface real MCP clients (Claude Code / Claude Desktop)
|
|
2973
|
+
# speak. Mounted on the running dev server so each `tina4 serve`d project
|
|
2974
|
+
# exposes its OWN endpoint, giving an AI agent live access scoped to that
|
|
2975
|
+
# project. Shares the same `_default_server` tool registry as the REST
|
|
2976
|
+
# shim above, so every @mcp_tool shows up on all surfaces.
|
|
2977
|
+
#
|
|
2978
|
+
# Two transports live here:
|
|
2979
|
+
# * Streamable HTTP (current) — POST /__dev/mcp with the JSON-RPC message;
|
|
2980
|
+
# the response comes back inline as application/json, and initialize
|
|
2981
|
+
# issues an Mcp-Session-Id header the client echoes on later requests.
|
|
2982
|
+
# GET is 405 (this server initiates no messages) and DELETE ends a
|
|
2983
|
+
# session.
|
|
2984
|
+
# * Legacy HTTP+SSE (2024-11-05) — GET /__dev/mcp/sse opens a persistent
|
|
2985
|
+
# stream that first names the POST endpoint, then delivers each JSON-RPC
|
|
2986
|
+
# response as an SSE `message` event; POST /__dev/mcp/message feeds it.
|
|
2987
|
+
# Kept working for older SSE-only clients.
|
|
2988
|
+
|
|
2989
|
+
|
|
2990
|
+
def _mcp_session_header(request) -> str:
|
|
2991
|
+
"""Read the Mcp-Session-Id request header (empty string when absent)."""
|
|
2992
|
+
headers = getattr(request, "headers", None) or {}
|
|
2993
|
+
return headers.get("mcp-session-id", "") or ""
|
|
2994
|
+
|
|
2995
|
+
|
|
2996
|
+
def _mcp_apply(response, outcome):
|
|
2997
|
+
"""Apply a dispatch_http/dispatch_sse_message result dict (status,
|
|
2998
|
+
headers, body) onto the dev-admin response."""
|
|
2982
2999
|
import json as _json
|
|
3000
|
+
for name, value in outcome["headers"].items():
|
|
3001
|
+
response.header(name, value)
|
|
3002
|
+
body = outcome["body"]
|
|
3003
|
+
if not body:
|
|
3004
|
+
return response("", outcome["status"])
|
|
3005
|
+
return response(_json.loads(body), outcome["status"])
|
|
3006
|
+
|
|
3007
|
+
|
|
3008
|
+
async def _api_mcp_endpoint(request, response):
|
|
3009
|
+
"""The Streamable HTTP endpoint at /__dev/mcp (method wildcard).
|
|
3010
|
+
|
|
3011
|
+
POST — a JSON-RPC message; response is inline application/json.
|
|
3012
|
+
GET — 405, this server pushes no unsolicited messages (use the
|
|
3013
|
+
legacy /sse stream if you need server-initiated framing).
|
|
3014
|
+
DELETE — terminate the session named by Mcp-Session-Id.
|
|
3015
|
+
"""
|
|
2983
3016
|
from tina4_python.mcp import _get_default_server
|
|
2984
3017
|
if not _mcp_request_allowed(request):
|
|
2985
3018
|
return response({"error": "MCP disabled"}, 404)
|
|
2986
3019
|
server = _get_default_server()
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
3020
|
+
method = (getattr(request, "method", "") or "GET").upper()
|
|
3021
|
+
|
|
3022
|
+
if method == "POST":
|
|
3023
|
+
outcome = server.dispatch_http(request.body, _mcp_session_header(request))
|
|
3024
|
+
return _mcp_apply(response, outcome)
|
|
3025
|
+
|
|
3026
|
+
if method == "DELETE":
|
|
3027
|
+
server.close_session(_mcp_session_header(request))
|
|
2991
3028
|
return response("", 204)
|
|
2992
|
-
|
|
3029
|
+
|
|
3030
|
+
# GET (and anything else): no server-initiated stream on this endpoint.
|
|
3031
|
+
response.header("Allow", "POST, DELETE")
|
|
3032
|
+
return response({"error": "method not allowed"}, 405)
|
|
3033
|
+
|
|
3034
|
+
|
|
3035
|
+
async def _api_mcp_message(request, response):
|
|
3036
|
+
"""POST /__dev/mcp/message — legacy HTTP+SSE message sink.
|
|
3037
|
+
|
|
3038
|
+
Delivers the JSON-RPC response on the matching open SSE stream (202
|
|
3039
|
+
here); with no open stream it degrades to an inline Streamable HTTP
|
|
3040
|
+
response, so the path also serves a plain POST client.
|
|
3041
|
+
"""
|
|
3042
|
+
from tina4_python.mcp import _get_default_server
|
|
3043
|
+
if not _mcp_request_allowed(request):
|
|
3044
|
+
return response({"error": "MCP disabled"}, 404)
|
|
3045
|
+
server = _get_default_server()
|
|
3046
|
+
params = getattr(request, "params", None) or {}
|
|
3047
|
+
session_id = params.get("sessionId") or _mcp_session_header(request)
|
|
3048
|
+
outcome = server.dispatch_sse_message(request.body, session_id)
|
|
3049
|
+
return _mcp_apply(response, outcome)
|
|
2993
3050
|
|
|
2994
3051
|
|
|
2995
3052
|
async def _api_mcp_sse(request, response):
|
|
2996
|
-
"""GET — SSE
|
|
2997
|
-
|
|
3053
|
+
"""GET /__dev/mcp/sse — legacy HTTP+SSE stream.
|
|
3054
|
+
|
|
3055
|
+
Opens a persistent SSE connection: first the `endpoint` event naming the
|
|
3056
|
+
POST target (session-tagged), then each JSON-RPC response as it arrives.
|
|
2998
3057
|
"""
|
|
3058
|
+
from tina4_python.mcp import _get_default_server
|
|
2999
3059
|
if not _mcp_request_allowed(request):
|
|
3000
3060
|
return response({"error": "MCP disabled"}, 404)
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3061
|
+
server = _get_default_server()
|
|
3062
|
+
session_id = server.open_session()
|
|
3063
|
+
base = getattr(request, "path", "/__dev/mcp/sse").rsplit("/sse", 1)[0]
|
|
3064
|
+
endpoint_url = f"{base}/message?sessionId={session_id}"
|
|
3065
|
+
return response.stream(server.sse_stream(session_id, endpoint_url))
|
|
3004
3066
|
|
|
3005
3067
|
|
|
3006
3068
|
# ─── Scaffold REST shim ────────────────────────────────────────────
|
|
@@ -22,6 +22,8 @@ import os
|
|
|
22
22
|
import json
|
|
23
23
|
import inspect
|
|
24
24
|
import socket
|
|
25
|
+
import secrets
|
|
26
|
+
import time
|
|
25
27
|
from pathlib import Path
|
|
26
28
|
|
|
27
29
|
from .protocol import (
|
|
@@ -31,6 +33,12 @@ from .protocol import (
|
|
|
31
33
|
INVALID_PARAMS, INTERNAL_ERROR,
|
|
32
34
|
)
|
|
33
35
|
|
|
36
|
+
# MCP protocol versions this server can speak, newest first. The 2025-*
|
|
37
|
+
# versions are the Streamable HTTP era; 2024-11-05 is the legacy HTTP+SSE
|
|
38
|
+
# transport we still accept for older clients (Claude Desktop et al.).
|
|
39
|
+
SUPPORTED_PROTOCOL_VERSIONS = ("2025-06-18", "2025-03-26", "2024-11-05")
|
|
40
|
+
LATEST_PROTOCOL_VERSION = SUPPORTED_PROTOCOL_VERSIONS[0]
|
|
41
|
+
|
|
34
42
|
# Re-export protocol helpers as public API (parity with PHP/Ruby/Node)
|
|
35
43
|
__all__ = [
|
|
36
44
|
"McpServer", "mcp_tool", "mcp_resource",
|
|
@@ -192,8 +200,126 @@ class McpServer:
|
|
|
192
200
|
self._tools: dict[str, dict] = {}
|
|
193
201
|
self._resources: dict[str, dict] = {}
|
|
194
202
|
self._initialized = False
|
|
203
|
+
# Streamable HTTP session ids issued at initialize time, mapped to
|
|
204
|
+
# their creation timestamp. Transport layers echo the id back in the
|
|
205
|
+
# Mcp-Session-Id header; a request bearing an unknown id gets a 404 so
|
|
206
|
+
# the client knows to re-initialize.
|
|
207
|
+
self._sessions: dict[str, float] = {}
|
|
208
|
+
# Open legacy HTTP+SSE streams keyed by session id. The GET /sse
|
|
209
|
+
# handler registers an asyncio.Queue here; the POST /message handler
|
|
210
|
+
# pushes each JSON-RPC response onto it so it streams back on the open
|
|
211
|
+
# connection (the 2024-11-05 transport). Empty for Streamable HTTP.
|
|
212
|
+
self._sse_queues: dict = {}
|
|
195
213
|
McpServer._instances.append(self)
|
|
196
214
|
|
|
215
|
+
# ── Session lifecycle (Streamable HTTP + legacy SSE correlation) ──
|
|
216
|
+
|
|
217
|
+
def open_session(self) -> str:
|
|
218
|
+
"""Mint a new session id and remember it. Called on `initialize`."""
|
|
219
|
+
sid = secrets.token_hex(16)
|
|
220
|
+
self._sessions[sid] = time.time()
|
|
221
|
+
return sid
|
|
222
|
+
|
|
223
|
+
def is_valid_session(self, session_id: str) -> bool:
|
|
224
|
+
"""True when `session_id` was issued by this server and still open."""
|
|
225
|
+
return bool(session_id) and session_id in self._sessions
|
|
226
|
+
|
|
227
|
+
def close_session(self, session_id: str) -> bool:
|
|
228
|
+
"""Forget a session (client DELETE or SSE stream close). Returns
|
|
229
|
+
True when a live session was actually removed."""
|
|
230
|
+
return self._sessions.pop(session_id, None) is not None
|
|
231
|
+
|
|
232
|
+
def negotiate_protocol_version(self, requested: str | None) -> str:
|
|
233
|
+
"""Pick the protocol version to run on. Echo the client's requested
|
|
234
|
+
version when we support it (proper negotiation), else fall back to the
|
|
235
|
+
newest version we speak so an unversioned/old client still connects."""
|
|
236
|
+
if requested in SUPPORTED_PROTOCOL_VERSIONS:
|
|
237
|
+
return requested
|
|
238
|
+
return LATEST_PROTOCOL_VERSION
|
|
239
|
+
|
|
240
|
+
def _peek_method(self, raw_data) -> str | None:
|
|
241
|
+
"""Read the JSON-RPC `method` from a raw request without dispatching.
|
|
242
|
+
Used by the transport to spot `initialize` (mint a session) before it
|
|
243
|
+
hands the message to handle_message()."""
|
|
244
|
+
try:
|
|
245
|
+
obj = raw_data if isinstance(raw_data, dict) else json.loads(raw_data or "{}")
|
|
246
|
+
except (ValueError, TypeError):
|
|
247
|
+
return None
|
|
248
|
+
return obj.get("method") if isinstance(obj, dict) else None
|
|
249
|
+
|
|
250
|
+
def dispatch_http(self, raw_data, session_id: str = "") -> dict:
|
|
251
|
+
"""Transport-agnostic Streamable HTTP POST handler.
|
|
252
|
+
|
|
253
|
+
Every language's transport calls this so the wire behaviour stays
|
|
254
|
+
identical:
|
|
255
|
+
- `initialize` mints a session id, returned in the Mcp-Session-Id
|
|
256
|
+
response header.
|
|
257
|
+
- a non-initialize request carrying an unknown session id is a 404
|
|
258
|
+
(JSON-RPC error) so the client knows to re-initialize.
|
|
259
|
+
- a notification / response-only POST (no id) yields 202 with an
|
|
260
|
+
empty body.
|
|
261
|
+
- anything else returns 200 with the JSON-RPC response as
|
|
262
|
+
application/json, which the MCP Streamable HTTP spec permits for a
|
|
263
|
+
POST that resolves to a single response.
|
|
264
|
+
|
|
265
|
+
Returns {"status": int, "headers": {name: value}, "body": str}.
|
|
266
|
+
"""
|
|
267
|
+
is_init = self._peek_method(raw_data) == "initialize"
|
|
268
|
+
if not is_init and session_id and not self.is_valid_session(session_id):
|
|
269
|
+
return {
|
|
270
|
+
"status": 404,
|
|
271
|
+
"headers": {},
|
|
272
|
+
"body": encode_error(None, INVALID_REQUEST, "session not found"),
|
|
273
|
+
}
|
|
274
|
+
body = self.handle_message(raw_data)
|
|
275
|
+
headers: dict[str, str] = {}
|
|
276
|
+
if is_init:
|
|
277
|
+
headers["Mcp-Session-Id"] = self.open_session()
|
|
278
|
+
if not body:
|
|
279
|
+
return {"status": 202, "headers": headers, "body": ""}
|
|
280
|
+
return {"status": 200, "headers": headers, "body": body}
|
|
281
|
+
|
|
282
|
+
def dispatch_sse_message(self, raw_data, session_id: str = "") -> dict:
|
|
283
|
+
"""Legacy HTTP+SSE POST /message handler.
|
|
284
|
+
|
|
285
|
+
When a live SSE stream is open for `session_id`, run the message and
|
|
286
|
+
push the JSON-RPC response down that stream (returning 202 here, per
|
|
287
|
+
the 2024-11-05 transport). With no open stream this degrades to an
|
|
288
|
+
inline Streamable HTTP response, so the same /message path serves both
|
|
289
|
+
a legacy SSE client and a plain POST client.
|
|
290
|
+
"""
|
|
291
|
+
queue = self._sse_queues.get(session_id) if session_id else None
|
|
292
|
+
if queue is None:
|
|
293
|
+
return self.dispatch_http(raw_data, session_id)
|
|
294
|
+
body = self.handle_message(raw_data)
|
|
295
|
+
if body:
|
|
296
|
+
queue.put_nowait(body)
|
|
297
|
+
return {"status": 202, "headers": {}, "body": ""}
|
|
298
|
+
|
|
299
|
+
async def sse_stream(self, session_id: str, endpoint_url: str, keepalive: float = 15.0):
|
|
300
|
+
"""Async generator of SSE frames for the legacy HTTP+SSE transport.
|
|
301
|
+
|
|
302
|
+
Emits the `endpoint` event first (telling the client where to POST),
|
|
303
|
+
then each queued JSON-RPC response as it arrives, with periodic
|
|
304
|
+
keep-alive comment frames so proxies do not close an idle connection.
|
|
305
|
+
Registers the per-session queue up front and tears it down (plus the
|
|
306
|
+
session) when the client disconnects and the generator is closed.
|
|
307
|
+
"""
|
|
308
|
+
import asyncio
|
|
309
|
+
queue: asyncio.Queue = asyncio.Queue()
|
|
310
|
+
self._sse_queues[session_id] = queue
|
|
311
|
+
try:
|
|
312
|
+
yield f"event: endpoint\ndata: {endpoint_url}\n\n"
|
|
313
|
+
while True:
|
|
314
|
+
try:
|
|
315
|
+
message = await asyncio.wait_for(queue.get(), timeout=keepalive)
|
|
316
|
+
yield f"event: message\ndata: {message}\n\n"
|
|
317
|
+
except asyncio.TimeoutError:
|
|
318
|
+
yield ": keep-alive\n\n"
|
|
319
|
+
finally:
|
|
320
|
+
self._sse_queues.pop(session_id, None)
|
|
321
|
+
self.close_session(session_id)
|
|
322
|
+
|
|
197
323
|
def register_tool(self, name: str, handler, description: str = "", schema: dict | None = None):
|
|
198
324
|
"""Register a tool callable."""
|
|
199
325
|
if schema is None:
|
|
@@ -244,10 +370,14 @@ class McpServer:
|
|
|
244
370
|
return encode_error(request_id, INTERNAL_ERROR, str(e))
|
|
245
371
|
|
|
246
372
|
def _handle_initialize(self, params: dict) -> dict:
|
|
247
|
-
"""Handle initialize request —
|
|
373
|
+
"""Handle initialize request — negotiate the protocol version and
|
|
374
|
+
return server capabilities. We echo the client's requested version
|
|
375
|
+
when we support it and otherwise offer our newest, so both a current
|
|
376
|
+
Streamable HTTP client and a legacy 2024-11-05 client connect."""
|
|
248
377
|
self._initialized = True
|
|
378
|
+
requested = (params or {}).get("protocolVersion")
|
|
249
379
|
return {
|
|
250
|
-
"protocolVersion":
|
|
380
|
+
"protocolVersion": self.negotiate_protocol_version(requested),
|
|
251
381
|
"capabilities": {
|
|
252
382
|
"tools": {"listChanged": False},
|
|
253
383
|
"resources": {"subscribe": False, "listChanged": False},
|
|
@@ -344,44 +474,52 @@ class McpServer:
|
|
|
344
474
|
def register_routes(self, router_module):
|
|
345
475
|
"""Register HTTP routes for this MCP server on the Tina4 router.
|
|
346
476
|
|
|
347
|
-
|
|
348
|
-
POST {path}
|
|
349
|
-
|
|
477
|
+
Mounts both supported transports on `path`:
|
|
478
|
+
POST {path} — Streamable HTTP (the current transport)
|
|
479
|
+
POST {path}/message — legacy HTTP+SSE message sink (+ inline fallback)
|
|
480
|
+
GET {path}/sse — legacy HTTP+SSE stream (persistent)
|
|
481
|
+
|
|
482
|
+
A Streamable HTTP client (Claude Code `--transport http`) POSTs to
|
|
483
|
+
`{path}` and reads the JSON-RPC response inline, with an Mcp-Session-Id
|
|
484
|
+
header issued on initialize. A legacy SSE client (Claude Desktop,
|
|
485
|
+
`--transport sse`) GETs `{path}/sse`, receives the endpoint event, and
|
|
486
|
+
its responses stream back on that connection.
|
|
350
487
|
"""
|
|
351
488
|
server = self
|
|
352
|
-
msg_path = f"{self.path}/message"
|
|
353
|
-
sse_path = f"{self.path}/sse"
|
|
354
489
|
|
|
355
|
-
|
|
490
|
+
def _session_header(request) -> str:
|
|
491
|
+
headers = getattr(request, "headers", None) or {}
|
|
492
|
+
return headers.get("mcp-session-id", "") or ""
|
|
493
|
+
|
|
494
|
+
def _apply(response, outcome):
|
|
495
|
+
for name, value in outcome["headers"].items():
|
|
496
|
+
response.header(name, value)
|
|
497
|
+
body = outcome["body"]
|
|
498
|
+
if not body:
|
|
499
|
+
return response("", outcome["status"])
|
|
500
|
+
return response(json.loads(body), outcome["status"])
|
|
501
|
+
|
|
502
|
+
@router_module.post(self.path)
|
|
503
|
+
@router_module.noauth()
|
|
504
|
+
async def mcp_streamable(request, response):
|
|
505
|
+
outcome = server.dispatch_http(request.body, _session_header(request))
|
|
506
|
+
return _apply(response, outcome)
|
|
507
|
+
|
|
508
|
+
@router_module.post(f"{self.path}/message")
|
|
356
509
|
@router_module.noauth()
|
|
357
510
|
async def mcp_message(request, response):
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
if not result:
|
|
365
|
-
return response("", 204)
|
|
366
|
-
return response(json.loads(result))
|
|
367
|
-
|
|
368
|
-
@router_module.get(sse_path)
|
|
511
|
+
params = getattr(request, "params", None) or {}
|
|
512
|
+
session_id = params.get("sessionId") or _session_header(request)
|
|
513
|
+
outcome = server.dispatch_sse_message(request.body, session_id)
|
|
514
|
+
return _apply(response, outcome)
|
|
515
|
+
|
|
516
|
+
@router_module.get(f"{self.path}/sse")
|
|
369
517
|
@router_module.noauth()
|
|
370
518
|
async def mcp_sse(request, response):
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
r = Resp()
|
|
376
|
-
r.status_code = 200
|
|
377
|
-
r.content_type = "text/event-stream"
|
|
378
|
-
r.content = sse_data.encode()
|
|
379
|
-
r._headers = [
|
|
380
|
-
(b"content-type", b"text/event-stream"),
|
|
381
|
-
(b"cache-control", b"no-cache"),
|
|
382
|
-
(b"connection", b"keep-alive"),
|
|
383
|
-
]
|
|
384
|
-
return r
|
|
519
|
+
session_id = server.open_session()
|
|
520
|
+
base = getattr(request, "path", self.path).rsplit("/sse", 1)[0]
|
|
521
|
+
endpoint_url = f"{base}/message?sessionId={session_id}"
|
|
522
|
+
return response.stream(server.sse_stream(session_id, endpoint_url))
|
|
385
523
|
|
|
386
524
|
def write_claude_config(self, port: int = 7145):
|
|
387
525
|
"""Write/update .claude/settings.json with this MCP server config."""
|
|
@@ -401,7 +539,8 @@ class McpServer:
|
|
|
401
539
|
|
|
402
540
|
server_key = self.name.lower().replace(" ", "-")
|
|
403
541
|
config["mcpServers"][server_key] = {
|
|
404
|
-
"
|
|
542
|
+
"type": "http",
|
|
543
|
+
"url": f"http://localhost:{port}{self.path}",
|
|
405
544
|
}
|
|
406
545
|
|
|
407
546
|
config_file.write_text(json.dumps(config, indent=2) + "\n")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/public/images/tina4-logo-icon.webp
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/public/swagger/oauth2-redirect.html
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/queue_backends/rabbitmq_backend.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/session_handlers/mongodb_handler.py
RENAMED
|
File without changes
|
{tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/session_handlers/redis_handler.py
RENAMED
|
File without changes
|
{tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/session_handlers/valkey_handler.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/templates/docker/distroless/Dockerfile
RENAMED
|
File without changes
|
{tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/templates/docker/poetry/Dockerfile
RENAMED
|
File without changes
|
{tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/templates/docker/python/Dockerfile
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/translations/af/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/translations/af/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/translations/en/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/translations/en/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/translations/es/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/translations/es/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/translations/fr/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/translations/fr/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/translations/ja/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/translations/ja/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/translations/zh/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.49 → tina4_python-3.13.51}/tina4_python/translations/zh/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|