
    kKj                       d Z ddlmZ ddlZddlZddlZddlZddlZddlZddl	Z	ddl
Z
ddlZddlZddlZddlZddlZddlZddlmZ ddlmZ ddlmZ ddlZddlZddlZddlmZ ddlmZ dd	lmZm Z m!Z! dd
l"m#Z# ddl$m%Z%  e%e&      Z'd'dZ( G d d      Z)d(dZ*dZ+dZ,dZ-dZ.dZ/d)dZ0d*dZ1d+dZ2d,dZ3d-dZ4d.dZ5	 	 	 	 	 	 d/dZ6	 	 	 	 	 	 d/dZ7	 	 	 	 	 	 	 	 	 	 	 	 d0dZ8ddd 	 	 	 	 	 	 	 	 	 d1d!Z9d2d3d"Z:d#d$dddd%	 	 	 	 	 	 	 	 	 	 	 	 	 d4d&Z;y)5u=  Dev server for previewing FastMCPApp UIs locally.

Starts the user's MCP server on a configurable port, then starts a lightweight
Starlette dev server that:

  - Serves a Prefab-based tool picker at GET /
  - Proxies /mcp to the user's server (avoids browser CORS restrictions)
  - Serves the AppBridge host page at GET /launch

The host page uses @modelcontextprotocol/ext-apps to connect to the MCP server
and render the selected UI tool inside an iframe.

Startup sequence
----------------
1. Download ext-apps app-bridge.js from npm and patch its bare
   ``@modelcontextprotocol/sdk/…`` imports to use concrete esm.sh URLs.
2. Detect the exact Zod v4 module URL that esm.sh serves for that SDK version
   and build an import-map entry that redirects the broken ``v4.mjs`` (which
   only re-exports ``{z, default}``) to ``v4/classic/index.mjs`` (which
   correctly exports every named Zod v4 function).  Import maps apply to the
   full module graph in the document, including cross-origin esm.sh modules.
3. Serve both the patched JS and the import-map JSON from the dev server.
    )annotationsN)Path)Any)	urlencode)	Starlette)Request)HTMLResponseResponseStreamingResponse)Route)
get_loggerc                    t        j                  |       j                  dd      j                  dd      j                  dd      j                  dd      j                  d	d
      S )z;Serialize JSON for embedding inside an HTML script element.&z\u0026<z\u003c>z\u003eu    z\u2028u    z\u2029)jsondumpsreplacevalues    e/Users/ahmed/devFolder/Ultron/claude-voice/.venv/lib/python3.12/site-packages/fastmcp/cli/apps_dev.py_json_for_scriptr   :   sQ     	

5	i	 	i	 	i	 	9	%	9	%    c                  B    e Zd ZdZd	dZd
dZd
dZdddZd
dZd	dZ	y)_MessageLogzDIn-memory buffer of MCP JSON-RPC messages flowing through the proxy.c                <    g | _         d| _        i | _        i | _        y )Nr   )_entries_counter_request_methods_request_timesselfs    r   __init__z_MessageLog.__init__N   s     .06868r   c                2   |j                  dd      }|j                  d      }t        j                         }||| j                  |<   || j                  |<   | xj                  dz  c_        | j
                  j                  | j                  |d||d       y )Nmethodunknownid   requestr'   	timestamp	directionr%   body)gettimer   r    r   r   append)r"   r-   r%   
jsonrpc_idr+   s        r   log_requestz_MessageLog.log_requestT   s    (I.XXd^
IIK	!06D!!*-.7D
+mm&& 	
r   c           	     R   d|v rgd|vrc| xj                   dz  c_         | j                  j                  | j                   t        j                         d|j	                  dd      |d       y |j	                  d      }|| j
                  j                  |d       nd }|| j                  j                  |d       nd }t        j                         }|rt        ||z
  dz  d      nd }| xj                   dz  c_         | j                  j                  | j                   |d|||d	       y )
Nr%   r'   r(   notificationr&   r*   i  response)r'   r+   r,   r%   r-   duration_ms)	r   r   r0   r/   r.   r   popr    round)r"   r-   r1   r%   request_timer+   r6   s          r   log_responsez_MessageLog.log_responsef   s#   tD 0MMQMMM  --!%!/"hhx;  XXd^
 % !!%%j$7 	 % ##J5 	
 IIK	;GE9|+t3Q7T 	 	mm&' *		
r   c                R    | j                   D cg c]  }|d   |kD  s| c}S c c}w )Nr'   )r   )r"   since_ides      r   	get_sincez_MessageLog.get_since   s)    ==?=aAdGh,>=???s   $$c                    |j                  dd      }| xj                  dz  c_        | j                  j                  | j                  t	        j                         d||d       y )Nr%   r&   r(   bridger*   )r.   r   r   r0   r/   )r"   r-   r%   s      r   
log_bridgez_MessageLog.log_bridge   sP    (I.mm!YY[% 	
r   c                    | j                   j                          | j                  j                          | j                  j                          y N)r   clearr   r    r!   s    r   rD   z_MessageLog.clear   s6    ##%!!#r   NreturnNone)r-   dict[str, Any]rF   rG   )r   )r<   intrF   list[dict[str, Any]])
__name__
__module____qualname____doc__r#   r2   r:   r>   rA   rD    r   r   r   r   K   s%    N9
$(
T@
$r   r   c           	         |sy	 d|v r|j                  dd      j                         D ]g  }|j                  d      st        j                  t
        j                        5  | j                  t        j                  |dd              ddd       i yt        j                  |      }t        |t              r|D ]  }| j                  |        y| j                  |       y# 1 sw Y   xY w# t
        j                  t        f$ r Y yw xY w)zBParse accumulated proxy response bytes and log as message entries.Ntext/event-streamutf-8r   errorsdata:    )decode
splitlines
startswith
contextlibsuppressr   JSONDecodeErrorr:   loads
isinstancelist	TypeError)lograwcontent_typeliner-   items         r   _log_response_bytesrf      s    ,.

79
=HHJ??8,#,,T-A-AB((DH)=> CB K
 ::c?D$% D$$T* !   & CB   ), s:   9C1 #C1 "(C%

C1 =C1 C1 %C.	*C1 1DDz1.0.1z1.25.2a~
  <!doctype html>
<html>
<head>
  <meta charset="UTF-8">
  <title>{title}</title>
{import_map_tag}
  <style>
    html, body {{ margin: 0; padding: 0; width: 100%; height: 100vh; overflow: hidden; }}
    #app-frame {{ width: 100%; height: 100%; border: none; display: none; }}
    #status {{
      display: flex; align-items: center; justify-content: center; height: 100vh;
      font-family: system-ui, sans-serif; color: #666; font-size: 1rem;
    }}
  </style>
</head>
<body>
  <div id="status" style="display:{status_display}">{status_text}</div>
  <iframe id="app-frame" style="display:{frame_display}"></iframe>
  <script type="module">
    import {{ AppBridge, PostMessageTransport }}
      from "/js/app-bridge.js";
    import {{ Client }}
      from "https://esm.sh/@modelcontextprotocol/sdk@{mcp_sdk_version}/client/index.js";
    import {{ StreamableHTTPClientTransport }}
      from "https://esm.sh/@modelcontextprotocol/sdk@{mcp_sdk_version}/client/streamableHttp.js";

    const status = document.getElementById("status");
    const iframe  = document.getElementById("app-frame");

    async function main() {{
      const client = new Client({{ name: "fastmcp-dev", version: "1.0.0" }});
      await client.connect(
        new StreamableHTTPClientTransport(new URL("/mcp", window.location.origin))
      );
      const serverCaps = client.getServerCapabilities();

      // Set iframe src after adding load listener to avoid race condition
      const loaded = new Promise(r => iframe.addEventListener("load", r, {{ once: true }}));
      iframe.src = {iframe_src_json};
      await loaded;

      const transport = new PostMessageTransport(
        iframe.contentWindow,
        iframe.contentWindow,
      );
      const bridge = new AppBridge(
        client,
        {{ name: "fastmcp-dev", version: "1.0.0" }},
        {{
          openLinks: {{}},
          serverTools: serverCaps?.tools,
          serverResources: serverCaps?.resources,
        }},
        {{
          hostContext: {{
            theme: window.matchMedia("(prefers-color-scheme: dark)").matches
              ? "dark" : "light",
            platform: "web",
            containerDimensions: {{ maxHeight: 8000 }},
            displayMode: "inline",
            availableDisplayModes: ["inline", "fullscreen"],
          }},
        }},
      );

      bridge.onmessage = async () => ({{}});
      {on_open_link}
      {on_initialized}

      await bridge.connect(transport);
    }}

    main().catch(err => {{
      console.error(err);
      if (status) {{
        status.style.display = "flex";
        status.textContent = "Error: " + err.message;
      }}
    }});
  </script>
