@mcp-shark/mcp-shark
Version:
Aggregate multiple Model Context Protocol (MCP) servers into a single unified interface with a powerful monitoring UI. Prov deep visibility into every request and response.
251 lines (244 loc) • 8.02 kB
JSX
import { colors, fonts } from '../theme';
import { useMcpPlayground } from './McpPlayground/useMcpPlayground';
import LoadingModal from './McpPlayground/LoadingModal';
import ToolsSection from './McpPlayground/ToolsSection';
import PromptsSection from './McpPlayground/PromptsSection';
import ResourcesSection from './McpPlayground/ResourcesSection';
function McpPlayground() {
const {
activeSection,
setActiveSection,
tools,
prompts,
resources,
loading,
error,
selectedTool,
setSelectedTool,
toolArgs,
setToolArgs,
toolResult,
selectedPrompt,
setSelectedPrompt,
promptArgs,
setPromptArgs,
promptResult,
selectedResource,
setSelectedResource,
resourceResult,
serverStatus,
showLoadingModal,
toolsLoading,
promptsLoading,
resourcesLoading,
toolsLoaded,
promptsLoaded,
resourcesLoaded,
loadTools,
loadPrompts,
loadResources,
handleCallTool,
handleGetPrompt,
handleReadResource,
availableServers,
selectedServer,
setSelectedServer,
} = useMcpPlayground();
return (
<>
<style>
{`
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
`}
</style>
<div
style={{
display: 'flex',
flexDirection: 'column',
height: '100%',
background: colors.bgPrimary,
padding: '20px',
gap: '16px',
position: 'relative',
}}
>
<LoadingModal show={showLoadingModal} />
{error && !error.includes(':') && (
<div
style={{
padding: '12px 16px',
background: colors.error,
color: colors.textInverse,
borderRadius: '6px',
fontSize: '13px',
fontFamily: fonts.body,
}}
>
Error: {error}
</div>
)}
<div
style={{
display: 'flex',
flexDirection: 'column',
gap: '12px',
}}
>
{availableServers.length > 0 && (
<div
style={{
display: 'flex',
flexDirection: 'column',
gap: '8px',
}}
>
<label
style={{
fontSize: '13px',
fontFamily: fonts.body,
color: colors.textSecondary,
fontWeight: '500',
}}
>
Server:
</label>
<div
style={{
display: 'flex',
flexWrap: 'wrap',
gap: '8px',
}}
>
{availableServers.map((server) => (
<button
key={server}
onClick={() => setSelectedServer(server)}
style={{
padding: '10px 18px',
background:
selectedServer === server ? colors.accentBlue : colors.bgSecondary,
border:
selectedServer === server
? `2px solid ${colors.accentBlue}`
: `1px solid ${colors.borderLight}`,
borderRadius: '8px',
color: selectedServer === server ? colors.textInverse : colors.textPrimary,
fontSize: '13px',
fontFamily: fonts.body,
fontWeight: selectedServer === server ? '600' : '500',
cursor: 'pointer',
transition: 'all 0.2s ease',
boxShadow:
selectedServer === server ? `0 2px 4px ${colors.shadowSm}` : 'none',
}}
onMouseEnter={(e) => {
if (selectedServer !== server) {
e.currentTarget.style.background = colors.bgHover;
e.currentTarget.style.borderColor = colors.borderMedium;
e.currentTarget.style.boxShadow = `0 2px 4px ${colors.shadowSm}`;
}
}}
onMouseLeave={(e) => {
if (selectedServer !== server) {
e.currentTarget.style.background = colors.bgSecondary;
e.currentTarget.style.borderColor = colors.borderLight;
e.currentTarget.style.boxShadow = 'none';
}
}}
>
{server}
</button>
))}
</div>
</div>
)}
<div
style={{
display: 'flex',
gap: '8px',
borderBottom: `1px solid ${colors.borderLight}`,
}}
>
{['tools', 'prompts', 'resources'].map((section) => (
<button
key={section}
onClick={() => setActiveSection(section)}
style={{
padding: '10px 18px',
background: activeSection === section ? colors.bgSecondary : 'transparent',
border: 'none',
borderBottom: `2px solid ${activeSection === section ? colors.accentBlue : 'transparent'}`,
color: activeSection === section ? colors.textPrimary : colors.textSecondary,
cursor: 'pointer',
fontSize: '13px',
fontFamily: fonts.body,
fontWeight: activeSection === section ? '500' : '400',
textTransform: 'capitalize',
borderRadius: '6px 6px 0 0',
transition: 'all 0.2s',
}}
>
{section}
</button>
))}
</div>
</div>
<div style={{ flex: 1, overflow: 'hidden', minHeight: 0 }}>
{activeSection === 'tools' && (
<ToolsSection
tools={tools}
selectedTool={selectedTool}
onSelectTool={setSelectedTool}
toolArgs={toolArgs}
onToolArgsChange={setToolArgs}
toolResult={toolResult}
onCallTool={handleCallTool}
loading={loading}
toolsLoading={toolsLoading}
toolsLoaded={toolsLoaded}
serverStatus={serverStatus}
error={error}
onRefresh={loadTools}
/>
)}
{activeSection === 'prompts' && (
<PromptsSection
prompts={prompts}
selectedPrompt={selectedPrompt}
onSelectPrompt={setSelectedPrompt}
promptArgs={promptArgs}
onPromptArgsChange={setPromptArgs}
promptResult={promptResult}
onGetPrompt={handleGetPrompt}
loading={loading}
promptsLoading={promptsLoading}
promptsLoaded={promptsLoaded}
serverStatus={serverStatus}
error={error}
onRefresh={loadPrompts}
/>
)}
{activeSection === 'resources' && (
<ResourcesSection
resources={resources}
selectedResource={selectedResource}
onSelectResource={setSelectedResource}
resourceResult={resourceResult}
onReadResource={handleReadResource}
loading={loading}
resourcesLoading={resourcesLoading}
resourcesLoaded={resourcesLoaded}
serverStatus={serverStatus}
error={error}
onRefresh={loadResources}
/>
)}
</div>
</div>
</>
);
}
export default McpPlayground;