Koskamo

Odile Labs MCP

Official MCP server enabling AI agents to interact with businesses via phone. It provides tools for Odile Direct (lookups, booking, messaging) and Odile Gate (API enforcement, audit logs), allowing agents to make calls, send messages, and manage credits using a hosted endpoint.

Endpoint and access

No sign-in to connect and list tools. Individual tools may still need a vendor account, credentials, or permissions; check the documented prerequisites.

Odile Labs MCP logo, by Odile LabsVendor evidenceStreamable HTTPNo sign-inEndpoint checked 2026-11-01Operated by Odile Labs
You need:Keys are made on this page after you sign inCustom API routes/scope require owner authorization
MCP endpoint (Streamable HTTP)
https://odilelabs.com/mcp/direct
Vendor documentation

What you can do with it

  • Look up businesses by phone number
  • Book appointments or send messages
  • Set up AI agent answering lines
  • Enforce API call policies via Gate

Source and maintenance

Vendor evidenceRegistry 1.9.0
Maintenance checked

Protocol and capabilities

Server
odile 0.7.0
MCP protocol
2025-06-18
Tools
10 advertised
Resources
Not advertised
Resource templates
Not advertised
Prompts
Not advertised
Server-advertised capabilities
{
  "tools": {
    "listChanged": true
  }
}

Server-provided instructions

Untrusted server output shown for inspection. It is not Koskamo guidance and is excluded from knowledge search.

Odile Direct lets an AI agent reach a business that has no API, by its phone number. find_business_agent looks the business up and is free; send_to_business leaves it a message, checks availability, books or cancels a time; get_replies and reply_to_business carry the conversation when the business answers. For a US or Canadian business without a Direct card, call_business places an informational phone call, get_call_result reads its outcome and answer_call_question answers the business mid-call. get_m2m_network lists the skills, credit prices and limits; get_direct_usage shows your own allowance and balance, and a payment_required reply carries the top-up link; get_direct_payment checks a deposit booking. This endpoint carries only the Direct tools: the answering-line tools are at https://odilelabs.com/mcp/lines, every Odile tool is at https://odilelabs.com/mcp, and Odile Gate, which limits what an agent may call on your own API, is a separate MCP server at https://gate.odilelabs.com/mcp. Lookups, availability and booking cancellation stay free. Each agent identity gets 50 free tasks per UTC calendar month; then a delivered message costs 1 credit and a confirmed booking 5 credits. 1 credit = $0.10. Direct 100: 100 credits for $10; Direct 1,000: 1,000 credits for $90; Direct 10,000: 10,000 credits for $800. Credits never expire; nothing renews. Failed, refused and unconfirmed tasks cost nothing. Existing prepaid dollar balances keep their full cent value; 10 cents of balance equals 1 credit. Price quotes and paying a quoted price are not available. The opt-in voice pilot costs 2.5 credits per answered minute, billed by the second with a 20-second minimum. It reserves a funded 20–600-second cap before dialing; failed and unanswered calls cost zero. It needs account opt-in; free-only connections cannot start voice. A wallet can pay $1.00 in Base USDC per answered call to a business (POST /a2a/v1/x402/call) or per answered test call of its own phone agent, with scores (POST /a2a/v1/x402/test-call); unanswered and failed calls are never charged.

Tools

10 advertised

get_m2m_network — OdileM2M network
Advertised by the server during the last successful anonymous check.
Advertised read-only

Read the OdileM2M network card for Direct: the live skills a business can accept, what each costs in credits, the credit packs, the limits that apply, and the Gate MCP address and quickstart. Use it to cost Direct tasks before sending any. It does not choose between products (get_agent_services) and does not show your own balance (get_direct_usage). Returns the pricing card (currency, credit_cents, packs, lookups) plus live_skills[{id, name, description, credits}], receipts, mcp_url, gate_mcp_url, quickstart_url, docs_url and connect_steps[]. Reference data, nothing account-specific: takes no arguments, needs no sign-in, contacts no business and changes nothing. Example: call it once with no arguments before the first send_to_business of a session.