</body>
</html>
u0  <!doctype html>
<html>
<head>
  <meta charset="UTF-8">
  <title>FastMCP Dev — {tool_name}</title>
{import_map_tag}
  <style>
    html, body {{ margin: 0; padding: 0; width: 100%; height: 100vh; overflow: hidden; }}
    #app-frame {{ width: 100%; height: 100%; border: none; display: none; }}
    #status {{
      display: flex; align-items: center; justify-content: center; height: 100vh;
      font-family: system-ui, sans-serif; color: #666; font-size: 1rem;
    }}
  </style>
</head>
<body>
  <div id="status">Launching {tool_name}…</div>
  <iframe id="app-frame"></iframe>
  <script type="module">
    import {{ AppBridge, PostMessageTransport, getToolUiResourceUri }}
      from "/js/app-bridge.js";
    import {{ Client }}
      from "https://esm.sh/@modelcontextprotocol/sdk@{mcp_sdk_version}/client/index.js";
    import {{ StreamableHTTPClientTransport }}
      from "https://esm.sh/@modelcontextprotocol/sdk@{mcp_sdk_version}/client/streamableHttp.js";

    const toolName = {tool_name_json};
    const toolArgs = {tool_args_json};
    const status = document.getElementById("status");
    const iframe  = document.getElementById("app-frame");

    async function main() {{
      // Connect to the proxied MCP server (same-origin, no CORS needed)
      const client = new Client({{ name: "fastmcp-dev", version: "1.0.0" }});
      await client.connect(
        new StreamableHTTPClientTransport(new URL("/mcp", window.location.origin))
      );

      // Find the tool and its UI resource URI
      const {{ tools }} = await client.listTools();
      const tool = tools.find(t => t.name === toolName);
      if (!tool) throw new Error("Tool not found: " + toolName);

      const uiUri = getToolUiResourceUri(tool);
      if (!uiUri) throw new Error("Tool has no UI resource: " + toolName);

      // The Prefab renderer calls earlyBridge.connect() at module-load time
      // (synchronously, before React mounts) so it sends its ui/initialize
      // request very early — potentially before the iframe's load event fires.
      // Fix: create the AppBridge and call bridge.connect() BEFORE loading the
      // iframe so our window.addEventListener is registered first.  We pass
      // null as the PostMessageTransport source so early messages from the
      // not-yet-known renderer window are not filtered out.  After the iframe
      // loads we update transport.eventTarget / .eventSource to the real
      // renderer window; the load-event microtask always runs before the
      // message macrotask, so the response reaches the correct window.
      const serverCaps = client.getServerCapabilities();
      const transport = new PostMessageTransport(iframe.contentWindow, null);
      const bridge = new AppBridge(
        client,
        {{ name: "fastmcp-dev", version: "1.0.0" }},
        {{
          openLinks: {{}},
          serverTools: serverCaps?.tools,
          serverResources: serverCaps?.resources,
        }},
        {{
          hostContext: {{
            theme: window.matchMedia("(prefers-color-scheme: dark)").matches
              ? "dark" : "light",
            platform: "web",
            containerDimensions: {{ maxHeight: 8000 }},
            displayMode: "inline",
            availableDisplayModes: ["inline", "fullscreen"],
          }},
        }},
      );

      bridge.onopenlink = async ({{ url }}) => {{
        window.open(url, "_blank", "noopener,noreferrer");
        return {{}};
      }};
      bridge.onmessage = async () => ({{}});

      // When the View initializes: send input args, call the tool, send result
      bridge.oninitialized = async () => {{
        await bridge.sendToolInput({{ arguments: toolArgs }});
        const result = await client.callTool({{ name: toolName, arguments: toolArgs }});
        await bridge.sendToolResult(result);
        status.style.display = "none";
        iframe.style.display = "block";
        // Prevent horizontal scrollbar when vertical scrollbar appears
        try {{ iframe.contentDocument.documentElement.style.overflowX = "hidden"; }} catch(e) {{}}
      }};

      // Start listening before the iframe loads
      await bridge.connect(transport);

      // Now load the renderer HTML via the server-side proxy
      const frameUrl = "/ui-resource?uri=" + encodeURIComponent(uiUri);
      const loaded = new Promise(r => {{ iframe.addEventListener("load", r, {{ once: true }}); }});
      iframe.src = frameUrl;
      await loaded;

      // Update transport to the real renderer window.  This microtask runs
      // before the ui/initialize message macrotask, ensuring the response
      // is dispatched to the correct window.
      transport.eventTarget = iframe.contentWindow;
      transport.eventSource = iframe.contentWindow;
    }}

    main().catch(err => {{
      status.textContent = "Error: " + err.message;
      console.error(err);
    }});
  </script>
