106 lines
6.1 KiB
HTML
106 lines
6.1 KiB
HTML
{% extends "base.html" %}
|
|
{% block title %}MCP-Server{% endblock %}
|
|
{% block content %}
|
|
<div class="stack stack-sm">
|
|
<h1>MCP-Server</h1>
|
|
<p class="text-muted">Zentrale MCP-Server im Firmennetz (Streamable HTTP); Workspace-Admins schalten sie für ihren Workspace frei. Workspaces können außerdem eigene Verbindungen anlegen: zu einer neuen Adresse erst nach Freigabe hier. Entdeckte Tools sind gesperrt, bis sie unter „Tools“ eingestuft sind; gleiche Tools desselben Dienstes übernehmen die Einstufung. Datenbanken laufen über <a href="/admin/databases">Datenbank-Treiber</a>.</p>
|
|
</div>
|
|
{% include "_flash.html" %}
|
|
|
|
{% if pending %}
|
|
<section class="card" id="pending"><div class="card-body stack stack-sm">
|
|
<h2>Warten auf Freigabe</h2>
|
|
<p class="text-sm text-muted">Verbindungen von Workspaces zu Adressen, die die Plattform noch nicht nutzt. Nach der Freigabe ruft Bifröst die Tools ab; eingestuft werden sie wie die zentraler Server.</p>
|
|
<div class="table-wrap"><table>
|
|
<thead><tr><th>Verbindung</th><th>Workspace</th><th>Endpunkt</th><th></th></tr></thead>
|
|
<tbody>
|
|
{% for s in pending %}
|
|
<tr>
|
|
<td>{{ s.label }} <code class="text-xs">{{ s.name }}</code>{% if s.header_names %}<p class="text-xs text-muted">Header: {{ s.header_names|join(', ') }}</p>{% endif %}</td>
|
|
<td>{{ workspace_names.get(s.workspace_id, s.workspace_id) }}</td>
|
|
<td class="text-sm"><code>{{ s.url }}</code></td>
|
|
<td>
|
|
<form method="post" action="/admin/mcp/{{ s.id }}/approve" class="cluster">
|
|
<input type="hidden" name="_csrf" value="{{ p.csrf }}">
|
|
<input name="note" maxlength="500" placeholder="Notiz (optional)" aria-label="Notiz">
|
|
<button class="btn btn-primary btn-sm" type="submit">Freigeben</button>
|
|
<button class="btn btn-secondary btn-sm" type="submit" formaction="/admin/mcp/{{ s.id }}/reject">Ablehnen</button>
|
|
</form>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table></div>
|
|
</div></section>
|
|
{% endif %}
|
|
|
|
<section class="card"><div class="card-body">
|
|
{% if servers %}
|
|
<div class="table-wrap"><table>
|
|
<thead><tr><th>Server</th><th>Endpunkt</th><th>Status</th></tr></thead>
|
|
<tbody>
|
|
{% for s in servers %}
|
|
<tr>
|
|
<td><a href="/admin/mcp/{{ s.id }}">{{ s.label }}</a> <code class="text-xs">{{ s.name }}</code>{% if s.role == 'memory' %} <span class="badge badge-info">Gedächtnis</span>{% elif s.role == 'documents' %} <span class="badge badge-info">Dokumenten-Auslese</span>{% endif %}{% if not s.active %} <span class="badge badge-default">inaktiv</span>{% endif %}</td>
|
|
<td class="text-sm"><code>{{ s.url }}</code>{% if s.secret_hint %}<p class="text-xs text-muted">Token …{{ s.secret_hint }}</p>{% endif %}</td>
|
|
<td>{% if s.status == 'ok' %}<span class="badge badge-success">Tools abgerufen</span>{% elif s.status == 'error' %}<span class="badge badge-danger">Fehler</span><p class="text-xs text-muted">{{ s.last_error }}</p>{% else %}<span class="badge badge-default">neu</span>{% endif %}</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table></div>
|
|
{% else %}<p class="text-muted">Noch kein Server registriert.</p>{% endif %}
|
|
</div></section>
|
|
|
|
{% set others = connections|rejectattr('approval', 'equalto', 'pending')|list %}
|
|
{% if others %}
|
|
<section class="card" id="connections"><div class="card-body stack stack-sm">
|
|
<h2>Verbindungen der Workspaces</h2>
|
|
<div class="table-wrap"><table>
|
|
<thead><tr><th>Verbindung</th><th>Workspace</th><th>Endpunkt</th><th>Status</th></tr></thead>
|
|
<tbody>
|
|
{% for s in others %}
|
|
<tr>
|
|
<td><a href="/admin/mcp/{{ s.id }}">{{ s.label }}</a> <code class="text-xs">{{ s.name }}</code></td>
|
|
<td>{{ workspace_names.get(s.workspace_id, s.workspace_id) }}</td>
|
|
<td class="text-sm"><code>{{ s.url }}</code></td>
|
|
<td>{% if s.approval == 'rejected' %}<span class="badge badge-danger">abgelehnt</span>{% elif s.status == 'ok' %}<span class="badge badge-success">ok</span>{% elif s.status == 'error' %}<span class="badge badge-danger">Fehler</span>{% else %}<span class="badge badge-default">neu</span>{% endif %}</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table></div>
|
|
</div></section>
|
|
{% endif %}
|
|
|
|
<section class="card"><div class="card-body stack stack-sm">
|
|
<h2>Server registrieren</h2>
|
|
{% if not vault_ready %}<div class="alert alert-warning" role="status"><div class="alert-content"><p>AIW_SECRET_KEY fehlt; Tokens können nicht gespeichert werden.</p></div></div>{% endif %}
|
|
<form method="post" action="/admin/mcp" class="stack stack-sm">
|
|
<input type="hidden" name="_csrf" value="{{ p.csrf }}">
|
|
<div class="form-group">
|
|
<label for="name">Kurzname</label>
|
|
<input id="name" name="name" required maxlength="16" pattern="[a-z][a-z0-9_]*" value="{{ form.name or '' }}" placeholder="munin">
|
|
<p class="form-hint">Präfix der Tool-Namen, z. B. <code>munin__search</code>. Lässt sich später nicht ändern.</p>
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="label">Bezeichnung</label>
|
|
<input id="label" name="label" maxlength="200" value="{{ form.label or '' }}" placeholder="Munin Agentic Memory">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="url">URL</label>
|
|
<input id="url" name="url" required type="url" value="{{ form.url or '' }}" placeholder="https://munin.intern/mcp">
|
|
<p class="form-hint">Nur https und nur Hosts aus AIW_MCP_ALLOWED_HOSTS.</p>
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="token">Token (optional)</label>
|
|
<input id="token" name="token" type="password" autocomplete="off">
|
|
<p class="form-hint">Wird als Bearer-Token gesendet und verschlüsselt gespeichert. Workspaces können ein eigenes setzen.</p>
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="role">Art</label>
|
|
<select id="role" name="role">{% for k, v in roles.items() %}<option value="{{ k }}" {% if (form.role or '') == k %}selected{% endif %}>{{ v }}</option>{% endfor %}</select>
|
|
<p class="form-hint">Ein Gedächtnis-Server (z. B. Munin) bekommt im Workspace eine Ansicht der Einträge; löschen können dort nur Menschen.</p>
|
|
</div>
|
|
<div><button class="btn btn-primary" type="submit">Registrieren</button></div>
|
|
</form>
|
|
</div></section>
|
|
{% endblock %}
|