Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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

This file was deleted.

1,014 changes: 1,014 additions & 0 deletions backend/__tests__/fixtures/hostedMcp/atlassian-tools-list-2026-10-19.json

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ const dcr = require('../../../services/hostedMcpDcrRegistry');

const { HOSTED_MCP_ENTRIES: SHIPPED_ENTRIES } = jest.requireActual('../../../integrations/hostedMcp/entries');
const LINEAR_SCOPE_CAPTURE = require('../../fixtures/hostedMcp/linear-tools-list-2026-10-01.json');
const ATLASSIAN_SCOPE_CAPTURE = require('../../fixtures/hostedMcp/atlassian-tools-list-2026-10-09.json');
const ATLASSIAN_SCOPE_CAPTURE = require('../../fixtures/hostedMcp/atlassian-tools-list-2026-10-19.json');
const AIRTABLE_SCOPE_CAPTURE = require('../../fixtures/hostedMcp/airtable-tools-list-2026-10-09.json');
const GOOGLE_CALENDAR_CAPTURE = require('../../fixtures/hostedMcp/google-calendar-tools-list-2026-10-01.json');

Expand Down Expand Up @@ -371,8 +371,10 @@ describe('hosted-mcp connect: callback', () => {
'read:account',
'offline_access',
'read:jira:agent-interface',
'write:jira:agent-interface',
'search:jira:agent-interface',
'read:confluence:agent-interface',
'write:confluence:agent-interface',
'search:confluence:agent-interface',
],
});
Expand Down
48 changes: 34 additions & 14 deletions backend/__tests__/unit/services/hostedMcpEntry.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const GOOGLE_CALENDAR_CAPTURE = require('../../fixtures/hostedMcp/google-calenda
// second (2026-10-14) is the one it is pinned to now, after five schemas drifted.
const LINEAR_FIRST_CAPTURE = require('../../fixtures/hostedMcp/linear-tools-list-2026-10-01.json');
const LINEAR_CAPTURE = require('../../fixtures/hostedMcp/linear-tools-list-2026-10-14.json');
const ATLASSIAN_CAPTURE = require('../../fixtures/hostedMcp/atlassian-tools-list-2026-10-09.json');
const ATLASSIAN_CAPTURE = require('../../fixtures/hostedMcp/atlassian-tools-list-2026-10-19.json');
const AIRTABLE_CAPTURE = require('../../fixtures/hostedMcp/airtable-tools-list-2026-10-09.json');

const ENTRY_TEXT = 'ENTRY TEXT the pin agreed to.';
Expand Down Expand Up @@ -658,27 +658,28 @@ describe('the shipped catalogue cannot land half-wired', () => {
});
});

