39 | {Array(lineCount)
40 | .fill(null)
41 | .map((_, i) => (
42 |
{
45 | if (el) lines.current[i] = el;
46 | }}
47 | className={cn(
48 | "w-1.5 min-h-1.5 rounded-full transition-all duration-200 ease-out transform-gpu",
49 | active ? "bg-default-800 animate-pulse" : "bg-default-300",
50 | hover && "animate-hover"
51 | )}
52 | style={{
53 | animationDelay: `${i * 133}ms`,
54 | }}
55 | />
56 | ))}
57 |
58 | );
59 | }
60 |
--------------------------------------------------------------------------------
/src/features/server/__tests__/PromptCard.test.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { describe, it, expect, vi } from "vitest";
3 | import { render, screen, fireEvent } from "@testing-library/react";
4 | import { PromptCard } from "../../../components/Card/PromptCard";
5 |
6 | describe("PromptCard", () => {
7 | const mockPrompt = {
8 | name: "Test Prompt",
9 | description: "A test prompt",
10 | type: "test",
11 | };
12 |
13 | const defaultProps = {
14 | prompt: mockPrompt,
15 | onExecute: vi.fn(),
16 | onView: vi.fn(),
17 | isLoading: false,
18 | };
19 |
20 | it("renders prompt information correctly", () => {
21 | render(
);
22 | expect(screen.getByText("Test Prompt")).toBeInTheDocument();
23 | expect(screen.getByText("A test prompt")).toBeInTheDocument();
24 | expect(screen.getByText("Prompt")).toBeInTheDocument();
25 | });
26 |
27 | it("calls onExecute when execute button is clicked", () => {
28 | render(
);
29 | const executeButton = screen.getByTestId("prompt-execute-Test Prompt");
30 | fireEvent.click(executeButton);
31 | expect(defaultProps.onExecute).toHaveBeenCalled();
32 | });
33 |
34 | it("calls onView when view button is clicked", () => {
35 | render(
);
36 | const viewButton = screen.getByRole("button", { name: /View Prompt/i });
37 | fireEvent.click(viewButton);
38 | expect(defaultProps.onView).toHaveBeenCalled();
39 | });
40 |
41 | it("disables execute button when loading", () => {
42 | render(
);
43 | const executeButton = screen.getByTestId("prompt-execute-Test Prompt");
44 | expect(executeButton).toBeDisabled();
45 | });
46 | });
47 |
--------------------------------------------------------------------------------
/config/agent.config.example.json:
--------------------------------------------------------------------------------
1 | {
2 | "welcome-to-systemprompt": {
3 | "name": "Welcome to Systemprompt",
4 | "description": "The default example agent that introduces you to Systemprompt and helps you get started",
5 | "instruction": "You are the Welcome Agent for Systemprompt, designed to help users understand and get started with the Systemprompt platform. Your role is to be friendly, informative, and helpful in guiding users through their first steps.\n\n1. Initial Greeting:\n- Always start with a warm welcome to Systemprompt\n- Introduce yourself as the Welcome Agent\n- Explain that you're here to help them understand and use Systemprompt effectively\n\n2. Core Concepts:\n- Explain that Systemprompt is a platform for managing and executing system prompts\n- Describe how system prompts work as instructions for AI models\n- Highlight the importance of well-structured prompts for consistent AI behavior\n\n3. Key Features:\n- Outline the main features of Systemprompt:\n * Creating and editing system prompts\n * Managing multiple prompt configurations\n * Testing prompts with different models\n * Organizing prompts in collections\n\n4. Best Practices:\n- Share tips for creating effective system prompts\n- Explain the importance of clear, specific instructions\n- Suggest ways to test and iterate on prompts\n\n5. Getting Started:\n- Guide users through their first steps\n- Recommend starting with simple prompts\n- Explain how to use the interface and tools\n\n6. Support:\n- Offer to answer any questions about Systemprompt\n- Direct users to documentation and resources\n- Encourage experimentation and learning\n\nRemember: Keep your responses friendly, clear, and focused on helping users understand and get value from Systemprompt. Always be patient and supportive as users learn the platform."
6 | }
7 | }
--------------------------------------------------------------------------------
/src/features/multimodal-agent/__tests__/contexts/McpContext.test.tsx:
--------------------------------------------------------------------------------
1 | import { describe, it, expect, vi } from "vitest";
2 | import { render, screen } from "@testing-library/react";
3 | import { McpProvider } from "../../../../contexts/McpProvider";
4 | import { useContext } from "react";
5 | import { McpContext } from "../../../../contexts/McpContext";
6 | import React from "react";
7 |
8 | const TestComponent = () => {
9 | const context = useContext(McpContext);
10 | if (!context) return null;
11 | return (
12 |
13 |
Active Clients: {context.activeClients.join(", ")}
14 |
15 | Connection Status: {context.clients.testServer?.connectionStatus}
16 |
17 |
18 | );
19 | };
20 |
21 | // Mock the hooks used by McpProvider
22 | vi.mock("../../../../hooks/useMcpClient", () => ({
23 | useMcpClient: () => ({
24 | clients: {},
25 | activeClients: [],
26 | updateClientState: vi.fn(),
27 | setupClientNotifications: vi.fn(),
28 | }),
29 | }));
30 |
31 | vi.mock("../../../../hooks/useMcpConnection", () => ({
32 | useMcpConnection: () => ({
33 | connectServer: vi.fn(),
34 | disconnectServer: vi.fn(),
35 | }),
36 | }));
37 |
38 | describe("McpProvider", () => {
39 | it("renders children", () => {
40 | render(
41 |
42 | Test Child
43 |
44 | );
45 |
46 | expect(screen.getByText("Test Child")).toBeInTheDocument();
47 | });
48 |
49 | it("initializes with empty state", () => {
50 | render(
51 |
52 |
53 |
54 | );
55 |
56 | expect(screen.getByText(/^Active Clients:$/)).toBeInTheDocument();
57 | expect(screen.getByText(/^Connection Status:$/)).toBeInTheDocument();
58 | });
59 | });
60 |
--------------------------------------------------------------------------------
/proxy/dist/mcpProxy.js:
--------------------------------------------------------------------------------
1 | import { McpHandlers } from "./handlers/mcpHandlers.js";
2 | import { ConfigHandlers } from "./handlers/configHandlers.js";
3 | import { TransportHandlers } from "./handlers/transportHandlers.js";
4 | import { defaults } from "./config/defaults.js";
5 | export default function mcpProxy({ transportToClient, transportToServer, onerror, }) {
6 | let transportToClientClosed = false;
7 | let transportToServerClosed = false;
8 | transportToClient.onmessage = (message) => {
9 | transportToServer.send(message).catch(onerror);
10 | };
11 | transportToServer.onmessage = (message) => {
12 | transportToClient.send(message).catch(onerror);
13 | };
14 | transportToClient.onclose = () => {
15 | if (transportToServerClosed) {
16 | return;
17 | }
18 | transportToClientClosed = true;
19 | transportToServer.close().catch(onerror);
20 | };
21 | transportToServer.onclose = () => {
22 | if (transportToClientClosed) {
23 | return;
24 | }
25 | transportToServerClosed = true;
26 | transportToClient.close().catch(onerror);
27 | };
28 | transportToClient.onerror = onerror;
29 | transportToServer.onerror = onerror;
30 | }
31 | export class McpProxy {
32 | constructor(config) {
33 | // Ensure all required properties are initialized
34 | const initializedConfig = {
35 | mcpServers: config.mcpServers || {},
36 | available: config.available || {},
37 | defaults: config.defaults || defaults,
38 | };
39 | this.mcpHandlers = new McpHandlers(initializedConfig);
40 | this.configHandlers = new ConfigHandlers(initializedConfig);
41 | this.transportHandlers = new TransportHandlers(initializedConfig);
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/features/server/hooks/usePromptLogger.ts:
--------------------------------------------------------------------------------
1 | import { useLogStore } from "@/stores/log-store";
2 |
3 | type OperationType = "View Prompt" | "Execute Prompt";
4 |
5 | interface LogEntry {
6 | type: "prompt";
7 | operation: OperationType;
8 | status: "success" | "error";
9 | name: string;
10 | params?: Record
;
11 | result?: unknown;
12 | error?: string;
13 | }
14 |
15 | interface UsePromptLoggerReturn {
16 | log: (entry: Omit) => void;
17 | logSuccess: (
18 | operation: OperationType,
19 | name: string,
20 | params?: Record,
21 | result?: unknown
22 | ) => void;
23 | logError: (
24 | operation: OperationType,
25 | name: string,
26 | error: Error | string,
27 | params?: Record
28 | ) => void;
29 | }
30 |
31 | export function usePromptLogger(): UsePromptLoggerReturn {
32 | const { addLog } = useLogStore();
33 |
34 | const log = (entry: Omit) => {
35 | addLog({
36 | type: "prompt",
37 | ...entry,
38 | });
39 | };
40 |
41 | const logSuccess = (
42 | operation: OperationType,
43 | name: string,
44 | params?: Record,
45 | result?: unknown
46 | ) => {
47 | log({
48 | operation,
49 | status: "success",
50 | name,
51 | params,
52 | result,
53 | });
54 | };
55 |
56 | const logError = (
57 | operation: OperationType,
58 | name: string,
59 | error: Error | string,
60 | params?: Record
61 | ) => {
62 | log({
63 | operation,
64 | status: "error",
65 | name,
66 | params,
67 | error: error instanceof Error ? error.message : error,
68 | });
69 | };
70 |
71 | return {
72 | log,
73 | logSuccess,
74 | logError,
75 | };
76 | }
77 |
--------------------------------------------------------------------------------
/src/features/server/components/sections/ServerHeader.tsx:
--------------------------------------------------------------------------------
1 | import { Button } from "@nextui-org/react";
2 | import { Icon } from "@iconify/react";
3 |
4 | interface ServerHeaderProps {
5 | serverName: string;
6 | serverId: string;
7 | isConnected: boolean;
8 | isConnecting: boolean;
9 | hasError: boolean;
10 | onConnect: () => void;
11 | onDisconnect: () => void;
12 | icon?: string;
13 | }
14 |
15 | export function ServerHeader({
16 | serverName,
17 | serverId,
18 | isConnected,
19 | isConnecting,
20 | icon = "solar:server-bold-duotone",
21 | onConnect,
22 | onDisconnect,
23 | }: ServerHeaderProps) {
24 | return (
25 |
26 |
27 |
28 |
29 |
{serverName}
30 |
{serverId}
31 |
32 |
33 |
34 | {isConnected ? (
35 | }
41 | >
42 | Disconnect
43 |
44 | ) : (
45 |
53 | }
54 | >
55 | Connect
56 |
57 | )}
58 |
59 |
60 | );
61 | }
62 |
--------------------------------------------------------------------------------
/proxy/src/types/server.types.ts:
--------------------------------------------------------------------------------
1 | import { McpModuleMetadata } from "./index.js";
2 | import { SystempromptModule } from "./systemprompt.js";
3 |
4 | export interface ServerMetadata {
5 | icon?: string;
6 | color?: string;
7 | description?: string;
8 | serverType?: "core" | "custom";
9 | name?: string;
10 | }
11 |
12 | export interface ServerConfig {
13 | id: string;
14 | command: string;
15 | args: string[];
16 | env?: string[];
17 | metadata?: ServerMetadata;
18 | }
19 |
20 | export interface ServerDefaults {
21 | serverTypes: {
22 | stdio: ServerMetadata;
23 | sse: ServerMetadata;
24 | };
25 | unconnected: ServerMetadata;
26 | }
27 |
28 | export const DEFAULT_SERVER_CONFIG: ServerDefaults = {
29 | serverTypes: {
30 | stdio: {
31 | icon: "solar:server-minimalistic-bold-duotone",
32 | color: "primary",
33 | description: "Local stdio-based MCP server",
34 | },
35 | sse: {
36 | icon: "solar:server-square-cloud-bold-duotone",
37 | color: "primary",
38 | description: "Remote SSE-based MCP server",
39 | },
40 | },
41 | unconnected: {
42 | icon: "solar:server-broken",
43 | color: "secondary",
44 | description: "Remote MCP server (not connected)",
45 | },
46 | };
47 |
48 | export interface ServerResponse {
49 | mcpServers: Record;
50 | customServers: Record;
51 | available: Record;
52 | defaults: ServerDefaults;
53 | }
54 |
55 | export interface McpServer extends ServerConfig {
56 | id: string;
57 | type: string;
58 | title: string;
59 | description: string;
60 | metadata: ServerMetadata & Partial;
61 | }
62 |
63 | export interface McpDataInterface {
64 | mcpServers: Record;
65 | defaults: ServerDefaults;
66 | available: Record;
67 | }
68 |
--------------------------------------------------------------------------------
/src/components/shared/Button/index.tsx:
--------------------------------------------------------------------------------
1 | import { Button as NextUIButton, ButtonProps } from "@nextui-org/react";
2 |
3 | interface SharedButtonProps extends ButtonProps {
4 | isLoading?: boolean;
5 | }
6 |
7 | export function Button({
8 | children,
9 | isLoading,
10 | disabled,
11 | className = "",
12 | ...props
13 | }: SharedButtonProps) {
14 | const isDisabled = disabled || isLoading;
15 |
16 | return (
17 |
25 | {isLoading ? (
26 |
27 |
34 |
42 |
47 |
48 |
Loading
49 |
50 | ) : null}
51 |
56 | {children}
57 |
58 |
59 | );
60 | }
61 |
--------------------------------------------------------------------------------
/src/components/sidebar/SidebarItem.tsx:
--------------------------------------------------------------------------------
1 | import { Button, Tooltip, cn } from "@nextui-org/react";
2 | import { Icon } from "@iconify/react";
3 | import { SidebarItem as SidebarItemType } from "./types";
4 |
5 | const colorMap = {
6 | success: "#17c964",
7 | warning: "#f5a524",
8 | primary: "#f6933c",
9 | secondary: "#7e868c",
10 | } as const;
11 |
12 | interface SidebarItemProps {
13 | item: SidebarItemType;
14 | isCompact?: boolean;
15 | isSelected?: boolean;
16 | onPress?: () => void;
17 | }
18 |
19 | export function SidebarItem({
20 | item,
21 | isCompact = false,
22 | isSelected = false,
23 | onPress,
24 | }: SidebarItemProps) {
25 | if (!item.icon) {
26 | console.warn("No icon provided for item:", item);
27 | }
28 |
29 | const buttonContent = (
30 | {
37 | onPress?.();
38 | }}
39 | variant="light"
40 | aria-label={isCompact && item.description ? item.description : undefined}
41 | >
42 | {item.icon && (
43 |
51 | )}
52 | {!isCompact && {item.label} }
53 |
54 | );
55 |
56 | if (isCompact && item.description) {
57 | return (
58 |
59 | {buttonContent}
60 |
61 | );
62 | }
63 |
64 | return buttonContent;
65 | }
66 |
--------------------------------------------------------------------------------
/src/components/Button/BaseButton.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Button as NextUIButton,
3 | ButtonProps as NextUIButtonProps,
4 | } from "@nextui-org/react";
5 | import { Icon } from "@iconify/react";
6 | import { forwardRef } from "react";
7 |
8 | export interface BaseButtonProps extends NextUIButtonProps {
9 | icon?: string;
10 | iconClassName?: string;
11 | label: string;
12 | loadingLabel?: string;
13 | loading?: boolean;
14 | iconPosition?: "start" | "end";
15 | className?: string;
16 | color?:
17 | | "default"
18 | | "primary"
19 | | "secondary"
20 | | "success"
21 | | "warning"
22 | | "danger";
23 | variant?:
24 | | "solid"
25 | | "bordered"
26 | | "light"
27 | | "flat"
28 | | "faded"
29 | | "shadow"
30 | | "ghost";
31 | }
32 |
33 | export const BaseButton = forwardRef(
34 | (
35 | {
36 | icon,
37 | iconClassName = "",
38 | label,
39 | loadingLabel,
40 | loading = false,
41 | iconPosition = "start",
42 | className = "",
43 | ...props
44 | },
45 | ref
46 | ) => {
47 | const displayLabel = loading && loadingLabel ? loadingLabel : label;
48 | const iconContent = icon && (
49 |
50 | );
51 |
52 | return (
53 |
65 | {displayLabel}
66 |
67 | );
68 | }
69 | );
70 |
71 | BaseButton.displayName = "BaseButton";
72 |
--------------------------------------------------------------------------------
/docs/layout-language-models-guide.md:
--------------------------------------------------------------------------------
1 | # Layout Component
2 |
3 | ## Overview
4 |
5 | The Layout component serves as the main structural wrapper for pages and content sections in the application. It provides consistent spacing, padding, and structural organization across different views.
6 |
7 | ## Available Variants
8 |
9 | - Default Layout: Standard page layout with header and footer
10 | - Minimal Layout: Simplified version without header/footer for specific use cases
11 | - Full-width Layout: Extends to screen edges without side padding
12 |
13 | ## Props and Configuration
14 |
15 | | Prop | Type | Default | Description |
16 | | --------- | -------------------------------------- | --------- | ---------------------------------------- |
17 | | children | ReactNode | required | Content to be rendered within the layout |
18 | | variant | 'default' \| 'minimal' \| 'full-width' | 'default' | Layout style variant |
19 | | className | string | '' | Additional CSS classes |
20 | | padding | boolean | true | Enable/disable default padding |
21 |
22 | ## Usage Examples
23 |
24 | ```jsx
25 | // Basic usage
26 |
27 |
28 |
29 |
30 | // Minimal variant
31 |
32 |
33 |
34 |
35 | // Full-width with custom class
36 |
37 |
38 |
39 | ```
40 |
41 | ## Testing Guidelines
42 |
43 | 1. Verify proper rendering of all layout variants
44 | 2. Test responsive behavior across different screen sizes
45 | 3. Validate proper spacing and padding application
46 | 4. Check accessibility compliance
47 | 5. Ensure proper propagation of className prop
48 |
--------------------------------------------------------------------------------
/src/components/Card/StatusCard.tsx:
--------------------------------------------------------------------------------
1 | import { Card } from "@nextui-org/react";
2 | import { Icon } from "@iconify/react";
3 |
4 | export type StatusType = "success" | "warning" | "danger" | "default";
5 |
6 | export interface StatusCardProps {
7 | status?: StatusType;
8 | title: string;
9 | description?: string;
10 | icon?: string;
11 | iconClassName?: string;
12 | className?: string;
13 | }
14 |
15 | const statusConfig: Record =
16 | {
17 | success: { bgColor: "bg-success-50", textColor: "text-success" },
18 | warning: { bgColor: "bg-warning-50", textColor: "text-warning" },
19 | danger: { bgColor: "bg-danger-50", textColor: "text-danger" },
20 | default: { bgColor: "bg-default-50", textColor: "text-default-600" },
21 | };
22 |
23 | /**
24 | * StatusCard displays status information with consistent styling
25 | * @component
26 | * @example
27 | * ```tsx
28 | *
34 | * ```
35 | */
36 | export function StatusCard({
37 | status = "default",
38 | title,
39 | description,
40 | icon,
41 | iconClassName,
42 | className = "",
43 | }: StatusCardProps) {
44 | const { bgColor, textColor } = statusConfig[status];
45 |
46 | return (
47 |
48 |
49 | {icon && (
50 |
54 | )}
55 |
56 |
{title}
57 | {description &&
{description}
}
58 |
59 |
60 |
61 | );
62 | }
63 |
--------------------------------------------------------------------------------
/src/components/Modal/hooks/useSchemaForm.ts:
--------------------------------------------------------------------------------
1 | import { useCallback, useMemo, useState } from "react";
2 | import { JSONSchema7 } from "json-schema";
3 | import {
4 | getInitialValues,
5 | getValueAtPath,
6 | setValueAtPath,
7 | } from "../utils/form-state";
8 | import { ValidationError, validateSchema } from "../utils/schema-utils";
9 |
10 | export interface UseSchemaFormProps {
11 | schema: JSONSchema7;
12 | initialValues?: Record;
13 | }
14 |
15 | export interface UseSchemaFormResult {
16 | values: Record;
17 | errors: ValidationError[];
18 | getFieldValue: (path: string[]) => unknown;
19 | setFieldValue: (path: string[], value: unknown) => void;
20 | setValues: (values: Record) => void;
21 | isValid: boolean;
22 | }
23 |
24 | export function useSchemaForm({
25 | schema,
26 | initialValues,
27 | }: UseSchemaFormProps): UseSchemaFormResult {
28 | // Initialize form state with either provided values or defaults from schema
29 | const [values, setValues] = useState>(() => ({
30 | ...getInitialValues(schema),
31 | ...initialValues,
32 | }));
33 |
34 | // Memoize validation errors
35 | const errors = useMemo(
36 | () => validateSchema(schema, values),
37 | [schema, values]
38 | );
39 |
40 | // Memoize field value getter
41 | const getFieldValue = useCallback(
42 | (path: string[]) => getValueAtPath(values, path),
43 | [values]
44 | );
45 |
46 | // Memoize field value setter
47 | const setFieldValue = useCallback((path: string[], value: unknown) => {
48 | setValues((prev) => setValueAtPath(prev, path, value));
49 | }, []);
50 |
51 | // Memoize form validity
52 | const isValid = useMemo(() => errors.length === 0, [errors]);
53 |
54 | return {
55 | values,
56 | errors,
57 | getFieldValue,
58 | setFieldValue,
59 | setValues,
60 | isValid,
61 | };
62 | }
63 |
--------------------------------------------------------------------------------
/docs/modal-language-models-guide.md:
--------------------------------------------------------------------------------
1 | # Modal Component
2 |
3 | ## Overview
4 |
5 | The Modal component provides a reusable overlay dialog that can be used to display content on top of the main application. It handles accessibility, keyboard interactions, and backdrop clicks.
6 |
7 | ## Available Variants
8 |
9 | - Standard Modal
10 | - Full Screen Modal
11 | - Side Panel Modal
12 |
13 | ## Props and Configuration
14 |
15 | | Prop | Type | Default | Description |
16 | | -------- | ------------------------------ | -------- | ------------------------------ |
17 | | isOpen | boolean | false | Controls modal visibility |
18 | | onClose | function | required | Callback when modal closes |
19 | | children | ReactNode | required | Content to render inside modal |
20 | | title | string | '' | Modal header title |
21 | | size | 'sm' \| 'md' \| 'lg' \| 'full' | 'md' | Controls modal size |
22 | | position | 'center' \| 'right' | 'center' | Modal position on screen |
23 |
24 | ## Usage Examples
25 |
26 | ```jsx
27 | // Basic usage
28 |
29 | Title
30 | Content goes here
31 |
32 | Close
33 |
34 |
35 |
36 | // Full screen modal
37 |
38 | {/* Modal content */}
39 |
40 | ```
41 |
42 | ## Testing Guidelines
43 |
44 | 1. Test modal open/close functionality
45 | 2. Verify backdrop click behavior
46 | 3. Test keyboard interactions (Esc key)
47 | 4. Check accessibility features:
48 | - Focus trap inside modal
49 | - ARIA attributes
50 | - Screen reader compatibility
51 | 5. Test different sizes and positions
52 |
--------------------------------------------------------------------------------
/src/providers/gemini/utils/validation.ts:
--------------------------------------------------------------------------------
1 | import Ajv from "ajv";
2 |
3 | const ajv = new Ajv({ allErrors: true });
4 |
5 | export interface ValidationResult {
6 | data?: unknown;
7 | error?: string;
8 | }
9 |
10 | /**
11 | * Extracts JSON from a text response, handling various formats
12 | */
13 | function extractJson(text: string): string {
14 | // Try to find JSON content between markers
15 | const jsonMatch = text.match(/\{[\s\S]*\}/);
16 | if (!jsonMatch) {
17 | throw new Error("No JSON object found in response");
18 | }
19 |
20 | // Return the first complete JSON object found
21 | return jsonMatch[0];
22 | }
23 |
24 | /**
25 | * Validates JSON data against a schema using AJV
26 | */
27 | export function validateAgainstSchema(
28 | data: unknown,
29 | schema: Record
30 | ): ValidationResult {
31 | const validate = ajv.compile(schema);
32 |
33 | if (validate(data)) {
34 | return { data };
35 | }
36 |
37 | const errors = validate.errors
38 | ?.map((error) => {
39 | const path = error.dataPath || error.schemaPath || "";
40 | const msg = error.message || "Invalid";
41 | return `${path} ${msg}`;
42 | })
43 | .join("; ");
44 |
45 | return { error: `JSON validation failed: ${errors}` };
46 | }
47 |
48 | /**
49 | * Attempts to parse and validate JSON response against a schema
50 | */
51 | export function parseAndValidateJson(
52 | text: string,
53 | schema: Record
54 | ): ValidationResult {
55 | try {
56 | // First try to extract JSON content from the response
57 | const jsonContent = extractJson(text);
58 |
59 | // Then parse the extracted JSON
60 | const parsed = JSON.parse(jsonContent);
61 | return validateAgainstSchema(parsed, schema);
62 | } catch (error) {
63 | return {
64 | error: `Failed to parse JSON: ${
65 | error instanceof Error ? error.message : "Unknown error"
66 | }`,
67 | };
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/scripts/google-auth/setup-google-env.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | import fs from "fs";
3 | import path from "path";
4 |
5 | // Get file paths from command line arguments or use defaults
6 | const credentialsPath =
7 | process.argv[2] || "credentials/google-credentials.json";
8 | const tokenPath = process.argv[3] || "credentials/google-token.json";
9 |
10 | try {
11 | // Read and encode the credentials file
12 | const credentials = fs.readFileSync(credentialsPath, "utf8");
13 | const credentialsBase64 = Buffer.from(credentials).toString("base64");
14 |
15 | // Read and encode the token file
16 | const token = fs.readFileSync(tokenPath, "utf8");
17 | const tokenBase64 = Buffer.from(token).toString("base64");
18 |
19 | // Prepare the .env content
20 | let envContent = "";
21 |
22 | // Read existing .env if it exists
23 | if (fs.existsSync(".env")) {
24 | envContent = fs.readFileSync(".env", "utf8");
25 | // Remove any existing Google credentials
26 | envContent = envContent
27 | .split("\n")
28 | .filter(
29 | (line) =>
30 | !line.startsWith("VITE_GOOGLE_CREDENTIALS=") &&
31 | !line.startsWith("VITE_GOOGLE_TOKEN=")
32 | )
33 | .join("\n");
34 | if (envContent && !envContent.endsWith("\n")) {
35 | envContent += "\n";
36 | }
37 | }
38 |
39 | // Add the new credentials
40 | envContent += `VITE_GOOGLE_CREDENTIALS=${credentialsBase64}\n`;
41 | envContent += `VITE_GOOGLE_TOKEN=${tokenBase64}\n`;
42 |
43 | // Write to .env file
44 | fs.writeFileSync(".env", envContent);
45 |
46 | console.log("Successfully wrote Google credentials and token to .env");
47 | console.log(`Credentials file used: ${path.resolve(credentialsPath)}`);
48 | console.log(`Token file used: ${path.resolve(tokenPath)}`);
49 | } catch (error) {
50 | console.error("Error:", error.message);
51 | console.log(
52 | "Default paths: credentials/google-credentials.json and credentials/google-token.json"
53 | );
54 | process.exit(1);
55 | }
56 |
--------------------------------------------------------------------------------
/src/types/systemprompt.d.ts:
--------------------------------------------------------------------------------
1 | export interface SystempromptBlock {
2 | id: string;
3 | type: string;
4 | content: string;
5 | metadata: Metadata;
6 | prefix: string;
7 | _link: string;
8 | }
9 |
10 | export interface SystempromptAgent {
11 | id: string;
12 | type: string;
13 | content: string;
14 | metadata: Metadata;
15 | _link: string;
16 | }
17 |
18 | type Metadata = {
19 | title: string;
20 | description: string;
21 | created: string;
22 | updated: string;
23 | version: number;
24 | status: string;
25 | tag: string[];
26 | };
27 |
28 | export interface SystempromptPrompt {
29 | id?: string;
30 | instruction?: {
31 | static: string;
32 | dynamic: string;
33 | state: string;
34 | };
35 | input?: {
36 | name: string;
37 | description: string;
38 | schema: {
39 | type: string;
40 | required?: string[];
41 | properties?: Record;
42 | description?: string;
43 | additionalProperties?: boolean;
44 | };
45 | type: string[];
46 | reference: unknown[];
47 | };
48 | output?: {
49 | name: string;
50 | description: string;
51 | schema: {
52 | type: string;
53 | required?: string[];
54 | properties?: Record;
55 | description?: string;
56 | additionalProperties?: boolean;
57 | };
58 | };
59 | metadata: Metadata;
60 | _meta?: unknown[];
61 | }
62 |
63 | export interface SystempromptModule {
64 | id: string;
65 | type: string;
66 | title: string;
67 | description: string;
68 | environment_variables: string[];
69 | github_link: string;
70 | npm_link: string;
71 | icon: string;
72 | metadata: ServerMetadata;
73 | block: SystempromptBlock[];
74 | prompt: SystempromptPrompt[];
75 | agent: SystempromptAgent[];
76 | _link: string;
77 | }
78 |
79 | export interface SystempromptUser {
80 | user: {
81 | name: string;
82 | email: string;
83 | roles: string[];
84 | };
85 | billing: null;
86 | api_key: string;
87 | }
88 |
--------------------------------------------------------------------------------
/proxy/src/types/systemprompt.d.ts:
--------------------------------------------------------------------------------
1 | export interface SystempromptBlock {
2 | id: string;
3 | type: string;
4 | content: string;
5 | metadata: Metadata;
6 | prefix: string;
7 | _link: string;
8 | }
9 |
10 | export interface SystempromptAgent {
11 | id: string;
12 | type: string;
13 | content: string;
14 | metadata: Metadata;
15 | _link: string;
16 | }
17 |
18 | type Metadata = {
19 | title: string;
20 | description: string;
21 | created: string;
22 | updated: string;
23 | version: number;
24 | status: string;
25 | tag: string[];
26 | };
27 |
28 | export interface SystempromptPrompt {
29 | id?: string;
30 | instruction?: {
31 | static: string;
32 | dynamic: string;
33 | state: string;
34 | };
35 | input?: {
36 | name: string;
37 | description: string;
38 | schema: {
39 | type: string;
40 | required?: string[];
41 | properties?: Record;
42 | description?: string;
43 | additionalProperties?: boolean;
44 | };
45 | type: string[];
46 | reference: unknown[];
47 | };
48 | output?: {
49 | name: string;
50 | description: string;
51 | schema: {
52 | type: string;
53 | required?: string[];
54 | properties?: Record;
55 | description?: string;
56 | additionalProperties?: boolean;
57 | };
58 | };
59 | metadata: Metadata;
60 | _meta?: unknown[];
61 | }
62 |
63 | export interface SystempromptModule {
64 | id: string;
65 | type: string;
66 | title: string;
67 | description: string;
68 | environment_variables: string[];
69 | github_link: string;
70 | npm_link: string;
71 | icon: string;
72 | metadata: ServerMetadata;
73 | block: SystempromptBlock[];
74 | prompt: SystempromptPrompt[];
75 | agent: SystempromptAgent[];
76 | _link: string;
77 | }
78 |
79 | export interface SystempromptUser {
80 | user: {
81 | name: string;
82 | email: string;
83 | roles: string[];
84 | };
85 | billing: null;
86 | api_key: string;
87 | }
88 |
--------------------------------------------------------------------------------
/proxy/dist/index.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | import { config } from "dotenv";
3 | import { EventSource } from "eventsource";
4 | import { parseArgs } from "node:util";
5 | import chalk from "chalk";
6 | import { ProxyServer } from "./server.js";
7 | // Load environment variables from .env file
8 | config();
9 | globalThis.EventSource = EventSource;
10 | // Parse command line arguments
11 | const { values } = parseArgs({
12 | args: process.argv.slice(2),
13 | options: {
14 | port: { type: "string", default: "3000" },
15 | },
16 | });
17 | // Print banner
18 | function printBanner() {
19 | const version = "v0.3.13"; // TODO: Get this from package.json
20 | console.log(chalk.cyan("\n┌" + "─".repeat(60) + "┐"));
21 | console.log(chalk.cyan("│") +
22 | " ".repeat(15) +
23 | chalk.bold("Systemprompt MCP Server") +
24 | " ".repeat(15) +
25 | chalk.dim(version) +
26 | " ".repeat(3) +
27 | chalk.cyan("│"));
28 | console.log(chalk.cyan("└" + "─".repeat(60) + "┘\n"));
29 | }
30 | // Start server
31 | export async function main() {
32 | printBanner();
33 | try {
34 | const server = await ProxyServer.create();
35 | await server.startServer(parseInt(values.port));
36 | }
37 | catch (error) {
38 | console.error("\n" +
39 | chalk.red("╔═ Error ═══════════════════════════════════════════════════════════╗"));
40 | console.error(chalk.red("║ ") +
41 | chalk.yellow("Failed to start server:") +
42 | " ".repeat(39) +
43 | chalk.red("║"));
44 | console.error(chalk.red("║ ") +
45 | chalk.white(error.message) +
46 | " ".repeat(Math.max(0, 57 - error.message.length)) +
47 | chalk.red("║"));
48 | console.error(chalk.red("╚════════════════════════════════════════════════════════════════════╝\n"));
49 | process.exit(1);
50 | }
51 | }
52 | const isMainModule = process.argv[1] &&
53 | import.meta.url.endsWith(process.argv[1].replace(/\\/g, "/"));
54 | if (isMainModule) {
55 | main();
56 | }
57 |
--------------------------------------------------------------------------------
/src/components/Card/AccordionCard.tsx:
--------------------------------------------------------------------------------
1 | import { ReactNode } from "react";
2 | import { Accordion, AccordionItem, Card, CardBody } from "@nextui-org/react";
3 |
4 | interface AccordionCardProps {
5 | /** Content to show in the accordion header */
6 | header: ReactNode;
7 | /** Content to show in the accordion header when collapsed (optional) */
8 | collapsedContent?: ReactNode;
9 | /** Whether the accordion is expanded by default */
10 | defaultExpanded?: boolean;
11 | /** The main content of the card */
12 | children: ReactNode;
13 | /** Additional class names */
14 | className?: string;
15 | }
16 |
17 | /**
18 | * A card component that combines Card with an accordion for collapsible content.
19 | * The header is part of the accordion itself for a more integrated look.
20 | */
21 | export function AccordionCard({
22 | header,
23 | collapsedContent,
24 | defaultExpanded = false,
25 | children,
26 | className = "",
27 | }: AccordionCardProps) {
28 | return (
29 |
30 |
31 |
36 |
47 | {header}
48 | {collapsedContent && (
49 |
50 | {collapsedContent}
51 |
52 | )}
53 |
54 | }
55 | >
56 | {children}
57 |
58 |
59 |
60 |
61 | );
62 | }
63 |
--------------------------------------------------------------------------------
/src/components/Modal/schema-utils.ts:
--------------------------------------------------------------------------------
1 | import { OpenAPIV3 } from "openapi-client-axios";
2 |
3 | export interface ValidationError {
4 | path: string[];
5 | message: string;
6 | }
7 |
8 | export function validateSchema(
9 | schema: OpenAPIV3.SchemaObject,
10 | data: unknown,
11 | path: string[] = []
12 | ): ValidationError[] {
13 | const errors: ValidationError[] = [];
14 |
15 | if (schema.required && Array.isArray(schema.required)) {
16 | for (const requiredField of schema.required) {
17 | if (
18 | data === undefined ||
19 | data === null ||
20 | !isRecord(data) ||
21 | !(requiredField in data)
22 | ) {
23 | errors.push({
24 | path: [...path, requiredField],
25 | message: "This field is required",
26 | });
27 | }
28 | }
29 | }
30 |
31 | if (schema.oneOf) {
32 | const validSchemas = schema.oneOf.filter((subSchema) => {
33 | const subErrors = validateSchema(
34 | subSchema as OpenAPIV3.SchemaObject,
35 | data,
36 | path
37 | );
38 | return subErrors.length === 0;
39 | });
40 |
41 | if (validSchemas.length === 0) {
42 | errors.push({
43 | path,
44 | message: "Data does not match any of the allowed schemas",
45 | });
46 | }
47 | }
48 |
49 | if (schema.properties && isRecord(data)) {
50 | for (const [key, propSchema] of Object.entries(schema.properties)) {
51 | if (key in data) {
52 | const propErrors = validateSchema(
53 | propSchema as OpenAPIV3.SchemaObject,
54 | data[key],
55 | [...path, key]
56 | );
57 | errors.push(...propErrors);
58 | }
59 | }
60 | }
61 |
62 | if (schema.enum) {
63 | if (data !== undefined && !schema.enum.includes(data)) {
64 | errors.push({
65 | path,
66 | message: `Must be one of: ${schema.enum.join(", ")}`,
67 | });
68 | }
69 | }
70 |
71 | return errors;
72 | }
73 |
74 | function isRecord(value: unknown): value is Record