davinci-resolve-mcp 2.35.2 → 2.36.1
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.
- package/CHANGELOG.md +29 -0
- package/README.md +1 -1
- package/install.py +1 -1
- package/package.json +1 -1
- package/src/analysis_dashboard.py +128 -1
- package/src/granular/common.py +1 -1
- package/src/server.py +19 -2
- package/src/utils/mcp_transport.py +128 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,35 @@
|
|
|
2
2
|
|
|
3
3
|
Release history for the DaVinci Resolve MCP Server. The latest release is summarized in the root README; older entries live here to keep the README focused.
|
|
4
4
|
|
|
5
|
+
## What's New in v2.36.1
|
|
6
|
+
|
|
7
|
+
Bug fix — restore the `fusion_comp` MCP tool.
|
|
8
|
+
|
|
9
|
+
- **Fixed** a regression from `32be0ec` (v2.33.0) that left `fusion_comp`
|
|
10
|
+
unregistered: a new `_parse_pos` helper was inserted between the `@mcp.tool()`
|
|
11
|
+
decorator and `def fusion_comp`, so the decorator landed on the private helper
|
|
12
|
+
instead. As a result `_parse_pos` was exposed as a tool while **all**
|
|
13
|
+
`fusion_comp` node-graph operations (`add_tool`, `connect`, `add_keyframe`,
|
|
14
|
+
`get_keyframes`, `copy_tool`, `set_position`, …) were missing from the tool
|
|
15
|
+
list. The decorator is restored to `fusion_comp` and `_parse_pos` is once
|
|
16
|
+
again a plain internal helper. Live-validated on DaVinci Resolve Studio 21.
|
|
17
|
+
|
|
18
|
+
## What's New in v2.36.0
|
|
19
|
+
|
|
20
|
+
Optional networked transport — run the MCP server over the network, safely, with
|
|
21
|
+
control-panel management. Local stdio remains the default and is unchanged.
|
|
22
|
+
|
|
23
|
+
- **Added** `--transport stdio|sse|streamable-http` (default `stdio`). The networked
|
|
24
|
+
modes bind to **loopback (127.0.0.1) by default** and **require a bearer token** on
|
|
25
|
+
every request (from `$DAVINCI_MCP_TOKEN`, or generated + logged at startup). A
|
|
26
|
+
non-loopback bind logs a loud security warning. stdio is untouched; you can run a
|
|
27
|
+
local stdio instance and a networked instance against the same Resolve at once.
|
|
28
|
+
- **Added** control-panel management (Setup → MCP): a Transport card showing the live
|
|
29
|
+
mode / URL / token / loopback status, with Start/Stop buttons (loopback-only). Backed
|
|
30
|
+
by `/api/mcp/transport/{start,stop}` and a transport field on `/api/mcp/status`.
|
|
31
|
+
- The page-switch lock (v2.34.1) already serializes concurrent page switches across the
|
|
32
|
+
local and networked instances.
|
|
33
|
+
|
|
5
34
|
## What's New in v2.35.2
|
|
6
35
|
|
|
7
36
|
Verification observability and a validator consolidation.
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# DaVinci Resolve MCP Server
|
|
2
2
|
|
|
3
|
-
[](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
|
|
4
4
|
[](https://www.npmjs.com/package/davinci-resolve-mcp)
|
|
5
5
|
[](docs/reference/api-coverage.md)
|
|
6
6
|
[-blue.svg)](#server-modes)
|
package/install.py
CHANGED
|
@@ -35,7 +35,7 @@ from src.utils.update_check import (
|
|
|
35
35
|
|
|
36
36
|
# ─── Version ──────────────────────────────────────────────────────────────────
|
|
37
37
|
|
|
38
|
-
VERSION = "2.
|
|
38
|
+
VERSION = "2.36.1"
|
|
39
39
|
# Only hard floor: mcp[cli] requires Python 3.10+. There is no upper bound —
|
|
40
40
|
# Resolve's scripting bridge loads into newer interpreters on recent builds
|
|
41
41
|
# (Python 3.14 verified against Resolve Studio 20.3.2). Older Resolve builds
|
package/package.json
CHANGED
|
@@ -5495,7 +5495,36 @@ HTML = r"""<!doctype html>
|
|
|
5495
5495
|
<span>${apiOk && libOk ? 'Auto-detected from standard install paths' : 'Install via install.py if paths cannot be detected'}</span>
|
|
5496
5496
|
</div>
|
|
5497
5497
|
</div>`;
|
|
5498
|
-
|
|
5498
|
+
const tr = data.transport || { networked: false, mode: 'stdio (local)' };
|
|
5499
|
+
const trPill = tr.networked
|
|
5500
|
+
? (tr.loopback ? statusPill('pill-ok', 'Networked · loopback')
|
|
5501
|
+
: statusPill('pill-warn', 'Networked · EXPOSED'))
|
|
5502
|
+
: statusPill('pill-ok', 'Local (stdio)');
|
|
5503
|
+
const transportCard = `
|
|
5504
|
+
<div class="diag-card">
|
|
5505
|
+
<div class="diag-card-header">
|
|
5506
|
+
<div class="diag-card-title">${DIAG_ICONS.connection}Transport</div>
|
|
5507
|
+
${trPill}
|
|
5508
|
+
</div>
|
|
5509
|
+
<div class="diag-card-rows">
|
|
5510
|
+
${diagRow('Mode', tr.mode || 'stdio (local)')}
|
|
5511
|
+
${tr.networked ? diagRow('URL', tr.url || '—') : ''}
|
|
5512
|
+
${tr.networked ? diagRow('Auth', tr.has_token ? 'Bearer token required' : 'NONE') : ''}
|
|
5513
|
+
${tr.networked && tr.token ? diagRow('Token', tr.token) : ''}
|
|
5514
|
+
</div>
|
|
5515
|
+
<div class="diag-card-footer">
|
|
5516
|
+
<span>${tr.networked
|
|
5517
|
+
? (tr.loopback ? 'Reachable on this machine only; every request needs the bearer token.'
|
|
5518
|
+
: 'WARNING: bound to a non-loopback host — exposed on the network.')
|
|
5519
|
+
: 'Default. Networked access is opt-in (launch with --transport sse|streamable-http).'}</span>
|
|
5520
|
+
<button class="secondary transport-toggle-btn" data-transport-action="${tr.networked ? 'stop' : 'start'}">
|
|
5521
|
+
${tr.networked ? 'Stop networked' : 'Start networked'}</button>
|
|
5522
|
+
</div>
|
|
5523
|
+
</div>`;
|
|
5524
|
+
setHtml('diagnosticsMcpServer', serverCard + resolveCard + transportCard);
|
|
5525
|
+
serverEl.querySelectorAll('.transport-toggle-btn').forEach(btn => {
|
|
5526
|
+
btn.addEventListener('click', () => toggleTransport(btn.dataset.transportAction, btn).catch(alertError));
|
|
5527
|
+
});
|
|
5499
5528
|
|
|
5500
5529
|
const clients = data.clients || [];
|
|
5501
5530
|
if (!clients.length) {
|
|
@@ -5592,6 +5621,28 @@ HTML = r"""<!doctype html>
|
|
|
5592
5621
|
}
|
|
5593
5622
|
}
|
|
5594
5623
|
|
|
5624
|
+
async function toggleTransport(action, btn) {
|
|
5625
|
+
if (action === 'start') {
|
|
5626
|
+
const proceed = await brandedConfirm({
|
|
5627
|
+
kicker: 'MCP Transport',
|
|
5628
|
+
title: 'Start networked transport?',
|
|
5629
|
+
body: 'Starts a second MCP server instance over streamable-http, bound to loopback (127.0.0.1) and protected by a bearer token. Local stdio is unaffected. The connection URL + token will appear here.',
|
|
5630
|
+
confirmLabel: 'Start',
|
|
5631
|
+
cancelLabel: 'Cancel',
|
|
5632
|
+
});
|
|
5633
|
+
if (!proceed) return;
|
|
5634
|
+
}
|
|
5635
|
+
const original = btn?.textContent;
|
|
5636
|
+
if (btn) { btn.disabled = true; btn.textContent = action === 'start' ? 'Starting…' : 'Stopping…'; }
|
|
5637
|
+
try {
|
|
5638
|
+
const ep = action === 'start' ? '/api/mcp/transport/start' : '/api/mcp/transport/stop';
|
|
5639
|
+
await api(ep, { method: 'POST', body: '{}' });
|
|
5640
|
+
} finally {
|
|
5641
|
+
if (btn) { btn.disabled = false; btn.textContent = original; }
|
|
5642
|
+
await refreshMcpStatus();
|
|
5643
|
+
}
|
|
5644
|
+
}
|
|
5645
|
+
|
|
5595
5646
|
function renderControlPanels() {
|
|
5596
5647
|
renderOverview();
|
|
5597
5648
|
renderDiagnostics();
|
|
@@ -13483,9 +13534,73 @@ def _mcp_status_payload() -> Dict[str, Any]:
|
|
|
13483
13534
|
"resolve_lib_detected": bool(paths.get("lib_path")),
|
|
13484
13535
|
},
|
|
13485
13536
|
"clients": clients_out,
|
|
13537
|
+
"transport": _transport_status(),
|
|
13486
13538
|
}
|
|
13487
13539
|
|
|
13488
13540
|
|
|
13541
|
+
def _transport_status() -> Dict[str, Any]:
|
|
13542
|
+
"""Live networked-transport status (or local-only) for the MCP diagnostics card."""
|
|
13543
|
+
try:
|
|
13544
|
+
from src.utils.mcp_transport import read_transport_state
|
|
13545
|
+
except Exception:
|
|
13546
|
+
return {"networked": False, "mode": "stdio (local)"}
|
|
13547
|
+
state = read_transport_state()
|
|
13548
|
+
if not state:
|
|
13549
|
+
return {"networked": False, "mode": "stdio (local)"}
|
|
13550
|
+
return {
|
|
13551
|
+
"networked": True,
|
|
13552
|
+
"mode": state.get("transport"),
|
|
13553
|
+
"url": state.get("url"),
|
|
13554
|
+
"loopback": state.get("loopback", True),
|
|
13555
|
+
"has_token": bool(state.get("token")),
|
|
13556
|
+
"token": state.get("token"),
|
|
13557
|
+
"pid": state.get("pid"),
|
|
13558
|
+
}
|
|
13559
|
+
|
|
13560
|
+
|
|
13561
|
+
def _transport_start() -> Dict[str, Any]:
|
|
13562
|
+
"""Spawn a networked MCP instance (streamable-http, loopback + token)."""
|
|
13563
|
+
import subprocess as _sp
|
|
13564
|
+
from src.utils.mcp_transport import read_transport_state
|
|
13565
|
+
if read_transport_state():
|
|
13566
|
+
return {"success": False, "error": "A networked transport instance is already running."}
|
|
13567
|
+
paths = _resolve_mcp_paths()
|
|
13568
|
+
py, script = paths.get("python_path"), paths.get("server_path")
|
|
13569
|
+
if not py or not script:
|
|
13570
|
+
return {"success": False, "error": "Could not resolve the Python interpreter or server script path."}
|
|
13571
|
+
try:
|
|
13572
|
+
_sp.Popen(
|
|
13573
|
+
[py, script, "--transport", "streamable-http"],
|
|
13574
|
+
stdin=_sp.DEVNULL, stdout=_sp.DEVNULL, stderr=_sp.DEVNULL,
|
|
13575
|
+
start_new_session=True,
|
|
13576
|
+
)
|
|
13577
|
+
except OSError as exc:
|
|
13578
|
+
return {"success": False, "error": f"Failed to launch: {exc}"}
|
|
13579
|
+
import time as _t
|
|
13580
|
+
for _ in range(15):
|
|
13581
|
+
_t.sleep(0.2)
|
|
13582
|
+
if read_transport_state():
|
|
13583
|
+
return {"success": True, "transport": _transport_status()}
|
|
13584
|
+
return {"success": True, "note": "Launch initiated; status will appear shortly."}
|
|
13585
|
+
|
|
13586
|
+
|
|
13587
|
+
def _transport_stop() -> Dict[str, Any]:
|
|
13588
|
+
"""Stop the running networked MCP instance via its state-file PID."""
|
|
13589
|
+
import signal as _sig
|
|
13590
|
+
from src.utils.mcp_transport import read_transport_state, clear_transport_state
|
|
13591
|
+
state = read_transport_state()
|
|
13592
|
+
if not state:
|
|
13593
|
+
return {"success": True, "note": "No networked transport running."}
|
|
13594
|
+
pid = state.get("pid")
|
|
13595
|
+
if isinstance(pid, int):
|
|
13596
|
+
try:
|
|
13597
|
+
os.kill(pid, _sig.SIGTERM)
|
|
13598
|
+
except (OSError, ProcessLookupError):
|
|
13599
|
+
pass
|
|
13600
|
+
clear_transport_state()
|
|
13601
|
+
return {"success": True}
|
|
13602
|
+
|
|
13603
|
+
|
|
13489
13604
|
def _mcp_install_payload(client_id: str) -> Dict[str, Any]:
|
|
13490
13605
|
"""Write the MCP entry for one client by delegating to install.write_client_config."""
|
|
13491
13606
|
installer, error = _load_installer_module()
|
|
@@ -14304,6 +14419,18 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
14304
14419
|
return
|
|
14305
14420
|
self._json(_mcp_uninstall_payload(client_id))
|
|
14306
14421
|
return
|
|
14422
|
+
if path == "/api/mcp/transport/start":
|
|
14423
|
+
if not _request_is_loopback(self):
|
|
14424
|
+
self._json({"success": False, "error": "Transport management is loopback-only."}, HTTPStatus.FORBIDDEN)
|
|
14425
|
+
return
|
|
14426
|
+
self._json(_transport_start())
|
|
14427
|
+
return
|
|
14428
|
+
if path == "/api/mcp/transport/stop":
|
|
14429
|
+
if not _request_is_loopback(self):
|
|
14430
|
+
self._json({"success": False, "error": "Transport management is loopback-only."}, HTTPStatus.FORBIDDEN)
|
|
14431
|
+
return
|
|
14432
|
+
self._json(_transport_stop())
|
|
14433
|
+
return
|
|
14307
14434
|
if path == "/api/jobs":
|
|
14308
14435
|
paths = body.get("paths") or []
|
|
14309
14436
|
if isinstance(paths, str):
|
package/src/granular/common.py
CHANGED
|
@@ -80,7 +80,7 @@ if not logging.getLogger().handlers:
|
|
|
80
80
|
handlers=[logging.StreamHandler()],
|
|
81
81
|
)
|
|
82
82
|
|
|
83
|
-
VERSION = "2.
|
|
83
|
+
VERSION = "2.36.1"
|
|
84
84
|
logger = logging.getLogger("davinci-resolve-mcp")
|
|
85
85
|
logger.info(f"Starting DaVinci Resolve MCP Server v{VERSION}")
|
|
86
86
|
logger.info(f"Detected platform: {get_platform()}")
|
package/src/server.py
CHANGED
|
@@ -11,7 +11,7 @@ Usage:
|
|
|
11
11
|
python src/server.py --full # Start the 341-tool granular server instead
|
|
12
12
|
"""
|
|
13
13
|
|
|
14
|
-
VERSION = "2.
|
|
14
|
+
VERSION = "2.36.1"
|
|
15
15
|
|
|
16
16
|
import base64
|
|
17
17
|
import os
|
|
@@ -18768,7 +18768,6 @@ def _fusion_boundary_report(comp, p: Dict[str, Any]):
|
|
|
18768
18768
|
}
|
|
18769
18769
|
|
|
18770
18770
|
|
|
18771
|
-
@mcp.tool()
|
|
18772
18771
|
def _parse_pos(raw):
|
|
18773
18772
|
"""Normalize a FlowView position return into (x, y) floats, or None.
|
|
18774
18773
|
|
|
@@ -18834,6 +18833,7 @@ def _fusion_tool_names(comp):
|
|
|
18834
18833
|
return [name for name, _ in _iter_fusion_tools(comp)]
|
|
18835
18834
|
|
|
18836
18835
|
|
|
18836
|
+
@mcp.tool()
|
|
18837
18837
|
def fusion_comp(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
18838
18838
|
"""Fusion composition node graph operations.
|
|
18839
18839
|
|
|
@@ -21041,5 +21041,22 @@ if __name__ == "__main__":
|
|
|
21041
21041
|
run_fastmcp_stdio(granular_mcp)
|
|
21042
21042
|
sys.exit(0)
|
|
21043
21043
|
|
|
21044
|
+
# --transport stdio (default) | sse | streamable-http. Networked modes bind
|
|
21045
|
+
# loopback by default and require a bearer token (see src/utils/mcp_transport).
|
|
21046
|
+
transport = "stdio"
|
|
21047
|
+
if "--transport" in sys.argv:
|
|
21048
|
+
_i = sys.argv.index("--transport")
|
|
21049
|
+
if _i + 1 < len(sys.argv):
|
|
21050
|
+
transport = sys.argv[_i + 1]
|
|
21051
|
+
del sys.argv[_i:_i + 2]
|
|
21052
|
+
if transport in ("sse", "streamable-http"):
|
|
21053
|
+
from src.utils.mcp_transport import run_networked
|
|
21054
|
+
logger.info(f"Starting DaVinci Resolve MCP Server ({transport} transport)")
|
|
21055
|
+
run_networked(mcp, transport)
|
|
21056
|
+
sys.exit(0)
|
|
21057
|
+
if transport != "stdio":
|
|
21058
|
+
logger.error(f"Unknown --transport {transport!r}; use stdio|sse|streamable-http")
|
|
21059
|
+
sys.exit(2)
|
|
21060
|
+
|
|
21044
21061
|
logger.info(f"Starting DaVinci Resolve MCP Server (32 compound tools)")
|
|
21045
21062
|
run_fastmcp_stdio(mcp)
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"""Networked transport for the MCP server (opt-in via --transport).
|
|
2
|
+
|
|
3
|
+
stdio remains the default. The `sse` and `streamable-http` modes bind to
|
|
4
|
+
loopback (127.0.0.1) by default and REQUIRE a bearer token on every request, so
|
|
5
|
+
turning networking on never silently exposes Resolve. The token comes from
|
|
6
|
+
``$DAVINCI_MCP_TOKEN`` or is generated and logged at startup. A small state file
|
|
7
|
+
lets the control panel show the live connection URL + token.
|
|
8
|
+
|
|
9
|
+
Security posture:
|
|
10
|
+
- Default host is loopback; a non-loopback bind logs a loud warning.
|
|
11
|
+
- Every HTTP request must carry ``Authorization: Bearer <token>`` (constant-time
|
|
12
|
+
compared); otherwise 401.
|
|
13
|
+
- stdio (the default transport) is unaffected by anything here.
|
|
14
|
+
"""
|
|
15
|
+
import json
|
|
16
|
+
import logging
|
|
17
|
+
import os
|
|
18
|
+
import secrets
|
|
19
|
+
import tempfile
|
|
20
|
+
import time
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger("davinci-resolve-mcp")
|
|
23
|
+
|
|
24
|
+
TRANSPORT_STATE_PATH = os.path.join(
|
|
25
|
+
tempfile.gettempdir(), "davinci_resolve_mcp_transport.json"
|
|
26
|
+
)
|
|
27
|
+
LOOPBACK_HOSTS = {"127.0.0.1", "localhost", "::1"}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def resolve_token():
|
|
31
|
+
"""Return (token, was_generated). Honors $DAVINCI_MCP_TOKEN."""
|
|
32
|
+
tok = os.environ.get("DAVINCI_MCP_TOKEN")
|
|
33
|
+
if tok:
|
|
34
|
+
return tok, False
|
|
35
|
+
return secrets.token_urlsafe(24), True
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _auth_middleware_cls(token):
|
|
39
|
+
from starlette.middleware.base import BaseHTTPMiddleware
|
|
40
|
+
from starlette.responses import JSONResponse
|
|
41
|
+
|
|
42
|
+
expected = f"Bearer {token}"
|
|
43
|
+
|
|
44
|
+
class BearerAuth(BaseHTTPMiddleware):
|
|
45
|
+
async def dispatch(self, request, call_next):
|
|
46
|
+
provided = request.headers.get("authorization", "")
|
|
47
|
+
if not secrets.compare_digest(provided, expected):
|
|
48
|
+
return JSONResponse(
|
|
49
|
+
{"error": "unauthorized: Authorization: Bearer <token> required"},
|
|
50
|
+
status_code=401,
|
|
51
|
+
)
|
|
52
|
+
return await call_next(request)
|
|
53
|
+
|
|
54
|
+
return BearerAuth
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def write_transport_state(transport, host, port, token):
|
|
58
|
+
try:
|
|
59
|
+
with open(TRANSPORT_STATE_PATH, "w", encoding="utf-8") as fh:
|
|
60
|
+
json.dump({
|
|
61
|
+
"transport": transport,
|
|
62
|
+
"host": host,
|
|
63
|
+
"port": port,
|
|
64
|
+
"url": f"http://{host}:{port}",
|
|
65
|
+
"token": token,
|
|
66
|
+
"loopback": host in LOOPBACK_HOSTS,
|
|
67
|
+
"pid": os.getpid(),
|
|
68
|
+
"started_at": time.time(),
|
|
69
|
+
}, fh)
|
|
70
|
+
except OSError as exc:
|
|
71
|
+
logger.warning("could not write transport state: %s", exc)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def clear_transport_state():
|
|
75
|
+
try:
|
|
76
|
+
os.remove(TRANSPORT_STATE_PATH)
|
|
77
|
+
except OSError:
|
|
78
|
+
pass
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def read_transport_state():
|
|
82
|
+
"""Return the live transport state dict, or None if no networked instance.
|
|
83
|
+
|
|
84
|
+
Treats a state file whose pid is no longer alive as stale (returns None).
|
|
85
|
+
"""
|
|
86
|
+
try:
|
|
87
|
+
with open(TRANSPORT_STATE_PATH, encoding="utf-8") as fh:
|
|
88
|
+
state = json.load(fh)
|
|
89
|
+
except (OSError, ValueError):
|
|
90
|
+
return None
|
|
91
|
+
pid = state.get("pid")
|
|
92
|
+
if isinstance(pid, int):
|
|
93
|
+
try:
|
|
94
|
+
os.kill(pid, 0)
|
|
95
|
+
except (OSError, ProcessLookupError):
|
|
96
|
+
return None
|
|
97
|
+
return state
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def run_networked(mcp, transport):
|
|
101
|
+
"""Serve `mcp` over an authenticated HTTP transport ('sse'|'streamable-http')."""
|
|
102
|
+
import uvicorn
|
|
103
|
+
|
|
104
|
+
host = os.environ.get("DAVINCI_MCP_HOST") or mcp.settings.host or "127.0.0.1"
|
|
105
|
+
port = int(os.environ.get("DAVINCI_MCP_PORT") or mcp.settings.port or 8000)
|
|
106
|
+
mcp.settings.host = host
|
|
107
|
+
mcp.settings.port = port
|
|
108
|
+
token, generated = resolve_token()
|
|
109
|
+
|
|
110
|
+
app = mcp.sse_app() if transport == "sse" else mcp.streamable_http_app()
|
|
111
|
+
app.add_middleware(_auth_middleware_cls(token))
|
|
112
|
+
|
|
113
|
+
if host not in LOOPBACK_HOSTS:
|
|
114
|
+
logger.warning(
|
|
115
|
+
"SECURITY: MCP %s transport bound to NON-loopback host %r — Resolve "
|
|
116
|
+
"control is exposed on the network. Ensure this is intended.",
|
|
117
|
+
transport, host,
|
|
118
|
+
)
|
|
119
|
+
logger.info("MCP %s transport: http://%s:%s (bearer token required)",
|
|
120
|
+
transport, host, port)
|
|
121
|
+
if generated:
|
|
122
|
+
logger.info("Generated bearer token (set $DAVINCI_MCP_TOKEN to pin it): %s", token)
|
|
123
|
+
|
|
124
|
+
write_transport_state(transport, host, port, token)
|
|
125
|
+
try:
|
|
126
|
+
uvicorn.run(app, host=host, port=port, log_level="warning")
|
|
127
|
+
finally:
|
|
128
|
+
clear_transport_state()
|