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
82 changes: 82 additions & 0 deletions src/commands/__tests__/tools-find.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: [
Expand Down
25 changes: 20 additions & 5 deletions src/commands/event/topics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { ConnectSession } from "../mcp/api"

export async function listTopics(
connection: string,
options: { namespace?: string },
options: { namespace?: string; prefix?: string },
): Promise<void> {
const isJson = isJsonMode()

Expand All @@ -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
}
Expand All @@ -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} <topic> to subscribe to events.`,
})
} finally {
Expand Down
10 changes: 9 additions & 1 deletion src/commands/mcp/search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ export async function findTools(
page?: string
all?: boolean
match?: string
prefix?: string
},
): Promise<void> {
const isJson = isJsonMode()
Expand Down Expand Up @@ -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,
Expand All @@ -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 }
Expand Down
36 changes: 27 additions & 9 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 <ns>", "Namespace to list from")
.option("--limit <n>", "Maximum number of tools to return (default: 10)")
Expand All @@ -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 <query>' to search tools by name or intent.`,
)
.action((connection, options) =>
handleFindTools(undefined, { ...options, connection }),
.action((connection, prefix, options) =>
handleFindTools(undefined, { ...options, connection, prefix }),
)

toolCmd
Expand Down Expand Up @@ -855,12 +860,25 @@ const eventCmd = program
.description("Subscribe to event streams from MCP servers")

eventCmd
.command("topics <connection>")
.command("topics <connection> [prefix]")
.description("List available event topics for a connection")
.option("--namespace <ns>", "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
Expand Down
Loading