diff --git a/packages/insomnia/src/common/__tests__/import-v5-parser.test.ts b/packages/insomnia/src/common/__tests__/import-v5-parser.test.ts index c288d22a29..52fa86e8a6 100644 --- a/packages/insomnia/src/common/__tests__/import-v5-parser.test.ts +++ b/packages/insomnia/src/common/__tests__/import-v5-parser.test.ts @@ -12,6 +12,8 @@ import { JsonSchema, KeyLiteralSchema, LiteralSchema, + McpClientSchema, + McpRequestSchema, MetaGroupSchema, MetaSchema, MockRouteSchema, @@ -131,6 +133,15 @@ const makeMockRoute = (overrides: Record = {}) => ({ ...overrides, }); +const makeMcpRequest = (overrides: Record = {}) => ({ + name: 'MCP Request', + url: 'https://example.com/mcp', + transportType: 'streamable-http', + headers: [{ name: 'X-MCP', value: '1' }], + authentication: { type: 'none' }, + ...overrides, +}); + // ----------------------------- // Primitive & Base Schemas // ----------------------------- @@ -424,6 +435,39 @@ describe('MockRouteSchema', () => { }); }); +// ----------------------------- +// Mcp Request +// ----------------------------- + +describe('McpRequestSchema', () => { + it('parses mcp request schmea and applies defaults', () => { + const mcpRequestData = makeMcpRequest({ + env: [ + { + id: 'env-1', + name: 'foo', + value: 'bar', + type: 'str', + enabled: true, + }, + ], + roots: [ + { + uri: '/data/to/root/file', + }, + { + uri: '/more/data', + }, + ], + }); + const r = McpRequestSchema.parse(mcpRequestData); + expect(r.transportType).toBe(mcpRequestData.transportType); + expect(r.url).toBe(mcpRequestData.url); + expect(r.env?.[0].name).toBe('foo'); + expect(r.roots?.[0].uri).toBe('/data/to/root/file'); + }); +}); + // ----------------------------- // Top-level documents // ----------------------------- @@ -476,6 +520,19 @@ describe('MockServerSchema (top-level)', () => { }); }); +describe('McpClientSchema (top-level)', () => { + it('parse mcp workspace with mcp request ', () => { + const mcpRequestData = makeMcpRequest(); + const mcpClient = McpClientSchema.parse({ + type: 'mcpClient.insomnia/5.0', + name: 'MCP Client', + mcpRequest: mcpRequestData, + }); + expect(mcpClient.mcpRequest?.url).toBe(mcpRequestData.url); + expect(mcpClient.mcpRequest?.transportType).toBe(mcpRequestData.transportType); + }); +}); + describe('InsomniaFileSchema (discriminated union)', () => { it('accepts collection variant', () => { const f = InsomniaFileSchema.parse({ @@ -505,6 +562,14 @@ describe('InsomniaFileSchema (discriminated union)', () => { expect(() => InsomniaFileSchema.parse({ type: 'futureCollection.insomnia.rest/5.0' })).toThrow(); }); + it('accepts mcp request', () => { + const mcpRequest = InsomniaFileSchema.parse({ + type: 'mcpClient.insomnia/5.0', + mcpRequest: makeMcpRequest(), + }); + expect(mcpRequest.type).toBe('mcpClient.insomnia/5.0'); + }); + it('rejects unknown type', () => { expect(() => InsomniaFileSchema.parse({ type: 'nope' })).toThrow(); }); diff --git a/packages/insomnia/src/common/__tests__/insomnia-v5.test.ts b/packages/insomnia/src/common/__tests__/insomnia-v5.test.ts index 1532aff926..c7b36b0557 100644 --- a/packages/insomnia/src/common/__tests__/insomnia-v5.test.ts +++ b/packages/insomnia/src/common/__tests__/insomnia-v5.test.ts @@ -10,6 +10,7 @@ import YAML from 'yaml'; import { INSOMNIA_SCHEMA_VERSION } from '../../common/insomnia-schema-migrations/schema-version'; import * as models from '../../models'; +import { EnvironmentKvPairDataType } from '../../models/environment'; import type { Request } from '../../models/request'; import { database as db } from '../database'; import { @@ -47,6 +48,7 @@ describe('Insomnia v5 Import/Export - Comprehensive Tests', () => { expect(insomniaSchemaTypeToScope('environment.insomnia.rest/5.0')).toBe('environment'); expect(insomniaSchemaTypeToScope('spec.insomnia.rest/5.0')).toBe('design'); expect(insomniaSchemaTypeToScope('mock.insomnia.rest/5.0')).toBe('mock-server'); + expect(insomniaSchemaTypeToScope('mcpClient.insomnia/5.0')).toBe('mcp'); }); }); @@ -324,6 +326,76 @@ collection: [] expect(parsed.server.url).toBe('http://localhost:3000'); }); + it('handles mcp client scope', async () => { + const workspace = await models.workspace.create({ + _id: 'wrk_mcp', + name: 'MCP Workspace', + parentId: 'proj_test', + scope: 'mcp', + }); + + await models.environment.create({ + _id: 'env_mcp', + name: 'Base Env', + parentId: workspace._id, + data: {}, + }); + + const mcpRequest = await models.mcpRequest.create({ + _id: 'mcp-request_test', + name: 'Test MCP client', + parentId: workspace._id, + url: 'http://mcp.test.com/mcp', + transportType: 'streamable-http', + }); + + let result = await getInsomniaV5DataExport({ + workspaceId: workspace._id, + includePrivateEnvironments: false, + }); + + let parsed = YAML.parse(result); + expect(parsed.type).toBe('mcpClient.insomnia/5.0'); + expect(parsed.mcpRequest.url).toBe('http://mcp.test.com/mcp'); + expect(parsed.mcpRequest.transportType).toBe('streamable-http'); + + await models.mcpRequest.update(mcpRequest, { + transportType: 'stdio', + url: 'npx mcp-client stdio', + env: [ + { + id: 'var1', + name: 'foo', + value: 'bar', + type: EnvironmentKvPairDataType.STRING, + }, + { + id: 'var2', + name: 'foo1', + value: 'bar1', + type: EnvironmentKvPairDataType.STRING, + }, + ], + roots: [ + { + uri: 'file:///path/to/root', + }, + ], + }); + + result = await getInsomniaV5DataExport({ + workspaceId: workspace._id, + includePrivateEnvironments: false, + }); + + parsed = YAML.parse(result); + expect(parsed.type).toBe('mcpClient.insomnia/5.0'); + expect(parsed.mcpRequest.url).toBe('npx mcp-client stdio'); + expect(parsed.mcpRequest.transportType).toBe('stdio'); + expect(parsed.mcpRequest.env).toHaveLength(2); + expect(parsed.mcpRequest.roots).toHaveLength(1); + }); + it('returns empty string for unknown workspace', async () => { const result = await getInsomniaV5DataExport({ workspaceId: 'missing', diff --git a/packages/insomnia/src/common/import-v5-parser.ts b/packages/insomnia/src/common/import-v5-parser.ts index bbf81fd19b..1eb4ec528b 100644 --- a/packages/insomnia/src/common/import-v5-parser.ts +++ b/packages/insomnia/src/common/import-v5-parser.ts @@ -465,6 +465,34 @@ export const SocketIORequestSchema = z.object({ eventListeners: SocketIOEventListenerSchema.array().optional(), }); +export const McpRequestSchema = z.object({ + name: z.string().optional().default(''), + url: z.string().optional().default(''), + transportType: z.enum(['stdio', 'streamable-http']).optional().default('streamable-http'), + headers: HeadersSchema.optional(), + authentication: AuthenticationSchema.optional(), + meta: MetaSchema.optional(), + env: z + .array( + z.object({ + id: z.string(), + name: z.string().optional().default(''), + value: z.string().optional().default(''), + type: z.literal('str'), + enabled: z.boolean().optional().default(true), + }), + ) + .optional(), + roots: z + .array( + z.object({ + name: z.string().optional(), + uri: z.string().optional().default(''), + }), + ) + .optional(), +}); + type Request = z.infer; type GRPCRequest = z.infer; type WebsocketRequest = z.infer; @@ -578,12 +606,24 @@ const GlobalEnvironmentsSchema = z.object({ environments: EnvironmentSchema.optional(), }); +export const McpClientSchema = z.object({ + // Does not follow the insomnia.rest pattern to prevent crashes in older versions when syncing this file: INS-1762 + type: z.literal('mcpClient.insomnia/5.0'), + schema_version: z.string().optional().default(INSOMNIA_SCHEMA_VERSION), + name: z.string().optional(), + meta: MetaSchema.optional(), + mcpRequest: McpRequestSchema.optional(), + environments: EnvironmentSchema.optional(), +}); + export const InsomniaFileSchema = z.discriminatedUnion('type', [ CollectionSchema, ApiSpecSchema, MockServerSchema, GlobalEnvironmentsSchema, + McpClientSchema, ]); +export const InsomniaFileTypeValues = InsomniaFileSchema.options.map(option => option.shape.type.value); export type InsomniaFile = z.infer; diff --git a/packages/insomnia/src/common/import.ts b/packages/insomnia/src/common/import.ts index 60f9d5d299..e5fd81ddfb 100644 --- a/packages/insomnia/src/common/import.ts +++ b/packages/insomnia/src/common/import.ts @@ -1,6 +1,7 @@ import { z, type ZodError } from 'zod/v4'; import { insecureReadFile } from '~/main/secure-read-file'; +import { isMcpRequest, type McpRequest } from '~/models/mcp-request'; import { type InsomniaImporter } from '../main/importers/convert'; import type { ImportEntry } from '../main/importers/entities'; @@ -27,6 +28,7 @@ import { generateId } from './misc'; export type AllExportTypes = | 'request' + | 'mcp_request' | 'grpc_request' | 'websocket_request' | 'websocket_payload' @@ -117,6 +119,7 @@ export interface ScanResult { unitTests?: UnitTest[]; unitTestSuites?: UnitTestSuite[]; mockRoutes?: MockRoute[]; + mcpRequests?: McpRequest[]; type?: InsomniaImporter; oriFileName?: string; errors: string[]; @@ -133,6 +136,7 @@ let resourceCacheList: ResourceCacheType[] = []; // All models that can be exported should be listed here export const MODELS_BY_EXPORT_TYPE: Record = { request: 'Request', + mcp_request: 'McpRequest', websocket_payload: 'WebSocketPayload', websocket_request: 'WebSocketRequest', socketio_payload: 'SocketIOPayload', @@ -233,6 +237,7 @@ export async function scanResources(importEntries: ImportEntry[]): Promise[] { + const commonProps: WithExportType = { + ...mapMetaToInsomniaMeta({ + id: '__MCP_CLIENT_ID__', + }), + parentId: file.meta?.id || '__WORKSPACE_ID__', + name: file.name || 'MCP Client', + type: 'McpRequest', + _type: 'mcp_request', + url: '', + transportType: 'streamable-http', + description: '', + authentication: {}, + headers: [], + env: [], + connected: false, + mcpStdioAccess: false, + roots: [], + subscribeResources: [], + sslValidation: true, + }; + + if ('mcpRequest' in file && file.mcpRequest) { + const mcpRequestParser = McpRequestSchema.safeParse(file.mcpRequest); + if (mcpRequestParser.success) { + const data = mcpRequestParser.data; + return [ + { + ...commonProps, + ...mapMetaToInsomniaMeta( + data.meta || { + id: '__MCP_CLIENT_ID__', + }, + ), + url: data.url, + transportType: data.transportType, + authentication: data.authentication || {}, + headers: mapHeaders(data.headers), + env: (data.env as EnvironmentKvPairData[]) || [], + }, + ]; + } + } + + return [commonProps]; +} + function importData(rawData: string) { // Apply schema migration before parsing to handle older schema versions const migratedData = migrateToLatestYaml(rawData); @@ -671,7 +723,10 @@ function importData(rawData: string) { if (file.type === 'mock.insomnia.rest/5.0') { return [getWorkspace(file), getMockServer(file), ...getMockRoutes(file)]; } - // @ts-expect-error this errors happen when new types are added but not handled here + if (file.type === 'mcpClient.insomnia/5.0') { + return [getWorkspace(file), ...getEnvironments(file), ...getMcpRequest(file)]; + } + // @ts-expect-error: Exhaustiveness check throw new Error(`No import handler found for type ${file.type}`); } throw new Error(`Failed to parse yaml file to Insomnia schema ${fileSchemaParser.error?.toString()}`); @@ -1051,6 +1106,33 @@ export async function getInsomniaV5DataExport({ })); } + function getMcpRequestFromResources( + resource: McpRequest, + ): Extract['mcpRequest'] { + return { + name: resource.name, + url: resource.url, + transportType: resource.transportType, + headers: resource.headers, + authentication: resource.authentication, + meta: { + id: resource._id, + created: resource.created, + modified: resource.modified, + }, + env: resource.env.map(envVar => ({ + id: envVar.id, + name: envVar.name, + value: envVar.value, + type: 'str', + enabled: !!envVar.enabled, + })), + roots: resource.roots.map(root => ({ + uri: root.uri, + })), + }; + } + if (workspace.scope === 'collection') { const collection: InsomniaFile = { type: 'collection.insomnia.rest/5.0', @@ -1148,6 +1230,25 @@ export async function getInsomniaV5DataExport({ const parsedMockServer = InsomniaFileSchema.parse(mockServer); return stringify(removeEmptyFields(parsedMockServer), {}); + } else if (workspace.scope === 'mcp') { + const mcpRequest = exportableResources.find(models.mcpRequest.isMcpRequest); + invariant(mcpRequest, 'No MCP Request found in MCP workspace'); + const mcpClient: InsomniaFile = { + type: 'mcpClient.insomnia/5.0', + schema_version: INSOMNIA_SCHEMA_VERSION, + name: workspace.name, + meta: mapWorkspaceMeta(workspace), + // each mcp workspace has exactly one mcpRequest + mcpRequest: getMcpRequestFromResources(mcpRequest), + environments: getEnvironmentsFromResources( + exportableResources.filter(models.environment.isEnvironment), + includePrivateEnvironments, + ), + }; + + const parsedMcpClient = InsomniaFileSchema.parse(mcpClient); + + return stringify(removeEmptyFields(parsedMcpClient)); } throw new Error('Unknown workspace scope'); } catch (err) { diff --git a/packages/insomnia/src/main/git-service.ts b/packages/insomnia/src/main/git-service.ts index 7145b181c7..01cf494f7e 100644 --- a/packages/insomnia/src/main/git-service.ts +++ b/packages/insomnia/src/main/git-service.ts @@ -38,7 +38,7 @@ import { PLAYWRIGHT, } from '../common/constants'; import { database } from '../common/database'; -import { InsomniaFileSchema } from '../common/import-v5-parser'; +import { InsomniaFileSchema, InsomniaFileTypeValues } from '../common/import-v5-parser'; import { migrateToLatestYaml } from '../common/insomnia-schema-migrations'; import { insomniaSchemaTypeToScope } from '../common/insomnia-v5'; import * as models from '../models'; @@ -641,7 +641,12 @@ async function isInsomniaFile(fullPath: string, fsClient: PromiseFsClient) { } const fileContents = await fsClient.promises.readFile(fullPath, 'utf8'); - return fileContents.split('\n')[0].trim().includes('insomnia.rest'); + const fileTypeStr = fileContents.split('\n')[0].trim(); + const doesFileContainInsomniaV5FormatTypeString = InsomniaFileTypeValues.some(fileType => + fileTypeStr.includes(fileType), + ); + + return doesFileContainInsomniaV5FormatTypeString; } // Recursively finds all .yaml files in a repository that are Insomnia files and returns their paths relative to the repo root. diff --git a/packages/insomnia/src/models/helpers/project.ts b/packages/insomnia/src/models/helpers/project.ts index 912fc62b84..1670b11568 100644 --- a/packages/insomnia/src/models/helpers/project.ts +++ b/packages/insomnia/src/models/helpers/project.ts @@ -7,7 +7,7 @@ import type { VCS } from '../../sync/vcs/vcs'; import { insomniaFetch } from '../../ui/insomnia-fetch'; import { invariant } from '../../utils/invariant'; import { isDefaultOrganizationProject, type Project, update as updateProject } from '../project'; -import { isMcp, type Workspace } from '../workspace'; +import { type Workspace } from '../workspace'; import { getOrCreateByParentId as getOrCreateWorkspaceMeta } from '../workspace-meta'; export const sortProjects = (projects: Project[]) => [ ...projects.filter(p => isDefaultOrganizationProject(p)).sort((a, b) => a.name.localeCompare(b.name)), @@ -62,9 +62,6 @@ export async function updateLocalProjectToRemote({ }); for (const workspace of projectWorkspaces) { - if (isMcp(workspace)) { - continue; - } const workspaceMeta = await getOrCreateWorkspaceMeta(workspace._id); // Initialize Sync on the workspace if it's not using Git sync diff --git a/packages/insomnia/src/models/mcp-request.ts b/packages/insomnia/src/models/mcp-request.ts index 46e3597c05..20fb11dfcd 100644 --- a/packages/insomnia/src/models/mcp-request.ts +++ b/packages/insomnia/src/models/mcp-request.ts @@ -11,7 +11,7 @@ export const name = 'MCP Request'; export const type = 'McpRequest'; export const prefix = 'mcp-req'; export const canDuplicate = true; -export const canSync = false; +export const canSync = true; export const TRANSPORT_TYPES = { STDIO: 'stdio', @@ -20,7 +20,6 @@ export const TRANSPORT_TYPES = { export type TransportType = (typeof TRANSPORT_TYPES)[keyof typeof TRANSPORT_TYPES]; export interface BaseMcpRequest { - name: string; url: string; transportType: TransportType; description: string; @@ -48,7 +47,6 @@ export function init(): BaseMcpRequest { return { url: '', transportType: TRANSPORT_TYPES.HTTP, - name: 'New MCP Client', description: '', headers: [], authentication: {}, diff --git a/packages/insomnia/src/models/workspace.ts b/packages/insomnia/src/models/workspace.ts index 6df2c5bcea..e1a93e9d75 100644 --- a/packages/insomnia/src/models/workspace.ts +++ b/packages/insomnia/src/models/workspace.ts @@ -79,8 +79,8 @@ export async function all() { return await db.find(type); } -export function count(scope?: WorkspaceScope) { - return db.count(type, scope ? { scope } : {}); +export function count() { + return db.count(type); } export function update(workspace: Workspace, patch: Partial) { diff --git a/packages/insomnia/src/root.tsx b/packages/insomnia/src/root.tsx index 70efc0cd65..ebe88908bf 100644 --- a/packages/insomnia/src/root.tsx +++ b/packages/insomnia/src/root.tsx @@ -157,14 +157,12 @@ export const useRootLoaderData = () => { export async function clientLoader(_args: Route.ClientLoaderArgs) { const settings = await models.settings.get(); const workspaceCount = await models.workspace.count(); - const mcpWorkspaceCount = await models.workspace.count('mcp'); const userSession = await models.userSession.getOrCreate(); const cloudCredentials = await models.cloudCredential.all(); return { settings, workspaceCount, - mcpWorkspaceCount, userSession, cloudCredentials, }; diff --git a/packages/insomnia/src/routes/organization.$organizationId.project.$projectId.workspace.$workspaceId.tsx b/packages/insomnia/src/routes/organization.$organizationId.project.$projectId.workspace.$workspaceId.tsx index 86d3fef8f0..03a5995f24 100644 --- a/packages/insomnia/src/routes/organization.$organizationId.project.$projectId.workspace.$workspaceId.tsx +++ b/packages/insomnia/src/routes/organization.$organizationId.project.$projectId.workspace.$workspaceId.tsx @@ -21,7 +21,7 @@ import type { RequestGroupMeta } from '~/models/request-group-meta'; import type { RequestMeta } from '~/models/request-meta'; import type { SocketIORequest } from '~/models/socket-io-request'; import type { WebSocketRequest } from '~/models/websocket-request'; -import { isMcp, type Workspace } from '~/models/workspace'; +import { type Workspace } from '~/models/workspace'; import type { WorkspaceMeta } from '~/models/workspace-meta'; import { pushSnapshotOnInitialize } from '~/sync/vcs/initialize-backend-project'; import { VCSInstance } from '~/sync/vcs/insomnia-sync'; @@ -257,8 +257,7 @@ export async function clientLoader({ params, request }: Route.ClientLoaderArgs) const userSession = await models.userSession.getOrCreate(); const isLoggedInIsCloudProjectAndIsNotGitRepo = userSession.id && activeProject.remoteId && !gitRepository; let vcsVersion = null; - // Mcp workspace do not support cloud sync for now - if (isLoggedInIsCloudProjectAndIsNotGitRepo && !isMcp(activeWorkspace)) { + if (isLoggedInIsCloudProjectAndIsNotGitRepo) { try { const vcs = VCSInstance(); await vcs.switchAndCreateBackendProjectIfNotExist(workspaceId, activeWorkspace.name); diff --git a/packages/insomnia/src/routes/organization.$organizationId.project.$projectId.workspace.delete.tsx b/packages/insomnia/src/routes/organization.$organizationId.project.$projectId.workspace.delete.tsx index fd6f444722..6e9d22f711 100644 --- a/packages/insomnia/src/routes/organization.$organizationId.project.$projectId.workspace.delete.tsx +++ b/packages/insomnia/src/routes/organization.$organizationId.project.$projectId.workspace.delete.tsx @@ -2,7 +2,7 @@ import { href, redirect } from 'react-router'; import * as models from '~/models'; import { isRemoteProject, type Project } from '~/models/project'; -import { isMcp, type Workspace } from '~/models/workspace'; +import { type Workspace } from '~/models/workspace'; import { VCSInstance } from '~/sync/vcs/insomnia-sync'; import { SegmentEvent } from '~/ui/analytics'; import { invariant } from '~/utils/invariant'; @@ -14,7 +14,7 @@ async function deleteWorkspaceFromCloud(workspace: Workspace, project: Project) const workspaceMeta = await models.workspaceMeta.getOrCreateByParentId(workspace._id); const isGitSync = !!workspaceMeta.gitRepositoryId; - if (isRemoteProject(project) && !isGitSync && !isMcp(workspace)) { + if (isRemoteProject(project) && !isGitSync) { try { const vcs = VCSInstance(); await vcs.switchAndCreateBackendProjectIfNotExist(workspace._id, workspace.name); 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 8aeeb43148..bfc2bdd6a6 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 @@ -146,7 +146,7 @@ export async function clientAction({ request, params }: Route.ClientActionArgs) await database.flushChanges(flushId); const { id } = await models.userSession.getOrCreate(); - if (id && !workspaceMeta.gitRepositoryId && !isGitProject(project) && !isLocalProject(project) && scope !== 'mcp') { + if (id && !workspaceMeta.gitRepositoryId && !isGitProject(project) && !isLocalProject(project)) { const vcs = VCSInstance(); await initializeLocalBackendProjectAndMarkForSync({ vcs, @@ -220,7 +220,7 @@ export async function clientAction({ request, params }: Route.ClientActionArgs) parentId: workspace._id, transportType: 'streamable-http', url: '', - name: 'My first MCP Client', + name: 'MCP Client', headers: defaultHeaders, description: '', }); diff --git a/packages/insomnia/src/sync/git/project-ne-db-client.ts b/packages/insomnia/src/sync/git/project-ne-db-client.ts index df9ca353e3..1ee6b75a2e 100644 --- a/packages/insomnia/src/sync/git/project-ne-db-client.ts +++ b/packages/insomnia/src/sync/git/project-ne-db-client.ts @@ -5,10 +5,10 @@ import YAML from 'yaml'; import { database, database as db } from '../../common/database'; import { extractErrorMessages } from '../../common/import'; -import type { InsomniaFile } from '../../common/import-v5-parser'; +import { type InsomniaFile, InsomniaFileTypeValues } from '../../common/import-v5-parser'; import { getInsomniaV5DataExport, tryImportV5Data } from '../../common/insomnia-v5'; import * as models from '../../models'; -import { isMcp, isWorkspace, type Workspace } from '../../models/workspace'; +import { isWorkspace, type Workspace } from '../../models/workspace'; import type { WorkspaceMeta } from '../../models/workspace-meta'; import Stat from './stat'; import { SystemError } from './system-error'; @@ -73,7 +73,10 @@ export class GitProjectNeDBClient { const dataStr = data.toString(); - const doesFileContainInsomniaV5FormatTypeString = dataStr.split('\n')[0].trim().includes('insomnia.rest'); + const fileTypeStr = dataStr.split('\n')[0].trim(); + const doesFileContainInsomniaV5FormatTypeString = InsomniaFileTypeValues.some(fileType => + fileTypeStr.includes(fileType), + ); if (!doesFileContainInsomniaV5FormatTypeString) { throw this._errMissing(filePath); @@ -146,10 +149,8 @@ export class GitProjectNeDBClient { async readdir(filePath: string) { filePath = path.normalize(filePath); - // Exclude the mcp workspace since it's not supported in git sync - const workspaces = (await db.find(models.workspace.type, { parentId: this._projectId })).filter( - w => !isMcp(w), - ); + const workspaces = await db.find(models.workspace.type, { parentId: this._projectId }); + const workspaceMetas = await db.find(models.workspaceMeta.type, { parentId: { $in: workspaces.map(w => w._id), diff --git a/packages/insomnia/src/ui/components/mcp/mcp-pane.tsx b/packages/insomnia/src/ui/components/mcp/mcp-pane.tsx index 0fb83706e3..b5c681c09c 100644 --- a/packages/insomnia/src/ui/components/mcp/mcp-pane.tsx +++ b/packages/insomnia/src/ui/components/mcp/mcp-pane.tsx @@ -38,6 +38,7 @@ import { } 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 { WorkspaceSyncDropdown } from '~/ui/components/dropdowns/workspace-sync-dropdown'; import { EnvironmentPicker } from '~/ui/components/environment-picker'; import { ErrorBoundary } from '~/ui/components/error-boundary'; import { Icon } from '~/ui/components/icon'; @@ -537,6 +538,8 @@ export const McpPane = () => { + + {isEnvironmentModalOpen && setEnvironmentModalOpen(false)} />} {isCertificatesModalOpen && setCertificatesModalOpen(false)} />} diff --git a/packages/insomnia/src/ui/components/modals/new-workspace-modal.tsx b/packages/insomnia/src/ui/components/modals/new-workspace-modal.tsx index eace9a3d41..8362582242 100644 --- a/packages/insomnia/src/ui/components/modals/new-workspace-modal.tsx +++ b/packages/insomnia/src/ui/components/modals/new-workspace-modal.tsx @@ -28,7 +28,7 @@ import { useAIFeatureStatus } from '~/ui/hooks/use-organization-features'; import { type ApiSpec } from '../../../models/api-spec'; import { isGitProject, type Project } from '../../../models/project'; -import { isMcp, type WorkspaceScope, WorkspaceScopeKeys } from '../../../models/workspace'; +import { type WorkspaceScope, WorkspaceScopeKeys } from '../../../models/workspace'; import { safeToUseInsomniaFileName, safeToUseInsomniaFileNameWithExt } from '../../../sync/git/insomnia-filename'; import { SegmentEvent } from '../../analytics'; import { Icon } from '../icon'; @@ -72,12 +72,8 @@ export const NewWorkspaceModal = ({ const isEnterprise = currentPlan?.type.includes('enterprise'); const isSelfHostedDisabled = !storageRules.enableLocalVault; const isCloudProjectDisabled = isLocalProject || !storageRules.enableCloudSync; - const isMcpWorkspace = isMcp({ scope }); - // Mcp workspaces do not support Git sync for now - const isGitProjectAndNotMcpWorkspace = isGitProject(project) && !isMcpWorkspace; const canOnlyCreateSelfHosted = isLocalProject && isEnterprise; - const defaultFileName = safeToUseInsomniaFileName(defaultNameByScope[scope]); const { isGenerateMockServersWithAIEnabled } = useAIFeatureStatus(); @@ -99,8 +95,7 @@ export const NewWorkspaceModal = ({ name: defaultNameByScope[scope], scope, folderPath: '', - // Add a unique timestamp for mcp file name to avoid conflicts since we hide the Git file and folder selector for it. - fileName: isGitProject(project) && isMcpWorkspace ? `${defaultFileName}_${Date.now()}` : defaultFileName, + fileName: safeToUseInsomniaFileName(defaultNameByScope[scope]), mockServerType: canOnlyCreateSelfHosted ? 'self-hosted' : 'cloud', mockServerUrl: '', mockServerCreationType: sourceApiSpec?.contents ? 'ai' : 'manual', @@ -178,7 +173,7 @@ export const NewWorkspaceModal = ({ className="fixed top-0 left-0 z-10 flex h-(--visual-viewport-height) w-full items-center justify-center bg-black/30" > - {/* Mcp workspaces do not support Git sync for now */} - {isGitProjectAndNotMcpWorkspace && ( + {isGitProject(project) && ( <> { const workspaces = await database.find(models.workspace.type); - const workspacesWithoutMcp = workspaces.filter(w => !isMcp(w)); - const baseEnvironments = await database.find(environment.type, { - parentId: { $in: workspacesWithoutMcp.map(w => w._id) }, + parentId: { $in: workspaces.map(w => w._id) }, }); const subEnvironments = await database.find(environment.type, { @@ -408,7 +406,7 @@ export async function exportAllData({ dirPath }: { dirPath: string }): Promise = ({ hideSettingsModal, onModalChange }) => const workspaceData = useWorkspaceLoaderData(); const activeWorkspaceName = workspaceData?.activeWorkspace.name; - const { workspaceCount, userSession, mcpWorkspaceCount } = useRootLoaderData()!; + const { workspaceCount, userSession } = useRootLoaderData()!; const workspacesFetcher = useProjectListWorkspacesLoaderFetcher(); useEffect(() => { const isIdleAndUninitialized = workspacesFetcher.state === 'idle' && !workspacesFetcher.data; @@ -647,11 +645,7 @@ export const ImportExport: FC = ({ hideSettingsModal, onModalChange }) => } }, [organizationId, projectId, workspacesFetcher]); const projectLoaderData = workspacesFetcher?.data; - const workspacesForActiveProject = - projectLoaderData?.files - .map(w => w.workspace) - .filter(isNotNullOrUndefined) - .filter(w => !isMcp(w)) || []; + const workspacesForActiveProject = projectLoaderData?.files.map(w => w.workspace).filter(isNotNullOrUndefined) || []; const activeProject = projectLoaderData?.activeProject; const projectName = activeProject?.name ?? getProductName(); const projects = projectLoaderData?.projects || []; @@ -717,7 +711,7 @@ export const ImportExport: FC = ({ hideSettingsModal, onModalChange }) => aria-label="Export all data" > - Export all data {`(${workspaceCount - mcpWorkspaceCount} files)`} + Export all data {`(${workspaceCount} files)`} ); } @@ -785,7 +779,7 @@ export const ImportExport: FC = ({ hideSettingsModal, onModalChange }) => aria-label="Export all data" > - Export all data {`(${workspaceCount - mcpWorkspaceCount} files)`} + Export all data {`(${workspaceCount} files)`}