telique-mcp 1.0.27 → 1.0.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ringer
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -4,12 +4,27 @@ Telecom data tools for AI assistants. Query LRN, CNAM, DNO, LERG routing tables,
4
4
 
5
5
  ## Install
6
6
 
7
+ Pick **one** install surface.
8
+
9
+ ### Recommended — Hosted connector
10
+
11
+ OAuth-authenticated. Works on claude.ai, Claude Desktop, Claude Code, and Claude mobile. No local Node runtime.
12
+
13
+ - **URL:** `https://mcp.telique.ringer.tel`
14
+ - Add it as a custom MCP connector in your Claude client's settings.
15
+
16
+ ### npm stdio — advanced / offline
17
+
18
+ For CI, local development, or clients that don't support remote MCP. Stores a long-lived `tlq_…` token at `~/.telique/config.json`.
19
+
7
20
  ```bash
8
21
  npm install -g telique-mcp
9
22
  telique-mcp setup
10
23
  ```
11
24
 
12
- The setup wizard detects your installed AI clients and registers automatically.
25
+ The setup wizard detects installed AI clients and registers automatically.
26
+
27
+ > ⚠️ **Install only one surface.** Running both exposes the same tools under overlapping namespaces and causes confusing shadowing of tool results.
13
28
 
14
29
  ## What You Get
15
30
 
@@ -1,3 +1,3 @@
1
1
  import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
2
  export declare function registerKnowledge(server: McpServer): void;
