├── .gitignore ├── .nano-staged.js ├── LICENSE ├── README.md ├── biome.json ├── kit ├── README.md ├── package.json ├── pnpm-lock.yaml ├── src │ ├── cli.ts │ ├── commands │ │ └── inspector.ts │ ├── index.ts │ └── tunnel │ │ ├── cloudflare.ts │ │ ├── index.ts │ │ └── ngrok.ts └── tsconfig.json ├── package.json ├── packages ├── inspector │ ├── .env.example │ ├── .gitignore │ ├── build.mjs │ ├── index.html │ ├── muppet.config.ts │ ├── package.json │ ├── public │ │ └── muppet.svg │ ├── src │ │ ├── cli.ts │ │ ├── client │ │ │ ├── App.tsx │ │ │ ├── components │ │ │ │ ├── AppProviders.tsx │ │ │ │ ├── CodeEditor.tsx │ │ │ │ ├── ConfigForm │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── useConfigForm.ts │ │ │ │ ├── ConfigFormFields │ │ │ │ │ ├── ConfigurationField.tsx │ │ │ │ │ ├── OptionalFields │ │ │ │ │ │ ├── SSE.tsx │ │ │ │ │ │ ├── STDIO.tsx │ │ │ │ │ │ └── index.tsx │ │ │ │ │ └── index.tsx │ │ │ │ ├── CopyButton.tsx │ │ │ │ ├── DuckField.tsx │ │ │ │ ├── DuckFieldNotFound.tsx │ │ │ │ ├── ErrorBoundaryRender.tsx │ │ │ │ ├── FieldErrorMessage.tsx │ │ │ │ ├── Hightlighter.tsx │ │ │ │ ├── Logo.tsx │ │ │ │ ├── LogoSmall.tsx │ │ │ │ ├── MakdownComponents.tsx │ │ │ │ ├── ModelField.tsx │ │ │ │ ├── NotFound.tsx │ │ │ │ ├── Sidebar │ │ │ │ │ ├── PingButton │ │ │ │ │ │ ├── OptionsMenu │ │ │ │ │ │ │ ├── Dialog │ │ │ │ │ │ │ │ ├── Form.tsx │ │ │ │ │ │ │ │ └── index.tsx │ │ │ │ │ │ │ └── index.tsx │ │ │ │ │ │ └── index.tsx │ │ │ │ │ ├── PreferencesDialog │ │ │ │ │ │ ├── ColorModeSetting.tsx │ │ │ │ │ │ ├── ItemCard.tsx │ │ │ │ │ │ ├── ThemeDialog │ │ │ │ │ │ │ ├── GenerateButtonGroup │ │ │ │ │ │ │ │ ├── GenerateDialog │ │ │ │ │ │ │ │ │ ├── Form.tsx │ │ │ │ │ │ │ │ │ └── index.tsx │ │ │ │ │ │ │ │ ├── index.tsx │ │ │ │ │ │ │ │ └── provider.tsx │ │ │ │ │ │ │ ├── index.tsx │ │ │ │ │ │ │ └── validation.ts │ │ │ │ │ │ ├── ThemeSettings │ │ │ │ │ │ │ ├── DeleteButton.tsx │ │ │ │ │ │ │ └── index.tsx │ │ │ │ │ │ ├── ToastSetting.tsx │ │ │ │ │ │ └── index.tsx │ │ │ │ │ ├── ServerInfo.tsx │ │ │ │ │ ├── SidebarItem.tsx │ │ │ │ │ ├── VersionBanner.tsx │ │ │ │ │ └── index.tsx │ │ │ │ ├── Wrapper.tsx │ │ │ │ ├── configurationsDialog │ │ │ │ │ ├── Tabs │ │ │ │ │ │ ├── Configurations.tsx │ │ │ │ │ │ ├── Connect │ │ │ │ │ │ │ ├── Fields.tsx │ │ │ │ │ │ │ ├── Footer.tsx │ │ │ │ │ │ │ └── index.tsx │ │ │ │ │ │ └── index.tsx │ │ │ │ │ └── index.tsx │ │ │ │ ├── highlightMatches.tsx │ │ │ │ ├── icons │ │ │ │ │ ├── Amazon.tsx │ │ │ │ │ ├── Anthropic.tsx │ │ │ │ │ ├── Cerebras.tsx │ │ │ │ │ ├── Cohere.tsx │ │ │ │ │ ├── DeepInfra.tsx │ │ │ │ │ ├── DeepSeek.tsx │ │ │ │ │ ├── Fireworks.tsx │ │ │ │ │ ├── Google.tsx │ │ │ │ │ ├── Groq.tsx │ │ │ │ │ ├── Inception.tsx │ │ │ │ │ ├── Mistral.tsx │ │ │ │ │ ├── OpenAI.tsx │ │ │ │ │ ├── Perplexity.tsx │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── type.ts │ │ │ │ │ └── xAI.tsx │ │ │ │ └── ui │ │ │ │ │ ├── accordion.tsx │ │ │ │ │ ├── badge.tsx │ │ │ │ │ ├── button.tsx │ │ │ │ │ ├── card.tsx │ │ │ │ │ ├── checkbox.tsx │ │ │ │ │ ├── combobox.tsx │ │ │ │ │ ├── command.tsx │ │ │ │ │ ├── dialog.tsx │ │ │ │ │ ├── dropdown-menu.tsx │ │ │ │ │ ├── input.tsx │ │ │ │ │ ├── label.tsx │ │ │ │ │ ├── popover.tsx │ │ │ │ │ ├── select.tsx │ │ │ │ │ ├── separator.tsx │ │ │ │ │ ├── sheet.tsx │ │ │ │ │ ├── sidebar.tsx │ │ │ │ │ ├── skeleton.tsx │ │ │ │ │ ├── spinner.tsx │ │ │ │ │ ├── table.tsx │ │ │ │ │ ├── tabs.tsx │ │ │ │ │ ├── textarea.tsx │ │ │ │ │ └── tooltip.tsx │ │ │ ├── hooks │ │ │ │ ├── use-completion-state.ts │ │ │ │ └── use-mobile.ts │ │ │ ├── index.css │ │ │ ├── lib │ │ │ │ ├── eventHandler.ts │ │ │ │ └── utils.ts │ │ │ ├── main.tsx │ │ │ ├── pages │ │ │ │ ├── Explorer │ │ │ │ │ ├── Render │ │ │ │ │ │ ├── CardDescriptionRender.tsx │ │ │ │ │ │ ├── Executor │ │ │ │ │ │ │ ├── AnalyseButtonGroup │ │ │ │ │ │ │ │ ├── AnalyseDialog │ │ │ │ │ │ │ │ │ ├── Form.tsx │ │ │ │ │ │ │ │ │ └── index.tsx │ │ │ │ │ │ │ │ ├── index.tsx │ │ │ │ │ │ │ │ └── useAnalyse.ts │ │ │ │ │ │ │ ├── AnalysePanel.tsx │ │ │ │ │ │ │ ├── FormPanel │ │ │ │ │ │ │ │ ├── DynamicResourceFieldRender.tsx │ │ │ │ │ │ │ │ ├── PromptFieldRender.tsx │ │ │ │ │ │ │ │ ├── ToolFieldsRender │ │ │ │ │ │ │ │ │ ├── fields │ │ │ │ │ │ │ │ │ │ ├── Array.tsx │ │ │ │ │ │ │ │ │ │ ├── Checkbox.tsx │ │ │ │ │ │ │ │ │ │ ├── Completion.tsx │ │ │ │ │ │ │ │ │ │ ├── FieldWrapper.tsx │ │ │ │ │ │ │ │ │ │ ├── Number.tsx │ │ │ │ │ │ │ │ │ │ ├── Object.tsx │ │ │ │ │ │ │ │ │ │ ├── Textarea.tsx │ │ │ │ │ │ │ │ │ │ ├── constants.ts │ │ │ │ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ │ │ │ │ └── types.ts │ │ │ │ │ │ │ │ │ └── index.tsx │ │ │ │ │ │ │ │ └── index.tsx │ │ │ │ │ │ │ ├── FormResetButton.tsx │ │ │ │ │ │ │ ├── FormWrapper.tsx │ │ │ │ │ │ │ ├── GenerateButtonGroup │ │ │ │ │ │ │ │ ├── GenerateDialog │ │ │ │ │ │ │ │ │ ├── Form.tsx │ │ │ │ │ │ │ │ │ └── index.tsx │ │ │ │ │ │ │ │ ├── index.tsx │ │ │ │ │ │ │ │ └── provider.tsx │ │ │ │ │ │ │ ├── JSONPanel.tsx │ │ │ │ │ │ │ ├── Reponse │ │ │ │ │ │ │ │ ├── Render │ │ │ │ │ │ │ │ │ ├── Audio.tsx │ │ │ │ │ │ │ │ │ ├── Image.tsx │ │ │ │ │ │ │ │ │ ├── Json.tsx │ │ │ │ │ │ │ │ │ └── index.tsx │ │ │ │ │ │ │ │ └── index.tsx │ │ │ │ │ │ │ ├── SchemaPanel.tsx │ │ │ │ │ │ │ ├── SendButton.tsx │ │ │ │ │ │ │ ├── constant.ts │ │ │ │ │ │ │ ├── index.tsx │ │ │ │ │ │ │ └── provider.tsx │ │ │ │ │ │ ├── GridComponent.tsx │ │ │ │ │ │ ├── MCPItem.tsx │ │ │ │ │ │ ├── RootsRender.tsx │ │ │ │ │ │ ├── Tabs.tsx │ │ │ │ │ │ └── index.tsx │ │ │ │ │ ├── index.tsx │ │ │ │ │ ├── providers │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── item.tsx │ │ │ │ │ │ └── tools.tsx │ │ │ │ │ └── types │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ └── items.ts │ │ │ │ ├── History │ │ │ │ │ ├── DownloadButton.tsx │ │ │ │ │ ├── Table │ │ │ │ │ │ ├── FilterMethod.tsx │ │ │ │ │ │ ├── TableDrawer │ │ │ │ │ │ │ ├── UpdateRequestDialog │ │ │ │ │ │ │ │ ├── Form.tsx │ │ │ │ │ │ │ │ └── index.tsx │ │ │ │ │ │ │ └── index.tsx │ │ │ │ │ │ └── index.tsx │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── providers │ │ │ │ │ │ ├── history.tsx │ │ │ │ │ │ └── index.ts │ │ │ │ ├── Home │ │ │ │ │ └── index.tsx │ │ │ │ ├── MCPScan │ │ │ │ │ ├── PageHeader.tsx │ │ │ │ │ ├── Render │ │ │ │ │ │ ├── StatusPanel.tsx │ │ │ │ │ │ ├── VulnerabilityItem.tsx │ │ │ │ │ │ └── index.tsx │ │ │ │ │ ├── ScanButton.tsx │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── providers │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ └── scan.tsx │ │ │ │ ├── OAuthCallback │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── utils.ts │ │ │ │ ├── Playground │ │ │ │ │ ├── Chat │ │ │ │ │ │ ├── Header.tsx │ │ │ │ │ │ ├── Thread │ │ │ │ │ │ │ ├── MarkdownText.tsx │ │ │ │ │ │ │ ├── TooltipIconButton.tsx │ │ │ │ │ │ │ └── index.tsx │ │ │ │ │ │ └── index.tsx │ │ │ │ │ ├── index.tsx │ │ │ │ │ ├── providers │ │ │ │ │ │ ├── chats.tsx │ │ │ │ │ │ └── index.ts │ │ │ │ │ └── type.ts │ │ │ │ ├── Settings │ │ │ │ │ ├── Footer.tsx │ │ │ │ │ └── index.tsx │ │ │ │ └── Tracing │ │ │ │ │ ├── DownloadButton.tsx │ │ │ │ │ ├── ServerOptionMenu.tsx │ │ │ │ │ ├── Table │ │ │ │ │ ├── FilterComponent.tsx │ │ │ │ │ ├── FilterMethod.tsx │ │ │ │ │ ├── FilterSession.tsx │ │ │ │ │ ├── TableDrawer │ │ │ │ │ │ ├── UpdateRequestDialog │ │ │ │ │ │ │ ├── Form.tsx │ │ │ │ │ │ │ └── index.tsx │ │ │ │ │ │ └── index.tsx │ │ │ │ │ └── index.tsx │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── providers │ │ │ │ │ ├── index.ts │ │ │ │ │ └── logs.tsx │ │ │ ├── providers │ │ │ │ ├── config.tsx │ │ │ │ ├── connection │ │ │ │ │ ├── auth.ts │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── manager.ts │ │ │ │ ├── duck-form │ │ │ │ │ ├── blueprint.tsx │ │ │ │ │ ├── duckform.tsx │ │ │ │ │ ├── field.tsx │ │ │ │ │ └── index.ts │ │ │ │ ├── index.ts │ │ │ │ ├── notifications.tsx │ │ │ │ ├── ping-server.tsx │ │ │ │ ├── preferences.tsx │ │ │ │ ├── roots.tsx │ │ │ │ ├── shiki.tsx │ │ │ │ └── traces.tsx │ │ │ ├── types │ │ │ │ ├── index.ts │ │ │ │ └── notifications.ts │ │ │ └── validations │ │ │ │ ├── index.ts │ │ │ │ └── transport.ts │ │ ├── index.ts │ │ ├── routes │ │ │ ├── client.ts │ │ │ ├── index.ts │ │ │ ├── models.ts │ │ │ ├── proxy │ │ │ │ ├── broadcast.ts │ │ │ │ ├── index.ts │ │ │ │ ├── mcpProxy.ts │ │ │ │ ├── sse.ts │ │ │ │ ├── stdio.ts │ │ │ │ ├── streaming.ts │ │ │ │ ├── types.ts │ │ │ │ └── utils.ts │ │ │ ├── scanning.ts │ │ │ ├── tunnel.ts │ │ │ └── utils.ts │ │ ├── types │ │ │ ├── env.ts │ │ │ └── index.ts │ │ └── validations │ │ │ ├── index.ts │ │ │ └── theme.ts │ ├── tsconfig.json │ ├── vc.config.ts │ ├── vite.config.ts │ └── wrangler.json ├── proxy │ ├── .gitignore │ ├── build.mjs │ ├── components.json │ ├── index.html │ ├── package.json │ ├── public │ │ └── muppet.svg │ ├── src │ │ ├── cli.ts │ │ ├── client │ │ │ ├── App.tsx │ │ │ ├── components │ │ │ │ ├── AppWrapper.tsx │ │ │ │ ├── CodeEditor.tsx │ │ │ │ ├── CopyButton.tsx │ │ │ │ ├── FieldErrorMessage.tsx │ │ │ │ ├── Hightlighter.tsx │ │ │ │ ├── Logo.tsx │ │ │ │ ├── LogoSmall.tsx │ │ │ │ ├── NotFound.tsx │ │ │ │ ├── sidebar │ │ │ │ │ ├── PreferencesDialog.tsx │ │ │ │ │ ├── SidebarItem.tsx │ │ │ │ │ └── index.tsx │ │ │ │ └── ui │ │ │ │ │ ├── accordion.tsx │ │ │ │ │ ├── button.tsx │ │ │ │ │ ├── card.tsx │ │ │ │ │ ├── checkbox.tsx │ │ │ │ │ ├── dialog.tsx │ │ │ │ │ ├── dropdown-menu.tsx │ │ │ │ │ ├── input.tsx │ │ │ │ │ ├── label.tsx │ │ │ │ │ ├── select.tsx │ │ │ │ │ ├── separator.tsx │ │ │ │ │ ├── sheet.tsx │ │ │ │ │ ├── sidebar.tsx │ │ │ │ │ ├── skeleton.tsx │ │ │ │ │ ├── spinner.tsx │ │ │ │ │ ├── table.tsx │ │ │ │ │ ├── tabs.tsx │ │ │ │ │ └── tooltip.tsx │ │ │ ├── hooks │ │ │ │ └── use-mobile.ts │ │ │ ├── index.css │ │ │ ├── lib │ │ │ │ ├── eventHandler.ts │ │ │ │ └── utils.ts │ │ │ ├── main.tsx │ │ │ ├── pages │ │ │ │ ├── Dashboard │ │ │ │ │ └── index.tsx │ │ │ │ ├── Home │ │ │ │ │ └── index.tsx │ │ │ │ ├── ServerConfigurations │ │ │ │ │ ├── Form.tsx │ │ │ │ │ └── index.tsx │ │ │ │ ├── Servers │ │ │ │ │ ├── AddServerDialog.tsx │ │ │ │ │ ├── Form │ │ │ │ │ │ ├── OptionalFields │ │ │ │ │ │ │ ├── SSE.tsx │ │ │ │ │ │ │ ├── STDIO.tsx │ │ │ │ │ │ │ └── index.tsx │ │ │ │ │ │ └── index.tsx │ │ │ │ │ ├── ServerTable │ │ │ │ │ │ ├── Table │ │ │ │ │ │ │ ├── Content │ │ │ │ │ │ │ │ ├── DeleteButton.tsx │ │ │ │ │ │ │ │ └── index.tsx │ │ │ │ │ │ │ └── index.tsx │ │ │ │ │ │ ├── TableDrawer │ │ │ │ │ │ │ ├── Header.tsx │ │ │ │ │ │ │ ├── Tabs │ │ │ │ │ │ │ │ ├── InspectorDialog.tsx │ │ │ │ │ │ │ │ ├── ServerCapabilitiesTable.tsx │ │ │ │ │ │ │ │ └── index.tsx │ │ │ │ │ │ │ └── index.tsx │ │ │ │ │ │ └── index.tsx │ │ │ │ │ └── index.tsx │ │ │ │ └── Tracing │ │ │ │ │ ├── DownloadButton.tsx │ │ │ │ │ ├── Table │ │ │ │ │ ├── FilterComponent.tsx │ │ │ │ │ ├── FilterMethod.tsx │ │ │ │ │ ├── FilterServer.tsx │ │ │ │ │ ├── FilterSession.tsx │ │ │ │ │ ├── TableDrawer │ │ │ │ │ │ ├── UpdateRequestDialog │ │ │ │ │ │ │ ├── Form.tsx │ │ │ │ │ │ │ └── index.tsx │ │ │ │ │ │ └── index.tsx │ │ │ │ │ └── index.tsx │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── providers │ │ │ │ │ ├── index.ts │ │ │ │ │ └── logs.tsx │ │ │ ├── providers │ │ │ │ ├── config.tsx │ │ │ │ ├── index.ts │ │ │ │ ├── preferences.tsx │ │ │ │ ├── shiki.tsx │ │ │ │ └── traces.tsx │ │ │ ├── queries │ │ │ │ ├── useServerData.ts │ │ │ │ ├── useServersData.ts │ │ │ │ └── useStats.ts │ │ │ └── validations │ │ │ │ ├── config.ts │ │ │ │ └── index.ts │ │ ├── index.ts │ │ ├── routes │ │ │ ├── client.ts │ │ │ ├── index.ts │ │ │ ├── servers.ts │ │ │ └── stats.ts │ │ └── types │ │ │ ├── env.ts │ │ │ └── index.ts │ ├── tsconfig.json │ ├── vc.config.ts │ ├── vite.config.ts │ └── wrangler.json └── shared │ ├── package.json │ ├── pnpm-lock.yaml │ ├── src │ ├── actions │ │ ├── index.ts │ │ └── inspector.ts │ ├── config │ │ ├── index.ts │ │ └── inspector.ts │ ├── index.ts │ ├── types │ │ ├── index.ts │ │ └── inspector.ts │ ├── utils │ │ └── index.ts │ └── validations │ │ ├── index.ts │ │ └── transport.ts │ └── tsconfig.json ├── pnpm-lock.yaml ├── pnpm-workspace.yaml └── public └── inspector.png /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist -------------------------------------------------------------------------------- /.nano-staged.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "./**/*.{js,jsx,ts,tsx,json}": (api) => 3 | `pnpm dlx @biomejs/biome check --write ${api.filenames.join(" ")}`, 4 | }; 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 muppet 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ./kit/README.md -------------------------------------------------------------------------------- /biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://biomejs.dev/schemas/1.4.1/schema.json", 3 | "organizeImports": { 4 | "enabled": true 5 | }, 6 | "linter": { 7 | "enabled": true, 8 | "rules": { 9 | "recommended": true, 10 | "style": { 11 | "noNonNullAssertion": "off" 12 | }, 13 | "suspicious": { 14 | "noExplicitAny": "warn" 15 | } 16 | } 17 | }, 18 | "formatter": { 19 | "indentStyle": "space", 20 | "lineWidth": 80, 21 | "lineEnding": "lf" 22 | }, 23 | "vcs": { 24 | "enabled": true, 25 | "clientKind": "git", 26 | "useIgnoreFile": true 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /kit/README.md: -------------------------------------------------------------------------------- 1 | # Muppet Kit 2 | 3 | It is a collection of tools for testing and debugging MCPs. 4 | 5 | ## Inspector 6 | 7 | It is a devtool for testing and debugging MCPs servers. 8 | 9 | [![Deploy to Cloudflare](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/muppet-dev/kit/tree/main/packages/inspector) 10 | 11 | ![Inspector](https://raw.githubusercontent.com/muppet-dev/kit/main/public/inspector.png) 12 | 13 | ### Usage 14 | 15 | ```bash 16 | npx muppet-kit inspector 17 | ``` 18 | 19 | ### Features 20 | 21 | - Explorer - Explore the MCP server's capabilities, while leveraging AI to assist you. 22 | - Playground - Test the MCP server with different LLMs and configurations. 23 | - MCP Scan - Scan the MCP server for vulnerabilities and security issues. 24 | - Tracing - Trace the requests and responses between the client and server. Tunneling is also supported for connecting to remote clients. 25 | - History - View the history of requests and responses between the client and server for the current session. 26 | 27 | ### Configuration 28 | 29 | You can configure the inspector by creating a `muppet.config.js`/`muppet.config.ts` file in the root of your project. The configuration file should export an object with the following properties: 30 | 31 | ```ts 32 | import { defineInspectorConfig } from "muppet-kit"; 33 | import { ngrok } from "muppet-kit/tunnel"; 34 | import { openai } from "@ai-sdk/openai"; 35 | 36 | export default defineInspectorConfig({ 37 | // ... 38 | models: [openai("gpt-4.1-nano")], 39 | // You can either pass the API key here or have it in .env as NGROK_API_KEY 40 | tunneling: ngrok(), 41 | }); 42 | ``` 43 | 44 | ## Credits 45 | 46 | The idea for this project was inspired by the official [MCP Inspector](https://github.com/modelcontextprotocol/inspector) and their amazing work. 47 | -------------------------------------------------------------------------------- /kit/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "muppet-kit", 3 | "description": "A collection of tools for building MCP servers.", 4 | "version": "0.1.4", 5 | "license": "MIT", 6 | "types": "./dist/index.d.ts", 7 | "main": "./dist/index.js", 8 | "type": "module", 9 | "bin": "./dist/cli.js", 10 | "files": [ 11 | "dist" 12 | ], 13 | "keywords": [ 14 | "mcp", 15 | "kit", 16 | "tools", 17 | "server", 18 | "inspector", 19 | "proxy", 20 | "tunnel" 21 | ], 22 | "scripts": { 23 | "build": "pkgroll --minify" 24 | }, 25 | "homepage": "https://github.com/muppet-dev/kit", 26 | "publishConfig": { 27 | "access": "public" 28 | }, 29 | "repository": { 30 | "type": "git", 31 | "url": "git+https://github.com/muppet-dev/kit.git", 32 | "directory": "packages/core" 33 | }, 34 | "bugs": { 35 | "url": "https://github.com/muppet-dev/kit/issues" 36 | }, 37 | "exports": { 38 | ".": { 39 | "import": { 40 | "types": "./dist/index.d.ts", 41 | "default": "./dist/index.js" 42 | }, 43 | "require": { 44 | "types": "./dist/index.d.cts", 45 | "default": "./dist/index.cjs" 46 | } 47 | }, 48 | "./tunnel": { 49 | "import": { 50 | "types": "./dist/tunnel/index.d.ts", 51 | "default": "./dist/tunnel/index.js" 52 | }, 53 | "require": { 54 | "types": "./dist/tunnel/index.d.cts", 55 | "default": "./dist/tunnel/index.cjs" 56 | } 57 | } 58 | }, 59 | "peerDependencies": { 60 | "@muppet-kit/inspector": "workspace:^", 61 | "@muppet-kit/shared": "workspace:^", 62 | "@ngrok/ngrok": "^1.5.1", 63 | "commander": "^13.1.0", 64 | "untun": "^0.1.3" 65 | }, 66 | "devDependencies": { 67 | "@types/node": "^22.15.17", 68 | "pkgroll": "^2.12.2" 69 | } 70 | } -------------------------------------------------------------------------------- /kit/src/cli.ts: -------------------------------------------------------------------------------- 1 | import { program } from "commander"; 2 | import pkg from "../package.json" with { type: "json" }; 3 | import inspectorCommand from "./commands/inspector.js"; 4 | 5 | program 6 | .name("muppet kit") 7 | .description("devtools for MCPs") 8 | .version(pkg.version) 9 | .addCommand(inspectorCommand) 10 | .parse(); 11 | -------------------------------------------------------------------------------- /kit/src/commands/inspector.ts: -------------------------------------------------------------------------------- 1 | import app from "@muppet-kit/inspector"; 2 | import { inspectorAction } from "@muppet-kit/shared/actions"; 3 | import { Command, Option } from "commander"; 4 | 5 | const command = new Command("inspector") 6 | .description("start the MCP Inspector") 7 | .addOption(new Option("-p, --port ", "Port to run the inspector on")) 8 | .addOption(new Option("-h, --host ", "Host to run the inspector on")) 9 | .addOption(new Option("-c, --config ", "Path to the config file")) 10 | .action((options) => 11 | inspectorAction({ 12 | options, 13 | // @ts-expect-error The build output is different 14 | app, 15 | }), 16 | ); 17 | 18 | export default command; 19 | -------------------------------------------------------------------------------- /kit/src/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | type InspectorConfig, 3 | defineInspectorConfig, 4 | } from "@muppet-kit/shared"; 5 | 6 | export { defineInspectorConfig, type InspectorConfig }; 7 | -------------------------------------------------------------------------------- /kit/src/tunnel/cloudflare.ts: -------------------------------------------------------------------------------- 1 | import type { TunnelHandler } from "@muppet-kit/shared"; 2 | import { startTunnel } from "untun"; 3 | 4 | export function cloudflare(): TunnelHandler { 5 | return { 6 | generate: async ({ port }) => { 7 | // Creating a new tunnel 8 | const listener = await startTunnel({ 9 | port, 10 | }); 11 | 12 | return { 13 | url: (await listener?.getURL()) ?? null, 14 | }; 15 | }, 16 | }; 17 | } 18 | -------------------------------------------------------------------------------- /kit/src/tunnel/index.ts: -------------------------------------------------------------------------------- 1 | export { cloudflare } from "./cloudflare"; 2 | export { ngrok } from "./ngrok"; 3 | -------------------------------------------------------------------------------- /kit/src/tunnel/ngrok.ts: -------------------------------------------------------------------------------- 1 | import type { TunnelHandler } from "@muppet-kit/shared"; 2 | import { disconnect, forward } from "@ngrok/ngrok"; 3 | 4 | /** 5 | * Ngrok tunnel handler 6 | */ 7 | export function ngrok(options?: { apiKey?: string }): TunnelHandler { 8 | return { 9 | generate: async ({ port }) => { 10 | // Disconnecting all existing tunnels 11 | await disconnect(); 12 | 13 | // Creating a new tunnel 14 | const listener = await forward({ 15 | addr: port, 16 | authtoken: options?.apiKey ?? process.env.NGROK_API_KEY, 17 | }); 18 | 19 | return { 20 | url: listener.url(), 21 | }; 22 | }, 23 | }; 24 | } 25 | -------------------------------------------------------------------------------- /kit/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "lib": ["ESNext", "DOM"], 5 | "moduleDetection": "force", 6 | "useDefineForClassFields": false, 7 | "experimentalDecorators": true, 8 | "module": "ESNext", 9 | "moduleResolution": "bundler", 10 | "resolveJsonModule": true, 11 | "allowJs": true, 12 | "strict": true, 13 | "noFallthroughCasesInSwitch": true, 14 | "noImplicitOverride": true, 15 | "noImplicitReturns": true, 16 | "noUnusedLocals": true, 17 | "noImplicitAny": true, 18 | "noUnusedParameters": true, 19 | "declaration": false, 20 | "noEmit": true, 21 | "outDir": "dist/", 22 | "sourceMap": true, 23 | "esModuleInterop": true, 24 | "forceConsistentCasingInFileNames": true, 25 | "isolatedModules": true, 26 | "verbatimModuleSyntax": true, 27 | "skipLibCheck": true 28 | }, 29 | "include": ["./src/**/*"] 30 | } 31 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@muppet-kit/workspace", 3 | "type": "module", 4 | "scripts": { 5 | "prepare": "is-ci || husky", 6 | "format": "biome check --write ." 7 | }, 8 | "devDependencies": { 9 | "@biomejs/biome": "^1.9.4", 10 | "husky": "^9.1.7", 11 | "is-ci": "^4.1.0", 12 | "nano-staged": "^0.8.0" 13 | }, 14 | "packageManager": "pnpm@10.0.0" 15 | } -------------------------------------------------------------------------------- /packages/inspector/.env.example: -------------------------------------------------------------------------------- 1 | OPENAI_API_KEY="" 2 | NGROK_API_KEY="" -------------------------------------------------------------------------------- /packages/inspector/.gitignore: -------------------------------------------------------------------------------- 1 | # dev 2 | .yarn/ 3 | !.yarn/releases 4 | .vscode/* 5 | !.vscode/launch.json 6 | !.vscode/*.code-snippets 7 | .idea/workspace.xml 8 | .idea/usage.statistics.xml 9 | .idea/shelf 10 | 11 | # deps 12 | node_modules/ 13 | 14 | # env 15 | .env 16 | .env.production 17 | .dev.vars 18 | 19 | # logs 20 | logs/ 21 | *.log 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | pnpm-debug.log* 26 | lerna-debug.log* 27 | 28 | # misc 29 | .DS_Store 30 | 31 | dist-server 32 | 33 | .wrangler -------------------------------------------------------------------------------- /packages/inspector/build.mjs: -------------------------------------------------------------------------------- 1 | import { performance } from "node:perf_hooks"; 2 | import esbuild from "esbuild"; 3 | 4 | const start = performance.now(); 5 | 6 | await esbuild.build({ 7 | entryPoints: ["./src/cli.ts"], 8 | banner: { js: "#!/usr/bin/env node" }, 9 | platform: "node", 10 | minify: true, 11 | outfile: "dist/cli.js", 12 | }); 13 | 14 | const end = performance.now(); 15 | 16 | console.log(`Build time: ${((end - start) / 1000).toFixed(2)}s`); 17 | -------------------------------------------------------------------------------- /packages/inspector/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Muppet Inspector 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /packages/inspector/muppet.config.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is used in development and when deploying to Cloudflare. 3 | * You can use it to configure the inspector. 4 | */ 5 | import { createOpenAI } from "@ai-sdk/openai"; 6 | import { defineInspectorConfig } from "@muppet-kit/shared"; 7 | 8 | export default (env: Record) => { 9 | const openai = createOpenAI({ 10 | apiKey: env.OPENAI_API_KEY, 11 | }); 12 | 13 | return defineInspectorConfig({ 14 | models: [openai("gpt-4.1-nano"), openai("gpt-4.1-mini")], 15 | }); 16 | }; 17 | -------------------------------------------------------------------------------- /packages/inspector/public/muppet.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /packages/inspector/src/cli.ts: -------------------------------------------------------------------------------- 1 | import { inspectorAction } from "@muppet-kit/shared/actions"; 2 | import { Option, program } from "commander"; 3 | import pkg from "../package.json" with { type: "json" }; 4 | import app from "./index.js"; 5 | 6 | program 7 | .name("muppet inspector") 8 | .description("start the MCP Inspector") 9 | .version(pkg.version) 10 | .addOption(new Option("-p, --port ", "Port to run the inspector on")) 11 | .addOption(new Option("-h, --host ", "Host to run the inspector on")) 12 | .addOption(new Option("-c, --config ", "Path to the config file")) 13 | .action((options) => 14 | inspectorAction({ 15 | options, 16 | // @ts-expect-error The build output is different 17 | app, 18 | }), 19 | ) 20 | .parse(); 21 | -------------------------------------------------------------------------------- /packages/inspector/src/client/App.tsx: -------------------------------------------------------------------------------- 1 | import { Transport } from "@muppet-kit/shared"; 2 | import { Route, Routes } from "react-router"; 3 | import { AppProviders } from "./components/AppProviders"; 4 | import { NotFound } from "./components/NotFound"; 5 | import { AppWrapper } from "./components/Wrapper"; 6 | import { ConfigurationsDialog } from "./components/configurationsDialog"; 7 | import ExplorerPage from "./pages/Explorer"; 8 | import HistoryPage from "./pages/History"; 9 | import { HomePage } from "./pages/Home"; 10 | import MCPScanPage from "./pages/MCPScan"; 11 | import OAuthCallbackPage from "./pages/OAuthCallback"; 12 | import PlaygroundPage from "./pages/Playground"; 13 | import SettingsPage from "./pages/Settings"; 14 | import TracingPage from "./pages/Tracing"; 15 | import { useConfig } from "./providers"; 16 | 17 | export default function App() { 18 | const { connectionInfo, setConnectionInfo } = useConfig(); 19 | 20 | if (window.location.pathname === "/oauth/callback") { 21 | return ( 22 | { 24 | setConnectionInfo({ 25 | type: Transport.SSE, 26 | url, 27 | }); 28 | }} 29 | /> 30 | ); 31 | } 32 | 33 | if (!connectionInfo) { 34 | return ; 35 | } 36 | 37 | return ( 38 | 39 | 40 | }> 41 | } /> 42 | } /> 43 | } /> 44 | } /> 45 | } /> 46 | } /> 47 | } /> 48 | 49 | } /> 50 | 51 | 52 | ); 53 | } 54 | -------------------------------------------------------------------------------- /packages/inspector/src/client/components/AppProviders.tsx: -------------------------------------------------------------------------------- 1 | import type { PropsWithChildren } from "react"; 2 | import { 3 | TracingProvider, 4 | NotificationProvider, 5 | ConnectionProvider, 6 | PingServerProvider, 7 | ShikiProvider, 8 | } from "../providers"; 9 | import { SidebarProvider } from "./ui/sidebar"; 10 | import { RootsProvider } from "../providers/roots"; 11 | 12 | export function AppProviders(props: PropsWithChildren) { 13 | return ( 14 | 15 | 16 | 17 | 18 | 19 | 20 | {props.children} 21 | 22 | 23 | 24 | 25 | 26 | 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /packages/inspector/src/client/components/ConfigForm/useConfigForm.ts: -------------------------------------------------------------------------------- 1 | import toast from "react-hot-toast"; 2 | import { generateName } from "../../lib/utils"; 3 | import { useConfig } from "../../providers"; 4 | import type { ConnectionInfo } from "../../providers/connection/manager"; 5 | import { DocumentSubmitType, SUBMIT_BUTTON_KEY } from "../../validations"; 6 | import { Transport } from "@muppet-kit/shared"; 7 | import { useMutation } from "@tanstack/react-query"; 8 | 9 | export function useConfigForm() { 10 | const { setConnectionInfo, addConfigurations } = useConfig(); 11 | 12 | return useMutation({ 13 | mutationFn: async (values: ConnectionInfo) => { 14 | const _values = { 15 | ...values, 16 | name: 17 | values.name && values.name.trim().length > 0 18 | ? values.name 19 | : generateName(), 20 | [SUBMIT_BUTTON_KEY]: undefined, 21 | }; 22 | 23 | if (_values.type === Transport.STDIO && _values.env) { 24 | // @ts-expect-error: converting data from array of object to string in order to store it in local storage 25 | _values.env = 26 | _values.env.length > 0 27 | ? JSON.stringify( 28 | Object.fromEntries( 29 | _values.env.map((item) => [item.key, item.value]) 30 | ) 31 | ) 32 | : undefined; 33 | } 34 | 35 | setConnectionInfo(_values); 36 | 37 | return _values; 38 | }, 39 | onSuccess: (values, formData) => { 40 | const submit_type_value = formData[SUBMIT_BUTTON_KEY]; 41 | 42 | if (submit_type_value === DocumentSubmitType.SAVE_AND_CONNECT) { 43 | addConfigurations(values); 44 | toast.success("Configuration saved successfully!"); 45 | } 46 | }, 47 | onError: (error) => { 48 | console.error(error); 49 | 50 | toast.error(error.message); 51 | }, 52 | }); 53 | } 54 | -------------------------------------------------------------------------------- /packages/inspector/src/client/components/ConfigFormFields/OptionalFields/index.tsx: -------------------------------------------------------------------------------- 1 | import type { configTransportSchema } from "../../../validations"; 2 | import { Transport } from "@muppet-kit/shared"; 3 | import { useFormContext, useWatch } from "react-hook-form"; 4 | import type z from "zod"; 5 | import { SSEFields } from "./SSE"; 6 | import { STDIOFields } from "./STDIO"; 7 | 8 | export function OptionalFields() { 9 | const { control } = useFormContext>(); 10 | const type = useWatch({ control, name: "type" }); 11 | 12 | if (type === Transport.STDIO) return ; 13 | return ; 14 | } 15 | -------------------------------------------------------------------------------- /packages/inspector/src/client/components/ConfigFormFields/index.tsx: -------------------------------------------------------------------------------- 1 | import type { configTransportSchema as schema } from "../../validations"; 2 | import { Transport } from "@muppet-kit/shared"; 3 | import { Controller, useFormContext } from "react-hook-form"; 4 | import type z from "zod"; 5 | import { Label } from "../ui/label"; 6 | import { 7 | Select, 8 | SelectContent, 9 | SelectItem, 10 | SelectTrigger, 11 | SelectValue, 12 | } from "../ui/select"; 13 | import { ConfigurationField } from "./ConfigurationField"; 14 | import { OptionalFields } from "./OptionalFields"; 15 | 16 | export function ConfigFormFields() { 17 | const { control } = useFormContext>(); 18 | 19 | return ( 20 | <> 21 |
22 | 23 | ( 27 | 37 | )} 38 | /> 39 |
40 | 41 | 42 | 43 | ); 44 | } 45 | -------------------------------------------------------------------------------- /packages/inspector/src/client/components/CopyButton.tsx: -------------------------------------------------------------------------------- 1 | import { eventHandler } from "../lib/eventHandler"; 2 | import { cn } from "../lib/utils"; 3 | import { Check, Copy } from "lucide-react"; 4 | import { Button } from "./ui/button"; 5 | import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip"; 6 | import { useCopyToClipboard } from "@uidotdev/usehooks"; 7 | import { useEffect, useState } from "react"; 8 | 9 | export type CopyButton = { 10 | data?: string; 11 | tooltipContent?: string; 12 | tooltipSide?: "top" | "right" | "bottom" | "left"; 13 | className?: HTMLDivElement["className"]; 14 | disabled?: boolean; 15 | }; 16 | 17 | export function CopyButton({ 18 | data, 19 | className, 20 | tooltipContent, 21 | tooltipSide, 22 | disabled, 23 | }: CopyButton) { 24 | const [_, copyToClipboard] = useCopyToClipboard(); 25 | const [isCopied, setIsCopied] = useState(false); 26 | 27 | useEffect(() => { 28 | if (!isCopied) return; 29 | 30 | const timeoutId = setTimeout(() => { 31 | setIsCopied(false); 32 | }, 3000); 33 | 34 | return () => clearTimeout(timeoutId); 35 | }, [isCopied]); 36 | 37 | const handleCopyToClipboard = eventHandler(() => { 38 | copyToClipboard(data ?? ""); 39 | setIsCopied(true); 40 | }); 41 | 42 | const Icon = isCopied ? Check : Copy; 43 | 44 | const button = ( 45 | 56 | ); 57 | 58 | if (tooltipContent === "") return button; 59 | 60 | return ( 61 | 62 | {button} 63 | 66 | 67 | ); 68 | } 69 | -------------------------------------------------------------------------------- /packages/inspector/src/client/components/DuckField.tsx: -------------------------------------------------------------------------------- 1 | import { FieldProvider, useBlueprint, useDuckForm } from "../providers"; 2 | import { Fragment } from "react/jsx-runtime"; 3 | 4 | export type DuckField< 5 | T extends Record = Record 6 | > = { 7 | type: string; 8 | } & T; 9 | 10 | export function DuckField>(props: T) { 11 | const { wrapper: Wrapper = Fragment, schema } = useBlueprint() ?? {}; 12 | const { components, resolver } = useDuckForm(); 13 | 14 | const options = resolver(schema, props) as DuckField; 15 | let Component = options?.type ? components[options.type] : undefined; 16 | Component ??= components.default; 17 | 18 | return ( 19 | 20 | 21 | 22 | 23 | 24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /packages/inspector/src/client/components/DuckFieldNotFound.tsx: -------------------------------------------------------------------------------- 1 | import { useField } from "../providers"; 2 | 3 | export function DuckFieldComponentNotFound() { 4 | const options = useField(); 5 | 6 | if (options.type === "default") 7 | return ( 8 |

