feat: integrate rjsf (#9120)

This commit is contained in:
Curry Yang
2025-09-09 16:43:23 +08:00
committed by GitHub
parent a9a4fa51dd
commit f1978d9dda
6 changed files with 255 additions and 8 deletions

176
package-lock.json generated
View File

@@ -7687,6 +7687,73 @@
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
}
},
"node_modules/@rjsf/core": {
"version": "6.0.0-beta.15",
"resolved": "https://registry.npmjs.org/@rjsf/core/-/core-6.0.0-beta.15.tgz",
"integrity": "sha512-VF6o/tetnYxgdv19qDgTdElkLQlPHH/2yn0Tyz7J8lUVucmYuPEctV6qo88NJUAtvts9EFLr7SYKaMQpfNJonQ==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"lodash": "^4.17.21",
"lodash-es": "^4.17.21",
"markdown-to-jsx": "^7.7.13",
"prop-types": "^15.8.1"
},
"engines": {
"node": ">=20"
},
"peerDependencies": {
"@rjsf/utils": "^6.0.0-beta.15",
"react": ">=18"
}
},
"node_modules/@rjsf/utils": {
"version": "6.0.0-beta.15",
"resolved": "https://registry.npmjs.org/@rjsf/utils/-/utils-6.0.0-beta.15.tgz",
"integrity": "sha512-AWF0/OVRpdkxNrp1JIrTL+ryzGGffm/IXSICq+c0Ju3HIdcV0kvRDrlQtmdgbIbjdcWogS1Gg97cFhk/DzgjhQ==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"fast-uri": "^3.0.6",
"json-schema-merge-allof": "^0.8.1",
"jsonpointer": "^5.0.1",
"lodash": "^4.17.21",
"lodash-es": "^4.17.21",
"react-is": "^18.3.1"
},
"engines": {
"node": ">=20"
},
"peerDependencies": {
"react": ">=18"
}
},
"node_modules/@rjsf/utils/node_modules/react-is": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
"integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
"dev": true,
"license": "MIT"
},
"node_modules/@rjsf/validator-ajv8": {
"version": "6.0.0-beta.15",
"resolved": "https://registry.npmjs.org/@rjsf/validator-ajv8/-/validator-ajv8-6.0.0-beta.15.tgz",
"integrity": "sha512-M40hmygBfR8NlaR3JEiBJIqs03Qli7F5DRS7ypFDBDsZLcSm1Kd6BWbjC3EM2IutYyTgxg6lvi1lQw95PqqcYw==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"ajv": "^8.17.1",
"ajv-formats": "^2.1.1",
"lodash": "^4.17.21",
"lodash-es": "^4.17.21"
},
"engines": {
"node": ">=20"
},
"peerDependencies": {
"@rjsf/utils": "^6.0.0-beta.15"
}
},
"node_modules/@rolldown/pluginutils": {
"version": "1.0.0-beta.32",
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.32.tgz",
@@ -13260,6 +13327,29 @@
"node": ">= 0.6"
}
},
"node_modules/compute-gcd": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/compute-gcd/-/compute-gcd-1.2.1.tgz",
"integrity": "sha512-TwMbxBNz0l71+8Sc4czv13h4kEqnchV9igQZBi6QUaz09dnz13juGnnaWWJTRsP3brxOoxeB4SA2WELLw1hCtg==",
"dev": true,
"dependencies": {
"validate.io-array": "^1.0.3",
"validate.io-function": "^1.0.2",
"validate.io-integer-array": "^1.0.0"
}
},
"node_modules/compute-lcm": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/compute-lcm/-/compute-lcm-1.1.2.tgz",
"integrity": "sha512-OFNPdQAXnQhDSKioX8/XYT6sdUlXwpeMjfd6ApxMJfyZ4GxmLR1xvMERctlYhlHwIiz6CSpBc2+qYKjHGZw4TQ==",
"dev": true,
"dependencies": {
"compute-gcd": "^1.2.1",
"validate.io-array": "^1.0.3",
"validate.io-function": "^1.0.2",
"validate.io-integer-array": "^1.0.0"
}
},
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -19185,6 +19275,31 @@
"integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==",
"license": "(AFL-2.1 OR BSD-3-Clause)"
},
"node_modules/json-schema-compare": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/json-schema-compare/-/json-schema-compare-0.2.2.tgz",
"integrity": "sha512-c4WYmDKyJXhs7WWvAWm3uIYnfyWFoIp+JEoX34rctVvEkMYCPGhXtvmFFXiffBbxfZsvQ0RNnV5H7GvDF5HCqQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"lodash": "^4.17.4"
}
},
"node_modules/json-schema-merge-allof": {
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/json-schema-merge-allof/-/json-schema-merge-allof-0.8.1.tgz",
"integrity": "sha512-CTUKmIlPJbsWfzRRnOXz+0MjIqvnleIXwFTzz+t9T86HnYX/Rozria6ZVGLktAU9e+NygNljveP+yxqtQp/Q4w==",
"dev": true,
"license": "MIT",
"dependencies": {
"compute-lcm": "^1.1.2",
"json-schema-compare": "^0.2.2",
"lodash": "^4.17.20"
},
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/json-schema-ref-parser": {
"version": "9.0.9",
"resolved": "https://registry.npmjs.org/json-schema-ref-parser/-/json-schema-ref-parser-9.0.9.tgz",
@@ -19916,6 +20031,13 @@
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"license": "MIT"
},
"node_modules/lodash-es": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==",
"dev": true,
"license": "MIT"
},
"node_modules/lodash.camelcase": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
@@ -20203,6 +20325,19 @@
"resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz",
"integrity": "sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g=="
},
"node_modules/markdown-to-jsx": {
"version": "7.7.13",
"resolved": "https://registry.npmjs.org/markdown-to-jsx/-/markdown-to-jsx-7.7.13.tgz",
"integrity": "sha512-DiueEq2bttFcSxUs85GJcQVrOr0+VVsPfj9AEUPqmExJ3f8P/iQNvZHltV4tm1XVhu1kl0vWBZWT3l99izRMaA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 10"
},
"peerDependencies": {
"react": ">= 0.14.0"
}
},
"node_modules/marked": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/marked/-/marked-5.1.2.tgz",
@@ -26890,6 +27025,44 @@
"builtins": "^1.0.3"
}
},
"node_modules/validate.io-array": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/validate.io-array/-/validate.io-array-1.0.6.tgz",
"integrity": "sha512-DeOy7CnPEziggrOO5CZhVKJw6S3Yi7e9e65R1Nl/RTN1vTQKnzjfvks0/8kQ40FP/dsjRAOd4hxmJ7uLa6vxkg==",
"dev": true,
"license": "MIT"
},
"node_modules/validate.io-function": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/validate.io-function/-/validate.io-function-1.0.2.tgz",
"integrity": "sha512-LlFybRJEriSuBnUhQyG5bwglhh50EpTL2ul23MPIuR1odjO7XaMLFV8vHGwp7AZciFxtYOeiSCT5st+XSPONiQ==",
"dev": true
},
"node_modules/validate.io-integer": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/validate.io-integer/-/validate.io-integer-1.0.5.tgz",
"integrity": "sha512-22izsYSLojN/P6bppBqhgUDjCkr5RY2jd+N2a3DCAUey8ydvrZ/OkGvFPR7qfOpwR2LC5p4Ngzxz36g5Vgr/hQ==",
"dev": true,
"dependencies": {
"validate.io-number": "^1.0.3"
}
},
"node_modules/validate.io-integer-array": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/validate.io-integer-array/-/validate.io-integer-array-1.0.0.tgz",
"integrity": "sha512-mTrMk/1ytQHtCY0oNO3dztafHYyGU88KL+jRxWuzfOmQb+4qqnWmI+gykvGp8usKZOM0H7keJHEbRaFiYA0VrA==",
"dev": true,
"dependencies": {
"validate.io-array": "^1.0.3",
"validate.io-integer": "^1.0.4"
}
},
"node_modules/validate.io-number": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/validate.io-number/-/validate.io-number-1.0.3.tgz",
"integrity": "sha512-kRAyotcbNaSYoDnXvb4MHg/0a1egJdLwS6oJ38TJY7aw9n93Fl/3blIXdyYvPOp55CNxywooG/3BcrwNrBpcSg==",
"dev": true
},
"node_modules/validator": {
"version": "13.15.0",
"resolved": "https://registry.npmjs.org/validator/-/validator-13.15.0.tgz",
@@ -28390,6 +28563,9 @@
"@react-router/fs-routes": "^7.7.0",
"@react-router/node": "^7.7.0",
"@react-router/serve": "^7.7.0",
"@rjsf/core": "^6.0.0-beta.15",
"@rjsf/utils": "^6.0.0-beta.15",
"@rjsf/validator-ajv8": "^6.0.0-beta.15",
"@sentry/electron": "^6.5.0",
"@tailwindcss/typography": "^0.5.16",
"@tanstack/react-virtual": "3.13.6",

View File

@@ -115,6 +115,9 @@
"@react-router/fs-routes": "^7.7.0",
"@react-router/node": "^7.7.0",
"@react-router/serve": "^7.7.0",
"@rjsf/core": "^6.0.0-beta.15",
"@rjsf/utils": "^6.0.0-beta.15",
"@rjsf/validator-ajv8": "^6.0.0-beta.15",
"@sentry/electron": "^6.5.0",
"@tailwindcss/typography": "^0.5.16",
"@tanstack/react-virtual": "3.13.6",

View File

@@ -46,7 +46,7 @@ type ToolItem = Tool & { type: 'tools' } & CommonItemProps;
type ResourceItem = Resource & { type: 'resources' } & CommonItemProps;
type ResourceTemplateItem = ResourceTemplate & { type: 'resources' } & CommonItemProps;
type PromptItem = Prompt & { type: 'prompts' } & CommonItemProps;
type PrimitiveSubItemTypes = ToolItem | ResourceItem | ResourceTemplateItem | PromptItem;
export type PrimitiveSubItemTypes = ToolItem | ResourceItem | ResourceTemplateItem | PromptItem;
interface PrimitiveTypeItem extends CommonItemProps {
type: McpServerPrimitiveTypes;
name: string;
@@ -88,7 +88,7 @@ const McpPage = () => {
const [mcpServerData, setMcpServerData] = useState<McpServerData | null>(null);
const [collapsedPrimitives, setCollapsedPrimitives] = useState<McpServerPrimitiveTypes[]>([]);
const [selectedPrimitiveItem, setSelectedPrimitiveItem] = useState<PrimitiveSubItemTypes | null>(null);
console.log('selectedPrimitiveItem', selectedPrimitiveItem);
const getPrimitiveCollection = () => {
const collection: (PrimitiveTypeItem | PrimitiveSubItemTypes)[] = [];
if (mcpServerData) {
@@ -437,7 +437,11 @@ const McpPage = () => {
<OrganizationTabList currentPage="mcp" />
<PanelGroup autoSaveId="insomnia-panels" id="insomnia-panels" direction={direction}>
<Panel id="mcp-request-pane" order={1} minSize={10} className="pane-one theme--pane">
<McpRequestPane environment={activeEnvironment} readyState={readyState} />
<McpRequestPane
selectedPrimitiveItem={selectedPrimitiveItem}
environment={activeEnvironment}
readyState={readyState}
/>
</Panel>
<Panel id="mcp-response-pane" order={2} minSize={10} className="pane-two theme--pane">
<ErrorBoundary showAlert>

View File

@@ -1,13 +1,19 @@
import React, { type FC } from 'react';
import type { IChangeEvent } from '@rjsf/core';
import type { RJSFSchema } from '@rjsf/utils';
import validator from '@rjsf/validator-ajv8';
import React, { type FC, useEffect, useRef, useState } from 'react';
import { Button, Heading, Tab, TabList, TabPanel, Tabs } from 'react-aria-components';
import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels';
import type { PrimitiveSubItemTypes } from '~/routes/organization.$organizationId.project.$projectId.workspace.$workspaceId.mcp';
import { InsomniaRjsfForm } from '~/ui/components/rjsf';
import { type AuthTypes } from '../../../common/constants';
import type { Environment } from '../../../models/environment';
import { getAuthObjectOrNull } from '../../../network/authentication';
import { useMcpRequestLoaderData } from '../../../routes/organization.$organizationId.project.$projectId.workspace.$workspaceId.mcp.request.$requestId';
import { useRequestPatcher } from '../../hooks/use-request';
import { CodeEditor } from '../.client/codemirror/code-editor';
import { CodeEditor, type CodeEditorHandle } from '../.client/codemirror/code-editor';
import { AuthWrapper } from '../editors/auth/auth-wrapper';
import { readOnlyWebsocketPairs, RequestHeadersEditor } from '../editors/request-headers-editor';
import { Pane } from '../panes/pane';
@@ -35,10 +41,13 @@ const PaneReadOnlyBanner = () => {
interface Props {
environment: Environment | null;
readyState: boolean;
selectedPrimitiveItem?: PrimitiveSubItemTypes | null;
}
export const McpRequestPane: FC<Props> = ({ environment, readyState }) => {
export const McpRequestPane: FC<Props> = ({ environment, readyState, selectedPrimitiveItem }) => {
const { activeRequest } = useMcpRequestLoaderData()!;
const [formData, setFormData] = useState({});
const paramEditorRef = useRef<CodeEditorHandle>(null);
const requestId = activeRequest._id;
const headersCount = activeRequest.headers.filter(h => !h.disabled).length + readOnlyWebsocketPairs.length;
@@ -48,6 +57,25 @@ export const McpRequestPane: FC<Props> = ({ environment, readyState }) => {
const uniqueKey = `${environment?.modified}::${requestId}`;
const requestAuth = getAuthObjectOrNull(activeRequest.authentication);
const isNoneOrInherited = requestAuth?.type === 'none' || requestAuth === null;
const toolsSchema = selectedPrimitiveItem?.type === 'tools' ? selectedPrimitiveItem.inputSchema : undefined;
const handleRjsfFormChange = (e: IChangeEvent) => {
setFormData(e.formData);
paramEditorRef.current?.setValue(JSON.stringify(e.formData, null, 2));
};
useEffect(() => {
setFormData({});
paramEditorRef.current?.setValue('');
}, [selectedPrimitiveItem]);
const handleSend = () => {
window.main.mcp.primitive.callTool({
name: selectedPrimitiveItem?.name || '',
parameters: formData,
requestId: requestId,
});
};
return (
<Pane type="request">
@@ -110,14 +138,22 @@ export const McpRequestPane: FC<Props> = ({ environment, readyState }) => {
<Heading className="text-xs font-bold uppercase text-[--hl]">Parameter Builder</Heading>
<div className="flex items-center gap-2">
<Button
isDisabled={readyState}
isDisabled={!readyState}
onClick={handleSend}
className="asma-pressed:bg-[--hl-sm] flex h-full w-[14ch] flex-shrink-0 items-center justify-start gap-2 rounded-sm px-2 py-1 text-sm text-[--color-font] ring-1 ring-transparent transition-colors hover:bg-[--hl-xs] focus:bg-[--hl-sm] focus:ring-inset focus:ring-[--hl-md] aria-selected:bg-[--hl-xs] aria-selected:hover:bg-[--hl-sm] aria-selected:focus:bg-[--hl-sm]"
>
Send
</Button>
</div>
</div>
TODO Parameter Builder UI
{toolsSchema && (
<InsomniaRjsfForm
formData={formData}
onChange={handleRjsfFormChange}
schema={toolsSchema as RJSFSchema}
validator={validator}
/>
)}
</div>
</Panel>
<PanelResizeHandle className="h-[1px] w-full bg-[--hl-md]" />
@@ -126,6 +162,7 @@ export const McpRequestPane: FC<Props> = ({ environment, readyState }) => {
<Heading className="p-4 text-xs font-bold uppercase text-[--hl]">Parameter Overview</Heading>
<div className="flex-1 overflow-hidden">
<CodeEditor
ref={paramEditorRef}
id="mcp-parameter-overview-editor"
showPrettifyButton
dynamicHeight

View File

@@ -0,0 +1,5 @@
import { withTheme } from '@rjsf/core';
import theme from './theme';
export const InsomniaRjsfForm = withTheme(theme);

View File

@@ -0,0 +1,22 @@
import type { ThemeProps } from '@rjsf/core';
import type { RegistryWidgetsType, WidgetProps } from '@rjsf/utils';
const CustomTextWidget = (props: WidgetProps) => {
return (
<input
className="w-full rounded-sm border border-solid border-[--hl-sm] bg-[--color-bg] py-1 pl-2 pr-7 text-[--color-font] transition-colors placeholder:italic focus:outline-none focus:ring-1 focus:ring-[--hl-md]"
onChange={e => props.onChange(e.target.value)}
/>
);
};
const themeWidgets: RegistryWidgetsType = {
TextWidget: CustomTextWidget,
};
const themeTemplates = {};
const ThemeObject: ThemeProps = {
widgets: themeWidgets,
templates: themeTemplates,
};
export default ThemeObject;