Sign inStart free
IntegrationsEmbedded Support Portal
Enterprise integration

Embedded Support Portal

Give signed-in customers a feedback button, private request history, conversations, status updates, and reopening without sending them to a separate support system.

How the integration works

Your application remains the identity provider. Its server sends stable user and organization IDs to MCPFeedback and receives a signed token that lasts 15 minutes. Browser code receives only that short-lived token—not the integration key.

What customers can do

Verified submission

Identity comes from your server, so the widget never asks an authenticated customer to type an email address.

Private history

Members see requests they submitted. Organization admins can also see requests submitted by other members of the current organization.

Closed-loop support

Customers can read public replies, add comments, and explicitly reopen a request after it has been resolved or closed.

Setup

1

Enable the enterprise entitlement

An MCPFeedback account administrator enables Embedded Portal for the customer account. Pricing and contract terms remain separate from the technical entitlement.

2

Configure the site integration

Open the site in MCPFeedback, choose Embedded Portal, add every allowed browser origin, enable the integration, and create an integration key.

3

Create a server-side exchange route

Your backend authenticates the current user, resolves their current organization and permissions, and exchanges that trusted identity for a short-lived portal token.

4

Mount the widget and portal

Pass only getSessionToken to browser code. The widget hides the email field and the portal loads the correct personal or organization view.

1. Exchange identity on your server

Use IDs from your own database. Never use an organization name or email address as the tenant boundary, and never put the integration key in a public environment variable.

app/api/support/session/route.ts
// Your server route — the integration key never reaches the browser
const response = await fetch(
  "https://mcpfeedback.com/api/portal/v1/session",
  {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.MCPFEEDBACK_PORTAL_SECRET}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      external_user_id: user.id,
      email: user.verifiedEmail,
      display_name: user.name,
      organization: {
        external_id: workspace.id,
        name: workspace.name,
      },
      access: canViewWorkspaceRequests
        ? "organization_admin"
        : "member",
      origin: request.headers.get("origin"),
    }),
  },
);

if (!response.ok) {
  return Response.json(
    { error: "Support is temporarily unavailable" },
    { status: 503 },
  );
}

return Response.json(await response.json());

2. Mount the portal

The portal bundle mounts into any element and uses Shadow DOM isolation, so it works in any stack — no build step and no package install required. The same token callback authenticates both the floating feedback widget and the full portal.

support.html
<div id="support-portal"></div>

<script>
  const getSessionToken = async () => {
    const response = await fetch("/api/support/session", { method: "POST" });
    if (!response.ok) throw new Error("Support unavailable");
    return (await response.json()).token;
  };

  window.MCPFeedbackSessionProvider = getSessionToken;

  const widget = document.createElement("script");
  widget.src = "https://mcpfeedback.com/widget/v1.js";
  widget.dataset.siteKey = "YOUR_SITE_KEY";
  widget.async = true;
  document.body.appendChild(widget);

  const portal = document.createElement("script");
  portal.src = "https://mcpfeedback.com/portal.js";
  portal.async = true;
  portal.addEventListener("load", () => {
    MCPFeedbackPortal.mount(document.querySelector("#support-portal"), {
      siteKey: "YOUR_SITE_KEY",
      getSessionToken,
    });
  });
  document.head.appendChild(portal);
</script>

React components

If you prefer React components over the script tag, the React SDK wraps the same bundle in <McpFeedback> and <McpFeedbackPortal>. Both retry once with a refreshed token after authentication expires.

npm package coming soon. @mcpfeedback/react is not on the npm registry yet. Copy packages/react-sdk/src into your app and import from there, or use the script tag above.

SupportExperience.tsx
import {
  McpFeedback,
  McpFeedbackPortal,
} from "@mcpfeedback/react";

const getSessionToken = async () => {
  const response = await fetch("/api/support/session", {
    method: "POST",
  });
  if (!response.ok) throw new Error("Support unavailable");
  return (await response.json()).token;
};

export function SupportExperience() {
  return (
    <>
      <McpFeedback
        siteKey="YOUR_SITE_KEY"
        getSessionToken={getSessionToken}
      />
      <McpFeedbackPortal
        siteKey="YOUR_SITE_KEY"
        getSessionToken={getSessionToken}
        className="min-h-[520px]"
      />
    </>
  );
}

Authorization rules

Site, organization, identity, membership, access level, expiry, audience, and browser origin come from the signed token and current integration state. Portal requests cannot override them with query parameters.

AccessVisible requests
memberOnly requests submitted by that verified identity
organization_adminAll linked requests in the current organization

Historical email-only feedback remains hidden until an MCPFeedback account owner deliberately links it to a known organization and identity. That action requires confirmation and is audited.

Portal API

MethodEndpoint
POST/api/portal/v1/requests
GET/api/portal/v1/requests
GET/api/portal/v1/requests/:id
POST/api/portal/v1/requests/:id/comments
POST/api/portal/v1/requests/:id/reopen

Customer-facing statuses

Open

backlog, new, reopen

Reviewing

accepted, in_review

In Progress

in_progress

Resolved

resolved, closed

Declined

rejected

Internal notes, assignments, private fix notes, GitHub data, console logs, network logs, and operational metadata are never returned by portal APIs.