get_m2m_network input schema
{
  "type": "object",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "properties": {}
}
get_m2m_network annotations
{
  "readOnlyHint": true,
  "openWorldHint": false,
  "idempotentHint": true,
  "destructiveHint": false
}
find_business_agent — Find a business agent
Advertised by the server during the last successful anonymous check.
Advertised read-only

Look up a business by its phone number in the Odile Direct directory and get its Direct card. Start here: send_to_business needs the business id (<biz>) from this card. Pass the number as customers dial it, in E.164 with the country code and no spaces or dashes (+15615550123); any other format is rejected. Returns {agent_card, business, trade, town}: agent_card is the A2A card URL (/b/<biz>/ for a business line, /c/<biz>/ for a claimed listing; pass that <biz> to send_to_business), and the card lists the skills the business accepts. A number that is not on Direct returns not_found with claim_url and listings_url; for a US or Canadian number, voice_fallback says whether call_business is available. Capped at 30 lookups per IP per hour without sign-in and 10000 per key per day signed in. Read-only; contacts no business. Example: number='+15615550123' returns the card whose id you pass to send_to_business as biz.

find_business_agent input schema
{
  "type": "object",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "required": [
    "number"
  ],
  "properties": {
    "number": {
      "type": "string",
      "pattern": "^\\+[1-9]\\d{6,14}$",
      "description": "The business's phone number in E.164, e.g. +15615550123."
    }
  }
}
find_business_agent annotations
{
  "readOnlyHint": true,
  "openWorldHint": false,
  "idempotentHint": true,
  "destructiveHint": false
}
send_to_business — Send a business a message or booking
Advertised by the server during the last successful anonymous check.
Advertised destructiveOpen-world

Send one request to a business found with find_business_agent, using a skill its card lists: take_message (leave a message), check_availability (read open times), book (book a slot check_availability returned), book_first_available (book the first open slot in up to 5 preferred windows) or cancel_booking (cancel a booking you made). Call find_business_agent first; for a business without a card, use call_business. Say an automated assistant is acting, and get the person's permission before booking. A delivered message cannot be recalled; a booking is undone only with cancel_booking. message_id is the retry key: reuse it unchanged on every retry so the request runs once. If you omit it, an identical request from you counts as a retry only within the same UTC day. Returns the A2A v1.0 {task}: read its state before telling anyone a message was delivered or a booking confirmed. A deposit booking returns INPUT_REQUIRED with a payment_url for the person; it is held 30 minutes and confirmed only after payment, which get_direct_payment checks. Failed, refused and unconfirmed tasks are not charged; costs are in get_m2m_network and your balance in get_direct_usage. Messages and confirmed bookings return thread_id and thread_token: use get_replies to read the business’s replies and reply_to_business to answer in the same conversation. Signed-in requesters may omit callback_number for take_message; anonymous requesters still need their own valid number. Example: biz='harbor-plumbing', skill='take_message', message_id='req-8f2', data={caller_name:'Ana Diaz', callback_number:'+15615550199', need:'Leaking water heater, today if possible'}.

