list_contacts
Retrieve all Signal contacts with names and phone numbers. Filter by name or number substring to find specific contacts quickly.
Instructions
List all Signal contacts known to this account, including names and phone numbers. Use the optional search parameter to filter by name or number substring. Returns contacts from signal-cli's local contact store. Use get_profile to fetch the current Signal profile for a specific contact.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| search | No | Filter contacts by name or number (case-insensitive substring match) |
Implementation Reference
- src/signal_mcp/server.py:1238-1240 (handler)MCP tool handler for 'list_contacts' — calls client.list_contacts(search) and returns results as dicts
elif name == "list_contacts": contacts = await client.list_contacts(search=arguments.get("search")) return _ok([c.to_dict() for c in contacts]) - src/signal_mcp/server.py:163-177 (schema)MCP Tool schema definition for 'list_contacts' with optional 'search' parameter for filtering by name or number substring
Tool( name="list_contacts", description=( "List all Signal contacts known to this account, including names and phone numbers. " "Use the optional search parameter to filter by name or number substring. " "Returns contacts from signal-cli's local contact store. " "Use get_profile to fetch the current Signal profile for a specific contact." ), inputSchema={ "type": "object", "properties": { "search": {"type": "string", "description": "Filter contacts by name or number (case-insensitive substring match)"}, }, }, ), - src/signal_mcp/client.py:673-697 (handler)SignalClient.list_contacts — core implementation that calls signal-cli JSON-RPC 'listContacts', parses results into Contact objects, optionally filters by search query (case-insensitive substring match on number, name, given_name, family_name)
async def list_contacts(self, search: str | None = None) -> list[Contact]: result = await self._rpc("listContacts") contacts = [] for c in result if isinstance(result, list) else []: profile = c.get("profile") or {} contacts.append(Contact( number=c.get("number") or "", uuid=c.get("uuid"), name=(c.get("name") or "").strip() or None, given_name=(profile.get("givenName") or c.get("givenName") or "").strip() or None, family_name=(profile.get("familyName") or c.get("familyName") or "").strip() or None, profile_name=None, about=(profile.get("about") or c.get("about") or "").strip() or None, blocked=c.get("isBlocked", False), )) if search: q = search.lower() contacts = [ c for c in contacts if q in (c.number or "").lower() or q in (c.name or "").lower() or q in (c.given_name or "").lower() or q in (c.family_name or "").lower() ] return contacts - src/signal_mcp/models.py:64-96 (helper)Contact dataclass with display_name property (prefers explicit name, then profile full name, then number) and to_dict() method
@dataclass class Contact: number: str uuid: str | None = None name: str | None = None given_name: str | None = None family_name: str | None = None profile_name: str | None = None about: str | None = None blocked: bool = False @property def display_name(self) -> str: # Prefer explicit name set by user, then profile full name, then number if self.name and self.name.strip(): return self.name.strip() parts = " ".join(filter(None, [self.given_name, self.family_name])).strip() if parts: return parts return self.profile_name or self.number or "" def to_dict(self) -> dict: return { "number": self.number, "uuid": self.uuid, "name": self.name, "given_name": self.given_name, "family_name": self.family_name, "profile_name": self.profile_name, "about": self.about, "blocked": self.blocked, "display_name": self.display_name, } - src/signal_mcp/server.py:1099-1101 (registration)Tool registration via MCP's @app.list_tools() decorator which returns the TOOLS list containing list_contacts
@app.list_tools() async def list_tools() -> list[Tool]: return TOOLS