From 9afee845ec57bdf31f0ad7cc3fe7b5c3a1a52862 Mon Sep 17 00:00:00 2001 From: Henry Mao <1828968+calclavia@users.noreply.github.com> Date: Thu, 28 May 2026 17:29:36 +0800 Subject: [PATCH 1/3] fix: use server target for mcp add --- package.json | 2 +- pnpm-lock.yaml | 10 ++--- src/commands/__tests__/mcp-add-impl.test.ts | 33 +++++++++++++- src/commands/__tests__/mcp-add-uplink.test.ts | 4 ++ src/commands/__tests__/mcp-api.test.ts | 41 +++++++++++++++++ src/commands/mcp/add-impl.ts | 17 +++---- src/commands/mcp/add.ts | 5 +-- src/commands/mcp/api.ts | 45 +++++++++++++------ src/commands/mcp/normalize-url.ts | 17 ------- src/index.ts | 7 ++- 10 files changed, 129 insertions(+), 52 deletions(-) delete mode 100644 src/commands/mcp/normalize-url.ts diff --git a/package.json b/package.json index 59397a0f..3ee4cd7f 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "@anthropic-ai/mcpb": "^1.1.1", "@biomejs/biome": "2.3.10", "@modelcontextprotocol/sdk": "^1.25.3", - "@smithery/api": "^0.64.2", + "@smithery/api": "^0.66.0", "@smithery/sdk": "^4.1.0", "@types/inquirer": "^8.2.4", "@types/inquirer-autocomplete-prompt": "^3.0.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 053d19b2..17a44a01 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -18,8 +18,8 @@ importers: specifier: ^1.25.3 version: 1.25.3(hono@4.11.1)(zod@4.2.1) '@smithery/api': - specifier: ^0.64.2 - version: 0.64.2(@modelcontextprotocol/sdk@1.25.3(hono@4.11.1)(zod@4.2.1)) + specifier: ^0.66.0 + version: 0.66.0(@modelcontextprotocol/sdk@1.25.3(hono@4.11.1)(zod@4.2.1)) '@smithery/sdk': specifier: ^4.1.0 version: 4.1.0(@modelcontextprotocol/sdk@1.25.3(hono@4.11.1)(zod@4.2.1))(zod@4.2.1) @@ -806,8 +806,8 @@ packages: resolution: {integrity: sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw==} engines: {node: '>=18'} - '@smithery/api@0.64.2': - resolution: {integrity: sha512-Wv6ZtTr6v+0yKREknsBgjFND4BkpbWEGeNTpFcvfzadKBUFF6U/bfrTcnPGffhmN9GyoSC1gOESTwiboYbOmhA==} + '@smithery/api@0.66.0': + resolution: {integrity: sha512-atE6NjMF6kg8cSjaeu8o9zA/6zF0uUtjSDJEdYRvT357f3WEldm+jcE1lSea59dl7r0m1OPAhrprxLD/zQU+aQ==} peerDependencies: '@modelcontextprotocol/sdk': '>=1.0.0' peerDependenciesMeta: @@ -2671,7 +2671,7 @@ snapshots: '@sindresorhus/is@7.2.0': {} - '@smithery/api@0.64.2(@modelcontextprotocol/sdk@1.25.3(hono@4.11.1)(zod@4.2.1))': + '@smithery/api@0.66.0(@modelcontextprotocol/sdk@1.25.3(hono@4.11.1)(zod@4.2.1))': optionalDependencies: '@modelcontextprotocol/sdk': 1.25.3(hono@4.11.1)(zod@4.2.1) diff --git a/src/commands/__tests__/mcp-add-impl.test.ts b/src/commands/__tests__/mcp-add-impl.test.ts index 81e91631..e0b2b185 100644 --- a/src/commands/__tests__/mcp-add-impl.test.ts +++ b/src/commands/__tests__/mcp-add-impl.test.ts @@ -36,6 +36,10 @@ vi.mock("../mcp/api", () => ({ ConnectSession: { create: mockCreateSession, }, + connectionTargetFromInput: (input: string) => + input.startsWith("http://") || input.startsWith("https://") + ? { mcpUrl: input } + : { server: input }, })) vi.mock("../mcp/output-connection", () => ({ @@ -97,7 +101,10 @@ describe("mcp add duplicate handling", () => { ], }) - await addServer("calclavia/test-input-required-two", {}) + await addServer( + "https://server.smithery.ai/calclavia/test-input-required-two", + {}, + ) expect(mockCreateConnection).not.toHaveBeenCalled() expect(mockOutputConnectionDetail).toHaveBeenCalledWith( @@ -112,6 +119,30 @@ describe("mcp add duplicate handling", () => { ) }) + test("creates registry connections with the server target", async () => { + mockCreateConnection.mockResolvedValue({ + connectionId: "github", + name: "github", + mcpUrl: "https://server.smithery.ai/github", + metadata: null, + status: { + state: "connected", + }, + }) + + await addServer("github", {}) + + expect(mockListConnectionsByUrl).not.toHaveBeenCalled() + expect(mockCreateConnection).toHaveBeenCalledWith( + { server: "github" }, + { + name: undefined, + metadata: undefined, + headers: undefined, + }, + ) + }) + test("prints setupUrl for auth_required duplicate connections", async () => { mockListConnectionsByUrl.mockResolvedValue({ connections: [ diff --git a/src/commands/__tests__/mcp-add-uplink.test.ts b/src/commands/__tests__/mcp-add-uplink.test.ts index c1551d05..19e86b09 100644 --- a/src/commands/__tests__/mcp-add-uplink.test.ts +++ b/src/commands/__tests__/mcp-add-uplink.test.ts @@ -44,6 +44,10 @@ vi.mock("../mcp/api", () => ({ ConnectSession: { create: mockCreateSession, }, + connectionTargetFromInput: (input: string) => + input.startsWith("http://") || input.startsWith("https://") + ? { mcpUrl: input } + : { server: input }, })) vi.mock("../../lib/uplink", async () => { diff --git a/src/commands/__tests__/mcp-api.test.ts b/src/commands/__tests__/mcp-api.test.ts index 0dd98237..9d3f2102 100644 --- a/src/commands/__tests__/mcp-api.test.ts +++ b/src/commands/__tests__/mcp-api.test.ts @@ -28,6 +28,26 @@ describe("ConnectSession uplink compatibility", () => { }) }) + test("creates registry connections with server targets", async () => { + const create = vi.fn().mockResolvedValue({ + connectionId: "exa", + name: "exa", + mcpUrl: "https://server.smithery.ai/exa", + metadata: null, + status: { state: "connected" }, + }) + + const session = new ConnectSession( + { connections: { create } } as never, + "calclavia", + ) + await session.createConnection({ server: "exa" }) + + expect(create).toHaveBeenCalledWith("calclavia", { + server: "exa", + }) + }) + test("does not replace conflicting uplink connections on 409", async () => { const conflict = new ConflictError(409, {}, undefined, new Headers()) const set = vi.fn().mockRejectedValueOnce(conflict) @@ -80,6 +100,27 @@ describe("ConnectSession uplink compatibility", () => { }) }) + test("sets registry connections with server targets", async () => { + const set = vi.fn().mockResolvedValue({ + connectionId: "exa", + name: "exa", + mcpUrl: "https://server.smithery.ai/exa", + metadata: null, + status: { state: "connected" }, + }) + + const session = new ConnectSession( + { connections: { set } } as never, + "calclavia", + ) + await session.setConnection("exa", { server: "exa" }) + + expect(set).toHaveBeenCalledWith("exa", { + namespace: "calclavia", + server: "exa", + }) + }) + test("lists tools over smithery.run REST", async () => { const get = vi.fn().mockResolvedValue({ tools: [ diff --git a/src/commands/mcp/add-impl.ts b/src/commands/mcp/add-impl.ts index edf60545..85e179ac 100644 --- a/src/commands/mcp/add-impl.ts +++ b/src/commands/mcp/add-impl.ts @@ -5,9 +5,8 @@ import { completeConnectionAuthorization, finalizeAddedConnection, } from "./add-flow" -import { ConnectSession } from "./api" +import { ConnectSession, connectionTargetFromInput } from "./api" import { isInputRequiredStatus } from "./connection-status" -import { normalizeMcpUrl } from "./normalize-url" import { outputConnectionDetail } from "./output-connection" import { parseJsonObject } from "./parse-json" @@ -29,13 +28,15 @@ export async function addServer( true, ) - const normalizedUrl = normalizeMcpUrl(mcpUrl) + const target = connectionTargetFromInput(mcpUrl) const session = await ConnectSession.create(options.namespace) - // Check for existing connections with the same URL - if (!options.force) { - const { connections: existing } = - await session.listConnectionsByUrl(normalizedUrl) + // URL inputs can still be checked exactly. Registry-name inputs are sent + // as `server` so Connect owns canonical URL resolution. + if (!options.force && target.mcpUrl) { + const { connections: existing } = await session.listConnectionsByUrl( + target.mcpUrl, + ) if (existing.length > 0) { let match = existing[0] const status = match.status?.state ?? "unknown" @@ -76,7 +77,7 @@ export async function addServer( } } - const connection = await session.createConnection(normalizedUrl, { + const connection = await session.createConnection(target, { name: options.name, metadata: parsedMetadata, headers: parsedHeaders, diff --git a/src/commands/mcp/add.ts b/src/commands/mcp/add.ts index 742efb66..70ce2220 100644 --- a/src/commands/mcp/add.ts +++ b/src/commands/mcp/add.ts @@ -12,8 +12,7 @@ import { addBundleUplinkServer, type BundleAddTarget, } from "./add-uplink-bundle" -import { ConnectSession } from "./api" -import { normalizeMcpUrl } from "./normalize-url" +import { ConnectSession, connectionTargetFromInput } from "./api" import { outputConnectionDetail } from "./output-connection" import { parseJsonObject } from "./parse-json" import { classifyAddTarget } from "./uplink-target" @@ -69,7 +68,7 @@ export async function addServer( const session = await ConnectSession.create(options.namespace) const connection = await session.setConnection( options.id, - normalizeMcpUrl(mcpUrl), + connectionTargetFromInput(mcpUrl), { name, metadata: parsedMetadata, diff --git a/src/commands/mcp/api.ts b/src/commands/mcp/api.ts index 69241d2e..a92d5f61 100644 --- a/src/commands/mcp/api.ts +++ b/src/commands/mcp/api.ts @@ -5,6 +5,7 @@ import type { Connection, ConnectionCreateParams, ConnectionListParams, + ConnectionSetParams, ConnectionsListResponse, } from "@smithery/api/resources/connections.js" import { createSmitheryClient } from "../../lib/smithery-client" @@ -15,6 +16,9 @@ import { export type { Connection, ConnectionsListResponse } export type ConnectionTransport = NonNullable +export type ConnectionTarget = + | (Required> & { server?: never }) + | (Required> & { mcpUrl?: never }) export interface Trigger { name: string @@ -156,12 +160,12 @@ export class ConnectSession { } async createConnection( - mcpUrl?: string, + target?: string | ConnectionTarget, options: ConnectionWriteOptions = {}, ): Promise { return this.smitheryClient.connections.create( this.namespace, - buildConnectionBody(mcpUrl, options) as ConnectionCreateParams, + buildConnectionBody(target, options), ) } @@ -171,20 +175,20 @@ export class ConnectSession { */ async setConnection( connectionId: string, - mcpUrl?: string, + target?: string | ConnectionTarget, options: ConnectionWriteOptions = {}, ): Promise { try { return await this.smitheryClient.connections.set( connectionId, - buildConnectionSetParams(this.namespace, mcpUrl, options), + buildConnectionSetParams(this.namespace, target, options), ) } catch (error) { if (error instanceof ConflictError && options.transport !== "uplink") { await this.deleteConnection(connectionId) return this.smitheryClient.connections.set( connectionId, - buildConnectionSetParams(this.namespace, mcpUrl, options), + buildConnectionSetParams(this.namespace, target, options), ) } throw error @@ -255,31 +259,46 @@ export class ConnectSession { } } +export function connectionTargetFromInput(input: string): ConnectionTarget { + return isHttpUrl(input) ? { mcpUrl: input } : { server: input } +} + function buildConnectionBody( - mcpUrl: string | undefined, + target: string | ConnectionTarget | undefined, options: ConnectionWriteOptions, -): Record { - const body = { - ...(mcpUrl ? { mcpUrl } : {}), +): ConnectionCreateParams { + return { + ...normalizeConnectionTarget(target), ...(options.name ? { name: options.name } : {}), ...(options.metadata ? { metadata: options.metadata } : {}), ...(options.headers ? { headers: options.headers } : {}), ...(options.transport ? { transport: options.transport } : {}), } - return body } function buildConnectionSetParams( namespace: string, - mcpUrl: string | undefined, + target: string | ConnectionTarget | undefined, options: ConnectionWriteOptions, -) { +): ConnectionSetParams { return { namespace, - ...buildConnectionBody(mcpUrl, options), + ...buildConnectionBody(target, options), } } +function normalizeConnectionTarget( + target: string | ConnectionTarget | undefined, +): Pick { + if (!target) return {} + if (typeof target === "string") return { mcpUrl: target } + return target +} + +function isHttpUrl(value: string): boolean { + return value.startsWith("http://") || value.startsWith("https://") +} + function namespacePath(namespace: string): string { return `/${encodeURIComponent(namespace)}` } diff --git a/src/commands/mcp/normalize-url.ts b/src/commands/mcp/normalize-url.ts deleted file mode 100644 index c9d2a6f9..00000000 --- a/src/commands/mcp/normalize-url.ts +++ /dev/null @@ -1,17 +0,0 @@ -const SMITHERY_GATEWAY_BASE = "https://server.smithery.ai/" - -/** - * Normalize an MCP URL. If the input doesn't start with https://, - * treat it as a Smithery server reference (namespace/serverId) and - * prepend the Smithery server base URL. - * - * Examples: - * "anthropic/fetch" → "https://server.smithery.ai/anthropic/fetch" - * "https://example.com/mcp" → "https://example.com/mcp" - */ -export function normalizeMcpUrl(url: string): string { - if (url.startsWith("https://") || url.startsWith("http://")) { - return url - } - return `${SMITHERY_GATEWAY_BASE}${url}` -} diff --git a/src/index.ts b/src/index.ts index 993c25f4..a6139f5f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -125,7 +125,6 @@ async function handleSearch(term: string | undefined, options: CliOptions) { qualifiedName: server.qualifiedName, description: server.description ?? "", useCount: server.useCount, - connectionUrl: `https://server.smithery.ai/${server.qualifiedName}`, })) const page = parseInt(options.page ?? "1", 10) || 1 @@ -146,7 +145,7 @@ async function handleSearch(term: string | undefined, options: CliOptions) { json, jsonData: { servers: data, page, hasMore }, pagination: { page, hasMore }, - tip: "Use smithery mcp add to connect a server.", + tip: "Use smithery mcp add to connect a server.", }) } @@ -638,10 +637,10 @@ mcpCmd "after", ` Examples: - smithery mcp add https://server.smithery.ai/exa + smithery mcp add exa smithery mcp add http://localhost:9090/mcp --id chrome smithery mcp add --id chrome -- npx -y @chromedevtools/chrome-devtools-mcp - smithery mcp add https://server.smithery.ai/exa --id exa --name "Exa Search" + smithery mcp add exa --id exa --name "Exa Search" smithery mcp add exa --client claude smithery mcp add https://mcp.smithery.run/arjunkmrm --name toolbox --client librechat`, ) From 3bd3cb9e4ce2e8f237b7e54f5413320e77c918cd Mon Sep 17 00:00:00 2001 From: Henry Mao <1828968+calclavia@users.noreply.github.com> Date: Thu, 28 May 2026 17:34:16 +0800 Subject: [PATCH 2/3] chore: remove unused connection transport type --- src/commands/mcp/api.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/commands/mcp/api.ts b/src/commands/mcp/api.ts index a92d5f61..b2de77b3 100644 --- a/src/commands/mcp/api.ts +++ b/src/commands/mcp/api.ts @@ -15,7 +15,6 @@ import { } from "../../utils/smithery-settings" export type { Connection, ConnectionsListResponse } -export type ConnectionTransport = NonNullable export type ConnectionTarget = | (Required> & { server?: never }) | (Required> & { mcpUrl?: never }) From 05d15fcc2b83055854ff223b8a2377d3c191ec31 Mon Sep 17 00:00:00 2001 From: Henry Mao <1828968+calclavia@users.noreply.github.com> Date: Thu, 28 May 2026 17:35:15 +0800 Subject: [PATCH 3/3] docs: remove legacy gateway examples --- README.md | 2 +- src/config/command-templates.ts | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 9514587c..3949c331 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,7 @@ smithery mcp publish -n # Publish an MCP bundle ```bash # Search and connect to an MCP server smithery mcp search "github" -smithery mcp add https://server.smithery.ai/github --id github +smithery mcp add github --id github # Find and call tools from your connected MCP servers smithery tool find "create issue" diff --git a/src/config/command-templates.ts b/src/config/command-templates.ts index f618c1c2..b13f6dd8 100644 --- a/src/config/command-templates.ts +++ b/src/config/command-templates.ts @@ -19,7 +19,7 @@ export function claudeCodeStdioCommand( /** * Claude Code HTTP command template * Generates: claude mcp add --transport http - * Example: claude mcp add --transport http upstash-context-7-mcp "https://server.smithery.ai/@upstash/context7-mcp/mcp" + * Example: claude mcp add --transport http upstash-context-7-mcp "https://mcp.example.com/mcp" */ export function claudeCodeHttpCommand(name: string, url: string): string[] { return ["mcp", "add", "--transport", "http", name, url] @@ -41,7 +41,7 @@ export function vscodeStdioCommand( /** * VS Code HTTP command template * Generates: code --add-mcp '{"name":"server","type":"http","url":"https://..."}' - * Example: code --add-mcp '{"name":"upstash-context","type":"http","url":"https://server.smithery.ai/@upstash/context7-mcp/mcp"}' + * Example: code --add-mcp '{"name":"example","type":"http","url":"https://mcp.example.com/mcp"}' */ export function vscodeHttpCommand(name: string, url: string): string[] { return ["--add-mcp", JSON.stringify({ name, type: "http", url })] @@ -63,7 +63,7 @@ export function geminiCliStdioCommand( /** * Gemini CLI HTTP command template * Generates: gemini mcp add --transport http "" - * Example: gemini mcp add --transport http upstash-context "https://server.smithery.ai/@upstash/context7-mcp/mcp" + * Example: gemini mcp add --transport http example "https://mcp.example.com/mcp" */ export function geminiCliHttpCommand(name: string, url: string): string[] { return ["mcp", "add", "--transport", "http", name, url] @@ -85,7 +85,7 @@ export function codexStdioCommand( /** * Codex HTTP command template * Generates: codex mcp add --url - * Example: codex mcp add upstash-context --url "https://server.smithery.ai/@upstash/context7-mcp/mcp" + * Example: codex mcp add example --url "https://mcp.example.com/mcp" */ export function codexHttpCommand(name: string, url: string): string[] { return ["mcp", "add", name, "--url", url]