send_to_business input schema
{
  "type": "object",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "required": [
    "biz",
    "skill",
    "data"
  ],
  "properties": {
    "biz": {
      "type": "string",
      "pattern": "^[a-zA-Z0-9_-]{1,100}$",
      "description": "The business id from its card: the <biz> in /b/<biz>/ or /c/<biz>/."
    },
    "data": {
      "type": "object",
      "description": "The skill's fields; put skill beside data, never inside it. take_message: caller_name, callback_number (the person's own E.164 number, never the business's), need, optional on_behalf_of and urgency (low, normal, high or urgent). check_availability: optional service and date (YYYY-MM-DD in the business's time zone). book: slot exactly as check_availability returned it, caller_name, callback_number, need. book_first_available: options (1 to 5 windows in order, each {date, optional after and before as HH:MM, optional service}), fallback (none or take_message), caller_name, callback_number, need. cancel_booking: booking_id and cancel_token from the confirmed booking, optional reason (up to 300 characters); cancelling is not charged and does not refund a booking fee. For take_message only, a signed-in requester may omit callback_number and receive replies in the thread.",
      "propertyNames": {
        "type": "string"
      },
      "additionalProperties": {}
    },
    "skill": {
      "enum": [
        "take_message",
        "check_availability",
        "book",
        "book_first_available",
        "cancel_booking"
      ],
      "type": "string",
      "description": "One skill the business card lists."
    },
    "target": {
      "enum": [
        "line",
        "claim"
      ],
      "type": "string",
      "description": "line (default) for a business line (/b/ card); claim for a claimed listing (/c/ card), which takes messages only."
    },
    "message_id": {
      "type": "string",
      "maxLength": 160,
      "minLength": 1,
      "description": "Your id for this request; reuse it unchanged on every retry so the request runs once."
    },
    "approval_id": {
      "type": "string",
      "pattern": "^da_[a-f0-9]{24}$",
      "description": "Only when the business approves bookings itself: the action_id (da_...) from its approval_required reply, sent with the same message_id and data once the owner approves."
    }
  }
}
send_to_business annotations
{
  "readOnlyHint": false,
  "openWorldHint": true,
  "idempotentHint": false,
  "destructiveHint": true
}
get_replies — Read replies from a business
Advertised by the server during the last successful anonymous check.
Advertised read-only

Read the replies in a Direct conversation you started with send_to_business: its status and messages, newest last. Use it to find out whether the business has answered; it only reads and does not wait, so call it again later if there is no reply yet. Not for messages callers left on your own answering line (get_line_messages) or the outcome of a phone call (get_call_result). Pass the thread_id that send_to_business returned; sign in as the original requester with direct:tasks, or supply the private thread_token instead. Returns {thread_id, status (received, business_notified, business_replied, booked, waiting_for_you or closed), caller_name, callback_number, messages[{from: customer or business, text, at}]}. An unknown thread or a wrong token returns not_found, and a thread expires 90 days after its last message. Changes nothing and uses no credits. Example: thread_id='th_7c1' (with thread_token unless you started the thread while signed in) returns the status and messages so far.

get_replies input schema
{
  "type": "object",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "required": [
    "thread_id"
  ],
  "properties": {
    "thread_id": {
      "type": "string",
      "maxLength": 160,
      "minLength": 1,
      "description": "The thread_id returned by send_to_business."
    },
    "thread_token": {
      "type": "string",
      "maxLength": 128,
      "minLength": 1,
      "description": "The private thread_token; required unless signed in as the requester who started the thread."
    }
  }
}
get_replies annotations
{
  "readOnlyHint": true,
  "openWorldHint": false,
  "idempotentHint": true,
  "destructiveHint": false
}
reply_to_business — Reply in a business conversation
Advertised by the server during the last successful anonymous check.
Advertised destructiveOpen-world

Send a customer reply in an open Direct conversation and notify the business. Use it after get_replies shows the business answered or asked something; to start a new conversation use send_to_business, and to answer a question during a live phone call use answer_call_question. Pass the thread_id that send_to_business returned and the reply text (1 to 500 characters); sign in as the original requester with direct:tasks, or supply the private thread_token instead. message_id is the retry key: reuse it unchanged when retrying. Returns the A2A {task} for the reply: read its state before telling anyone it was delivered. A delivered reply cannot be recalled and is billed the same way as a take_message task; closed conversations refuse replies. Example: thread_id='th_7c1', text='Thursday after 2pm works, thank you.', message_id='rep-1'.