</body>
</html>
uL  <style>
  #mcp-log-panel {
    position: fixed; top: 0; left: 0; bottom: 0; width: 360px;
    z-index: 10000;
    font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
    font-size: 12px; background: #1e1e2e; color: #cdd6f4;
    border-right: 1px solid #45475a;
    display: flex; flex-direction: column;
  }
  #mcp-log-panel.hidden { display: none; }
  #app-frame {
    width: 100% !important; height: 100% !important;
    margin-left: 0 !important;
  }
  #mcp-log-resize {
    position: absolute; right: -3px; top: 0; bottom: 0; width: 6px;
    cursor: col-resize; z-index: 1;
  }
  #mcp-log-resize:hover, #mcp-log-resize.active { background: #585b70; }
  #mcp-log-header {
    display: flex; justify-content: space-between; align-items: center;
    padding: 10px 12px; background: #181825;
    border-bottom: 1px solid #45475a; flex-shrink: 0;
  }
  #mcp-log-brand {
    display: flex; align-items: center; gap: 8px;
  }
  #mcp-log-brand svg { flex-shrink: 0; }
  #mcp-log-brand-text {
    font-weight: 700; font-size: 13px; color: #cdd6f4;
    letter-spacing: -0.3px;
  }
  #mcp-log-count-badge {
    font-size: 11px; color: #6c7086; font-weight: 400;
  }
  #mcp-log-actions { display: flex; gap: 6px; }
  #mcp-log-actions button {
    background: #313244; color: #cdd6f4; border: 1px solid #45475a;
    padding: 2px 8px; border-radius: 3px; cursor: pointer;
    font-size: 11px; font-family: inherit;
  }
  #mcp-log-actions button:hover { background: #45475a; }
  #mcp-log-entries { flex: 1; overflow-y: auto; }
  .log-entry {
    padding: 6px 12px; border-bottom: 1px solid #232334; cursor: pointer;
  }
  .log-entry:hover { background: #313244; }
  .log-entry.error { background: rgba(243, 139, 168, 0.08); }
  .log-entry.error:hover { background: rgba(243, 139, 168, 0.14); }
  .log-entry.error .log-method { color: #f38ba8; }
  .log-primary {
    display: flex; justify-content: space-between;
    align-items: baseline; gap: 8px;
  }
  .log-left {
    display: flex; gap: 6px; align-items: baseline; min-width: 0;
  }
  .log-dir { flex-shrink: 0; }
  .log-dir.request { color: #89b4fa; }
  .log-dir.response { color: #a6e3a1; }
  .log-dir.error { color: #f38ba8; }
  .log-dir.bridge { color: #cba6f7; }
  .log-dir.notification { color: #fab387; }
  .log-method {
    color: #f9e2af; font-weight: 600;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  .log-meta { color: #6c7086; font-size: 11px; white-space: nowrap; flex-shrink: 0; }
  .log-subtitle {
    color: #a6adc8; font-size: 11px; padding-left: 22px;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    margin-top: 1px;
  }
  .log-detail {
    display: none; padding: 8px 12px 4px 22px; background: #11111b;
    white-space: pre-wrap; word-break: break-all;
    color: #bac2de; font-size: 11px; line-height: 1.4;
    margin-top: 4px; border-radius: 4px;
  }
  .log-entry.expanded .log-detail { display: block; }
  @keyframes log-flash {
    from { background: rgba(137, 180, 250, 0.22); }
    to { background: transparent; }
  }
  @keyframes log-flash-error {
    from { background: rgba(243, 139, 168, 0.25); }
    to { background: rgba(243, 139, 168, 0.08); }
  }
  .log-entry.new { animation: log-flash 2s ease-out; }
  .log-entry.error.new { animation: log-flash-error 2s ease-out; }
  .log-copy {
    opacity: 0; transition: opacity 0.15s;
    background: #313244; color: #a6adc8; border: 1px solid #45475a;
    padding: 1px 6px; border-radius: 3px; cursor: pointer;
    font-size: 10px; font-family: inherit; flex-shrink: 0;
  }
  .log-entry:hover .log-copy { opacity: 1; }
  .log-copy:hover { background: #45475a; color: #cdd6f4; }
  #mcp-log-open {
    position: fixed; bottom: 12px; left: 12px; z-index: 10000;
    background: #181825; color: #cdd6f4; border: 1px solid #45475a;
    padding: 6px 12px; border-radius: 6px; cursor: pointer;
    font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
    font-size: 11px; display: block;
  }
  #mcp-log-open:hover { background: #313244; }
  #mcp-log-filters {
    display: flex; gap: 8px; align-items: center;
    padding: 6px 12px; border-bottom: 1px solid #45475a; flex-shrink: 0;
  }
  .log-seg {
    display: inline-flex; border: 1px solid #45475a; border-radius: 6px;
    overflow: hidden;
  }
  .log-seg button {
    background: transparent; color: #6c7086; border: none;
    border-right: 1px solid #45475a; padding: 3px 10px; cursor: pointer;
    font-size: 10px; font-family: inherit; transition: all 0.15s;
  }
  .log-seg button:last-child { border-right: none; }
  .log-seg button:hover { background: rgba(205, 214, 244, 0.06); }
  .log-seg button.active[data-filter="tools"] { background: rgba(137, 180, 250, 0.15); color: #89b4fa; }
  .log-seg button.active[data-filter="notifications"] { background: rgba(250, 179, 135, 0.15); color: #fab387; }
  .log-seg button.active[data-filter="bridge"] { background: rgba(203, 166, 247, 0.15); color: #cba6f7; }
  .log-seg button.active[data-filter="errors"] { background: rgba(243, 139, 168, 0.15); color: #f38ba8; }
  #mcp-log-filters-label {
    font-size: 9px; color: #6c7086; text-transform: uppercase;
    letter-spacing: 0.5px; font-weight: 600;
  }
  #mcp-log-level-select {
    background: #313244; color: #cdd6f4; border: 1px solid #45475a;
    border-radius: 6px; padding: 3px 8px; cursor: pointer;
    font-size: 10px; font-family: inherit;
  }
  #mcp-log-level-select option { background: #1e1e2e; }
  .log-level {
    font-size: 9px; padding: 0 5px; border-radius: 3px;
    font-weight: 600; text-transform: uppercase; letter-spacing: 0.3px;
    flex-shrink: 0; line-height: 16px;
  }
  .log-level-debug { background: #313244; color: #6c7086; }
  .log-level-info { background: rgba(137, 180, 250, 0.15); color: #89b4fa; }
  .log-level-warning { background: rgba(249, 226, 175, 0.15); color: #f9e2af; }
  .log-level-error { background: rgba(243, 139, 168, 0.15); color: #f38ba8; }
  .log-level-notice { background: rgba(148, 226, 213, 0.15); color: #94e2d5; }
  .log-level-critical { background: rgba(243, 139, 168, 0.2); color: #f38ba8; }
  .log-level-alert { background: rgba(243, 139, 168, 0.25); color: #f38ba8; }
  .log-level-emergency { background: rgba(243, 139, 168, 0.3); color: #f38ba8; }
</style>
<div id="mcp-log-panel" class="hidden">
  <div id="mcp-log-resize"></div>
  <div id="mcp-log-header">
    <div id="mcp-log-brand">
      <svg width="20" height="20" viewBox="0 0 196 196" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M145.747 44.611L145.355 44.3877L144.96 44.611L86.0283 78.5276V171.267L86.4014 171.499L99.6674 179.667V86.3859L159 52.2379L145.747 44.611Z" fill="#cdd6f4"/><path d="M121.616 30.2714L121.224 30.0454L120.832 30.2714L61.8975 64.188V156.928L62.2732 157.156L75.5393 165.325V72.0463L134.869 37.8983L121.616 30.2714Z" fill="#cdd6f4"/><path d="M97.4894 16.3818L97.0973 16.1558L96.7025 16.3818L37.7705 50.3038V142.066L51.4096 150.463V58.1567L110.742 24.0086L97.4894 16.3818Z" fill="#cdd6f4"/><path d="M131.23 113.671L124.979 117.266L124.584 117.494V117.5L116.796 121.987L110.547 125.581L110.152 125.807V141.51L144.564 121.709V121.698L158.999 113.394V97.6851L139.277 109.034L131.23 113.671Z" fill="#cdd6f4"/></svg>
      <span id="mcp-log-brand-text">FastMCP Apps</span>
      <span id="mcp-log-count-badge">· <span id="mcp-log-count">0</span></span>
    </div>
    <div id="mcp-log-actions">
      <button id="mcp-log-reset" onclick="window.location.href='/'">&#8592; Back</button>
      <script>if (window.location.pathname === "/") document.getElementById("mcp-log-reset").style.display = "none";</script>
      <button id="mcp-log-clear">Clear</button>
      <button id="mcp-log-close">×</button>
    </div>
  </div>
  <div id="mcp-log-filters">
    <span id="mcp-log-filters-label">Show</span>
    <div class="log-seg">
      <button class="active" data-filter="tools">Tools</button>
      <button class="active" data-filter="notifications">Logs</button>
      <button class="active" data-filter="bridge">Host</button>
      <button class="active" data-filter="errors">Errors</button>
    </div>
    <select id="mcp-log-level-select">
      <option value="debug">Debug+</option>
      <option value="info">Info+</option>
      <option value="warning">Warn+</option>
      <option value="error">Error+</option>
      <option value="critical">Critical+</option>
    </select>
  </div>
  <div id="mcp-log-entries"></div>
</div>
<button id="mcp-log-open">MCP Log</button>
<script>
(function() {
  var lastId = 0, totalCount = 0, panelWidth = 360;
  var panel = document.getElementById("mcp-log-panel");
  var entries = document.getElementById("mcp-log-entries");
  var countEl = document.getElementById("mcp-log-count");
  var openBtn = document.getElementById("mcp-log-open");
  var resizeHandle = document.getElementById("mcp-log-resize");
  var allFilterKeys = ["tools", "notifications", "bridge", "errors"];

  function syncURL() {
    var params = new URLSearchParams(window.location.search);
    if (!panel.classList.contains("hidden")) {
      params.set("log", "open");
    } else {
      params.delete("log");
    }
    var on = [];
    for (var i = 0; i < allFilterKeys.length; i++) {
      if (activeFilters[allFilterKeys[i]]) on.push(allFilterKeys[i]);
    }
    if (on.length === allFilterKeys.length) {
      params.delete("filters");
    } else {
      params.set("filters", on.join(","));
    }
    if (minLevel === 0) {
      params.delete("level");
    } else {
      params.set("level", levelOrder[minLevel]);
    }
    var qs = params.toString();
    var url = window.location.pathname + (qs ? "?" + qs : "");
    history.replaceState(null, "", url);
  }

  function setFrameLayout(w) {
    var frame = document.getElementById("app-frame");
    if (!frame) return;
    frame.style.setProperty("width", w, "important");
    frame.style.setProperty("margin-left", w === "100%" ? "0" : panelWidth + "px", "important");
  }

  document.getElementById("mcp-log-close").addEventListener("click", function() {
    panel.classList.add("hidden");
    openBtn.style.display = "block";
    setFrameLayout("100%");
    syncURL();
  });

  openBtn.addEventListener("click", function() {
    panel.classList.remove("hidden");
    openBtn.style.display = "none";
    setFrameLayout("calc(100% - " + panelWidth + "px)");
    entries.scrollTop = entries.scrollHeight;
    syncURL();
  });

  resizeHandle.addEventListener("mousedown", function(e) {
    e.preventDefault();
    resizeHandle.classList.add("active");
    var frame = document.getElementById("app-frame");
    if (frame) frame.style.pointerEvents = "none";
    document.addEventListener("mousemove", onResize);
    document.addEventListener("mouseup", stopResize);
  });

  function onResize(e) {
    var w = Math.max(200, Math.min(e.clientX, window.innerWidth * 0.8));
    panelWidth = w;
    panel.style.width = w + "px";
    setFrameLayout("calc(100% - " + w + "px)");
  }

  function stopResize() {
    resizeHandle.classList.remove("active");
    var frame = document.getElementById("app-frame");
    if (frame) frame.style.pointerEvents = "";
    document.removeEventListener("mousemove", onResize);
    document.removeEventListener("mouseup", stopResize);
  }

  document.getElementById("mcp-log-clear").addEventListener("click", function() {
    entries.innerHTML = "";
    totalCount = 0;
    countEl.textContent = "0";
    fetch("/api/logs/clear", { method: "POST" });
  });

  var activeFilters = {tools: true, notifications: true, bridge: true, errors: true};
  var levelOrder = ["debug", "info", "notice", "warning", "error", "critical", "alert", "emergency"];
  var minLevel = 0;

  // Restore state from URL params
  (function restoreURL() {
    var params = new URLSearchParams(window.location.search);
    if (params.get("log") === "open") {
      panel.classList.remove("hidden");
      openBtn.style.display = "none";
      setFrameLayout("calc(100% - " + panelWidth + "px)");
    }
    var fp = params.get("filters");
    if (fp !== null) {
      var on = fp ? fp.split(",") : [];
      for (var i = 0; i < allFilterKeys.length; i++) {
        var k = allFilterKeys[i];
        activeFilters[k] = on.indexOf(k) !== -1;
        var btn = document.querySelector("[data-filter='" + k + "']");
        if (btn) btn.classList.toggle("active", activeFilters[k]);
      }
    }
    var lp = params.get("level");
    if (lp) {
      var idx = levelOrder.indexOf(lp);
      if (idx >= 0) {
        minLevel = idx;
        document.getElementById("mcp-log-level-select").value = lp;
      }
    }
  })();

  document.getElementById("mcp-log-filters").addEventListener("click", function(e) {
    var btn = e.target.closest("[data-filter]");
    if (!btn) return;
    var f = btn.dataset.filter;
    activeFilters[f] = !activeFilters[f];
    btn.classList.toggle("active", activeFilters[f]);
    applyFilters();
    syncURL();
  });

  document.getElementById("mcp-log-level-select").addEventListener("change", function(e) {
    minLevel = levelOrder.indexOf(e.target.value);
    applyFilters();
    syncURL();
  });

  function shouldShow(el) {
    var cat = el.dataset.category || "";
    if (activeFilters[cat] === false) return false;
    var lv = el.dataset.level;
    if (lv && levelOrder.indexOf(lv) < minLevel) return false;
    return true;
  }

  function applyFilters() {
    var items = entries.querySelectorAll(".log-entry");
    for (var i = 0; i < items.length; i++) {
      items[i].style.display = shouldShow(items[i]) ? "" : "none";
    }
  }

  function summarize(entry) {
    var b = entry.body;
    if (!b) return "";
    if (entry.direction === "request" || entry.direction === "notification") {
      if (b.method === "tools/call" && b.params) return b.params.name || "";
      if (b.method === "resources/read" && b.params) return b.params.uri || "";
      if (b.method === "notifications/message" && b.params) {
        var d = b.params.data;
        if (d && typeof d === "object") return d.msg || d.message || JSON.stringify(d);
        return d || b.params.level || "";
      }
      return "";
    }
    if (b.error) return "error: " + (b.error.message || JSON.stringify(b.error));
    if (b.result && typeof b.result === "object") {
      if (Array.isArray(b.result.tools)) return b.result.tools.length + " tools";
      if (Array.isArray(b.result.resources)) return b.result.resources.length + " resources";
      if (Array.isArray(b.result.prompts)) return b.result.prompts.length + " prompts";
      if (b.result.content) {
        var first = b.result.content[0];
        if (first && first.text) {
          return first.text.length > 60 ? first.text.slice(0, 60) + "…" : first.text;
        }
        return b.result.content.length + " content item(s)";
      }
    }
    return "";
  }

  function formatTime(ts) {
    var d = new Date(ts * 1000);
    return String(d.getHours()).padStart(2, "0") + ":"
      + String(d.getMinutes()).padStart(2, "0") + ":"
      + String(d.getSeconds()).padStart(2, "0");
  }

  function renderEntry(entry) {
    var div = document.createElement("div");
    var isError = entry.direction === "response" && entry.body
      && (entry.body.error || (entry.body.result && entry.body.result.isError));
    div.className = "log-entry" + (isError ? " error" : "");
    var dirClass = isError ? "error" : entry.direction;
    var arrows = {request: "→", response: "←", bridge: "↑", notification: "↓"};

    // Categorize for filtering
    if (isError) div.dataset.category = "errors";
    else if (entry.direction === "bridge") div.dataset.category = "bridge";
    else if (entry.direction === "notification") div.dataset.category = "notifications";
    else div.dataset.category = "tools";

    var primary = document.createElement("div");
    primary.className = "log-primary";

    var left = document.createElement("div");
    left.className = "log-left";

    var dirEl = document.createElement("span");
    dirEl.className = "log-dir " + dirClass;
    dirEl.textContent = arrows[entry.direction] || "←";

    var methodEl = document.createElement("span");
    methodEl.className = "log-method";
    methodEl.textContent = entry.method || "";

    left.appendChild(dirEl);
    left.appendChild(methodEl);

    // Log level badge for notifications
    if (entry.direction === "notification" && entry.body && entry.body.params) {
      var level = (entry.body.params.level || "").toLowerCase();
      if (level) {
        div.dataset.level = level;
        var lvl = document.createElement("span");
        lvl.className = "log-level log-level-" + level;
        lvl.textContent = level;
        left.appendChild(lvl);
      }
    }

    var metaEl = document.createElement("span");
    metaEl.className = "log-meta";
    metaEl.textContent = entry.duration_ms != null
      ? entry.duration_ms + "ms"
      : formatTime(entry.timestamp);

    var copyBtn = document.createElement("button");
    copyBtn.className = "log-copy";
    copyBtn.textContent = "Copy";
    copyBtn.addEventListener("click", function(e) {
      e.stopPropagation();
      navigator.clipboard.writeText(JSON.stringify(entry.body, null, 2));
      copyBtn.textContent = "Copied";
      setTimeout(function() { copyBtn.textContent = "Copy"; }, 1000);
    });

    primary.appendChild(left);
    primary.appendChild(metaEl);
    primary.appendChild(copyBtn);
    div.appendChild(primary);

    var summary = summarize(entry);
    if (summary) {
      var subtitle = document.createElement("div");
      subtitle.className = "log-subtitle";
      subtitle.textContent = summary;
      div.appendChild(subtitle);
    }

    var detail = document.createElement("div");
    detail.className = "log-detail";
    detail.textContent = JSON.stringify(entry.body, null, 2);
    div.appendChild(detail);

    div.addEventListener("click", function() { div.classList.toggle("expanded"); });
    return div;
  }

  var polling = false;
  var firstPoll = true;
  function poll() {
    if (polling) return;
    polling = true;
    fetch("/api/logs?since=" + lastId)
      .then(function(r) { return r.ok ? r.json() : []; })
      .then(function(data) {
        if (!data || !data.length) return;
        lastId = data[data.length - 1].id;
        totalCount += data.length;
        countEl.textContent = String(totalCount);
        var panelVisible = !panel.classList.contains("hidden");
        var atBottom = !panelVisible || entries.scrollHeight - entries.scrollTop - entries.clientHeight < 40;
        for (var i = 0; i < data.length; i++) {
          var el = renderEntry(data[i]);
          el.classList.add("new");
          if (!shouldShow(el)) el.style.display = "none";
          entries.appendChild(el);
        }
        if (atBottom || (firstPoll && panelVisible)) entries.scrollTop = entries.scrollHeight;
        firstPoll = false;
      })
      .catch(function() {})
      .finally(function() { polling = false; });
  }

  window.addEventListener("message", function(event) {
    var data = event.data;
    if (typeof data === "string") {
      try { data = JSON.parse(data); } catch(e) { return; }
    }
    if (!data || typeof data !== "object") return;
    if (!data.jsonrpc && !data.method) return;
    fetch("/api/logs/bridge", {
      method: "POST",
      headers: {"Content-Type": "application/json"},
      body: JSON.stringify({body: data})
    });
  });

  setInterval(poll, 500);
  poll();
})();
</script>
c                6     | j                   dt        dz         S )z0Inject the MCP message log panel before </body>.z</body>z
</body>)r   _LOG_PANEL_HTML)htmls    r   _inject_log_panelrj     s    4<<	?[#@AAr   c                    dD ]Y  }| j                  |      }t        |t              s%|j                  d      }t        |t              sG|j                  d      sY y y)z=Return True if the tool has a UI resourceUri in its metadata.)meta_metauiresourceUriTF)r.   r^   dict)toolkeymrn   s       r   _has_ui_resourcert     sM     HHSMatB"d#}(= ! r   c           	        ddl }ddl}|j                  d      xs i }|j                  d      xs g }i }|j                         D ]  \  }}|j                  dd      }dD ]r  }	|	|v s||	   D 
cg c](  }
t	        |
t
              r|
j                  d      dk7  r|
* }}
|r2|D 
cg c]  }
d|
v s|
j                  d       }}
d	D ]
  }||v s|} n  n |xd
k(  r t        }n7xdk(  r t        }n*xdk(  r t        }nxdk(  rnxdk(  rn n t        }n	 t        }|j                  d      xs  |j                  dd      j                         }|j                  d      }||v }|r|j                  j                  }nd|v r|d   }nd}|dz  }i }|j                  d      rddlm} |t!        |d            }|j                  d      dk(  xs> t	        |j                  d      t
              xr |d   j                  d      dk(  xs |dv }|r	dddii|d<   | |j"                  d|||d|f||<     |j$                  | j                          dfi |S c c}
w c c}
w )zKDynamically create a Pydantic model from a JSON Schema for form generation.r   N
propertiesrequiredtypestring)anyOfoneOfnull)objectarrayry   integernumberbooleanr   r   r   r}   r~   title_ descriptiondefaultenum)Literalformattextarearn   )r}   r~   json_schema_extra)r   r   r   FormrO   )pydanticpydantic.fieldsr.   itemsr^   rp   rI   floatboolstrr   r   fieldsPydanticUndefinedtypingr   tupleFieldcreate_model)	tool_nameinput_schemar   rv   rw   field_definitions	prop_nameprop	json_typerr   tnon_nulltypes	candidatepy_typer   r   is_requiredr   extrar   use_textareas                         r   _model_from_schemar     s   !-!1!1,!?!E2J&**:6<"H(*%++-	4HHVX.	 &Cd{ "#Y&!!T*quuV}/F &  
 4<LHq!QUU6]HEL&	 %-(1I!& - &0  #G#!HY%6%6sC%@%F%F%Hhh}-8+oo77G$9oGGnG "88F&eDL12G HHX*, 0488D>40 9JNN6*j80
 // 	 *.0D)EE%& HNN u+IN(
)$S .` !8  IOO$5#6d!;Q?PQQU Ms   /-I$	I.Ic                
   	 ddl m}m}m}m} ddlm} ddlm}m	}m
}m}	m}
m}m}m}m}m}m} ddlm} ddlm}m} | s; |dd	
      5 } |d        |d       ddd        |d      j3                         S | d   d   }dJd} |dd
      5 } |d       t5        |       dkD  r] |d      5   |	d        |d |d |d                  5  | D ]  } | ||      |d   |d   |k(          	 ddd       ddd       n | || d         d        |d|      5  | D ]  }|d   }|j7                  d      xs d }|j7                  d!      xs i }t9        ||      }d|i}|j:                  D ]  } ||      ||<    | |d"      d#} | |d$      d%&      }d'| } d(}! |||)      5   |d      5  |r|j=                  d*      }"d+}#t5        |"      |!k  r |
||#,       nd-| }$d*j?                  |"d|!       }% ||$d.      5   |d.d.)      5   |dd/
      5   |
|%|#,        |d0d1d2 ||$d3      d45       ddd       ddd        |d3d3)      5   |dd/
      5   |
||#,        |d6d1d2 ||$d.      d45       ddd       ddd       ddd        || d7      5   |d7d7)      5   |d      5   |dd/
      5   |d8d        |d9d1d2 || d:      d45       ddd        ||jA                  d;| ||      |<      =      5  |jC                  |d>?        |d@dAdBC       ddd       ddd       ddd        |d:d:)      5   |d      5   |dd/
      5   |d8d        |dDd1d2 || d7      d45       ddd        ||jA                  d;| ||      |<      =      5   |d"dEdFG        |d@dAdBC       ddd       ddd       ddd       ddd       ddd       ddd        	 ddd        |
dHdI,       ddd        |d      j3                         S # t0        $ r Y yw xY w# 1 sw Y   xY w# 1 sw Y   VxY w# 1 sw Y   GxY w# 1 sw Y   #xY w# 1 sw Y   (xY w# 1 sw Y   xY w# 1 sw Y   xY w# 1 sw Y   xY w# 1 sw Y   xY w# 1 sw Y   xY w# 1 sw Y   xY w# 1 sw Y   xY w# 1 sw Y   TxY w# 1 sw Y    xY w# 1 sw Y   %xY w# 1 sw Y   *xY w# 1 sw Y   /xY w# 1 sw Y   4xY w# 1 sw Y   
xY w# 1 sw Y   :xY w# 1 sw Y   5xY w)Kz@Build Prefab picker page: dropdown selector with per-tool forms.r   )FetchOpenLinkSetState	ShowToast)	PrefabApp)ButtonColumnHeadingLabelMarkdownMutedPagePagesSelectSelectOptionTextarea)r   )RESULTRxzX<html><body><p>prefab-ui not installed. Run: pip install fastmcp[apps]</p></body></html>   zp-6 max-w-2xl mx-auto)gap	css_classFastMCP AppszNNo UI tools found on this server. Use @app.ui() to register entry-point tools.N)r   viewnamerq   c                2    | j                  d      xs | d   S )Nr   r   )r.   )rq   s    r   _tool_titlez'_build_picker_html.<locals>._tool_title+  s    xx 0DL0r   rV   zp-8 max-w-2xl mx-autor(   )r   Toolu   Choose a tool…
activeToolz$event)placeholder	on_change)r   selected   )level)r   r   r    inputSchema__json_args__)rq   r   z$errorerror)variant_mode_
   r   
ztext-sm text-muted-foreground)r   _desc_shortzitems-startu   Show more ▾linkxsfullz text-muted-foreground p-0 h-auto)r   sizeon_clickr   u   Show less ▴form	ArgumentszEdit as JSONr   /api/launch)r-   
on_successon_error)	on_submitT)fields_onlyLaunchsuccesssubmit)r   button_typezUse formz{"key": "value"}   )r   r   rowsu5   Generated by [Prefab](https://prefab.prefect.io) 🎨z(text-xs text-muted-foreground text-right)rq   rH   rF   r   )"prefab_ui.actionsr   r   r   r   prefab_ui.appr   prefab_ui.componentsr   r   r   r   r   r   r   r   r   r   r   prefab_ui.components.formr   prefab_ui.rxr   r   ImportErrorri   lenr.   r   model_fieldssplitjoinpost
from_model)&toolsr   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   
first_namer   rq   r   descr   model	form_body
field_name	json_bodyr   
input_mode_desc_max_lineslinesmd_css
desc_stater   s&                                         r   _build_picker_htmlr   
  s@   jJJ+	
 	
 	
 	
 	3+ %<=N#` >
 ~D9>>@@Ahv&J1 
