mirror of
https://github.com/Kong/insomnia.git
synced 2026-11-02 19:15:23 -05:00
test(insomnia-test): add mcp-client error-response, same-name selection and TLS specs
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
21d4d4146d
commit
e124b2c8a8
20 files changed
+953
No files matched your search
@@ -0,0 +1,46 @@
|
||||
import { faker } from "@faker-js/faker";
|
||||
import { expect } from "@playwright/test";
|
||||
|
||||
import { ProjectType } from "../../enums/project-types";
|
||||
import { certPath } from "../../misc/certs";
|
||||
import { MCP_SERVER_CUSTOM_CA_HTTPS, test } from "../../misc/fixtures";
|
||||
import { McpClient } from "../../models/mcp-client";
|
||||
import { Project } from "../../models/project";
|
||||
|
||||
test("Verify Custom CA Certificate Toggle Controls Trust For MCP Client Over TLS", async ({
|
||||
user,
|
||||
}) => {
|
||||
const { certificatesFlow, mcpClientFlow, workspaceFlow } = user.flowManager;
|
||||
|
||||
const project = await workspaceFlow.createProject(
|
||||
new Project(faker.string.alphanumeric(10), ProjectType.Local),
|
||||
);
|
||||
|
||||
const mcpClient = await mcpClientFlow.create(
|
||||
project,
|
||||
new McpClient(faker.string.alphanumeric(10), MCP_SERVER_CUSTOM_CA_HTTPS),
|
||||
);
|
||||
await mcpClientFlow.connect(mcpClient, { expectConnected: false });
|
||||
const toolsBeforeCa = mcpClientFlow.getTools(mcpClient);
|
||||
await expect(toolsBeforeCa).rejects.toThrow();
|
||||
|
||||
await certificatesFlow.setCaCertificate(mcpClient, certPath("mtls-ca.pem"));
|
||||
|
||||
await mcpClientFlow.connect(mcpClient);
|
||||
const toolsAfterCa = await mcpClientFlow.getTools(mcpClient);
|
||||
await mcpClientFlow.disconnect(mcpClient);
|
||||
|
||||
await certificatesFlow.setCaCertificateEnabled(mcpClient, false);
|
||||
|
||||
await mcpClientFlow.connect(mcpClient, { expectConnected: false });
|
||||
const toolsAfterDisable = mcpClientFlow.getTools(mcpClient);
|
||||
await expect(toolsAfterDisable).rejects.toThrow();
|
||||
|
||||
expect(toolsAfterCa).toEqual(
|
||||
expect.arrayContaining([
|
||||
"read_wiki_structure",
|
||||
"read_wiki_contents",
|
||||
"ask_question",
|
||||
]),
|
||||
);
|
||||
});
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
import { faker } from '@faker-js/faker';
|
||||
import { expect } from "@playwright/test";
|
||||
|
||||
import { McpPrimitiveType } from '../../../enums/mcp-capability';
|
||||
import { ProjectType } from '../../../enums/project-types';
|
||||
import { DEFAULT_TIMEOUT, test } from "../../../misc/fixtures";
|
||||
import { scriptMcpListServer } from '../../../misc/mcp-list-server';
|
||||
import { McpClient } from '../../../models/mcp-client';
|
||||
import { Project } from '../../../models/project';
|
||||
import { skippedTitle, validTool } from './helpers';
|
||||
|
||||
test('Verify Invalid Entries Sharing One Name Are All Listed Per Response Alongside a Valid Entry of That Name', async ({
|
||||
user,
|
||||
}) => {
|
||||
test.fail(true, 'INS-4130');
|
||||
const { mcpClientFlow, workspaceFlow } = user.flowManager;
|
||||
const { mcpClientPage } = user.pageManager;
|
||||
|
||||
const sharedName = faker.string.alphanumeric(8);
|
||||
const url = await scriptMcpListServer(faker.string.alphanumeric(12), {
|
||||
'tools/list': {
|
||||
result: {
|
||||
tools: [
|
||||
{ name: sharedName, inputSchema: { type: 'object' } },
|
||||
validTool,
|
||||
{ name: sharedName, inputSchema: { type: 'object' } },
|
||||
{ name: sharedName },
|
||||
{ name: sharedName },
|
||||
],
|
||||
},
|
||||
},
|
||||
'resources/list': {
|
||||
result: { resources: [{ uri: 'file:///valid.txt', name: sharedName }, { name: sharedName }, { name: sharedName }] },
|
||||
},
|
||||
'resources/templates/list': {
|
||||
result: { resourceTemplates: [{ uriTemplate: 'file:///{path}', name: sharedName }, { name: sharedName }] },
|
||||
},
|
||||
'prompts/list': {
|
||||
result: {
|
||||
prompts: [{ name: sharedName }, { name: sharedName, arguments: 'x' }, { name: sharedName, arguments: 'y' }],
|
||||
},
|
||||
},
|
||||
});
|
||||
const project = await workspaceFlow.createProject(new Project(faker.string.alphanumeric(10), ProjectType.Local));
|
||||
const client = await mcpClientFlow.create(project, new McpClient(faker.string.alphanumeric(10), url));
|
||||
await mcpClientFlow.connect(client);
|
||||
|
||||
const toolsError = await mcpClientFlow.getListError(client, McpPrimitiveType.Tools);
|
||||
const resourcesError = await mcpClientFlow.getListError(client, McpPrimitiveType.Resources);
|
||||
const promptsError = await mcpClientFlow.getListError(client, McpPrimitiveType.Prompts);
|
||||
|
||||
expect(toolsError.title).toMatch(skippedTitle(3));
|
||||
expect(toolsError.entries.map(entry => entry.label)).toEqual([sharedName, sharedName, sharedName]);
|
||||
expect(resourcesError.entries.map(entry => entry.label)).toEqual([sharedName, sharedName, sharedName]);
|
||||
expect(resourcesError.entries.map(entry => entry.reason)).toEqual([
|
||||
expect.stringMatching(/^uri:/),
|
||||
expect.stringMatching(/^uri:/),
|
||||
expect.stringMatching(/^uriTemplate:/),
|
||||
]);
|
||||
expect(promptsError.title).toMatch(skippedTitle(2));
|
||||
expect(promptsError.entries.map(entry => entry.label)).toEqual([sharedName, sharedName]);
|
||||
await expect
|
||||
.poll(() => mcpClientPage.getPrimitives(McpPrimitiveType.Tools), { timeout: DEFAULT_TIMEOUT })
|
||||
.toEqual([sharedName, validTool.name]);
|
||||
for (const type of [McpPrimitiveType.Resources, McpPrimitiveType.Prompts, McpPrimitiveType.ResourceTemplates]) {
|
||||
await expect
|
||||
.poll(() => mcpClientPage.getPrimitives(type), { timeout: DEFAULT_TIMEOUT })
|
||||
.toEqual([sharedName]);
|
||||
}
|
||||
});
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
import { faker } from '@faker-js/faker';
|
||||
import { expect } from "@playwright/test";
|
||||
|
||||
import { McpPrimitiveType } from '../../../enums/mcp-capability';
|
||||
import { ProjectType } from '../../../enums/project-types';
|
||||
import { test } from "../../../misc/fixtures";
|
||||
import { scriptMcpListServer } from '../../../misc/mcp-list-server';
|
||||
import { McpClient } from '../../../models/mcp-client';
|
||||
import { Project } from '../../../models/project';
|
||||
import { validTool } from './helpers';
|
||||
|
||||
test('Verify an Entry Without a Usable Name Is Labeled by Its Position and Shows Its First Issue', async ({ user }) => {
|
||||
const { mcpClientFlow, workspaceFlow } = user.flowManager;
|
||||
const url = await scriptMcpListServer(faker.string.alphanumeric(12), {
|
||||
'tools/list': {
|
||||
result: {
|
||||
tools: [validTool, 42, 'just-a-string', null, { name: 123, description: 456, inputSchema: 'not-an-object' }],
|
||||
},
|
||||
},
|
||||
});
|
||||
const project = await workspaceFlow.createProject(new Project(faker.string.alphanumeric(10), ProjectType.Local));
|
||||
const client = await mcpClientFlow.create(project, new McpClient(faker.string.alphanumeric(10), url));
|
||||
await mcpClientFlow.connect(client);
|
||||
|
||||
const error = await mcpClientFlow.getListError(client, McpPrimitiveType.Tools);
|
||||
expect(error.entries.map(entry => entry.label)).toEqual(['index 1', 'index 2', 'index 3', 'index 4']);
|
||||
expect(error.entries[3].reason).toMatch(/\(And \d+ more\)$/);
|
||||
});
|
||||
|
||||
test('Verify a Long Error Panel Scrolls Inside Its Own Area Without Overlapping the Next Row', async ({ user }) => {
|
||||
const { mcpClientFlow, workspaceFlow } = user.flowManager;
|
||||
const { mcpClientPage } = user.pageManager;
|
||||
const badTools = Array.from({ length: 12 }, (_, i) => ({ name: `bad-${i + 1}` }));
|
||||
const url = await scriptMcpListServer(faker.string.alphanumeric(12), {
|
||||
'tools/list': { result: { tools: [validTool, ...badTools] } },
|
||||
'prompts/list': { result: { prompts: [{ name: 'valid-prompt' }] } },
|
||||
});
|
||||
const project = await workspaceFlow.createProject(new Project(faker.string.alphanumeric(10), ProjectType.Local));
|
||||
const client = await mcpClientFlow.create(project, new McpClient(faker.string.alphanumeric(10), url));
|
||||
await mcpClientFlow.connect(client);
|
||||
|
||||
const error = await mcpClientFlow.getListError(client, McpPrimitiveType.Tools);
|
||||
expect(error.entries).toHaveLength(12);
|
||||
expect(await mcpClientPage.isCapabilityErrorScrollable(McpPrimitiveType.Tools)).toBe(true);
|
||||
|
||||
const panelBox = await mcpClientPage.getCapabilityErrorBounds(McpPrimitiveType.Tools);
|
||||
const nextCapabilityBox = await mcpClientPage.getCapabilityBounds(McpPrimitiveType.Prompts);
|
||||
expect(panelBox.y + panelBox.height).toBeLessThanOrEqual(nextCapabilityBox.y + 1);
|
||||
});
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
import { faker } from '@faker-js/faker';
|
||||
import { expect } from "@playwright/test";
|
||||
|
||||
import { McpPrimitiveType } from '../../../enums/mcp-capability';
|
||||
import { ProjectType } from '../../../enums/project-types';
|
||||
import { test } from "../../../misc/fixtures";
|
||||
import { scriptMcpListServer } from '../../../misc/mcp-list-server';
|
||||
import { McpClient } from '../../../models/mcp-client';
|
||||
import { Project } from '../../../models/project';
|
||||
import { validTool } from './helpers';
|
||||
|
||||
test('Verify the Error Panel of a Partially Invalid List Follows Expand and Collapse', async ({ user }) => {
|
||||
const { mcpClientFlow, workspaceFlow } = user.flowManager;
|
||||
const { mcpClientPage } = user.pageManager;
|
||||
const url = await scriptMcpListServer(faker.string.alphanumeric(12), {
|
||||
'tools/list': {
|
||||
result: { tools: [validTool, { name: 'no-schema-tool' }] },
|
||||
},
|
||||
'prompts/list': { result: { prompts: [{ name: 'valid-prompt' }] } },
|
||||
});
|
||||
const project = await workspaceFlow.createProject(new Project(faker.string.alphanumeric(10), ProjectType.Local));
|
||||
const client = await mcpClientFlow.create(project, new McpClient(faker.string.alphanumeric(10), url));
|
||||
await mcpClientFlow.connect(client);
|
||||
await mcpClientFlow.getListError(client, McpPrimitiveType.Tools);
|
||||
await expect.poll(() => mcpClientPage.hasCapability(McpPrimitiveType.Prompts)).toBe(true);
|
||||
|
||||
const panelBox = await mcpClientPage.getCapabilityErrorBounds(McpPrimitiveType.Tools);
|
||||
const nextCapabilityBox = await mcpClientPage.getCapabilityBounds(McpPrimitiveType.Prompts);
|
||||
expect(panelBox.y + panelBox.height).toBeLessThanOrEqual(nextCapabilityBox.y + 1);
|
||||
|
||||
await mcpClientFlow.toggleCapability(client, McpPrimitiveType.Tools);
|
||||
await expect.poll(() => mcpClientPage.getCapabilityErrorCount(McpPrimitiveType.Tools)).toBe(0);
|
||||
|
||||
await mcpClientFlow.toggleCapability(client, McpPrimitiveType.Tools);
|
||||
await expect.poll(() => mcpClientPage.getCapabilityErrorCount(McpPrimitiveType.Tools)).toBe(1);
|
||||
});
|
||||
|
||||
test('Verify the Error Panel of an Empty List Stays Visible Without a Collapse Caret', async ({ user }) => {
|
||||
const { mcpClientFlow, workspaceFlow } = user.flowManager;
|
||||
const { mcpClientPage } = user.pageManager;
|
||||
const url = await scriptMcpListServer(faker.string.alphanumeric(12), {
|
||||
'tools/list': { result: { tools: 'not-an-array' } },
|
||||
});
|
||||
const project = await workspaceFlow.createProject(new Project(faker.string.alphanumeric(10), ProjectType.Local));
|
||||
const client = await mcpClientFlow.create(project, new McpClient(faker.string.alphanumeric(10), url));
|
||||
await mcpClientFlow.connect(client);
|
||||
await mcpClientFlow.getListError(client, McpPrimitiveType.Tools);
|
||||
|
||||
expect(await mcpClientPage.hasCaret(McpPrimitiveType.Tools)).toBe(false);
|
||||
await mcpClientFlow.toggleCapability(client, McpPrimitiveType.Tools);
|
||||
await expect.poll(() => mcpClientPage.getCapabilityErrorCount(McpPrimitiveType.Tools)).toBe(1);
|
||||
});
|
||||
@@ -0,0 +1,9 @@
|
||||
export const validTool = { name: 'valid-tool', inputSchema: { type: 'object' } };
|
||||
export const wholeFailureTitle = (method: string) =>
|
||||
`The server returned a ${method} response that does not match the MCP schema.`;
|
||||
// The wording of the skipped-entries title is still being settled (INS-3647
|
||||
// notes): accept either the primitive name or "entries" as the noun.
|
||||
export const skippedTitle = (count: number) =>
|
||||
new RegExp(
|
||||
`^${count} (entries|entry|tools?|resources?|prompts?) (was|were) skipped because they do not match the MCP schema\\.$`,
|
||||
);
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
import { faker } from '@faker-js/faker';
|
||||
import { expect } from "@playwright/test";
|
||||
|
||||
import { McpPrimitiveType } from '../../../enums/mcp-capability';
|
||||
import { ProjectType } from '../../../enums/project-types';
|
||||
import { test } from "../../../misc/fixtures";
|
||||
import { scriptMcpListServer } from '../../../misc/mcp-list-server';
|
||||
import { McpClient } from '../../../models/mcp-client';
|
||||
import { Project } from '../../../models/project';
|
||||
|
||||
test('Verify a JSON-RPC Error Whose Message Is a JSON String Is Shown Readably', async ({ user }) => {
|
||||
const { mcpClientFlow, workspaceFlow } = user.flowManager;
|
||||
const url = await scriptMcpListServer(faker.string.alphanumeric(12), {
|
||||
'tools/list': { error: { code: -32_603, message: "{ detail: 'boom' }" } },
|
||||
});
|
||||
const project = await workspaceFlow.createProject(new Project(faker.string.alphanumeric(10), ProjectType.Local));
|
||||
const client = await mcpClientFlow.create(project, new McpClient(faker.string.alphanumeric(10), url));
|
||||
await mcpClientFlow.connect(client);
|
||||
|
||||
const error = await user.flowManager.mcpClientFlow.getListError(client, McpPrimitiveType.Tools);
|
||||
|
||||
expect(error.title).toContain('boom');
|
||||
expect(error.title).not.toContain('[object Object]');
|
||||
});
|
||||
@@ -0,0 +1,30 @@
|
||||
import { faker } from '@faker-js/faker';
|
||||
import { expect } from "@playwright/test";
|
||||
|
||||
import { McpPrimitiveType } from '../../../enums/mcp-capability';
|
||||
import { ProjectType } from '../../../enums/project-types';
|
||||
import { test } from "../../../misc/fixtures";
|
||||
import { scriptMcpListServer } from '../../../misc/mcp-list-server';
|
||||
import { McpClient } from '../../../models/mcp-client';
|
||||
import { Project } from '../../../models/project';
|
||||
|
||||
const jsonRpcErrors = [
|
||||
{ code: -32_601, message: 'Method not found' },
|
||||
{ code: -32_603, message: 'Internal error' },
|
||||
];
|
||||
|
||||
for (const rpcError of jsonRpcErrors) {
|
||||
test(`Verify a JSON-RPC Error on tools/list (${rpcError.message}) Is Shown`, async ({ user }) => {
|
||||
const { mcpClientFlow, workspaceFlow } = user.flowManager;
|
||||
const url = await scriptMcpListServer(faker.string.alphanumeric(12), {
|
||||
'tools/list': { error: rpcError },
|
||||
});
|
||||
const project = await workspaceFlow.createProject(new Project(faker.string.alphanumeric(10), ProjectType.Local));
|
||||
const client = await mcpClientFlow.create(project, new McpClient(faker.string.alphanumeric(10), url));
|
||||
await mcpClientFlow.connect(client);
|
||||
|
||||
const error = await user.flowManager.mcpClientFlow.getListError(client, McpPrimitiveType.Tools);
|
||||
expect(error.title).toContain(rpcError.message);
|
||||
expect(error.entries).toEqual([]);
|
||||
});
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
import { faker } from '@faker-js/faker';
|
||||
import { expect } from "@playwright/test";
|
||||
|
||||
import { McpPrimitiveType } from '../../../enums/mcp-capability';
|
||||
import { ProjectType } from '../../../enums/project-types';
|
||||
import { DEFAULT_TIMEOUT, test } from "../../../misc/fixtures";
|
||||
import { scriptMcpListServer } from '../../../misc/mcp-list-server';
|
||||
import { McpClient } from '../../../models/mcp-client';
|
||||
import { Project } from '../../../models/project';
|
||||
|
||||
test('Verify Load More Whose Next Page Has an Invalid Entry Lists the Skipped Entry in the Error Panel', async ({
|
||||
user,
|
||||
}) => {
|
||||
const { mcpClientFlow, workspaceFlow } = user.flowManager;
|
||||
const { mcpClientPage } = user.pageManager;
|
||||
const url = await scriptMcpListServer(faker.string.alphanumeric(12), {
|
||||
'resources/list': {
|
||||
pages: {
|
||||
'': {
|
||||
result: {
|
||||
resources: [{ name: 'item-1', uri: 'file:///item-1.txt' }],
|
||||
nextCursor: 'page-2',
|
||||
},
|
||||
},
|
||||
'page-2': {
|
||||
result: {
|
||||
resources: [{ name: 'item-2', uri: 'file:///item-2.txt' }, { name: 'bad-resource' }],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const project = await workspaceFlow.createProject(new Project(faker.string.alphanumeric(10), ProjectType.Local));
|
||||
const client = await mcpClientFlow.create(project, new McpClient(faker.string.alphanumeric(10), url));
|
||||
await mcpClientFlow.connect(client);
|
||||
await expect.poll(() => mcpClientPage.getPrimitives(McpPrimitiveType.Resources), { timeout: DEFAULT_TIMEOUT }).toEqual(['item-1']);
|
||||
|
||||
await mcpClientFlow.loadMore(client, McpPrimitiveType.Resources);
|
||||
await expect
|
||||
.poll(() => mcpClientPage.getPrimitives(McpPrimitiveType.Resources), { timeout: DEFAULT_TIMEOUT })
|
||||
.toEqual(['item-1', 'item-2']);
|
||||
const error = await mcpClientFlow.getListError(client, McpPrimitiveType.Resources);
|
||||
|
||||
expect(error.entries.map(entry => entry.label)).toEqual(['bad-resource']);
|
||||
});
|
||||
@@ -0,0 +1,56 @@
|
||||
import { faker } from '@faker-js/faker';
|
||||
import { expect } from "@playwright/test";
|
||||
|
||||
import { McpListAction, McpPrimitiveType } from '../../../enums/mcp-capability';
|
||||
import { ProjectType } from '../../../enums/project-types';
|
||||
import { DEFAULT_TIMEOUT, test } from "../../../misc/fixtures";
|
||||
import { scriptMcpListServer } from '../../../misc/mcp-list-server';
|
||||
import { McpClient } from '../../../models/mcp-client';
|
||||
import { Project } from '../../../models/project';
|
||||
|
||||
const loadMoreVariants = [
|
||||
{
|
||||
type: McpPrimitiveType.Tools,
|
||||
method: 'tools/list',
|
||||
entry: (name: string) => ({ name, inputSchema: { type: 'object' } }),
|
||||
badEntry: { name: 'bad-tool' },
|
||||
},
|
||||
{
|
||||
type: McpPrimitiveType.Prompts,
|
||||
method: 'prompts/list',
|
||||
entry: (name: string) => ({ name }),
|
||||
badEntry: { name: 'bad-prompt', arguments: 'x' },
|
||||
},
|
||||
{
|
||||
type: McpPrimitiveType.Resources,
|
||||
method: 'resources/list',
|
||||
entry: (name: string) => ({ name, uri: `file:///${name}.txt` }),
|
||||
badEntry: { name: 'bad-resource' },
|
||||
},
|
||||
] as const;
|
||||
|
||||
for (const { type, method, entry, badEntry } of loadMoreVariants) {
|
||||
test(`Verify Load More on ${type} with a Bad Page Keeps Loaded Entries and Clears the Cursor`, async ({ user }) => {
|
||||
const { mcpClientFlow, workspaceFlow } = user.flowManager;
|
||||
const { mcpClientPage } = user.pageManager;
|
||||
const url = await scriptMcpListServer(faker.string.alphanumeric(12), {
|
||||
[method]: {
|
||||
pages: {
|
||||
'': { result: { [type]: [entry('item-1')], nextCursor: 'page-2' } },
|
||||
'page-2': { result: { [type]: [entry('item-2'), badEntry] } },
|
||||
},
|
||||
},
|
||||
});
|
||||
const project = await workspaceFlow.createProject(new Project(faker.string.alphanumeric(10), ProjectType.Local));
|
||||
const client = await mcpClientFlow.create(project, new McpClient(faker.string.alphanumeric(10), url));
|
||||
await mcpClientFlow.connect(client);
|
||||
await expect.poll(() => mcpClientPage.getPrimitives(type), { timeout: DEFAULT_TIMEOUT }).toEqual(['item-1']);
|
||||
expect(await mcpClientFlow.getListAction(client, type)).toBe(McpListAction.LoadMore);
|
||||
|
||||
await mcpClientFlow.loadMore(client, type);
|
||||
await expect
|
||||
.poll(() => mcpClientPage.getPrimitives(type), { timeout: DEFAULT_TIMEOUT })
|
||||
.toEqual(['item-1', 'item-2']);
|
||||
expect(await mcpClientFlow.getListAction(client, type)).toBe(McpListAction.Reload);
|
||||
});
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
import { faker } from '@faker-js/faker';
|
||||
import { expect } from "@playwright/test";
|
||||
|
||||
import { McpPrimitiveType } from '../../../enums/mcp-capability';
|
||||
import { ProjectType } from '../../../enums/project-types';
|
||||
import { DEFAULT_TIMEOUT, test } from "../../../misc/fixtures";
|
||||
import { scriptMcpListServer } from '../../../misc/mcp-list-server';
|
||||
import { McpClient } from '../../../models/mcp-client';
|
||||
import { Project } from '../../../models/project';
|
||||
import { skippedTitle, validTool } from './helpers';
|
||||
|
||||
const partialInvalidTools: Record<string, { tools: unknown[]; skipped: string[] }> = {
|
||||
'missing or invalid inputSchema': {
|
||||
tools: [
|
||||
{ ...validTool, description: 'ok', inputSchema: { type: 'object', properties: {} } },
|
||||
{ name: 'no-schema-tool', description: 'missing inputSchema' },
|
||||
{ name: 'bad-schema-tool', inputSchema: { type: 'string' } },
|
||||
],
|
||||
skipped: ['no-schema-tool', 'bad-schema-tool'],
|
||||
},
|
||||
'missing or non-string name': {
|
||||
tools: [validTool, { inputSchema: { type: 'object' } }, { name: 123, inputSchema: { type: 'object' } }],
|
||||
skipped: ['index 1', 'index 2'],
|
||||
},
|
||||
};
|
||||
|
||||
for (const [variant, { tools, skipped }] of Object.entries(partialInvalidTools)) {
|
||||
test(`Verify a Partially Invalid tools/list (${variant}) Skips the Bad Entries and Lists Them`, async ({ user }) => {
|
||||
const { mcpClientFlow, workspaceFlow } = user.flowManager;
|
||||
const { mcpClientPage } = user.pageManager;
|
||||
const url = await scriptMcpListServer(faker.string.alphanumeric(12), { 'tools/list': { result: { tools } } });
|
||||
const project = await workspaceFlow.createProject(new Project(faker.string.alphanumeric(10), ProjectType.Local));
|
||||
const client = await mcpClientFlow.create(project, new McpClient(faker.string.alphanumeric(10), url));
|
||||
await mcpClientFlow.connect(client);
|
||||
|
||||
const error = await mcpClientFlow.getListError(client, McpPrimitiveType.Tools);
|
||||
expect(error.title).toMatch(skippedTitle(2));
|
||||
expect(error.entries.map(entry => entry.label)).toEqual(skipped);
|
||||
for (const entry of error.entries) {
|
||||
expect(entry.reason).not.toBe('');
|
||||
}
|
||||
await expect
|
||||
.poll(() => mcpClientPage.getPrimitives(McpPrimitiveType.Tools), {
|
||||
timeout: DEFAULT_TIMEOUT,
|
||||
})
|
||||
.toEqual(['valid-tool']);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import { faker } from '@faker-js/faker';
|
||||
import { expect } from "@playwright/test";
|
||||
|
||||
import { McpPrimitiveType } from '../../../enums/mcp-capability';
|
||||
import { ProjectType } from '../../../enums/project-types';
|
||||
import { DEFAULT_TIMEOUT, test } from "../../../misc/fixtures";
|
||||
import { type McpListConfig, scriptMcpListServer } from '../../../misc/mcp-list-server';
|
||||
import { McpClient } from '../../../models/mcp-client';
|
||||
import { Project } from '../../../models/project';
|
||||
import { validTool, wholeFailureTitle } from './helpers';
|
||||
|
||||
test('Verify Refresh Clears the Error Once the Server Returns Valid Data', async ({ user }) => {
|
||||
const { mcpClientFlow } = user.flowManager;
|
||||
const { mcpClientPage } = user.pageManager;
|
||||
const invalid: McpListConfig = { 'tools/list': { result: { tools: 'not-an-array' } } };
|
||||
const valid: McpListConfig = { 'tools/list': { result: { tools: [validTool] } } };
|
||||
const key = faker.string.alphanumeric(12);
|
||||
const url = await scriptMcpListServer(key, invalid);
|
||||
const project = await user.flowManager.workspaceFlow.createProject(
|
||||
new Project(faker.string.alphanumeric(10), ProjectType.Local),
|
||||
);
|
||||
const client = await mcpClientFlow.create(project, new McpClient(faker.string.alphanumeric(10), url));
|
||||
await mcpClientFlow.connect(client);
|
||||
await mcpClientFlow.getListError(client, McpPrimitiveType.Tools);
|
||||
|
||||
await scriptMcpListServer(key, valid);
|
||||
await mcpClientFlow.refresh(client, McpPrimitiveType.Tools);
|
||||
await expect.poll(() => mcpClientPage.getCapabilityErrorCount(McpPrimitiveType.Tools)).toBe(0);
|
||||
await expect.poll(() => mcpClientPage.getPrimitives(McpPrimitiveType.Tools), { timeout: DEFAULT_TIMEOUT }).toEqual(['valid-tool']);
|
||||
|
||||
await scriptMcpListServer(key, invalid);
|
||||
await mcpClientFlow.refresh(client, McpPrimitiveType.Tools);
|
||||
expect((await mcpClientFlow.getListError(client, McpPrimitiveType.Tools)).title).toBe(wholeFailureTitle('tools/list'));
|
||||
});
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
import { faker } from '@faker-js/faker';
|
||||
import { expect } from "@playwright/test";
|
||||
|
||||
import { McpPrimitiveType } from '../../../enums/mcp-capability';
|
||||
import { ProjectType } from '../../../enums/project-types';
|
||||
import { DEFAULT_TIMEOUT, test } from "../../../misc/fixtures";
|
||||
import { scriptMcpListServer } from '../../../misc/mcp-list-server';
|
||||
import { McpClient } from '../../../models/mcp-client';
|
||||
import { Project } from '../../../models/project';
|
||||
|
||||
test('Verify Refresh on Resources Clears a Resource Templates Error Once the Server Is Fixed', async ({ user }) => {
|
||||
test.fail(true, "INS-4065");
|
||||
const { mcpClientFlow, workspaceFlow } = user.flowManager;
|
||||
const { mcpClientPage } = user.pageManager;
|
||||
const resources = { result: { resources: [{ name: 'valid-resource', uri: 'file:///valid.txt' }] } };
|
||||
const key = faker.string.alphanumeric(12);
|
||||
const url = await scriptMcpListServer(key, {
|
||||
'resources/list': resources,
|
||||
'resources/templates/list': {
|
||||
result: {
|
||||
resourceTemplates: [{ uriTemplate: 'file:///{path}', name: 'valid-template' }, { name: 'no-template' }],
|
||||
},
|
||||
},
|
||||
});
|
||||
const project = await workspaceFlow.createProject(new Project(faker.string.alphanumeric(10), ProjectType.Local));
|
||||
const client = await mcpClientFlow.create(project, new McpClient(faker.string.alphanumeric(10), url));
|
||||
await mcpClientFlow.connect(client);
|
||||
await mcpClientFlow.getListError(client, McpPrimitiveType.Resources);
|
||||
|
||||
await scriptMcpListServer(key, {
|
||||
'resources/list': resources,
|
||||
'resources/templates/list': {
|
||||
result: { resourceTemplates: [{ uriTemplate: 'file:///{path}', name: 'valid-template' }] },
|
||||
},
|
||||
});
|
||||
await mcpClientFlow.refresh(client, McpPrimitiveType.Resources);
|
||||
|
||||
await expect.poll(() => mcpClientPage.getCapabilityErrorCount(McpPrimitiveType.Resources), { timeout: DEFAULT_TIMEOUT }).toBe(0);
|
||||
});
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
import { faker } from '@faker-js/faker';
|
||||
import { expect } from "@playwright/test";
|
||||
|
||||
import { McpPrimitiveType } from '../../../enums/mcp-capability';
|
||||
import { ProjectType } from '../../../enums/project-types';
|
||||
import { DEFAULT_TIMEOUT, test } from "../../../misc/fixtures";
|
||||
import { scriptMcpListServer } from '../../../misc/mcp-list-server';
|
||||
import { McpClient } from '../../../models/mcp-client';
|
||||
import { Project } from '../../../models/project';
|
||||
import { skippedTitle, wholeFailureTitle } from './helpers';
|
||||
|
||||
test('Verify Invalid resources/list and prompts/list Are Reported Like tools/list', async ({ user }) => {
|
||||
const { mcpClientFlow, workspaceFlow } = user.flowManager;
|
||||
const { mcpClientPage } = user.pageManager;
|
||||
const url = await scriptMcpListServer(faker.string.alphanumeric(12), {
|
||||
'resources/list': {
|
||||
result: {
|
||||
resources: [
|
||||
{ uri: 'file:///valid.txt', name: 'valid-resource' },
|
||||
{ name: 'no-uri-resource' },
|
||||
{ uri: 42, name: 'numeric-uri-resource' },
|
||||
],
|
||||
},
|
||||
},
|
||||
'prompts/list': { result: { prompts: 'none' } },
|
||||
});
|
||||
const project = await workspaceFlow.createProject(new Project(faker.string.alphanumeric(10), ProjectType.Local));
|
||||
const client = await mcpClientFlow.create(project, new McpClient(faker.string.alphanumeric(10), url));
|
||||
await mcpClientFlow.connect(client);
|
||||
|
||||
const resourcesError = await mcpClientFlow.getListError(client, McpPrimitiveType.Resources);
|
||||
expect(resourcesError.title).toMatch(skippedTitle(2));
|
||||
expect(resourcesError.entries.map(entry => entry.label)).toEqual(['no-uri-resource', 'numeric-uri-resource']);
|
||||
await expect
|
||||
.poll(() => mcpClientPage.getPrimitives(McpPrimitiveType.Resources), {
|
||||
timeout: DEFAULT_TIMEOUT,
|
||||
})
|
||||
.toEqual(['valid-resource']);
|
||||
|
||||
expect(await mcpClientFlow.getListError(client, McpPrimitiveType.Prompts)).toEqual({
|
||||
title: wholeFailureTitle('prompts/list'),
|
||||
entries: [],
|
||||
});
|
||||
expect(await mcpClientPage.getPrimitives(McpPrimitiveType.Prompts)).toEqual([]);
|
||||
});
|
||||
|
||||
test('Verify a prompts/list Whose prompts Is an Object Is a Whole-Response Error', async ({ user }) => {
|
||||
const { mcpClientFlow, workspaceFlow } = user.flowManager;
|
||||
const url = await scriptMcpListServer(faker.string.alphanumeric(12), {
|
||||
'prompts/list': { result: { prompts: {} } },
|
||||
});
|
||||
const project = await workspaceFlow.createProject(new Project(faker.string.alphanumeric(10), ProjectType.Local));
|
||||
const client = await mcpClientFlow.create(project, new McpClient(faker.string.alphanumeric(10), url));
|
||||
await mcpClientFlow.connect(client);
|
||||
|
||||
expect(await user.flowManager.mcpClientFlow.getListError(client, McpPrimitiveType.Prompts)).toEqual({
|
||||
title: wholeFailureTitle('prompts/list'),
|
||||
entries: [],
|
||||
});
|
||||
});
|
||||
|
||||
test('Verify a Prompt with Bad arguments Is Skipped While Valid Prompts Are Listed', async ({ user }) => {
|
||||
const { mcpClientFlow, workspaceFlow } = user.flowManager;
|
||||
const url = await scriptMcpListServer(faker.string.alphanumeric(12), {
|
||||
'prompts/list': {
|
||||
result: {
|
||||
prompts: [{ name: 'valid-prompt' }, { name: 'bad-args-prompt', arguments: 'x' }],
|
||||
},
|
||||
},
|
||||
});
|
||||
const project = await workspaceFlow.createProject(new Project(faker.string.alphanumeric(10), ProjectType.Local));
|
||||
const client = await mcpClientFlow.create(project, new McpClient(faker.string.alphanumeric(10), url));
|
||||
await mcpClientFlow.connect(client);
|
||||
|
||||
const error = await mcpClientFlow.getListError(client, McpPrimitiveType.Prompts);
|
||||
expect(error.title).toMatch(skippedTitle(1));
|
||||
expect(error.entries.map(entry => entry.label)).toEqual(['bad-args-prompt']);
|
||||
await expect
|
||||
.poll(() => user.pageManager.mcpClientPage.getPrimitives(McpPrimitiveType.Prompts), {
|
||||
timeout: DEFAULT_TIMEOUT,
|
||||
})
|
||||
.toEqual(['valid-prompt']);
|
||||
});
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
import { faker } from '@faker-js/faker';
|
||||
import { expect } from "@playwright/test";
|
||||
|
||||
import { McpPrimitiveType } from '../../../enums/mcp-capability';
|
||||
import { ProjectType } from '../../../enums/project-types';
|
||||
import { DEFAULT_TIMEOUT, test } from "../../../misc/fixtures";
|
||||
import { scriptMcpListServer } from '../../../misc/mcp-list-server';
|
||||
import { McpClient } from '../../../models/mcp-client';
|
||||
import { Project } from '../../../models/project';
|
||||
import { validTool } from './helpers';
|
||||
|
||||
test('Verify a Server-Initiated Request Reusing the tools/list Request Id Does Not Produce a Tools Error', async ({
|
||||
user,
|
||||
}) => {
|
||||
test.fail(true, 'INS-4066');
|
||||
const { mcpClientPage } = user.pageManager;
|
||||
const { mcpClientFlow, workspaceFlow } = user.flowManager;
|
||||
const url = await scriptMcpListServer(faker.string.alphanumeric(12), {
|
||||
'tools/list': { result: { tools: [validTool] } },
|
||||
'serverRequestReusingToolsListId': true,
|
||||
});
|
||||
const project = await workspaceFlow.createProject(new Project(faker.string.alphanumeric(10), ProjectType.Local));
|
||||
const client = await mcpClientFlow.create(project, new McpClient(faker.string.alphanumeric(10), url));
|
||||
await mcpClientFlow.connect(client);
|
||||
|
||||
await expect.poll(() => mcpClientPage.getPrimitives(McpPrimitiveType.Tools), { timeout: DEFAULT_TIMEOUT }).toEqual(['valid-tool']);
|
||||
expect(await mcpClientPage.getCapabilityErrorCount(McpPrimitiveType.Tools)).toBe(0);
|
||||
});
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
import { faker } from '@faker-js/faker';
|
||||
import { expect } from "@playwright/test";
|
||||
|
||||
import { McpPrimitiveType } from '../../../enums/mcp-capability';
|
||||
import { ProjectType } from '../../../enums/project-types';
|
||||
import { DEFAULT_TIMEOUT, test } from "../../../misc/fixtures";
|
||||
import { scriptMcpListServer } from '../../../misc/mcp-list-server';
|
||||
import { McpClient } from '../../../models/mcp-client';
|
||||
import { Project } from '../../../models/project';
|
||||
import { wholeFailureTitle } from './helpers';
|
||||
|
||||
test('Verify Resources and Resource Templates Errors Share One Capability Item Panel', async ({ user }) => {
|
||||
const { mcpClientFlow, workspaceFlow } = user.flowManager;
|
||||
const { mcpClientPage } = user.pageManager;
|
||||
const url = await scriptMcpListServer(faker.string.alphanumeric(12), {
|
||||
'resources/list': { result: { resources: null } },
|
||||
'resources/templates/list': {
|
||||
result: {
|
||||
resourceTemplates: [{ uriTemplate: 'file:///{path}', name: 'valid-template' }, { name: 'no-uri-template' }],
|
||||
},
|
||||
},
|
||||
});
|
||||
const project = await workspaceFlow.createProject(new Project(faker.string.alphanumeric(10), ProjectType.Local));
|
||||
const client = await mcpClientFlow.create(project, new McpClient(faker.string.alphanumeric(10), url));
|
||||
await mcpClientFlow.connect(client);
|
||||
|
||||
const error = await mcpClientFlow.getListError(client, McpPrimitiveType.Resources);
|
||||
await expect.poll(() => mcpClientPage.getCapabilityErrorCount(McpPrimitiveType.Resources)).toBe(1);
|
||||
expect(error.title).toContain(wholeFailureTitle('resources/list'));
|
||||
expect(error.title).toContain('skipped because they do not match the MCP schema');
|
||||
expect(error.entries.map(entry => entry.label)).toEqual(['no-uri-template']);
|
||||
await expect
|
||||
.poll(() => mcpClientPage.getPrimitives(McpPrimitiveType.ResourceTemplates), {
|
||||
timeout: DEFAULT_TIMEOUT,
|
||||
})
|
||||
.toEqual(['valid-template']);
|
||||
});
|
||||
@@ -0,0 +1,48 @@
|
||||
import { faker } from '@faker-js/faker';
|
||||
import { expect } from "@playwright/test";
|
||||
|
||||
import { McpPrimitiveType } from '../../../enums/mcp-capability';
|
||||
import { ProjectType } from '../../../enums/project-types';
|
||||
import { test } from "../../../misc/fixtures";
|
||||
import { scriptMcpListServer } from '../../../misc/mcp-list-server';
|
||||
import { McpClient } from '../../../models/mcp-client';
|
||||
import { Project } from '../../../models/project';
|
||||
import { wholeFailureTitle } from './helpers';
|
||||
|
||||
const wholeInvalidTools: Record<string, unknown> = {
|
||||
'tools is not an array': { tools: 'not-an-array' },
|
||||
'tools is null': { tools: null },
|
||||
'nextCursor has the wrong type': { tools: [], nextCursor: 123 },
|
||||
};
|
||||
|
||||
for (const [variant, result] of Object.entries(wholeInvalidTools)) {
|
||||
test(`Verify a Whole tools/list Response Invalid (${variant}) Shows a Title-Only Error`, async ({ user }) => {
|
||||
const { mcpClientFlow, workspaceFlow } = user.flowManager;
|
||||
const url = await scriptMcpListServer(faker.string.alphanumeric(12), { 'tools/list': { result } });
|
||||
const project = await workspaceFlow.createProject(new Project(faker.string.alphanumeric(10), ProjectType.Local));
|
||||
const client = await mcpClientFlow.create(project, new McpClient(faker.string.alphanumeric(10), url));
|
||||
await mcpClientFlow.connect(client);
|
||||
|
||||
expect(await mcpClientFlow.getListError(client, McpPrimitiveType.Tools)).toEqual({
|
||||
title: wholeFailureTitle('tools/list'),
|
||||
entries: [],
|
||||
});
|
||||
expect(await user.pageManager.mcpClientPage.getPrimitives(McpPrimitiveType.Tools)).toEqual([]);
|
||||
});
|
||||
}
|
||||
|
||||
test('Verify a tools/list Result That Is Not an Object Shows an Error and Lists No Tools', async ({ user }) => {
|
||||
const { mcpClientFlow, workspaceFlow } = user.flowManager;
|
||||
const url = await scriptMcpListServer(faker.string.alphanumeric(12), { 'tools/list': { result: 'oops' } });
|
||||
const project = await workspaceFlow.createProject(new Project(faker.string.alphanumeric(10), ProjectType.Local));
|
||||
const client = await mcpClientFlow.create(project, new McpClient(faker.string.alphanumeric(10), url));
|
||||
await mcpClientFlow.connect(client);
|
||||
|
||||
// The MCP SDK rejects a non-object `result` before the pane's own
|
||||
// validation runs, so the title is the SDK's ZodError, not the agreed
|
||||
// "does not match the MCP schema" wording.
|
||||
const error = await user.flowManager.mcpClientFlow.getListError(client, McpPrimitiveType.Tools);
|
||||
expect(error.title).not.toBe('');
|
||||
expect(error.entries).toEqual([]);
|
||||
expect(await user.pageManager.mcpClientPage.getPrimitives(McpPrimitiveType.Tools)).toEqual([]);
|
||||
});
|
||||
@@ -0,0 +1,61 @@
|
||||
import { faker } from "@faker-js/faker";
|
||||
import { expect } from "@playwright/test";
|
||||
|
||||
import { McpPrimitiveType } from "../../enums/mcp-capability";
|
||||
import { ProjectType } from "../../enums/project-types";
|
||||
import { DEFAULT_TIMEOUT, MCP_SERVER, test } from "../../misc/fixtures";
|
||||
import { scriptMcpListServer } from "../../misc/mcp-list-server";
|
||||
import { McpClient } from "../../models/mcp-client";
|
||||
import { Project } from "../../models/project";
|
||||
|
||||
test("Verify Loaded Pages Are Kept After Switching to Another MCP Client and Back", async ({
|
||||
user,
|
||||
}) => {
|
||||
const { mcpClientFlow, workspaceFlow } = user.flowManager;
|
||||
const { mcpClientPage } = user.pageManager;
|
||||
|
||||
const url = await scriptMcpListServer(faker.string.alphanumeric(12), {
|
||||
"resources/list": {
|
||||
pages: {
|
||||
"": {
|
||||
result: {
|
||||
resources: [{ name: "item-1", uri: "file:///item-1.txt" }],
|
||||
nextCursor: "page-2",
|
||||
},
|
||||
},
|
||||
"page-2": {
|
||||
result: {
|
||||
resources: [{ name: "item-2", uri: "file:///item-2.txt" }],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const project = await workspaceFlow.createProject(
|
||||
new Project(faker.string.alphanumeric(10), ProjectType.Local),
|
||||
);
|
||||
|
||||
const client = await mcpClientFlow.create(
|
||||
project,
|
||||
new McpClient(faker.string.alphanumeric(10), url),
|
||||
);
|
||||
await mcpClientFlow.connect(client);
|
||||
await mcpClientFlow.loadMore(client, McpPrimitiveType.Resources);
|
||||
await expect
|
||||
.poll(() => mcpClientPage.getPrimitives(McpPrimitiveType.Resources), {
|
||||
timeout: DEFAULT_TIMEOUT,
|
||||
})
|
||||
.toEqual(["item-1", "item-2"]);
|
||||
|
||||
await mcpClientFlow.create(
|
||||
project,
|
||||
new McpClient(faker.string.alphanumeric(10), MCP_SERVER),
|
||||
);
|
||||
await mcpClientFlow.getListAction(client, McpPrimitiveType.Resources);
|
||||
|
||||
await expect
|
||||
.poll(() => mcpClientPage.getPrimitives(McpPrimitiveType.Resources), {
|
||||
timeout: DEFAULT_TIMEOUT,
|
||||
})
|
||||
.toEqual(["item-1", "item-2"]);
|
||||
});
|
||||
@@ -0,0 +1,91 @@
|
||||
import { faker } from "@faker-js/faker";
|
||||
import { expect } from "@playwright/test";
|
||||
|
||||
import { McpPrimitiveType } from "../../enums/mcp-capability";
|
||||
import { ProjectType } from "../../enums/project-types";
|
||||
import { DEFAULT_TIMEOUT, test } from "../../misc/fixtures";
|
||||
import { scriptMcpListServer } from "../../misc/mcp-list-server";
|
||||
import { McpClient } from "../../models/mcp-client";
|
||||
import { Project } from "../../models/project";
|
||||
|
||||
test("Verify Same-Name Tools, Resources and Prompts Are Each Listed Once and Selecting One Selects Only That Row Through Filter, Collapse and Reload", async ({
|
||||
user,
|
||||
}) => {
|
||||
const { mcpClientFlow, workspaceFlow } = user.flowManager;
|
||||
const { mcpClientPage } = user.pageManager;
|
||||
|
||||
const sharedName = faker.string.alphanumeric(8);
|
||||
const url = await scriptMcpListServer(faker.string.alphanumeric(12), {
|
||||
"tools/list": {
|
||||
result: {
|
||||
tools: [
|
||||
{ name: sharedName, inputSchema: { type: "object", properties: { alpha: { type: "string" } } } },
|
||||
{ name: sharedName, inputSchema: { type: "object", properties: { beta: { type: "string" } } } },
|
||||
],
|
||||
},
|
||||
},
|
||||
"resources/list": {
|
||||
result: {
|
||||
resources: [
|
||||
{ uri: "file:///first.txt", name: sharedName },
|
||||
{ uri: "file:///second.txt", name: sharedName },
|
||||
],
|
||||
},
|
||||
},
|
||||
"prompts/list": {
|
||||
result: { prompts: [{ name: sharedName }, { name: sharedName }] },
|
||||
},
|
||||
});
|
||||
const project = await workspaceFlow.createProject(
|
||||
new Project(faker.string.alphanumeric(10), ProjectType.Local),
|
||||
);
|
||||
const client = await mcpClientFlow.create(
|
||||
project,
|
||||
new McpClient(faker.string.alphanumeric(10), url),
|
||||
);
|
||||
await mcpClientFlow.connect(client);
|
||||
|
||||
await mcpClientPage.clickPrimitiveAt(McpPrimitiveType.Tools, 1);
|
||||
await expect
|
||||
.poll(() => mcpClientPage.getPrimitiveSelection(McpPrimitiveType.Tools), { timeout: DEFAULT_TIMEOUT })
|
||||
.toEqual([false, true]);
|
||||
await expect
|
||||
.poll(() => mcpClientPage.getToolArgumentNames(), { timeout: DEFAULT_TIMEOUT })
|
||||
.toEqual(["beta"]);
|
||||
await mcpClientPage.clickPrimitiveAt(McpPrimitiveType.Tools, 0);
|
||||
await expect
|
||||
.poll(() => mcpClientPage.getPrimitiveSelection(McpPrimitiveType.Tools), { timeout: DEFAULT_TIMEOUT })
|
||||
.toEqual([true, false]);
|
||||
await expect
|
||||
.poll(() => mcpClientPage.getToolArgumentNames(), { timeout: DEFAULT_TIMEOUT })
|
||||
.toEqual(["alpha"]);
|
||||
|
||||
await mcpClientFlow.filterCapabilities(client, sharedName);
|
||||
await mcpClientPage.clickPrimitiveAt(McpPrimitiveType.Tools, 1);
|
||||
await expect
|
||||
.poll(() => mcpClientPage.getPrimitiveSelection(McpPrimitiveType.Tools), { timeout: DEFAULT_TIMEOUT })
|
||||
.toEqual([false, true]);
|
||||
|
||||
await mcpClientFlow.toggleCapability(client, McpPrimitiveType.Tools);
|
||||
await mcpClientFlow.toggleCapability(client, McpPrimitiveType.Tools);
|
||||
await mcpClientFlow.refresh(client, McpPrimitiveType.Tools);
|
||||
await expect
|
||||
.poll(() => mcpClientPage.getPrimitiveSelection(McpPrimitiveType.Tools), { timeout: DEFAULT_TIMEOUT })
|
||||
.toEqual([false, true]);
|
||||
await expect
|
||||
.poll(() => mcpClientPage.getToolArgumentNames(), { timeout: DEFAULT_TIMEOUT })
|
||||
.toEqual(["beta"]);
|
||||
|
||||
await mcpClientPage.clickPrimitiveAt(McpPrimitiveType.Resources, 1);
|
||||
await expect
|
||||
.poll(() => mcpClientPage.getPrimitiveSelection(McpPrimitiveType.Resources), { timeout: DEFAULT_TIMEOUT })
|
||||
.toEqual([false, true]);
|
||||
await mcpClientPage.clickPrimitiveAt(McpPrimitiveType.Prompts, 1);
|
||||
|
||||
expect(await mcpClientPage.getPrimitives(McpPrimitiveType.Tools)).toEqual([sharedName, sharedName]);
|
||||
expect(await mcpClientPage.getPrimitives(McpPrimitiveType.Resources)).toEqual([sharedName, sharedName]);
|
||||
expect(await mcpClientPage.getPrimitives(McpPrimitiveType.Prompts)).toEqual([sharedName, sharedName]);
|
||||
await expect
|
||||
.poll(() => mcpClientPage.getPrimitiveSelection(McpPrimitiveType.Prompts), { timeout: DEFAULT_TIMEOUT })
|
||||
.toEqual([false, true]);
|
||||
});
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
import { faker } from "@faker-js/faker";
|
||||
import { expect } from "@playwright/test";
|
||||
|
||||
import { McpPrimitiveType } from "../../enums/mcp-capability";
|
||||
import { ProjectType } from "../../enums/project-types";
|
||||
import { DEFAULT_TIMEOUT, test } from "../../misc/fixtures";
|
||||
import { scriptMcpListServer } from "../../misc/mcp-list-server";
|
||||
import { McpClient } from "../../models/mcp-client";
|
||||
import { Project } from "../../models/project";
|
||||
|
||||
test("Verify the Selected Same-Name Tool Stays Selected When a Reload Prepends Another Tool to the List", async ({
|
||||
user,
|
||||
}) => {
|
||||
test.fail(true, "INS-4147");
|
||||
const { mcpClientFlow, workspaceFlow } = user.flowManager;
|
||||
const { mcpClientPage } = user.pageManager;
|
||||
|
||||
const sharedName = faker.string.alphanumeric(8);
|
||||
const alphaTool = {
|
||||
name: sharedName,
|
||||
inputSchema: { type: "object", properties: { alpha: { type: "string" } } },
|
||||
};
|
||||
const betaTool = {
|
||||
name: sharedName,
|
||||
inputSchema: { type: "object", properties: { beta: { type: "string" } } },
|
||||
};
|
||||
const key = faker.string.alphanumeric(12);
|
||||
const url = await scriptMcpListServer(key, {
|
||||
"tools/list": { result: { tools: [alphaTool, betaTool] } },
|
||||
});
|
||||
const project = await workspaceFlow.createProject(
|
||||
new Project(faker.string.alphanumeric(10), ProjectType.Local),
|
||||
);
|
||||
const client = await mcpClientFlow.create(
|
||||
project,
|
||||
new McpClient(faker.string.alphanumeric(10), url),
|
||||
);
|
||||
await mcpClientFlow.connect(client);
|
||||
await mcpClientPage.clickPrimitiveAt(McpPrimitiveType.Tools, 1);
|
||||
await expect
|
||||
.poll(() => mcpClientPage.getToolArgumentNames(), { timeout: DEFAULT_TIMEOUT })
|
||||
.toEqual(["beta"]);
|
||||
|
||||
await scriptMcpListServer(key, {
|
||||
"tools/list": {
|
||||
result: {
|
||||
tools: [
|
||||
{ name: faker.string.alphanumeric(8), inputSchema: { type: "object" } },
|
||||
alphaTool,
|
||||
betaTool,
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
await mcpClientFlow.refresh(client, McpPrimitiveType.Tools);
|
||||
|
||||
await expect
|
||||
.poll(() => mcpClientPage.getPrimitiveSelection(McpPrimitiveType.Tools), {
|
||||
timeout: DEFAULT_TIMEOUT,
|
||||
})
|
||||
.toEqual([false, false, true]);
|
||||
await expect
|
||||
.poll(() => mcpClientPage.getToolArgumentNames(), { timeout: DEFAULT_TIMEOUT })
|
||||
.toEqual(["beta"]);
|
||||
});
|
||||
@@ -0,0 +1,38 @@
|
||||
import { faker } from "@faker-js/faker";
|
||||
import { expect } from "@playwright/test";
|
||||
|
||||
import { ProjectType } from "../../enums/project-types";
|
||||
import { MCP_SERVER_HTTPS, test } from "../../misc/fixtures";
|
||||
import { McpClient } from "../../models/mcp-client";
|
||||
import { Project } from "../../models/project";
|
||||
|
||||
test("Verify Disabling SSL Certificate Validation Lets MCP Client Connect To A Self-Signed Server", async ({
|
||||
user,
|
||||
}) => {
|
||||
const { certificatesFlow, mcpClientFlow, workspaceFlow } = user.flowManager;
|
||||
|
||||
const project = await workspaceFlow.createProject(
|
||||
new Project(faker.string.alphanumeric(10), ProjectType.Local),
|
||||
);
|
||||
|
||||
const mcpClient = await mcpClientFlow.create(
|
||||
project,
|
||||
new McpClient(faker.string.alphanumeric(10), MCP_SERVER_HTTPS),
|
||||
);
|
||||
await mcpClientFlow.connect(mcpClient, { expectConnected: false });
|
||||
const toolsWithValidation = mcpClientFlow.getTools(mcpClient);
|
||||
await expect(toolsWithValidation).rejects.toThrow();
|
||||
|
||||
await certificatesFlow.setSslValidationEnabled(mcpClient, false);
|
||||
|
||||
await mcpClientFlow.connect(mcpClient);
|
||||
const toolsWithoutValidation = await mcpClientFlow.getTools(mcpClient);
|
||||
|
||||
expect(toolsWithoutValidation).toEqual(
|
||||
expect.arrayContaining([
|
||||
"read_wiki_structure",
|
||||
"read_wiki_contents",
|
||||
"ask_question",
|
||||
]),
|
||||
);
|
||||
});
|
||||
Reference in new issue
Block a user