reply_to_business input schema
{
  "type": "object",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "required": [
    "thread_id",
    "text"
  ],
  "properties": {
    "text": {
      "type": "string",
      "maxLength": 500,
      "minLength": 1,
      "description": "The customer’s reply to the business."
    },
    "thread_id": {
      "type": "string",
      "maxLength": 160,
      "minLength": 1,
      "description": "The thread_id returned by send_to_business."
    },
    "message_id": {
      "type": "string",
      "maxLength": 160,
      "minLength": 1,
      "description": "Your id for this reply; reuse it unchanged when retrying."
    },
    "thread_token": {
      "type": "string",
      "maxLength": 128,
      "minLength": 1,
      "description": "The private thread_token; required unless signed in as the requester who started the thread."
    }
  }
}
reply_to_business annotations
{
  "readOnlyHint": false,
  "openWorldHint": true,
  "idempotentHint": true,
  "destructiveHint": true
}
call_business — Call a business without a Direct card
Advertised by the server during the last successful anonymous check.
Advertised destructiveOpen-world

Place an outbound phone call to a business to ask for information on a named person’s behalf. Use it when find_business_agent returned not_found for a US or Canadian number; when a Direct card exists use send_to_business. It dials a real number and a call cannot be recalled once placed. Requires direct:tasks, human opt-in at https://odilelabs.com/developers#voice and the person’s permission for this call (on_behalf_consent true). Informational calls only: no sales, payments or account access. Calls run Monday to Friday, 9:00 to 17:00 in the business’s local time; outside that window the request is refused and the error includes next_window. A number is not called twice within 24 hours, and each account has a daily call cap. number and callback_number must be callable US or Canadian E.164 numbers, callback_number being the person’s own, never the business’s. message_id is required: reuse it unchanged on every retry so only one call is placed. Returns {task_id, state (queued, completed or failed), status}; read get_call_result for progress and answers. Voice credit costs: get_m2m_network. Example: number='+15615550123', business_name='Harbor Plumbing', caller_name='Ana Diaz', callback_number='+15615550199', need='Do you replace water heaters?', on_behalf_consent=true, message_id='call-8f2'.

call_business input schema
{
  "type": "object",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "required": [
    "number",
    "business_name",
    "caller_name",
    "callback_number",
    "need",
    "on_behalf_consent",
    "message_id"
  ],
  "properties": {
    "need": {
      "type": "string",
      "maxLength": 500,
      "minLength": 1,
      "description": "The information the person wants from the business."
    },
    "number": {
      "type": "string",
      "pattern": "^\\+[1-9]\\d{6,14}$",
      "description": "The business’s E.164 phone number, e.g. +15615550123."
    },
    "questions": {
      "type": "array",
      "items": {
        "type": "string",
        "maxLength": 200,
        "minLength": 1
      },
      "maxItems": 5,
      "description": "Up to five questions to ask the business."
    },
    "message_id": {
      "type": "string",
      "maxLength": 160,
      "minLength": 1,
      "description": "A stable request id; reuse it unchanged on every retry."
    },
    "caller_name": {
      "type": "string",
      "maxLength": 100,
      "minLength": 1,
      "description": "The named person who requested this call."
    },
    "max_seconds": {
      "type": "integer",
      "maximum": 600,
      "minimum": 60,
      "description": "Maximum call duration, from 60 to 600 seconds, subject to available credits."
    },
    "business_name": {
      "type": "string",
      "maxLength": 120,
      "minLength": 1,
      "description": "The business to call."
    },
    "callback_number": {
      "type": "string",
      "pattern": "^\\+[1-9]\\d{6,14}$",
      "description": "That person’s own callback number in E.164, never the business’s."
    },
    "on_behalf_consent": {
      "type": "boolean",
      "const": true,
      "description": "True only after that person asked for this call."
    }
  }
}
call_business annotations
{
  "readOnlyHint": false,
  "openWorldHint": true,
  "idempotentHint": true,
  "destructiveHint": true
}
get_call_result — Read your business call result
Advertised by the server during the last successful anonymous check.
Advertised read-only