A!8	9Tu:>Af 2&|R\B !&$'-"&v,%)&\Z%? !&	  Ka)3J7 L HH]39r/3xx/F/L"*4>-3TN	"'"4"4J,.zNIj) #5 !%'%8-	
 %R\7C%dV_
"$$d+V] $

4 0!@u:8$TV<+1$J$(IIe4D_.E$FE!&Jg!F$($@$*qM$J$,Uf$E$*(:06-119*f1M2T%& %K %A %)v$>$*qM$J$,TV$D$*(:06-119*g1N2T%& %K %? "G4 Jf=!&7A!'A!G '1 = &$2,2)--5j&-I.P!" "H "&*/**$1)2/7/?-5	 +5 +"" !%4 H &$,,508!"" :G70 "&7A!'A!G '1 = &$.,2)--5j&-I.P!" "H "&*/**$1)2/7/?-5	 +5 +"" !))80B)*!"
 !'$,,508!"" :G73 >G .;++'  8Z 	C@	
A 
:J >5::<<k  jij >=$  ^ %K$J %A$@ %K$J %?$> "G!F8 "H!G" " :G772 "H!G" " :G773 >=G .;]++) 87' 
:	9s  :P* P9 T=/ Q"Q2Q:&T= BT0.
T#	8AT
RQ- Q ?Q-RR$Q:RRT%T	0
S:R;R!$*R;R.-R;5S=T	
S<S/$S*S/-S"S/S<T	T$T#	,T08T=*	P65P69QQQQ	T= Q*%Q--Q72R:R?RRRRT!R+&R;.R83R;;S SST	SS/"S,'S//S94S<<TT		TTT T#	#T-(T00T:	5T==Uc                  K   	 ddl m} ddlm} 	  ||       4 d{   \  }}} |||      4 d{   }|j                          d{    |j                          d{   }|j                  D cg c]  }|j                          c}cddd      d{    cddd      d{    S # t        $ r g cY S w xY w7 7 7 {7 ec c}w 7 47 %# 1 d{  7  sw Y   nxY wddd      d{  7   y# 1 d{  7  sw Y   yxY w# t        $ r'}	t        j                  d|  d|	        g cY d}	~	S d}	~	ww xY ww)z5Return raw tool dicts from the MCP server at mcp_url.r   ClientSessionstreamable_http_clientNzCould not list tools from : )mcpr  mcp.client.streamable_httpr  r   
initialize
list_toolsr   
model_dump	Exceptionloggerdebug)
mcp_urlr  r  readwriter   sessionresultr   excs
             r   _list_toolsr    s    %E)'226FtUA$T511W((***&113306=1= 211 322	  	 31*3= 2 3111 32222
  1'"SEBC	s5  EB2 D CD C;CC;CCC#C	$C6CCC;CC; D ,C-D 1E2C =E?C  ED C;C	CCC;D C&	CC&	"C;)D 4C75D :E;DDD	D ED 	E D;5E 6E;E  Ec           	       K   	 ddl m} ddlm} ddlm} 	  ||       4 d{   \  }}} |||      4 d{   }|j                          d{    |j                   ||             d{   }	|	j                  D ]7  }
