Skip to content

Commit 6b932f2

Browse files
adaam2claudeqstearns
authored
feat: multi-remote server selection for external MCPs (#1792)
## Summary - Adds support for MCP servers that have multiple remote endpoints (like Salesforce which has different remotes for different sub apis) - Users can now select which remote endpoints to include when adding such servers to their project - Adds new `selectRemotes` workflow phase for configuring multi-remote servers before deployment - **Backend now stores and uses selected remotes during deployment processing** ## Changes - **API**: Added `ExternalMCPRemote` type and `remotes` field to `ExternalMCPServer` in Goa design - **API**: Added `selected_remotes` field to `AddExternalMCPForm` for storing user selections - **Backend**: Updated SQL queries to store `selected_remotes` in `external_mcp_attachments` - **Backend**: Modified `GetServerDetails` to filter remotes based on user selection - **Backend**: Pass selected remotes through ToolExtractor during deployment - **Frontend**: Added `selectRemotes` phase to the workflow for multi-remote server configuration - **Frontend**: Added UI for selecting which remote endpoints to include, with friendly display names for known services (Salesforce sub-APIs) - **Frontend**: Added `goBack` navigation to return from configure phase to selectRemotes phase - **Frontend**: Now sends selected remote URLs to backend on deployment ## Dependencies - Requires migration PR #1793 to be merged first (adds `selected_remotes` column) ## Test plan - [ ] Merge migration PR #1793 first - [ ] Add a Salesforce MCP server from the catalog - [ ] Verify the remote selection UI appears with all available endpoints - [ ] Verify friendly names are shown for Salesforce environments - [ ] Verify deselecting some endpoints and deploying works correctly - [ ] Verify the backend uses only the selected remotes during tool extraction - [ ] Verify the "Back" button returns to remote selection from the configure phase 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> Co-authored-by: Quinn Stearns <quinn@speakeasy.com>
1 parent 5163d0d commit 6b932f2

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

65 files changed

+4491
-187
lines changed

.speakeasy/out.openapi.yaml

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5869,6 +5869,120 @@ paths:
58695869
tags:
58705870
- mcpMetadata
58715871
x-speakeasy-name-override: set
5872+
/rpc/mcpRegistries.getServerDetails:
5873+
get:
5874+
description: Get detailed information about an MCP server including remotes
5875+
operationId: getMCPServerDetails
5876+
parameters:
5877+
- allowEmptyValue: true
5878+
description: ID of the registry
5879+
in: query
5880+
name: registry_id
5881+
required: true
5882+
schema:
5883+
description: ID of the registry
5884+
format: uuid
5885+
type: string
5886+
- allowEmptyValue: true
5887+
description: Server specifier (e.g., 'io.github.user/server')
5888+
in: query
5889+
name: server_specifier
5890+
required: true
5891+
schema:
5892+
description: Server specifier (e.g., 'io.github.user/server')
5893+
type: string
5894+
- allowEmptyValue: true
5895+
description: Session header
5896+
in: header
5897+
name: Gram-Session
5898+
schema:
5899+
description: Session header
5900+
type: string
5901+
- allowEmptyValue: true
5902+
description: API Key header
5903+
in: header
5904+
name: Gram-Key
5905+
schema:
5906+
description: API Key header
5907+
type: string
5908+
- allowEmptyValue: true
5909+
description: project header
5910+
in: header
5911+
name: Gram-Project
5912+
schema:
5913+
description: project header
5914+
type: string
5915+
responses:
5916+
"200":
5917+
content:
5918+
application/json:
5919+
schema:
5920+
$ref: '#/components/schemas/ExternalMCPServer'
5921+
description: OK response.
5922+
"400":
5923+
content:
5924+
application/json:
5925+
schema:
5926+
$ref: '#/components/schemas/Error'
5927+
description: 'bad_request: request is invalid'
5928+
"401":
5929+
content:
5930+
application/json:
5931+
schema:
5932+
$ref: '#/components/schemas/Error'
5933+
description: 'unauthorized: unauthorized access'
5934+
"403":
5935+
content:
5936+
application/json:
5937+
schema:
5938+
$ref: '#/components/schemas/Error'
5939+
description: 'forbidden: permission denied'
5940+
"404":
5941+
content:
5942+
application/json:
5943+
schema:
5944+
$ref: '#/components/schemas/Error'
5945+
description: 'not_found: resource not found'
5946+
"409":
5947+
content:
5948+
application/json:
5949+
schema:
5950+
$ref: '#/components/schemas/Error'
5951+
description: 'conflict: resource already exists'
5952+
"415":
5953+
content:
5954+
application/json:
5955+
schema:
5956+
$ref: '#/components/schemas/Error'
5957+
description: 'unsupported_media: unsupported media type'
5958+
"422":
5959+
content:
5960+
application/json:
5961+
schema:
5962+
$ref: '#/components/schemas/Error'
5963+
description: 'invalid: request contains one or more invalidation fields'
5964+
"500":
5965+
content:
5966+
application/json:
5967+
schema:
5968+
$ref: '#/components/schemas/Error'
5969+
description: 'unexpected: an unexpected error occurred'
5970+
"502":
5971+
content:
5972+
application/json:
5973+
schema:
5974+
$ref: '#/components/schemas/Error'
5975+
description: 'gateway_error: an unexpected error occurred'
5976+
security:
5977+
- project_slug_header_Gram-Project: []
5978+
session_header_Gram-Session: []
5979+
- apikey_header_Gram-Key: []
5980+
project_slug_header_Gram-Project: []
5981+
- {}
5982+
summary: getServerDetails mcpRegistries
5983+
tags:
5984+
- mcpRegistries
5985+
x-speakeasy-name-override: getServerDetails
58725986
/rpc/mcpRegistries.listCatalog:
58735987
get:
58745988
description: List available MCP servers from configured registries
@@ -12164,6 +12278,13 @@ components:
1216412278
type: string
1216512279
description: The canonical server name used to look up the server in the registry (e.g., 'slack', 'ai.exa/exa').
1216612280
example: slack
12281+
selected_remotes:
12282+
type: array
12283+
items:
12284+
type: string
12285+
description: URLs of the remotes to use for this MCP server. If not provided, the backend will auto-select based on transport type preference.
12286+
example:
12287+
- https://mcp.example.com/sse
1216712288
slug:
1216812289
type: string
1216912290
description: A short url-friendly label that uniquely identifies a resource.
@@ -13853,6 +13974,23 @@ components:
1385313974
- header_name
1385413975
- required
1385513976
- secret
13977+
ExternalMCPRemote:
13978+
type: object
13979+
properties:
13980+
transport_type:
13981+
type: string
13982+
description: Transport type (sse or streamable-http)
13983+
enum:
13984+
- sse
13985+
- streamable-http
13986+
url:
13987+
type: string
13988+
description: URL of the remote endpoint
13989+
format: uri
13990+
description: A remote endpoint for an MCP server
13991+
required:
13992+
- url
13993+
- transport_type
1385613994
ExternalMCPServer:
1385713995
type: object
1385813996
properties:
@@ -13873,6 +14011,11 @@ components:
1387314011
type: string
1387414012
description: Server specifier used to look up in the registry (e.g., 'io.github.user/server')
1387514013
example: io.modelcontextprotocol.anonymous/exa
14014+
remotes:
14015+
type: array
14016+
items:
14017+
$ref: '#/components/schemas/ExternalMCPRemote'
14018+
description: Available remote endpoints for the server
1387614019
title:
1387714020
type: string
1387814021
description: Display name for the server

.speakeasy/workflow.lock

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,17 @@ speakeasyVersion: 1.700.2
22
sources:
33
Gram-Internal:
44
sourceNamespace: gram-api-description
5-
sourceRevisionDigest: sha256:62b46b18ae06f666f1a67c0de57aceab4645753f186576c9b21110a9ae41cab1
6-
sourceBlobDigest: sha256:33c27aa018dc73e73a35889d75cee2d352788284794361f0194c3d24ba7d5c7f
5+
sourceRevisionDigest: sha256:c747d9b735a72b0efa38e98bebf8551d87ce6845103a26c5832d48351777e5ce
6+
sourceBlobDigest: sha256:3cc940413a9ed86217b0f103a47bb8d9f628cbb9f97b3476ca237d95aff1b0b4
77
tags:
88
- latest
99
- 0.0.1
1010
targets:
1111
gram-internal:
1212
source: Gram-Internal
1313
sourceNamespace: gram-api-description
14-
sourceRevisionDigest: sha256:62b46b18ae06f666f1a67c0de57aceab4645753f186576c9b21110a9ae41cab1
15-
sourceBlobDigest: sha256:33c27aa018dc73e73a35889d75cee2d352788284794361f0194c3d24ba7d5c7f
16-
codeSamplesNamespace: gram-api-description-typescript-code-samples
17-
codeSamplesRevisionDigest: sha256:3226bb17f37d693bdee3cb0bf7865d766e95249cb1ec0f68de7cf7fd1bc1ee3f
14+
sourceRevisionDigest: sha256:c747d9b735a72b0efa38e98bebf8551d87ce6845103a26c5832d48351777e5ce
15+
sourceBlobDigest: sha256:3cc940413a9ed86217b0f103a47bb8d9f628cbb9f97b3476ca237d95aff1b0b4
1816
workflow:
1917
workflowVersion: 1.0.0
2018
speakeasyVersion: pinned

client/dashboard/src/App.css

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,3 +85,18 @@
8585
transition-duration: 200ms;
8686
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
8787
}
88+
89+
/* Dotted background scroll animation for catalog cards */
90+
@keyframes scroll-dots {
91+
from {
92+
background-position: 0 0;
93+
}
94+
to {
95+
background-position: 64px 64px;
96+
}
97+
}
98+
99+
.server-card:hover .scroll-dots-target {
100+
animation: scroll-dots 3s linear infinite;
101+
color: oklch(0.553 0.013 58.071 / 0.4);
102+
}

0 commit comments

Comments
 (0)