Skip to main content
Glama
paralelum

ipbx-mcp

by paralelum

ipbx-mcp

IPBX용 MCP 서버(TypeScript). Streamable HTTP 전송, stateless 모드, 정적 bearer 및/또는 OAuth 2.1 + Google Workspace 인증, SQLite 로컬 영속화(OAuth 클라이언트, refresh token, 감사 로그). base-mcp 스캐폴드에서 상속받았으며, PABX(MySQL) 데이터를 타입화된 tools로 노출합니다.

프로덕션 공개 URL: https://mcp.ipbx.vivavox.com.br.

요구 사항

  • Node.js >= 22 (better-sqlite3 v12 필요)

  • OAuth 사용 시: Google Cloud Console의 OAuth Client를 Internal 모드로

Related MCP server: utel-mcp

설치

npm install
cp .env.example .env   # depois preencha os valores reais
npm run build

구성

.env를 프로세스에 로드합니다(systemd EnvironmentFile=, docker env_file:, 또는 시작 시 node --env-file=.env).

필수

최소 하나의 인증 경로:

변수

사용 시점

MCP_AUTH_TOKEN

정적 Bearer — Claude Desktop, CLI, API, 스크립트, cron

OAUTH_JWT_SECRET + OAUTH_ISSUER

OAuth — claude.ai(웹/모바일) 클라이언트

OAuth(선택 사항이지만 claude.ai에 필요)

변수

설명

OAUTH_ISSUER