test('the Atlassian pin is the six named reads of its measured list, under snake_case names', () => {
// Capture taken by connector-ops on 2026-10-09 with read and search scopes
// only. Wren 76760: pin a read only if it is named, product-scoped and
// carries no credential or foreign payload. So the by-name dispatcher
// (executeRead) and its catalogue (discover) stay out even though both are
// annotated read-only, and the two writers stay out by class.
test('the Atlassian pin is six named reads and five named writes under snake_case names', () => {
// The 2026-10-19 capture includes Jira and Confluence write scopes.
// Broad discover/execute dispatchers stay out even under those scopes.
const capture = ATLASSIAN_CAPTURE;
const entry = findHostedMcpEntry(HOSTED_MCP_ENTRIES, 'atlassian');
expect(entry.client).toBe('cimd');
expect(capture.server).toBe(entry.resource);
expect(capture.requestedScope.split(' ').sort()).toEqual([...entry.scopes].sort());
expect(entry.scopes.some((scope) => scope.startsWith('write:'))).toBe(false);
expect(capture.scopeRequested.split(' ').sort()).toEqual([...entry.scopes].sort());
expect(entry.scopes.filter((scope) => scope.startsWith('write:'))).toEqual([
'write:jira:agent-interface', 'write:confluence:agent-interface',
]);
expect(entry.revoke.endpoint).toBe('https://auth.atlassian.com/oauth/revoke');

const pinnedUpstream = [
'getAccessibleAtlassianResources', 'atlassianUserInfo', 'getConfluenceContent',
'searchConfluence', 'getJiraIssue', 'searchJiraIssuesUsingJql',
'updateConfluenceContent', 'createJiraIssue',
'editJiraIssue', 'transitionJiraIssue', 'addOrEditJiraIssueComment',
];
expect(entry.tools.map((tool) => tool.upstreamName)).toEqual(pinnedUpstream);
expect(capture.tools.map((tool) => tool.name).filter((name) => !pinnedUpstream.includes(name)).sort())
.toEqual(['discover', 'executeDestructive', 'executeRead', 'executeWrite']);
.toEqual(['createConfluenceContent', 'discover', 'executeDestructive', 'executeRead', 'executeWrite']);

const measured = new Map(capture.tools.map((tool) => [tool.name, tool]));
for (const tool of entry.tools) {
Expand All @@ -688,13 +689,32 @@ describe('the shipped catalogue cannot land half-wired', () => {
// from the vendor's; calls and drift use upstreamName.
expect(tool.name).toMatch(/^[a-z0-9_]+$/);
expect(tool.name).not.toBe(tool.upstreamName);
expect(tool.class).toBe('read');
expect(tool.class).toBe(found.annotations.readOnlyHint ? 'read' : 'write');
expect(canonicalJson(tool.inputSchema)).toBe(canonicalJson(found.inputSchema));
expect(tool.annotations?.readOnlyHint).toBe(true);
expect(found.annotations.readOnlyHint).toBe(true);
expect(tool.annotations?.readOnlyHint).toBe(found.annotations.readOnlyHint);
expect(tool.annotations?.destructiveHint).toBe(found.annotations.destructiveHint);
}
expect(capture.tools).toHaveLength(10);
expect(entry.tools.filter((tool) => tool.class === 'write')).toHaveLength(5);
expect(entryToolProjections(entry).filter((tool) => tool.requiredWriteMode === 'write-with-confirm'))
.toHaveLength(5);
expect(entry.tools.find((tool) => tool.upstreamName === 'addOrEditJiraIssueComment').irreversible).toBe(true);
expect(entryToolProjections(entry).find((tool) => tool.name === 'atlassian.add_or_edit_jira_issue_comment'))
.toMatchObject({ irreversible: true, requiredWriteMode: 'write-with-confirm' });
expect(capture.tools).toHaveLength(16);
});

test('the Atlassian domain notice uses this instance\'s public API origin', () => {
const before = process.env.PUBLIC_API_URL;
try {
process.env.PUBLIC_API_URL = 'https://api.self-host.example/';
jest.isolateModules(() => {
const { ATLASSIAN_ENTRY } = require('../../../integrations/hostedMcp/atlassian');
expect(ATLASSIAN_ENTRY.connectNotice).toContain('https://api.self-host.example/**');
});
} finally {
if (before === undefined) delete process.env.PUBLIC_API_URL;
else process.env.PUBLIC_API_URL = before;
}
});

test('the Airtable pin is every measured read minus three credential- or payload-bearing ones', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@ it("a hosted entry's tool row carries its connect descriptor and only its own co
id: 'acme',
title: 'Acme',
description: 'Tickets in Acme.',
connectNotice: 'Ask your admin to allow this connection first.',
resource: 'https://mcp.acme.example/mcp',
issuer: 'https://mcp.acme.example',
client: 'cimd',
Expand Down Expand Up @@ -281,6 +282,8 @@ it("a hosted entry's tool row carries its connect descriptor and only its own co
// derivable from anything else the row carries.
expect(linear).toMatchObject({ connectionType: 'hosted-mcp', entryId: 'linear', available: true, label: 'Linear' });
expect(acmeRow).toMatchObject({ connectionType: 'hosted-mcp', entryId: 'acme' });
expect(acmeRow.connectNotice).toBe(acme.connectNotice);
expect(linear.connectNotice).toBeUndefined();
// Each row sees its own vendor's connection and not the other's.
expect(linear.connections).toEqual([
{ connectionId: 'conn-linear', owner: '', repo: '' },
Expand Down
Loading
Loading