From ef89ff41786ae0c8cb9322c312da99c6dee73074 Mon Sep 17 00:00:00 2001 From: Kent Wang Date: Tue, 16 Sep 2025 11:22:27 +0800 Subject: [PATCH] Chore: Move MCP request page into debug router path (#9140) * refactor mcp page router path * fix lint issue --- packages/insomnia/src/main/network/mcp.ts | 1 + ...aceId.debug.request.$requestId.connect.tsx | 12 + ....$workspaceId.debug.request.$requestId.tsx | 21 + ...projectId.workspace.$workspaceId.debug.tsx | 12 +- ...spaceId.mcp.request.$requestId.connect.tsx | 88 --- ...ce.$workspaceId.mcp.request.$requestId.tsx | 44 -- ....$projectId.workspace.$workspaceId.mcp.tsx | 513 +----------------- ...ionId.project.$projectId.workspace.new.tsx | 2 +- .../src/ui/components/mcp/event-view.tsx | 4 +- .../src/ui/components/mcp/mcp-pane.tsx | 489 +++++++++++++++++ .../ui/components/mcp/mcp-request-pane.tsx | 7 +- .../src/ui/components/mcp/mcp-url-bar.tsx | 2 +- .../modals/request-settings-modal.tsx | 3 +- .../src/ui/components/tags/method-tag.tsx | 10 +- .../websockets/realtime-response-pane.tsx | 19 +- .../insomnia/src/ui/hooks/use-insomnia-tab.ts | 4 +- 16 files changed, 572 insertions(+), 659 deletions(-) delete mode 100644 packages/insomnia/src/routes/organization.$organizationId.project.$projectId.workspace.$workspaceId.mcp.request.$requestId.connect.tsx delete mode 100644 packages/insomnia/src/routes/organization.$organizationId.project.$projectId.workspace.$workspaceId.mcp.request.$requestId.tsx create mode 100644 packages/insomnia/src/ui/components/mcp/mcp-pane.tsx diff --git a/packages/insomnia/src/main/network/mcp.ts b/packages/insomnia/src/main/network/mcp.ts index f79f35a3d5..c2cd3de40c 100644 --- a/packages/insomnia/src/main/network/mcp.ts +++ b/packages/insomnia/src/main/network/mcp.ts @@ -437,6 +437,7 @@ const openMcpClientConnection = async (options: OpenMcpClientConnectionOptions) message: error.message || 'Something went wrong', }); console.error(`Failed to create ${options.transportType} transport: ${error}`); + return; } mcpConnections.set(requestId, mcpClient as McpClient); const serverCapabilities = mcpClient.getServerCapabilities(); diff --git a/packages/insomnia/src/routes/organization.$organizationId.project.$projectId.workspace.$workspaceId.debug.request.$requestId.connect.tsx b/packages/insomnia/src/routes/organization.$organizationId.project.$projectId.workspace.$workspaceId.debug.request.$requestId.connect.tsx index 3e84977d02..093f3f351c 100644 --- a/packages/insomnia/src/routes/organization.$organizationId.project.$projectId.workspace.$workspaceId.debug.request.$requestId.connect.tsx +++ b/packages/insomnia/src/routes/organization.$organizationId.project.$projectId.workspace.$workspaceId.debug.request.$requestId.connect.tsx @@ -4,6 +4,7 @@ import { href } from 'react-router'; import type { ChangeBufferEvent } from '~/common/database'; import type { CookieJar } from '~/models/cookie-jar'; import * as requestOperations from '~/models/helpers/request-operations'; +import { isMcpRequest, type TransportType } from '~/models/mcp-request'; import type { RequestAuthentication, RequestHeader } from '~/models/request'; import { isEventStreamRequest, isGraphqlSubscriptionRequest } from '~/models/request'; import { isRequestMeta } from '~/models/request-meta'; @@ -22,6 +23,7 @@ export interface ConnectActionParams { authentication: RequestAuthentication; cookieJar: CookieJar; suppressUserAgent: boolean; + transportType?: TransportType; query?: Record; } @@ -91,6 +93,16 @@ export async function clientAction({ params, request }: Route.ClientActionArgs) query: rendered.query || {}, }); } + if (isMcpRequest(req)) { + window.main.mcp.connect({ + requestId, + workspaceId, + transportType: rendered.transportType || 'streamable-http', + url: rendered.url, + headers: rendered.headers, + authentication: rendered.authentication, + }); + } // HACK: even more elaborate hack to get the request to update return new Promise(resolve => { const unsubscribe = window.main.on('db.changes', async (_, changes: ChangeBufferEvent[]) => { diff --git a/packages/insomnia/src/routes/organization.$organizationId.project.$projectId.workspace.$workspaceId.debug.request.$requestId.tsx b/packages/insomnia/src/routes/organization.$organizationId.project.$projectId.workspace.$workspaceId.debug.request.$requestId.tsx index dd2e765311..f233075b30 100644 --- a/packages/insomnia/src/routes/organization.$organizationId.project.$projectId.workspace.$workspaceId.debug.request.$requestId.tsx +++ b/packages/insomnia/src/routes/organization.$organizationId.project.$projectId.workspace.$workspaceId.debug.request.$requestId.tsx @@ -7,6 +7,7 @@ import { type GrpcRequest, isGrpcRequestId } from '~/models/grpc-request'; import type { GrpcRequestMeta } from '~/models/grpc-request-meta'; import * as requestOperations from '~/models/helpers/request-operations'; import { isMcpRequest, type McpRequest } from '~/models/mcp-request'; +import type { McpResponse } from '~/models/mcp-response'; import type { MockRoute } from '~/models/mock-route'; import type { MockServer } from '~/models/mock-server'; import { isGraphqlSubscriptionRequest } from '~/models/request'; @@ -46,6 +47,14 @@ export interface GrpcRequestLoaderData { responses: []; requestVersions: RequestVersion[]; } + +export interface McpRequestLoaderData { + activeRequest: McpRequest; + activeRequestMeta: RequestMeta; + activeResponse: McpResponse; + responses: McpResponse[]; + requestVersions: RequestVersion[]; +} export interface RequestLoaderData { activeRequest: Request; activeRequestMeta: RequestMeta; @@ -155,6 +164,18 @@ export async function clientLoader({ params }: Route.ClientLoaderArgs) { requestPayload: socketIOPayload, } as SocketIORequestLoaderData; } + + if (isMcpRequest(activeRequest)) { + // TODO - add mcp request payload model + return { + activeRequest, + activeRequestMeta, + activeResponse, + responses, + requestVersions: await models.requestVersion.findByParentId(requestId), + } as McpRequestLoaderData; + } + return { activeRequest, activeRequestMeta, diff --git a/packages/insomnia/src/routes/organization.$organizationId.project.$projectId.workspace.$workspaceId.debug.tsx b/packages/insomnia/src/routes/organization.$organizationId.project.$projectId.workspace.$workspaceId.debug.tsx index 959e3336d2..34ec9816c1 100644 --- a/packages/insomnia/src/routes/organization.$organizationId.project.$projectId.workspace.$workspaceId.debug.tsx +++ b/packages/insomnia/src/routes/organization.$organizationId.project.$projectId.workspace.$workspaceId.debug.tsx @@ -93,6 +93,7 @@ import { EnvironmentPicker } from '~/ui/components/environment-picker'; import { ErrorBoundary } from '~/ui/components/error-boundary'; import { Icon } from '~/ui/components/icon'; import { useDocBodyKeyboardShortcuts } from '~/ui/components/keydown-binder'; +import { McpPane } from '~/ui/components/mcp/mcp-pane'; import { showModal } from '~/ui/components/modals'; import { AskModal } from '~/ui/components/modals/ask-modal'; import { CookiesModal } from '~/ui/components/modals/cookies-modal'; @@ -230,6 +231,15 @@ const RequestTiming = ({ requestId }: { requestId: string }) => { ) : null; }; +const DebugEntry = () => { + const { activeWorkspace } = useWorkspaceLoaderData()!; + if (activeWorkspace.scope === 'mcp') { + // MCP request under mcp workspace has different layout so we need to render a different component + return ; + } + return ; +}; + const Debug = () => { const { activeWorkspace, @@ -1265,7 +1275,7 @@ const Debug = () => { ); }; -export default Debug; +export default DebugEntry; const ScratchPadTutorialPanel = () => { const [signUpTipDismissedState, setSignUpTipDismissedState] = useLocalStorage<{ diff --git a/packages/insomnia/src/routes/organization.$organizationId.project.$projectId.workspace.$workspaceId.mcp.request.$requestId.connect.tsx b/packages/insomnia/src/routes/organization.$organizationId.project.$projectId.workspace.$workspaceId.mcp.request.$requestId.connect.tsx deleted file mode 100644 index c7dac15d48..0000000000 --- a/packages/insomnia/src/routes/organization.$organizationId.project.$projectId.workspace.$workspaceId.mcp.request.$requestId.connect.tsx +++ /dev/null @@ -1,88 +0,0 @@ -import { href } from 'react-router'; - -import type { ChangeBufferEvent } from '~/common/database'; -import type { CookieJar } from '~/models/cookie-jar'; -import * as requestOperations from '~/models/helpers/request-operations'; -import { type TransportType } from '~/models/mcp-request'; -import type { RequestAuthentication, RequestHeader } from '~/models/request'; -import { isRequestMeta } from '~/models/request-meta'; -import { invariant } from '~/utils/invariant'; -import { createFetcherSubmitHook } from '~/utils/router'; - -import type { Route } from './+types/organization.$organizationId.project.$projectId.workspace.$workspaceId.mcp.request.$requestId.connect'; - -export interface ConnectActionParams { - url: string; - headers: RequestHeader[]; - authentication: RequestAuthentication; - cookieJar: CookieJar; - suppressUserAgent: boolean; - transportType?: TransportType; - query?: Record; -} - -export async function clientAction({ params, request }: Route.ClientActionArgs) { - const { requestId, workspaceId } = params; - - const req = await requestOperations.getById(requestId); - invariant(req, 'Request not found'); - invariant(workspaceId, 'Workspace ID is required'); - const rendered = (await request.json()) as ConnectActionParams; - - window.main.mcp.connect({ - requestId, - workspaceId, - transportType: rendered.transportType || 'streamable-http', - url: rendered.url, - headers: rendered.headers, - authentication: rendered.authentication, - }); - - // HACK: even more elaborate hack to get the request to update - return new Promise(resolve => { - const unsubscribe = window.main.on('db.changes', async (_, changes: ChangeBufferEvent[]) => { - for (const change of changes) { - const [event, doc] = change; - if (isRequestMeta(doc) && doc.parentId === requestId && event === 'update') { - resolve(null); - unsubscribe(); - return; - } - } - }); - }); -} - -export const useRequestConnectActionFetcher = createFetcherSubmitHook( - submit => - ({ - organizationId, - projectId, - workspaceId, - requestId, - connectParams, - }: { - organizationId: string; - projectId: string; - workspaceId: string; - requestId: string; - connectParams: ConnectActionParams; - }) => { - const url = href( - '/organization/:organizationId/project/:projectId/workspace/:workspaceId/mcp/request/:requestId/connect', - { - organizationId, - projectId, - workspaceId, - requestId, - }, - ); - - return submit(JSON.stringify(connectParams), { - action: url, - method: 'POST', - encType: 'application/json', - }); - }, - clientAction, -); diff --git a/packages/insomnia/src/routes/organization.$organizationId.project.$projectId.workspace.$workspaceId.mcp.request.$requestId.tsx b/packages/insomnia/src/routes/organization.$organizationId.project.$projectId.workspace.$workspaceId.mcp.request.$requestId.tsx deleted file mode 100644 index ecd2b25667..0000000000 --- a/packages/insomnia/src/routes/organization.$organizationId.project.$projectId.workspace.$workspaceId.mcp.request.$requestId.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import { Outlet, useRouteLoaderData } from 'react-router'; - -import type { BaseModel } from '~/models'; -import * as models from '~/models'; -import * as requestOperations from '~/models/helpers/request-operations'; -import type { McpRequest } from '~/models/mcp-request'; -import { invariant } from '~/utils/invariant'; - -import type { Route } from './+types/organization.$organizationId.project.$projectId.workspace.$workspaceId.mcp.request.$requestId'; -export default Outlet; - -export async function clientLoader({ params }: Route.ClientLoaderArgs) { - const { requestId, workspaceId } = params; - - const activeRequest = (await requestOperations.getById(requestId)) as McpRequest; - const activeWorkspaceMeta = await models.workspaceMeta.getByParentId(workspaceId); - invariant(activeWorkspaceMeta, 'Active workspace meta not found'); - const activeRequestMeta = await models.requestMeta.updateOrCreateByParentId(requestId, { lastActive: Date.now() }); - invariant(activeRequestMeta, 'Request meta not found'); - const { filterResponsesByEnv } = await models.settings.get(); - - const activeResponse = activeRequestMeta.activeResponseId - ? await models.mcpResponse.getById(activeRequestMeta.activeResponseId) - : await models.mcpResponse.getLatestForRequestId(requestId, activeWorkspaceMeta.activeEnvironmentId); - const allResponses = await models.mcpResponse.findByParentId(requestId); - const filteredResponses = allResponses.filter(r => r.environmentId === activeWorkspaceMeta.activeEnvironmentId); - const responses = (filterResponsesByEnv ? filteredResponses : allResponses).sort((a: BaseModel, b: BaseModel) => - a.created > b.created ? -1 : 1, - ); - - return { - activeRequest, - activeRequestMeta, - activeResponse, - responses, - requestVersions: await models.requestVersion.findByParentId(requestId), - }; -} - -export function useMcpRequestLoaderData() { - return useRouteLoaderData( - 'routes/organization.$organizationId.project.$projectId.workspace.$workspaceId.mcp.request.$requestId', - ); -} diff --git a/packages/insomnia/src/routes/organization.$organizationId.project.$projectId.workspace.$workspaceId.mcp.tsx b/packages/insomnia/src/routes/organization.$organizationId.project.$projectId.workspace.$workspaceId.mcp.tsx index 3f4d7868ca..1ffbc2a114 100644 --- a/packages/insomnia/src/routes/organization.$organizationId.project.$projectId.workspace.$workspaceId.mcp.tsx +++ b/packages/insomnia/src/routes/organization.$organizationId.project.$projectId.workspace.$workspaceId.mcp.tsx @@ -1,509 +1,24 @@ -import { useVirtualizer } from '@tanstack/react-virtual'; -import { useCallback, useEffect, useRef, useState } from 'react'; -import { - Breadcrumb, - Breadcrumbs, - Button, - GridList, - GridListItem, - Input, - SearchField, - ToggleButton, - Tooltip, - TooltipTrigger, -} from 'react-aria-components'; -import { type ImperativePanelGroupHandle, Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels'; -import { NavLink, redirect, useParams } from 'react-router'; -import { useLocalStorage } from 'react-use'; +import { redirect } from 'react-router'; -import { DEFAULT_SIDEBAR_SIZE } from '~/common/constants'; -import { - getDefaultServerCapabilities, - type McpServerData, - METHOD_INITIALIZE, - METHOD_LIST_PROMPTS, - METHOD_LIST_RESOURCE_TEMPLATES, - METHOD_LIST_RESOURCES, - METHOD_LIST_TOOLS, -} from '~/common/mcp-utils'; -import type { McpEvent, McpMessageEvent } from '~/main/network/mcp'; import * as models from '~/models'; -import type { McpRequest, McpServerPrimitiveTypes } from '~/models/mcp-request'; -import { useRootLoaderData } from '~/root'; -import { useWorkspaceLoaderData } from '~/routes/organization.$organizationId.project.$projectId.workspace.$workspaceId'; -import { useMcpRequestLoaderData } from '~/routes/organization.$organizationId.project.$projectId.workspace.$workspaceId.mcp.request.$requestId'; -import { McpActionsDropdown } from '~/ui/components/dropdowns/mcp-actions-dropdown'; -import { WorkspaceDropdown } from '~/ui/components/dropdowns/workspace-dropdown'; -import { EnvironmentPicker } from '~/ui/components/environment-picker'; -import { ErrorBoundary } from '~/ui/components/error-boundary'; -import { Icon } from '~/ui/components/icon'; -import { McpRequestPane } from '~/ui/components/mcp/mcp-request-pane'; -import { - type PrimitiveSubItem, - type PrimitiveTypeItem, - type PromptItem, - type ResourceItem, - type ResourceTemplateItem, - type ToolItem, -} from '~/ui/components/mcp/types'; -import { WorkspaceEnvironmentsEditModal } from '~/ui/components/modals/workspace-environments-edit-modal'; -import { OrganizationTabList } from '~/ui/components/tabs/tab-list'; -import { McpRealtimeResponsePane } from '~/ui/components/websockets/realtime-response-pane'; -import { INSOMNIA_TAB_HEIGHT } from '~/ui/constant'; -import { useReadyState } from '~/ui/hooks/use-ready-state'; import { invariant } from '~/utils/invariant'; import type { Route } from './+types/organization.$organizationId.project.$projectId.workspace.$workspaceId.mcp'; export async function clientLoader({ params }: Route.ClientLoaderArgs) { - if (!params.requestId) { - const { projectId, workspaceId, organizationId } = params; - invariant(workspaceId, 'Workspace ID is required'); - invariant(projectId, 'Project ID is required'); - const activeWorkspace = await models.workspace.getById(workspaceId); - invariant(activeWorkspace, 'Workspace not found'); - // Mcp collection only have one request - const activeRequest = await models.mcpRequest.getByParentId(workspaceId); - invariant(activeRequest, 'MCP Request not found'); - - if (activeRequest) { - return redirect( - `/organization/${organizationId}/project/${projectId}/workspace/${workspaceId}/mcp/request/${activeRequest._id}`, - ); - } + const { projectId, workspaceId, organizationId } = params; + invariant(workspaceId, 'Workspace ID is required'); + invariant(projectId, 'Project ID is required'); + const activeWorkspace = await models.workspace.getById(workspaceId); + invariant(activeWorkspace, 'Workspace not found'); + // Mcp collection only have one request + const activeRequest = await models.mcpRequest.getByParentId(workspaceId); + invariant(activeRequest, 'MCP Request not found'); + // Redirect to the debug page of the only request in the MCP workspace + if (activeRequest) { + return redirect( + `/organization/${organizationId}/project/${projectId}/workspace/${workspaceId}/debug/request/${activeRequest._id}`, + ); } return null; } - -const McpPage = () => { - const { organizationId, projectId, workspaceId } = useParams() as { - organizationId: string; - projectId: string; - workspaceId: string; - }; - const { activeRequest, activeResponse } = useMcpRequestLoaderData()!; - const sidebarPanelRef = useRef(null); - const [isEnvironmentPickerOpen, setIsEnvironmentPickerOpen] = useState(false); - const [isEnvironmentModalOpen, setEnvironmentModalOpen] = useState(false); - const [allExpanded, setAllExpanded] = useState(true); - const [filter, setFilter] = useLocalStorage(`${workspaceId}:mcp-list-filter`); - const { settings } = useRootLoaderData()!; - const [mcpServerData, setMcpServerData] = useState(null); - const [collapsedPrimitives, setCollapsedPrimitives] = useState([]); - const [selectedPrimitiveItem, setSelectedPrimitiveItem] = useState(null); - - const getPrimitiveCollection = () => { - const collection: (PrimitiveTypeItem | PrimitiveSubItem)[] = []; - if (mcpServerData) { - const { primitives } = mcpServerData; - const { tools, resources, resourceTemplates, prompts } = primitives; - if (tools.length > 0) { - collection.push({ - type: 'tools', - name: 'Tools', - collapsed: collapsedPrimitives.includes('tools'), - itemLevel: 0, - hide: false, - }); - const hide = collapsedPrimitives.includes('tools'); - collection.push(...(tools.map(t => ({ ...t, type: 'tools', itemLevel: 1, hide })) as ToolItem[])); - } - if (resources.length > 0 || resourceTemplates.length > 0) { - collection.push({ - type: 'resources', - name: 'Resources', - collapsed: collapsedPrimitives.includes('resources'), - itemLevel: 0, - hide: false, - }); - const hide = collapsedPrimitives.includes('resources'); - collection.push(...(resources.map(r => ({ ...r, type: 'resources', itemLevel: 1, hide })) as ResourceItem[])); - collection.push( - ...(resourceTemplates.map(rt => ({ - ...rt, - type: 'resources', - itemLevel: 1, - hide, - })) as ResourceTemplateItem[]), - ); - } - if (prompts.length > 0) { - collection.push({ - type: 'prompts', - name: 'Prompts', - collapsed: collapsedPrimitives.includes('prompts'), - itemLevel: 0, - hide: false, - }); - const hide = collapsedPrimitives.includes('prompts'); - collection.push(...(prompts.map(p => ({ ...p, type: 'prompts', itemLevel: 1, hide })) as PromptItem[])); - } - } - return collection; - }; - - const getServerCapabilities = () => { - const serverCapabilities = getDefaultServerCapabilities(); - if (mcpServerData) { - const { tools, resources, prompts } = mcpServerData.serverCapabilities; - if (tools) { - serverCapabilities.tools.enabled = true; - serverCapabilities.tools.listChanged = !!tools.listChanged; - } - if (resources) { - serverCapabilities.resources.enabled = true; - serverCapabilities.resources.listChanged = !!resources.listChanged; - serverCapabilities.resources.subscribe = !!resources.subscribe; - } - if (prompts) { - serverCapabilities.prompts.enabled = true; - serverCapabilities.prompts.listChanged = !!prompts.listChanged; - } - } - return serverCapabilities; - }; - - // TODO Support filter - const visibleCollection = getPrimitiveCollection().filter(item => !item.hide); - const serverCapabilities = getServerCapabilities(); - const allowSubscribeResources = serverCapabilities.resources.enabled && serverCapabilities.resources.subscribe; - const enableNotification = - serverCapabilities.tools.listChanged || - serverCapabilities.resources.listChanged || - serverCapabilities.prompts.listChanged; - // TODO Use these variables to enable notification - console.log(`enableNotification`, enableNotification); - console.log(`allowSubscribeResources`, allowSubscribeResources); - - const requestId = activeRequest._id; - const { activeEnvironment } = useWorkspaceLoaderData()!; - const readyState = useReadyState({ requestId, protocol: 'mcp' }); - - const parentRef = useRef(null); - const virtualizer = useVirtualizer({ - getScrollElement: () => parentRef.current, - count: visibleCollection.length, - estimateSize: useCallback(() => 32, []), - overscan: 20, - getItemKey: index => { - const item = visibleCollection[index]; - return `${item.itemLevel}::${item.type}::${item.name}`; - }, - }); - - function toggleSidebar() { - const layout = sidebarPanelRef.current?.getLayout(); - - if (!layout) { - return; - } - - if (layout && layout[0] > 0) { - layout[0] = 0; - } else { - layout[0] = DEFAULT_SIDEBAR_SIZE; - } - - sidebarPanelRef.current?.setLayout(layout); - } - - useEffect(() => { - const unsubscribe = window.main.on('toggle-sidebar', toggleSidebar); - - return unsubscribe; - }, []); - - const [direction, setDirection] = useState<'horizontal' | 'vertical'>( - settings.forceVerticalLayout ? 'vertical' : 'horizontal', - ); - - useEffect(() => { - if (settings.forceVerticalLayout) { - setDirection('vertical'); - return () => {}; - } - // Listen on media query changes - const mediaQuery = window.matchMedia('(max-width: 880px)'); - setDirection(mediaQuery.matches ? 'vertical' : 'horizontal'); - - const handleChange = (e: MediaQueryListEvent) => { - setDirection(e.matches ? 'vertical' : 'horizontal'); - }; - - mediaQuery.addEventListener('change', handleChange); - - return () => { - mediaQuery.removeEventListener('change', handleChange); - }; - }, [settings.forceVerticalLayout, direction]); - - useEffect(() => { - const updateServerData = async () => { - const findFirstMatchEventData = (mcpEvents: McpEvent[], method: string) => { - const firstMatchEvent = mcpEvents.find( - event => 'method' in event && event.method === method, - ) as McpMessageEvent; - if (firstMatchEvent) { - return firstMatchEvent.data.result; - } - return undefined; - }; - const activeResponseId = activeResponse?._id; - if (activeResponseId) { - const allEvents = await window.main.mcp.event.findMany({ responseId: activeResponseId }); - const serverCapabilities = - findFirstMatchEventData(allEvents, METHOD_INITIALIZE)?.capabilities || getDefaultServerCapabilities(); - const tools = findFirstMatchEventData(allEvents, METHOD_LIST_TOOLS)?.tools || []; - const resources = findFirstMatchEventData(allEvents, METHOD_LIST_RESOURCES)?.resources || []; - const resourceTemplates = - findFirstMatchEventData(allEvents, METHOD_LIST_RESOURCE_TEMPLATES)?.resourceTemplates || []; - const prompts = findFirstMatchEventData(allEvents, METHOD_LIST_PROMPTS)?.prompts || []; - const mcpServerData = { - serverCapabilities: serverCapabilities, - primitives: { - tools, - resources, - resourceTemplates, - prompts, - }, - } as McpServerData; - setMcpServerData(mcpServerData); - } - }; - if (readyState) { - // Get MCP server data when connection is ready - updateServerData(); - } - }, [readyState, activeResponse?._id]); - - return ( - - -
-
-
- - - - - - - - - - - -
-
- -
-
- setEnvironmentModalOpen(true)} - /> -
-
- -
-
- - -
- -
-
- - { - const newState = !allExpanded; - if (newState) { - setCollapsedPrimitives([]); - } else { - setCollapsedPrimitives(['tools', 'resources', 'prompts']); - } - setAllExpanded(newState); - }} - className="flex aspect-square h-full items-center justify-center rounded-sm text-sm text-[--color-font] ring-1 ring-transparent transition-all hover:bg-[--hl-xs] focus:ring-inset focus:ring-[--hl-md]" - > - {({ isSelected }) => ( - - )} - - - {allExpanded ? 'Collapse all' : 'Expand all'} - - -
- -
- { - const id = key.toString(); - if (id.startsWith('root_')) { - // Click on primitive type item - const primitiveType = id.split('root_')[1] as McpServerPrimitiveTypes; - setCollapsedPrimitives(prev => { - if (prev.includes(primitiveType)) { - return prev.filter(p => p !== primitiveType); - } - return [...prev, primitiveType]; - }); - } else { - // Click a specified primitive - const [type, name] = id.split('_'); - const item = visibleCollection.find(i => i.itemLevel === 1 && i.type === type && i.name === name); - setSelectedPrimitiveItem(item as PrimitiveSubItem); - } - }} - > - {virtualItem => { - const item = visibleCollection[virtualItem.index]; - return ( - - ); - }} - -
-
- {isEnvironmentModalOpen && setEnvironmentModalOpen(false)} />} -
-
- - - - - - - - - - - - - - -
- ); -}; - -const CollectionGridListItem = ({ - activeRequest, - style, - item, - collapsedPrimitives, -}: { - activeRequest: McpRequest; - item: PrimitiveTypeItem | PrimitiveSubItem; - style: React.CSSProperties; - collapsedPrimitives: McpServerPrimitiveTypes[]; -}) => { - const label = 'title' in item ? item.title : item.name; - const uniqueId = item.itemLevel === 0 ? `root_${item.type}` : `${item.type}_${item.name}`; - const itemLevel = item.itemLevel; - const [isContextMenuOpen, setIsContextMenuOpen] = useState(false); - const triggerRef = useRef(null); - - return ( - -
{ - e.preventDefault(); - setIsContextMenuOpen(true); - }} - className="relative flex h-[--line-height-xs] w-full select-none items-center gap-2 overflow-hidden pl-4 pr-2 text-[--hl] outline-none transition-colors group-hover:bg-[--hl-xs] group-focus:bg-[--hl-sm] data-[selected=true]:text-[--color-font]" - style={{ - paddingLeft: `${itemLevel}em`, - }} - > -
- {itemLevel === 0 && ( - - )} - {item.type === 'tools' && item.itemLevel === 1 && ( - - Tool - - )} - {item.type === 'resources' && item.itemLevel === 1 && ( - - Res - - )} - {item.type === 'prompts' && item.itemLevel === 1 && ( - - Prompt - - )} - {label} -
- -
-
- ); -}; - -export default McpPage; diff --git a/packages/insomnia/src/routes/organization.$organizationId.project.$projectId.workspace.new.tsx b/packages/insomnia/src/routes/organization.$organizationId.project.$projectId.workspace.new.tsx index 1b646b8b1e..9641331f60 100644 --- a/packages/insomnia/src/routes/organization.$organizationId.project.$projectId.workspace.new.tsx +++ b/packages/insomnia/src/routes/organization.$organizationId.project.$projectId.workspace.new.tsx @@ -198,7 +198,7 @@ export async function clientAction({ request, params }: Route.ClientActionArgs) }); return redirect( - href('/organization/:organizationId/project/:projectId/workspace/:workspaceId/mcp/request/:requestId', { + href('/organization/:organizationId/project/:projectId/workspace/:workspaceId/debug/request/:requestId', { organizationId, projectId, workspaceId: workspace._id, diff --git a/packages/insomnia/src/ui/components/mcp/event-view.tsx b/packages/insomnia/src/ui/components/mcp/event-view.tsx index 1e9fcf173f..1b61f7acb2 100644 --- a/packages/insomnia/src/ui/components/mcp/event-view.tsx +++ b/packages/insomnia/src/ui/components/mcp/event-view.tsx @@ -12,7 +12,7 @@ import { PREVIEW_MODES, } from '../../../common/constants'; import type { McpEvent } from '../../../main/network/mcp'; -import { useMcpRequestLoaderData } from '../../../routes/organization.$organizationId.project.$projectId.workspace.$workspaceId.mcp.request.$requestId'; +import { useRequestLoaderData } from '../../../routes/organization.$organizationId.project.$projectId.workspace.$workspaceId.debug.request.$requestId'; import { CodeEditor } from '../../components/.client/codemirror/code-editor'; import { showError } from '../../components/modals'; import { useRequestMetaPatcher } from '../../hooks/use-request'; @@ -64,7 +64,7 @@ export const MessageEventView = ({ event }: Props) => { } catch { // Can't parse as JSON. } - const { activeRequestMeta } = useMcpRequestLoaderData()!; + const { activeRequestMeta } = useRequestLoaderData()!; const previewMode = ('previewMode' in activeRequestMeta && activeRequestMeta.previewMode) || PREVIEW_MODE_SOURCE; return (
diff --git a/packages/insomnia/src/ui/components/mcp/mcp-pane.tsx b/packages/insomnia/src/ui/components/mcp/mcp-pane.tsx new file mode 100644 index 0000000000..353718cc59 --- /dev/null +++ b/packages/insomnia/src/ui/components/mcp/mcp-pane.tsx @@ -0,0 +1,489 @@ +import { useVirtualizer } from '@tanstack/react-virtual'; +import { useCallback, useEffect, useRef, useState } from 'react'; +import { + Breadcrumb, + Breadcrumbs, + Button, + GridList, + GridListItem, + Input, + SearchField, + ToggleButton, + Tooltip, + TooltipTrigger, +} from 'react-aria-components'; +import { type ImperativePanelGroupHandle, Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels'; +import { NavLink, useParams } from 'react-router'; +import { useLocalStorage } from 'react-use'; + +import { DEFAULT_SIDEBAR_SIZE } from '~/common/constants'; +import { + getDefaultServerCapabilities, + type McpServerData, + METHOD_INITIALIZE, + METHOD_LIST_PROMPTS, + METHOD_LIST_RESOURCE_TEMPLATES, + METHOD_LIST_RESOURCES, + METHOD_LIST_TOOLS, +} from '~/common/mcp-utils'; +import type { McpEvent, McpMessageEvent } from '~/main/network/mcp'; +import type { McpRequest, McpServerPrimitiveTypes } from '~/models/mcp-request'; +import { useRootLoaderData } from '~/root'; +import { useWorkspaceLoaderData } from '~/routes/organization.$organizationId.project.$projectId.workspace.$workspaceId'; +import { + type McpRequestLoaderData, + useRequestLoaderData, +} from '~/routes/organization.$organizationId.project.$projectId.workspace.$workspaceId.debug.request.$requestId'; +import { McpActionsDropdown } from '~/ui/components/dropdowns/mcp-actions-dropdown'; +import { WorkspaceDropdown } from '~/ui/components/dropdowns/workspace-dropdown'; +import { EnvironmentPicker } from '~/ui/components/environment-picker'; +import { ErrorBoundary } from '~/ui/components/error-boundary'; +import { Icon } from '~/ui/components/icon'; +import { McpRequestPane } from '~/ui/components/mcp/mcp-request-pane'; +import { + type PrimitiveSubItem, + type PrimitiveTypeItem, + type PromptItem, + type ResourceItem, + type ResourceTemplateItem, + type ToolItem, +} from '~/ui/components/mcp/types'; +import { WorkspaceEnvironmentsEditModal } from '~/ui/components/modals/workspace-environments-edit-modal'; +import { OrganizationTabList } from '~/ui/components/tabs/tab-list'; +import { RealtimeResponsePane } from '~/ui/components/websockets/realtime-response-pane'; +import { INSOMNIA_TAB_HEIGHT } from '~/ui/constant'; +import { useReadyState } from '~/ui/hooks/use-ready-state'; + +export const McpPane = () => { + const requestData = useRequestLoaderData()!; + const { activeResponse, activeRequest } = requestData as McpRequestLoaderData; + const { organizationId, projectId, workspaceId } = useParams() as { + organizationId: string; + projectId: string; + workspaceId: string; + }; + const sidebarPanelRef = useRef(null); + const [isEnvironmentPickerOpen, setIsEnvironmentPickerOpen] = useState(false); + const [isEnvironmentModalOpen, setEnvironmentModalOpen] = useState(false); + const [allExpanded, setAllExpanded] = useState(true); + const [filter, setFilter] = useLocalStorage(`${workspaceId}:mcp-list-filter`); + const { settings } = useRootLoaderData()!; + const [mcpServerData, setMcpServerData] = useState(null); + const [collapsedPrimitives, setCollapsedPrimitives] = useState([]); + const [selectedPrimitiveItem, setSelectedPrimitiveItem] = useState(null); + + const getPrimitiveCollection = () => { + const collection: (PrimitiveTypeItem | PrimitiveSubItem)[] = []; + if (mcpServerData) { + const { primitives } = mcpServerData; + const { tools, resources, resourceTemplates, prompts } = primitives; + if (tools.length > 0) { + collection.push({ + type: 'tools', + name: 'Tools', + collapsed: collapsedPrimitives.includes('tools'), + itemLevel: 0, + hide: false, + }); + const hide = collapsedPrimitives.includes('tools'); + collection.push(...(tools.map(t => ({ ...t, type: 'tools', itemLevel: 1, hide })) as ToolItem[])); + } + if (resources.length > 0 || resourceTemplates.length > 0) { + collection.push({ + type: 'resources', + name: 'Resources', + collapsed: collapsedPrimitives.includes('resources'), + itemLevel: 0, + hide: false, + }); + const hide = collapsedPrimitives.includes('resources'); + collection.push(...(resources.map(r => ({ ...r, type: 'resources', itemLevel: 1, hide })) as ResourceItem[])); + collection.push( + ...(resourceTemplates.map(rt => ({ + ...rt, + type: 'resources', + itemLevel: 1, + hide, + })) as ResourceTemplateItem[]), + ); + } + if (prompts.length > 0) { + collection.push({ + type: 'prompts', + name: 'Prompts', + collapsed: collapsedPrimitives.includes('prompts'), + itemLevel: 0, + hide: false, + }); + const hide = collapsedPrimitives.includes('prompts'); + collection.push(...(prompts.map(p => ({ ...p, type: 'prompts', itemLevel: 1, hide })) as PromptItem[])); + } + } + return collection; + }; + + const getServerCapabilities = () => { + const serverCapabilities = getDefaultServerCapabilities(); + if (mcpServerData) { + const { tools, resources, prompts } = mcpServerData.serverCapabilities; + if (tools) { + serverCapabilities.tools.enabled = true; + serverCapabilities.tools.listChanged = !!tools.listChanged; + } + if (resources) { + serverCapabilities.resources.enabled = true; + serverCapabilities.resources.listChanged = !!resources.listChanged; + serverCapabilities.resources.subscribe = !!resources.subscribe; + } + if (prompts) { + serverCapabilities.prompts.enabled = true; + serverCapabilities.prompts.listChanged = !!prompts.listChanged; + } + } + return serverCapabilities; + }; + + // TODO Support filter + const visibleCollection = getPrimitiveCollection().filter(item => !item.hide); + const serverCapabilities = getServerCapabilities(); + const allowSubscribeResources = serverCapabilities.resources.enabled && serverCapabilities.resources.subscribe; + const enableNotification = + serverCapabilities.tools.listChanged || + serverCapabilities.resources.listChanged || + serverCapabilities.prompts.listChanged; + // TODO Use these variables to enable notification + console.log(`enableNotification`, enableNotification); + console.log(`allowSubscribeResources`, allowSubscribeResources); + + const requestId = activeRequest._id; + const { activeEnvironment } = useWorkspaceLoaderData()!; + const readyState = useReadyState({ requestId, protocol: 'mcp' }); + + const parentRef = useRef(null); + const virtualizer = useVirtualizer({ + getScrollElement: () => parentRef.current, + count: visibleCollection.length, + estimateSize: useCallback(() => 32, []), + overscan: 20, + getItemKey: index => { + const item = visibleCollection[index]; + return `${item.itemLevel}::${item.type}::${item.name}`; + }, + }); + + function toggleSidebar() { + const layout = sidebarPanelRef.current?.getLayout(); + + if (!layout) { + return; + } + + if (layout && layout[0] > 0) { + layout[0] = 0; + } else { + layout[0] = DEFAULT_SIDEBAR_SIZE; + } + + sidebarPanelRef.current?.setLayout(layout); + } + + useEffect(() => { + const unsubscribe = window.main.on('toggle-sidebar', toggleSidebar); + + return unsubscribe; + }, []); + + const [direction, setDirection] = useState<'horizontal' | 'vertical'>( + settings.forceVerticalLayout ? 'vertical' : 'horizontal', + ); + + useEffect(() => { + if (settings.forceVerticalLayout) { + setDirection('vertical'); + return () => {}; + } + // Listen on media query changes + const mediaQuery = window.matchMedia('(max-width: 880px)'); + setDirection(mediaQuery.matches ? 'vertical' : 'horizontal'); + + const handleChange = (e: MediaQueryListEvent) => { + setDirection(e.matches ? 'vertical' : 'horizontal'); + }; + + mediaQuery.addEventListener('change', handleChange); + + return () => { + mediaQuery.removeEventListener('change', handleChange); + }; + }, [settings.forceVerticalLayout, direction]); + + useEffect(() => { + const updateServerData = async () => { + const findFirstMatchEventData = (mcpEvents: McpEvent[], method: string) => { + const firstMatchEvent = mcpEvents.find( + event => 'method' in event && event.method === method, + ) as McpMessageEvent; + if (firstMatchEvent) { + return firstMatchEvent.data.result; + } + return undefined; + }; + const activeResponseId = activeResponse?._id; + if (activeResponseId) { + const allEvents = await window.main.mcp.event.findMany({ responseId: activeResponseId }); + const serverCapabilities = + findFirstMatchEventData(allEvents, METHOD_INITIALIZE)?.capabilities || getDefaultServerCapabilities(); + const tools = findFirstMatchEventData(allEvents, METHOD_LIST_TOOLS)?.tools || []; + const resources = findFirstMatchEventData(allEvents, METHOD_LIST_RESOURCES)?.resources || []; + const resourceTemplates = + findFirstMatchEventData(allEvents, METHOD_LIST_RESOURCE_TEMPLATES)?.resourceTemplates || []; + const prompts = findFirstMatchEventData(allEvents, METHOD_LIST_PROMPTS)?.prompts || []; + const mcpServerData = { + serverCapabilities: serverCapabilities, + primitives: { + tools, + resources, + resourceTemplates, + prompts, + }, + } as McpServerData; + setMcpServerData(mcpServerData); + } + }; + if (readyState) { + // Get MCP server data when connection is ready + updateServerData(); + } + }, [readyState, activeResponse?._id]); + + return ( + + +
+
+
+ + + + + + + + + + + +
+
+ +
+
+ setEnvironmentModalOpen(true)} + /> +
+
+ +
+
+ + +
+ +
+
+ + { + const newState = !allExpanded; + if (newState) { + setCollapsedPrimitives([]); + } else { + setCollapsedPrimitives(['tools', 'resources', 'prompts']); + } + setAllExpanded(newState); + }} + className="flex aspect-square h-full items-center justify-center rounded-sm text-sm text-[--color-font] ring-1 ring-transparent transition-all hover:bg-[--hl-xs] focus:ring-inset focus:ring-[--hl-md]" + > + {({ isSelected }) => ( + + )} + + + {allExpanded ? 'Collapse all' : 'Expand all'} + + +
+ +
+ { + const id = key.toString(); + if (id.startsWith('root_')) { + // Click on primitive type item + const primitiveType = id.split('root_')[1] as McpServerPrimitiveTypes; + setCollapsedPrimitives(prev => { + if (prev.includes(primitiveType)) { + return prev.filter(p => p !== primitiveType); + } + return [...prev, primitiveType]; + }); + } else { + // Click a specified primitive + const [type, name] = id.split('_'); + const item = visibleCollection.find(i => i.itemLevel === 1 && i.type === type && i.name === name); + setSelectedPrimitiveItem(item as PrimitiveSubItem); + } + }} + > + {virtualItem => { + const item = visibleCollection[virtualItem.index]; + return ( + + ); + }} + +
+
+ {isEnvironmentModalOpen && setEnvironmentModalOpen(false)} />} +
+
+ + + + + + + + + + + + + + +
+ ); +}; + +const CollectionGridListItem = ({ + activeRequest, + style, + item, + collapsedPrimitives, +}: { + activeRequest: McpRequest; + item: PrimitiveTypeItem | PrimitiveSubItem; + style: React.CSSProperties; + collapsedPrimitives: McpServerPrimitiveTypes[]; +}) => { + const label = 'title' in item ? item.title : item.name; + const uniqueId = item.itemLevel === 0 ? `root_${item.type}` : `${item.type}_${item.name}`; + const itemLevel = item.itemLevel; + const [isContextMenuOpen, setIsContextMenuOpen] = useState(false); + const triggerRef = useRef(null); + + return ( + +
{ + e.preventDefault(); + setIsContextMenuOpen(true); + }} + className="relative flex h-[--line-height-xs] w-full select-none items-center gap-2 overflow-hidden pl-4 pr-2 text-[--hl] outline-none transition-colors group-hover:bg-[--hl-xs] group-focus:bg-[--hl-sm] data-[selected=true]:text-[--color-font]" + style={{ + paddingLeft: `${itemLevel}em`, + }} + > +
+ {itemLevel === 0 && ( + + )} + {item.type === 'tools' && item.itemLevel === 1 && ( + + Tool + + )} + {item.type === 'resources' && item.itemLevel === 1 && ( + + Res + + )} + {item.type === 'prompts' && item.itemLevel === 1 && ( + + Prompt + + )} + {label} +
+ +
+
+ ); +}; + +export default McpPane; diff --git a/packages/insomnia/src/ui/components/mcp/mcp-request-pane.tsx b/packages/insomnia/src/ui/components/mcp/mcp-request-pane.tsx index 6b52c5574d..5bc14422f5 100644 --- a/packages/insomnia/src/ui/components/mcp/mcp-request-pane.tsx +++ b/packages/insomnia/src/ui/components/mcp/mcp-request-pane.tsx @@ -10,7 +10,10 @@ 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 { + type McpRequestLoaderData, + useRequestLoaderData, +} from '../../../routes/organization.$organizationId.project.$projectId.workspace.$workspaceId.debug.request.$requestId'; import { useRequestPatcher } from '../../hooks/use-request'; import { CodeEditor, type CodeEditorHandle } from '../.client/codemirror/code-editor'; import { AuthWrapper } from '../editors/auth/auth-wrapper'; @@ -51,7 +54,7 @@ interface Props { } export const McpRequestPane: FC = ({ environment, readyState, selectedPrimitiveItem }) => { - const { activeRequest } = useMcpRequestLoaderData()!; + const { activeRequest } = useRequestLoaderData()! as McpRequestLoaderData; const [formData, setFormData] = useState({}); const paramEditorRef = useRef(null); const requestId = activeRequest._id; diff --git a/packages/insomnia/src/ui/components/mcp/mcp-url-bar.tsx b/packages/insomnia/src/ui/components/mcp/mcp-url-bar.tsx index 2230ba4ff6..60110c5e20 100644 --- a/packages/insomnia/src/ui/components/mcp/mcp-url-bar.tsx +++ b/packages/insomnia/src/ui/components/mcp/mcp-url-bar.tsx @@ -5,7 +5,7 @@ import { useParams } from 'react-router'; import { type ConnectActionParams, useRequestConnectActionFetcher, -} from '~/routes/organization.$organizationId.project.$projectId.workspace.$workspaceId.mcp.request.$requestId.connect'; +} from '~/routes/organization.$organizationId.project.$projectId.workspace.$workspaceId.debug.request.$requestId.connect'; import { OneLineEditor, type OneLineEditorHandle } from '~/ui/components/.client/codemirror/one-line-editor'; import { Dropdown, DropdownItem, DropdownSection, ItemContent } from '~/ui/components/base/dropdown'; diff --git a/packages/insomnia/src/ui/components/modals/request-settings-modal.tsx b/packages/insomnia/src/ui/components/modals/request-settings-modal.tsx index ff6d4d3b7b..202d29cb07 100644 --- a/packages/insomnia/src/ui/components/modals/request-settings-modal.tsx +++ b/packages/insomnia/src/ui/components/modals/request-settings-modal.tsx @@ -2,6 +2,7 @@ import React, { useEffect, useRef, useState } from 'react'; import { OverlayContainer } from 'react-aria'; import { useNavigate, useParams } from 'react-router'; +import type { McpRequest } from '~/models/mcp-request'; import { useProjectListWorkspacesLoaderFetcher } from '~/routes/organization.$organizationId.project.$projectId.list-workspaces'; import { useRequestDuplicateActionFetcher } from '~/routes/organization.$organizationId.project.$projectId.workspace.$workspaceId.debug.request.$requestId.duplicate'; @@ -22,7 +23,7 @@ import { HelpTooltip } from '../help-tooltip'; import { Icon } from '../icon'; export interface RequestSettingsModalOptions { - request: Request | GrpcRequest | WebSocketRequest | SocketIORequest; + request: Request | GrpcRequest | WebSocketRequest | SocketIORequest | McpRequest; } export const RequestSettingsModal = ({ request, onHide }: ModalProps & RequestSettingsModalOptions) => { diff --git a/packages/insomnia/src/ui/components/tags/method-tag.tsx b/packages/insomnia/src/ui/components/tags/method-tag.tsx index bfc106154c..f92772f9f3 100644 --- a/packages/insomnia/src/ui/components/tags/method-tag.tsx +++ b/packages/insomnia/src/ui/components/tags/method-tag.tsx @@ -1,5 +1,7 @@ import React, { type FC, memo } from 'react'; +import { isMcpRequest, type McpRequest } from '~/models/mcp-request'; + import { CONTENT_TYPE_GRAPHQL, METHOD_DELETE, METHOD_OPTIONS } from '../../../common/constants'; import { type GrpcRequest, isGrpcRequest } from '../../../models/grpc-request'; import { isEventStreamRequest, isRequest, type Request } from '../../../models/request'; @@ -37,7 +39,9 @@ export function formatMethodName(method: string) { return methodName; } -export const getRequestMethodShortHand = (doc?: Request | WebSocketRequest | GrpcRequest | SocketIORequest) => { +export const getRequestMethodShortHand = ( + doc?: Request | WebSocketRequest | GrpcRequest | SocketIORequest | McpRequest, +) => { if (!doc) { return ''; } @@ -57,6 +61,10 @@ export const getRequestMethodShortHand = (doc?: Request | WebSocketRequest | Grp return 'IO'; } + if (isMcpRequest(doc)) { + return 'MCP'; + } + return ''; }; diff --git a/packages/insomnia/src/ui/components/websockets/realtime-response-pane.tsx b/packages/insomnia/src/ui/components/websockets/realtime-response-pane.tsx index 069cc02bb6..540aebc81a 100644 --- a/packages/insomnia/src/ui/components/websockets/realtime-response-pane.tsx +++ b/packages/insomnia/src/ui/components/websockets/realtime-response-pane.tsx @@ -16,7 +16,6 @@ import type { Response } from '../../../models/response'; import { isSocketIOResponse, type SocketIOResponse } from '../../../models/socket-io-response'; import { type WebSocketResponse } from '../../../models/websocket-response'; import { useRequestLoaderData } from '../../../routes/organization.$organizationId.project.$projectId.workspace.$workspaceId.debug.request.$requestId'; -import { useMcpRequestLoaderData } from '../../../routes/organization.$organizationId.project.$projectId.workspace.$workspaceId.mcp.request.$requestId'; import { deserializeNDJSON } from '../../../utils/ndjson'; import { useReadyState } from '../../hooks/use-ready-state'; import { useRealtimeConnectionEvents } from '../../hooks/use-realtime-connection-events'; @@ -38,7 +37,7 @@ import { ResponseTimelineViewer } from '../viewers/response-timeline-viewer'; import { EventLogView } from './event-log-view'; import { EventView } from './event-view'; -export const RealtimeResponsePane: FC<{ requestId: string }> = () => { +export const RealtimeResponsePane: FC<{ requestId?: string }> = () => { const { activeResponse, responses, requestVersions } = useRequestLoaderData()!; if (!activeResponse) { @@ -54,22 +53,6 @@ export const RealtimeResponsePane: FC<{ requestId: string }> = () => { ); }; -export const McpRealtimeResponsePane = () => { - const { activeResponse, responses, requestVersions } = useMcpRequestLoaderData()!; - - if (!activeResponse) { - return ( - - - - - ); - } - return ( - - ); -}; - type ResponseType = WebSocketResponse | Response | SocketIOResponse | McpResponse; type EventType = CurlEvent | WebSocketEvent | SocketIOEvent | McpEvent; const RealtimeActiveResponsePane: FC<{ diff --git a/packages/insomnia/src/ui/hooks/use-insomnia-tab.ts b/packages/insomnia/src/ui/hooks/use-insomnia-tab.ts index d3e98a85b0..fc3006f79e 100644 --- a/packages/insomnia/src/ui/hooks/use-insomnia-tab.ts +++ b/packages/insomnia/src/ui/hooks/use-insomnia-tab.ts @@ -1,6 +1,8 @@ import { useCallback, useEffect } from 'react'; import { matchPath, useLocation, useSearchParams } from 'react-router'; +import type { McpRequest } from '~/models/mcp-request'; + import type { GrpcRequest } from '../../models/grpc-request'; import type { MockRoute } from '../../models/mock-route'; import type { Project } from '../../models/project'; @@ -22,7 +24,7 @@ interface InsomniaTabProps { workspaceId: string; activeProject: Project; activeWorkspace: Workspace; - activeRequest?: Request | GrpcRequest | WebSocketRequest | SocketIORequest; + activeRequest?: Request | GrpcRequest | WebSocketRequest | SocketIORequest | McpRequest; activeRequestGroup?: RequestGroup; activeMockRoute?: MockRoute; unitTestSuite?: UnitTestSuite;