Skip to content
Draft
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
17 changes: 17 additions & 0 deletions packages/cli/src/config/settingsSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2883,6 +2883,23 @@ export const SETTINGS_SCHEMA_DEFINITIONS: Record<
'Tools that should be disabled for this server even if exposed.',
items: { type: 'string' },
},
skillsEnabled: {
type: 'boolean',
description:
'Whether to discover Agent Skills published by this server per the skills-over-MCP SEP (io.modelcontextprotocol/skills). Defaults to true.',
},
includeSkills: {
type: 'array',
description:
'Allowlist of skill names to surface from this server. When omitted or empty all skills are surfaced. Matching is case-insensitive.',
items: { type: 'string' },
},
excludeSkills: {
type: 'array',
description:
'Blocklist of skill names to hide from this server even when published. When omitted or empty no skills are blocked. Matching is case-insensitive.',
items: { type: 'string' },
},
extension: {
type: 'object',
description:
Expand Down
4 changes: 4 additions & 0 deletions packages/cli/src/ui/commands/mcpCommand.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ describe('mcpCommand', () => {
getGeminiClient: ReturnType<typeof vi.fn>;
getMcpClientManager: ReturnType<typeof vi.fn>;
getResourceRegistry: ReturnType<typeof vi.fn>;
getSkillManager: ReturnType<typeof vi.fn>;
setUserInteractedWithMcp: ReturnType<typeof vi.fn>;
getLastMcpError: ReturnType<typeof vi.fn>;
};
Expand Down Expand Up @@ -113,6 +114,9 @@ describe('mcpCommand', () => {
getResourceRegistry: vi.fn().mockReturnValue({
getAllResources: vi.fn().mockReturnValue([]),
}),
getSkillManager: vi.fn().mockReturnValue({
getAllSkills: vi.fn().mockReturnValue([]),
}),
setUserInteractedWithMcp: vi.fn(),
getLastMcpError: vi.fn().mockReturnValue(undefined),
};
Expand Down
16 changes: 16 additions & 0 deletions packages/cli/src/ui/commands/mcpCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,16 @@ const listAction = async (
.getAllResources()
.filter((entry) => serverNames.includes(entry.serverName));

const skillManager = config.getSkillManager();
const mcpSkills = skillManager
.getAllSkills()
.filter(
(skill) =>
skill.source === 'mcp' &&
!!skill.mcp &&
serverNames.includes(skill.mcp.serverName),
);

const authStatus: HistoryItemMcpStatus['authStatus'] = {};
const tokenStorage = new MCPOAuthTokenStorage();
for (const serverName of serverNames) {
Expand Down Expand Up @@ -301,6 +311,12 @@ const listAction = async (
mimeType: resource.mimeType,
description: resource.description,
})),
skills: mcpSkills.map((skill) => ({
serverName: skill.mcp!.serverName,
name: skill.name,
description: skill.description,
uri: skill.mcp!.skillUri,
})),
authStatus,
enablementState,
errors,
Expand Down
38 changes: 37 additions & 1 deletion packages/cli/src/ui/components/views/McpStatus.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import type {
HistoryItemMcpStatus,
JsonMcpPrompt,
JsonMcpResource,
JsonMcpSkill,
JsonMcpTool,
} from '../../types.js';

Expand All @@ -21,6 +22,7 @@ interface McpStatusProps {
tools: JsonMcpTool[];
prompts: JsonMcpPrompt[];
resources: JsonMcpResource[];
skills?: JsonMcpSkill[];
blockedServers: Array<{ name: string; extensionName: string }>;
serverStatus: (serverName: string) => MCPServerStatus;
authStatus: HistoryItemMcpStatus['authStatus'];
Expand All @@ -37,6 +39,7 @@ export const McpStatus: React.FC<McpStatusProps> = ({
tools,
prompts,
resources,
skills = [],
blockedServers,
serverStatus,
authStatus,
Expand Down Expand Up @@ -97,11 +100,15 @@ export const McpStatus: React.FC<McpStatusProps> = ({
const serverResources = resources.filter(
(resource) => resource.serverName === serverName,
);
const serverSkills = skills.filter(
(skill) => skill.serverName === serverName,
);
const originalStatus = serverStatus(serverName);
const hasCachedItems =
serverTools.length > 0 ||
serverPrompts.length > 0 ||
serverResources.length > 0;
serverResources.length > 0 ||
serverSkills.length > 0;
const status =
originalStatus === MCPServerStatus.DISCONNECTED && hasCachedItems
? MCPServerStatus.CONNECTED
Expand Down Expand Up @@ -164,6 +171,10 @@ export const McpStatus: React.FC<McpStatusProps> = ({
`${resourceCount} ${resourceCount === 1 ? 'resource' : 'resources'}`,
);
}
const skillCount = serverSkills.length;
if (skillCount > 0) {
parts.push(`${skillCount} ${skillCount === 1 ? 'skill' : 'skills'}`);
}

const serverAuthStatus = authStatus[serverName];
let authStatusNode: React.ReactNode = null;
Expand Down Expand Up @@ -315,6 +326,31 @@ export const McpStatus: React.FC<McpStatusProps> = ({
)}
</Box>
)}

{serverSkills.length > 0 && (
<Box flexDirection="column" marginLeft={2}>
<Text color={theme.text.primary}>
Skills (io.modelcontextprotocol/skills):
</Text>
{serverSkills.map((skill) => (
<Box key={skill.name} flexDirection="column">
<Text>
- <Text color={theme.text.primary}>{skill.name}</Text>
<Text
color={theme.text.secondary}
>{` (${skill.uri})`}</Text>
</Text>
{showDescriptions && skill.description && (
<Box marginLeft={2}>
<Text color={theme.text.secondary}>
{skill.description.trim()}
</Text>
</Box>
)}
</Box>
))}
</Box>
)}
</Box>
);
})}
Expand Down
5 changes: 5 additions & 0 deletions packages/cli/src/ui/components/views/SkillsList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ export const SkillsList: React.FC<SkillsListProps> = ({
{skill.isBuiltin && (
<Text color={theme.text.secondary}>{' [Built-in]'}</Text>
)}
{skill.source === 'mcp' && skill.mcp && (
<Text color={theme.text.secondary}>
{` [mcp:${skill.mcp.serverName}]`}
</Text>
)}
</Box>
{showDescriptions && skill.description && (
<Box marginLeft={2}>
Expand Down
8 changes: 8 additions & 0 deletions packages/cli/src/ui/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -355,12 +355,20 @@ export interface JsonMcpResource {
description?: string;
}

export interface JsonMcpSkill {
serverName: string;
name: string;
description: string;
uri: string;
}

export type HistoryItemMcpStatus = HistoryItemBase & {
type: 'mcp_status';
servers: Record<string, MCPServerConfig>;
tools: JsonMcpTool[];
prompts: JsonMcpPrompt[];
resources: JsonMcpResource[];
skills?: JsonMcpSkill[];
authStatus: Record<
string,
'authenticated' | 'expired' | 'unauthenticated' | 'not-configured'
Expand Down
9 changes: 9 additions & 0 deletions packages/core/src/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -498,6 +498,15 @@ export class MCPServerConfig {
readonly targetAudience?: string,
/* targetServiceAccount format: <service-account-name>@<project-num>.iam.gserviceaccount.com */
readonly targetServiceAccount?: string,
// Skills-over-MCP (io.modelcontextprotocol/skills) extension controls.
// Appended at the end so existing positional callers remain source-compatible.
// When false, skill discovery is suppressed for this server even if it
// declares the extension capability. Defaults to true.
readonly skillsEnabled?: boolean,
// Allowlist of skill names to surface from this server (exact match).
readonly includeSkills?: string[],
// Blocklist of skill names to hide from this server (exact match).
readonly excludeSkills?: string[],
) {}
}

Expand Down
Loading