t        |
dd      }|s|c cddd      d{    cddd      d{    S  ddd      d{    ddd      d{    y# t        $ r Y yw xY w7 7 7 7 7 O7 @7 0# 1 d{  7  sw Y   @xY w7 7# 1 d{  7  sw Y   yxY w# t        $ r(}t        j                  d| d|  d	|        Y d}~yd}~ww xY ww)
z8Read an MCP resource by URI and return its text content.r   r   r  )AnyUrlNtextzCould not read resource z from r  )r  r  r  r  r   r  r   r  read_resourcecontentsgetattrr
  r  r  )r  urir  r  r  r  r  r   r  r  contentr  r  s                r   _read_mcp_resourcer    s:    %E#)'226FtUA$T511W((***&44VC[AA%G"7FD9D# 211 322  / 21 32    31*A 2 31111 3222  /uF7)2cUKLsG  EC D% C+D% DC-DC9C/C90C11"C9C9D#C3$D(D% 4C55D% 9E:C9;DC7DD% DD% E	C(%E'C((E+D% -D/C91C93D5D% 7D9D	?D D	DD% D"DD"D% !E"D% %	E.EEEEc           	        d| }| d}t        j                  d      5 }|j                  |d      }|j                          |j                  }ddd       t        j                  d      }|st        d	| d
|dd        |j                  d      }d| }	t        j                  d      5 }|j                  |	d      }|j                          |j                  }
ddd       t        j                  d
      }|st        d|	 d
