mirror of
https://github.com/Kong/insomnia.git
synced 2026-02-14 08:01:42 -05:00
initial check-in for sync support
1.Remove sync related ui limitation for MCP client 2.Initial support for cloud sync add basic parser add missing attribute revert changes use tailwind v4 1.update mcp client types add a new type naming for insomnia mcp git files add test and fix export issue add mcp yaml export test update logic to detect insomnia supported yaml files fix type issue
This commit is contained in:
@@ -12,6 +12,8 @@ import {
|
||||
JsonSchema,
|
||||
KeyLiteralSchema,
|
||||
LiteralSchema,
|
||||
McpClientSchema,
|
||||
McpRequestSchema,
|
||||
MetaGroupSchema,
|
||||
MetaSchema,
|
||||
MockRouteSchema,
|
||||
@@ -131,6 +133,15 @@ const makeMockRoute = (overrides: Record<string, unknown> = {}) => ({
|
||||
...overrides,
|
||||
});
|
||||
|
||||
const makeMcpRequest = (overrides: Record<string, unknown> = {}) => ({
|
||||
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();
|
||||
});
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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<typeof RequestSchema>;
|
||||
type GRPCRequest = z.infer<typeof GRPCRequestSchema>;
|
||||
type WebsocketRequest = z.infer<typeof WebsocketRequestSchema>;
|
||||
@@ -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<typeof InsomniaFileSchema>;
|
||||
|
||||
|
||||
@@ -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<AllExportTypes, AllTypes> = {
|
||||
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<ScanR
|
||||
const workspaces = resources.filter(isWorkspace);
|
||||
const cookieJars = resources.filter(isCookieJar);
|
||||
const mockRoutes = resources.filter(isMockRoute);
|
||||
const mcpRequests = resources.filter(isMcpRequest);
|
||||
|
||||
return {
|
||||
type,
|
||||
@@ -244,6 +249,7 @@ export async function scanResources(importEntries: ImportEntry[]): Promise<ScanR
|
||||
apiSpecs,
|
||||
cookieJars,
|
||||
mockRoutes,
|
||||
mcpRequests,
|
||||
oriFileName,
|
||||
errors: [],
|
||||
};
|
||||
|
||||
@@ -17,11 +17,13 @@ import { parse, stringify } from 'yaml';
|
||||
import { type AllExportTypes, MODELS_BY_EXPORT_TYPE } from '~/common/import';
|
||||
import { migrateToLatestYaml } from '~/common/insomnia-schema-migrations';
|
||||
import { INSOMNIA_SCHEMA_VERSION } from '~/common/insomnia-schema-migrations/schema-version';
|
||||
import type { McpRequest } from '~/models/mcp-request';
|
||||
import { invariant } from '~/utils/invariant';
|
||||
|
||||
import * as models from '../models';
|
||||
import type { ApiSpec } from '../models/api-spec';
|
||||
import type { CookieJar } from '../models/cookie-jar';
|
||||
import type { EnvironmentKvPairData } from '../models/environment';
|
||||
import { type Environment, maskVaultEnvironmentData } from '../models/environment';
|
||||
import type { GrpcRequest } from '../models/grpc-request';
|
||||
import type { MockRoute } from '../models/mock-route';
|
||||
@@ -42,6 +44,7 @@ import {
|
||||
type Insomnia_WebsocketRequest,
|
||||
type InsomniaFile,
|
||||
InsomniaFileSchema,
|
||||
McpRequestSchema,
|
||||
type Meta,
|
||||
SocketIORequestSchema,
|
||||
WebsocketRequestSchema,
|
||||
@@ -279,6 +282,8 @@ export function insomniaSchemaTypeToScope(type: InsomniaFile['type']): Workspace
|
||||
return 'environment';
|
||||
} else if (type === 'spec.insomnia.rest/5.0') {
|
||||
return 'design';
|
||||
} else if (type === 'mcpClient.insomnia/5.0') {
|
||||
return 'mcp';
|
||||
}
|
||||
return 'mock-server';
|
||||
}
|
||||
@@ -642,6 +647,53 @@ function getCollection(
|
||||
return [];
|
||||
}
|
||||
|
||||
function getMcpRequest(file: InsomniaFile): WithExportType<McpRequest>[] {
|
||||
const commonProps: WithExportType<McpRequest> = {
|
||||
...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<InsomniaFile, { type: 'mcpClient.insomnia/5.0' }>['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) {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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: {},
|
||||
|
||||
@@ -79,8 +79,8 @@ export async function all() {
|
||||
return await db.find<Workspace>(type);
|
||||
}
|
||||
|
||||
export function count(scope?: WorkspaceScope) {
|
||||
return db.count<Workspace>(type, scope ? { scope } : {});
|
||||
export function count() {
|
||||
return db.count(type);
|
||||
}
|
||||
|
||||
export function update(workspace: Workspace, patch: Partial<Workspace>) {
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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: '',
|
||||
});
|
||||
|
||||
@@ -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<Workspace>(models.workspace.type, { parentId: this._projectId })).filter(
|
||||
w => !isMcp(w),
|
||||
);
|
||||
const workspaces = await db.find<Workspace>(models.workspace.type, { parentId: this._projectId });
|
||||
|
||||
const workspaceMetas = await db.find<WorkspaceMeta>(models.workspaceMeta.type, {
|
||||
parentId: {
|
||||
$in: workspaces.map(w => w._id),
|
||||
|
||||
@@ -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 = () => {
|
||||
</GridList>
|
||||
</div>
|
||||
</div>
|
||||
<WorkspaceSyncDropdown />
|
||||
|
||||
{isEnvironmentModalOpen && <WorkspaceEnvironmentsEditModal onClose={() => setEnvironmentModalOpen(false)} />}
|
||||
{isCertificatesModalOpen && <MCPCertificatesModal onClose={() => setCertificatesModalOpen(false)} />}
|
||||
</div>
|
||||
|
||||
@@ -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"
|
||||
>
|
||||
<Modal
|
||||
className={`flex max-h-[90dvh] w-full max-w-3xl flex-col overflow-hidden rounded-md border border-solid border-(--hl-sm) bg-(--color-bg) text-(--color-font) ${isGitProjectAndNotMcpWorkspace ? 'min-h-[420px]' : 'min-h-[220px]'}`}
|
||||
className={`flex max-h-[90dvh] w-full max-w-3xl flex-col overflow-hidden rounded-md border border-solid border-(--hl-sm) bg-(--color-bg) text-(--color-font) ${isGitProject(project) ? 'min-h-[420px]' : 'min-h-[220px]'}`}
|
||||
>
|
||||
<Dialog
|
||||
aria-label="Create or update dialog"
|
||||
@@ -236,8 +231,7 @@ export const NewWorkspaceModal = ({
|
||||
/>
|
||||
<FieldError className="text-xs text-red-500" />
|
||||
</TextField>
|
||||
{/* Mcp workspaces do not support Git sync for now */}
|
||||
{isGitProjectAndNotMcpWorkspace && (
|
||||
{isGitProject(project) && (
|
||||
<>
|
||||
<TextField
|
||||
name="fileName"
|
||||
|
||||
@@ -12,7 +12,7 @@ import * as models from 'insomnia/src/models/index';
|
||||
import { type BaseModel, environment } from 'insomnia/src/models/index';
|
||||
import { isScratchpadOrganizationId, type Organization } from 'insomnia/src/models/organization';
|
||||
import type { Project } from 'insomnia/src/models/project';
|
||||
import { isMcp, isScratchpad, type Workspace } from 'insomnia/src/models/workspace';
|
||||
import { isScratchpad, type Workspace } from 'insomnia/src/models/workspace';
|
||||
import { SegmentEvent } from 'insomnia/src/ui/analytics';
|
||||
import { Icon } from 'insomnia/src/ui/components/icon';
|
||||
import { showError, showModal } from 'insomnia/src/ui/components/modals';
|
||||
@@ -391,10 +391,8 @@ export async function exportWorkspaceData({
|
||||
export async function exportAllData({ dirPath }: { dirPath: string }): Promise<void> {
|
||||
const workspaces = await database.find<Workspace>(models.workspace.type);
|
||||
|
||||
const workspacesWithoutMcp = workspaces.filter(w => !isMcp(w));
|
||||
|
||||
const baseEnvironments = await database.find<Environment>(environment.type, {
|
||||
parentId: { $in: workspacesWithoutMcp.map(w => w._id) },
|
||||
parentId: { $in: workspaces.map(w => w._id) },
|
||||
});
|
||||
|
||||
const subEnvironments = await database.find<Environment>(environment.type, {
|
||||
@@ -408,7 +406,7 @@ export async function exportAllData({ dirPath }: { dirPath: string }): Promise<v
|
||||
|
||||
const insomniaExportFolder = window.path.join(dirPath, `insomnia-export.${Date.now()}`);
|
||||
|
||||
for (const workspace of workspacesWithoutMcp) {
|
||||
for (const workspace of workspaces) {
|
||||
await exportWorkspaceData({
|
||||
workspace,
|
||||
dirPath: insomniaExportFolder,
|
||||
@@ -635,7 +633,7 @@ export const ImportExport: FC<Props> = ({ 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<Props> = ({ 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<Props> = ({ hideSettingsModal, onModalChange }) =>
|
||||
aria-label="Export all data"
|
||||
>
|
||||
<Icon icon="file-export" />
|
||||
<span>Export all data {`(${workspaceCount - mcpWorkspaceCount} files)`}</span>
|
||||
<span>Export all data {`(${workspaceCount} files)`}</span>
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
@@ -785,7 +779,7 @@ export const ImportExport: FC<Props> = ({ hideSettingsModal, onModalChange }) =>
|
||||
aria-label="Export all data"
|
||||
>
|
||||
<Icon icon="file-export" />
|
||||
<span>Export all data {`(${workspaceCount - mcpWorkspaceCount} files)`}</span>
|
||||
<span>Export all data {`(${workspaceCount} files)`}</span>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
|
||||
@@ -4,6 +4,7 @@ import * as models from '~/models';
|
||||
import type { ApiSpec } from '~/models/api-spec';
|
||||
import type { Environment } from '~/models/environment';
|
||||
import type { GrpcRequest } from '~/models/grpc-request';
|
||||
import type { McpRequest } from '~/models/mcp-request';
|
||||
import type { MockRoute } from '~/models/mock-route';
|
||||
import type { MockServer } from '~/models/mock-server';
|
||||
import type { Request } from '~/models/request';
|
||||
@@ -49,6 +50,7 @@ export async function getSyncItems({ workspaceId }: { workspaceId: string }) {
|
||||
| Environment
|
||||
| ApiSpec
|
||||
| Request
|
||||
| McpRequest
|
||||
| WebSocketRequest
|
||||
| SocketIORequest
|
||||
| GrpcRequest
|
||||
@@ -108,6 +110,11 @@ export async function getSyncItems({ workspaceId }: { workspaceId: string }) {
|
||||
mockRoutes.map(m => syncItemsList.push(m));
|
||||
}
|
||||
|
||||
const mcpRequest = await models.mcpRequest.getByParentId(workspaceId);
|
||||
if (mcpRequest) {
|
||||
syncItemsList.push(mcpRequest);
|
||||
}
|
||||
|
||||
const baseEnvironment = await models.environment.getByParentId(workspaceId);
|
||||
invariant(baseEnvironment, 'Base environment not found');
|
||||
|
||||
|
||||
Reference in New Issue
Block a user