Read the state and outcome of a business call you placed with call_business (an outbound call; the calls your own answering line took are in get_line_calls), using its task_id. Call it again until the state is final: queued, dialing and in_call are still in progress; completed, no_answer, voicemail, refused and failed are final; input_required means the business asked a question that answer_call_question can answer within 45 seconds. Requires direct:tasks and ownership of the call (another account’s task_id returns Not found). Returns {task_id, state, outcome, duration_seconds, transcript, answers, summary, cost {credits}}, plus pending_question {question, expires_at} while a question waits. Changes nothing, starts no call and is safe to repeat. Example: task_id='dv_9a3' from call_business; stop polling once state is completed, failed or no_answer.

get_call_result input schema
{
  "type": "object",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "required": [
    "task_id"
  ],
  "properties": {
    "task_id": {
      "type": "string",
      "maxLength": 100,
      "minLength": 1,
      "description": "The task_id returned by call_business."
    }
  }
}
get_call_result annotations
{
  "readOnlyHint": true,
  "openWorldHint": false,
  "idempotentHint": true,
  "destructiveHint": false
}
answer_call_question — Answer a question during your business call
Advertised by the server during the last successful anonymous check.
Advertised destructiveOpen-world

Give the calling assistant your answer to a question the business asked during a call placed with call_business, so the call can continue. Use it only when get_call_result shows state input_required (its pending_question holds the question); reply within 45 seconds or the assistant tells the business the person will follow up and carries on. The assistant uses the answer on the live call and it cannot be taken back, so keep it short and factual (up to 300 characters). It is refused if it contains a number of four to eight digits, a card-length number, or words such as password, PIN or code; never include payment card numbers, passwords or one-time codes. Requires direct:tasks and ownership of the call. Returns the call state; read get_call_result for the outcome. Example: task_id='dv_9a3', answer='A 40-gallon gas heater, installed in 2016.'

answer_call_question input schema
{
  "type": "object",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "required": [
    "task_id",
    "answer"
  ],
  "properties": {
    "answer": {
      "type": "string",
      "maxLength": 300,
      "minLength": 1,
      "description": "The answer to the pending question, in plain words, without card numbers, passwords, one-time codes or any number of four to eight digits."
    },
    "task_id": {
      "type": "string",
      "maxLength": 100,
      "minLength": 1,
      "description": "The task_id returned by call_business."
    }
  }
}
answer_call_question annotations
{
  "readOnlyHint": false,
  "openWorldHint": true,
  "idempotentHint": false,
  "destructiveHint": true
}
get_direct_usage — Your Direct allowance and credits
Advertised by the server during the last successful anonymous check.
Advertised read-only

Read your own Direct account ledger: the signed-in account’s UTC free-send allowance, prepaid balance in cents, reserved cents and last 20 charges. It never buys credit and does not list costs (see get_m2m_network). Use it before sending tasks that may cost credit; a payment_required reply from send_to_business carries the top-up link. Uses the same ledger as REST and your developer page. Requires direct:tasks and sign-in. Changes nothing; phone minutes and legacy credits are tracked separately. Example: call it with no arguments before a booking to confirm the allowance or balance covers it.

get_direct_usage input schema
{
  "type": "object",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "properties": {}
}
get_direct_usage annotations
{
  "readOnlyHint": true,
  "openWorldHint": false,
  "idempotentHint": true,
  "destructiveHint": false
}
get_direct_payment — Check your merchant checkout and booking
Advertised by the server during the last successful anonymous check.
Open-world

Check an existing deposit booking on your signed-in Direct account: reconcile its Stripe Checkout payment and return its receipt and the separate booking state. Use it after send_to_business returned a payment_url for a deposit booking, to see whether the person has paid; it cannot start a payment, which the person makes on Stripe’s hosted page. Pass biz (the business id from its card) and task_id (the task id send_to_business returned for that booking). Requires direct:tasks. Uses only the stored merchant account and amount and creates no new charge or booking identity; a verified paid checkout may finish its already-authorized held booking. Payout status remains unknown: paid does not mean a bank payout. Safe to repeat. Example: biz='harbor-plumbing', task_id='tk_5d0' after send_to_business returned a payment_url for a deposit booking.