|
dd        |j                  d      }|j                  d      d   j                  d      d   }d| }d| d}t        j                  d||ii      }t        t        j                               d|  d| dz  }|j                         r|j!                  d      }||fS d|  d}t        j                  d      5 }|j                  |d      }|j                          |j"                  }ddd       t%        j&                  t)        j*                        d      5 }|j-                  d      }|t        d       |j/                         j1                         }ddd       d!D ]  }j3                  d"| d#d$| d| d#      }! |j5                  d       ||fS # 1 sw Y   lxY w# 1 sw Y   xY w# 1 sw Y   xY w# 1 sw Y   jxY w)%u  Download app-bridge.js and build an import-map that fixes Zod v4 on esm.sh.

    Returns ``(app_bridge_js, import_map_json)`` where *import_map_json* is a
    JSON string ready to embed in a ``<script type="importmap">`` tag.

    Background
    ----------
    esm.sh's ``zod@x.y.z/es2022/v4.mjs`` only re-exports ``{z, default}``,
    losing all individual named exports (``custom``, ``string``, etc.).  The
    MCP SDK does ``import * as t from "zod/v4"`` and calls ``t.custom(…)``
    which fails.  ``zod@x.y.z/es2022/v4/classic/index.mjs`` exports everything
    correctly.  An import-map that remaps the broken URL to the working one
    fixes all modules in the page's graph, including those loaded cross-origin
    from esm.sh.

    ext-apps app-bridge.js imports the SDK via bare specifiers
    (``@modelcontextprotocol/sdk/types.js`` etc.) that the browser cannot
    resolve.  We rewrite them to concrete esm.sh URLs before serving.

    Caching
    -------
    ``app_bridge_js`` is cached on disk because it is static for a given
    ext-apps + SDK version pair (downloaded from npm).

    The import map is NOT cached.  It contains the concrete zod version that
    esm.sh resolves ``zod@^x`` to at fetch time, and that version can change
    when esm.sh publishes a new zod release.  A stale cached import map would
    redirect the old version URL while the browser loads the new version URL,
    so the redirect would not apply and ``t.custom`` would be undefined.
    z)https://esm.sh/@modelcontextprotocol/sdk@z	/types.jsg      >@timeoutT)follow_redirectsNzimport "(/zod@[^"]*v4[^"]*)"z Could not find zod/v4 import in z:
i  r(   zhttps://esm.shz-export \* from "(/zod@[\d.]+/es2022/v4\.mjs)"z#Could not find v4.mjs re-export in @/r   zhttps://esm.sh/zod@z/es2022/v4/classic/index.mjsimportszfastmcp-ext-apps-z-sdk-z.jsrR   )encodingzEhttps://registry.npmjs.org/@modelcontextprotocol/ext-apps/-/ext-apps-z.tgzzr:gz)fileobjmodezpackage/dist/src/app-bridge.jsz+app-bridge.js not found in ext-apps tarball)ztypes.jszshared/protocol.jszfrom"@modelcontextprotocol/sdk/"zfrom")httpxClientr.   raise_for_statusr  researchRuntimeErrorgroupr   r   r   r   tempfile
gettempdirexists	read_textr  tarfileopenioBytesIOextractfiler  rW   r   
write_text)versionsdk_versionsdk_base	types_urlclientresptypes_contentzod_wrapper_matchzod_wrapper_pathzod_wrapper_urlwrapper_contentbroken_matchbroken_pathzod_version
broken_url	fixed_urlimport_map_jsonapp_bridge_cacheapp_bridge_jsnpm_urldatatarmembersdk_paths                           r   _fetch_app_bridge_bundle_syncrQ    s   D ;;-HH *I&I	d	#vzz)dz;		 
$ 		"A=Q.yk]4C=P<QR
 	
 )..q1&'7&89O	d	#vzz/DzA)) 
$ 998/L 1/1B#oVZWZF[E\]
 	
 $$Q'K##C(+11#6q9K!+/J%k]2NOIjj)j)-D!EFO 	X  "#(9'%}TW&XX   (22G2Do--UV]U^^bcG	d	#vzz'Dz9|| 
$
 
bjj.V	<!AB>LMM,,.	 
= 7%---hZq9H:Qxj*
 7 @/)) 
$	# 
$	#< 
$	#
 
=	<s/   0J;10K0K;=K!;KKK!K*c                v   K   t        j                         }|j                  dt        | |       d{   S 7 w)z3Async wrapper around _fetch_app_bridge_bundle_sync.N)asyncioget_running_looprun_in_executorrQ  )r9  r:  loops      r   _fetch_app_bridge_bundlerW  Z  s=     
 ##%D%%+Wk   s   0979c                    dfd}d fd}dfd}dd}d fd}	dfd}
d fd}dfd}dfd	}dfd
}t        t        d|      t        d|      t        d|      t        d|dg      t        d|      t        d|dg      t        d|dg      t        d|	      t        d|
      t        d|g d      g
      S )z+Build the Starlette dev server application.c                   K   t         j                  ddddt        t        j                  d      dd	      }rt        t        |            S t        |      S w)	uO   AppBridge host page — loads the picker app in an iframe and wires the bridge.r   r   noneblock/picker-appzRbridge.onopenlink = async ({ url }) => { window.location.href = url; return {}; };z&bridge.oninitialized = async () => {};)	r   import_map_tagstatus_textstatus_displayframe_displaymcp_sdk_versioniframe_src_jsonon_open_linkon_initialized)_HOST_SHELLr   _MCP_SDK_VERSIONr   r   r	   rj   )r)   	host_htmlr]  	log_panels     r   pickerz_make_dev_app.<locals>.pickers  si     && )!!, JJ}5mC ' 

	  *956	
 i(	
   AAc                  K   	 t               d{   }|D cg c]  }t        |      s| }}t        |      }t        |      S 7 5c c}w # t        $ r/}t        j                  d       d| d}Y d}~t        |      S d}~ww xY ww)u8   Prefab picker UI — tool list with one tab per UI tool.NzError building picker UIz+<pre style='padding:2rem;color:red'>Error: z</pre>)r  rt   r   r
  r  	exceptionr	   )r)   	raw_toolsr   ui_toolsri   r  r  s         r   