9 | Unable to render component with props{" "} 10 | {JSON.stringify(options)} 11 |

12 | ); 13 | 14 | return ( 15 |

16 | Component of type {options.type} doesn't exist! 17 |

18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /packages/inspector/src/client/components/ErrorBoundaryRender.tsx: -------------------------------------------------------------------------------- 1 | import type { FallbackProps } from "react-error-boundary"; 2 | import { Button } from "./ui/button"; 3 | import { X } from "lucide-react"; 4 | import { useState } from "react"; 5 | import { eventHandler } from "../lib/eventHandler"; 6 | 7 | export function ErrorBoundaryRender({ error }: FallbackProps) { 8 | const [showBanner, setShowBanner] = useState(true); 9 | const { name, message, stack } = error; 10 | 11 | const handleCloseBanner = eventHandler(() => setShowBanner(false)); 12 | 13 | if (showBanner) 14 | return ( 15 |
16 |
17 |
18 |
19 |

{name}

20 |

Error: {message}

21 |
22 |
23 |

Source

24 |
25 |               {stack}
26 |             
27 |
28 | 37 |
38 |
39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /packages/inspector/src/client/components/FieldErrorMessage.tsx: -------------------------------------------------------------------------------- 1 | import { ErrorMessage } from "@hookform/error-message"; 2 | 3 | export function FieldErrorMessage(props: { name: string }) { 4 | return ( 5 | ( 8 |

9 | {message} 10 |

11 | )} 12 | /> 13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /packages/inspector/src/client/components/Hightlighter.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | import { cn } from "../lib/utils"; 3 | import { Theme, usePreferences, useShiki } from "../providers"; 4 | import { CopyButton } from "./CopyButton"; 5 | import { Skeleton } from "./ui/skeleton"; 6 | 7 | export type CodeHighlighter = { content: string; className?: string }; 8 | 9 | export function CodeHighlighter({ content, className }: CodeHighlighter) { 10 | const highlighter = useShiki(); 11 | const [html, setHtml] = useState(""); 12 | const { resolvedTheme } = usePreferences(); 13 | 14 | if (!highlighter) 15 | return ( 16 |
17 | 18 | 19 | 20 | 21 | 22 |
23 | ); 24 | 25 | // biome-ignore lint/correctness/useExhaustiveDependencies: 26 | useEffect(() => { 27 | highlighter 28 | .codeToHtml(content, { 29 | lang: "json", 30 | theme: 31 | resolvedTheme === Theme.LIGHT 32 | ? "github-light-default" 33 | : "github-dark-default", 34 | }) 35 | .then((val) => setHtml(val)); 36 | }, [content, resolvedTheme]); 37 | 38 | return ( 39 |
40 |
46 |
51 |
52 | 56 |
57 | ); 58 | } 59 | -------------------------------------------------------------------------------- /packages/inspector/src/client/components/LogoSmall.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "../lib/utils"; 2 | import type { ComponentProps } from "react"; 3 | 4 | export type LogoSmall = ComponentProps<"svg">; 5 | 6 | export function LogoSmall({ className, ...props }: LogoSmall) { 7 | return ( 8 | 15 | Muppet 16 | 17 | 25 | 26 | 27 | 28 | 29 | 33 | 34 | 35 | ); 36 | } 37 | -------------------------------------------------------------------------------- /packages/inspector/src/client/components/NotFound.tsx: -------------------------------------------------------------------------------- 1 | import { Link } from "react-router"; 2 | import { Button } from "./ui/button"; 3 | 4 | export function NotFound() { 5 | return ( 6 |
7 |
8 |

404

9 |
10 |

11 | Page not found 12 |

13 |

14 | Sorry, we couldn't find the page you're looking for. 15 |

16 |
17 | 18 | 19 | 20 |
21 |
22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /packages/inspector/src/client/components/Sidebar/PingButton/OptionsMenu/Dialog/index.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Dialog, 3 | DialogContent, 4 | DialogDescription, 5 | DialogHeader, 6 | DialogOverlay, 7 | DialogTitle, 8 | } from "../../../../ui/dialog"; 9 | import type { DialogProps } from "@radix-ui/react-dialog"; 10 | import { RadioTower } from "lucide-react"; 11 | import type { ComponentProps, FC } from "react"; 12 | import { CustomTimeIntervalForm } from "./Form"; 13 | 14 | export function CustomTimeIntervalDialog( 15 | props: ComponentProps> 16 | ) { 17 | return ( 18 | 19 | 20 | 21 | 22 |
23 | 24 | Auto Ping 25 |
26 | 27 | Add custom time duration to ping the server 28 | 29 |
30 | props.onOpenChange?.(false)} /> 31 |
32 |
33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /packages/inspector/src/client/components/Sidebar/PingButton/index.tsx: -------------------------------------------------------------------------------- 1 | import { eventHandler } from "../../../lib/eventHandler"; 2 | import { useConnection } from "../../../providers"; 3 | import { ConnectionStatus } from "../../../providers/connection/manager"; 4 | import { EmptyResultSchema } from "@modelcontextprotocol/sdk/types.js"; 5 | import { Bell } from "lucide-react"; 6 | import { useState } from "react"; 7 | import { SidebarMenuButton, SidebarMenuItem } from "../../ui/sidebar"; 8 | import { Spinner } from "../../ui/spinner"; 9 | import { OptionsMenu } from "./OptionsMenu"; 10 | 11 | export function PingButton() { 12 | // Show this loading state when the server is pinging 13 | const [isLoading, setIsLoading] = useState(false); 14 | const { makeRequest, connectionStatus } = useConnection(); 15 | 16 | const handlePingServer = eventHandler(async () => { 17 | setIsLoading(true); 18 | await makeRequest( 19 | { 20 | method: "ping", 21 | }, 22 | EmptyResultSchema 23 | ); 24 | setIsLoading(false); 25 | }); 26 | 27 | return ( 28 | 29 | 35 | 36 | Ping Server 37 | {isLoading && ( 38 | <> 39 |
40 | 41 | 42 | )} 43 | 44 | 45 | 46 | ); 47 | } 48 | -------------------------------------------------------------------------------- /packages/inspector/src/client/components/Sidebar/PreferencesDialog/ColorModeSetting.tsx: -------------------------------------------------------------------------------- 1 | import { eventHandler } from "@/client/lib/eventHandler"; 2 | import { Theme, usePreferences } from "@/client/providers"; 3 | import { Moon, Sun, Tv } from "lucide-react"; 4 | import { Label } from "../../ui/label"; 5 | import { ItemCard } from "./ItemCard"; 6 | 7 | const THEMES = { 8 | [Theme.LIGHT]: Sun, 9 | [Theme.DARK]: Moon, 10 | [Theme.SYSTEM]: Tv, 11 | }; 12 | 13 | export function ColorModeSetting() { 14 | const { theme, setTheme } = usePreferences(); 15 | 16 | const handleChangeColorMode = (name: Theme) => 17 | eventHandler(() => setTheme(name)); 18 | 19 | return ( 20 |
21 | 22 |
23 | {Object.entries(THEMES).map(([name, icon]) => { 24 | const isSelected = theme === name; 25 | 26 | return ( 27 | 36 | ); 37 | })} 38 |
39 |
40 | ); 41 | } 42 | -------------------------------------------------------------------------------- /packages/inspector/src/client/components/Sidebar/PreferencesDialog/ItemCard.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@/client/lib/utils"; 2 | import type { LucideIcon } from "lucide-react"; 3 | import type { ComponentProps, PropsWithChildren } from "react"; 4 | 5 | export type ItemCard = { 6 | isSelected: boolean; 7 | icon?: LucideIcon; 8 | name: string; 9 | } & Pick, "className" | "onClick" | "onKeyDown">; 10 | 11 | export function ItemCard({ 12 | isSelected, 13 | name, 14 | icon: Icon, 15 | children, 16 | className, 17 | ...props 18 | }: PropsWithChildren) { 19 | return ( 20 |
28 | {Icon && } 29 | {name} 30 | {children} 31 |
32 | ); 33 | } 34 | -------------------------------------------------------------------------------- /packages/inspector/src/client/components/Sidebar/PreferencesDialog/ThemeDialog/GenerateButtonGroup/GenerateDialog/index.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from "@/client/components/ui/button"; 2 | import { 3 | Dialog, 4 | DialogContent, 5 | DialogDescription, 6 | DialogHeader, 7 | DialogOverlay, 8 | DialogTitle, 9 | DialogTrigger, 10 | } from "@/client/components/ui/dialog"; 11 | import { Settings, SparklesIcon } from "lucide-react"; 12 | import { useState } from "react"; 13 | import { GenerateForm } from "./Form"; 14 | import { 15 | Tooltip, 16 | TooltipContent, 17 | TooltipTrigger, 18 | } from "@/client/components/ui/tooltip"; 19 | 20 | export function GenerateDialog() { 21 | const [isOpen, setOpen] = useState(false); 22 | 23 | return ( 24 | 25 | 26 | 27 |
28 | 29 | 35 | 36 |
37 |
38 | Generate with context 39 |
40 | 41 | 42 | 43 |
44 | 45 | Generate 46 |
47 | 48 |
49 | setOpen(false)} /> 50 |
51 |
52 | ); 53 | } 54 | -------------------------------------------------------------------------------- /packages/inspector/src/client/components/Sidebar/PreferencesDialog/ThemeDialog/GenerateButtonGroup/provider.tsx: -------------------------------------------------------------------------------- 1 | import { useMutation } from "@tanstack/react-query"; 2 | import { type PropsWithChildren, createContext, useContext } from "react"; 3 | import { useFormContext } from "react-hook-form"; 4 | import toast from "react-hot-toast"; 5 | import type z from "zod"; 6 | import type { colorThemeValidation as schema } from "../validation"; 7 | 8 | type GenerateContextType = ReturnType; 9 | 10 | const GenerateContext = createContext(null); 11 | 12 | export const GenerateProvider = (props: PropsWithChildren) => { 13 | const values = useGenerateManager(); 14 | 15 | return ( 16 | 17 | {props.children} 18 | 19 | ); 20 | }; 21 | 22 | function useGenerateManager() { 23 | const { setValue } = useFormContext>(); 24 | 25 | return useMutation({ 26 | mutationFn: async (values: { context?: string; modelId?: string }) => 27 | await fetch("/api/theme", { 28 | method: "POST", 29 | headers: { 30 | "Content-Type": "application/json", 31 | }, 32 | body: JSON.stringify(values), 33 | }).then((res) => { 34 | if (!res.ok) { 35 | throw new Error("Failed to generate data. Please try again later."); 36 | } 37 | 38 | return res.json() as Promise>; 39 | }), 40 | onSuccess: (data) => { 41 | const formattedData = JSON.stringify(data, null, 2); 42 | 43 | setValue("variables", formattedData); 44 | toast.success("Theme generated successfully!"); 45 | }, 46 | onError: (err) => { 47 | toast.error(err.message); 48 | console.error(err); 49 | }, 50 | }); 51 | } 52 | 53 | export const useGenerate = () => { 54 | const context = useContext(GenerateContext); 55 | 56 | if (!context) 57 | throw new Error("Missing GenerateContext.Provider in the tree!"); 58 | 59 | return context; 60 | }; 61 | -------------------------------------------------------------------------------- /packages/inspector/src/client/components/Sidebar/PreferencesDialog/ThemeDialog/validation.ts: -------------------------------------------------------------------------------- 1 | import { customThemeSchema } from "@/validations"; 2 | import z from "zod"; 3 | 4 | export const colorThemeValidation = z.object({ 5 | name: z.string().optional(), 6 | variables: z.string().refine( 7 | (str) => { 8 | try { 9 | const parsed = JSON.parse(str); 10 | customThemeSchema.parse(parsed); 11 | return true; 12 | } catch { 13 | return false; 14 | } 15 | }, 16 | { 17 | message: "Invalid theme JSON.", 18 | }, 19 | ), 20 | }); 21 | -------------------------------------------------------------------------------- /packages/inspector/src/client/components/Sidebar/PreferencesDialog/ThemeSettings/DeleteButton.tsx: -------------------------------------------------------------------------------- 1 | import { eventHandler } from "@/client/lib/eventHandler"; 2 | import { usePreferences } from "@/client/providers"; 3 | import { useMutation } from "@tanstack/react-query"; 4 | import { Trash } from "lucide-react"; 5 | import toast from "react-hot-toast"; 6 | import { Button } from "../../../ui/button"; 7 | 8 | export function DeleteThemeButton(props: { name: string }) { 9 | const { setColorTheme, setCurrentColorTheme, currentColorTheme } = 10 | usePreferences(); 11 | 12 | const mutation = useMutation({ 13 | mutationFn: async () => { 14 | setColorTheme((prev) => { 15 | const newThemes = { ...prev }; 16 | delete newThemes[props.name]; 17 | return newThemes; 18 | }); 19 | }, 20 | onSuccess: () => { 21 | toast.success(`${props.name} theme deleted successfully`); 22 | if (currentColorTheme === props.name) { 23 | setCurrentColorTheme("default"); 24 | } 25 | }, 26 | onError: () => { 27 | toast.error(`Error deleting ${props.name} theme`); 28 | }, 29 | }); 30 | 31 | const handleDeleteTheme = eventHandler(() => mutation.mutateAsync()); 32 | 33 | return ( 34 | 44 | ); 45 | } 46 | -------------------------------------------------------------------------------- /packages/inspector/src/client/components/Sidebar/PreferencesDialog/ToastSetting.tsx: -------------------------------------------------------------------------------- 1 | import { usePreferences } from "@/client/providers"; 2 | import { Label } from "../../ui/label"; 3 | import toast, { type ToastPosition } from "react-hot-toast"; 4 | import { ItemCard } from "./ItemCard"; 5 | import { eventHandler } from "@/client/lib/eventHandler"; 6 | 7 | const TOAST_POSITIONS = [ 8 | "top-left", 9 | "top-center", 10 | "top-right", 11 | "bottom-left", 12 | "bottom-center", 13 | "bottom-right", 14 | ]; 15 | 16 | export function ToastSetting() { 17 | const { toastPosition, setToast } = usePreferences(); 18 | 19 | const handleChangeToastPosition = (name: ToastPosition) => 20 | eventHandler(() => { 21 | setToast(name as ToastPosition); 22 | 23 | toast.success(`Toast position changed to ${name}`, { 24 | id: "toast-position-changed", 25 | }); 26 | }); 27 | 28 | return ( 29 |
30 | 31 |
32 | {TOAST_POSITIONS.map((name) => { 33 | const isSelected = toastPosition === name; 34 | 35 | return ( 36 | 44 | ); 45 | })} 46 |
47 |
48 | ); 49 | } 50 | -------------------------------------------------------------------------------- /packages/inspector/src/client/components/Sidebar/SidebarItem.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | SidebarGroup, 3 | SidebarMenu, 4 | SidebarMenuItem, 5 | sidebarMenuButtonVariants, 6 | useSidebar, 7 | } from "../ui/sidebar"; 8 | import { cn } from "../../lib/utils"; 9 | import type { LucideIcon } from "lucide-react"; 10 | import type { PropsWithChildren } from "react"; 11 | import { NavLink } from "react-router"; 12 | import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip"; 13 | 14 | export type SidebarItem = { 15 | items: { 16 | name: string; 17 | url: string; 18 | icon: LucideIcon; 19 | }[]; 20 | }; 21 | 22 | export function SidebarItem({ items }: SidebarItem) { 23 | const { state } = useSidebar(); 24 | 25 | const ToolTipWrapper = (props: PropsWithChildren<{ name: string }>) => { 26 | if (state === "collapsed") 27 | return ( 28 | 29 | {props.children} 30 | {props.name} 31 | 32 | ); 33 | 34 | return <>{props.children}; 35 | }; 36 | 37 | return ( 38 | 39 | 40 | {items.map((item) => ( 41 | 42 | 43 | 46 | cn( 47 | sidebarMenuButtonVariants(), 48 | isActive && 49 | "bg-primary hover:bg-primary text-primary-foreground hover:text-primary-foreground" 50 | ) 51 | } 52 | > 53 | 54 | {item.name} 55 | 56 | 57 | 58 | ))} 59 | 60 | 61 | ); 62 | } 63 | -------------------------------------------------------------------------------- /packages/inspector/src/client/components/configurationsDialog/Tabs/Connect/Fields.tsx: -------------------------------------------------------------------------------- 1 | import type { configTransportSchema as schema } from "../../../../validations"; 2 | import { useFormContext } from "react-hook-form"; 3 | import type z from "zod"; 4 | import { ConfigFormFields } from "../../../ConfigFormFields"; 5 | import { Input } from "../../../ui/input"; 6 | import { Label } from "../../../ui/label"; 7 | 8 | export function FormFields() { 9 | const { register } = useFormContext>(); 10 | 11 | return ( 12 |
13 |
14 | 15 | 21 |
22 | 23 |
24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /packages/inspector/src/client/components/configurationsDialog/Tabs/Connect/Footer.tsx: -------------------------------------------------------------------------------- 1 | import { eventHandler } from "../../../../lib/eventHandler"; 2 | import { DocumentSubmitType, SUBMIT_BUTTON_KEY } from "../../../../validations"; 3 | import { Transport } from "@muppet-kit/shared"; 4 | import type { BaseSyntheticEvent } from "react"; 5 | import { useFormContext } from "react-hook-form"; 6 | import { Button } from "../../../ui/button"; 7 | import { DialogFooter } from "../../../ui/dialog"; 8 | 9 | export function FormFooter() { 10 | const { reset, setValue } = useFormContext(); 11 | 12 | const handleResetForm = eventHandler(() => 13 | reset({ 14 | type: Transport.STDIO, 15 | }) 16 | ); 17 | 18 | const handleSaveAndConnect = (event: BaseSyntheticEvent) => { 19 | if ("key" in event && event.key !== "Enter") return; 20 | 21 | setValue(SUBMIT_BUTTON_KEY, DocumentSubmitType.SAVE_AND_CONNECT); 22 | }; 23 | 24 | const handleConnect = (event: BaseSyntheticEvent) => { 25 | if ("key" in event && event.key !== "Enter") return; 26 | 27 | setValue(SUBMIT_BUTTON_KEY, DocumentSubmitType.CONNECT); 28 | }; 29 | 30 | return ( 31 | 32 | 39 |
40 | 43 | 50 | 51 | ); 52 | } 53 | -------------------------------------------------------------------------------- /packages/inspector/src/client/components/configurationsDialog/Tabs/Connect/index.tsx: -------------------------------------------------------------------------------- 1 | import { ConfigForm } from "../../../ConfigForm"; 2 | import { FormFields } from "./Fields"; 3 | import { FormFooter } from "./Footer"; 4 | 5 | export function Connect() { 6 | return ( 7 | 8 | 9 | 10 | 11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /packages/inspector/src/client/components/configurationsDialog/Tabs/index.tsx: -------------------------------------------------------------------------------- 1 | import { Tabs, TabsContent, TabsList, TabsTrigger } from "../../ui/tabs"; 2 | import { Configurations } from "./Configurations"; 3 | import { Connect } from "./Connect"; 4 | 5 | export function ConfigTabs() { 6 | return ( 7 | 8 | 9 | 13 | Connect 14 | 15 | 19 | Quick Connect 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /packages/inspector/src/client/components/configurationsDialog/index.tsx: -------------------------------------------------------------------------------- 1 | import { useConfig } from "../../providers"; 2 | import { 3 | Dialog, 4 | DialogContent, 5 | DialogDescription, 6 | DialogHeader, 7 | DialogOverlay, 8 | DialogTitle, 9 | } from "../ui/dialog"; 10 | import { ConfigTabs } from "./Tabs"; 11 | 12 | export function ConfigurationsDialog() { 13 | const { version } = useConfig(); 14 | 15 | return ( 16 | 17 | 18 |
19 |

{version}

20 |
21 |
22 | 23 | 24 | Configure Transport 25 | 26 | Please configure the transport settings to continue 27 | 28 | 29 | 30 | 31 |
32 | ); 33 | } 34 | -------------------------------------------------------------------------------- /packages/inspector/src/client/components/highlightMatches.tsx: -------------------------------------------------------------------------------- 1 | import type { RangeTuple } from "fuse.js"; 2 | import type { ReactNode } from "react"; 3 | 4 | export const highlightMatches = ( 5 | inputText: string, 6 | regions: readonly RangeTuple[] = [] 7 | ) => { 8 | const children: ReactNode[] = []; 9 | let nextUnhighlightedRegionStartingIndex = 0; 10 | 11 | regions.forEach((region, i) => { 12 | const lastRegionNextIndex = region[1] + 1; 13 | 14 | children.push( 15 | ...[ 16 | inputText 17 | .substring(nextUnhighlightedRegionStartingIndex, region[0]) 18 | .replace(" ", "\u00A0"), 19 | 20 | {inputText 21 | .substring(region[0], lastRegionNextIndex) 22 | .replace(" ", "\u00A0")} 23 | , 24 | ] 25 | ); 26 | 27 | nextUnhighlightedRegionStartingIndex = lastRegionNextIndex; 28 | }); 29 | 30 | children.push( 31 | inputText 32 | .substring(nextUnhighlightedRegionStartingIndex) 33 | .replace(" ", "\u00A0") 34 | ); 35 | 36 | return children; 37 | }; 38 | -------------------------------------------------------------------------------- /packages/inspector/src/client/components/icons/Anthropic.tsx: -------------------------------------------------------------------------------- 1 | import type { IconProps } from "./type"; 2 | 3 | export function AnthropicIcon({ style, ...props }: IconProps) { 4 | return ( 5 | 14 | Anthropic 15 | 16 | 20 | 21 | 22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /packages/inspector/src/client/components/icons/Cohere.tsx: -------------------------------------------------------------------------------- 1 | import type { IconProps } from "./type"; 2 | 3 | export function CohereIcon({ style, ...props }: IconProps) { 4 | return ( 5 | 17 | Cohere 18 | 25 | 26 | 27 | 28 | 32 | 36 | 40 | 41 | 42 | 43 | 44 | ); 45 | } 46 | -------------------------------------------------------------------------------- /packages/inspector/src/client/components/icons/Fireworks.tsx: -------------------------------------------------------------------------------- 1 | import type { IconProps } from "./type"; 2 | 3 | export function FireworksIcon({ style, ...props }: IconProps) { 4 | return ( 5 | 14 | Fireworks 15 | 16 | 17 | 18 | 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /packages/inspector/src/client/components/icons/Google.tsx: -------------------------------------------------------------------------------- 1 | import type { IconProps } from "./type"; 2 | 3 | export function GoogleIcon({ style, ...props }: IconProps) { 4 | return ( 5 | 14 | Google 15 | 19 | 23 | 27 | 31 | 32 | ); 33 | } 34 | -------------------------------------------------------------------------------- /packages/inspector/src/client/components/icons/Groq.tsx: -------------------------------------------------------------------------------- 1 | import type { IconProps } from "./type"; 2 | 3 | export function GroqIconIcon(props: IconProps) { 4 | return ( 5 | 12 | Groq 13 | 14 | 15 | 20 | 21 | 22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /packages/inspector/src/client/components/icons/Inception.tsx: -------------------------------------------------------------------------------- 1 | import type { IconProps } from "./type"; 2 | 3 | export function InceptionIcon({ style, ...props }: IconProps) { 4 | return ( 5 | 14 | Inception 15 | 16 | 17 | 18 | 19 | 20 | 21 | 25 | 29 | 30 | 31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /packages/inspector/src/client/components/icons/Perplexity.tsx: -------------------------------------------------------------------------------- 1 | import type { IconProps } from "./type"; 2 | 3 | export function PerplexityIcon(props: IconProps) { 4 | return ( 5 | 12 | Perplexity 13 | 17 | 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /packages/inspector/src/client/components/icons/index.ts: -------------------------------------------------------------------------------- 1 | import { AmazonIcon } from "./Amazon"; 2 | import { AnthropicIcon } from "./Anthropic"; 3 | import { CerebrasIcon } from "./Cerebras"; 4 | import { CohereIcon } from "./Cohere"; 5 | import { DeepInfraIcon } from "./DeepInfra"; 6 | import { DeepSeekIcon } from "./DeepSeek"; 7 | import { FireworksIcon } from "./Fireworks"; 8 | import { GoogleIcon } from "./Google"; 9 | import { GroqIconIcon } from "./Groq"; 10 | import { InceptionIcon } from "./Inception"; 11 | import { MistralIcon } from "./Mistral"; 12 | import { OpenAIIcon } from "./OpenAI"; 13 | import { PerplexityIcon } from "./Perplexity"; 14 | import { xAIIcon } from "./xAI"; 15 | 16 | export const PROVIDER_ICONS: Record = { 17 | openai: OpenAIIcon, 18 | anthropic: AnthropicIcon, 19 | cerebras: CerebrasIcon, 20 | cohere: CohereIcon, 21 | deepInfra: DeepInfraIcon, 22 | deepseek: DeepSeekIcon, 23 | fireworks: FireworksIcon, 24 | google: GoogleIcon, 25 | groq: GroqIconIcon, 26 | inception: InceptionIcon, 27 | mistral: MistralIcon, 28 | amazon: AmazonIcon, 29 | perplexity: PerplexityIcon, 30 | xai: xAIIcon, 31 | }; 32 | -------------------------------------------------------------------------------- /packages/inspector/src/client/components/icons/type.ts: -------------------------------------------------------------------------------- 1 | import type { SVGProps } from "react"; 2 | 3 | export type IconProps = SVGProps; 4 | -------------------------------------------------------------------------------- /packages/inspector/src/client/components/icons/xAI.tsx: -------------------------------------------------------------------------------- 1 | import type { IconProps } from "./type"; 2 | 3 | export function xAIIcon({ style, ...props }: IconProps) { 4 | return ( 5 | 13 | xAI 14 | 15 | 19 | 23 | 27 | 31 | 32 | 33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /packages/inspector/src/client/components/ui/badge.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "../../lib/utils"; 2 | import { Slot } from "@radix-ui/react-slot"; 3 | import { type VariantProps, cva } from "class-variance-authority"; 4 | import type * as React from "react"; 5 | 6 | const badgeVariants = cva( 7 | "inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden", 8 | { 9 | variants: { 10 | variant: { 11 | default: 12 | "border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90", 13 | secondary: 14 | "border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90", 15 | destructive: 16 | "border-transparent bg-destructive text-background [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", 17 | outline: 18 | "text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground", 19 | }, 20 | }, 21 | defaultVariants: { 22 | variant: "default", 23 | }, 24 | } 25 | ); 26 | 27 | function Badge({ 28 | className, 29 | variant, 30 | asChild = false, 31 | ...props 32 | }: React.ComponentProps<"span"> & 33 | VariantProps & { asChild?: boolean }) { 34 | const Comp = asChild ? Slot : "span"; 35 | 36 | return ( 37 | 42 | ); 43 | } 44 | 45 | export { Badge, badgeVariants }; 46 | -------------------------------------------------------------------------------- /packages/inspector/src/client/components/ui/checkbox.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "../../lib/utils"; 2 | import * as CheckboxPrimitive from "@radix-ui/react-checkbox"; 3 | import { Check } from "lucide-react"; 4 | import type * as React from "react"; 5 | 6 | function Checkbox({ 7 | className, 8 | ...props 9 | }: React.ComponentProps) { 10 | return ( 11 | 19 | 23 | 24 | 25 | 26 | ); 27 | } 28 | 29 | export { Checkbox }; 30 | -------------------------------------------------------------------------------- /packages/inspector/src/client/components/ui/input.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "../../lib/utils"; 2 | import type * as React from "react"; 3 | 4 | function Input({ className, type, ...props }: React.ComponentProps<"input">) { 5 | return ( 6 | 17 | ); 18 | } 19 | 20 | export { Input }; 21 | -------------------------------------------------------------------------------- /packages/inspector/src/client/components/ui/label.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { cn } from "../../lib/utils"; 4 | import * as LabelPrimitive from "@radix-ui/react-label"; 5 | import type * as React from "react"; 6 | 7 | function Label({ 8 | className, 9 | required, 10 | ...props 11 | }: React.ComponentProps & { required?: boolean }) { 12 | return ( 13 | 22 | ); 23 | } 24 | 25 | export { Label }; 26 | -------------------------------------------------------------------------------- /packages/inspector/src/client/components/ui/popover.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { cn } from "../../lib/utils"; 4 | import * as PopoverPrimitive from "@radix-ui/react-popover"; 5 | import type * as React from "react"; 6 | 7 | function Popover({ 8 | ...props 9 | }: React.ComponentProps) { 10 | return ; 11 | } 12 | 13 | function PopoverTrigger({ 14 | ...props 15 | }: React.ComponentProps) { 16 | return ; 17 | } 18 | 19 | function PopoverContent({ 20 | className, 21 | align = "center", 22 | sideOffset = 4, 23 | ...props 24 | }: React.ComponentProps) { 25 | return ( 26 | 27 | 37 | 38 | ); 39 | } 40 | 41 | function PopoverAnchor({ 42 | ...props 43 | }: React.ComponentProps) { 44 | return ; 45 | } 46 | 47 | export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }; 48 | -------------------------------------------------------------------------------- /packages/inspector/src/client/components/ui/separator.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "../../lib/utils"; 2 | import * as SeparatorPrimitive from "@radix-ui/react-separator"; 3 | import type * as React from "react"; 4 | 5 | function Separator({ 6 | className, 7 | orientation = "horizontal", 8 | decorative = true, 9 | ...props 10 | }: React.ComponentProps) { 11 | return ( 12 | 22 | ); 23 | } 24 | 25 | export { Separator }; 26 | -------------------------------------------------------------------------------- /packages/inspector/src/client/components/ui/skeleton.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "../../lib/utils"; 2 | 3 | function Skeleton({ className, ...props }: React.ComponentProps<"div">) { 4 | return ( 5 |
10 | ); 11 | } 12 | 13 | export { Skeleton }; 14 | -------------------------------------------------------------------------------- /packages/inspector/src/client/components/ui/spinner.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "../../lib/utils"; 2 | 3 | function Spinner({ 4 | title = "loading", 5 | className, 6 | ...props 7 | }: React.ComponentProps<"svg"> & { title?: string }) { 8 | return ( 9 | 19 | {title} 20 | 25 | 26 | ); 27 | } 28 | 29 | export { Spinner }; 30 | -------------------------------------------------------------------------------- /packages/inspector/src/client/components/ui/textarea.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "../../lib/utils"; 2 | 3 | function Textarea({ className, ...props }: React.ComponentProps<"textarea">) { 4 | return ( 5 |