get_direct_payment input schema
{
  "type": "object",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "required": [
    "biz",
    "task_id"
  ],
  "properties": {
    "biz": {
      "type": "string",
      "maxLength": 100,
      "minLength": 1,
      "description": "The business id (from its card) the deposit booking was made with."
    },
    "task_id": {
      "type": "string",
      "maxLength": 100,
      "minLength": 1,
      "description": "The task id send_to_business returned for that booking."
    }
  }
}
get_direct_payment annotations
{
  "readOnlyHint": false,
  "openWorldHint": true,
  "idempotentHint": true,
  "destructiveHint": false
}

Prompts

Not advertised

No prompt definitions available.

Resources

Not advertised

No resource definitions available.

30-day availability

Aggregated from read-only remote checks; this is not a tool-execution or security guarantee.

Successful checks
1 of 1 (100%)
Average latency
662 ms
95th percentile latency
662 ms

Maintenance history

Public check and source-refresh events. Editorial and ownership records remain private.

  • · probe · passed · 662 ms · metadata changed · 10 tool(s)

Connect from your client

Each snippet registers the same endpoint; pick the client you use.

This server is operated by the named third party. Listing and endpoint checks are not an endorsement or a security audit. Review the operator's permissions, terms, and data handling before connecting. How listings are checked and corrected.

Claude Code

Run this once in a terminal. It registers the remote server. Use --scope project to write .mcp.json for the repo.
Claude Code configuration
claude mcp add --transport http odile-labs-mcp-odilelabs https://odilelabs.com/mcp/direct

Codex

Run this once. Codex stores MCP servers in ~/.codex/config.toml (or .codex/config.toml in a trusted project).
Codex configuration
codex mcp add odile-labs-mcp-odilelabs --url https://odilelabs.com/mcp/direct

Cursor

Merge this server into .cursor/mcp.json in your project, then enable odile-labs-mcp-odilelabs in your MCP settings.
Cursor configuration
{
  "mcpServers": {
    "odile-labs-mcp-odilelabs": {
      "url": "https://odilelabs.com/mcp/direct"
    }
  }
}

Cline

Merge this into cline_mcp_settings.json (Cline → MCP Servers → Configure).
Cline configuration
{
  "mcpServers": {
    "odile-labs-mcp-odilelabs": {
      "type": "streamableHttp",
      "url": "https://odilelabs.com/mcp/direct",
      "disabled": false
    }
  }
}

VS Code

Merge this server into .vscode/mcp.json. Use MCP: List Servers to start odile-labs-mcp-odilelabs and inspect its tools.
VS Code configuration
{
  "servers": {
    "odile-labs-mcp-odilelabs": {
      "type": "http",
      "url": "https://odilelabs.com/mcp/direct"
    }
  }
}

Gemini CLI

Run this once. It writes .gemini/settings.json in the current project; pass --scope user for ~/.gemini/settings.json.
Gemini CLI configuration
gemini mcp add --transport http odile-labs-mcp-odilelabs https://odilelabs.com/mcp/direct

Claude Desktop

Merge this into claude_desktop_config.json (macOS: ~/Library/Application Support/Claude/, Windows: %APPDATA%\Claude\), then restart Claude Desktop.
Claude Desktop configuration
{
  "mcpServers": {
    "odile-labs-mcp-odilelabs": {
      "url": "https://odilelabs.com/mcp/direct"
    }
  }
}

Windsurf

Merge this into ~/.codeium/windsurf/mcp_config.json, then refresh MCP in Cascade. Windsurf remote servers use serverUrl, not url.
Windsurf configuration
{
  "mcpServers": {
    "odile-labs-mcp-odilelabs": {
      "serverUrl": "https://odilelabs.com/mcp/direct"
    }
  }
}

Let your agent find its tools

Find servers like Odile Labs MCP from your agent. Add Koskamo once to search the index for endpoints, access requirements, and client setup. Public discovery needs no key.

Do you run Odile Labs MCP?

Prove ownership with a DNS record, a well-known file, or your Official MCP Registry entry, and your edits publish immediately. Proofs are re-checked weekly; admins can still hide or remove a listing.

Claim this listing