systemprompt-mcp 0.32.2

Native Model Context Protocol (MCP) implementation for systemprompt.io. Orchestration, per-server OAuth2, RBAC middleware, and tool-call governance — the core of the AI governance pipeline.
Documentation
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Artifact Viewer</title>
  <style>
    * { box-sizing: border-box; margin: 0; padding: 0; }
    html, body { height: 100%; }
    body {
      font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
      font-size: 14px;
      color: #333;
      background: #fff;
    }
    #frame {
      display: block;
      width: 100%;
      border: 0;
      min-height: 120px;
    }
    .status {
      display: flex;
      align-items: center;
      justify-content: center;
      gap: 10px;
      padding: 32px;
      color: #666;
    }
    .spinner {
      width: 18px;
      height: 18px;
      border: 2px solid #e2e2e8;
      border-top-color: #6366f1;
      border-radius: 50%;
      animation: spin 0.8s linear infinite;
    }
    @keyframes spin { to { transform: rotate(360deg); } }
    .fallback { padding: 16px; }
    .fallback pre {
      background: #f6f6f9;
      border: 1px solid #e2e2e8;
      border-radius: 6px;
      padding: 12px;
      overflow-x: auto;
      font-size: 12px;
    }
    .error {
      margin: 16px;
      padding: 12px;
      border: 1px solid #f8b4b4;
      border-left: 3px solid #ef4444;
      border-radius: 6px;
      background: #fef4f4;
      color: #a00;
    }
  </style>
</head>
<body>
  <div id="root">
    <div class="status"><span class="spinner"></span><span>Loading artifact…</span></div>
  </div>
  <script>
    /*MCP_UI_CONSTANTS*/

    // Method names come from the generated MCP_UI constants; never spell them
    // out here. All artifact markup is rendered server-side, so this shell
    // needs no knowledge of artifact types.
    const HTML_MIME_PREFIX = 'text/html';
    const PENDING = new Map();
    let requestId = 0;
    let lastSize = { width: 0, height: 0 };

    function post(message) {
      window.parent.postMessage(message, '*');
    }

    function request(method, params) {
      return new Promise((resolve, reject) => {
        const id = ++requestId;
        PENDING.set(id, { resolve, reject });
        post({ jsonrpc: '2.0', id, method, params });
      });
    }

    function publishSize() {
      const doc = document.documentElement;
      const width = Math.ceil(Math.max(doc.scrollWidth, document.body.scrollWidth));
      const height = Math.ceil(Math.max(doc.scrollHeight, document.body.scrollHeight));
      if (!height || (width === lastSize.width && height === lastSize.height)) {
        return;
      }
      lastSize = { width, height };
      post({
        jsonrpc: '2.0',
        method: MCP_UI.SIZE_CHANGED,
        params: { width, height }
      });
    }

    // The tool result carries the rendered artifact as an embedded ui://
    // resource — the zero-round-trip path.
    function embeddedHtml(result) {
      for (const block of (result && result.content) || []) {
        const resource = block && block.resource;
        const mime = resource && (resource.mimeType || resource.mime_type);
        if (resource && typeof resource.text === 'string' && mime && mime.startsWith(HTML_MIME_PREFIX)) {
          return resource.text;
        }
      }
      return null;
    }

    // Spec-sanctioned fallback: read the artifact's own ui:// resource from
    // the server when the host does not forward embedded content blocks.
    async function fetchedHtml(result) {
      const meta = result && result._meta;
      const uri = meta && meta['io.systemprompt/ui-resource-uri'];
      if (!uri) {
        return null;
      }
      const read = await request('resources/read', { uri });
      for (const contents of (read && read.contents) || []) {
        if (typeof contents.text === 'string') {
          return contents.text;
        }
      }
      return null;
    }

    function mount(html) {
      const root = document.getElementById('root');
      root.innerHTML = '<iframe id="frame" sandbox="allow-scripts allow-popups"></iframe>';
      document.getElementById('frame').srcdoc = html;
    }

    function showFallback(result) {
      const payload = (result && result.structuredContent) || result || {};
      const wrapper = document.createElement('div');
      wrapper.className = 'fallback';
      const pre = document.createElement('pre');
      pre.textContent = JSON.stringify(payload, null, 2);
      wrapper.appendChild(pre);
      const root = document.getElementById('root');
      root.innerHTML = '';
      root.appendChild(wrapper);
      publishSize();
    }

    function showError(message) {
      const div = document.createElement('div');
      div.className = 'error';
      div.textContent = message;
      const root = document.getElementById('root');
      root.innerHTML = '';
      root.appendChild(div);
      publishSize();
    }

    async function render(result) {
      const inline = embeddedHtml(result);
      if (inline) {
        mount(inline);
        return;
      }
      try {
        const fetched = await fetchedHtml(result);
        if (fetched) {
          mount(fetched);
          return;
        }
      } catch (err) {
        console.error('resources/read for artifact failed:', err);
      }
      showFallback(result);
    }

    window.addEventListener('message', (event) => {
      const data = event.data || {};
      try {
        if (data.id && PENDING.has(data.id)) {
          const { resolve, reject } = PENDING.get(data.id);
          PENDING.delete(data.id);
          if (data.error) {
            reject(new Error(data.error.message || 'Request failed'));
          } else {
            resolve(data.result);
          }
          return;
        }

        // The mounted artifact reports its own size; grow to fit and pass the
        // measurement up to the host.
        if (data.method === MCP_UI.SIZE_CHANGED && data.params) {
          const frame = document.getElementById('frame');
          if (frame && event.source === frame.contentWindow) {
            frame.style.height = data.params.height + 'px';
            publishSize();
          }
          return;
        }

        if (data.method === MCP_UI.TOOL_RESULT) {
          render(data.params);
        }
      } catch (err) {
        console.error('Artifact shell failed to render:', err);
        showError(err.message || 'Failed to render artifact');
      }
    });

    request(MCP_UI.INITIALIZE, {
      appInfo: { name: 'systemprompt-artifact-viewer', version: '1.0.0' },
      appCapabilities: {},
      protocolVersion: MCP_UI.PROTOCOL_VERSION
    }).then(() => {
      post({ jsonrpc: '2.0', method: MCP_UI.INITIALIZED, params: {} });
      publishSize();
    }).catch((err) => {
      console.error('MCP Apps initialize failed:', err);
    });
  </script>
</body>
</html>