@mexl/backstage-plugin-mcp-frontend
Version:
Enhanced Backstage frontend plugin for MCP (Model Context Protocol) entity visualization with JSON/Text toggle, syntax highlighting, and VSCode integration
241 lines (238 loc) • 10.6 kB
JavaScript
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
import { useState } from 'react';
import { makeStyles, Card, CardHeader, Button, CardContent, Typography, Box, Chip, Table, TableBody, TableRow, TableCell } from '@material-ui/core';
import { useEntity } from '@backstage/plugin-catalog-react';
const useStyles = makeStyles((theme) => ({
card: {
height: "100%"
},
section: {
marginBottom: theme.spacing(2)
},
codeBlock: {
backgroundColor: theme.palette.type === "dark" ? theme.palette.grey[800] : theme.palette.grey[900],
color: theme.palette.type === "dark" ? theme.palette.text.primary : theme.palette.common.white,
padding: theme.spacing(1.5),
borderRadius: theme.shape.borderRadius,
fontFamily: "monospace",
fontSize: "0.875rem",
marginBottom: theme.spacing(2),
overflowX: "auto"
},
jsonView: {
backgroundColor: theme.palette.type === "dark" ? theme.palette.grey[900] : theme.palette.grey[50],
border: `1px solid ${theme.palette.divider}`,
padding: theme.spacing(2),
borderRadius: theme.shape.borderRadius,
fontFamily: 'Monaco, Menlo, "Ubuntu Mono", Consolas, source-code-pro, monospace',
fontSize: "0.875rem",
overflowX: "auto",
whiteSpace: "pre-wrap",
lineHeight: 1.5
},
toggleButton: {
borderRadius: 20,
textTransform: "none",
minWidth: 80,
fontSize: "0.75rem",
padding: theme.spacing(0.5, 1.5)
},
jsonKey: {
color: theme.palette.type === "dark" ? "#79C0FF" : "#0969DA",
fontWeight: 600
},
jsonString: {
color: theme.palette.type === "dark" ? "#A5D6FF" : "#0A3069"
},
jsonBracket: {
color: theme.palette.type === "dark" ? "#F85149" : "#CF222E",
fontWeight: 600
},
table: {
marginBottom: theme.spacing(2)
},
envChip: {
margin: theme.spacing(0.25),
fontSize: "0.75rem"
}
}));
const formatJsonWithSyntaxHighlighting = (obj, classes) => {
const jsonString = JSON.stringify(obj, null, 2);
const highlighted = jsonString.replace(/"([^"]+)":/g, `<span class="${classes.jsonKey}">"$1":</span>`).replace(/: "([^"]+)"/g, `: <span class="${classes.jsonString}">"$1"</span>`).replace(/[{}]/g, `<span class="${classes.jsonBracket}">$&</span>`).replace(/[\[\]]/g, `<span class="${classes.jsonBracket}">$&</span>`);
return highlighted;
};
const MCPConfigurationCard = () => {
const { entity } = useEntity();
const classes = useStyles();
const [viewMode, setViewMode] = useState("text");
if (!entity || entity.kind !== "MCP") {
return null;
}
const spec = entity.spec;
const { configuration, authentication } = spec;
const hasConfiguration = configuration || authentication;
const toggleViewMode = () => {
setViewMode(viewMode === "text" ? "json" : "text");
};
const renderJsonView = () => /* @__PURE__ */ jsx(
Box,
{
className: classes.jsonView,
dangerouslySetInnerHTML: {
__html: formatJsonWithSyntaxHighlighting({ configuration, authentication }, classes)
}
}
);
const renderEnvironmentVariables = (env) => {
if (!env || env.length === 0) return null;
return /* @__PURE__ */ jsxs(Box, { className: classes.section, children: [
/* @__PURE__ */ jsx(Typography, { variant: "h6", gutterBottom: true, children: "Environment Variables" }),
/* @__PURE__ */ jsx(Box, { children: env.map((envVar, index) => {
const isSecret = envVar.valueFrom === "secret";
const value = envVar.value || (isSecret ? "(from secret)" : "(not set)");
return /* @__PURE__ */ jsx(
Chip,
{
label: `${envVar.name}=${value}`,
variant: isSecret ? "default" : "outlined",
color: isSecret ? "secondary" : "default",
className: classes.envChip,
size: "small"
},
index
);
}) })
] });
};
const renderAuthentication = (auth) => {
if (!auth) return null;
const renderApiKeyDetails = (apiKey) => {
if (!apiKey) return null;
return /* @__PURE__ */ jsxs(Fragment, { children: [
/* @__PURE__ */ jsxs(TableRow, { children: [
/* @__PURE__ */ jsx(TableCell, { children: /* @__PURE__ */ jsx("strong", { children: "Key Location" }) }),
/* @__PURE__ */ jsx(TableCell, { children: /* @__PURE__ */ jsx(
Chip,
{
label: apiKey.keyLocation,
size: "small",
color: apiKey.keyLocation === "header" ? "primary" : "default"
}
) })
] }),
/* @__PURE__ */ jsxs(TableRow, { children: [
/* @__PURE__ */ jsx(TableCell, { children: /* @__PURE__ */ jsx("strong", { children: "Key Name" }) }),
/* @__PURE__ */ jsx(TableCell, { children: /* @__PURE__ */ jsx("code", { children: apiKey.keyName }) })
] }),
/* @__PURE__ */ jsxs(TableRow, { children: [
/* @__PURE__ */ jsx(TableCell, { children: /* @__PURE__ */ jsx("strong", { children: "Key Source" }) }),
/* @__PURE__ */ jsx(TableCell, { children: /* @__PURE__ */ jsx(
Chip,
{
label: apiKey.keySource,
size: "small",
color: apiKey.keySource === "secret" ? "secondary" : "default"
}
) })
] })
] });
};
const renderOAuth2Details = (oauth2) => {
if (!oauth2) return null;
return /* @__PURE__ */ jsxs(Fragment, { children: [
/* @__PURE__ */ jsxs(TableRow, { children: [
/* @__PURE__ */ jsx(TableCell, { children: /* @__PURE__ */ jsx("strong", { children: "Provider" }) }),
/* @__PURE__ */ jsx(TableCell, { children: oauth2.provider })
] }),
/* @__PURE__ */ jsxs(TableRow, { children: [
/* @__PURE__ */ jsx(TableCell, { children: /* @__PURE__ */ jsx("strong", { children: "Client ID" }) }),
/* @__PURE__ */ jsx(TableCell, { children: /* @__PURE__ */ jsx("code", { children: oauth2.clientId }) })
] }),
oauth2.scopes && /* @__PURE__ */ jsxs(TableRow, { children: [
/* @__PURE__ */ jsx(TableCell, { children: /* @__PURE__ */ jsx("strong", { children: "Scopes" }) }),
/* @__PURE__ */ jsx(TableCell, { children: oauth2.scopes.map((scope, index) => /* @__PURE__ */ jsx(Chip, { label: scope, size: "small", className: classes.envChip }, index)) })
] })
] });
};
return /* @__PURE__ */ jsxs(Box, { className: classes.section, children: [
/* @__PURE__ */ jsx(Typography, { variant: "h6", gutterBottom: true, children: "Authentication" }),
/* @__PURE__ */ jsx(Table, { size: "small", className: classes.table, children: /* @__PURE__ */ jsxs(TableBody, { children: [
/* @__PURE__ */ jsxs(TableRow, { children: [
/* @__PURE__ */ jsx(TableCell, { children: /* @__PURE__ */ jsx("strong", { children: "Type" }) }),
/* @__PURE__ */ jsx(TableCell, { children: /* @__PURE__ */ jsx(
Chip,
{
label: auth.type,
size: "small",
color: auth.type === "api-key" ? "primary" : "default"
}
) })
] }),
auth.type === "api-key" && auth.apiKey && renderApiKeyDetails(auth.apiKey),
auth.type === "oauth2" && auth.oauth2 && renderOAuth2Details(auth.oauth2),
auth.type === "basic" && auth.basic && /* @__PURE__ */ jsxs(TableRow, { children: [
/* @__PURE__ */ jsx(TableCell, { children: /* @__PURE__ */ jsx("strong", { children: "Username" }) }),
/* @__PURE__ */ jsx(TableCell, { children: /* @__PURE__ */ jsx("code", { children: auth.basic.username }) })
] }),
auth.provider && !auth.oauth2 && /* @__PURE__ */ jsxs(TableRow, { children: [
/* @__PURE__ */ jsx(TableCell, { children: /* @__PURE__ */ jsx("strong", { children: "Provider" }) }),
/* @__PURE__ */ jsx(TableCell, { children: auth.provider })
] }),
auth.config && Object.entries(auth.config).map(([key, value]) => /* @__PURE__ */ jsxs(TableRow, { children: [
/* @__PURE__ */ jsx(TableCell, { children: /* @__PURE__ */ jsx("strong", { children: key }) }),
/* @__PURE__ */ jsx(TableCell, { children: String(value) })
] }, key))
] }) })
] });
};
const renderCommandInfo = () => {
if (!configuration) return null;
const { command, args, timeout, url } = configuration;
if (command && args) {
const fullCommand = `${command} ${args.join(" ")}`;
return /* @__PURE__ */ jsxs(Box, { className: classes.section, children: [
/* @__PURE__ */ jsx(Typography, { variant: "h6", gutterBottom: true, children: "\u{1F680} Start Command" }),
/* @__PURE__ */ jsx(Box, { className: classes.codeBlock, children: fullCommand })
] });
}
if (url) {
return /* @__PURE__ */ jsxs(Box, { className: classes.section, children: [
/* @__PURE__ */ jsx(Typography, { variant: "h6", gutterBottom: true, children: "\u{1F310} Server URL" }),
/* @__PURE__ */ jsx(Box, { className: classes.codeBlock, children: url })
] });
}
return null;
};
return /* @__PURE__ */ jsxs(Card, { className: classes.card, children: [
/* @__PURE__ */ jsx(
CardHeader,
{
title: "Configuration",
action: hasConfiguration && /* @__PURE__ */ jsx(
Button,
{
variant: viewMode === "json" ? "contained" : "outlined",
color: viewMode === "json" ? "primary" : "default",
size: "small",
onClick: toggleViewMode,
className: classes.toggleButton,
children: viewMode === "text" ? "JSON" : "Text"
}
)
}
),
/* @__PURE__ */ jsx(CardContent, { children: !hasConfiguration ? /* @__PURE__ */ jsx(Typography, { variant: "body2", color: "textSecondary", children: "No configuration details available." }) : /* @__PURE__ */ jsx(Fragment, { children: viewMode === "json" ? renderJsonView() : /* @__PURE__ */ jsxs(Fragment, { children: [
renderCommandInfo(),
configuration?.env && renderEnvironmentVariables(configuration.env),
authentication && renderAuthentication(authentication),
configuration?.timeout && /* @__PURE__ */ jsxs(Typography, { variant: "body2", color: "textSecondary", children: [
/* @__PURE__ */ jsx("strong", { children: "Timeout:" }),
" ",
configuration.timeout,
"ms"
] })
] }) }) })
] });
};
export { MCPConfigurationCard };
//# sourceMappingURL=MCPConfigurationCard.esm.js.map