ecb52e9978
A server is a row with a URL; its tools are discovered by a button and cached, then offered beside the built-in ones. Written by hand rather than taken from the reference SDK, because that SDK's transport does its own connecting -- and the one thing that must not be bypassed is check_url on every hop. Owning the transport is the point; the framing beside it is the small part. Sessions are per call: initialize, initialized, the call, a best-effort DELETE. Caching one wants an owner, a TTL, eviction, a lock and a shutdown hook, and the server may expire it under all of that anyway -- ToolContext is a session-free snapshot precisely so nothing in a tool holds live state. A server's names and descriptions reach the model as instructions and are bounded before they do; what it returns is escaped preformatted text, never markdown. Tools are namespaced per server, so two servers exposing "search" do not collide and neither shadows a built-in. Also: a round's calls now run together under a semaphore, results indexed so each tool turn stays paired with its call, and generation.status names what is running -- a remote tool is latency-bound, and a silent pause is what a hang looks like. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
37 lines
1.6 KiB
HTML
37 lines
1.6 KiB
HTML
{% from "_macros.html" import icon %}
|
|
{#
|
|
One MCP server in the list.
|
|
|
|
Swapped in place by the “Test & refresh” button, so this fragment has to be
|
|
able to render on its own as well as inside the list.
|
|
#}
|
|
<div class="model-row {{ 'is-off' if not server.enabled }}" id="mcp-{{ server.id }}">
|
|
<div class="model-row__main">
|
|
<div class="model-row__title">
|
|
<a class="model-row__name" href="/admin/mcp/{{ server.id }}/edit">{{ server.name }}</a>
|
|
<span class="badge">{{ tool_count }} tool{{ '' if tool_count == 1 else 's' }}</span>
|
|
{% if not server.enabled %}<span class="badge badge--danger">disabled</span>{% endif %}
|
|
{% if not server.public %}<span class="badge">restricted</span>{% endif %}
|
|
{% if server.allow_private %}<span class="badge">private network</span>{% endif %}
|
|
{% if server.protocol_version %}
|
|
<span class="badge badge--leaf">MCP {{ server.protocol_version }}</span>
|
|
{% endif %}
|
|
</div>
|
|
<code class="model-row__id">{{ server.slug }} · {{ server.url }}</code>
|
|
{% if message %}
|
|
<p class="text-xs {{ 'danger' if message_kind == 'error' else 'faint' }}">{{ message }}</p>
|
|
{% elif server.last_error %}
|
|
<p class="text-xs danger">{{ server.last_error }}</p>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<div class="model-row__actions">
|
|
<button class="btn btn--sm" type="button"
|
|
hx-post="/admin/mcp/{{ server.id }}/test"
|
|
hx-target="#mcp-{{ server.id }}" hx-swap="outerHTML">
|
|
{{ icon("refresh", "icon--sm") }} Test & refresh
|
|
</button>
|
|
<a class="btn btn--sm" href="/admin/mcp/{{ server.id }}/edit">Edit</a>
|
|
</div>
|
|
</div>
|