Skip to content

Pairing Agent

import { Aside } from ‘@astrojs/starlight/components’;

Bluetooth pairing establishes trust between devices. mcbluetooth includes a pairing agent that handles all pairing methods automatically.

In BlueZ, a pairing agent is a program that:

  1. Registers with BlueZ’s AgentManager
  2. Receives callbacks during pairing
  3. Responds to PIN/passkey requests
  4. Confirms or rejects pairings

Without an agent, pairing requests fail because there’s no program to handle the negotiation.

Modern Bluetooth uses SSP with four association models:

No user interaction required.

  • Used when one device has no display/keyboard
  • Provides encryption but no MITM protection
  • mcbluetooth auto-accepts these

Example devices: Simple speakers, mice, keyboards

Both devices show 6-digit code; user confirms they match.

Your device: 123456
Their device: 123456
Do they match? [Y/N]
  • Both devices have displays
  • User verifies the numbers match
  • Protects against MITM attacks

Example devices: Phones, computers, smart watches

One device displays code; user enters it on the other.

Their device shows: 123456
Enter on your device: [______]
  • One device has display, other has keyboard
  • Code entry proves physical access

Example devices: Phone pairing with keyboard

Old-style 4-6 digit PIN entry.

Enter PIN: [____]
  • Pre-SSP devices (Bluetooth 2.0 and earlier)
  • Often uses “0000” or “1234”

Example devices: Older headsets, car systems

The bt_pair tool supports three modes:

bt_pair adapter="hci0" address="..." pairing_mode="interactive"
  1. mcbluetooth initiates pairing
  2. Returns immediately with status
  3. If confirmation needed, returns passkey/info
  4. Use bt_pair_confirm to respond

Flow:

bt_pair → {"status": "awaiting_confirmation", "passkey": 123456}
bt_pair_confirm passkey=123456 accept=true → {"status": "paired"}

Best for: LLM-driven pairing where you want control over each step.

bt_pair adapter="hci0" address="..." pairing_mode="auto"
  • Automatically accepts all pairing requests
  • No user confirmation required
  • Just Works and auto-confirm numeric comparison

Best for: Automated testing, trusted lab environments.

bt_pair adapter="hci0" address="..." pairing_mode="elicit"
  • Uses MCP elicitation to prompt user directly
  • If MCP client supports elicitation, user sees prompt
  • Falls back to interactive if not supported

Best for: Human-in-the-loop pairing with MCP clients that support elicitation.

mcbluetooth registers its agent at startup:

# Register with BlueZ
agent_manager = await bus.get_proxy_object(
"org.bluez",
"/org/bluez"
).get_interface("org.bluez.AgentManager1")
await agent_manager.call_register_agent(
agent_path,
"KeyboardDisplay" # Capability
)
await agent_manager.call_request_default_agent(agent_path)

The agent declares “KeyboardDisplay” capability:

  • Can display codes (for numeric comparison)
  • Can accept input (for passkey entry)
  • Maximizes pairing compatibility

The agent implements these D-Bus methods:

MethodCalled When
RequestPinCodeLegacy PIN needed
RequestPasskeyPasskey entry needed
DisplayPasskeyShow passkey to user
DisplayPinCodeShow PIN to user
RequestConfirmationNumeric comparison
RequestAuthorizationJust Works confirmation
AuthorizeServiceService-level auth
CancelPairing cancelled
ReleaseAgent released

When pairing requires confirmation:

# Store pending request
_pending_pairings[address] = {
"method": "numeric_comparison",
"passkey": 123456,
"timestamp": datetime.now()
}
# Return to bt_pair caller
return {
"status": "awaiting_confirmation",
"method": "numeric_comparison",
"passkey": 123456
}

Later, bt_pair_confirm resolves it:

@mcp.tool()
async def bt_pair_confirm(adapter: str, address: str, accept: bool, passkey: int = None):
pending = _pending_pairings.get(address)
if pending:
if accept:
pending["resolve"](passkey)
else:
pending["reject"]()

mcbluetooth’s agent didn’t register properly:

  1. Check mcbluetooth is running
  2. Verify D-Bus connectivity
  3. Check for existing agents (some DEs register their own)

The other device rejected pairing:

  1. Device may have reached pairing limit
  2. Try removing existing pairing on both sides
  3. Restart Bluetooth on the other device

No response within timeout period:

  1. Extend timeout: bt_pair ... timeout=120
  2. Check bt_pairing_status for pending requests
  3. Other device may need user interaction

For Passkey Entry mode:

  1. Ensure you’re entering the exact code shown
  2. Some devices show passkey only briefly
  3. Use bt_pairing_status to see expected passkey
ModeMITM Protected
Just Works✗ No
Numeric Comparison✓ Yes
Passkey Entry✓ Yes
Legacy PINPartial
  1. Prefer Numeric Comparison - Verify codes match on both devices
  2. Avoid auto mode in production - Human verification is important
  3. Check device identity - Verify name/address before confirming
  4. Remove unused pairings - Reduce attack surface

Desktop environments often register their own agents:

EnvironmentAgent
GNOMEgnome-shell
KDEbluedevil
XFCEblueman

mcbluetooth requests to be the default agent, which usually works. If you have issues:

  1. Temporarily stop the DE’s Bluetooth applet
  2. Or use mcbluetooth’s agent alongside (may cause double prompts)