picker_appz!_make_dev_app.<locals>.picker_app  s     	M)'22I#,D9a0@0C9HD%h/D D!! 3D 	M78@VLDD!!	MsR   BA A	A AAA B	A A 	BB4BBBc                r  K   | j                   j                  dd      }| j                   j                  dd      }t        j                  |      }t        j                  t        j                  |d      t        |      t        |      t              }rt        t        |            S t        |      S w)z+Host page: GET /launch?tool=name&args={...}rq   r   args{}T)quote)r   r]  tool_name_jsontool_args_jsonra  )query_paramsr.   r   r]   _HOST_HTML_TEMPLATEr   ri   escaper   rf  r	   rj   )r)   rq   args_raw	tool_argsrg  r]  rh  s        r   launchz_make_dev_app.<locals>.launch  s     ##''3''++FD9JJx(	'..kk$d3)+D1+I6, / 
	  *956	
 i(	
s   B4B7c           	       K   | j                          d{   }|j                  dd      }|j                  dd      }|r|j                         si }n	 t        j                  |      }t        |t              st        t        j                  dd
t        |      j                  z   i      dd	      S i }|j                         D ]b  \  }}t        |t              rH|j                         }|s)|d   dv r.	 t        j                  |      }	t        |	t        t        f      r|	}|||<   d dt        |t        j                  |      d      z   }
t        t        j                  |
      d      S 7 X# t         j                  $ r0}t        t        j                  dd| i      dd	      cY d}~S d}~ww xY w# t         j                  t        f$ r Y w xY ww)zDPicker form submits here; returns a /launch URL string for OpenLink.Nrq   r   r   r   zInvalid JSON:   application/jsonr  status_code
media_typezJSON must be an object, not r   ){[z/launch?)rq   rq  r  r  )r   r7   stripr]   r\   r
   r   r^   rp   rx   rK   r   r   r_   r`   r   )r)   rM  rq   raw_json_argsrz  r  kvstrippedparsedurls              r   
api_launchz!_make_dev_app.<locals>.api_launch  s    \\^#xx# $7$ &&(	 $

= 9I ")T2# $

 ')G"&y/":":*;! %(#5	 	 I

1a% wwyH $ {j0!%)ZZ%9F)&4,?$*  !	! %  9dDJJy<Q%RSSJJsO)
 	
e $ ++ # $

G~cU5K+L M$'#5 D !% 4 4i@ ! !se   GE.=GE1 *BG6-F7#AG1F4%F/)F4*G/F44G7GGGGc                   K   | j                   j                  dd      }|st        dd      S t        |       d{   }|t        d| d      S t	        |      S 7 !w)	u  Fetch an MCP resource server-side and return it as HTML.

        Used by the launch page to load the renderer via iframe.src rather
        than iframe.srcdoc — avoids a race condition where the Prefab renderer
        sends its MCP initialize message before the AppBridge transport is
        listening (srcdoc parses and runs module scripts synchronously, while
        iframe.src load adds the network-roundtrip gap needed).
        r  r   zMissing uri parameterr}  )r  NzCould not read MCP resource: i  )rv  r.   r
   r  r	   )r)   r  ri   r  s      r   ui_resourcez"_make_dev_app.<locals>.ui_resource  sk      ""&&ub13EE'55<;C5AsSSD!! 6s   ;A"A "A"c                &   K   t        d      S w)z(Serve the locally patched app-bridge.js.zapplication/javascriptr  )r
   )r)   rK  s    r   serve_app_bridge_jsz*_make_dev_app.<locals>.serve_app_bridge_js  s     !/
 	
s   c           	       K   | j                          d{   }|r| j                  dk(  rq	 t        j                  |      }t	        |t
              r*|D ]$  }t	        |t              sj                  |       & n!t	        |t              rj                  |       | j                  j                         D ci c]  \  }}|j                         dvr|| }}}t        j                  t        j                  dd      d      dfd}	 j!                  | j                  ||t        | j"                        	      }j%                  |d
       d{   }	|	j                  j'                  dd      }
|	j                  j                         D ci c]  \  }}|j                         dvr|| }}}t)         ||	      |	j*                  ||
xs d      S 7 # t        j                  t        f$ r Y ^w xY wc c}}w 7 c c}}w # t        j,                  t        j.                  f$ rM j1                          d{  7   t3        t        j4                  ddi      j7                         dd      cY S w xY ww)zBProxy all MCP requests to the user's server (avoids browser CORS).NPOST)hostzcontent-lengthg      N@)r  F)r  	trust_envc           
    
  K   d| j                   j                  dd      v }g }d}	 | j                         2 3 d {   }| |r||j                  dd      z  }d|v sd|v s/|j	                  d      }|j	                  d      }|d	k\  r|d	k  s||k  r|d | ||d
z   d  }}n|d | ||dz   d  }}|j                         D ]g  }|j                  d      st        j                  t        j                        5  
j                  t        j                  |dd               d d d        i d|v rd|v rА|j                  |       7 # 1 sw Y   xY w6 nG# t        j                  t        j                   t        j"                  t$        j                  f$ r Y nw xY w|rt'        
dj)                  |      d       t        j                  t*              5  | j-                          d {  7   d d d        n# 1 sw Y   nxY wt        j                  t*              5  	j-                          d {  7   d d d        y # 1 sw Y   y xY w# |rt'        
dj)                  |      d       t        j                  t*              5  | j-                          d {  7   d d d        n# 1 sw Y   nxY wt        j                  t*              5  	j-                          d {  7   d d d        w # 1 sw Y   w xY wxY ww)NrQ   content-typer   rR   r   rS   z

z

r   r      rU   rV   r   r~  )headersr.   aiter_bytesrW   findrX   rY   rZ   r[   r   r\   r:   r]   r0   r(  RemoteProtocolError	ReadErrorReadTimeouthttpcorerf   r   r
  aclose)r>  is_ssebufsse_bufchunkrinieventrd   r=  message_logs            r   _stream_and_cleanupz=_make_dev_app.<locals>.proxy_mcp.<locals>._stream_and_cleanup  su    (DLL,<,<^R,PPF!CG"*#'#3#3#5 *%K5<<	<#JJ(G3v7H!(j!9B!(f!5B!QwBFb2g18"wrAvx?Pw18"wrAvx?Pw(-(8(8(:#'??8#<)3)<)<T=Q=Q)R(3(@(@DQRQSHAU(V *S)R ); )G3v7H 

5)%* *S)R $6( ))!!,,	   'SXXc]DVW((3++-'' 433((3 --/)) 433	 'SXXc]DVW((3++-'' 433((3 --/)) 433s  #LE EEE&E &A0E #E :(E"E 1E 6E EEE I AF I F  I #7LG=.G1/G=4	L=H L"I6H97I<	LI
L8L 	J,J 
J,#	L ,J51 L K4%K(
&K4+	L 4K=9L  L)r%   r  r  r  paramsT)streamr  r   )ztransfer-encoding
connectionz
keep-alivezcontent-encodingzapplication/octet-stream)r  r  r  r   zMCP server not reachablei  r~  r  )r>  zhttpx.ResponserF   r   )r-   r%   r   r]   r^   r_   rp   r2   r\   r`   r  r   lowerr(  AsyncClientTimeoutbuild_requestrv  sendr.   r   r  ConnectErrorConnectTimeoutr  r
   r   encode)r)   r-   req_jsonre   r  r  r  r  reqr>  rc   fwd_headersr=  r  r  s               @r   	proxy_mcpz _make_dev_app.<locals>.proxy_mcp  sY    \\^# GNNf,	::d+h- (%dD1'33D9 !)  $/++H5  --/
/1wwy :: qD/ 	 
 ""MM$T2e
&	*P"	&&~~G001 ' C  S66D<<++NB?L !LL..0
0DAq779 10  
 %#D) ,,#'E+E	 s $ (()4 
x 7
" ""E$8$89 	--/!!

G-G#HIPPR- 	s   JGJ:G! *5G! J<H6JA	H H=H H	8%H J!G>:J=G>>	JH 	H 6I?I6I?<J>I??Jc                   K   t        | j                  j                  dd            }j                  |      }t	        t        j                  |      d      S w)z,Return message log entries since a given id.since0r~  r  )rI   rv  r.   r>   r
   r   r   )r)   r  entriesr  s      r   api_logsz_make_dev_app.<locals>.api_logs]  sP     G((,,Wc:;''.JJw')
 	
rj  c                   K   | j                          d{   }j                  |j                  d|             t        dd      S 7 2w)z?Log a bridge message (postMessage between app iframe and host).Nr-   rr  r~  r  )r   rA   r.   r
   )r)   rM  r  s     r   api_logs_bridgez&_make_dev_app.<locals>.api_logs_bridgef  sA     \\^#txx561CDD $s   AA
