Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
5ea7e97
feat: mcp client creation-[INS-1328] (#9103)
CurryYangxx Sep 5, 2025
2f7e671
feat: mcp client support - SDK integration & Basic request pane (#9109)
cwangsmv Sep 8, 2025
08bb209
feat: add custom mcp icon (#9112)
CurryYangxx Sep 8, 2025
e4e6ec1
feat: mcp workspace icon
CurryYangxx Sep 8, 2025
f7909fa
fix: mcp redirect route path
CurryYangxx Sep 8, 2025
a9a4fa5
feat: Support server capability list (#9117)
cwangsmv Sep 9, 2025
f1978d9
feat: integrate rjsf (#9120)
CurryYangxx Sep 9, 2025
e46cce6
feat: custom rjsf theme (#9125)
CurryYangxx Sep 10, 2025
dc7c0ee
feat: Add basic response pane for MCP (#9131)
cwangsmv Sep 11, 2025
4f576df
feat: support stdio transport (#9135)
ZxBing0066 Sep 12, 2025
1de3651
Chore: Move MCP request page into debug router path (#9140)
cwangsmv Sep 16, 2025
f4df197
feat: support stdio events, console, and UI (#9142)
ZxBing0066 Sep 16, 2025
2b9a104
feat: Enhance MCP response pane (#9144)
cwangsmv Sep 16, 2025
38bf4a9
fix rebase issue of duplicate close
cwangsmv Sep 16, 2025
cc46249
feat: support resource&prompt calling (#9148)
CurryYangxx Sep 17, 2025
1b21da0
fix: classNames
CurryYangxx Sep 17, 2025
aef08a3
fix message issue
cwangsmv Sep 17, 2025
908882e
feat: add access grant modal for stdio transport (#9149)
ZxBing0066 Sep 18, 2025
bbfb087
Merge branch 'develop' into feat/support-mcp-client
CurryYangxx Sep 18, 2025
a046273
feat: disable sync&export for mcp (#9155)
CurryYangxx Sep 18, 2025
104ad04
feat: store params in mcp payload model (#9153)
CurryYangxx Sep 18, 2025
c277f90
Merge branch 'develop' into feat/support-mcp-client
ZxBing0066 Sep 19, 2025
4364bf6
add hint for server list update (#9160)
cwangsmv Sep 19, 2025
7996f5d
fix: parse value (#9161)
CurryYangxx Sep 19, 2025
2f831bf
fix: base select placeholder
CurryYangxx Sep 22, 2025
9272a65
feat: complete the existing auth for mcp client (#9159)
ZxBing0066 Sep 23, 2025
9f88644
feat: Change observer pattern for mcp events and enable auto selectio…
cwangsmv Sep 24, 2025
b99f171
Feat: add MCP tab - [INS-1354] (#9166)
CurryYangxx Sep 24, 2025
020bf9e
support filter (#9169)
cwangsmv Sep 25, 2025
d592918
Merge branch 'develop' into feat/support-mcp-client
CurryYangxx Sep 25, 2025
a57ef2a
merge fix
CurryYangxx Sep 25, 2025
079d445
fix: resource template
CurryYangxx Sep 25, 2025
b567474
feat: add roots support for mcp client (#9175)
cwangsmv Sep 26, 2025
422f7da
fix: text color
CurryYangxx Sep 26, 2025
bc373ce
feat: support mcp auth flow (#9178)
ZxBing0066 Sep 26, 2025
9c97933
customize more template (#9177)
CurryYangxx Sep 26, 2025
3476bd2
make call tool button sticky to top
cwangsmv Sep 28, 2025
b6d6332
feat: hide duplicate (#9187)
CurryYangxx Sep 29, 2025
25d280e
fix: hide runner & hide send button & avoid multiple connect (#9186)
ZxBing0066 Sep 29, 2025
1a08518
fix: mcp multiple tab issue (#9185)
cwangsmv Sep 30, 2025
894e1d3
fix: mcp issues (#9196)
CurryYangxx Sep 30, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
607 changes: 607 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions packages/insomnia/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@
},
"devDependencies": {
"@develohpanda/fluent-builder": "^2.1.2",
"@modelcontextprotocol/sdk": "^1.17.5",
"@faker-js/faker": "9.7.0",
"@fortawesome/fontawesome-svg-core": "^6.7.2",
"@fortawesome/free-brands-svg-icons": "^6.7.2",
Expand All @@ -115,6 +116,9 @@
"@react-router/fs-routes": "^7.7.0",
"@react-router/node": "^7.7.0",
"@react-router/serve": "^7.7.0",
"@rjsf/core": "^6.0.0-beta.15",
"@rjsf/utils": "^6.0.0-beta.15",
"@rjsf/validator-ajv8": "^6.0.0-beta.15",
"@sentry/electron": "^6.5.0",
"@tailwindcss/typography": "^0.5.16",
"@tanstack/react-virtual": "3.13.6",
Expand Down
6 changes: 6 additions & 0 deletions packages/insomnia/src/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -550,3 +550,9 @@ export const RESPONSE_CODE_REASONS: Record<number, string> = {

// (ms) curently server timeout is 30s
export const INSOMNIA_FETCH_TIME_OUT = 30_000;

// channel names for real time events (websocket/socket-io/mcp)
export const REALTIME_EVENTS_CHANNELS = {
READY_STATE: 'readyState',
NEW_EVENT: 'newEventReceived',
};
12 changes: 12 additions & 0 deletions packages/insomnia/src/common/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,18 @@ export const database = {
...defaultConfig,
filename: fsPath.join(dbPath, 'insomnia.MockServer.db'),
}),
McpRequest: new NeDB({
...defaultConfig,
filename: fsPath.join(dbPath, 'insomnia.McpRequest.db'),
}),
McpResponse: new NeDB({
...defaultConfig,
filename: fsPath.join(dbPath, 'insomnia.McpResponse.db'),
}),
McpPayload: new NeDB({
...defaultConfig,
filename: fsPath.join(dbPath, 'insomnia.McpPayload.db'),
}),
OAuth2Token: new NeDB({
...defaultConfig,
filename: fsPath.join(dbPath, 'insomnia.OAuth2Token.db'),
Expand Down
6 changes: 5 additions & 1 deletion packages/insomnia/src/common/get-workspace-label.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { isDesign, isEnvironment, isMockServer, type Workspace } from '../models/workspace';
import { isDesign, isEnvironment, isMcp, isMockServer, type Workspace } from '../models/workspace';
import { strings } from './strings';

export const getWorkspaceLabel = (workspace: Workspace) => {
Expand All @@ -14,5 +14,9 @@ export const getWorkspaceLabel = (workspace: Workspace) => {
return strings.environment;
}

if (isMcp(workspace)) {
return strings.mcp;
}

return strings.collection;
};
9 changes: 8 additions & 1 deletion packages/insomnia/src/common/import-v5-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,14 @@ const ApiKeyAuthenticationSchema = z.object({
const OAuth2AuthenticationSchema = z.object({
type: z.literal('oauth2'),
disabled: z.boolean().optional(),
grantType: z.enum(['authorization_code', 'client_credentials', 'implicit', 'password', 'refresh_token']),
grantType: z.enum([
'authorization_code',
'client_credentials',
'implicit',
'password',
'refresh_token',
'mcp-auth-flow',
]),
accessTokenUrl: z.string().optional(),
authorizationUrl: z.string().optional(),
clientId: z.string().optional(),
Expand Down
205 changes: 205 additions & 0 deletions packages/insomnia/src/common/mcp-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
import { UriTemplate } from '@modelcontextprotocol/sdk/shared/uriTemplate.js';
import {
CallToolRequestSchema,
CallToolResultSchema,
CancelledNotificationSchema,
CreateMessageRequestSchema,
ElicitRequestSchema,
GetPromptRequestSchema,
GetPromptResultSchema,
InitializeRequestSchema,
InitializeResultSchema,
type JSONRPCMessage,
ListPromptsRequestSchema,
ListPromptsResultSchema,
ListResourcesRequestSchema,
ListResourcesResultSchema,
ListResourceTemplatesRequestSchema,
ListResourceTemplatesResultSchema,
ListRootsRequestSchema,
ListToolsRequestSchema,
ListToolsResultSchema,
LoggingMessageNotificationSchema,
ProgressNotificationSchema,
type Prompt,
PromptListChangedNotificationSchema,
ReadResourceRequestSchema,
ReadResourceResultSchema,
type Resource,
ResourceListChangedNotificationSchema,
type ResourceTemplate,
ResourceUpdatedNotificationSchema,
type ServerCapabilities,
ServerNotificationSchema,
ServerRequestSchema,
SubscribeRequestSchema,
type Tool,
ToolListChangedNotificationSchema,
UnsubscribeRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
import type { RJSFSchema } from '@rjsf/utils';

// methods for server features
export const METHOD_INITIALIZE = InitializeRequestSchema.shape.method.value;
export const METHOD_LIST_TOOLS = ListToolsRequestSchema.shape.method.value;
export const METHOD_LIST_RESOURCES = ListResourcesRequestSchema.shape.method.value;
export const METHOD_LIST_RESOURCE_TEMPLATES = ListResourceTemplatesRequestSchema.shape.method.value;
export const METHOD_LIST_PROMPTS = ListPromptsRequestSchema.shape.method.value;
export const METHOD_CALL_TOOL = CallToolRequestSchema.shape.method.value;
export const METHOD_READ_RESOURCE = ReadResourceRequestSchema.shape.method.value;
export const METHOD_GET_PROMPT = GetPromptRequestSchema.shape.method.value;
export const METHOD_SUBSCRIBE_RESOURCE = SubscribeRequestSchema.shape.method.value;
export const METHOD_UNSUBSCRIBE_RESOURCE = UnsubscribeRequestSchema.shape.method.value;
// methods for client features
export const METHOD_SAMPLING_CREATE_MESSAGE = CreateMessageRequestSchema.shape.method.value;
export const METHOD_LIST_ROOTS = ListRootsRequestSchema.shape.method.value;
export const METHOD_ELICITATION_CREATE_MESSAGE = ElicitRequestSchema.shape.method.value;
// methods for notifications
export const METHOD_NOTIFICATION_CANCELLED = CancelledNotificationSchema.shape.method.value;
export const METHOD_NOTIFICATION_PROGRESS = ProgressNotificationSchema.shape.method.value;
export const METHOD_NOTIFICATION_LOGGING_MESSAGE = LoggingMessageNotificationSchema.shape.method.value;
export const METHOD_NOTIFICATION_RESOURCE_UPDATED = ResourceUpdatedNotificationSchema.shape.method.value;
export const METHOD_NOTIFICATION_RESOURCE_LIST_CHANGED = ResourceListChangedNotificationSchema.shape.method.value;
export const METHOD_NOTIFICATION_TOOL_LIST_CHANGED = ToolListChangedNotificationSchema.shape.method.value;
export const METHOD_NOTIFICATION_PROMPT_LIST_CHANGED = PromptListChangedNotificationSchema.shape.method.value;

export const unsupportedMethodPrefix = 'Unsupported/';
export const METHOD_UNKNOWN = 'Unknown Method';
export const NOTIFICATION_METHODS = [
METHOD_NOTIFICATION_CANCELLED,
METHOD_NOTIFICATION_PROGRESS,
METHOD_NOTIFICATION_LOGGING_MESSAGE,
METHOD_NOTIFICATION_RESOURCE_UPDATED,
METHOD_NOTIFICATION_RESOURCE_LIST_CHANGED,
METHOD_NOTIFICATION_TOOL_LIST_CHANGED,
METHOD_NOTIFICATION_PROMPT_LIST_CHANGED,
] as const;
export const CLIENT_METHODS = [
METHOD_SAMPLING_CREATE_MESSAGE,
METHOD_LIST_ROOTS,
METHOD_ELICITATION_CREATE_MESSAGE,
] as const;
export const SERVER_METHODS = [
METHOD_INITIALIZE,
METHOD_LIST_TOOLS,
METHOD_LIST_RESOURCES,
METHOD_LIST_RESOURCE_TEMPLATES,
METHOD_LIST_PROMPTS,
METHOD_CALL_TOOL,
METHOD_READ_RESOURCE,
METHOD_GET_PROMPT,
];
export const NOTIFICATIONS_LIST_CHANGED = [
METHOD_NOTIFICATION_RESOURCE_LIST_CHANGED,
METHOD_NOTIFICATION_TOOL_LIST_CHANGED,
METHOD_NOTIFICATION_PROMPT_LIST_CHANGED,
];

export type McpServerMethods = (typeof SERVER_METHODS)[number];
export type NotificationMethods = (typeof NOTIFICATION_METHODS)[number];
export type McpClientMethods = (typeof CLIENT_METHODS)[number];
export type UnsupportedMcpClientMethods = `${typeof unsupportedMethodPrefix}${string}`;

export type JSONRPCMessageMethods = McpServerMethods | McpClientMethods | NotificationMethods;
export interface McpServerData {
serverCapabilities: ServerCapabilities;
primitives: {
tools: Tool[];
resources: Resource[];
resourceTemplates: ResourceTemplate[];
prompts: Prompt[];
};
}

type McpMessageEventMethods = JSONRPCMessageMethods | typeof METHOD_UNKNOWN | UnsupportedMcpClientMethods;
export const getMcpMethodFromMessage = (message: JSONRPCMessage): McpMessageEventMethods => {
let method: McpMessageEventMethods = 'Unknown Method';
if (ServerNotificationSchema.safeParse(message).success) {
// for server notification messages
method = ServerNotificationSchema.parse(message).method;
} else if ('result' in message) {
const messageResult = message.result;
if (InitializeResultSchema.safeParse(messageResult).success) {
method = METHOD_INITIALIZE;
} else if (ListToolsResultSchema.safeParse(messageResult).success) {
method = METHOD_LIST_TOOLS;
} else if (ListResourcesResultSchema.safeParse(messageResult).success) {
method = METHOD_LIST_RESOURCES;
} else if (ListResourceTemplatesResultSchema.safeParse(messageResult).success) {
method = METHOD_LIST_RESOURCE_TEMPLATES;
} else if (ListPromptsResultSchema.safeParse(messageResult).success) {
method = METHOD_LIST_PROMPTS;
} else if (GetPromptResultSchema.safeParse(messageResult).success) {
method = METHOD_GET_PROMPT;
} else if (ReadResourceResultSchema.safeParse(messageResult).success) {
method = METHOD_READ_RESOURCE;
} else if (CallToolResultSchema.safeParse(messageResult).success) {
method = METHOD_CALL_TOOL;
}
} else if (ServerRequestSchema.safeParse(message).success) {
const requestMethod = ServerRequestSchema.parse(message).method;
if (requestMethod === METHOD_LIST_ROOTS) {
method = METHOD_LIST_ROOTS;
} else {
// Do not support any server requests to client including ping, elicitation and sampling
method = `${unsupportedMethodPrefix}${ServerRequestSchema.parse(message).method}`;
}
}
return method;
};

export const getDefaultServerCapabilities = () => {
return {
tools: {
enabled: false,
listChanged: false,
},
resources: {
enabled: false,
listChanged: false,
subscribe: true,
},
prompts: {
enabled: false,
listChanged: false,
},
};
};

export const isResourceTemplate = (resource: Resource | ResourceTemplate): resource is ResourceTemplate => {
return 'uriTemplate' in resource && resource.uriTemplate !== undefined;
};

export const buildResourceJsonSchema = (resource: Resource | ResourceTemplate): RJSFSchema => {
if (isResourceTemplate(resource)) {
const uriTemplate = new UriTemplate(resource.uriTemplate);
const properties: Record<string, any> = {};
const required: string[] = [];
uriTemplate.variableNames.forEach(name => {
properties[name] = {
type: 'string',
};
required.push(name);
});
return {
type: 'object',
properties,
required,
};
}
return {
type: 'object',
properties: {
uri: {
type: 'string',
default: resource.uri,
},
},
required: ['uri'],
readOnly: true,
};
};

export const fillUriTemplate = (template: string, values: Record<string, string>): string => {
return new UriTemplate(template).expand(values);
};
4 changes: 3 additions & 1 deletion packages/insomnia/src/common/render.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
vaultEnvironmentRuntimePath,
} from '../models/environment';
import type { GrpcRequest, GrpcRequestBody } from '../models/grpc-request';
import type { McpRequest } from '../models/mcp-request';
import { isProject } from '../models/project';
import { PATH_PARAMETER_REGEX, type Request } from '../models/request';
import { isRequestGroup, type RequestGroup } from '../models/request-group';
Expand Down Expand Up @@ -704,12 +705,13 @@ function _getOrderedEnvironmentKeys(finalRenderContext: Record<string, any>): st
}

export async function getRenderContextAncestors(
base?: Request | GrpcRequest | WebSocketRequest | SocketIORequest | RequestGroup | Workspace,
base?: Request | GrpcRequest | WebSocketRequest | SocketIORequest | McpRequest | RequestGroup | Workspace,
): Promise<RenderContextAncestor[]> {
return await db.withAncestors<RenderContextAncestor>(base, [
models.request.type,
models.grpcRequest.type,
models.webSocketRequest.type,
models.mcpRequest.type,
models.requestGroup.type,
models.workspace.type,
models.project.type,
Expand Down
7 changes: 6 additions & 1 deletion packages/insomnia/src/common/strings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ type StringId =
| 'defaultProject'
| 'localProject'
| 'remoteProject'
| 'environment';
| 'environment'
| 'mcp';

export const strings: Record<StringId, StringInfo> = {
collection: {
Expand Down Expand Up @@ -56,4 +57,8 @@ export const strings: Record<StringId, StringInfo> = {
singular: 'Environment',
plural: 'Environments',
},
mcp: {
singular: 'MCP Client',
plural: 'MCP Clients',
},
};
2 changes: 2 additions & 0 deletions packages/insomnia/src/entry.main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { registerMainHandlers } from './main/ipc/main';
import { registerSecretStorageHandlers } from './main/ipc/secret-storage';
import log, { initializeLogging } from './main/log';
import { registerCurlHandlers } from './main/network/curl';
import { registerMcpHandlers } from './main/network/mcp';
import { registerSocketIOHandlers } from './main/network/socket-io';
import { registerWebSocketHandlers } from './main/network/websocket';
import { watchProxySettings } from './main/proxy';
Expand Down Expand Up @@ -75,6 +76,7 @@ app.on('ready', async () => {
registerWebSocketHandlers();
registerSocketIOHandlers();
registerCurlHandlers();
registerMcpHandlers();
registerSecretStorageHandlers();

/**
Expand Down
29 changes: 29 additions & 0 deletions packages/insomnia/src/entry.preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type { GitServiceAPI } from './main/git-service';
import type { gRPCBridgeAPI } from './main/ipc/grpc';
import type { secretStorageBridgeAPI } from './main/ipc/secret-storage';
import type { CurlBridgeAPI } from './main/network/curl';
import type { McpBridgeAPI } from './main/network/mcp';
import type { SocketIOBridgeAPI } from './main/network/socket-io';
import type { WebSocketBridgeAPI } from './main/network/websocket';
import { invariant } from './utils/invariant';
Expand Down Expand Up @@ -49,6 +50,33 @@ const socketIO: SocketIOBridgeAPI = {
},
};

const mcp: McpBridgeAPI = {
connect: options => ipcRenderer.invoke('mcp.connect', options),
close: options => ipcRenderer.invoke('mcp.close', options),
closeAll: () => ipcRenderer.send('mcp.closeAll'),
authConfirmation: confirmed => ipcRenderer.send('mcp.authConfirmed', confirmed),
primitive: {
listTools: options => ipcRenderer.invoke('mcp.primitive.listTools', options),
callTool: options => ipcRenderer.invoke('mcp.primitive.callTool', options),
listResources: options => ipcRenderer.invoke('mcp.primitive.listResources', options),
listResourceTemplates: options => ipcRenderer.invoke('mcp.primitive.listResourceTemplates', options),
readResource: options => ipcRenderer.invoke('mcp.primitive.readResource', options),
subscribeResource: options => ipcRenderer.invoke('mcp.primitive.subscribeResource', options),
unsubscribeResource: options => ipcRenderer.invoke('mcp.primitive.unsubscribeResource', options),
listPrompts: options => ipcRenderer.invoke('mcp.primitive.listPrompts', options),
getPrompt: options => ipcRenderer.invoke('mcp.primitive.getPrompt', options),
},
notification: {
rootListChange: options => ipcRenderer.invoke('mcp.notification.rootListChange', options),
},
readyState: {
getCurrent: options => ipcRenderer.invoke('mcp.readyState', options),
},
event: {
findMany: options => ipcRenderer.invoke('mcp.event.findMany', options),
},
};

const grpc: gRPCBridgeAPI = {
start: options => ipcRenderer.send('grpc.start', options),
sendMessage: options => ipcRenderer.send('grpc.sendMessage', options),
Expand Down Expand Up @@ -141,6 +169,7 @@ const main: Window['main'] = {
},
webSocket,
socketIO,
mcp,
git,
grpc,
curl,
Expand Down
Loading