3
- export declare const TELIQUE_KNOWLEDGE = "# Telique Telecom API Knowledge Base\n\nYou have access to 13 Telique tools for querying live telecom data. NEVER guess carrier names, LRNs, routing data, or CNAM \u2014 always query the API.\n\n---\n\n## CRITICAL: LERG vs LSMS \u2014 Two Different Databases\n\n**LERG** = Static telecom infrastructure reference (updated monthly from iconectiv BIRRDS)\n- NPA-NXX assignments, switches, tandems, homing arrangements, rate centers, LATAs, carrier names by OCN\n- 27 tables. NO individual phone number data.\n- Query with: `lerg_query`, `lerg_complex_query`, `lerg_tandem`, `lerg_table_info`, or `graphql_query(service=\"lerg\")`\n\n**LSMS** = Live NPAC porting data (refreshed within minutes)\n- Phone number ownership (SPID), LRN assignments, porting state\n- NO routing infrastructure (no tandems, switches, homing, rate centers, OCN names)\n- Query with: `lrn_lookup`, `lrn_relationship_query`, or `graphql_query(service=\"lsms\")`\n\n| Data Type | Database | Tool |\n|-----------|----------|------|\n| Tandem switches | **LERG** | `lerg_tandem` |\n| Switch/CLLI details | **LERG** | `lerg_query` on lerg_7 |\n| Homing arrangements | **LERG** | `lerg_query` on lerg_7_sha |\n| NPA-NXX routing (OCN, switch, LATA, rate center) | **LERG** | `lerg_query` on lerg_6 |\n| Carrier/OCN names | **LERG** | `lerg_query` on lerg_1 |\n| Current TN ownership (SPID) | **LSMS** | `lrn_lookup` |\n| Current LRN for a TN | **LSMS** | `lrn_lookup` |\n| Porting state | **LSMS** | `lrn_relationship_query` or GraphQL |\n\n---\n\n## CRITICAL: Always Dip the LRN First\n\nA ported phone number's NPA-NXX often differs from its LRN's NPA-NXX. For ANY routing question about a specific phone number:\n\n1. **`lrn_lookup`** \u2192 get the LRN and SPID\n2. Extract NPA-NXX from the **LRN** (first 3 digits = NPA, next 3 = NXX)\n3. Use the **LRN's** NPA-NXX for all LERG lookups\n\n**Example:** TN 303-629-8301\n- WRONG: Look up 303-629 in LERG \u2192 returns the original carrier's data (before porting)\n- CORRECT: `lrn_lookup(\"3036298301\")` \u2192 LRN = 7207081999 \u2192 look up 720-708 in LERG \u2192 returns current carrier\n\nThis applies to: tandem, switch, LATA, OCN, rate center \u2014 anything keyed by NPA-NXX.\n\n### The Golden Pattern for Routing Questions\n\n```\nStep 1: lrn_lookup({phone_number}) \u2192 get LRN and SPID\nStep 2: Extract NPA (first 3 digits of LRN) and NXX (next 3 digits of LRN)\nStep 3: Use LRN's NPA-NXX for LERG queries:\n - lerg_tandem({npa, nxx}) \u2192 tandem switch\n - lerg_query(\"lerg_6\", ..., \"npa={npa}&nxx={nxx}\") \u2192 OCN, switch, LATA, rate center\n - lerg_query(\"lerg_7\", ..., \"switch={clli}\") \u2192 switch details\n```\n\nOr use `lookup_tn` for a quick consolidated view (dips LRN + CNAM + DNO + LERG in parallel).\n\n---\n\n## LERG Table Reference\n\n### Big 4 \u2014 Cover ~80% of Queries\n\n**lerg_1** \u2014 OCN/carrier directory\n- Fields: ocn_num, ocn_name, abbre_ocn_name, ocn_state, category, overall_ocn\n- Use: Look up carrier name by OCN. Join from lerg_6 `ocn` \u2192 lerg_1 `ocn_num`.\n- Example: `lerg_query(\"lerg_1\", \"ocn_num,ocn_name,ocn_state\", \"ocn_num=567G\")`\n\n**lerg_6** \u2014 NPA-NXX block assignments (the workhorse)\n- Fields: npa, nxx, block_id, lata, lata_name, loc_state, ocn, aocn, switch, sha_indicator, rc_abbre, rc_type, coc_type, eff_date, status\n- Has LATA + state + NPA + NXX + OCN + switch + rate center all on one row.\n- Example: `lerg_query(\"lerg_6\", \"npa,nxx,block_id,loc_name,loc_state,ocn,switch,lata\", \"npa=720&nxx=708\")`\n\n**CRITICAL: Understanding block_id and NPA-NXX ownership:**\n- `block_id=\"A\"` = the **Code Holder** \u2014 the original LERG owner of the entire NPA-NXX. This is THE owner of the NPA-NXX.\n- `block_id=\"0\"` through `\"9\"` = **Block Holders** \u2014 carriers who received a pooled 1,000-number block (thousands-block pooling). Each block covers numbers x000-x999 within the NXX.\n- When asked \"who owns NPA-NXX 720-708?\", the answer is the OCN on the `block_id=\"A\"` row.\n- A query for `npa=720&nxx=708` may return multiple rows \u2014 one for block_id=\"A\" (the Code Holder) and additional rows for pooled blocks (0-9). Always filter or identify the \"A\" row for ownership questions.\n- To find which carrier serves a specific phone number within a pooled NXX, use the LRN dip pattern instead (the block_id approach only tells you who holds the block, not who currently serves an individual ported number).\n\n**lerg_7_sha** \u2014 Switch homing arrangements\n- Fields: switch, sha_indicator, h_trm_d_tdm (FGD tandem), host, ocn\n- Match `switch` + `sha_indicator` from lerg_6 to find the correct tandem.\n- Or use `lerg_tandem` which does the join automatically.\n\n**lerg_12** \u2014 LRN registry\n- Fields: lrn, lata, lata_name, switch, ocn, status, eff_date\n- Which company/switch established each LRN.\n\n### Supporting Tables\n\n| Table | Purpose |\n|-------|---------|\n| lerg_3 | NPA (area code) metadata \u2014 state, effective date |\n| lerg_5 | NPA/LATA cross-reference \u2014 which NPAs exist in a LATA |\n| lerg_7 | Switch/CLLI details \u2014 address, coordinates, equipment type |\n| lerg_8 | Rate center details \u2014 geography, V&H coordinates |\n| lerg_8_loc | Localities (towns) \u2192 rate centers |\n| lerg_8_pst | ZIP codes \u2192 localities (US only) |\n| lerg_9 | Homing by tandem \u2014 \"top-down\" view of which NPA-NXXs subtend a tandem |\n| lerg_4 | SS7 point codes |\n| lerg_10 | NPA-NXX \u2192 operator services ATC |\n| lerg_11 | Locality \u2192 operator services ATC |\n| lerg_16 | IP capability by LRN |\n| lerg_17 | IP capability by NPA-NXX |\n\nUse `lerg_table_info` to list all tables or get the schema for a specific one.\n\n---\n\n## LERG Query Syntax\n\nREST queries use path-based filters with `&` joining multiple conditions:\n- Single filter: `lerg_query(\"lerg_1\", \"ocn_num,ocn_name\", \"ocn_state=CO\")`\n- Multiple filters: `lerg_query(\"lerg_6\", \"npa,nxx,loc_name,ocn\", \"npa=303&nxx=629\")`\n\nFor complex queries with JOINs and advanced operators, use `lerg_complex_query`:\n```json\n{\n \"table\": \"lerg_6\",\n \"fields\": [\"npa\", \"nxx\", \"ocn\", \"loc_name\"],\n \"filters\": [{\"field\": \"npa\", \"operator\": \"eq\", \"value\": 720}],\n \"join\": {\n \"table\": \"lerg_1\",\n \"on\": [{\"left_field\": \"ocn\", \"right_field\": \"ocn_num\"}],\n \"fields\": [\"ocn_name\", \"ocn_state\"]\n }\n}\n```\n\nFilter operators: eq, ne, gt, gte, lt, lte, like, in, notin, isnull, isnotnull.\nUse `*` as the fields value to return all fields from a table.\nNote: REST `like` is **case-insensitive** (in-memory matching), unlike GraphQL LIKE which is case-sensitive (PostgreSQL).\n\n---\n\n## When to Use REST vs GraphQL\n\n| Use Case | Best Tool | Why |\n|----------|-----------|-----|\n| Single LRN/SPID for a phone number | `lrn_lookup` (REST) | Sub-millisecond, in-memory |\n| Carrier name by OCN | `lerg_query` (REST) | Simple single-field lookup |\n| NPA-NXX routing info | `lerg_query` (REST) | Fast, straightforward |\n| Tandem routing | `lerg_tandem` (REST) | Pre-built JOIN, optimized |\n| NPA-NXX + carrier name + switch in one call | `graphql_query` (LERG) | Nested relationships avoid multiple round trips |\n| Filter by fields not in REST (e.g., all switches for an OCN) | `graphql_query` (LERG) | FilterInput supports any field |\n| Cross-table JOIN with arbitrary conditions | `graphql_query` (LERG dynamicJoin) | Only way to do ad-hoc joins |\n| All phone numbers for an LRN (paginated with totalCount) | `graphql_query` (LSMS) | SVConnection returns totalCount + hasMore |\n| Quick profile of a phone number | `lookup_tn` (composite) | Parallel dip across LRN + CNAM + DNO + LERG |\n\n**Rule of thumb:** Use REST for simple lookups (one table, one or two filters). Use GraphQL when you need joins, relationship traversal, or fields not exposed by REST endpoints.\n\n---\n\n## LSMS / LRN REST Endpoints\n\nThe LRN API serves live NPAC porting data from an in-memory Judy Array store (500M+ records, sub-millisecond) backed by a PostgreSQL LSMS database.\n\n### LRN Lookup (`lrn_lookup` tool)\n\n`GET /v1/telique/lrn/{phone_number}?format=json`\n\nReturns the current LRN and carrier for a phone number. This is the fastest lookup \u2014 served from in-memory store, not PostgreSQL.\n\n**JSON response shape:**\n```json\n{\n \"phone_number\": \"3036298301\",\n \"lrn\": \"7207081999\",\n \"status\": \"success\",\n \"timestamp\": \"2025-08-07T02:30:00Z\",\n \"metadata\": {\n \"spid\": \"567G\",\n \"lnp_type\": \"lspp\",\n \"activation_timestamp\": \"2024-01-15T10:30:00Z\",\n \"last_updated\": \"2025-08-07T01:30:00Z\"\n }\n}\n```\n\nDefault format is plain text (`LRN;SPID`, e.g., `7207081999;567G`). Use `?format=json` for structured response.\n\n### LSMS Relationship Queries (`lrn_relationship_query` tool)\n\nQuery relationships between phone numbers, LRNs, and SPIDs from the LSMS PostgreSQL database. These hit the database directly and have higher latency than the in-memory LRN lookup.\n\n**6 query types and their endpoints:**\n\n| query_type | Endpoint | Description |\n|------------|----------|-------------|\n| `phones_by_lrn` | `GET /v1/telique/lsms/list/phone_number?lrn={value}` | All phone numbers for a given LRN |\n| `phones_by_spid` | `GET /v1/telique/lsms/list/phone_number?spid={value}` | All phone numbers for a given SPID |\n| `spid_by_lrn` | `GET /v1/telique/lsms/list/spid?lrn={value}` | All SPIDs associated with a given LRN |\n| `spid_by_phone` | `GET /v1/telique/lsms/list/spid?phone_number={value}` | All SPIDs for a given phone number |\n| `lrn_by_spid` | `GET /v1/telique/lsms/list/lrn?spid={value}` | All LRNs for a given SPID |\n| `lrn_by_phone` | `GET /v1/telique/lsms/list/lrn?phone_number={value}` | All LRNs for a given phone number |\n\n**Constraints:**\n- Exactly ONE query parameter per request (multiple parameters return a validation error)\n- Results are filtered to active records only\n- Phone numbers and LRNs are returned as strings\n\n**Example response (phones_by_lrn):**\n```json\n{\n \"phone_numbers\": [\"3036298301\", \"3039982743\", \"3033334444\"],\n \"count\": 3,\n \"query\": { \"lrn\": \"7207081999\", \"spid\": null, \"phone_number\": null }\n}\n```\n\n---\n\n## CNAM (Caller Name)\n\n`cnam_lookup` queries TransUnion's LIDB for the caller name associated with a phone number.\n\n- Returns: calling_name (up to 15 chars), calling_name_status (available/unavailable), presentation_indicator (allowed/restricted)\n- Results cached server-side for 24 hours\n- \"WIRELESS CALLER\" typically means the carrier hasn't provisioned a specific CNAM entry\n\n---\n\n## DNO (Do Not Originate)\n\n`dno_check` checks if a phone number should never appear as a caller ID.\n\n- DNO numbers belong to entities that only receive calls (IRS, major banks, government agencies)\n- If is_dno=true, the number appearing as caller ID indicates **spoofing/fraud**\n- Supports prefix matching: 3-digit (NPA), 6-digit (NPA-NXX), 7-digit, or 10-digit patterns\n- Response includes: is_dno, matched_pattern, source\n- Phone numbers are auto-normalized: E.164 (+12003456789), domestic (12003456789), and international (0012003456789) formats all accepted\n\n---\n\n## RouteLink \u2014 Toll-Free Number Routing\n\n**What is a toll-free number?** A phone number with an 8XX NPA (area code) that is free for the caller \u2014 the called party pays. Toll-free NPAs: **800, 888, 877, 866, 855, 844, 833, 822**. Any 10-digit number starting with one of these NPAs is a toll-free number (also called TFN or CRN in RouteLink context).\n\nToll-free numbers are managed by the **Somos TFN Registry**, not NPAC/LSMS. They have their own routing system (CPR decision trees) and their own management hierarchy (RespOrgs, not SPIDs). Use the RouteLink tools for toll-free lookups \u2014 do NOT use `lrn_lookup` for toll-free numbers (they don't have LRNs).\n\n### ROR (Responsible Organization)\n`routelink_lookup` with lookup_type=\"ror\" returns the RespOrg managing a toll-free number.\n- RespOrg is a 5-char code (e.g., \"NEX01\", \"TZN99\")\n- Only needs the CRN (toll-free number), no ANI/LATA required\n\n### CIC (Carrier Identification Code)\n`routelink_lookup` with lookup_type=\"cic\" or \"cicror\" returns which carrier handles calls to a toll-free number FROM a specific caller (ANI) in a specific LATA.\n- Requires: crn, ani (10-digit), lata (3-digit)\n- Common carrier codes: 0288 (AT&T), 0222 (MCI/Verizon), 0333 (Sprint), 0432 (Lumen)\n\n### CPR (Call Processing Record)\n`routelink_cpr` retrieves the full routing decision tree for a toll-free number.\n\n**Decision tree node types:**\n| Node | Description |\n|------|-------------|\n| [LATA] | Routes by caller's LATA |\n| [NPA] | Routes by caller's area code |\n| [NXX] | Routes by caller's exchange |\n| [STATE] | Routes by caller's state |\n| [DAY_OF_WEEK] | Routes by day (1-7, Sunday=1) |\n| [TIME_OF_DAY] | Routes by time (15-min intervals) |\n| [PERCENT] | Percentage-based load balancing |\n| [ANI] | Routes by specific caller number |\n\n**Action types:** Carrier XXXX (4-digit carrier code), Template XXXXXXXXXX (reference to template CRN), Routing XXXXX, NMC X (Network Management Class)\n\n**Common patterns:**\n- Simple: All calls \u2192 one carrier \u2192 one termination number\n- Time-of-day: Business hours \u2192 office, after hours \u2192 answering service\n- Geographic: Different terminations per LATA/state/NPA\n- Percentage: Load balancing across call centers (e.g., 60%/40%)\n\nUse `expand=true` (default) to recursively resolve template references into their full decision trees.\n\n---\n\n## GraphQL\n\nUse `graphql_query` for complex queries not possible with REST:\n- Cross-table relationship joins (lerg6 \u2192 carrier, lerg6 \u2192 switchInfo, lerg7Sha \u2192 tandemSwitch)\n- Filtering by fields not in REST endpoints\n- Multiple related data points in one query\n\n### LERG GraphQL (`service=\"lerg\"`)\n\n**CRITICAL naming rules:**\n- **Query names** are camelCase: `lerg1`, `lerg6`, `lerg7Sha`, `lerg7ShaIns`, `lerg12`, etc.\n- **Return fields** MUST be camelCase: `ocnName`, `locState`, `locName`, `shaIndicator`, `hTrmDTdm`\n- **Filter field names** accept BOTH camelCase (`ocnName`) and snake_case (`ocn_name`)\n- **All values are strings** \u2014 even numeric fields. Always quote: `value: \"720\"` not `value: 720`\n- **GraphQL LIKE is case-sensitive (SQL)** \u2014 LERG data is stored UPPERCASE, so patterns must be uppercase: `%VERIZON%` not `%verizon%`. (The REST `like` operator is case-insensitive \u2014 this only applies to GraphQL.)\n\n**FilterInput:**\n```graphql\n{ field: \"ocnName\", op: LIKE, value: \"%VERIZON%\" } # partial match (UPPERCASE!)\n{ field: \"npa\", op: EQ, value: \"720\" } # exact match\n{ field: \"npa\", op: IN, values: [\"212\", \"646\", \"917\"] } # IN uses 'values' (plural), not 'value'\n```\n\nOperators: EQ, NE, GT, GTE, LT, LTE, LIKE, IN, IS_NULL, IS_NOT_NULL\n\n**Predefined relationships** (avoid N+1 \u2014 use these instead of separate queries):\n- `lerg6` \u2192 `carrier` (joins to lerg1 via OCN), `switchInfo` (\u2192 lerg7), `homingArrangements` (\u2192 lerg7Sha)\n- `lerg7` \u2192 `carrier` (\u2192 lerg1)\n- `lerg7Sha` \u2192 `tandemSwitch` (\u2192 lerg7)\n\n**Example \u2014 find all Verizon OCNs:**\n```graphql\n{\n lerg1(\n filters: [{ field: \"ocnName\", op: LIKE, value: \"%VERIZON%\" }]\n pagination: { limit: 10 }\n ) {\n ocnNum\n ocnName\n ocnState\n category\n }\n}\n```\n\n**Example \u2014 NPA-NXX with carrier and switch in one query:**\n```graphql\n{\n lerg6(\n filters: [{ field: \"npa\", op: EQ, value: \"303\" }, { field: \"nxx\", op: EQ, value: \"629\" }]\n pagination: { limit: 1 }\n ) {\n npa nxx ocn locName switch\n carrier { ocnNum ocnName ocnState }\n switchInfo { switch eqpType swCity swState }\n homingArrangements {\n shaIndicator hTrmDTdm\n tandemSwitch { switch swCity swState }\n }\n }\n}\n```\n\n**dynamicJoin** \u2014 for arbitrary cross-table SQL joins (returns raw JSON):\n```graphql\n{\n dynamicJoin(input: {\n table: \"lerg_6\"\n fields: [\"npa\", \"nxx\", \"ocn\", \"locName\"]\n filters: [{ field: \"locName\", op: LIKE, value: \"%DENVER%\" }]\n join: {\n table: \"lerg_1\"\n fields: [\"ocnName\", \"category\"]\n on: [{ leftField: \"ocn\", rightField: \"ocnNum\" }]\n joinType: \"INNER\"\n }\n pagination: { limit: 10 }\n })\n}\n```\nNote: dynamicJoin uses snake_case table IDs (lerg_6, lerg_1, lerg_7_sha) and returns raw PostgreSQL column names (UPPERCASE with spaces, e.g., `\"OCN_NAME\"`, `\"LOC NAME\"`, `\"EFF DATE\"`), NOT GraphQL camelCase.\n\n### LSMS GraphQL (`service=\"lsms\"`)\n\nThe LSMS GraphQL API is a **completely separate implementation** from LERG GraphQL. It has different query patterns, different field naming, and different schema design. Do NOT use LERG-style FilterInput syntax with LSMS.\n\n**5 tables:**\n\n| Table | ~Rows | Requires Filter? |\n|-------|-------|------------------|\n| subscriptionVersions | 514M | Yes (phoneNumber, lrn, or spid) |\n| numberBlocks | 751K | Yes (npanxxx, spid, or lrn) |\n| serviceProviders | 5.2K | No |\n| locationRoutingNumbers | 56K | No |\n| npanxx | 192K | No |\n\n**Query patterns (NOT FilterInput \u2014 uses typed named parameters):**\n```graphql\n# Single phone number lookup\n{ subscriptionVersion(phoneNumber: \"3036298301\") { phoneNumber lrn spid serviceProvider { name } } }\n\n# Paginated list by LRN (returns totalCount, hasMore)\n{ subscriptionVersionsByLrn(lrn: \"7207081999\", limit: 10) { totalCount hasMore items { phoneNumber spid } } }\n\n# Paginated list by SPID\n{ subscriptionVersionsBySpid(spid: \"567G\", limit: 10) { totalCount hasMore items { phoneNumber lrn } } }\n\n# Number block lookup (single)\n{ numberBlock(npanxxx: \"3035551\") { npanxxx lrn spid serviceProvider { name } } }\n\n# Number blocks by SPID or LRN (paginated \u2014 at least one filter required)\n{ numberBlocks(spid: \"567G\", limit: 10) { totalCount hasMore items { npanxxx lrn spid } } }\n\n# Single carrier lookup (note: composite PK \u2014 same SPID may exist in multiple regions)\n{ serviceProvider(spid: \"567G\") { spid name npacRegion status contactInfo } }\n\n# List carriers (paginated)\n{ serviceProviders(limit: 20) { spid name npacRegion } }\n\n# LRN metadata\n{ locationRoutingNumber(lrn: \"7207081999\") { lrn ocn regionId status switchInfo } }\n\n# NPA-NXX single lookup\n{ npanxx(npa: \"303\", nxx: \"629\") { npa nxx spid effectiveTimestamp serviceProvider { name } } }\n\n# NPA-NXX codes for a carrier (paginated)\n{ npanxxBySpid(spid: \"567G\", limit: 50) { npa nxx effectiveTimestamp } }\n\n# Database statistics\n{ lsmsStats { activeSubscriptionVersions activeNumberBlocks totalServiceProviders totalLocationRoutingNumbers activeNpanxx } }\n```\n\n**Relationships** (use DataLoader batching \u2014 no N+1):\n- subscriptionVersion \u2192 `serviceProvider`, `lrnMetadata`\n- numberBlock \u2192 `serviceProvider`, `lrnMetadata`\n- npanxx \u2192 `serviceProvider`\n\n**Safety limits:** max 1000 results, depth 5, complexity 200, 10-second statement timeout\n**Auto-filters:** Soft-deletable records filter to is_active = true automatically\n**Note:** Phone numbers/LRNs are strings (stored as BIGINT but GraphQL Int is 32-bit). SPIDs are auto-trimmed (stored as CHAR(4) with trailing spaces).\n**Note:** Service providers have a composite PK of (spid, npac_region) \u2014 the same SPID may appear in multiple NPAC regions.\n\n---\n\n## REST Paths for Direct API Access\n\nThe MCP tools wrap these HTTP endpoints. Use this table when calling the Telique API directly with `curl` or another HTTP client. Base URL: `https://api-dev.ringer.tel`. Authentication: `-H \"x-api-token: tlq_\u2026\"` (per-request header, never in query string).\n\n| Tool | Method | Path | Notes |\n|------|--------|------|-------|\n| `lrn_lookup` | GET | `/v1/telique/lrn/{phone_number}` | Add `?format=json` for structured response; default is `LRN;SPID` plain text |\n| `cnam_lookup` | GET | `/v1/telique/cnam/{phone_number}` | Returns calling_name, presentation_indicator |\n| `dno_check` | GET | `/v1/telique/dno/{phone_number}` | Add `?format=json` for details; default is `true`/`false` text |\n| `lrn_relationship_query` | GET | `/v1/telique/lsms/list/{resource}?{filter}={value}` | `resource` \u2208 {phone_number, spid, lrn}; filter \u2208 {lrn, spid, phone_number}; exactly one filter |\n| `lerg_table_info` | GET | `/v1/telique/lerg/tables` or `/v1/telique/lerg/tables/{table_name}` | No args = list all tables |\n| `lerg_query` | GET | `/v1/telique/lerg/{table_name}/{fields}/{query}` | Example: `/lerg/lerg_6/npa,nxx,ocn/npa=303&nxx=629` |\n| `lerg_complex_query` | POST | `/v1/telique/lerg/query` | JSON body with table, fields, filters, join, limit, offset |\n| `lerg_tandem` | GET | `/v1/telique/lerg/tandem?npa={npa}&nxx={nxx}` | Pre-joined tandem lookup |\n| `routelink_lookup` (ror) | GET | `/v1/telique/ror/{crn}` | Responsible Organization for a toll-free number |\n| `routelink_lookup` (cic/cicror) | GET | `/v1/telique/{cic\\|cicror}/{crn}/{ani}/{lata}` | CIC or CIC+ROR for a toll-free call |\n| `routelink_ror_query` | GET | `/v1/telique/ror/{ror}/{tfns\\|cprs}` | List TFNs or CPRs for a ROR; paginated `?limit=&offset=` |\n| `routelink_cpr` | GET | `/v1/telique/cpr/{crn}` | Call Processing Record; add `?expand=true` to inline templates |\n| `graphql_query` (lerg) | POST | `/v1/telique/lerg/gql` | JSON body `{\"query\":\"...\"}`; GET returns GraphiQL playground HTML |\n| `graphql_query` (lsms) | POST | `/v1/telique/lsms/gql` | JSON body `{\"query\":\"...\"}`; GET returns GraphiQL playground HTML |\n| `lookup_tn` | \u2014 | (composite) | Not a single endpoint \u2014 fans out to `/lrn/`, `/cnam/`, `/dno/`, `/lerg/\u2026` in parallel |\n\n**Note on RouteLink paths**: there is NO `/routelink/` segment. The public OpenAPI spec at `https://telique.ringer.tel/docs/api-reference` historically listed `/v1/telique/routelink/cpr/{crn}` etc. \u2014 those paths return 404. The real paths are bare (`/v1/telique/cpr/{crn}`) as shown above.\n\n---\n\n## Key Telecom Concepts\n\n| Term | Definition |\n|------|-----------|\n| **LRN** | Location Routing Number \u2014 identifies the switch serving a ported number |\n| **SPID** | Service Provider ID \u2014 identifies the carrier that owns a number |\n| **OCN** | Operating Company Number \u2014 4-char carrier identifier (often same as SPID) |\n| **NPA** | Numbering Plan Area \u2014 3-digit area code |\n| **NXX** | Exchange code \u2014 next 3 digits after area code |\n| **LATA** | Local Access Transport Area \u2014 geographic region for call routing |\n| **CLLI** | Common Language Location Identifier \u2014 8-11 char switch/building code |\n| **CRN** | Call Routing Number \u2014 a toll-free number in RouteLink context |\n| **ROR/RespOrg** | Responsible Organization \u2014 entity managing a toll-free number's routing |\n| **CIC** | Carrier Identification Code \u2014 identifies which carrier handles a toll-free call |\n| **CPR** | Call Processing Record \u2014 routing decision tree for a toll-free number |\n| **Rate Center** | Geographic area defining local calling boundaries |\n| **Tandem** | A switching office that connects local switches to the long-distance network |\n| **Homing** | The relationship between a local switch and its tandem |\n\n---\n\n## Common Mistakes to Avoid\n\n1. **Never guess carrier names** \u2014 always query lerg_1 by OCN\n2. **Never skip the LRN dip** \u2014 ported TNs have different NPA-NXX than their LRN\n3. **Never look for tandem/switch data in LSMS** \u2014 LSMS has no infrastructure data\n4. **Never look for TN-level data in LERG** \u2014 LERG has no per-phone-number data\n5. **Never query LSMS subscriptionVersions without a filter** \u2014 514M rows will timeout\n6. **Always use the LRN's NPA-NXX** (not the TN's) for LERG routing lookups\n7. **GraphQL return fields must be camelCase** \u2014 `ocnName` not `ocn_name`, `locState` not `loc_state`\n8. **GraphQL LIKE patterns must be UPPERCASE** \u2014 LERG data is uppercase in PostgreSQL, so `%VERIZON%` works but `%verizon%` returns nothing. (REST `like` is case-insensitive \u2014 this only applies to GraphQL.)\n9. **IN operator uses `values` (plural)** \u2014 `{ field: \"npa\", op: IN, values: [\"212\", \"646\"] }` not `value`\n";
3
+ export declare const TELIQUE_KNOWLEDGE = "# Telique Telecom API Knowledge Base\n\nYou have access to 13 Telique tools for querying live telecom data. NEVER guess carrier names, LRNs, routing data, or CNAM \u2014 always query the API.\n\n---\n\n## CRITICAL: LERG vs LSMS \u2014 Two Different Databases\n\n**LERG** = Static telecom infrastructure reference (updated monthly from iconectiv BIRRDS)\n- NPA-NXX assignments, switches, tandems, homing arrangements, rate centers, LATAs, carrier names by OCN\n- 27 tables. NO individual phone number data.\n- Query with: `lerg_query`, `lerg_complex_query`, `lerg_tandem`, `lerg_table_info`, or `graphql_query(service=\"lerg\")`\n\n**LSMS** = Live NPAC porting data (refreshed within minutes)\n- Phone number ownership (SPID), LRN assignments, porting state\n- NO routing infrastructure (no tandems, switches, homing, rate centers, OCN names)\n- Query with: `lrn_lookup`, `lrn_relationship_query`, or `graphql_query(service=\"lsms\")`\n\n| Data Type | Database | Tool |\n|-----------|----------|------|\n| Tandem switches | **LERG** | `lerg_tandem` |\n| Switch/CLLI details | **LERG** | `lerg_query` on lerg_7 |\n| Homing arrangements | **LERG** | `lerg_query` on lerg_7_sha |\n| NPA-NXX routing (OCN, switch, LATA, rate center) | **LERG** | `lerg_query` on lerg_6 |\n| Carrier/OCN names | **LERG** | `lerg_query` on lerg_1 |\n| Current TN ownership (SPID) | **LSMS** | `lrn_lookup` |\n| Current LRN for a TN | **LSMS** | `lrn_lookup` |\n| Porting state | **LSMS** | `lrn_relationship_query` or GraphQL |\n\n---\n\n## CRITICAL: Always Dip the LRN First\n\nA ported phone number's NPA-NXX often differs from its LRN's NPA-NXX. For ANY routing question about a specific phone number:\n\n1. **`lrn_lookup`** \u2192 get the LRN and SPID\n2. Extract NPA-NXX from the **LRN** (first 3 digits = NPA, next 3 = NXX)\n3. Use the **LRN's** NPA-NXX for all LERG lookups\n\n**Example:** TN 303-629-8301\n- WRONG: Look up 303-629 in LERG \u2192 returns the original carrier's data (before porting)\n- CORRECT: `lrn_lookup(\"3036298301\")` \u2192 LRN = 7207081999 \u2192 look up 720-708 in LERG \u2192 returns current carrier\n\nThis applies to: tandem, switch, LATA, OCN, rate center \u2014 anything keyed by NPA-NXX.\n\n### The Golden Pattern for Routing Questions\n\n```\nStep 1: lrn_lookup({phone_number}) \u2192 get LRN and SPID\nStep 2: Extract NPA (first 3 digits of LRN) and NXX (next 3 digits of LRN)\nStep 3: Use LRN's NPA-NXX for LERG queries:\n - lerg_tandem({npa, nxx}) \u2192 tandem switch\n - lerg_query(\"lerg_6\", ..., \"npa={npa}&nxx={nxx}\") \u2192 OCN, switch, LATA, rate center\n - lerg_query(\"lerg_7\", ..., \"switch={clli}\") \u2192 switch details\n```\n\nOr use `lookup_tn` for a quick consolidated view (dips LRN + CNAM + DNO + LERG in parallel).\n\n---\n\n## LERG Table Reference\n\n### Big 4 \u2014 Cover ~80% of Queries\n\n**lerg_1** \u2014 OCN/carrier directory\n- Fields: ocn_num, ocn_name, abbre_ocn_name, ocn_state, category, overall_ocn\n- Use: Look up carrier name by OCN. Join from lerg_6 `ocn` \u2192 lerg_1 `ocn_num`.\n- Example: `lerg_query(\"lerg_1\", \"ocn_num,ocn_name,ocn_state\", \"ocn_num=567G\")`\n\n**lerg_6** \u2014 NPA-NXX block assignments (the workhorse)\n- Fields: npa, nxx, block_id, lata, lata_name, loc_state, ocn, aocn, switch, sha_indicator, rc_abbre, rc_type, coc_type, eff_date, status\n- Has LATA + state + NPA + NXX + OCN + switch + rate center all on one row.\n- Example: `lerg_query(\"lerg_6\", \"npa,nxx,block_id,loc_name,loc_state,ocn,switch,lata\", \"npa=720&nxx=708\")`\n\n**CRITICAL: Understanding block_id and NPA-NXX ownership:**\n- `block_id=\"A\"` = the **Code Holder** \u2014 the original LERG owner of the entire NPA-NXX. This is THE owner of the NPA-NXX.\n- `block_id=\"0\"` through `\"9\"` = **Block Holders** \u2014 carriers who received a pooled 1,000-number block (thousands-block pooling). Each block covers numbers x000-x999 within the NXX.\n- When asked \"who owns NPA-NXX 720-708?\", the answer is the OCN on the `block_id=\"A\"` row.\n- A query for `npa=720&nxx=708` may return multiple rows \u2014 one for block_id=\"A\" (the Code Holder) and additional rows for pooled blocks (0-9). Always filter or identify the \"A\" row for ownership questions.\n- To find which carrier serves a specific phone number within a pooled NXX, use the LRN dip pattern instead (the block_id approach only tells you who holds the block, not who currently serves an individual ported number).\n\n**lerg_7_sha** \u2014 Switch homing arrangements\n- Fields: switch, sha_indicator, h_trm_d_tdm (FGD tandem), host, ocn\n- Match `switch` + `sha_indicator` from lerg_6 to find the correct tandem.\n- Or use `lerg_tandem` which does the join automatically.\n\n**lerg_12** \u2014 LRN registry\n- Fields: lrn, lata, lata_name, switch, ocn, status, eff_date\n- Which company/switch established each LRN.\n\n### Supporting Tables\n\n| Table | Purpose |\n|-------|---------|\n| lerg_3 | NPA (area code) metadata \u2014 state, effective date |\n| lerg_5 | NPA/LATA cross-reference \u2014 which NPAs exist in a LATA |\n| lerg_7 | Switch/CLLI details \u2014 address, coordinates, equipment type |\n| lerg_8 | Rate center details \u2014 geography, V&H coordinates |\n| lerg_8_loc | Localities (towns) \u2192 rate centers |\n| lerg_8_pst | ZIP codes \u2192 localities (US only) |\n| lerg_9 | Homing by tandem \u2014 \"top-down\" view of which NPA-NXXs subtend a tandem |\n| lerg_4 | SS7 point codes |\n| lerg_10 | NPA-NXX \u2192 operator services ATC |\n| lerg_11 | Locality \u2192 operator services ATC |\n| lerg_16 | IP capability by LRN |\n| lerg_17 | IP capability by NPA-NXX |\n\nUse `lerg_table_info` to list all tables or get the schema for a specific one.\n\n---\n\n## LERG Query Syntax\n\nREST queries use path-based filters with `&` joining multiple conditions:\n- Single filter: `lerg_query(\"lerg_1\", \"ocn_num,ocn_name\", \"ocn_state=CO\")`\n- Multiple filters: `lerg_query(\"lerg_6\", \"npa,nxx,loc_name,ocn\", \"npa=303&nxx=629\")`\n\nFor complex queries with JOINs and advanced operators, use `lerg_complex_query`:\n```json\n{\n \"table\": \"lerg_6\",\n \"fields\": [\"npa\", \"nxx\", \"ocn\", \"loc_name\"],\n \"filters\": [{\"field\": \"npa\", \"operator\": \"eq\", \"value\": 720}],\n \"join\": {\n \"table\": \"lerg_1\",\n \"on\": [{\"left_field\": \"ocn\", \"right_field\": \"ocn_num\"}],\n \"fields\": [\"ocn_name\", \"ocn_state\"]\n }\n}\n```\n\nFilter operators: eq, ne, gt, gte, lt, lte, like, in, notin, isnull, isnotnull.\nUse `*` as the fields value to return all fields from a table.\nNote: REST `like` is **case-insensitive** (in-memory matching), unlike GraphQL LIKE which is case-sensitive (PostgreSQL).\n\n---\n\n## When to Use REST vs GraphQL\n\n| Use Case | Best Tool | Why |\n|----------|-----------|-----|\n| Single LRN/SPID for a phone number | `lrn_lookup` (REST) | Sub-millisecond, in-memory |\n| Carrier name by OCN | `lerg_query` (REST) | Simple single-field lookup |\n| NPA-NXX routing info | `lerg_query` (REST) | Fast, straightforward |\n| Tandem routing | `lerg_tandem` (REST) | Pre-built JOIN, optimized |\n| NPA-NXX + carrier name + switch in one call | `graphql_query` (LERG) | Nested relationships avoid multiple round trips |\n| Filter by fields not in REST (e.g., all switches for an OCN) | `graphql_query` (LERG) | FilterInput supports any field |\n| Cross-table JOIN with arbitrary conditions | `graphql_query` (LERG dynamicJoin) | Only way to do ad-hoc joins |\n| All phone numbers for an LRN (paginated with totalCount) | `graphql_query` (LSMS) | SVConnection returns totalCount + hasMore |\n| Quick profile of a phone number | `lookup_tn` (composite) | Parallel dip across LRN + CNAM + DNO + LERG |\n\n**Rule of thumb:** Use REST for simple lookups (one table, one or two filters). Use GraphQL when you need joins, relationship traversal, or fields not exposed by REST endpoints.\n\n---\n\n## LSMS / LRN REST Endpoints\n\nThe LRN API serves live NPAC porting data from an in-memory Judy Array store (500M+ records, sub-millisecond) backed by a PostgreSQL LSMS database.\n\n### LRN Lookup (`lrn_lookup` tool)\n\n`GET /v1/telique/lrn/{phone_number}?format=json`\n\nReturns the current LRN and carrier for a phone number. This is the fastest lookup \u2014 served from in-memory store, not PostgreSQL.\n\n**JSON response shape:**\n```json\n{\n \"phone_number\": \"3036298301\",\n \"lrn\": \"7207081999\",\n \"status\": \"success\",\n \"timestamp\": \"2025-08-07T02:30:00Z\",\n \"metadata\": {\n \"spid\": \"567G\",\n \"lnp_type\": \"lspp\",\n \"activation_timestamp\": \"2024-01-15T10:30:00Z\",\n \"last_updated\": \"2025-08-07T01:30:00Z\"\n }\n}\n```\n\nDefault format is plain text (`LRN;SPID`, e.g., `7207081999;567G`). Use `?format=json` for structured response.\n\n### LSMS Relationship Queries (`lrn_relationship_query` tool)\n\nQuery relationships between phone numbers, LRNs, and SPIDs from the LSMS PostgreSQL database. These hit the database directly and have higher latency than the in-memory LRN lookup.\n\n**6 query types and their endpoints:**\n\n| query_type | Endpoint | Description |\n|------------|----------|-------------|\n| `phones_by_lrn` | `GET /v1/telique/lsms/list/phone_number?lrn={value}` | All phone numbers for a given LRN |\n| `phones_by_spid` | `GET /v1/telique/lsms/list/phone_number?spid={value}` | All phone numbers for a given SPID |\n| `spid_by_lrn` | `GET /v1/telique/lsms/list/spid?lrn={value}` | All SPIDs associated with a given LRN |\n| `spid_by_phone` | `GET /v1/telique/lsms/list/spid?phone_number={value}` | All SPIDs for a given phone number |\n| `lrn_by_spid` | `GET /v1/telique/lsms/list/lrn?spid={value}` | All LRNs for a given SPID |\n| `lrn_by_phone` | `GET /v1/telique/lsms/list/lrn?phone_number={value}` | All LRNs for a given phone number |\n\n**Constraints:**\n- Exactly ONE query parameter per request (multiple parameters return a validation error)\n- Results are filtered to active records only\n- Phone numbers and LRNs are returned as strings\n\n**Example response (phones_by_lrn):**\n```json\n{\n \"phone_numbers\": [\"3036298301\", \"3039982743\", \"3033334444\"],\n \"count\": 3,\n \"query\": { \"lrn\": \"7207081999\", \"spid\": null, \"phone_number\": null }\n}\n```\n\n---\n\n## CNAM (Caller Name)\n\n`cnam_lookup` queries TransUnion's LIDB for the caller name associated with a phone number.\n\n- Returns: calling_name (up to 15 chars), calling_name_status (available/unavailable), presentation_indicator (allowed/restricted)\n- Results cached server-side for 24 hours\n- \"WIRELESS CALLER\" typically means the carrier hasn't provisioned a specific CNAM entry\n\n---\n\n## DNO (Do Not Originate)\n\n`dno_check` checks if a phone number should never appear as a caller ID.\n\n- DNO numbers belong to entities that only receive calls (IRS, major banks, government agencies)\n- If is_dno=true, the number appearing as caller ID indicates **spoofing/fraud**\n- Supports prefix matching: 3-digit (NPA), 6-digit (NPA-NXX), 7-digit, or 10-digit patterns\n- Response includes: is_dno, matched_pattern, source\n- Phone numbers are auto-normalized: E.164 (+12003456789), domestic (12003456789), and international (0012003456789) formats all accepted\n\n---\n\n## RouteLink \u2014 Toll-Free Number Routing\n\n**What is a toll-free number?** A phone number with an 8XX NPA (area code) that is free for the caller \u2014 the called party pays. Toll-free NPAs: **800, 888, 877, 866, 855, 844, 833, 822**. Any 10-digit number starting with one of these NPAs is a toll-free number (also called TFN or CRN in RouteLink context).\n\nToll-free numbers are managed by the **Somos TFN Registry**, not NPAC/LSMS. They have their own routing system (CPR decision trees) and their own management hierarchy (RespOrgs, not SPIDs). Use the RouteLink tools for toll-free lookups \u2014 do NOT use `lrn_lookup` for toll-free numbers (they don't have LRNs).\n\n### ROR (Responsible Organization)\n`routelink_lookup` with lookup_type=\"ror\" returns the RespOrg managing a toll-free number.\n- RespOrg is a 5-char code (e.g., \"NEX01\", \"TZN99\")\n- Only needs the CRN (toll-free number), no ANI/LATA required\n\n### CIC (Carrier Identification Code)\n`routelink_lookup` with lookup_type=\"cic\" or \"cicror\" returns which carrier handles calls to a toll-free number FROM a specific caller (ANI) in a specific LATA.\n- Requires: crn, ani (10-digit), lata (3-digit)\n- Common carrier codes: 0288 (AT&T), 0222 (MCI/Verizon), 0333 (Sprint), 0432 (Lumen)\n\n### CPR (Call Processing Record)\n`routelink_cpr` retrieves the full routing decision tree for a toll-free number.\n\n**Decision tree node types:**\n| Node | Description |\n|------|-------------|\n| [LATA] | Routes by caller's LATA |\n| [NPA] | Routes by caller's area code |\n| [NXX] | Routes by caller's exchange |\n| [STATE] | Routes by caller's state |\n| [DAY_OF_WEEK] | Routes by day (1-7, Sunday=1) |\n| [TIME_OF_DAY] | Routes by time (15-min intervals) |\n| [PERCENT] | Percentage-based load balancing |\n| [ANI] | Routes by specific caller number |\n\n**Action types:** Carrier XXXX (4-digit carrier code), Template XXXXXXXXXX (reference to template CRN), Routing XXXXX, NMC X (Network Management Class)\n\n**Common patterns:**\n- Simple: All calls \u2192 one carrier \u2192 one termination number\n- Time-of-day: Business hours \u2192 office, after hours \u2192 answering service\n- Geographic: Different terminations per LATA/state/NPA\n- Percentage: Load balancing across call centers (e.g., 60%/40%)\n\nUse `expand=true` (default) to recursively resolve template references into their full decision trees.\n\n---\n\n## GraphQL\n\nUse `graphql_query` for complex queries not possible with REST:\n- Cross-table relationship joins (lerg6 \u2192 carrier, lerg6 \u2192 switchInfo, lerg7Sha \u2192 tandemSwitch)\n- Filtering by fields not in REST endpoints\n- Multiple related data points in one query\n\n### LERG GraphQL (`service=\"lerg\"`)\n\n**CRITICAL naming rules:**\n- **Query names** are camelCase: `lerg1`, `lerg6`, `lerg7Sha`, `lerg7ShaIns`, `lerg12`, etc.\n- **Object-type names use a `Gql` prefix.** `__type(name: \"Lerg6\")` returns null \u2014 the actual type is `GqlLerg6`. Rule: snake_case table ID \u2192 PascalCase + `Gql` prefix (`lerg_6` \u2192 `GqlLerg6`, `lerg_7_sha` \u2192 `GqlLerg7Sha`, `lerg_8_loc` \u2192 `GqlLerg8Loc`). Use this when introspecting types directly.\n- **Return fields** MUST be camelCase: `ocnName`, `locState`, `locName`, `shaIndicator`, `hTrmDTdm`\n- **REST fields are snake_case; GraphQL fields are camelCase \u2014 1:1 convertible.** `coc_type` \u2194 `cocType`, `loc_name` \u2194 `locName`, `ocn_num` \u2194 `ocnNum`, `sha_indicator` \u2194 `shaIndicator`. GraphQL `FilterInput` `field:` accepts both forms; REST query paths and GraphQL selection sets do not.\n- **Filter field names** accept BOTH camelCase (`ocnName`) and snake_case (`ocn_name`)\n- **All values are strings** \u2014 even numeric fields. Always quote: `value: \"720\"` not `value: 720`\n- **GraphQL LIKE is case-sensitive (SQL)** \u2014 LERG data is stored UPPERCASE, so patterns must be uppercase: `%VERIZON%` not `%verizon%`. (The REST `like` operator is case-insensitive \u2014 this only applies to GraphQL.)\n\n**FilterInput:**\n```graphql\n{ field: \"ocnName\", op: LIKE, value: \"%VERIZON%\" } # partial match (UPPERCASE!)\n{ field: \"npa\", op: EQ, value: \"720\" } # exact match\n{ field: \"npa\", op: IN, values: [\"212\", \"646\", \"917\"] } # IN uses 'values' (plural), not 'value'\n```\n\nOperators: EQ, NE, GT, GTE, LT, LTE, LIKE, IN, IS_NULL, IS_NOT_NULL\n\n**Predefined relationships** (avoid N+1 \u2014 use these instead of separate queries):\n- `lerg6` \u2192 `carrier` (joins to lerg1 via OCN), `switchInfo` (\u2192 lerg7), `homingArrangements` (\u2192 lerg7Sha)\n- `lerg7` \u2192 `carrier` (\u2192 lerg1)\n- `lerg7Sha` \u2192 `tandemSwitch` (\u2192 lerg7)\n\n**Example \u2014 find all Verizon OCNs:**\n```graphql\n{\n lerg1(\n filters: [{ field: \"ocnName\", op: LIKE, value: \"%VERIZON%\" }]\n pagination: { limit: 10 }\n ) {\n ocnNum\n ocnName\n ocnState\n category\n }\n}\n```\n\n**Example \u2014 NPA-NXX with carrier and switch in one query:**\n```graphql\n{\n lerg6(\n filters: [{ field: \"npa\", op: EQ, value: \"303\" }, { field: \"nxx\", op: EQ, value: \"629\" }]\n pagination: { limit: 1 }\n ) {\n npa nxx ocn locName switch\n carrier { ocnNum ocnName ocnState }\n switchInfo { switch eqpType swCity swState }\n homingArrangements {\n shaIndicator hTrmDTdm\n tandemSwitch { switch swCity swState }\n }\n }\n}\n```\n\n**dynamicJoin** \u2014 for arbitrary cross-table SQL joins (returns raw JSON):\n```graphql\n{\n dynamicJoin(input: {\n table: \"lerg_6\"\n fields: [\"npa\", \"nxx\", \"ocn\", \"locName\"]\n filters: [{ field: \"locName\", op: LIKE, value: \"%DENVER%\" }]\n join: {\n table: \"lerg_1\"\n fields: [\"ocnName\", \"category\"]\n on: [{ leftField: \"ocn\", rightField: \"ocnNum\" }]\n joinType: \"INNER\"\n }\n pagination: { limit: 10 }\n })\n}\n```\nNote: dynamicJoin uses snake_case table IDs (lerg_6, lerg_1, lerg_7_sha) and returns raw PostgreSQL column names (UPPERCASE with spaces, e.g., `\"OCN_NAME\"`, `\"LOC NAME\"`, `\"EFF DATE\"`), NOT GraphQL camelCase.\n\n### LSMS GraphQL (`service=\"lsms\"`)\n\nThe LSMS GraphQL API is a **completely separate implementation** from LERG GraphQL. It has different query patterns, different field naming, and different schema design. Do NOT use LERG-style FilterInput syntax with LSMS.\n\n**5 tables:**\n\n| Table | ~Rows | Requires Filter? |\n|-------|-------|------------------|\n| subscriptionVersions | 514M | Yes (phoneNumber, lrn, or spid) |\n| numberBlocks | 751K | Yes (npanxxx, spid, or lrn) |\n| serviceProviders | 5.2K | No |\n| locationRoutingNumbers | 56K | No |\n| npanxx | 192K | No |\n\n**Query patterns (NOT FilterInput \u2014 uses typed named parameters):**\n```graphql\n# Single phone number lookup\n{ subscriptionVersion(phoneNumber: \"3036298301\") { phoneNumber lrn spid serviceProvider { name } } }\n\n# Paginated list by LRN (returns totalCount, hasMore)\n{ subscriptionVersionsByLrn(lrn: \"7207081999\", limit: 10) { totalCount hasMore items { phoneNumber spid } } }\n\n# Paginated list by SPID\n{ subscriptionVersionsBySpid(spid: \"567G\", limit: 10) { totalCount hasMore items { phoneNumber lrn } } }\n\n# Number block lookup (single)\n{ numberBlock(npanxxx: \"3035551\") { npanxxx lrn spid serviceProvider { name } } }\n\n# Number blocks by SPID or LRN (paginated \u2014 at least one filter required)\n{ numberBlocks(spid: \"567G\", limit: 10) { totalCount hasMore items { npanxxx lrn spid } } }\n\n# Single carrier lookup (note: composite PK \u2014 same SPID may exist in multiple regions)\n{ serviceProvider(spid: \"567G\") { spid name npacRegion status contactInfo } }\n\n# List carriers (paginated)\n{ serviceProviders(limit: 20) { spid name npacRegion } }\n\n# LRN metadata\n{ locationRoutingNumber(lrn: \"7207081999\") { lrn ocn regionId status switchInfo } }\n\n# NPA-NXX single lookup\n{ npanxx(npa: \"303\", nxx: \"629\") { npa nxx spid effectiveTimestamp serviceProvider { name } } }\n\n# NPA-NXX codes for a carrier (paginated)\n{ npanxxBySpid(spid: \"567G\", limit: 50) { npa nxx effectiveTimestamp } }\n\n# Database statistics\n{ lsmsStats { activeSubscriptionVersions activeNumberBlocks totalServiceProviders totalLocationRoutingNumbers activeNpanxx } }\n```\n\n**Relationships** (use DataLoader batching \u2014 no N+1):\n- subscriptionVersion \u2192 `serviceProvider`, `lrnMetadata`\n- numberBlock \u2192 `serviceProvider`, `lrnMetadata`\n- npanxx \u2192 `serviceProvider`\n\n**Safety limits:** max 1000 results, depth 5, complexity 200, 10-second statement timeout\n**Auto-filters:** Soft-deletable records filter to is_active = true automatically\n**Note:** Phone numbers/LRNs are strings (stored as BIGINT but GraphQL Int is 32-bit). SPIDs are auto-trimmed (stored as CHAR(4) with trailing spaces).\n**Note:** Service providers have a composite PK of (spid, npac_region) \u2014 the same SPID may appear in multiple NPAC regions.\n\n---\n\n## REST Paths for Direct API Access\n\nThe MCP tools wrap these HTTP endpoints. Use this table when calling the Telique API directly with `curl` or another HTTP client. Base URL: `https://api-dev.ringer.tel`. Authentication: `-H \"x-api-token: tlq_\u2026\"` (per-request header, never in query string).\n\n| Tool | Method | Path | Notes |\n|------|--------|------|-------|\n| `lrn_lookup` | GET | `/v1/telique/lrn/{phone_number}` | Add `?format=json` for structured response; default is `LRN;SPID` plain text |\n| `cnam_lookup` | GET | `/v1/telique/cnam/{phone_number}` | Returns calling_name, presentation_indicator |\n| `dno_check` | GET | `/v1/telique/dno/{phone_number}` | Add `?format=json` for details; default is `true`/`false` text |\n| `lrn_relationship_query` | GET | `/v1/telique/lsms/list/{resource}?{filter}={value}` | `resource` \u2208 {phone_number, spid, lrn}; filter \u2208 {lrn, spid, phone_number}; exactly one filter |\n| `lerg_table_info` | GET | `/v1/telique/lerg/tables` or `/v1/telique/lerg/tables/{table_name}` | No args = list all tables |\n| `lerg_query` | GET | `/v1/telique/lerg/{table_name}/{fields}/{query}` | Example: `/lerg/lerg_6/npa,nxx,ocn/npa=303&nxx=629` |\n| `lerg_complex_query` | POST | `/v1/telique/lerg/query` | JSON body with table, fields, filters, join, limit, offset |\n| `lerg_tandem` | GET | `/v1/telique/lerg/tandem?npa={npa}&nxx={nxx}` | Pre-joined tandem lookup |\n| `routelink_lookup` (ror) | GET | `/v1/telique/ror/{crn}` | Responsible Organization for a toll-free number |\n| `routelink_lookup` (cic/cicror) | GET | `/v1/telique/{cic\\|cicror}/{crn}/{ani}/{lata}` | CIC or CIC+ROR for a toll-free call |\n| `routelink_ror_query` | GET | `/v1/telique/ror/{ror}/{tfns\\|cprs}` | List TFNs or CPRs for a ROR; paginated `?limit=&offset=` |\n| `routelink_cpr` | GET | `/v1/telique/cpr/{crn}` | Call Processing Record; add `?expand=true` to inline templates |\n| `graphql_query` (lerg) | POST | `/v1/telique/lerg/gql` | JSON body `{\"query\":\"...\"}`; GET returns GraphiQL playground HTML |\n| `graphql_query` (lsms) | POST | `/v1/telique/lsms/gql` | JSON body `{\"query\":\"...\"}`; GET returns GraphiQL playground HTML |\n| `lookup_tn` | \u2014 | (composite) | Not a single endpoint \u2014 fans out to `/lrn/`, `/cnam/`, `/dno/`, `/lerg/\u2026` in parallel |\n\n**Note on RouteLink paths**: there is NO `/routelink/` segment. The public OpenAPI spec at `https://telique.ringer.tel/docs/api-reference` historically listed `/v1/telique/routelink/cpr/{crn}` etc. \u2014 those paths return 404. The real paths are bare (`/v1/telique/cpr/{crn}`) as shown above.\n\n---\n\n## Key Telecom Concepts\n\n| Term | Definition |\n|------|-----------|\n| **LRN** | Location Routing Number \u2014 identifies the switch serving a ported number |\n| **SPID** | Service Provider ID \u2014 identifies the carrier that owns a number |\n| **OCN** | Operating Company Number \u2014 4-char carrier identifier (often same as SPID) |\n| **NPA** | Numbering Plan Area \u2014 3-digit area code |\n| **NXX** | Exchange code \u2014 next 3 digits after area code |\n| **LATA** | Local Access Transport Area \u2014 geographic region for call routing |\n| **CLLI** | Common Language Location Identifier \u2014 8-11 char switch/building code |\n| **CRN** | Call Routing Number \u2014 a toll-free number in RouteLink context |\n| **ROR/RespOrg** | Responsible Organization \u2014 entity managing a toll-free number's routing |\n| **CIC** | Carrier Identification Code \u2014 identifies which carrier handles a toll-free call |\n| **CPR** | Call Processing Record \u2014 routing decision tree for a toll-free number |\n| **Rate Center** | Geographic area defining local calling boundaries |\n| **Tandem** | A switching office that connects local switches to the long-distance network |\n| **Homing** | The relationship between a local switch and its tandem |\n\n---\n\n## Common Mistakes to Avoid\n\n1. **Never guess carrier names** \u2014 always query lerg_1 by OCN\n2. **Never skip the LRN dip** \u2014 ported TNs have different NPA-NXX than their LRN\n3. **Never look for tandem/switch data in LSMS** \u2014 LSMS has no infrastructure data\n4. **Never look for TN-level data in LERG** \u2014 LERG has no per-phone-number data\n5. **Never query LSMS subscriptionVersions without a filter** \u2014 514M rows will timeout\n6. **Always use the LRN's NPA-NXX** (not the TN's) for LERG routing lookups\n7. **GraphQL return fields must be camelCase** \u2014 `ocnName` not `ocn_name`, `locState` not `loc_state`\n8. **GraphQL LIKE patterns must be UPPERCASE** \u2014 LERG data is uppercase in PostgreSQL, so `%VERIZON%` works but `%verizon%` returns nothing. (REST `like` is case-insensitive \u2014 this only applies to GraphQL.)\n9. **IN operator uses `values` (plural)** \u2014 `{ field: \"npa\", op: IN, values: [\"212\", \"646\"] }` not `value`\n";
package/dist/knowledge.js CHANGED
@@ -301,7 +301,9 @@ Use \`graphql_query\` for complex queries not possible with REST:
301
301
 
302
302
  **CRITICAL naming rules:**
303
303
  - **Query names** are camelCase: \`lerg1\`, \`lerg6\`, \`lerg7Sha\`, \`lerg7ShaIns\`, \`lerg12\`, etc.
304
+ - **Object-type names use a \`Gql\` prefix.** \`__type(name: "Lerg6")\` returns null — the actual type is \`GqlLerg6\`. Rule: snake_case table ID → PascalCase + \`Gql\` prefix (\`lerg_6\` → \`GqlLerg6\`, \`lerg_7_sha\` → \`GqlLerg7Sha\`, \`lerg_8_loc\` → \`GqlLerg8Loc\`). Use this when introspecting types directly.
304
305
  - **Return fields** MUST be camelCase: \`ocnName\`, \`locState\`, \`locName\`, \`shaIndicator\`, \`hTrmDTdm\`
306
+ - **REST fields are snake_case; GraphQL fields are camelCase — 1:1 convertible.** \`coc_type\` ↔ \`cocType\`, \`loc_name\` ↔ \`locName\`, \`ocn_num\` ↔ \`ocnNum\`, \`sha_indicator\` ↔ \`shaIndicator\`. GraphQL \`FilterInput\` \`field:\` accepts both forms; REST query paths and GraphQL selection sets do not.
305
307
  - **Filter field names** accept BOTH camelCase (\`ocnName\`) and snake_case (\`ocn_name\`)
306
308
  - **All values are strings** — even numeric fields. Always quote: \`value: "720"\` not \`value: 720\`
307
309
  - **GraphQL LIKE is case-sensitive (SQL)** — LERG data is stored UPPERCASE, so patterns must be uppercase: \`%VERIZON%\` not \`%verizon%\`. (The REST \`like\` operator is case-insensitive — this only applies to GraphQL.)
package/dist/setup.js CHANGED
@@ -3,6 +3,7 @@ import { mkdirSync, writeFileSync, existsSync, readFileSync } from "node:fs";
3
3
  import { execSync, execFileSync } from "node:child_process";
4
4
  import { stdin, stdout } from "node:process";
5
5
  import { CONFIG_DIR, CONFIG_FILE } from "./config.js";
6
+ import { TeliqueClient } from "./client.js";
6
7
  import { ICON_DARK_DATA_URI } from "./icons.js";
7
8
  const REGISTER_URL = "https://telique.ringer.tel/register";
8
9
  const API_BASE_URL = "https://api-dev.ringer.tel";
@@ -127,11 +128,13 @@ function detectMcpClients() {
127
128
  register: (token) => registerJsonConfig(cursorConfig, token),
128
129
  });
129
130
  }
130
- // Codex CLI
131
- if (commandExists("codex")) {
131
+ // Codex (CLI and/or Desktop — both share ~/.codex/config.toml,
132
+ // so one `codex mcp add` configures whichever surfaces are installed)
133
+ const codex = findCodexBinary();
134
+ if (codex) {
132
135
  clients.push({
133
- name: "Codex CLI",
134
- register: (token) => registerCodex(token),
136
+ name: codex.label,
137
+ register: (token) => registerCodex(token, codex.bin),
135
138
  });
136
139
  }
137
140
  // GitHub Copilot (VS Code)
@@ -184,11 +187,11 @@ function registerClaudeCode(token) {
184
187
  return false;
185
188
  }
186
189
  }
187
- function registerCodex(token) {
190
+ function registerCodex(token, bin = "codex") {
188
191
  try {
189
192
  // Remove existing entry first
190
193
  try {
191
- execFileSync("codex", ["mcp", "remove", "telique"], { stdio: "ignore" });
194
+ execFileSync(bin, ["mcp", "remove", "telique"], { stdio: "ignore" });
192
195
  }
193
196
  catch {
194
197
  // ignore
@@ -198,7 +201,7 @@ function registerCodex(token) {
198
201
  args.push("--env", `TELIQUE_API_TOKEN=${token}`);
199
202
  }
200
203
  args.push("--", "npx", "-y", "telique-mcp");
201
- execFileSync("codex", args, { stdio: "ignore" });
204
+ execFileSync(bin, args, { stdio: "ignore" });
202
205
  return true;
203
206
  }
204
207
  catch {
@@ -315,6 +318,25 @@ function commandExists(cmd) {
315
318
  return false;
316
319
  }
317
320
  }
321
+ function findCodexBinary() {
322
+ // Codex Desktop ships its own codex binary inside the .app bundle, and both
323
+ // it and the standalone CLI read/write the same ~/.codex/config.toml — so
324
+ // either binary is sufficient to register an MCP server for both surfaces.
325
+ const desktopBin = process.platform === "darwin"
326
+ ? "/Applications/Codex.app/Contents/Resources/codex"
327
+ : null;
328
+ const desktopAvailable = desktopBin !== null && existsSync(desktopBin);
329
+ if (commandExists("codex")) {
330
+ return {
331
+ bin: "codex",
332
+ label: desktopAvailable ? "Codex (CLI + Desktop)" : "Codex CLI",
333
+ };
334
+ }
335
+ if (desktopAvailable && desktopBin) {
336
+ return { bin: desktopBin, label: "Codex Desktop" };
337
+ }
338
+ return null;
339
+ }
318
340
  function loadExistingToken() {
319
341
  try {
320
342
  const raw = readFileSync(CONFIG_FILE, "utf-8");
@@ -334,12 +356,17 @@ function saveToken(token) {
334
356
  writeFileSync(CONFIG_FILE, JSON.stringify(existing, null, 2) + "\n");
335
357
  }
336
358
  async function validateToken(token) {
359
+ // Dip a documented, token-required endpoint. /health is an internal probe
360
+ // not in openapi.yaml and returns 403 from non-allowlisted IPs, which
361
+ // would falsely reject valid tokens during setup.
337
362
  try {
338
- const response = await fetch(`${API_BASE_URL}/health`, {
339
- headers: { "x-api-token": token },
340
- signal: AbortSignal.timeout(10000),
363
+ const client = new TeliqueClient({
364
+ baseUrl: API_BASE_URL,
365
+ apiToken: token,
366
+ requestTimeoutMs: 10000,
341
367
  });
342
- return response.ok;
368
+ const result = await client.get("/v1/telique/lerg/tables");
369
+ return (typeof result === "object" && result !== null && !("_error" in result));
343
370
  }
344
371
  catch {
345
372
  return false;
@@ -5,6 +5,8 @@ export function registerStatusTools(server, client) {
5
5
  server.tool("telique_status", "Returns the Telique MCP server version, authentication mode, and API connectivity status. Use this when asked about the server version or connection status.", {}, READ_ONLY_ANNOTATIONS, async () => {
6
6
  let apiReachable = false;
7
7
  try {
8
+ // Internal probe: /health is not in openapi.yaml and returns 403 from
9
+ // non-allowlisted IPs. Best-effort connectivity indicator only.
8
10
  const result = await client.get("/health");
9
11
  apiReachable =
10
12
  typeof result === "object" &&
package/openapi.yaml ADDED
@@ -0,0 +1,882 @@
1
+ openapi: 3.1.0
2
+ info:
3
+ title: Telique Telecom API
4
+ version: 1.0.0
5
+ description: |
6
+ Unified telecom data API providing access to LRN (Local Routing Number) lookups,
7
+ CNAM (Caller Name) identification, LERG (Local Exchange Routing Guide) infrastructure data,
8
+ toll-free number routing via RouteLink, and GraphQL interfaces for advanced queries.
9
+
10
+ ## Authentication
11
+
12
+ All endpoints require an API token passed via the `x-api-token` header.
13
+ Anonymous access is available with a rate limit of 10 requests per minute.
14
+ Authenticated access provides higher rate limits based on your subscription.
15
+
16
+ Get your API token at [telique.ringer.tel](https://telique.ringer.tel).
17
+
18
+ ## MCP Server
19
+
20
+ For AI-assisted development, install the Telique MCP server:
21
+ ```
22
+ npm install -g telique-mcp
23
+ ```
24
+
25
+ ## Key Concepts
26
+
27
+ - **LERG** — Static telecom infrastructure reference (27 tables, updated monthly from iconectiv BIRRDS). Contains NPA-NXX assignments, switches, tandems, rate centers, carrier names.
28
+ - **LSMS** — Live NPAC porting data (refreshed within minutes). Contains phone number ownership (SPID), LRN assignments, porting state.
29
+ - **LRN** — Local Routing Number. Identifies the switch serving a ported phone number. Always dip the LRN before doing LERG lookups on a specific phone number.
30
+ - **RouteLink** — Toll-free number routing. CPR (Call Processing Records) are decision trees that determine how toll-free calls are routed.
31
+ contact:
32
+ name: Ringer Support
33
+ email: support@ringer.com
34
+ url: https://telique.ringer.tel
35
+ license:
36
+ name: Proprietary
37
+
38
+ servers:
39
+ - url: https://api.telique.ringer.tel
40
+ description: Production
41
+
42
+ security:
43
+ - apiToken: []
44
+
45
+ tags:
46
+ - name: LRN
47
+ description: Local Routing Number lookups and LSMS relationship queries (live NPAC porting data)
48
+ - name: CNAM
49
+ description: Caller Name identification via TransUnion LIDB
50
+ - name: LERG
51
+ description: Local Exchange Routing Guide — static telecom infrastructure reference (27 tables)
52
+ - name: RouteLink
53
+ description: Toll-free number routing — CIC, ROR, and CPR lookups
54
+ - name: GraphQL
55
+ description: GraphQL interfaces for LSMS and LERG with advanced filtering and joins
56
+
57
+ paths:
58
+ # ── LRN ──────────────────────────────────────────────────────────
59
+ /v1/telique/lrn/{phone_number}:
60
+ get:
61
+ operationId: lrnLookup
62
+ tags: [LRN]
63
+ summary: Look up LRN for a phone number
64
+ description: |
65
+ Returns the Local Routing Number (LRN), SPID (Service Provider ID), LNP type,
66
+ and activation timestamp. LRN identifies the switch that serves a ported phone number.
67
+ Queries live LSMS/NPAC porting data.
68
+ parameters:
69
+ - name: phone_number
70
+ in: path
71
+ required: true
72
+ description: 10-digit US phone number
73
+ schema:
74
+ type: string
75
+ pattern: '^\d{10}$'
76
+ example: '3036298301'
77
+ responses:
78
+ '200':
79
+ description: LRN lookup result
80
+ content:
81
+ application/json:
82
+ schema:
83
+ $ref: '#/components/schemas/LrnLookupResponse'
84
+ example:
85
+ lrn: '7207081999'
86
+ spid: '6214'
87
+ lnp_type: '1'
88
+ activation_timestamp: '2019-03-15T00:00:00Z'
89
+ '404':
90
+ description: Phone number not found in LSMS
91
+ '429':
92
+ description: Rate limit exceeded
93
+
94
+ /v1/telique/lsms/list/{resource}:
95
+ get:
96
+ operationId: lrnRelationshipQuery
97
+ tags: [LRN]
98
+ summary: Query LSMS relationships
99
+ description: |
100
+ Find phone numbers by LRN or SPID, SPIDs by LRN or phone number, or LRNs by SPID
101
+ or phone number. Queries live NPAC porting data.
102
+ parameters:
103
+ - name: resource
104
+ in: path
105
+ required: true
106
+ description: The resource type to list
107
+ schema:
108
+ type: string
109
+ enum: [phone_number, spid, lrn]
110
+ - name: lrn
111
+ in: query
112
+ description: Filter by LRN (10-digit)
113
+ schema:
114
+ type: string
115
+ - name: spid
116
+ in: query
117
+ description: Filter by SPID (4-character)
118
+ schema:
119
+ type: string
120
+ - name: phone_number
121
+ in: query
122
+ description: Filter by phone number (10-digit)
123
+ schema:
124
+ type: string
125
+ responses:
126
+ '200':
127
+ description: List of matching resources
128
+ content:
129
+ application/json:
130
+ schema:
131
+ type: object
132
+ properties:
133
+ data:
134
+ type: array
135
+ items:
136
+ type: object
137
+ count:
138
+ type: integer
139
+
140
+ /v1/telique/dno/{phone_number}:
141
+ get:
142
+ operationId: dnoCheck
143
+ tags: [LRN]
144
+ summary: Check Do Not Originate (DNO) list
145
+ description: |
146
+ Check if a phone number is on the Do Not Originate list. DNO numbers should never
147
+ appear as a caller ID because they belong to entities that only receive calls
148
+ (e.g., IRS, major banks). A match indicates potential caller ID spoofing.
149
+ Supports prefix matching (3, 6, 7, or 10 digit patterns).
150
+ parameters:
151
+ - name: phone_number
152
+ in: path
153
+ required: true
154
+ description: 10-digit US phone number to check
155
+ schema:
156
+ type: string
157
+ pattern: '^\d{10}$'
158
+ example: '8002829500'
159
+ responses:
160
+ '200':
161
+ description: DNO check result
162
+ content:
163
+ application/json:
164
+ schema:
165
+ $ref: '#/components/schemas/DnoCheckResponse'
166
+ example:
167
+ is_dno: true
168
+ matched_pattern: '8002829500'
169
+ source: 'FTC'
170
+
171
+ # ── CNAM ─────────────────────────────────────────────────────────
172
+ /v1/telique/cnam/{phone_number}:
173
+ get:
174
+ operationId: cnamLookup
175
+ tags: [CNAM]
176
+ summary: Look up Caller Name (CNAM)
177
+ description: |
178
+ Look up the Caller Name (CNAM) for a phone number via TransUnion LIDB.
179
+ Returns the calling name (up to 15 characters), status, and presentation indicator.
180
+ Results are cached server-side for 24 hours.
181
+ parameters:
182
+ - name: phone_number
183
+ in: path
184
+ required: true
185
+ description: 10-15 digit phone number
186
+ schema:
187
+ type: string
188
+ pattern: '^\d{10,15}$'
189
+ example: '3036298301'
190
+ responses:
191
+ '200':
192
+ description: CNAM lookup result
193
+ content:
194
+ application/json:
195
+ schema:
196
+ $ref: '#/components/schemas/CnamLookupResponse'
197
+ example:
198
+ calling_name: 'SMITH JOHN'
199
+ calling_name_status: 'available'
200
+ presentation_indicator: 'allowed'
201
+
202
+ # ── LERG ─────────────────────────────────────────────────────────
203
+ /v1/telique/lerg/tables:
204
+ get:
205
+ operationId: lergListTables
206
+ tags: [LERG]
207
+ summary: List all LERG tables
208
+ description: |
209
+ Returns metadata for all 27 LERG tables. Key tables: lerg_1 (OCN/carrier directory),
210
+ lerg_6 (NPA-NXX block assignments), lerg_7 (switch details),
211
+ lerg_7_sha (switch homing arrangements/tandems), lerg_12 (LRN registry).
212
+ responses:
213
+ '200':
214
+ description: List of LERG tables with metadata
215
+ content:
216
+ application/json:
217
+ schema:
218
+ type: array
219
+ items:
220
+ $ref: '#/components/schemas/LergTableInfo'
221
+
222
+ /v1/telique/lerg/tables/{table_name}:
223
+ get:
224
+ operationId: lergTableInfo
225
+ tags: [LERG]
226
+ summary: Get LERG table schema
227
+ description: Returns detailed metadata and field schema for a specific LERG table.
228
+ parameters:
229
+ - name: table_name
230
+ in: path
231
+ required: true
232
+ description: "Table name (e.g. lerg_1, lerg_6, lerg_7_sha)"
233
+ schema:
234
+ type: string
235
+ example: lerg_6
236
+ responses:
237
+ '200':
238
+ description: Table schema and metadata
239
+ content:
240
+ application/json:
241
+ schema:
242
+ $ref: '#/components/schemas/LergTableInfo'
243
+ '404':
244
+ description: Table not found
245
+
246
+ /v1/telique/lerg/{table_name}/{fields}/{query}:
247
+ get:
248
+ operationId: lergQuery
249
+ tags: [LERG]
250
+ summary: Query a LERG table
251
+ description: |
252
+ Query any LERG table by field values. Common queries:
253
+ - Carrier by OCN: `lerg_1/ocn_num,ocn_name,ocn_state/ocn_num=9545`
254
+ - NPA-NXX info: `lerg_6/npa,nxx,loc_name,ocn,switch,lata/npa=303&nxx=629`
255
+ - Switch details: `lerg_7/switch,ocn,aocn/switch=DNVRCOMADS0`
256
+ - LRN registry: `lerg_12/lrn,lata,switch,ocn/lrn=7207081999`
257
+
258
+ Multiple filters are joined with `&` in the query path segment.
259
+ parameters:
260
+ - name: table_name
261
+ in: path
262
+ required: true
263
+ description: Table to query (e.g. lerg_1, lerg_6, lerg_7)
264
+ schema:
265
+ type: string
266
+ - name: fields
267
+ in: path
268
+ required: true
269
+ description: Comma-separated field names to return
270
+ schema:
271
+ type: string
272
+ example: 'npa,nxx,loc_name,ocn,switch,lata'
273
+ - name: query
274
+ in: path
275
+ required: true
276
+ description: "Filter in field=value format, multiple filters joined with & (URL-encoded as %26)"
277
+ schema:
278
+ type: string
279
+ example: 'npa=303%26nxx=629'
280
+ - name: limit
281
+ in: query
282
+ description: Max results (default 100, max 10000)
283
+ schema:
284
+ type: integer
285
+ minimum: 1
286
+ maximum: 10000
287
+ default: 100
288
+ - name: offset
289
+ in: query
290
+ description: Pagination offset
291
+ schema:
292
+ type: integer
293
+ minimum: 0
294
+ default: 0
295
+ responses:
296
+ '200':
297
+ description: Query results
298
+ content:
299
+ application/json:
300
+ schema:
301
+ $ref: '#/components/schemas/LergQueryResponse'
302
+
303
+ /v1/telique/lerg/query:
304
+ post:
305
+ operationId: lergComplexQuery
306
+ tags: [LERG]
307
+ summary: Complex LERG query with JOINs
308
+ description: |
309
+ Execute a complex LERG query with JOINs across multiple tables. Supports filter
310
+ operators: eq, ne, gt, gte, lt, lte, like, in, isnull, isnotnull.
311
+
312
+ Use this when you need to combine data from different LERG tables, such as joining
313
+ NPA-NXX data (lerg_6) with carrier info (lerg_1) via OCN.
314
+ requestBody:
315
+ required: true
316
+ content:
317
+ application/json:
318
+ schema:
319
+ $ref: '#/components/schemas/LergComplexQueryRequest'
320
+ example:
321
+ table: lerg_6
322
+ fields: ['npa', 'nxx', 'ocn', 'loc_name']
323
+ filters:
324
+ - field: npa
325
+ operator: eq
326
+ value: '303'
327
+ - field: nxx
328
+ operator: eq
329
+ value: '629'
330
+ join:
331
+ table: lerg_1
332
+ on:
333
+ - left_field: ocn
334
+ right_field: ocn_num
335
+ fields: ['ocn_name', 'ocn_state']
336
+ join_type: inner
337
+ limit: 100
338
+ offset: 0
339
+ responses:
340
+ '200':
341
+ description: Query results
342
+ content:
343
+ application/json:
344
+ schema:
345
+ $ref: '#/components/schemas/LergQueryResponse'
346
+
347
+ /v1/telique/lerg/tandem:
348
+ get:
349
+ operationId: lergTandem
350
+ tags: [LERG]
351
+ summary: Look up tandem routing
352
+ description: |
353
+ Look up tandem routing information via SQL JOINs across lerg_6, lerg_7_sha, and lerg_1.
354
+ Query by NPA+NXX, switch CLLI, tandem CLLI, or carrier name pattern.
355
+ Returns tandem switch, OCN, LATA, and routing path.
356
+ parameters:
357
+ - name: npa
358
+ in: query
359
+ description: 3-digit area code (use with nxx)
360
+ schema:
361
+ type: string
362
+ pattern: '^\d{3}$'
363
+ - name: nxx
364
+ in: query
365
+ description: 3-digit exchange code (use with npa)
366
+ schema:
367
+ type: string
368
+ pattern: '^\d{3}$'
369
+ - name: switch
370
+ in: query
371
+ description: Switch CLLI code (e.g. DNVRCOMADS0)
372
+ schema:
373
+ type: string
374
+ - name: tandem
375
+ in: query
376
+ description: Tandem CLLI code — reverse lookup to find what subtends it
377
+ schema:
378
+ type: string
379
+ - name: name
380
+ in: query
381
+ description: "Carrier name pattern with % wildcard (e.g. %VERIZON%). Case-insensitive."
382
+ schema:
383
+ type: string
384
+ - name: limit
385
+ in: query
386
+ description: Max results (default 100, max 10000)
387
+ schema:
388
+ type: integer
389
+ minimum: 1
390
+ maximum: 10000
391
+ default: 100
392
+ - name: offset
393
+ in: query
394
+ description: Pagination offset
395
+ schema:
396
+ type: integer
397
+ minimum: 0
398
+ default: 0
399
+ responses:
400
+ '200':
401
+ description: Tandem routing results
402
+ content:
403
+ application/json:
404
+ schema:
405
+ $ref: '#/components/schemas/LergQueryResponse'
406
+
407
+ # ── RouteLink ────────────────────────────────────────────────────
408
+ /v1/telique/ror/{crn}:
409
+ get:
410
+ operationId: routelinkRorLookup
411
+ tags: [RouteLink]
412
+ summary: Look up Responsible Organization for a toll-free number
413
+ description: |
414
+ Resolves a toll-free number (CRN) to its Responsible Organization (ROR).
415
+ The ROR is the entity responsible for managing the toll-free number's routing.
416
+ parameters:
417
+ - name: crn
418
+ in: path
419
+ required: true
420
+ description: 10-digit toll-free number (CRN)
421
+ schema:
422
+ type: string
423
+ pattern: '^\d{10}$'
424
+ example: '8005551234'
425
+ responses:
426
+ '200':
427
+ description: ROR lookup result
428
+ content:
429
+ application/json:
430
+ schema:
431
+ $ref: '#/components/schemas/RorLookupResponse'
432
+ example:
433
+ crn: '8005551234'
434
+ ror: 'ATX01'
435
+
436
+ /v1/telique/{lookup_type}/{crn}/{ani}/{lata}:
437
+ get:
438
+ operationId: routelinkCicLookup
439
+ tags: [RouteLink]
440
+ summary: Look up toll-free carrier routing
441
+ description: |
442
+ Resolves a toll-free number (CRN) to its carrier (CIC) by interpreting the CPR
443
+ decision tree using the caller's ANI and LATA. Use `cicror` to get both CIC and ROR
444
+ in a single request.
445
+ parameters:
446
+ - name: lookup_type
447
+ in: path
448
+ required: true
449
+ description: "Type of lookup: cic (carrier only) or cicror (carrier + responsible org)"
450
+ schema:
451
+ type: string
452
+ enum: [cic, cicror]
453
+ - name: crn
454
+ in: path
455
+ required: true
456
+ description: 10-digit toll-free number (CRN)
457
+ schema:
458
+ type: string
459
+ pattern: '^\d{10}$'
460
+ example: '8005551234'
461
+ - name: ani
462
+ in: path
463
+ required: true
464
+ description: 10-digit calling party number
465
+ schema:
466
+ type: string
467
+ pattern: '^\d{10}$'
468
+ example: '3035551234'
469
+ - name: lata
470
+ in: path
471
+ required: true
472
+ description: 3-digit LATA code
473
+ schema:
474
+ type: string
475
+ pattern: '^\d{3}$'
476
+ example: '656'
477
+ responses:
478
+ '200':
479
+ description: Carrier routing result
480
+ content:
481
+ application/json:
482
+ schema:
483
+ $ref: '#/components/schemas/CicLookupResponse'
484
+ example:
485
+ crn: '8005551234'
486
+ cic: '0288'
487
+ ror: 'ATX01'
488
+
489
+ /v1/telique/ror/{ror}/{resource_type}:
490
+ get:
491
+ operationId: routelinkRorQuery
492
+ tags: [RouteLink]
493
+ summary: List toll-free numbers or CPRs by ROR
494
+ description: |
495
+ List toll-free numbers (TFNs) or Call Processing Records (CPRs) associated with
496
+ a Responsible Organization. Use this to explore which toll-free numbers a specific
497
+ organization manages.
498
+ parameters:
499
+ - name: ror
500
+ in: path
501
+ required: true
502
+ description: 1-5 character alphanumeric ROR code
503
+ schema:
504
+ type: string
505
+ pattern: '^[A-Za-z0-9]{1,5}$'
506
+ example: 'ATX01'
507
+ - name: resource_type
508
+ in: path
509
+ required: true
510
+ description: Whether to list TFNs or CPRs
511
+ schema:
512
+ type: string
513
+ enum: [tfns, cprs]
514
+ - name: limit
515
+ in: query
516
+ description: Max results (default 100, max 10000)
517
+ schema:
518
+ type: integer
519
+ minimum: 1
520
+ maximum: 10000
521
+ default: 100
522
+ - name: offset
523
+ in: query
524
+ description: Pagination offset
525
+ schema:
526
+ type: integer
527
+ minimum: 0
528
+ default: 0
529
+ responses:
530
+ '200':
531
+ description: List of TFNs or CPRs
532
+ content:
533
+ application/json:
534
+ schema:
535
+ type: object
536
+ properties:
537
+ data:
538
+ type: array
539
+ items:
540
+ type: object
541
+ count:
542
+ type: integer
543
+
544
+ /v1/telique/cpr/{crn}:
545
+ get:
546
+ operationId: routelinkCpr
547
+ tags: [RouteLink]
548
+ summary: Retrieve Call Processing Record (CPR)
549
+ description: |
550
+ Retrieve the full Call Processing Record for a toll-free number. A CPR is a routing
551
+ decision tree that determines how calls are routed based on criteria like LATA, NPA,
552
+ NXX, ANI, DAY_OF_WEEK, TIME_OF_DAY, PERCENT, STATE, etc.
553
+
554
+ Returns the CPR structure, SHA1 hash, ROR, and optionally expanded template references.
555
+ parameters:
556
+ - name: crn
557
+ in: path
558
+ required: true
559
+ description: 1-10 digit CRN (toll-free number or template CRN). Shorter values are zero-padded to 10 digits.
560
+ schema:
561
+ type: string
562
+ pattern: '^\d{1,10}$'
563
+ example: '8005551234'
564
+ - name: expand
565
+ in: query
566
+ description: Recursively resolve and inline template decision trees (default true)
567
+ schema:
568
+ type: boolean
569
+ default: true
570
+ responses:
571
+ '200':
572
+ description: CPR decision tree
573
+ content:
574
+ application/json:
575
+ schema:
576
+ $ref: '#/components/schemas/CprResponse'
577
+
578
+ # ── GraphQL ──────────────────────────────────────────────────────
579
+ /v1/telique/lsms/gql:
580
+ post:
581
+ operationId: graphqlLsms
582
+ tags: [GraphQL]
583
+ summary: LSMS GraphQL endpoint
584
+ description: |
585
+ Execute GraphQL queries against the LSMS (live NPAC porting data).
586
+
587
+ Key queries: `subscriptionVersion(phoneNumber)`, `subscriptionVersionsByLrn(lrn, limit)`,
588
+ `subscriptionVersionsBySpid(spid, limit)`, `numberBlock(npanxxx)`, `serviceProviders(limit)`,
589
+ `locationRoutingNumber(lrn)`, `npanxxBySpid(spid, limit)`, `lsmsStats`.
590
+
591
+ Relationships: `subscriptionVersion` → `serviceProvider`, → `lrnMetadata`.
592
+ Safety limits: max 1000 results, 10s timeout. Large tables (subscriptionVersions 514M rows) MUST be filtered.
593
+
594
+ **Important:** LSMS uses named query parameters, NOT FilterInput syntax.
595
+ requestBody:
596
+ required: true
597
+ content:
598
+ application/json:
599
+ schema:
600
+ $ref: '#/components/schemas/GraphQLRequest'
601
+ example:
602
+ query: '{ subscriptionVersion(phoneNumber: "3036298301") { phoneNumber lrn spid lnpType activationTimestamp } }'
603
+ responses:
604
+ '200':
605
+ description: GraphQL response
606
+ content:
607
+ application/json:
608
+ schema:
609
+ $ref: '#/components/schemas/GraphQLResponse'
610
+
611
+ /v1/telique/lerg/gql:
612
+ post:
613
+ operationId: graphqlLerg
614
+ tags: [GraphQL]
615
+ summary: LERG GraphQL endpoint
616
+ description: |
617
+ Execute GraphQL queries against the LERG (static telecom reference data).
618
+
619
+ 27 tables with camelCase names (lerg1, lerg6, lerg7Sha). Return fields MUST be camelCase
620
+ (ocnName not ocn_name). Uses FilterInput with operators: EQ, NE, GT, GTE, LT, LTE, LIKE, IN, ISNULL, ISNOTNULL.
621
+
622
+ LIKE patterns MUST be UPPERCASE. Filter syntax: `{ field: "ocnName", op: LIKE, value: "%VERIZON%" }`.
623
+ IN uses `values` (plural). Relationships: `lerg6` → `carrier`, → `switchInfo`, → `homingArrangements`.
624
+ Also supports `dynamicJoin` for arbitrary cross-table SQL joins.
625
+ requestBody:
626
+ required: true
627
+ content:
628
+ application/json:
629
+ schema:
630
+ $ref: '#/components/schemas/GraphQLRequest'
631
+ example:
632
+ query: '{ lerg6(filters: [{ field: "npa", op: EQ, value: "303" }, { field: "nxx", op: EQ, value: "629" }], limit: 5) { npa nxx locName ocn switchClli lata carrier { ocnName } } }'
633
+ responses:
634
+ '200':
635
+ description: GraphQL response
636
+ content:
637
+ application/json:
638
+ schema:
639
+ $ref: '#/components/schemas/GraphQLResponse'
640
+
641
+ components:
642
+ securitySchemes:
643
+ apiToken:
644
+ type: apiKey
645
+ in: header
646
+ name: x-api-token
647
+ description: |
648
+ API token for authentication. Get yours at [telique.ringer.tel](https://telique.ringer.tel).
649
+ Anonymous access (no token) is rate-limited to 10 requests per minute.
650
+
651
+ schemas:
652
+ LrnLookupResponse:
653
+ type: object
654
+ properties:
655
+ lrn:
656
+ type: string
657
+ description: 10-digit Local Routing Number
658
+ example: '7207081999'
659
+ spid:
660
+ type: string
661
+ description: 4-character Service Provider ID
662
+ example: '6214'
663
+ lnp_type:
664
+ type: string
665
+ description: Local Number Portability type
666
+ example: '1'
667
+ activation_timestamp:
668
+ type: string
669
+ format: date-time
670
+ description: When the port was activated
671
+
672
+ DnoCheckResponse:
673
+ type: object
674
+ properties:
675
+ is_dno:
676
+ type: boolean
677
+ description: Whether the number is on the DNO list
678
+ matched_pattern:
679
+ type: string
680
+ description: The DNO pattern that matched (3, 6, 7, or 10 digits)
681
+ source:
682
+ type: string
683
+ description: Source of the DNO listing
684
+
685
+ CnamLookupResponse:
686
+ type: object
687
+ properties:
688
+ calling_name:
689
+ type: string
690
+ description: Caller name (up to 15 characters)
691
+ example: 'SMITH JOHN'
692
+ calling_name_status:
693
+ type: string
694
+ enum: [available, unavailable]
695
+ description: Whether a name was found
696
+ presentation_indicator:
697
+ type: string
698
+ enum: [allowed, restricted]
699
+ description: Whether the name may be displayed
700
+
701
+ LergTableInfo:
702
+ type: object
703
+ properties:
704
+ table_name:
705
+ type: string
706
+ example: lerg_6
707
+ description:
708
+ type: string
709
+ row_count:
710
+ type: integer
711
+ fields:
712
+ type: array
713
+ items:
714
+ type: object
715
+ properties:
716
+ name:
717
+ type: string
718
+ type:
719
+ type: string
720
+ description:
721
+ type: string
722
+
723
+ LergQueryResponse:
724
+ type: object
725
+ properties:
726
+ data:
727
+ type: array
728
+ items:
729
+ type: object
730
+ description: Array of result rows
731
+ count:
732
+ type: integer
733
+ description: Total matching rows (may exceed returned rows)
734
+ limit:
735
+ type: integer
736
+ offset:
737
+ type: integer
738
+
739
+ LergComplexQueryRequest:
740
+ type: object
741
+ required: [table, filters]
742
+ properties:
743
+ table:
744
+ type: string
745
+ description: Primary table name (e.g. lerg_6)
746
+ fields:
747
+ type: array
748
+ items:
749
+ type: string
750
+ description: Fields to return from primary table
751
+ filters:
752
+ type: array
753
+ items:
754
+ $ref: '#/components/schemas/LergFilter'
755
+ description: Filter conditions
756
+ join:
757
+ $ref: '#/components/schemas/LergJoin'
758
+ limit:
759
+ type: integer
760
+ minimum: 1
761
+ maximum: 10000
762
+ default: 100
763
+ offset:
764
+ type: integer
765
+ minimum: 0
766
+ default: 0
767
+
768
+ LergFilter:
769
+ type: object
770
+ required: [field, operator, value]
771
+ properties:
772
+ field:
773
+ type: string
774
+ operator:
775
+ type: string
776
+ enum: [eq, ne, gt, gte, lt, lte, like, in, isnull, isnotnull]
777
+ value:
778
+ oneOf:
779
+ - type: string
780
+ - type: number
781
+
782
+ LergJoin:
783
+ type: object
784
+ required: [table, on]
785
+ properties:
786
+ table:
787
+ type: string
788
+ description: Table to join (e.g. lerg_1)
789
+ on:
790
+ type: array
791
+ items:
792
+ type: object
793
+ required: [left_field, right_field]
794
+ properties:
795
+ left_field:
796
+ type: string
797
+ right_field:
798
+ type: string
799
+ description: Join conditions
800
+ fields:
801
+ type: array
802
+ items:
803
+ type: string
804
+ description: Fields to return from joined table
805
+ join_type:
806
+ type: string
807
+ enum: [inner, left]
808
+ default: inner
809
+
810
+ RorLookupResponse:
811
+ type: object
812
+ properties:
813
+ crn:
814
+ type: string
815
+ description: The toll-free number queried
816
+ ror:
817
+ type: string
818
+ description: 1-5 character Responsible Organization code
819
+
820
+ CicLookupResponse:
821
+ type: object
822
+ properties:
823
+ crn:
824
+ type: string
825
+ description: The toll-free number queried
826
+ cic:
827
+ type: string
828
+ description: 4-digit Carrier Identification Code
829
+ ror:
830
+ type: string
831
+ description: Responsible Organization code (only for cicror lookups)
832
+
833
+ CprResponse:
834
+ type: object
835
+ properties:
836
+ crn:
837
+ type: string
838
+ ror:
839
+ type: string
840
+ sha1:
841
+ type: string
842
+ description: SHA1 hash of the CPR
843
+ cpr:
844
+ type: object
845
+ description: |
846
+ CPR decision tree structure. Nodes contain a `type` (LATA, NPA, NXX, ANI,
847
+ DAY_OF_WEEK, TIME_OF_DAY, PERCENT, STATE, etc.) and either `branches`
848
+ (for decision nodes) or a `cic` (for terminal/carrier nodes).
849
+ properties:
850
+ type:
851
+ type: string
852
+ branches:
853
+ type: array
854
+ items:
855
+ type: object
856
+
857
+ GraphQLRequest:
858
+ type: object
859
+ required: [query]
860
+ properties:
861
+ query:
862
+ type: string
863
+ description: GraphQL query string
864
+ variables:
865
+ type: object
866
+ additionalProperties: true
867
+ description: Optional GraphQL variables
868
+
869
+ GraphQLResponse:
870
+ type: object
871
+ properties:
872
+ data:
873
+ type: object
874
+ description: Query result data
875
+ errors:
876
+ type: array
877
+ items:
878
+ type: object
879
+ properties:
880
+ message:
881
+ type: string
882
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "telique-mcp",
3
- "version": "1.0.27",
3
+ "version": "1.0.29",
4
4
  "description": "MCP server for Telique telecom APIs (RouteLink, LRN, CNAM, LERG)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -19,13 +19,15 @@
19
19
  },
20
20
  "files": [
21
21
  "dist",
22
- "postinstall.js"
22
+ "postinstall.js",
23
+ "openapi.yaml"
23
24
  ],
24
25
  "scripts": {
25
26
  "build": "tsc",
26
27
  "start": "node dist/index.js",
27
28
  "dev": "tsx src/index.ts",
28
29
  "postinstall": "node postinstall.js",
30
+ "validate:openapi": "tsx scripts/validate-openapi.ts",
29
31
  "prepublishOnly": "npm run build"
30
32
  },
31
33
  "keywords": [
@@ -42,7 +44,7 @@
42
44
  "type": "git",
43
45
  "url": "git+https://github.com/Ringer/telique-mcp.git"
44
46
  },
45
- "license": "UNLICENSED",
47
+ "license": "MIT",
46
48
  "dependencies": {
47
49
  "@modelcontextprotocol/sdk": "^1.12.0",
48
50
  "zod": "^3.23.0"