registro-br-mcp 1.0.0

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.
Files changed (3) hide show
  1. package/README.md +124 -0
  2. package/package.json +40 -0
  3. package/src/index.js +375 -0
package/README.md ADDED
@@ -0,0 +1,124 @@
1
+ # Registro.br MCP Server
2
+
3
+ Um servidor [MCP (Model Context Protocol)](https://modelcontextprotocol.io/) para consultar o RDAP do Registro.br.
4
+
5
+ ## Ferramentas Disponíveis
6
+
7
+ | Ferramenta | Descrição |
8
+ |------------|-----------|
9
+ | `rdap_domain` | Consulta informações de domínios .br (ex: `nic.br`, `registro.br`) |
10
+ | `rdap_entity` | Consulta entidades por CNPJ, CPF ou handle (ex: `05506560000136`, `FAN`) |
11
+ | `rdap_nameserver` | Consulta informações de nameservers (ex: `a.dns.br`) |
12
+ | `rdap_ip` | Consulta informações de IP ou rede (ex: `200.160.0.0`, `200.160.0.0/20`) |
13
+ | `rdap_asn` | Consulta Autonomous System Numbers (ex: `22548`, `AS22548`) |
14
+
15
+ ## Instalação
16
+
17
+ ### Via npx (recomendado)
18
+
19
+ Não é necessário instalar. Configure diretamente no seu cliente MCP.
20
+
21
+ ### Via npm (global)
22
+
23
+ ```bash
24
+ npm install -g registro-br-mcp
25
+ ```
26
+
27
+ ## Configuração
28
+
29
+ ### Claude Desktop
30
+
31
+ Adicione ao arquivo de configuração (`~/Library/Application Support/Claude/claude_desktop_config.json` no macOS):
32
+
33
+ ```json
34
+ {
35
+ "mcpServers": {
36
+ "registro-br": {
37
+ "command": "npx",
38
+ "args": ["registro-br-mcp"]
39
+ }
40
+ }
41
+ }
42
+ ```
43
+
44
+ ### Claude Code
45
+
46
+ Adicione ao arquivo `~/.claude/settings.json`:
47
+
48
+ ```json
49
+ {
50
+ "mcpServers": {
51
+ "registro-br": {
52
+ "command": "npx",
53
+ "args": ["registro-br-mcp"]
54
+ }
55
+ }
56
+ }
57
+ ```
58
+
59
+ ## Exemplos de Uso
60
+
61
+ Após configurar, você pode fazer perguntas como:
62
+
63
+ - "Consulte o domínio nic.br"
64
+ - "Quem é o registrante do domínio registro.br?"
65
+ - "Busque informações do ASN 22548"
66
+ - "Qual o IP do nameserver a.dns.br?"
67
+ - "Consulte a entidade com CNPJ 05506560000136"
68
+
69
+ ## Exemplo de Resposta
70
+
71
+ Consulta do domínio `nic.br`:
72
+
73
+ ```
74
+ Domain: nic.br
75
+ Handle: nic.br
76
+ Status: active
77
+
78
+ Events:
79
+ - registration: 1997-07-11T12:00:00Z
80
+ - last changed: 2018-03-27T20:09:08Z
81
+
82
+ Nameservers:
83
+ - a.dns.br
84
+ - b.dns.br
85
+ - c.dns.br
86
+ - d.dns.br
87
+ - e.dns.br
88
+
89
+ DNSSEC: Signed
90
+ - KeyTag: 47828, Algorithm: 13, DigestType: 2
91
+
92
+ Entities:
93
+ - Núcleo de Inf. e Coord. do Ponto BR - NIC.BR (registrant)
94
+ - Frederico Augusto de Carvalho Neves (technical)
95
+ ```
96
+
97
+ ## API RDAP
98
+
99
+ Este servidor utiliza a API RDAP pública do Registro.br:
100
+
101
+ - Base URL: `https://rdap.registro.br`
102
+ - Documentação: https://registro.br/rdap/
103
+
104
+ ## Desenvolvimento
105
+
106
+ ```bash
107
+ # Clonar o repositório
108
+ git clone https://github.com/yvesmariano/registro-br-mcp.git
109
+ cd registro-br-mcp
110
+
111
+ # Instalar dependências
112
+ npm install
113
+
114
+ # Executar localmente
115
+ npm start
116
+ ```
117
+
118
+ ## Requisitos
119
+
120
+ - Node.js >= 18.0.0
121
+
122
+ ## Licença
123
+
124
+ MIT
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "registro-br-mcp",
3
+ "version": "1.0.0",
4
+ "description": "MCP Server para consultar o RDAP do Registro.br",
5
+ "type": "module",
6
+ "main": "src/index.js",
7
+ "bin": {
8
+ "registro-br-mcp": "./src/index.js"
9
+ },
10
+ "files": [
11
+ "src"
12
+ ],
13
+ "scripts": {
14
+ "start": "node src/index.js"
15
+ },
16
+ "dependencies": {
17
+ "@modelcontextprotocol/sdk": "^1.0.0",
18
+ "zod": "^3.23.0"
19
+ },
20
+ "keywords": [
21
+ "mcp",
22
+ "model-context-protocol",
23
+ "rdap",
24
+ "registro.br",
25
+ "whois",
26
+ "domain",
27
+ "brasil",
28
+ "brazil"
29
+ ],
30
+ "license": "MIT",
31
+ "engines": {
32
+ "node": ">=18.0.0"
33
+ },
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "https://github.com/yvesmariano/registro-br-mcp.git"
37
+ },
38
+ "author": "Yves Mariano",
39
+ "homepage": "https://github.com/yvesmariano/registro-br-mcp#readme"
40
+ }
package/src/index.js ADDED
@@ -0,0 +1,375 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
+ import { z } from "zod";
6
+
7
+ const RDAP_BASE_URL = "https://rdap.registro.br";
8
+
9
+ async function fetchRdap(path) {
10
+ const url = `${RDAP_BASE_URL}${path}`;
11
+ const response = await fetch(url, {
12
+ headers: {
13
+ Accept: "application/rdap+json",
14
+ },
15
+ });
16
+
17
+ if (!response.ok) {
18
+ if (response.status === 404) {
19
+ throw new Error(`Not found: ${path}`);
20
+ }
21
+ throw new Error(`RDAP request failed: ${response.status} ${response.statusText}`);
22
+ }
23
+
24
+ return response.json();
25
+ }
26
+
27
+ function formatDomainInfo(data) {
28
+ const lines = [];
29
+
30
+ lines.push(`Domain: ${data.ldhName}`);
31
+ lines.push(`Handle: ${data.handle}`);
32
+ lines.push(`Status: ${data.status?.join(", ") || "N/A"}`);
33
+
34
+ if (data.events) {
35
+ lines.push("\nEvents:");
36
+ for (const event of data.events) {
37
+ lines.push(` - ${event.eventAction}: ${event.eventDate}`);
38
+ }
39
+ }
40
+
41
+ if (data.nameservers) {
42
+ lines.push("\nNameservers:");
43
+ for (const ns of data.nameservers) {
44
+ lines.push(` - ${ns.ldhName}`);
45
+ }
46
+ }
47
+
48
+ if (data.secureDNS) {
49
+ lines.push(`\nDNSSEC: ${data.secureDNS.delegationSigned ? "Signed" : "Not signed"}`);
50
+ if (data.secureDNS.dsData) {
51
+ for (const ds of data.secureDNS.dsData) {
52
+ lines.push(` - KeyTag: ${ds.keyTag}, Algorithm: ${ds.algorithm}, DigestType: ${ds.digestType}`);
53
+ }
54
+ }
55
+ }
56
+
57
+ if (data.entities) {
58
+ lines.push("\nEntities:");
59
+ for (const entity of data.entities) {
60
+ const name = entity.vcardArray?.[1]?.find(v => v[0] === "fn")?.[3] || entity.handle;
61
+ lines.push(` - ${name} (${entity.roles?.join(", ") || "N/A"})`);
62
+ }
63
+ }
64
+
65
+ return lines.join("\n");
66
+ }
67
+
68
+ function formatEntityInfo(data) {
69
+ const lines = [];
70
+
71
+ lines.push(`Handle: ${data.handle}`);
72
+
73
+ if (data.vcardArray) {
74
+ const vcard = data.vcardArray[1];
75
+ const fn = vcard.find(v => v[0] === "fn")?.[3];
76
+ const email = vcard.find(v => v[0] === "email")?.[3];
77
+ const kind = vcard.find(v => v[0] === "kind")?.[3];
78
+
79
+ if (fn) lines.push(`Name: ${fn}`);
80
+ if (kind) lines.push(`Type: ${kind}`);
81
+ if (email) lines.push(`Email: ${email}`);
82
+ }
83
+
84
+ if (data.roles) {
85
+ lines.push(`Roles: ${data.roles.join(", ")}`);
86
+ }
87
+
88
+ if (data.publicIds) {
89
+ lines.push("\nPublic IDs:");
90
+ for (const pid of data.publicIds) {
91
+ lines.push(` - ${pid.type}: ${pid.identifier}`);
92
+ }
93
+ }
94
+
95
+ if (data.events) {
96
+ lines.push("\nEvents:");
97
+ for (const event of data.events) {
98
+ lines.push(` - ${event.eventAction}: ${event.eventDate}`);
99
+ }
100
+ }
101
+
102
+ if (data.legalRepresentative) {
103
+ lines.push(`\nLegal Representative: ${data.legalRepresentative}`);
104
+ }
105
+
106
+ return lines.join("\n");
107
+ }
108
+
109
+ function formatNameserverInfo(data) {
110
+ const lines = [];
111
+
112
+ lines.push(`Nameserver: ${data.ldhName}`);
113
+ lines.push(`Handle: ${data.handle || "N/A"}`);
114
+
115
+ if (data.ipAddresses) {
116
+ if (data.ipAddresses.v4) {
117
+ lines.push(`IPv4: ${data.ipAddresses.v4.join(", ")}`);
118
+ }
119
+ if (data.ipAddresses.v6) {
120
+ lines.push(`IPv6: ${data.ipAddresses.v6.join(", ")}`);
121
+ }
122
+ }
123
+
124
+ if (data.events) {
125
+ lines.push("\nEvents:");
126
+ for (const event of data.events) {
127
+ lines.push(` - ${event.eventAction}: ${event.eventDate}`);
128
+ }
129
+ }
130
+
131
+ return lines.join("\n");
132
+ }
133
+
134
+ function formatIpInfo(data) {
135
+ const lines = [];
136
+
137
+ lines.push(`Handle: ${data.handle}`);
138
+ lines.push(`Start Address: ${data.startAddress}`);
139
+ lines.push(`End Address: ${data.endAddress}`);
140
+ lines.push(`IP Version: ${data.ipVersion}`);
141
+ lines.push(`Name: ${data.name || "N/A"}`);
142
+ lines.push(`Type: ${data.type || "N/A"}`);
143
+ lines.push(`Country: ${data.country || "N/A"}`);
144
+
145
+ if (data.status) {
146
+ lines.push(`Status: ${data.status.join(", ")}`);
147
+ }
148
+
149
+ if (data.events) {
150
+ lines.push("\nEvents:");
151
+ for (const event of data.events) {
152
+ lines.push(` - ${event.eventAction}: ${event.eventDate}`);
153
+ }
154
+ }
155
+
156
+ return lines.join("\n");
157
+ }
158
+
159
+ function formatAsnInfo(data) {
160
+ const lines = [];
161
+
162
+ lines.push(`Handle: ${data.handle}`);
163
+ lines.push(`Start ASN: ${data.startAutnum}`);
164
+ lines.push(`End ASN: ${data.endAutnum}`);
165
+ lines.push(`Name: ${data.name || "N/A"}`);
166
+ lines.push(`Type: ${data.type || "N/A"}`);
167
+ lines.push(`Country: ${data.country || "N/A"}`);
168
+
169
+ if (data.status) {
170
+ lines.push(`Status: ${data.status.join(", ")}`);
171
+ }
172
+
173
+ if (data.events) {
174
+ lines.push("\nEvents:");
175
+ for (const event of data.events) {
176
+ lines.push(` - ${event.eventAction}: ${event.eventDate}`);
177
+ }
178
+ }
179
+
180
+ return lines.join("\n");
181
+ }
182
+
183
+ const server = new McpServer({
184
+ name: "registro-br-rdap",
185
+ version: "1.0.0",
186
+ });
187
+
188
+ // Tool: Query domain information
189
+ server.tool(
190
+ "rdap_domain",
191
+ "Query RDAP information for a .br domain",
192
+ {
193
+ domain: z.string().describe("The domain name to query (e.g., nic.br, registro.br)"),
194
+ },
195
+ async ({ domain }) => {
196
+ try {
197
+ const data = await fetchRdap(`/domain/${domain}`);
198
+ return {
199
+ content: [
200
+ {
201
+ type: "text",
202
+ text: formatDomainInfo(data),
203
+ },
204
+ {
205
+ type: "text",
206
+ text: "\n\n--- Raw JSON ---\n" + JSON.stringify(data, null, 2),
207
+ },
208
+ ],
209
+ };
210
+ } catch (error) {
211
+ return {
212
+ content: [
213
+ {
214
+ type: "text",
215
+ text: `Error querying domain: ${error.message}`,
216
+ },
217
+ ],
218
+ isError: true,
219
+ };
220
+ }
221
+ }
222
+ );
223
+
224
+ // Tool: Query entity information
225
+ server.tool(
226
+ "rdap_entity",
227
+ "Query RDAP information for an entity (by CNPJ, CPF, or handle)",
228
+ {
229
+ entity: z.string().describe("The entity identifier (CNPJ without punctuation, CPF, or handle like 'FAN')"),
230
+ },
231
+ async ({ entity }) => {
232
+ try {
233
+ const data = await fetchRdap(`/entity/${entity}`);
234
+ return {
235
+ content: [
236
+ {
237
+ type: "text",
238
+ text: formatEntityInfo(data),
239
+ },
240
+ {
241
+ type: "text",
242
+ text: "\n\n--- Raw JSON ---\n" + JSON.stringify(data, null, 2),
243
+ },
244
+ ],
245
+ };
246
+ } catch (error) {
247
+ return {
248
+ content: [
249
+ {
250
+ type: "text",
251
+ text: `Error querying entity: ${error.message}`,
252
+ },
253
+ ],
254
+ isError: true,
255
+ };
256
+ }
257
+ }
258
+ );
259
+
260
+ // Tool: Query nameserver information
261
+ server.tool(
262
+ "rdap_nameserver",
263
+ "Query RDAP information for a nameserver",
264
+ {
265
+ nameserver: z.string().describe("The nameserver hostname (e.g., a.dns.br)"),
266
+ },
267
+ async ({ nameserver }) => {
268
+ try {
269
+ const data = await fetchRdap(`/nameserver/${nameserver}`);
270
+ return {
271
+ content: [
272
+ {
273
+ type: "text",
274
+ text: formatNameserverInfo(data),
275
+ },
276
+ {
277
+ type: "text",
278
+ text: "\n\n--- Raw JSON ---\n" + JSON.stringify(data, null, 2),
279
+ },
280
+ ],
281
+ };
282
+ } catch (error) {
283
+ return {
284
+ content: [
285
+ {
286
+ type: "text",
287
+ text: `Error querying nameserver: ${error.message}`,
288
+ },
289
+ ],
290
+ isError: true,
291
+ };
292
+ }
293
+ }
294
+ );
295
+
296
+ // Tool: Query IP network information
297
+ server.tool(
298
+ "rdap_ip",
299
+ "Query RDAP information for an IP address or network",
300
+ {
301
+ ip: z.string().describe("The IP address or CIDR notation (e.g., 200.160.0.0, 200.160.0.0/20)"),
302
+ },
303
+ async ({ ip }) => {
304
+ try {
305
+ const data = await fetchRdap(`/ip/${ip}`);
306
+ return {
307
+ content: [
308
+ {
309
+ type: "text",
310
+ text: formatIpInfo(data),
311
+ },
312
+ {
313
+ type: "text",
314
+ text: "\n\n--- Raw JSON ---\n" + JSON.stringify(data, null, 2),
315
+ },
316
+ ],
317
+ };
318
+ } catch (error) {
319
+ return {
320
+ content: [
321
+ {
322
+ type: "text",
323
+ text: `Error querying IP: ${error.message}`,
324
+ },
325
+ ],
326
+ isError: true,
327
+ };
328
+ }
329
+ }
330
+ );
331
+
332
+ // Tool: Query ASN information
333
+ server.tool(
334
+ "rdap_asn",
335
+ "Query RDAP information for an Autonomous System Number",
336
+ {
337
+ asn: z.string().describe("The AS number (e.g., 22548 or AS22548)"),
338
+ },
339
+ async ({ asn }) => {
340
+ try {
341
+ const asnNumber = asn.replace(/^AS/i, "");
342
+ const data = await fetchRdap(`/autnum/${asnNumber}`);
343
+ return {
344
+ content: [
345
+ {
346
+ type: "text",
347
+ text: formatAsnInfo(data),
348
+ },
349
+ {
350
+ type: "text",
351
+ text: "\n\n--- Raw JSON ---\n" + JSON.stringify(data, null, 2),
352
+ },
353
+ ],
354
+ };
355
+ } catch (error) {
356
+ return {
357
+ content: [
358
+ {
359
+ type: "text",
360
+ text: `Error querying ASN: ${error.message}`,
361
+ },
362
+ ],
363
+ isError: true,
364
+ };
365
+ }
366
+ }
367
+ );
368
+
369
+ async function main() {
370
+ const transport = new StdioServerTransport();
371
+ await server.connect(transport);
372
+ console.error("Registro.br RDAP MCP Server running on stdio");
373
+ }
374
+
375
+ main().catch(console.error);