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.
Your application
Trusted identity
- Logged-in user
- Organization or workspace
- Current permissions
Server to server
Secure exchange
- Server-only integration key
- Allowed-origin check
- 15-minute signed token
MCPFeedback
Embedded portal
- Verified feedback button
- Private request history
- Replies and reopening
Members see their own requests
Organization admins see their workspace
Other organizations are always blocked
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
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.
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.
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.
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.
// 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.
<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.
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.
| Access | Visible requests |
|---|---|
| member | Only requests submitted by that verified identity |
| organization_admin | All 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
| Method | Endpoint | Purpose |
|---|---|---|
| POST | /api/portal/v1/requests | Create verified feedback |
| GET | /api/portal/v1/requests | List personal or organization requests |
| GET | /api/portal/v1/requests/:id | Read safe request detail and conversation |
| POST | /api/portal/v1/requests/:id/comments | Add a customer reply |
| POST | /api/portal/v1/requests/:id/reopen | Reopen a resolved request |
Customer-facing statuses
Open
backlog, new, reopenReviewing
accepted, in_reviewIn Progress
in_progressResolved
resolved, closedDeclined
rejectedInternal notes, assignments, private fix notes, GitHub data, console logs, network logs, and operational metadata are never returned by portal APIs.