Tired of MCP clients bogged down by dependencies, complex OOP structures, and Node.js legacy? MCP Client Plugin is the antidote.
Built from the ground up with a Bun-first, zero-dependency, functional programming mindset, this plugin provides a refreshingly modern and efficient way to interact with Model Context Protocol servers and manage their processes seamlessly.
This plugin stands out by offering:
- 💧 Pure Functional & Immutable Core: Predictable state management, easier reasoning, and pure functions eliminate side effects common in OOP. No classes, no
this
. - 🚫 Truly Zero Runtime Dependencies: Relies exclusively on built-in Bun APIs (process spawning, fetch/SSE, streams). No
node_modules
baggage for core functionality. - 🔥 Bun-Native Performance & Mindset: Designed and optimized for Bun's blazing-fast runtime, leveraging its speed and integrated tooling.
- ⚙️ Integrated Server Process Management: Fluently start, stop, communicate with, and manage MCP server lifecycles (Stdio transport) directly within the client manager API. No separate manager needed!
- 🌐 Multi-Transport Support: Seamlessly handle both
stdio
(for local processes) andsse
(HTTP Server-Sent Events) transports. - ⛓️ Fluent, Chainable API: Inspired by ElysiaJS, manage servers and access client APIs with elegant method chaining (
.use()
). - 🔒 Type-Safe by Design: Strong TypeScript definitions throughout, avoiding
any
orunknown
for robust development. - 💨 Lightweight & Direct: Minimal abstraction layers between your code and the MCP communication.
- 🎯 Exceptional DX: Strongly typed, predictable API designed to get you productive fast.
Feature | ✨ MCP Client Plugin | Typical SDK Wrappers | Foundational Clients / SDKs |
---|---|---|---|
Dependencies | ✨ Zero Runtime! | SDK + Helpers (Zod, etc.) | SDK Core / Specific Libs |
Paradigm | 💧 Pure Functional | Often OOP/Mixed | Often OOP/Class-based |
State Management | 🧊 Immutable | Internal/Mutable | Internal/Mutable |
Process Management | ✅ Integrated (Stdio) | Usually Separate | Usually Separate / Manual |
Transport Handling | 🚀 Bun Native (Stdio/SSE) | Relies on SDK/Node APIs | Relies on SDK/Node APIs |
API Style | 🔗 Fluent Chaining | Method-based | Method-based |
Runtime | 🔥 Bun Optimized | Node.js (May work in Bun) | Node.js (May work in Bun) |
Bundle Size | 🤏 Tiny | Small -> Medium | Medium |
bun add mcp-client-plugin
# or
npm install mcp-client-plugin
# or
yarn add mcp-client-plugin
Get up and running in seconds!
// example.ts
import { manager } from 'mcp-client-plugin'; // Assuming installed package name
import type { ManagerConfig, NotificationHandler } from 'mcp-client-plugin';
// 1. Define your server configurations
const config: ManagerConfig = {
// An MCP server communicating via standard I/O
memoryServer: {
transport: {
type: 'stdio',
command: 'bunx', // Use bunx to run package executables
args: ['@modelcontextprotocol/server-memory'], // Example command
// env: { API_KEY: '...' } // Optional environment variables
},
},
// Add another server (e.g., filesystem)
fileSystem: {
transport: {
type: 'stdio',
command: 'bunx',
args: ['@modelcontextprotocol/server-filesystem', '.'], // Allow access to current dir
}
},
// Example SSE server (if you have one running)
// searchServer: {
// transport: {
// type: 'sse',
// url: 'http://localhost:3001/mcp', // URL for POST and SSE endpoint
// // headers: { 'Authorization': 'Bearer ...' } // Optional headers
// }
// }
};
// 2. Optional: Define a handler for server notifications
const handleNotifications: NotificationHandler = (serverName, notification) => {
console.log(`🔔 Notification [${serverName}]: ${notification.method}`, notification.params ?? '');
};
// 3. Initialize the manager and connect to servers using the chainable API
async function run() {
console.log('🚀 Initializing MCP Manager...');
try {
const mcpManager = await manager(config, { onNotification: handleNotifications })
.use('memoryServer') // Connect to the memory server
.use('fileSystem'); // Connect to the filesystem server
// .use('searchServer') // Connect to the SSE server
console.log('✅ Servers Connected & Ready!');
// 4. Get client APIs for specific servers
const memory = mcpManager.getClient('memoryServer');
const fs = mcpManager.getClient('fileSystem');
// 5. Interact with the servers!
if (memory) {
console.log('\n🧠 Querying Memory Server...');
const memTools = await memory.listTools();
console.log('Memory Tools:', memTools.map(t => t.name));
// Example: const result = await memory.callTool('createEntity', { ... });
}
if (fs) {
console.log('\n📁 Querying Filesystem Server...');
const fsResources = await fs.listResources();
console.log('FS Resources:', fsResources.map(r => r.uri));
// Example: const content = await fs.readResource('file:///./README.md');
// console.log('README content length:', content.length);
}
// ... Your agent logic here ...
// 6. Disconnect all servers when done
console.log('\n🔌 Disconnecting all servers...');
await mcpManager.disconnectAll();
console.log('✅ Disconnected.');
} catch (error) {
console.error('❌ MCP Manager Error:', error);
}
}
run();
Adding new MCP servers is straightforward. Just update the ManagerConfig
object passed to the manager
function.
The ManagerConfig
is an object where keys are your chosen server names (e.g., memoryServer
, myApiTool
) and values are ServerConfig
objects.
Each ServerConfig
requires a transport
property:
Use this for MCP servers running as local processes that communicate via stdin
/stdout
.
import type { ManagerConfig } from 'mcp-client-plugin';
const config: ManagerConfig = {
myStdioServer: {
transport: {
type: 'stdio',
command: 'bun', // The command to execute (e.g., 'bun', 'node', 'python', 'my_server_binary')
args: ['run', 'start-mcp-server.js', '--port', '8080'], // Arguments for the command
env: { // Optional: Environment variables for the process
API_KEY: Bun.env.MY_API_KEY,
LOG_LEVEL: 'debug',
},
cwd: '/path/to/server/working/directory', // Optional: Working directory
},
// requiredCapabilities: { ... } // Optional: Specify expected capabilities
},
// ... other servers
};
Use this for MCP servers accessible via HTTP, using Server-Sent Events for server-to-client communication and HTTP POST for client-to-server requests.
import type { ManagerConfig } from 'mcp-client-plugin';
const config: ManagerConfig = {
myRemoteServer: {
transport: {
type: 'sse',
// The *single* URL for both the SSE connection and POST requests
url: 'https://my-mcp-server.com/api/mcp',
headers: { // Optional: Headers for both SSE connection and POST requests
'Authorization': `Bearer ${Bun.env.REMOTE_API_TOKEN}`,
'X-Client-Version': '1.0.0',
},
},
// requiredCapabilities: { ... } // Optional
},
// ... other servers
};
- Initializes the manager.
-
config
: TheManagerConfig
object defining your servers. -
options
: Optional configuration:-
onNotification
:(serverName, notification) => void
- Callback for handling server-sent notifications. -
requestTimeoutMs
:number
- Default timeout for requests (default: 30000ms).
-
-
Returns:
Promise<ManagerAPI>
- A promise resolving to the manager API object.
The object returned by manager()
and .use()
.
-
.use(serverName: string):
- Connects to the specified server (spawns process if
stdio
). - Sends
initialize
request. -
Returns:
Promise<ManagerAPI>
- A new ManagerAPI instance representing the updated state, allowing chaining.
- Connects to the specified server (spawns process if
-
.getClient(serverName: string):
- Retrieves the
ClientAPI
for an already connected server. -
Returns:
ClientAPI | undefined
.
- Retrieves the
-
.disconnectAll():
- Disconnects all active clients and terminates their associated processes/connections.
-
Returns:
Promise<void>
.
- ._getState(): Internal use/debugging only. Returns the current immutable state object.
The object returned by manager.getClient()
. Contains methods to interact with a specific MCP server.
-
.getCapabilities()
: Returns server capabilities. -
.callTool(name, params)
: Calls a tool. -
.listTools()
: Lists tools. -
.readResource(uri)
: Reads a resource. -
.listResources()
: Lists resources. -
.listPrompts()
: Lists prompts. -
.getPrompt(name, args?)
: Gets a prompt template. -
.ping()
: Checks connectivity. -
.disconnect()
: Disconnects this specific client/server.
(Refer to src/types.ts
for detailed parameter and return types)
Provide an onNotification
callback in the manager
options to react to notifications sent by servers (e.g., $/progress
, resource changes).
const handleNotifications: NotificationHandler = (serverName, notification) => {
if (notification.method === '$/progress') {
console.log(`Progress from ${serverName}:`, notification.params);
} else {
console.log(`Notification [${serverName}]: ${notification.method}`);
}
};
const mcpManager = await manager(config, { onNotification: handleNotifications })
// ... .use() calls
- Requests automatically time out based on
requestTimeoutMs
in options. - Errors during connection, communication, or from the server will reject the corresponding promises (e.g.,
.use()
,.callTool()
). - Use standard
try...catch
blocks aroundawait
calls. - Unhandled transport or process errors are logged to the console. Implement robust error handling appropriate for your application.
Contributions are welcome! Feel free to open issues or submit Pull Requests.
- Fork the repository.
- Create your feature branch (
git checkout -b feature/AmazingFeature
). - Commit your changes (
git commit -m 'Add some AmazingFeature'
). - Push to the branch (
git push origin feature/AmazingFeature
). - Open a Pull Request.
This project is licensed under the MIT License - see the LICENSE file for details.