서버의 표준 URL(예: https://mcp.ipbx.vivavox.com.br)

OAUTH_JWT_SECRET

JWT용 HS256 키(32바이트 hex)

GOOGLE_CLIENT_ID

Google Cloud Console의 OAuth Client에서

GOOGLE_CLIENT_SECRET

Google Cloud Console의 OAuth Client에서

ALLOWED_GOOGLE_HD

허용된 Workspace 도메인(기본값: vivavox.com.br)

이 변수들이 모두 있으면 /authorize, /oauth/google/callback, /token, /register(DCR) 라우트가 마운트됩니다. 없으면 정적 bearer만 동작합니다.

기타

변수

기본값

설명

PORT

3000

HTTP 포트

HOST

0.0.0.0

인터페이스(로컬 개발에서는 127.0.0.1 사용)

MCP_ALLOWED_HOSTS

Host 헤더에서 허용할 호스트 CSV 목록

SQLITE_PATH

./data/app.db

SQLite 파일 경로

IPBX_RECORD_BASE_URL

녹음을 제공하는 IPBX API의 기본 URL(예: https://ipbx.vivavox.com.br/api). 없으면 ipbx_recording_get 실패

MySQL(IPBX 데이터 소스)

변수

기본값

설명

MYSQL_HOST

MySQL 호스트

MYSQL_PORT

3306

MYSQL_USER

GRANT SELECT만 있는 전용 사용자 사용

MYSQL_PASSWORD

MYSQL_DATABASE

MYSQL_POOL_LIMIT

5

풀 크기(mysql2)

MYSQL_SSL

비어 있음

아무 값이든 TLS + 인증서 검증 활성화

IPBX_ID

이 인스턴스가 담당하는 테넌트(아래 참조)

데이터베이스는 멀티 테넌트입니다 — 고객별 Asterisk 인스턴스, ipbx 테이블 — 하지만 각 MCP 인스턴스는 하나의 테넌트만 담당합니다. 모든 쿼리는 IPBX_ID로 필터링되며, 어떤 tool도 이 ID를 매개변수로 받지 않습니다. 이렇게 하면 클라이언트 간 격리가 모델이 호출에 전달하는 값에 의존하지 않습니다. 테넌트당 컨테이너 하나, 서브도메인 하나.

임의 토큰 생성:

node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"

엔드포인트

메서드

경로

인증

설명

POST

/mcp

bearer

Streamable HTTP를 통한 MCP JSON-RPC

GET

/mcp

bearer

405

DELETE

/mcp

bearer

405

GET

/health

공개

{"status":"ok"}

GET

/.well-known/oauth-authorization-server

공개

RFC 8414 메타데이터

GET

/.well-known/oauth-protected-resource

공개

RFC 9728 메타데이터

POST

/register

공개

Dynamic Client Registration (RFC 7591)

GET

/authorize

공개

Google로 리다이렉트

GET

/oauth/google/callback

공개

Google의 리다이렉트 수신

POST

/token

공개

authorization_code / refresh_token

/mcp401에는 WWW-Authenticate: Bearer realm=..., resource_metadata=...가 포함됩니다 — 이것이 없으면 claude.ai가 첫 접촉에서 AS를 발견하지 못합니다.

사용 가능한 Tools

Tool 이름은 ipbx_<model>_<action> 형식을 따르며, <action>list / get / search / count 어휘를 사용합니다.

ipbx_instance_get

이 서버가 담당하는 IPBX 인스턴스의 등록 데이터 — 이름, IP, SIP/AMI 포트.

매개변수: 없음. 인스턴스는 고정되어 있으며 환경의 IPBX_ID로 정의됩니다.

반환:

{
  "id": 1,
  "shortname": "vivavox",
  "fullname": "Vivavox Telecom",
  "ipaddr": "138.94.55.155",
  "sipport": 5601,
  "amiport": 6501,
  "created": "2024-06-17T16:37:59.000Z",
  "updated": "2024-06-17T16:37:59.000Z"
}

구성된 IPBX_IDipbx 테이블에 없으면 isError를 반환합니다.

ipbx_branch_list

인스턴스의 내선 목록.

매개변수:

  • search (string, 선택): 내선 번호 또는 이름 부분 검색

  • limit (number, 선택): 1–500, 기본 100

반환:

{
  "total": 27,
  "truncated": false,
  "branches": [
    {
      "id": 2,
      "exten": "23",
      "name": "Ricardo Landim",
      "group": "Suporte",
      "record": true,
      "webrtc": false,
      "dtmf": "rfc4733",
      "forward_busy": "035988023317",
      "forward_noanswer": "035988023317",
      "forward_noanswer_wait": 5
    }
  ]
}

SIP 자격 증명은 반환하지 않습니다. password(평문 비밀번호)와 username(인증 식별자, 내선 번호와 다름) 열은 설계상 제외됩니다 — 이 둘을 함께 사용하면 소프트폰을 등록하고 고객 계정으로 통화를 발신할 수 있습니다. SELECT의 열 목록은 실수로 포함되지 않도록 명시적입니다.

ipbx_user_list

인스턴스 패널의 사용자 목록.

매개변수:

  • search (string, 선택): 이름 또는 이메일 부분 검색

  • limit (number, 선택): 1–500, 기본 100

반환:

{
  "total": 6,
  "truncated": false,
  "users": [
    {
      "id": 11,
      "name": "Suporte",
      "email": "suporte@vivavox.com.br",
      "created": "2024-07-10T13:56:41.000Z",
      "updated": "2024-07-10T13:56:41.000Z"
    }
  ]
}

접속 비밀번호는 반환하지 않습니다. secret 열은 제외됩니다 — 패널 로그인 비밀번호이며, 데이터베이스에 평문(해시 없음)으로 저장됩니다. 이를 노출하면 PABX에 대한 관리자 접근 권한이 제공됩니다.

ipbx_group_list

인스턴스의 내선 그룹 목록, 각 그룹의 내선 수 포함.

매개변수:

  • search (string, 선택): 이름 또는 설명 부분 검색

  • limit (number, 선택): 1–500, 기본 100

반환:

{
  "total": 6,
  "truncated": false,
  "groups": [
    {
      "id": 1,
      "name": "Suporte",
      "description": "Grupo do suporte",
      "branches": 11
    }
  ]
}

groups 테이블은 자격 증명을 저장하지 않습니다 — branchusers와 달리 여기서는 모든 열이 노출됩니다.

ipbx_trunk_list

인스턴스의 트렁크 목록.

매개변수:

  • search (string, 선택): 이름 또는 호스트 부분 검색

  • limit (number, 선택): 1–500, 기본 100

반환:

{
  "total": 2,
  "truncated": false,
  "trunks": [
    {
      "id": 1,
      "name": "Vivavox",
      "host": "sip.vivavox.com.br",
      "port": "5060",
      "register": true,
      "record": true,
      "auth": "credentials"
    }
  ]
}

통신사 자격 증명은 반환하지 않습니다. usernamepassword는 제외됩니다 — 데이터베이스에서 가장 가치 있는 자격 증명으로, 통신사를 통해 직접 통화를 발신하고 계정에 요금이 부과될 수 있습니다. 대신 auth가 트렁크가 인증하는 방식을 알려줍니다: "credentials"(사용자/비밀번호) 또는 "ip"(IP 허용 목록, 비밀번호 없음).

ipbx_queue_list

대기열 목록, 분배 전략 및 각 대기열의 구성원 수 포함.

매개변수: search (string, 선택), limit (1–500, 기본 100)

{
  "total": 5,
  "queues": [
    { "id": 1, "name": "Suporte", "strategy": "ringall", "members": 8 },
    { "id": 5, "name": "Teste", "strategy": "leastrecent", "members": 1 }
  ]
}

ipbx_queue_member_list

대기열 구성원 목록, 호출 순서대로.

매개변수:

  • queue_id (number, 선택): 특정 대기열 필터링; 생략하면 모두 가져옴

  • limit (number, 선택): 1–500, 기본 200

반환:

{
  "total": 8,
  "members": [
    {
      "queue_id": 1,
      "queue": "Suporte",
      "position": 1,
      "type": "branch",
      "exten": "29",
      "name": "Mateus Damaceno",
      "ref": "branch-10"
    }
  ]
}

queue_member.member 열은 <type>-<id> 형식의 참조를 저장합니다 — branch-10branch.id 10을 가리키며, 이는 내선 29입니다. 내선 번호가 아닙니다. tool은 구성원이 내선일 때 이를 exten + name으로 해석합니다. 모든 구성원이 그런 것은 아닙니다: redirect-N 항목이 있으며, 이는 type: "redirect"로 반환되고 exten/name은 null입니다.

ipbx_ivr_list

IVR 목록, 연결된 오디오 및 발신자에게 말하는 내용의 전사 포함.

매개변수: search (string, 선택 — 이름 또는 전사 텍스트에서 일치), limit (1–500, 기본 100)

{
  "total": 1,
  "ivrs": [
    {
      "id": 5,
      "name": "URA Rompimento",
      "audio": "URA Rompimento",
      "transcription": "Olá, se você está com falta de conexão e o LED Loss do seu modem óptico...",
      "options": 1
    }
  ]
}

전사는 가장 유용한 필드입니다: 이름뿐만 아니라 말하는 내용으로 IVR을 찾을 수 있습니다.

ipbx_ivr_option_list

IVR 옵션 목록 — 어떤 키가 어떤 대상으로 이어지는지.

매개변수:

  • ivr_id (number, 선택): 특정 IVR 필터링; 생략하면 모두 가져옴

  • limit (number, 선택): 1–500, 기본 200

반환:

{
  "total": 7,
  "options": [
    {
      "ivr_id": 1,
      "ivr": "URA Principal - Horario comercial",
      "digit": "1",
      "goto": { "type": "queue", "name": "Financeiro", "exten": null, "ref": "queue-3" }
    },
    {
      "ivr_id": 1,
      "ivr": "URA Principal - Horario comercial",
      "digit": "7X",
      "goto": { "type": "internal", "name": null, "exten": null, "ref": "internal" }
    }
  ]
}

ivr_option.goto다형적입니다: <type>-<id> 형식으로 5개의 다른 테이블(branch, queue, ivr, redirect, app)을 가리키며, id 없는 리터럴(internal)도 허용합니다. tool은 모든 경우에 대상 이름을 해석합니다. 리터럴은 name이 null이고 ref가 보존된 채 반환됩니다.

digit 필드는 항상 숫자가 아닙니다: t는 타임아웃이고 7X 같은 패턴은 내선 범위와 일치합니다.

ipbx_redirect_list

리다이렉트 목록 — 트렁크를 통해 외부 번호로 전달하는 짧은 내선. 대기열, IVR, 라우팅 규칙의 대상으로 나타나는 것과 동일한 redirect-<id>입니다.

매개변수: search (string, 선택 — 내선, 이름 또는 번호 일치), limit (1–500, 기본 100)

{
  "total": 12,
  "redirects": [
    {
      "id": 2,
      "exten": "73",
      "name": "Ricardo Landim",
      "forward": "5535988023317",
      "trunk": "Vivavox",
      "ref": "redirect-2"
    }
  ]
}

⚠️ 개인 데이터. forward는 100%의 행에서 개인 휴대폰 번호입니다 — 자격 증명은 아니지만 LGPD에 따른 개인 데이터입니다. tool은 테이블의 존재 이유이므로 이를 반환하지만, 감사 로그(audit_log)에는 포함되지 않습니다.

ipbx_routing_list

라우팅 플랜 목록, 각 플랜의 규칙 및 시간 창 수 포함.

매개변수: search (string, 선택), limit (1–500, 기본 100)

{
  "total": 2,
  "routings": [
    { "id": 1, "name": "Entrada - Padrão", "rules": 6, "time_windows": 3 },
    { "id": 2, "name": "Saida - Padrão", "rules": 8, "time_windows": 1 }
  ]
}

ipbx_routing_time_list

플랜의 시간 창 목록.

매개변수: routing_id (number, 선택), limit (1–500, 기본 100)

{
  "id": 1,
  "routing": "Entrada - Padrão",
  "name": "Horario comercial",
  "ranges": ["08:00-18:00,mon", "08:00-18:00,tue", "08:00-12:00,sat"]
}

pattern은 Asterisk 형식으로 저장되며, 각 줄에 범위 하나; tool은 목록으로 반환합니다.

ipbx_routing_rule_list

라우팅 규칙 목록 — 다이얼플랜. 각 규칙은 시간 창 내에서 숫자 패턴을 일치시키고, 숫자를 제거하고, 접두사를 추가하고, 대상으로 보냅니다.

매개변수: routing_id (number, 선택), limit (1–500, 기본 200)

{
  "id": 4,
  "routing": "Saida - Padrão",
  "name": "LDN",
  "time_window": "Geral",
  "match": "0ZZ.",
  "suppress": 1,
  "prefix": "55",
  "goto": { "type": "trunk", "name": "Vivavox", "exten": null, "ref": "trunk-1" }
}

goto1은 IVR의 것과 같이 다형적이며, trunk 유형이 추가됩니다(발신 규칙에서 사용) — 총 6개의 가능한 대상.

여기서 처리되는 스키마의 두 가지 세부 사항: 데이터베이스 열은 supress(p 하나)로 불리며 suppress로 노출됩니다; 그리고 goto2/goto3는 존재하지만 모든 행에서 비어 있습니다 — goto_extra로 표시되며, 언젠가 채워지는 경우에만 나타납니다.

ipbx_cdr_list

통화 기록. 기간은 필수이며 31일로 제한됩니다: cdr에는 PK 외에 인덱스가 없으므로 모든 필터는 전체 스캔입니다(현재 약 268k 행).

파라미터: date_fromdate_to (YYYY-MM-DD, 필수), scope (call | leg, 기본값 call), srcdst (부분 일치), branch_id, trunk_id, answered (bool), call_id, limit (1–500, 기본값 25)

{
  "call_id": "sip1-1787578699.251937",
  "started": "2026-08-24 10:38:19",
  "ended": "2026-08-24 10:42:06",
  "direction": "inbound",
  "from": { "type": "trunk", "id": 1, "name": "Vivavox" },
  "caller": "35997609940",
  "dialed": null,
  "context": "queue-3",
  "answered": true,
  "talk_seconds": 265,
  "ring_attempts": 6,
  "targets": [{ "type": "branch", "id": 16, "exten": "35", "name": "Ester Vilela" }],
  "answered_by": [{ "type": "branch", "id": 16, "exten": "35", "name": "Ester Vilela" }],
  "dispositions": ["ANSWERED", "NO ANSWER"],
  "has_recording": true,
  "legs": 10
}

cdr은 PABX에서 ipbx_id가 없는 유일한 테이블입니다. 테넌트와의 연결은 Asterisk의 systemname이며, ipbx-api는 이를 sip<ipbx_id>로 기록하고 Asterisk는 각 레코드의 uniqueid/linkedid에 스탬프를 찍습니다 — 필터는 uniqueid LIKE 'sip<id>-%'이며 하이픈이 포함됩니다 (하이픈이 없으면 sip1sip10-도 매칭됩니다).

하나의 통화는 여러 레코드로 구성됩니다: uniqueid는 채널을 식별하고, linkedid는 통화를 식별하며, 각 Dial 시도는 레코드를 생성합니다 — 큐 착신 통화는 22개까지 도달합니다. scope=calllinkedid로 그룹화합니다. 대상이 Local/ 채널인 레그는 각 상담원에 대한 큐의 링(ring)이며 (ring_attempts가 됨), 나머지는 통화 시간입니다 (talk_seconds 합산). scope=leg는 원시 레그를 반환합니다 — 통화 디버깅에는 call_id와 함께 사용하세요.

has_recordingrec이 채워진 것 외에 ANSWERED를 요구합니다 — 패널의 recAvailable과 동일한 규칙입니다. rec 열은 Dial 이전에 기록되므로 (다이얼플랜이 prerouting에서 MixMonitor를 설정), "녹음 준비 완료"를 표시할 뿐 "오디오가 존재함"을 의미하지는 않습니다: 단독으로 사용하면 통화의 99.96%에 녹음이 있다고 표시됩니다.

채널 열은 원시 상태로 출력되지 않습니다: channel, dstchannellastdata는 엔드포인트의 username을 담고 있으며, 이는 SIP 자격 증명의 절반이고, src는 내부 통화에서 동일한 username을 가져옵니다. 모든 것은 src/channel.ts를 거쳐 내선/트렁크/큐로 출력됩니다. rec도 제외되며 has_recording이 됩니다.

branch_id/trunk_id로 필터링하면 레코드가 아닌 세미 조인(semi-join)으로 통화를 선택합니다: 집계는 계속해서 전체 통화를 설명하며, 해당 내선의 레그만 설명하지 않습니다.

ipbx_recording_get

ipbx_cdr_list가 반환하는 call_id를 기반으로 하나의 통화에 대한 오디오 URL입니다.

파라미터: call_id (문자열, 필수)

{
  "call_id": "sip1-1787577145.251772",
  "started": "2026-08-24 10:12:25",
  "has_recording": true,
  "url": "https://ipbx.vivavox.com.br/api/call/record/sip1-8f0e5161….wav",
  "note": "URL publica e sem expiracao: o nome do arquivo e a unica credencial. …"
}

ipbx_cdr_list의 필드 대신 별도의 도구인 이유가 있습니다: ipbx-api의 /call/record 경로는 인증을 요구하지 않으며 URL은 만료되지 않습니다 — 파일 이름(SHA1)이 자격 증명이기 때문입니다. 목록 필드로 제공되면 CDR의 각 통화가 컨텍스트에 25개의 영구적인 대화 접근 권한을 쏟아내고, 거의 모두 사용되지 않으며, 감사(audit)는 25개의 자격 증명을 기록하거나 아무것도 기록하지 않아야 합니다. 녹음별 도구는 요청한 사람의 신원을 포함한 감사 라인을 제공합니다. CDR의 has_recording은 발견 신호이고, 이 도구는 접근입니다.

오디오가 없으면 응답은 단순히 거부하는 대신 이유를 알려줍니다 — Chamada nao atendida (녹음은 Dial 이전에 설정됨) 또는 내선에서 녹음이 꺼져 있음. 다른 테넌트의 call_idisError를 반환합니다: IPBX_ID 필터가 쿼리에 적용되고, URL의 sip<id>는 환경에서 오며 수신된 call_id에서 오지 않습니다.

IPBX_RECORD_BASE_URL에 의존합니다. 이 값이 없으면 서버는 정상적으로 시작되고 이 도구만 명시적인 메시지와 함께 실패합니다 — 서버에서 도구를 끄는 것과 같습니다.

모든 통화는 호출자의 신원과 함께 audit_log에 레코드를 생성합니다: JWT면 Google 이메일, 정적 bearer면 service:static. ipbx_cdr_listsrc/dst는 전화번호이므로 감사에 포함되지 않습니다 — number_filter: true만 남습니다.

명령어

npm run build      # tsc
npm run check      # tsc --noEmit (sem emitir)
npm run dev        # tsc --watch
npm start          # node dist/index.js
npm run inspect    # MCP Inspector

로컬 스모크 테스트:

curl -s http://localhost:3000/health
curl -s http://localhost:3000/.well-known/oauth-authorization-server
curl -s -X POST http://localhost:3000/mcp \
  -H "Authorization: Bearer $MCP_AUTH_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

배포

Docker (권장)

멀티 스테이지 Dockerfile (node:22-slim), 비-root 사용자 mcp로 런타임 실행, SQLite용 볼륨으로 /data 노출, /health를 통한 헬스체크. 프로덕션에서는 .github/workflows/deploy.yml을 통해 배포가 자동화됩니다 (태그 vX.Y.Z 푸시 → GHCR에서 빌드 → VPS에서 docker run). 수동으로:

docker image build . -t ipbx-mcp:1.0

docker container run -d --env-file .env -p 50020:3000 \
  -v ipbx_data:/data --restart unless-stopped --name ipbx-mcp ipbx-mcp:1.0

docker stop ipbx-mcp && docker rm ipbx-mcp
docker logs -f ipbx-mcp

SQLite 백업:

docker run --rm \
  -v ipbx_data:/data \
  -v $PWD:/backup \
  alpine tar czf /backup/sqlite-bkp.tgz -C /data .

systemd

[Unit]
Description=ipbx-mcp
After=network.target

[Service]
Type=simple
WorkingDirectory=/var/local/ipbx-mcp
ExecStart=/usr/bin/node dist/index.js
EnvironmentFile=/var/local/ipbx-mcp/.env
User=mcp
Restart=on-failure
RestartSec=10

[Install]
WantedBy=multi-user.target

EnvironmentFile=는 systemd에서 .env에 해당하는 네이티브 기능입니다. root 대신 전용 사용자(mcp)를 사용하세요.

MCP 클라이언트에서 구성

Claude Desktop / CLI (정적 bearer)

{
  "mcpServers": {
    "ipbx": {
      "type": "http",
      "url": "https://mcp.ipbx.vivavox.com.br/mcp",
      "headers": {
        "Authorization": "Bearer SEU_MCP_AUTH_TOKEN"
      }
    }
  }
}

claude.ai (OAuth)

https://mcp.ipbx.vivavox.com.br/mcp를 사용하여 Custom Connector로 추가합니다. OAuth 흐름이 자동으로 시작됩니다 — claude.ai가 WWW-Authenticate를 통해 AS를 발견하고, DCR을 통해 클라이언트를 등록하고, Google로 리디렉션하고, code를 수신하여 access token으로 교환합니다.

구조

src/
  index.ts            # bootstrap HTTP, leitura de env, registro de rotas
  server.ts           # createServer() registra as tools (ipbx_*)
  mysql.ts            # pool mysql2 + queries do IPBX (tenant fixo)
  channel.ts          # nome de canal do Asterisk -> ramal/tronco/fila
  sqlite.ts           # better-sqlite3 + apply schemas
  audit.ts            # logToolCall() -> audit_log
  auth/
    jwt.ts            # sign/verify HS256 (jose)
    middleware.ts     # requireAuth: JWT -> fallback bearer estático
  oauth/
    routes.ts         # registerOAuthRoutes()
    store.ts          # DCR clients, codes, refresh, authorize-tx
    google.ts         # OAuth do Google (authorize URL + token exchange)
    pkce.ts           # verificação S256 em tempo constante
sql/
  001_oauth_schema.sql           # oauth_clients, oauth_codes, oauth_refresh_tokens, audit_log
  002_oauth_authorize_tx.sql     # oauth_authorize_tx (state Google <-> params)
Dockerfile
.github/workflows/deploy.yml     # build GHCR + deploy SSH na VPS

Maintenance

ActivityMaintained
ResponsivenessSyncing

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

Related MCP Servers

  • F
    license
    A
    quality
    B
    maintenance
    An MCP server that wraps the UTEL IP-telephony REST API as MCP tools, enabling LLM agents to make authenticated HTTP requests to the UTEL API via a simple tool interface.
    1
    1
  • F
    license
    Not graded
    quality
    C
    maintenance
    MCP server for Bvoip / 1Stream that exposes call-reporting, phone-status, and CRM-extension-mapping endpoints as MCP tools.

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/paralelum/ipbx-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server