Skip to content
Open
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
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Changelog

## [0.3.1] - 2026-02-03

### Added

- **SSE Transport Support** - Support for Server-Sent Events transport protocol
- Requires `type: "sse"` in server config
- Enables connection to MCP servers using SSE (e.g. LangChain, remote servers)

## [0.3.0] - 2026-01-22

### Added
Expand Down
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,24 @@ The CLI uses `mcp_servers.json`, compatible with Claude Desktop, Gemini or VS Co
}
```

### SSE Transport (Legacy)

For servers using the deprecated SSE transport protocol, add `"type": "sse"` to the configuration:

```json
{
"mcpServers": {
"legacy-server": {
"url": "http://localhost:8000",
"type": "sse"
}
}
}
```

> [!NOTE]
> SSE transport is deprecated. New servers should use Streamable HTTP transport.

**Environment Variable Substitution:** Use `${VAR_NAME}` syntax anywhere in the config. Values are substituted at load time. By default, missing environment variables cause an error with a clear message. Set `MCP_STRICT_ENV=false` to use empty values instead (with a warning).

### Tool Filtering
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "mcp-cli",
"version": "0.3.0",
"version": "0.3.1",
"description": "A lightweight CLI for interacting with MCP (Model Context Protocol) servers",
"type": "module",
"main": "src/index.ts",
Expand Down
32 changes: 29 additions & 3 deletions src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@
*/

import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
import type { Tool } from '@modelcontextprotocol/sdk/types.js';
import {
type HttpServerConfig,
type ServerConfig,
type SseServerConfig,
type StdioServerConfig,
debug,
filterTools,
Expand All @@ -18,6 +20,7 @@ import {
getTimeoutMs,
isDaemonEnabled,
isHttpServer,
isSseServer,
isToolAllowed,
} from './config.js';
import {
Expand Down Expand Up @@ -236,9 +239,14 @@ export async function connectToServer(
},
);

let transport: StdioClientTransport | StreamableHTTPClientTransport;
let transport:
| StdioClientTransport
| StreamableHTTPClientTransport
| SSEClientTransport;

if (isHttpServer(config)) {
if (isSseServer(config)) {
transport = createSseTransport(config);
} else if (isHttpServer(config)) {
transport = createHttpTransport(config);
} else {
transport = createStdioTransport(config);
Expand Down Expand Up @@ -269,7 +277,7 @@ export async function connectToServer(
}

// For successful connections, forward stderr to console
if (!isHttpServer(config)) {
if (!isHttpServer(config) && !isSseServer(config)) {
const stderrStream = (transport as StdioClientTransport).stderr;
if (stderrStream) {
stderrStream.on('data', (chunk: Buffer) => {
Expand Down Expand Up @@ -302,6 +310,24 @@ function createHttpTransport(
});
}

/**
* Create SSE transport for remote servers
*/
function createSseTransport(config: SseServerConfig): SSEClientTransport {
// SSE transport expects the URL to end with /sse
let urlStr = config.url;
if (!urlStr.endsWith('/sse')) {
urlStr = `${urlStr.replace(/\/$/, '')}/sse`;
}
const url = new URL(urlStr);

return new SSEClientTransport(url, {
requestInit: {
headers: config.headers,
},
});
}

/**
* Create stdio transport for local servers
* Uses stderr: 'pipe' to capture server output for debugging
Expand Down
24 changes: 22 additions & 2 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,20 @@ export interface HttpServerConfig extends BaseServerConfig {
timeout?: number;
}

export type ServerConfig = StdioServerConfig | HttpServerConfig;
/**
* SSE server configuration (remote) - uses SSE transport
*/
export interface SseServerConfig extends BaseServerConfig {
url: string;
headers?: Record<string, string>;
timeout?: number;
type: 'sse';
}

export type ServerConfig =
| StdioServerConfig
| HttpServerConfig
| SseServerConfig;

export interface McpServersConfig {
mcpServers: Record<string, ServerConfig>;
Expand Down Expand Up @@ -143,11 +156,18 @@ export function isToolAllowed(toolName: string, config: ServerConfig): boolean {
return true;
}

/**
* Check if a server config is SSE-based
*/
export function isSseServer(config: ServerConfig): config is SseServerConfig {
return 'url' in config && (config as SseServerConfig).type === 'sse';
}

/**
* Check if a server config is HTTP-based
*/
export function isHttpServer(config: ServerConfig): config is HttpServerConfig {
return 'url' in config;
return 'url' in config && !isSseServer(config);
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/version.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
* Version constant - single source of truth
* This file is auto-updated by scripts/release.sh
*/
export const VERSION = '0.3.0';
export const VERSION = '0.3.1';
31 changes: 31 additions & 0 deletions tests/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
getServerConfig,
listServerNames,
isHttpServer,
isSseServer,
isStdioServer,
} from '../src/config';

Expand Down Expand Up @@ -42,6 +43,25 @@ describe('config', () => {
expect((config.mcpServers.test as any).command).toBe('echo');
});

test('loads server with sse transport', async () => {
const configPath = join(tempDir, 'sse_config.json');
await writeFile(
configPath,
JSON.stringify({
mcpServers: {
sse: {
url: 'http://localhost:3000/sse',
type: 'sse',
},
},
}),
);

const config = await loadConfig(configPath);
expect(config.mcpServers.sse).toBeDefined();
expect((config.mcpServers.sse as any).type).toBe('sse');
});

test('throws on missing config file', async () => {
const configPath = join(tempDir, 'nonexistent.json');
await expect(loadConfig(configPath)).rejects.toThrow('not found');
Expand Down Expand Up @@ -233,6 +253,17 @@ describe('config', () => {
test('isHttpServer identifies HTTP config', () => {
expect(isHttpServer({ url: 'https://example.com' })).toBe(true);
expect(isHttpServer({ command: 'echo' })).toBe(false);
expect(isHttpServer({ url: 'https://example.com', type: 'sse' })).toBe(
false,
);
});

test('isSseServer identifies SSE config', () => {
expect(isSseServer({ url: 'https://example.com', type: 'sse' })).toBe(
true,
);
expect(isSseServer({ url: 'https://example.com' })).toBe(false);
expect(isSseServer({ command: 'echo' })).toBe(false);
});

test('isStdioServer identifies stdio config', () => {
Expand Down