Getting Started
Get up and running with Micdrop to build real-time voice conversations with AI agents.
Quick Setup
Client (Browser)
Install the browser package and start capturing voice input:
npm install @micdrop/webIf you’re using React, you can also install @micdrop/react package to get a ready-to-use React hooks.
import { Micdrop } from '@micdrop/web'
// Start a voice conversationMicdrop.start({ url: 'ws://localhost:8081',})
// Listen for eventsMicdrop.on('StateChange', (state) => { console.log('Conversation:', state.conversation) console.log('isAssistantSpeaking:', state.isAssistantSpeaking)})
Micdrop.on('EndCall', () => { console.log('Call ended by assistant')})Client (React Native)
The same call runs on a phone, against the same server:
npm install @micdrop/react-native react-native-audio-apiimport { Micdrop, useMicdropState } from '@micdrop/react-native'
function Call() { const state = useMicdropState()
return ( <Button title={state.isStarted ? 'Hang up' : 'Start the call'} onPress={() => state.isStarted ? Micdrop.stop() : Micdrop.start({ url: 'ws://localhost:8081' }) } /> )}Audio goes through native code there, see React Native for the setup.
Server (Node.js)
Install the server package and set up AI integrations:
npm install @micdrop/server @micdrop/openai @micdrop/gladia @micdrop/elevenlabsThis example uses OpenAI, Gladia and ElevenLabs. You can use other providers with provided or custom integrations.
import { MicdropServer } from '@micdrop/server'import { OpenaiAgent } from '@micdrop/openai'import { GladiaSTT } from '@micdrop/gladia'import { ElevenLabsTTS } from '@micdrop/elevenlabs'import { WebSocketServer } from 'ws'
const wss = new WebSocketServer({ port: 8081 })
wss.on('connection', (socket) => { // Handle voice conversation new MicdropServer(socket, { firstMessage: 'How can I help you today?',
agent: new OpenaiAgent({ apiKey: process.env.OPENAI_API_KEY, systemPrompt: 'You are a helpful assistant', }),
stt: new GladiaSTT({ apiKey: process.env.GLADIA_API_KEY, }),
tts: new ElevenLabsTTS({ apiKey: process.env.ELEVENLABS_API_KEY, voiceId: process.env.ELEVENLABS_VOICE_ID, }), })})Don’t forget to set the API keys in the environment variables.
Examples
To hear Micdrop work, start with the basic example. Every other example app and demo, with the command that runs it, is listed in Examples and demos.
Setup Documentation MCP
To vibe code your Micdrop implementation faster with Cursor, Claude Code or any other agent, install Context7 MCP.
Once the MCP is installed, you can prompt like this:
create a turbo monorepo in current folder with a client and a server in a new /apps folder:- server: a simple fastify server with Typescript that implements a micdrop server (use context7)- client: a simple React webapp with micdrop client. show play/stop/pause buttons and conversation, no other UI. make it beautiful and futuristic.Troubleshooting
Turn on the logs first
Both sides stay quiet by default. On the client, debugLog prints each step of
the call: microphone, WebSocket, reconnections.
Micdrop.start({ url: 'ws://localhost:8081', debugLog: true })
Micdrop.on('Error', (error) => { console.error(error.code, error.message)})On the server, every piece takes a Logger. Errors from the providers (a
rejected API key, a wrong model name, a quota reached) only show up there, so
give one to each piece while you set things up:
import { Logger, MicdropServer } from '@micdrop/server'
const server = new MicdropServer(socket, { agent, stt, tts })server.logger = new Logger('Server')agent.logger = new Logger('Agent')stt.logger = new Logger('STT')tts.logger = new Logger('TTS')The error code the client receives tells you where to look next.
The call stops at once with a Mic error
The microphone was refused, and error.message carries the browser’s reason.
NotAllowedError means the user or the site settings denied access,
NotFoundError means no microphone is plugged in.
Browsers only give the microphone to secure pages, served over HTTPS or from
localhost. Opening your dev server from a phone at http://192.168.1.20:3000
fails for this reason, so go through an HTTPS tunnel instead. On React Native,
the permission has to be declared in the app, see
Installation.
Connection errors, or a client that keeps reconnecting
The client gives the server 5 seconds to answer, then retries every second
with no limit (see the reconnect option). The usual causes:
- The URL misses the route. Behind Fastify or
NestJS, the socket lives on a path such as
ws://localhost:8081/call. - An HTTPS page has to connect with
wss://, browsers blockws://from it. A reverse proxy in front of the server must forward theUpgradeandConnectionheaders. - A phone has its own
localhost, so it needs your machine’s address on the network, as explained in Reaching the server.
InternalServer, Unauthorized, BadRequest or NotFound
These come from the server closing the socket. InternalServer means your
connection handler threw: handleError prints the error in the server console,
most often a missing environment variable. The other three are the
MicdropError your own code throws when it checks the
parameters and authentication, and the client
does not retry them.
The call connects but the assistant stays silent
A provider is failing, and the server logs tell you which one. Follow a turn through them:
- With
firstMessageset, hearing it proves the speech synthesis and the speakers work. Received chunklines show the audio arrives. None while you talk means the client never detected your voice, see the next section.User transcriptshows speech to text works. Its absence points at the STT key or language.Adding assistant messageshows the agent answered. Its absence points at the LLM key, model name or quota.Send audio chunkshows the answer is being spoken. Its absence points at the TTS key or voice ID.
A provider that gives up after its retries emits Failed, and the call stays
open without a word. In production, put a fallback behind each piece:
STT,
agent and
TTS.
The assistant does not hear you, or never stops listening
Voice detection runs in the browser, and the volume detector used by default depends on the room and the microphone. A quiet microphone never crosses the threshold, a noisy room never drops below it. Tune it or switch to Silero, which recognises human speech, as described in VAD. Check also that the right input is selected with device management, and that the call is neither muted nor paused.
The assistant interrupts itself, or you cannot interrupt it
Without echo cancellation, the microphone hears the speakers and the assistant
would answer its own voice. Micdrop then turns interruptions off
(isInterruptionDisabled in the state), and debugLog prints
No echo cancellation, interruptions are disabled. Headphones fix it, and so
does a microphone the browser can cancel echo on (some USB and Bluetooth
headsets lose it).
No sound on Safari or iOS
Browsers keep audio locked until the user interacts with the page. Call
Micdrop.start() from a click or tap handler, never on page load.
Answers come too late
Most of the delay is the silence the client waits for before it ends your turn. Latency breaks down a turn and lists the settings worth changing.