dmcp
v0.0.14
Published
<div class="oranda-hide">
Downloads
4
Readme
🦑 dmcp 🦑
A TypeScript/JavaScript library for connecting to Model Context Protocol (MCP) servers. Provides OAuth authentication components and a React hook for easy integration with AI systems implementing the MCP standard.
Try it out: MCP Inspector | Cloudflare Workers AI Playground
Installation
npm install dmcp
# or
pnpm add dmcp
# or
yarn add dmcpWhat's Included
- 🔐 OAuth Authentication Components - Framework-agnostic browser OAuth flow handling
- ⚛️ React Hook - Complete MCP client with connection management, tool calling, and state management
- 🧰 TypeScript Types - Full type safety and editor support
Framework Support
| Framework | OAuth Components | MCP Client | Status | |-----------|:----------------:|:----------:|:------:| | React | ✅ | ✅ | Full Support | | Vue.js | ✅ | ❌ | OAuth Only | | Angular | ✅ | ❌ | OAuth Only | | Svelte | ✅ | ❌ | OAuth Only | | Vanilla JS | ✅ | ❌ | OAuth Only |
Note: Currently, only React has a complete MCP client implementation. Other frameworks can use the OAuth components to handle authentication, but you'll need to implement the MCP client logic using the @modelcontextprotocol/sdk directly.
Quick Start
React (Complete MCP Client)
import { useMcp } from 'dmcp/react'
function MyAIComponent() {
const {
state, // Connection state
tools, // Available tools from MCP server
error, // Error message if connection failed
callTool, // Function to call tools
retry, // Retry connection
authenticate, // Manual authentication trigger
clearStorage, // Clear stored credentials
} = useMcp({
url: 'https://your-mcp-server.com',
clientName: 'My App',
autoReconnect: true,
})
// Handle different connection states
if (state === 'failed') {
return (
<div>
<p>Connection failed: {error}</p>
<button onClick={retry}>Retry</button>
<button onClick={authenticate}>Authenticate Manually</button>
</div>
)
}
if (state !== 'ready') {
return <div>Connecting to AI service...</div>
}
// Use available tools
const handleSearch = async () => {
try {
const result = await callTool('search', { query: 'example search' })
console.log('Search results:', result)
} catch (err) {
console.error('Tool call failed:', err)
}
}
return (
<div>
<h2>Available Tools: {tools.length}</h2>
<ul>
{tools.map(tool => (
<li key={tool.name}>{tool.name}</li>
))}
</ul>
<button onClick={handleSearch}>Search</button>
</div>
)
}Other Frameworks (OAuth Only)
For Vue.js, Angular, Svelte, and vanilla JavaScript, dmcp provides OAuth authentication components. You'll need to implement the MCP client functionality yourself using the @modelcontextprotocol/sdk.
Basic OAuth Setup
import { BrowserOAuthClientProvider, onMcpAuthorization } from 'dmcp'
import { Client } from '@modelcontextprotocol/sdk/client/index.js'
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js'
// Initialize OAuth provider
const authProvider = new BrowserOAuthClientProvider('https://your-mcp-server.com', {
clientName: 'My App',
storageKeyPrefix: 'myapp:auth'
})
// Create MCP client
const client = new Client(
{ name: 'my-app', version: '1.0.0' },
{ capabilities: {} }
)
// Create transport with auth
const transport = new SSEClientTransport(
new URL('https://your-mcp-server.com'),
{ authProvider }
)
// Connect and use
try {
await client.connect(transport)
const tools = await client.request({ method: 'tools/list' })
console.log('Available tools:', tools)
} catch (error) {
console.error('Connection failed:', error)
}OAuth Callback Setup
All frameworks need to set up an OAuth callback route to handle authentication redirects.
React Router
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'
import { useEffect } from 'react'
import { onMcpAuthorization } from 'dmcp'
function OAuthCallback() {
useEffect(() => {
onMcpAuthorization()
}, [])
return (
<div>
<h1>Authenticating...</h1>
<p>This window should close automatically.</p>
</div>
)
}
function App() {
return (
<Router>
<Routes>
<Route path="/oauth/callback" element={<OAuthCallback />} />
<Route path="/" element={<YourMainComponent />} />
</Routes>
</Router>
)
}Next.js Pages Router
// pages/oauth/callback.tsx
import { useEffect } from 'react'
import { onMcpAuthorization } from 'dmcp'
export default function OAuthCallbackPage() {
useEffect(() => {
onMcpAuthorization()
}, [])
return (
<div>
<h1>Authenticating...</h1>
<p>This window should close automatically.</p>
</div>
)
}Next.js App Router
// app/oauth/callback/page.tsx
'use client'
import { useEffect } from 'react'
import { onMcpAuthorization } from 'dmcp'
export default function OAuthCallbackPage() {
useEffect(() => {
onMcpAuthorization()
}, [])
return (
<div>
<h1>Authenticating...</h1>
<p>This window should close automatically.</p>
</div>
)
}Vue.js
<!-- OAuthCallback.vue -->
<template>
<div>
<h1>Authenticating...</h1>
<p>This window should close automatically.</p>
</div>
</template>
<script setup>
import { onMounted } from 'vue'
import { onMcpAuthorization } from 'dmcp'
onMounted(() => {
onMcpAuthorization()
})
</script>// router.js
import { createRouter, createWebHistory } from 'vue-router'
import OAuthCallback from './components/OAuthCallback.vue'
import Home from './components/Home.vue'
const routes = [
{ path: '/', component: Home },
{ path: '/oauth/callback', component: OAuthCallback }
]
export default createRouter({
history: createWebHistory(),
routes
})Angular
// oauth-callback.component.ts
import { Component, OnInit } from '@angular/core'
import { onMcpAuthorization } from 'dmcp'
@Component({
selector: 'app-oauth-callback',
template: `
<div>
<h1>Authenticating...</h1>
<p>This window should close automatically.</p>
</div>
`
})
export class OAuthCallbackComponent implements OnInit {
ngOnInit() {
onMcpAuthorization()
}
}// app-routing.module.ts
import { NgModule } from '@angular/core'
import { RouterModule, Routes } from '@angular/router'
import { OAuthCallbackComponent } from './oauth-callback.component'
const routes: Routes = [
{ path: 'oauth/callback', component: OAuthCallbackComponent },
// other routes...
]
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }Svelte/SvelteKit
<!-- src/routes/oauth/callback/+page.svelte -->
<script>
import { onMount } from 'svelte'
import { onMcpAuthorization } from 'dmcp'
onMount(() => {
onMcpAuthorization()
})
</script>
<div>
<h1>Authenticating...</h1>
<p>This window should close automatically.</p>
</div>Vanilla JavaScript
<!-- /oauth/callback.html -->
<!DOCTYPE html>
<html>
<head>
<title>OAuth Callback</title>
</head>
<body>
<h1>Authenticating...</h1>
<p>This window should close automatically.</p>
<script type="module">
import { onMcpAuthorization } from 'dmcp'
onMcpAuthorization()
</script>
</body>
</html>Framework-Specific Examples
Vue.js MCP Integration
<template>
<div>
<div v-if="connecting">Connecting to MCP server...</div>
<div v-else-if="error" class="error">
Error: {{ error }}
<button @click="reconnect">Retry</button>
</div>
<div v-else>
<h2>Available Tools: {{ tools.length }}</h2>
<button @click="callExampleTool">Call Tool</button>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
import { BrowserOAuthClientProvider } from 'dmcp'
import { Client } from '@modelcontextprotocol/sdk/client/index.js'
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js'
const tools = ref([])
const error = ref(null)
const connecting = ref(true)
let client = null
let transport = null
const initMcp = async () => {
try {
const authProvider = new BrowserOAuthClientProvider('https://your-mcp-server.com', {
clientName: 'Vue MCP App'
})
client = new Client(
{ name: 'vue-mcp-client', version: '1.0.0' },
{ capabilities: {} }
)
transport = new SSEClientTransport(
new URL('https://your-mcp-server.com'),
{ authProvider }
)
await client.connect(transport)
const toolsResponse = await client.request({ method: 'tools/list' })
tools.value = toolsResponse.tools
error.value = null
} catch (err) {
error.value = err.message
} finally {
connecting.value = false
}
}
const callExampleTool = async () => {
if (!client || tools.value.length === 0) return
try {
const result = await client.request({
method: 'tools/call',
params: {
name: tools.value[0].name,
arguments: {}
}
})
console.log('Tool result:', result)
} catch (err) {
console.error('Tool call failed:', err)
}
}
const reconnect = () => {
connecting.value = true
error.value = null
initMcp()
}
onMounted(initMcp)
onUnmounted(() => {
if (transport) {
transport.close()
}
})
</script>
<style>
.error {
color: red;
padding: 10px;
border: 1px solid red;
border-radius: 4px;
margin: 10px 0;
}
</style>Angular MCP Service
// mcp.service.ts
import { Injectable } from '@angular/core'
import { BehaviorSubject, Observable } from 'rxjs'
import { BrowserOAuthClientProvider } from 'dmcp'
import { Client } from '@modelcontextprotocol/sdk/client/index.js'
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js'
export interface McpState {
connected: boolean
tools: any[]
error: string | null
loading: boolean
}
@Injectable({
providedIn: 'root'
})
export class McpService {
private client: Client | null = null
private transport: any = null
private stateSubject = new BehaviorSubject<McpState>({
connected: false,
tools: [],
error: null,
loading: false
})
public state$: Observable<McpState> = this.stateSubject.asObservable()
async connect(serverUrl: string, clientName: string = 'Angular MCP App') {
this.updateState({ loading: true, error: null })
try {
const authProvider = new BrowserOAuthClientProvider(serverUrl, {
clientName
})
this.client = new Client(
{ name: 'angular-mcp-client', version: '1.0.0' },
{ capabilities: {} }
)
this.transport = new SSEClientTransport(
new URL(serverUrl),
{ authProvider }
)
await this.client.connect(this.transport)
const toolsResponse = await this.client.request({ method: 'tools/list' })
this.updateState({
connected: true,
tools: toolsResponse.tools,
loading: false,
error: null
})
} catch (error) {
this.updateState({
connected: false,
loading: false,
error: error instanceof Error ? error.message : 'Connection failed'
})
}
}
async callTool(name: string, args: any = {}) {
if (!this.client) {
throw new Error('Not connected to MCP server')
}
return await this.client.request({
method: 'tools/call',
params: { name, arguments: args }
})
}
disconnect() {
if (this.transport) {
this.transport.close()
}
this.updateState({
connected: false,
tools: [],
error: null,
loading: false
})
}
private updateState(partialState: Partial<McpState>) {
const currentState = this.stateSubject.value
this.stateSubject.next({ ...currentState, ...partialState })
}
}// mcp.component.ts
import { Component, OnInit, OnDestroy } from '@angular/core'
import { McpService, McpState } from './mcp.service'
import { Observable } from 'rxjs'
@Component({
selector: 'app-mcp',
template: `
<div *ngIf="state$ | async as state">
<div *ngIf="state.loading">Connecting to MCP server...</div>
<div *ngIf="state.error" class="error">
Error: {{ state.error }}
<button (click)="connect()">Retry</button>
</div>
<div *ngIf="state.connected">
<h2>Available Tools: {{ state.tools.length }}</h2>
<ul>
<li *ngFor="let tool of state.tools">
{{ tool.name }}
<button (click)="callTool(tool.name)">Call</button>
</li>
</ul>
<button (click)="disconnect()">Disconnect</button>
</div>
</div>
`,
styles: [`
.error {
color: red;
padding: 10px;
border: 1px solid red;
border-radius: 4px;
margin: 10px 0;
}
`]
})
export class McpComponent implements OnInit, OnDestroy {
state$: Observable<McpState>
constructor(private mcpService: McpService) {
this.state$ = this.mcpService.state$
}
ngOnInit() {
this.connect()
}
ngOnDestroy() {
this.mcpService.disconnect()
}
connect() {
this.mcpService.connect('https://your-mcp-server.com')
}
disconnect() {
this.mcpService.disconnect()
}
async callTool(toolName: string) {
try {
const result = await this.mcpService.callTool(toolName)
console.log('Tool result:', result)
} catch (error) {
console.error('Tool call failed:', error)
}
}
}Svelte MCP Store
// lib/mcp.js
import { writable } from 'svelte/store'
import { BrowserOAuthClientProvider } from 'dmcp'
import { Client } from '@modelcontextprotocol/sdk/client/index.js'
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js'
export const mcpState = writable({
connected: false,
tools: [],
error: null,
loading: false
})
let client = null
let transport = null
export async function connectMcp(serverUrl, clientName = 'Svelte MCP App') {
mcpState.update(state => ({ ...state, loading: true, error: null }))
try {
const authProvider = new BrowserOAuthClientProvider(serverUrl, {
clientName
})
client = new Client(
{ name: 'svelte-mcp-client', version: '1.0.0' },
{ capabilities: {} }
)
transport = new SSEClientTransport(
new URL(serverUrl),
{ authProvider }
)
await client.connect(transport)
const toolsResponse = await client.request({ method: 'tools/list' })
mcpState.set({
connected: true,
tools: toolsResponse.tools,
loading: false,
error: null
})
} catch (error) {
mcpState.update(state => ({
...state,
connected: false,
loading: false,
error: error.message
}))
}
}
export async function callTool(name, args = {}) {
if (!client) {
throw new Error('Not connected to MCP server')
}
return await client.request({
method: 'tools/call',
params: { name, arguments: args }
})
}
export function disconnectMcp() {
if (transport) {
transport.close()
}
mcpState.set({
connected: false,
tools: [],
error: null,
loading: false
})
}<!-- McpComponent.svelte -->
<script>
import { onMount, onDestroy } from 'svelte'
import { mcpState, connectMcp, callTool, disconnectMcp } from './lib/mcp.js'
onMount(() => {
connectMcp('https://your-mcp-server.com')
})
onDestroy(() => {
disconnectMcp()
})
async function handleToolCall(toolName) {
try {
const result = await callTool(toolName)
console.log('Tool result:', result)
} catch (error) {
console.error('Tool call failed:', error)
}
}
</script>
<div>
{#if $mcpState.loading}
<div>Connecting to MCP server...</div>
{:else if $mcpState.error}
<div class="error">
Error: {$mcpState.error}
<button on:click={() => connectMcp('https://your-mcp-server.com')}>
Retry
</button>
</div>
{:else if $mcpState.connected}
<div>
<h2>Available Tools: {$mcpState.tools.length}</h2>
<ul>
{#each $mcpState.tools as tool (tool.name)}
<li>
{tool.name}
<button on:click={() => handleToolCall(tool.name)}>
Call
</button>
</li>
{/each}
</ul>
<button on:click={disconnectMcp}>Disconnect</button>
</div>
{/if}
</div>
<style>
.error {
color: red;
padding: 10px;
border: 1px solid red;
border-radius: 4px;
margin: 10px 0;
}
</style>API Reference
useMcp React Hook
import { useMcp } from 'dmcp/react'
function useMcp(options: UseMcpOptions): UseMcpResultOptions
| Option | Type | Description |
|--------|------|-------------|
| url | string | Required. URL of your MCP server |
| clientName | string | Name of your client for OAuth registration |
| clientUri | string | URI of your client for OAuth registration |
| callbackUrl | string | Custom callback URL for OAuth redirect (defaults to /oauth/callback on the current origin) |
| storageKeyPrefix | string | Storage key prefix for OAuth data in localStorage (defaults to "mcp:auth") |
| clientConfig | object | Custom configuration for the MCP client identity |
| debug | boolean | Whether to enable verbose debug logging |
| autoRetry | boolean \| number | Auto retry connection if initial connection fails, with delay in ms |
| autoReconnect | boolean \| number | Auto reconnect if an established connection is lost, with delay in ms (default: 3000) |
| transportType | 'auto' \| 'http' \| 'sse' | Transport type preference: 'auto' (HTTP with SSE fallback), 'http' (HTTP only), 'sse' (SSE only) (default: 'auto') |
Return Value
| Property | Type | Description |
|----------|------|-------------|
| state | string | Current connection state: 'discovering', 'authenticating', 'connecting', 'loading', 'ready', 'failed' |
| tools | Tool[] | Available tools from the MCP server |
| error | string \| undefined | Error message if connection failed |
| authUrl | string \| undefined | Manual authentication URL if popup is blocked |
| log | LogEntry[] | Array of log messages |
| callTool | (name: string, args?: Record<string, unknown>) => Promise<any> | Function to call a tool on the MCP server |
| retry | () => void | Manually attempt to reconnect |
| disconnect | () => void | Disconnect from the MCP server |
| authenticate | () => void | Manually trigger authentication |
| clearStorage | () => void | Clear all stored authentication data |
OAuth Components
The main package exports OAuth authentication components that can be used in any framework:
import { BrowserOAuthClientProvider, onMcpAuthorization } from 'dmcp'BrowserOAuthClientProvider
new BrowserOAuthClientProvider(serverUrl: string, options?: {
storageKeyPrefix?: string
clientName?: string
clientUri?: string
callbackUrl?: string
})onMcpAuthorization
onMcpAuthorization(): Promise<void>Call this function in your OAuth callback route to handle the authentication response.
License
MIT