3Ac                F   K   j                          t        dd      S w)zClear the message log.rr  r~  r  )rD   r
   )r)   r  s    r   api_logs_clearz%_make_dev_app.<locals>.api_logs_clearl  s!     1CDDs   !r"  r\  z/launchr   r  )methodsz	/api/logsz/api/logs/bridgez/api/logs/clearz/ui-resourcez/js/app-bridge.js/mcp)GETr  DELETEPUTPATCHOPTIONS)routes)r)   r   rF   r	   )r)   r   rF   r
   )r   r   )r  rK  r]  r  rh  ri  ro  r{  r  r  r  r  r  r  r  s   `````          r   _make_dev_appr  j  s    
&	"
$7
r""
gR
EE
 #v-,)V$-fX>+x($oxH#^fXF.+.%':;L
 r   T	127.0.0.1reloadr  c               0  K   t         j                  ddd| ddd|dt        |      dg}|r|j                  d	       n|j                  d
       i t        j
                  ddi}t        j                  ||t         j                  dk7  d d{   }|S 7 w)z8Start the user's MCP server as a subprocess on mcp_port.z-mzfastmcp.clirunz--transporthttpz--hostz--portz--no-bannerz--reloadz--no-reloadFASTMCP_LOG_LEVELWARNINGwin32)envstart_new_sessionN)	sys
executabler   r0   osenvironrS  create_subprocess_execplatform)server_specmcp_portr  r  cmdr  processs          r   _start_user_serverr    s      	HC 

:

=!
8RZZ
8,i
8C22	,,'1 G
 Ns   BBBBc                J  K   t        j                         }|j                         |z   }t        j                  d      4 d{   }|j                         |k  r.	 |j                  | d       d{    	 ddd      d{    yddd      d{    y7 V7 )7 # t        j                  t        j                  t        j                  f$ r! t        j                  d       d{  7   Y nw xY w|j                         |k  r7 v# 1 d{  7  sw Y   yxY ww)z/Poll until the server is accepting connections.F)r  Ng      ?r  Tg      ?)
rS  rT  r/   r(  r  r.   r  r  TimeoutExceptionsleep)r  r  rV  deadliner=  s        r   _wait_for_serverr    s     ##%Dyy{W$H  511ViikH$*jjcj222	 21111  2 3 2 ""))&& *
 mmD)))*	 iikH$ 2111 s   AD#BD#DB!3B4B!9D#BD#DD#B!D#!A
C4+C.,C41D3C44DD#D DD D#i@  i  )r  dev_portr  r  rh  c          
        	
K   dvr	d d dnd d d
dvrd d nd d 	dd
	
 f	d}t        j                         }t        j                   |             d
fd}t        j                  d	k7  r@|j                  t        j                  |       |j                  t        j                  |       	  d{    t        j                  d	k7  r>|j                  t        j                         |j                  t        j                         j                  	 t        j                  d	k7  rBt        j                  t        j                  j                        t        j                         nj!                          j'                          d{    yyy7 # t         j                  $ r Y w xY w# t"        t$        f$ r j!                          Y Ww xY w7 G# t        j                  d	k7  r>|j                  t        j                         |j                  t        j                         j                  	 t        j                  d	k7  rBt        j                  t        j                  j                        t        j                         nj!                          n%# t"        t$        f$ r j!                          Y nw xY wj'                          d{  7   w w w xY ww)zStart the full dev environment for a FastMCPApp server.

    Starts the user's MCP server on *mcp_port*, starts the Prefab dev UI
    on *dev_port* (with an /mcp proxy to the user's server), then opens
    the browser.
    :zhttp://r  zhttp://[z]:Nc            
       	K   dd l } ddfddffD ]  \  }}}d}dk(  r"| j                  d|ff| j                  d|ddfffn'd	v r| j                  |ddfffn| j                  |fff}|D ]M  \  }}	 | j                  || j                        5 }|j	                  |      dk(  rd
}	 d d d         n	 d d d        O |st        j                  d| d| d| d       t        j                  d        t        j                  d d       t        j                  d       t               d {   t        t        t               d {   \  }	}
d|
 d}t        d       d {   }|st!        d       t        j                  d        t#        |	|t%                     }t'        j(                  |dd      }t'        j*                  |      }d |_        dfd}t/        j0                  |j3                          |              d {    y # 1 sw Y   rxY w# t
        $ r Y w xY w7 
7 7 7 )w) Nr   z
MCP serverz
--mcp-portzdev UIz
--dev-portFr  z::1r  TzPort z (z) is already in use. Try z to use a different port.r(   zStarting user server on port u   …u"   Fetching app-bridge.js from npm…r  z  <script type="importmap">
  z
  </script>      .@r  z"User server did not start on port zFastMCP dev UI at warningzwebsockets-sansio)r  port	log_levelwsc                      y rC   rO   rO   r   r   <lambda>z-run_dev_apps.<locals>._body.<locals>.<lambda>&  s    r   c                 v   K   t        j                  d       d {    t        j                          y 7 w)Ng?)rS  r  
webbrowserr4  )dev_urls   r   _open_browserz2run_dev_apps.<locals>._body.<locals>._open_browser(  s*     --$$$OOG$ %s   979rE   )socketAF_INETAF_INET6SOCK_STREAM
connect_exOSErrorr  r   r  exitinfor  rW  _EXT_APPS_VERSIONrf  r  r-  r  r   uvicornConfigServerinstall_signal_handlersrS  gatherserve)r  r  labelflagin_use_targetsfamilyaddrsrK  rI  r]  readydev_appconfigserverr  r  r  r  rh  r  r  r  r  	user_procs                    r   _bodyzrun_dev_apps.<locals>._body  s     	 |\2x."
D% F ;& ^^k4%89__udAq&9: d{ oodAq'9:<!>>D$<8:  !)vv/A/ABa<<-2%)F! CB2 C !) D6E7 +& 9; ?"
B 	3H:SAB89
 -&t
 
	 0H/0
 *
&
 .o->mL 	 'w==!CH:NOO(	23]NKM9
 "
 ' *6&	% nnV\\^]_===o CB  
*
 >2 	>s   A/I3H/H"'H//I2H/:I?A6I5H?6III/I0B,III"H,'H//	H<8I;H<<IIIIc                     t        j                  d      j                  t         j                          j	                          y )Nzuvicorn.error)logging	getLoggersetLevelCRITICALcancel)tasks   r   
_on_signalz run_dev_apps.<locals>._on_signal5  s-     	/*33G4D4DEr   r  rE   )rS  rT  ensure_futurer  r  add_signal_handlersignalSIGINTSIGTERMCancelledErrorremove_signal_handler
returncoder  killpggetpgidpidkillProcessLookupErrorPermissionErrorwait)r  r  r  r  r  rh  r  rV  r  r  r  r  r  s   ``````   @@@@r   run_dev_appsr'    su    " d? $q
$'vRz.  d? $q
#vRz*  48IQ> Q>n ##%D  )D ||wz:
;#

 <<7"&&v}}5&&v~~6 Y%9%9%A!<<7*IIbjj7HNN$ ..""" &B  	!!  '8 ! !" <<7"&&v}}5&&v~~6 Y%9%9%A!<<7*IIbjj7HNN$&8 ! !..""" &B s   CLF; F9F; AL9A%G L1G92L9F; ;GG; GG; G63L5G66L;A LA%KLK$!L#K$$L:K=;LL)r   r   rF   r   )ra   r   rb   bytesrc   r   rF   rG   )ri   r   rF   r   )rq   rH   rF   r   )r   r   r   rH   rF   z	type[Any])r   rJ   rF   r   )r  r   rF   rJ   )r  r   r  r   rF   z
str | None)r9  r   r:  r   rF   ztuple[str, str])r  r   rK  r   r]  r   r  r   rh  r   rF   r   )
r  r   r  rI   r  r   r  r   rF   zasyncio.subprocess.Process)r  )r  r   r  r   rF   r   )r  r   r  rI   r  rI   r  r   r  r   rh  r   rF   rG   )<rN   
__future__r   rS  rZ   ri   r5  r   r  r  r+  r  r  r3  r/  r/   r  pathlibr   r   r   urllib.parser   r  r(  r  starlette.applicationsr   starlette.requestsr   starlette.responsesr	   r
   r   starlette.routingr   fastmcp.utilities.loggingr   rK   r  r   r   rf   r  rf  re  rw  rh   rj   rt   r   r   r  r  rQ  rW  r  r  r  r'  rO   r   r   <module>r1     s  0 #    	   	 	  
       "    , & I I # 0	H		"X$ X$v*     Srw zrjBYRxi=b&:l*l*l* l*^  XXX X 	X
 X XH	     	 
     F* T#T# T# 	T#
 T# T# T# 
T#r   