chore(mcp): regenerate openapi artifacts and sync payload expectations

Co-Authored-By: bot_apk <apk@cognition.ai>
This commit is contained in:
Devin AI 2026-09-25 03:27:51 +00:00
parent e7456e8a0f
commit 4dd482e923
11 changed files with 104 additions and 28 deletions

View file

@ -2856,6 +2856,20 @@
],
"title": "Mcp Servers"
},
"mcp_tool_overrides": {
"anyOf": [
{
"additionalProperties": {
"$ref": "#/components/schemas/MCPToolOverrideEntry"
},
"type": "object"
},
{
"type": "null"
}
],
"title": "Mcp Tool Overrides"
},
"mcp_tool_permissions": {
"anyOf": [
{
@ -3574,6 +3588,27 @@
"title": "KeyMetricWithMetadata",
"type": "object"
},
"MCPToolOverrideEntry": {
"description": "Per-server tool overrides stored on an object permission row's\n``mcp_tool_overrides``: ``allow`` re-arms names the convention denies,\n``deny`` disables names the convention or an allowlist would permit.",
"properties": {
"allow": {
"items": {
"type": "string"
},
"title": "Allow",
"type": "array"
},
"deny": {
"items": {
"type": "string"
},
"title": "Deny",
"type": "array"
}
},
"title": "MCPToolOverrideEntry",
"type": "object"
},
"MakeAgentsPublicRequest": {
"properties": {
"agent_ids": {

View file

@ -817,6 +817,9 @@ _EXPECTED_CUSTOMER = {
"mcp_servers": ["s1"],
"mcp_access_groups": [],
"mcp_tool_permissions": None,
"mcp_tool_overrides": None,
"mcp_tool_permissions_archive": None,
"mcp_permission_version": None,
"vector_stores": [],
"agents": [],
"agent_access_groups": [],

View file

@ -179,7 +179,7 @@ describe("AgentInfoView update payload", () => {
rpm_limit: 222,
session_tpm_limit: 333,
session_rpm_limit: 444,
object_permission: { mcp_servers: [], mcp_access_groups: [], mcp_toolsets: [], mcp_tool_permissions: {} },
object_permission: { mcp_servers: [], mcp_access_groups: [], mcp_toolsets: [], mcp_tool_permissions: {}, mcp_tool_overrides: {} },
access_group_ids: [],
});
});
@ -221,7 +221,7 @@ describe("AgentInfoView update payload", () => {
rpm_limit: 222,
session_tpm_limit: 333,
session_rpm_limit: 444,
object_permission: { mcp_servers: [], mcp_access_groups: [], mcp_toolsets: [], mcp_tool_permissions: {} },
object_permission: { mcp_servers: [], mcp_access_groups: [], mcp_toolsets: [], mcp_tool_permissions: {}, mcp_tool_overrides: {} },
access_group_ids: [],
});
});
@ -300,7 +300,7 @@ describe("AgentInfoView update payload", () => {
api_base: "https://other.example.com",
model: "langgraph/asst_1",
},
object_permission: { mcp_servers: [], mcp_access_groups: [], mcp_toolsets: [], mcp_tool_permissions: {} },
object_permission: { mcp_servers: [], mcp_access_groups: [], mcp_toolsets: [], mcp_tool_permissions: {}, mcp_tool_overrides: {} },
access_group_ids: [],
});
});
@ -311,6 +311,7 @@ describe("AgentInfoView update payload", () => {
mcp_access_groups: ["grp-a"],
mcp_toolsets: ["toolset-1"],
mcp_tool_permissions: { "srv-1": ["tool_x"] },
mcp_tool_overrides: {},
};
vi.mocked(networking.getAgentInfo).mockResolvedValue({
...A2A_AGENT,

View file

@ -103,7 +103,9 @@ const teamCreateFieldsSchema = z.object({
})
.optional(),
mcp_tool_permissions: z.record(z.string(), z.array(z.string())).optional(),
mcp_tool_overrides: z.record(z.string(), z.object({ allow: z.array(z.string()), deny: z.array(z.string()) })).optional(),
mcp_tool_overrides: z
.record(z.string(), z.object({ allow: z.array(z.string()), deny: z.array(z.string()) }))
.optional(),
allowed_agents_and_groups: z.object({ agents: z.array(z.string()), accessGroups: z.array(z.string()) }).optional(),
object_permission_search_tools: z.array(z.string()).optional(),
object_permission_skills: z.array(z.string()).optional(),

View file

@ -350,7 +350,8 @@ const MCPToolPermissions: React.FC<MCPToolPermissionsProps> = ({
permissionKey: entry.permissionKey,
edits: tools
.filter(
(tool) => allowed.includes(tool.name) !== stateFor(tool).checked && !stateFor(tool).locked,
(tool) =>
allowed.includes(tool.name) !== stateFor(tool).checked && !stateFor(tool).locked,
)
.map((tool) => ({
toolName: tool.name,

View file

@ -612,7 +612,11 @@ describe("convention servers and tool overrides", () => {
});
it("locks toolset tools as granted", () => {
const toolset = { toolset_id: "ts-1", toolset_name: "TS", tools: [{ server_id: "srv-1", tool_name: "list_pages" }] };
const toolset = {
toolset_id: "ts-1",
toolset_name: "TS",
tools: [{ server_id: "srv-1", tool_name: "list_pages" }],
};
const input: Parameters<typeof resolveEffectiveMcpServers>[0] = {
...emptyInput,
allServers: [srv],
@ -640,7 +644,13 @@ describe("convention servers and tool overrides", () => {
it("moves a delete tool in and out of allow on toggle", () => {
const overrides = { "srv-1": { allow: [], deny: ["list_pages"] } };
const grant = { toolOverrides: overrides, permissionKey: "srv-1", toolName: "delete_page", isDeleteTool: true, checked: true };
const grant = {
toolOverrides: overrides,
permissionKey: "srv-1",
toolName: "delete_page",
isDeleteTool: true,
checked: true,
};
expect(applyToolOverrideWrite(grant)).toEqual({ "srv-1": { allow: ["delete_page"], deny: ["list_pages"] } });
@ -656,7 +666,13 @@ describe("convention servers and tool overrides", () => {
});
it("moves a non-delete tool in and out of deny on toggle without writing allows", () => {
const deny = { toolOverrides: {}, permissionKey: "srv-1", toolName: "list_pages", isDeleteTool: false, checked: false };
const deny = {
toolOverrides: {},
permissionKey: "srv-1",
toolName: "list_pages",
isDeleteTool: false,
checked: false,
};
expect(applyToolOverrideWrite(deny)).toEqual({ "srv-1": { allow: [], deny: ["list_pages"] } });
@ -731,14 +747,14 @@ describe("retainedMcpToolOverrides", () => {
});
it("keeps an override while any server the key names stays granted", () => {
expect(
retainedMcpToolOverrides({ shared: { allow: [], deny: ["t"] } }, new Set(["srv-2"]), catalog),
).toEqual({ shared: { allow: [], deny: ["t"] } });
expect(retainedMcpToolOverrides({ shared: { allow: [], deny: ["t"] } }, new Set(["srv-2"]), catalog)).toEqual({
shared: { allow: [], deny: ["t"] },
});
});
it("keeps an override whose key resolves to nothing, so an unloaded catalog prunes nothing", () => {
expect(retainedMcpToolOverrides({ "not-yet-loaded": { allow: [], deny: ["t"] } }, new Set(), catalog)).toEqual(
{ "not-yet-loaded": { allow: [], deny: ["t"] } },
);
expect(retainedMcpToolOverrides({ "not-yet-loaded": { allow: [], deny: ["t"] } }, new Set(), catalog)).toEqual({
"not-yet-loaded": { allow: [], deny: ["t"] },
});
});
});

View file

@ -265,11 +265,7 @@ export const resolveEffectiveMcpServers = ({
);
};
export const mcpToolState = (
entry: EffectiveMcpServer,
toolName: string,
isDeleteTool: boolean,
): McpToolState => {
export const mcpToolState = (entry: EffectiveMcpServer, toolName: string, isDeleteTool: boolean): McpToolState => {
const denied = (entry.overrides?.deny ?? []).includes(toolName);
if (denied) {
if (entry.keyedTools !== undefined) {
@ -335,7 +331,16 @@ export const applyToolOverrideWrites = ({
readonly permissionKey: string;
readonly edits: readonly { toolName: string; isDeleteTool: boolean; checked: boolean }[];
}): Record<string, McpToolOverrideEntry> =>
edits.reduce((overrides, edit) => {
const write = { toolOverrides: overrides, permissionKey, toolName: edit.toolName, isDeleteTool: edit.isDeleteTool, checked: edit.checked };
return applyToolOverrideWrite(write);
}, { ...toolOverrides });
edits.reduce(
(overrides, edit) => {
const write = {
toolOverrides: overrides,
permissionKey,
toolName: edit.toolName,
isDeleteTool: edit.isDeleteTool,
checked: edit.checked,
};
return applyToolOverrideWrite(write);
},
{ ...toolOverrides },
);

View file

@ -404,7 +404,9 @@ const teamUpdateFieldsSchema = z.object({
})
.optional(),
mcp_tool_permissions: z.record(z.string(), z.array(z.string())).optional(),
mcp_tool_overrides: z.record(z.string(), z.object({ allow: z.array(z.string()), deny: z.array(z.string()) })).optional(),
mcp_tool_overrides: z
.record(z.string(), z.object({ allow: z.array(z.string()), deny: z.array(z.string()) }))
.optional(),
agents_and_groups: z.object({ agents: z.array(z.string()), accessGroups: z.array(z.string()) }).optional(),
object_permission_search_tools: z.array(z.string()).optional(),
object_permission_skills: z.array(z.string()).optional(),

View file

@ -244,6 +244,7 @@ describe("KeyEditView", () => {
mcp_servers: [],
mcp_access_groups: [],
mcp_tool_permissions: {},
mcp_tool_overrides: {},
vector_stores: [],
},
auto_rotate: false,
@ -2112,6 +2113,7 @@ describe("KeyEditView", () => {
vector_stores: [],
mcp_servers_and_groups: { servers: [], accessGroups: [], toolsets: [] },
mcp_tool_permissions: {},
mcp_tool_overrides: {},
agents_and_groups: { agents: [], accessGroups: [] },
skills: [],
organization_id: null,

View file

@ -24417,6 +24417,10 @@ export interface components {
mcp_access_groups?: string[] | null;
/** Mcp Servers */
mcp_servers?: string[] | null;
/** Mcp Tool Overrides */
mcp_tool_overrides?: {
[key: string]: components["schemas"]["MCPToolOverrideEntry"];
} | null;
/** Mcp Tool Permissions */
mcp_tool_permissions?: {
[key: string]: string[];

View file

@ -91,12 +91,17 @@ const descriptionTokens = (description: string): string[] =>
.filter((token) => token.length > 0)
.map((token) => token.toLowerCase());
const stripSuffix = (token: string, suffix: string): string => (token.endsWith(suffix) ? token.slice(0, -suffix.length) : token);
const stripSuffix = (token: string, suffix: string): string =>
token.endsWith(suffix) ? token.slice(0, -suffix.length) : token;
const tokenVariants = (token: string): string[] =>
[token, stripSuffix(token, "es"), stripSuffix(token, "s"), stripSuffix(token, "ed"), stripSuffix(token, "ing")].filter(
(variant) => variant.length > 0,
);
[
token,
stripSuffix(token, "es"),
stripSuffix(token, "s"),
stripSuffix(token, "ed"),
stripSuffix(token, "ing"),
].filter((variant) => variant.length > 0);
const CONJUNCTION_TOKENS = new Set(["and", "then", "or", "n"]);