diff --git a/README.md b/README.md
index c7f9e73..8c82e8a 100644
--- a/README.md
+++ b/README.md
@@ -23,10 +23,7 @@ All the most popular MCP clients (Claude Desktop, Cursor & Windsurf) use the fol
"mcpServers": {
"remote-example": {
"command": "npx",
- "args": [
- "mcp-remote",
- "https://remote.mcp.server/sse"
- ]
+ "args": ["mcp-remote", "https://remote.mcp.server/sse"]
}
}
}
@@ -41,12 +38,7 @@ To bypass authentication, or to emit custom headers on all requests to your remo
"mcpServers": {
"remote-example": {
"command": "npx",
- "args": [
- "mcp-remote",
- "https://remote.mcp.server/sse",
- "--header",
- "Authorization: Bearer ${AUTH_TOKEN}"
- ]
+ "args": ["mcp-remote", "https://remote.mcp.server/sse", "--header", "Authorization: Bearer ${AUTH_TOKEN}"]
},
"env": {
"AUTH_TOKEN": "..."
@@ -74,7 +66,7 @@ To bypass authentication, or to emit custom headers on all requests to your remo
### Flags
-* If `npx` is producing errors, consider adding `-y` as the first argument to auto-accept the installation of the `mcp-remote` package.
+- If `npx` is producing errors, consider adding `-y` as the first argument to auto-accept the installation of the `mcp-remote` package.
```json
"command": "npx",
@@ -85,7 +77,7 @@ To bypass authentication, or to emit custom headers on all requests to your remo
]
```
-* To force `npx` to always check for an updated version of `mcp-remote`, add the `@latest` flag:
+- To force `npx` to always check for an updated version of `mcp-remote`, add the `@latest` flag:
```json
"args": [
@@ -94,7 +86,7 @@ To bypass authentication, or to emit custom headers on all requests to your remo
]
```
-* To change which port `mcp-remote` listens for an OAuth redirect (by default `3334`), add an additional argument after the server URL. Note that whatever port you specify, if it is unavailable an open port will be chosen at random.
+- To change which port `mcp-remote` listens for an OAuth redirect (by default `3334`), add an additional argument after the server URL. Note that whatever port you specify, if it is unavailable an open port will be chosen at random.
```json
"args": [
@@ -104,7 +96,7 @@ To bypass authentication, or to emit custom headers on all requests to your remo
]
```
-* To allow HTTP connections in trusted private networks, add the `--allow-http` flag. Note: This should only be used in secure private networks where traffic cannot be intercepted.
+- To allow HTTP connections in trusted private networks, add the `--allow-http` flag. Note: This should only be used in secure private networks where traffic cannot be intercepted.
```json
"args": [
@@ -120,8 +112,8 @@ To bypass authentication, or to emit custom headers on all requests to your remo
In order to add an MCP server to Claude Desktop you need to edit the configuration file located at:
-* macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
-* Windows: `%APPDATA%\Claude\claude_desktop_config.json`
+- macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
+- Windows: `%APPDATA%\Claude\claude_desktop_config.json`
If it does not exist yet, [you may need to enable it under Settings > Developer](https://modelcontextprotocol.io/quickstart/user#2-add-the-filesystem-mcp-server).
@@ -143,16 +135,16 @@ As of version `0.48.0`, Cursor supports unauthed SSE servers directly. If your M
For instructions on building & deploying remote MCP servers, including acting as a valid OAuth client, see the following resources:
-* https://developers.cloudflare.com/agents/guides/remote-mcp-server/
+- https://developers.cloudflare.com/agents/guides/remote-mcp-server/
In particular, see:
-* https://github.com/cloudflare/workers-oauth-provider for defining an MCP-comlpiant OAuth server in Cloudflare Workers
-* https://github.com/cloudflare/agents/tree/main/examples/mcp for defining an `McpAgent` using the [`agents`](https://npmjs.com/package/agents) framework.
+- https://github.com/cloudflare/workers-oauth-provider for defining an MCP-comlpiant OAuth server in Cloudflare Workers
+- https://github.com/cloudflare/agents/tree/main/examples/mcp for defining an `McpAgent` using the [`agents`](https://npmjs.com/package/agents) framework.
For more information about testing these servers, see also:
-* https://developers.cloudflare.com/agents/guides/test-remote-mcp-server/
+- https://developers.cloudflare.com/agents/guides/test-remote-mcp-server/
Know of more resources you'd like to share? Please add them to this Readme and send a PR!
@@ -170,7 +162,7 @@ Then restarting your MCP client.
### Check your Node version
-Make sure that the version of Node you have installed is [18 or
+Make sure that the version of Node you have installed is [18 or
higher](https://modelcontextprotocol.io/quickstart/server). Claude
Desktop will use your system version of Node, even if you have a newer
version installed elsewhere.
@@ -187,13 +179,10 @@ this might look like:
```json
{
- "mcpServers": {
+ "mcpServers": {
"remote-example": {
"command": "npx",
- "args": [
- "mcp-remote",
- "https://remote.mcp.server/sse"
- ],
+ "args": ["mcp-remote", "https://remote.mcp.server/sse"],
"env": {
"NODE_EXTRA_CA_CERTS": "{your CA certificate file path}.pem"
}
@@ -204,10 +193,10 @@ this might look like:
### Check the logs
-* [Follow Claude Desktop logs in real-time](https://modelcontextprotocol.io/docs/tools/debugging#debugging-in-claude-desktop)
-* MacOS / Linux:
`tail -n 20 -F ~/Library/Logs/Claude/mcp*.log`
-* For bash on WSL:
`tail -n 20 -f "C:\Users\YourUsername\AppData\Local\Claude\Logs\mcp.log"`
-* Powershell:
`Get-Content "C:\Users\YourUsername\AppData\Local\Claude\Logs\mcp.log" -Wait -Tail 20`
+- [Follow Claude Desktop logs in real-time](https://modelcontextprotocol.io/docs/tools/debugging#debugging-in-claude-desktop)
+- MacOS / Linux:
`tail -n 20 -F ~/Library/Logs/Claude/mcp*.log`
+- For bash on WSL:
`tail -n 20 -f "C:\Users\YourUsername\AppData\Local\Claude\Logs\mcp.log"`
+- Powershell:
`Get-Content "C:\Users\YourUsername\AppData\Local\Claude\Logs\mcp.log" -Wait -Tail 20`
## Debugging
diff --git a/package.json b/package.json
index 47cfa99..6eb6f3f 100644
--- a/package.json
+++ b/package.json
@@ -28,7 +28,7 @@
"check": "prettier --check . && tsc"
},
"dependencies": {
- "@modelcontextprotocol/sdk": "^1.9.0",
+ "@modelcontextprotocol/sdk": "^1.10.2",
"express": "^4.21.2",
"open": "^10.1.0"
},
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index e550c3e..ad22d61 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -9,8 +9,8 @@ importers:
.:
dependencies:
'@modelcontextprotocol/sdk':
- specifier: ^1.9.0
- version: 1.9.0
+ specifier: ^1.10.2
+ version: 1.10.2
express:
specifier: ^4.21.2
version: 4.21.2
@@ -217,8 +217,8 @@ packages:
'@jridgewell/trace-mapping@0.3.25':
resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==}
- '@modelcontextprotocol/sdk@1.9.0':
- resolution: {integrity: sha512-Jq2EUCQpe0iyO5FGpzVYDNFR6oR53AIrwph9yWl7uSc7IWUMsrmpmSaTGra5hQNunXpM+9oit85p924jWuHzUA==}
+ '@modelcontextprotocol/sdk@1.10.2':
+ resolution: {integrity: sha512-rb6AMp2DR4SN+kc6L1ta2NCpApyA9WYNx3CrTSZvGxq9wH71bRur+zRqPfg0vQ9mjywR7qZdX2RGHOPq3ss+tA==}
engines: {node: '>=18'}
'@pkgjs/parseargs@0.11.0':
@@ -1238,7 +1238,7 @@ snapshots:
'@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.5.0
- '@modelcontextprotocol/sdk@1.9.0':
+ '@modelcontextprotocol/sdk@1.10.2':
dependencies:
content-type: 1.0.5
cors: 2.8.5
diff --git a/src/client.ts b/src/client.ts
index d620884..477c86d 100644
--- a/src/client.ts
+++ b/src/client.ts
@@ -2,7 +2,7 @@
/**
* MCP Client with OAuth support
- * A command-line client that connects to an MCP server using SSE with OAuth authentication.
+ * A command-line client that connects to an MCP server using StreamableHTTP with OAuth authentication.
*
* Run with: npx tsx client.ts https://example.remote/server [callback-port]
*
@@ -11,7 +11,7 @@
import { EventEmitter } from 'events'
import { Client } from '@modelcontextprotocol/sdk/client/index.js'
-import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js'
+import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'
import { ListResourcesResultSchema, ListToolsResultSchema } from '@modelcontextprotocol/sdk/types.js'
import { UnauthorizedError } from '@modelcontextprotocol/sdk/client/auth.js'
import { NodeOAuthClientProvider } from './lib/node-oauth-client-provider'
@@ -60,7 +60,16 @@ async function runClient(serverUrl: string, callbackPort: number, headers: Recor
// Create the transport factory
const url = new URL(serverUrl)
function initTransport() {
- const transport = new SSEClientTransport(url, { authProvider, requestInit: { headers } })
+ const transport = new StreamableHTTPClientTransport(url, {
+ authProvider,
+ requestInit: { headers },
+ reconnectionOptions: {
+ initialReconnectionDelay: 1000,
+ maxReconnectionDelay: 10000,
+ reconnectionDelayGrowFactor: 1.5,
+ maxRetries: 10,
+ },
+ })
// Set up message and error handlers
transport.onmessage = (message) => {
diff --git a/src/lib/utils.ts b/src/lib/utils.ts
index 40c744d..bead62f 100644
--- a/src/lib/utils.ts
+++ b/src/lib/utils.ts
@@ -1,5 +1,5 @@
import { OAuthClientProvider, UnauthorizedError } from '@modelcontextprotocol/sdk/client/auth.js'
-import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js'
+import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'
import { Transport } from '@modelcontextprotocol/sdk/shared/transport.js'
import { OAuthCallbackServerOptions } from './types'
import express from 'express'
@@ -65,13 +65,13 @@ export function mcpProxy({ transportToClient, transportToServer }: { transportTo
}
/**
- * Creates and connects to a remote SSE server with OAuth authentication
+ * Creates and connects to a remote server with OAuth authentication
* @param serverUrl The URL of the remote server
* @param authProvider The OAuth client provider
* @param headers Additional headers to send with the request
* @param waitForAuthCode Function to wait for the auth code
* @param skipBrowserAuth Whether to skip browser auth and use shared auth
- * @returns The connected SSE client transport
+ * @returns The connected StreamableHTTP client transport
*/
export async function connectToRemoteServer(
serverUrl: string,
@@ -79,31 +79,19 @@ export async function connectToRemoteServer(
headers: Record,
waitForAuthCode: () => Promise,
skipBrowserAuth: boolean = false,
-): Promise {
+): Promise {
log(`[${pid}] Connecting to remote server: ${serverUrl}`)
const url = new URL(serverUrl)
- // Create transport with eventSourceInit to pass Authorization header if present
- const eventSourceInit = {
- fetch: (url: string | URL, init?: RequestInit) => {
- return Promise.resolve(authProvider?.tokens?.()).then((tokens) =>
- fetch(url, {
- ...init,
- headers: {
- ...(init?.headers as Record | undefined),
- ...headers,
- ...(tokens?.access_token ? { Authorization: `Bearer ${tokens.access_token}` } : {}),
- Accept: "text/event-stream",
- } as Record,
- })
- );
- },
- };
-
- const transport = new SSEClientTransport(url, {
+ const transport = new StreamableHTTPClientTransport(url, {
authProvider,
requestInit: { headers },
- eventSourceInit,
+ reconnectionOptions: {
+ initialReconnectionDelay: 1000,
+ maxReconnectionDelay: 10000,
+ reconnectionDelayGrowFactor: 1.5,
+ maxRetries: 10,
+ },
})
try {
@@ -126,7 +114,16 @@ export async function connectToRemoteServer(
await transport.finishAuth(code)
// Create a new transport after auth
- const newTransport = new SSEClientTransport(url, { authProvider, requestInit: { headers } })
+ const newTransport = new StreamableHTTPClientTransport(url, {
+ authProvider,
+ requestInit: { headers },
+ reconnectionOptions: {
+ initialReconnectionDelay: 1000,
+ maxReconnectionDelay: 10000,
+ reconnectionDelayGrowFactor: 1.5,
+ maxRetries: 10,
+ },
+ })
await newTransport.start()
log('Connected to remote server after authentication')
return newTransport
diff --git a/src/proxy.ts b/src/proxy.ts
index 9fd87d1..207a866 100644
--- a/src/proxy.ts
+++ b/src/proxy.ts
@@ -2,7 +2,7 @@
/**
* MCP Proxy with OAuth support
- * A bidirectional proxy between a local STDIO MCP server and a remote SSE server with OAuth authentication.
+ * A bidirectional proxy between a local STDIO MCP server and a remote server with OAuth authentication.
*
* Run with: npx tsx proxy.ts https://example.remote/server [callback-port]
*
@@ -59,7 +59,7 @@ async function runProxy(serverUrl: string, callbackPort: number, headers: Record
// Start the local STDIO server
await localTransport.start()
log('Local STDIO server running')
- log('Proxy established successfully between local STDIO and remote SSE')
+ log('Proxy established successfully between local STDIO and remote server')
log('Press Ctrl+C to exit')
// Setup cleanup handler