diff --git a/src/commands/__tests__/tools-find.test.ts b/src/commands/__tests__/tools-find.test.ts index ec32fb5..3492c3b 100644 --- a/src/commands/__tests__/tools-find.test.ts +++ b/src/commands/__tests__/tools-find.test.ts @@ -95,6 +95,88 @@ describe("tools find command", () => { ) }) + test("filters tools by name prefix", async () => { + mockGetConnection.mockResolvedValue({ + connectionId: "github-abc", + name: "github-abc", + status: { state: "connected" }, + }) + mockListToolsForConnection.mockResolvedValue([ + { + connectionId: "github-abc", + connectionName: "github-abc", + name: "issues.create", + description: "Create an issue", + inputSchema: { type: "object" }, + }, + { + connectionId: "github-abc", + connectionName: "github-abc", + name: "issues.list", + description: "List issues", + inputSchema: { type: "object" }, + }, + { + connectionId: "github-abc", + connectionName: "github-abc", + name: "pulls.create", + description: "Create a pull request", + inputSchema: { type: "object" }, + }, + ]) + + await findTools(undefined, { + connection: "github-abc", + prefix: "issues.", + all: true, + }) + + expect(mockOutputTable).toHaveBeenCalledWith( + expect.objectContaining({ + jsonData: expect.objectContaining({ + total: 2, + prefix: "issues.", + tools: expect.arrayContaining([ + expect.objectContaining({ name: "issues.create" }), + expect.objectContaining({ name: "issues.list" }), + ]), + }), + }), + ) + }) + + test("prefix with no matches returns empty result", async () => { + mockGetConnection.mockResolvedValue({ + connectionId: "github-abc", + name: "github-abc", + status: { state: "connected" }, + }) + mockListToolsForConnection.mockResolvedValue([ + { + connectionId: "github-abc", + connectionName: "github-abc", + name: "pulls.create", + description: "Create a PR", + inputSchema: { type: "object" }, + }, + ]) + + await findTools(undefined, { + connection: "github-abc", + prefix: "issues.", + all: true, + }) + + expect(mockOutputTable).toHaveBeenCalledWith( + expect.objectContaining({ + jsonData: expect.objectContaining({ + total: 0, + tools: [], + }), + }), + ) + }) + test("supports listing behavior via find --all without a query", async () => { mockListConnections.mockResolvedValue({ connections: [ diff --git a/src/commands/event/topics.ts b/src/commands/event/topics.ts index d6fbf24..67d87a2 100644 --- a/src/commands/event/topics.ts +++ b/src/commands/event/topics.ts @@ -11,7 +11,7 @@ import { ConnectSession } from "../mcp/api" export async function listTopics( connection: string, - options: { namespace?: string }, + options: { namespace?: string; prefix?: string }, ): Promise { const isJson = isJsonMode() @@ -20,13 +20,25 @@ export async function listTopics( const mcpClient = await session.getEventsClient(connection) try { - const { eventTopics: topics } = await listEventTopics(mcpClient) + const { eventTopics } = await listEventTopics(mcpClient) + + const topics = options.prefix + ? eventTopics.filter((t) => + t.topic.toLowerCase().startsWith(options.prefix!.toLowerCase()), + ) + : eventTopics if (topics.length === 0) { if (isJson) { - outputJson({ topics: [] }) + outputJson({ + topics: [], + ...(options.prefix ? { prefix: options.prefix } : {}), + }) } else { - console.log(pc.yellow("No event topics found for this connection.")) + const msg = options.prefix + ? `No event topics found matching prefix "${options.prefix}".` + : "No event topics found for this connection." + console.log(pc.yellow(msg)) } return } @@ -51,7 +63,10 @@ export async function listTopics( { key: "hasInputSchema", header: "PARAMS" }, ], json: isJson, - jsonData: { topics }, + jsonData: { + topics, + ...(options.prefix ? { prefix: options.prefix } : {}), + }, tip: `Use smithery event subscribe ${connection} to subscribe to events.`, }) } finally { diff --git a/src/commands/mcp/search.ts b/src/commands/mcp/search.ts index a21c3ca..44fe922 100644 --- a/src/commands/mcp/search.ts +++ b/src/commands/mcp/search.ts @@ -117,6 +117,7 @@ export async function findTools( page?: string all?: boolean match?: string + prefix?: string }, ): Promise { const isJson = isJsonMode() @@ -227,8 +228,14 @@ export async function findTools( return } + const candidates = options.prefix + ? allTools.filter((tool) => + tool.name.toLowerCase().startsWith(options.prefix!.toLowerCase()), + ) + : allTools + const matches = matchTools( - allTools, + candidates, normalizedQuery, mode, limit, @@ -249,6 +256,7 @@ export async function findTools( tools: data, total: matches.length, mode, + ...(options.prefix ? { prefix: options.prefix } : {}), ...(normalizedQuery ? { query: normalizedQuery } : {}), ...(options.all ? { all: true, page: 1, hasMore: false } diff --git a/src/index.ts b/src/index.ts index f6be577..8227e93 100644 --- a/src/index.ts +++ b/src/index.ts @@ -800,7 +800,7 @@ Examples: .action(handleFindTools) toolCmd - .command("list [connection]") + .command("list [connection] [prefix]") .description("List tools from your connected MCP servers") .option("--namespace ", "Namespace to list from") .option("--limit ", "Maximum number of tools to return (default: 10)") @@ -809,15 +809,20 @@ toolCmd .addHelpText( "after", ` +Arguments: + connection Connection ID to list tools from (omit to list from all) + prefix Only show tools whose name starts with this prefix (e.g. "issues.") + Examples: - smithery tool list List tools from all connections - smithery tool list myserver List tools for a specific connection - smithery tool list myserver --json Output as JSON + smithery tool list List tools from all connections + smithery tool list myserver List tools for a specific connection + smithery tool list myserver issues. List tools starting with "issues." + smithery tool list myserver issues. --json Prefix-filtered output as JSON Tip: Use 'smithery tool find ' to search tools by name or intent.`, ) - .action((connection, options) => - handleFindTools(undefined, { ...options, connection }), + .action((connection, prefix, options) => + handleFindTools(undefined, { ...options, connection, prefix }), ) toolCmd @@ -855,12 +860,25 @@ const eventCmd = program .description("Subscribe to event streams from MCP servers") eventCmd - .command("topics ") + .command("topics [prefix]") .description("List available event topics for a connection") .option("--namespace ", "Namespace for the connection") - .action(async (connection: string, options: any) => { + .addHelpText( + "after", + ` +Arguments: + connection Connection ID to list topics from + prefix Only show topics whose identifier starts with this prefix (e.g. "user.") + +Examples: + smithery event topics myserver List all topics + smithery event topics myserver user. List topics starting with "user." + smithery event topics myserver user. --json Prefix-filtered output as JSON`, + ) + // biome-ignore lint/suspicious/noExplicitAny: commander.js passes options as any + .action(async (connection: string, prefix: string | undefined, options: any) => { const { listTopics } = await import("./commands/event") - await listTopics(connection, options) + await listTopics(connection, { ...options, prefix }) }) eventCmd