feat: support remote MCP servers via Streamable HTTP - #14
Conversation
Skills can now declare remote MCP servers alongside local stdio ones:
mcp:
my-server:
type: remote
url: https://mcp.example.com/mcp
headers:
Authorization: Bearer ${API_TOKEN}
- Split McpServerConfig into a LocalMcpServerConfig | RemoteMcpServerConfig
discriminated union (type: "remote" selects StreamableHTTPClientTransport)
- Accept remote configs in direct-format mcp.json files
- normalizeCommand/normalizeEnv keep the union signature and fail fast on
remote configs
- Document remote config in README; add loader and normalize tests
Ported from #8 by @orionpax1997, minus that PR's packaging changes.
Co-authored-by: orionpax1997 <orionpax.1997@outlook.com>
There was a problem hiding this comment.
Pull request overview
Adds first-class support for remote MCP servers (in addition to existing local/stdio servers) by introducing a discriminated McpServerConfig union and wiring the MCP manager to use the SDK’s Streamable HTTP transport when type: "remote" is configured. This keeps existing local behavior as the default while enabling URL-based remote connections with optional request headers.
Changes:
- Introduce
LocalMcpServerConfig | RemoteMcpServerConfigand re-export the new types for external consumers. - Add remote connection support in
skill-mcp-managerusingStreamableHTTPClientTransport, including remote-specific connection failure messaging. - Extend
skill-loader(and tests) to recognize remote entries in direct-formatmcp.json, and update docs/tests accordingly.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| src/utils/env-vars.ts | Fail-fast when normalizeCommand / normalizeEnv are accidentally used with remote configs. |
| src/types.ts | Define LocalMcpServerConfig, RemoteMcpServerConfig, and the McpServerConfig discriminated union. |
| src/skill-mcp-manager.ts | Select stdio vs Streamable HTTP transport based on config; add remote connect error messaging. |
| src/skill-loader.ts | Recognize remote entries in direct-format mcp.json configs. |
| src/index.ts | Re-export new config types for downstream usage. |
| src/tests/skill-loader.test.ts | Add coverage for remote configs via frontmatter and direct-format mcp.json. |
| src/tests/normalize-command.test.ts | Add coverage for rejecting remote configs in normalizeCommand/normalizeEnv. |
| README.md | Document the remote MCP server configuration format and example. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| let url: URL | ||
| try { | ||
| url = new URL(config.url) | ||
| } catch { | ||
| throw new Error( | ||
| `MCP server "${info.serverName}" has an invalid URL: ${config.url}\n\n` + | ||
| `The URL must be a valid HTTP or HTTPS URL.` | ||
| ) | ||
| } |
|
@copilot address all merge conflicts, build failures, and review comments on this PR. When you have addressed a code review issue mark it as resolved. |
…oteTransport Co-authored-by: keybrdist <3534206+keybrdist@users.noreply.github.com>
Addressed the
All 30 tests still pass. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.
Suppressed comments (2)
src/skill-mcp-manager.ts:103
isRemoteConfigonly checksconfig.type === "remote". If a user misspells the discriminator (e.g.type: "Remote") or provides some other invalid value via JSON/frontmatter, the code will treat it as a local config and emit a misleading "missing command" error. Consider validatingtypeexplicitly and failing fast with a clearer message.
const isRemoteConfig = (config: McpServerConfig): config is RemoteMcpServerConfig => {
return config.type === 'remote'
}
README.md:166
- The README introduces
LocalMcpServerConfigandRemoteMcpServerConfig, but it no longer shows how these relate back to theMcpServerConfigtype that the plugin/API surface uses. Adding the union alias here would make the documented shape match the exported types and reduce confusion for consumers.
}
Ports the remote MCP server support from #8 by @orionpax1997 onto current main, without that PR's packaging/rebrand changes (which had conflicted with #12/#13).
What's included
McpServerConfigis now a discriminated union:LocalMcpServerConfig | RemoteMcpServerConfig.type: "remote"+urlselects the SDK'sStreamableHTTPClientTransport; local stdio behavior is unchanged and remains the default.headerson remote configs (with the existing${VAR}env expansion, since the whole config is expanded before connect) for e.g. bearer auth.skill-loaderaccepts remote entries in direct-formatmcp.jsonfiles.normalizeCommand/normalizeEnvkeep theMcpServerConfigunion signature and fail fast on remote configs instead of narrowing their parameter type.src/index.ts; README documents the remote format.Not included from #8
prepackremoval, README deprecation-notice removal (fork-specific packaging).skill/→skills/path changes (already on main via Restore native skill discovery and prepare v1.0.4 #12).oauthconfig field (declared but unused in feat: support remote mcp #8 — left out rather than shipping dead config surface).Testing
npm run buildclean,npm test30/30 passing.mcp.jsonremote entries, rejection of configs with neithercommandnortype: remote, and remote rejection innormalizeCommand/normalizeEnv.Closes #8
Co-authored-by: orionpax1997