@brazil-mcp/cep 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.
- package/dist/index.d.ts +2 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/providers/brasilapi.d.ts +5 -0
- package/dist/providers/brasilapi.js +14 -0
- package/dist/providers/brasilapi.js.map +1 -0
- package/dist/providers/index.d.ts +9 -0
- package/dist/providers/index.js +32 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/providers/viacep.d.ts +5 -0
- package/dist/providers/viacep.js +13 -0
- package/dist/providers/viacep.js.map +1 -0
- package/dist/server.d.ts +11 -0
- package/dist/server.js +31 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/format.d.ts +2 -0
- package/dist/tools/format.js +6 -0
- package/dist/tools/format.js.map +1 -0
- package/dist/tools/handlers.d.ts +10 -0
- package/dist/tools/handlers.js +19 -0
- package/dist/tools/handlers.js.map +1 -0
- package/dist/types.d.ts +14 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/cep.d.ts +3 -0
- package/dist/utils/cep.js +10 -0
- package/dist/utils/cep.js.map +1 -0
- package/dist/utils/rate-limiter.d.ts +88 -0
- package/dist/utils/rate-limiter.js +263 -0
- package/dist/utils/rate-limiter.js.map +1 -0
- package/package.json +31 -0
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
+
import { createServer } from "./server.js";
|
|
4
|
+
async function main() {
|
|
5
|
+
const { server } = createServer();
|
|
6
|
+
await server.connect(new StdioServerTransport());
|
|
7
|
+
console.error("[cep-mcp] Server iniciado via stdio");
|
|
8
|
+
}
|
|
9
|
+
main().catch((err) => { console.error("[cep-mcp] Erro fatal:", err); process.exit(1); });
|
|
10
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,KAAK,UAAU,IAAI;IACjB,MAAM,EAAE,MAAM,EAAE,GAAG,YAAY,EAAE,CAAC;IAClC,MAAM,MAAM,CAAC,OAAO,CAAC,IAAI,oBAAoB,EAAE,CAAC,CAAC;IACjD,OAAO,CAAC,KAAK,CAAC,qCAAqC,CAAC,CAAC;AACvD,CAAC;AACD,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE,GAAG,OAAO,CAAC,KAAK,CAAC,uBAAuB,EAAE,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export class BrasilApiCepProvider {
|
|
2
|
+
name = "BrasilAPI";
|
|
3
|
+
async fetchCep(cep) {
|
|
4
|
+
const res = await fetch(`https://brasilapi.com.br/api/cep/v2/${cep}`, { headers: { Accept: "application/json" }, signal: AbortSignal.timeout(10_000) });
|
|
5
|
+
if (!res.ok) {
|
|
6
|
+
if (res.status === 404)
|
|
7
|
+
throw new Error(`CEP ${cep} não encontrado.`);
|
|
8
|
+
throw new Error(`BrasilAPI CEP retornou status ${res.status}`);
|
|
9
|
+
}
|
|
10
|
+
const raw = await res.json();
|
|
11
|
+
return { cep: (raw.cep || "").replace(/\D/g, ""), logradouro: raw.street || "", complemento: "", bairro: raw.neighborhood || "", cidade: raw.city || "", uf: raw.state || "", ibge: raw.city_ibge || "", ddd: "" };
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=brasilapi.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"brasilapi.js","sourceRoot":"","sources":["../../src/providers/brasilapi.ts"],"names":[],"mappings":"AACA,MAAM,OAAO,oBAAoB;IAC/B,IAAI,GAAG,WAAW,CAAC;IACnB,KAAK,CAAC,QAAQ,CAAC,GAAW;QACxB,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,uCAAuC,GAAG,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,kBAAkB,EAAE,EAAE,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QACxJ,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACZ,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG;gBAAE,MAAM,IAAI,KAAK,CAAC,OAAO,GAAG,kBAAkB,CAAC,CAAC;YACtE,MAAM,IAAI,KAAK,CAAC,iCAAiC,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;QACjE,CAAC;QACD,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;QAC7B,OAAO,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,GAAG,IAAE,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,EAAC,EAAE,CAAC,EAAE,UAAU,EAAE,GAAG,CAAC,MAAM,IAAE,EAAE,EAAE,WAAW,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,CAAC,YAAY,IAAE,EAAE,EAAE,MAAM,EAAE,GAAG,CAAC,IAAI,IAAE,EAAE,EAAE,EAAE,EAAE,GAAG,CAAC,KAAK,IAAE,EAAE,EAAE,IAAI,EAAE,GAAG,CAAC,SAAS,IAAE,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE,CAAC;IACxM,CAAC;CACF"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { CepData, CepProvider } from "../types.js";
|
|
2
|
+
export declare class CepService {
|
|
3
|
+
private providers;
|
|
4
|
+
private cache;
|
|
5
|
+
constructor(providers?: CepProvider[]);
|
|
6
|
+
lookup(cep: string): Promise<CepData>;
|
|
7
|
+
clearCache(): void;
|
|
8
|
+
get cacheSize(): number;
|
|
9
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { ViaCepProvider } from "./viacep.js";
|
|
2
|
+
import { BrasilApiCepProvider } from "./brasilapi.js";
|
|
3
|
+
export class CepService {
|
|
4
|
+
providers;
|
|
5
|
+
cache = new Map();
|
|
6
|
+
constructor(providers) { this.providers = providers ?? [new ViaCepProvider(), new BrasilApiCepProvider()]; }
|
|
7
|
+
async lookup(cep) {
|
|
8
|
+
const cached = this.cache.get(cep);
|
|
9
|
+
if (cached && Date.now() < cached.expiry) {
|
|
10
|
+
console.error(`[cep-mcp] Cache hit para ${cep}`);
|
|
11
|
+
return cached.data;
|
|
12
|
+
}
|
|
13
|
+
const errors = [];
|
|
14
|
+
for (const p of this.providers) {
|
|
15
|
+
try {
|
|
16
|
+
console.error(`[cep-mcp] Consultando ${p.name} para ${cep}`);
|
|
17
|
+
const data = await p.fetchCep(cep);
|
|
18
|
+
this.cache.set(cep, { data, expiry: Date.now() + 86_400_000 });
|
|
19
|
+
return data;
|
|
20
|
+
}
|
|
21
|
+
catch (err) {
|
|
22
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
23
|
+
console.error(`[cep-mcp] ${p.name} falhou: ${msg}`);
|
|
24
|
+
errors.push(`${p.name}: ${msg}`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
throw new Error(`Não foi possível consultar o CEP ${cep}. Erros: ${errors.join(" | ")}`);
|
|
28
|
+
}
|
|
29
|
+
clearCache() { this.cache.clear(); }
|
|
30
|
+
get cacheSize() { return this.cache.size; }
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/providers/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAEtD,MAAM,OAAO,UAAU;IACb,SAAS,CAAgB;IACzB,KAAK,GAAG,IAAI,GAAG,EAA6C,CAAC;IACrE,YAAY,SAAyB,IAAI,IAAI,CAAC,SAAS,GAAG,SAAS,IAAI,CAAC,IAAI,cAAc,EAAE,EAAE,IAAI,oBAAoB,EAAE,CAAC,CAAC,CAAC,CAAC;IAC5H,KAAK,CAAC,MAAM,CAAC,GAAW;QACtB,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACnC,IAAI,MAAM,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC;YAAC,OAAO,CAAC,KAAK,CAAC,4BAA4B,GAAG,EAAE,CAAC,CAAC;YAAC,OAAO,MAAM,CAAC,IAAI,CAAC;QAAC,CAAC;QACnH,MAAM,MAAM,GAAa,EAAE,CAAC;QAC5B,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;YAC/B,IAAI,CAAC;gBAAC,OAAO,CAAC,KAAK,CAAC,yBAAyB,CAAC,CAAC,IAAI,SAAS,GAAG,EAAE,CAAC,CAAC;gBAAC,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;gBAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,UAAU,EAAE,CAAC,CAAC;gBAAC,OAAO,IAAI,CAAC;YAAC,CAAC;YACtL,OAAO,GAAG,EAAE,CAAC;gBAAC,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;gBAAC,OAAO,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC,IAAI,YAAY,GAAG,EAAE,CAAC,CAAC;gBAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,KAAK,GAAG,EAAE,CAAC,CAAC;YAAC,CAAC;QACtK,CAAC;QACD,MAAM,IAAI,KAAK,CAAC,oCAAoC,GAAG,YAAY,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAC3F,CAAC;IACD,UAAU,KAAK,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IACpC,IAAI,SAAS,KAAK,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;CAC5C"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export class ViaCepProvider {
|
|
2
|
+
name = "ViaCEP";
|
|
3
|
+
async fetchCep(cep) {
|
|
4
|
+
const res = await fetch(`https://viacep.com.br/ws/${cep}/json/`, { signal: AbortSignal.timeout(10_000) });
|
|
5
|
+
if (!res.ok)
|
|
6
|
+
throw new Error(`ViaCEP retornou status ${res.status}`);
|
|
7
|
+
const raw = await res.json();
|
|
8
|
+
if (raw.erro)
|
|
9
|
+
throw new Error(`CEP ${cep} não encontrado.`);
|
|
10
|
+
return { cep: (raw.cep || "").replace(/\D/g, ""), logradouro: raw.logradouro || "", complemento: raw.complemento || "", bairro: raw.bairro || "", cidade: raw.localidade || "", uf: raw.uf || "", ibge: raw.ibge || "", ddd: raw.ddd || "" };
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=viacep.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"viacep.js","sourceRoot":"","sources":["../../src/providers/viacep.ts"],"names":[],"mappings":"AACA,MAAM,OAAO,cAAc;IACzB,IAAI,GAAG,QAAQ,CAAC;IAChB,KAAK,CAAC,QAAQ,CAAC,GAAW;QACxB,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,4BAA4B,GAAG,QAAQ,EAAE,EAAE,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QAC1G,IAAI,CAAC,GAAG,CAAC,EAAE;YAAE,MAAM,IAAI,KAAK,CAAC,0BAA0B,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;QACrE,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;QAC7B,IAAI,GAAG,CAAC,IAAI;YAAE,MAAM,IAAI,KAAK,CAAC,OAAO,GAAG,kBAAkB,CAAC,CAAC;QAC5D,OAAO,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,GAAG,IAAE,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,EAAC,EAAE,CAAC,EAAE,UAAU,EAAE,GAAG,CAAC,UAAU,IAAE,EAAE,EAAE,WAAW,EAAE,GAAG,CAAC,WAAW,IAAE,EAAE,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,IAAE,EAAE,EAAE,MAAM,EAAE,GAAG,CAAC,UAAU,IAAE,EAAE,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,IAAE,EAAE,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,IAAE,EAAE,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,IAAE,EAAE,EAAE,CAAC;IAC9N,CAAC;CACF"}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { CepService } from "./providers/index.js";
|
|
3
|
+
import { DailyRateLimiter } from "./utils/rate-limiter.js";
|
|
4
|
+
export declare function createServer(opts?: {
|
|
5
|
+
service?: CepService;
|
|
6
|
+
rateLimiter?: DailyRateLimiter | null;
|
|
7
|
+
}): {
|
|
8
|
+
server: McpServer;
|
|
9
|
+
cepService: CepService;
|
|
10
|
+
rateLimiter: DailyRateLimiter | null;
|
|
11
|
+
};
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { CepService } from "./providers/index.js";
|
|
4
|
+
import { handleCepLookup, handleCepValidate } from "./tools/handlers.js";
|
|
5
|
+
import { DailyRateLimiter, rateLimitMessage } from "./utils/rate-limiter.js";
|
|
6
|
+
export function createServer(opts) {
|
|
7
|
+
const cepService = opts?.service ?? new CepService();
|
|
8
|
+
const limiter = opts?.rateLimiter === null
|
|
9
|
+
? null
|
|
10
|
+
: opts?.rateLimiter ?? new DailyRateLimiter(50);
|
|
11
|
+
const server = new McpServer({
|
|
12
|
+
name: "cep-mcp", version: "1.0.0",
|
|
13
|
+
description: "Consulta CEP e endereços brasileiros. Plano gratuito: 50 consultas/dia.",
|
|
14
|
+
});
|
|
15
|
+
function withLimit(fn, label) {
|
|
16
|
+
if (limiter && !limiter.tryConsume()) {
|
|
17
|
+
return Promise.resolve({ content: [{ type: "text", text: rateLimitMessage(label, limiter.limit) }] });
|
|
18
|
+
}
|
|
19
|
+
return fn();
|
|
20
|
+
}
|
|
21
|
+
server.registerTool("cep_lookup", {
|
|
22
|
+
description: "Consulta endereço completo por CEP brasileiro.",
|
|
23
|
+
inputSchema: { cep: z.string().describe("CEP (com ou sem formatação)") },
|
|
24
|
+
}, async ({ cep }) => withLimit(() => handleCepLookup(cep, cepService), "CEP"));
|
|
25
|
+
server.registerTool("cep_validate", {
|
|
26
|
+
description: "Valida formato de CEP (local, sem consumir quota).",
|
|
27
|
+
inputSchema: { cep: z.string().describe("CEP para validar") },
|
|
28
|
+
}, async ({ cep }) => handleCepValidate(cep));
|
|
29
|
+
return { server, cepService, rateLimiter: limiter };
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAmB,MAAM,qBAAqB,CAAC;AAC1F,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAE7E,MAAM,UAAU,YAAY,CAAC,IAG5B;IACC,MAAM,UAAU,GAAG,IAAI,EAAE,OAAO,IAAI,IAAI,UAAU,EAAE,CAAC;IACrD,MAAM,OAAO,GACX,IAAI,EAAE,WAAW,KAAK,IAAI;QACxB,CAAC,CAAC,IAAI;QACN,CAAC,CAAC,IAAI,EAAE,WAAW,IAAI,IAAI,gBAAgB,CAAC,EAAE,CAAC,CAAC;IAEpD,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;QAC3B,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,OAAO;QACjC,WAAW,EAAE,yEAAyE;KACvF,CAAC,CAAC;IAEH,SAAS,SAAS,CAAC,EAA6B,EAAE,KAAa;QAC7D,IAAI,OAAO,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC;YACrC,OAAO,OAAO,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,gBAAgB,CAAC,KAAK,EAAE,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;QACjH,CAAC;QACD,OAAO,EAAE,EAAE,CAAC;IACd,CAAC;IAED,MAAM,CAAC,YAAY,CAAC,YAAY,EAAE;QAChC,WAAW,EAAE,gDAAgD;QAC7D,WAAW,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,6BAA6B,CAAC,EAAE;KACzE,EAAE,KAAK,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC,eAAe,CAAC,GAAG,EAAE,UAAU,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC;IAEhF,MAAM,CAAC,YAAY,CAAC,cAAc,EAAE;QAClC,WAAW,EAAE,oDAAoD;QACjE,WAAW,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,kBAAkB,CAAC,EAAE;KAC9D,EAAE,KAAK,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE,CAAC,iBAAiB,CAAC,GAAG,CAAC,CAAC,CAAC;IAE9C,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC;AACtD,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { formatCep } from "../utils/cep.js";
|
|
2
|
+
export function formatCepResult(data) {
|
|
3
|
+
const lines = [`CEP: ${formatCep(data.cep)}`, data.logradouro ? `Logradouro: ${data.logradouro}` : "", data.complemento ? `Complemento: ${data.complemento}` : "", data.bairro ? `Bairro: ${data.bairro}` : "", `Cidade: ${data.cidade}/${data.uf}`, data.ibge ? `Código IBGE: ${data.ibge}` : "", data.ddd ? `DDD: ${data.ddd}` : ""];
|
|
4
|
+
return lines.filter(Boolean).join("\n");
|
|
5
|
+
}
|
|
6
|
+
//# sourceMappingURL=format.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format.js","sourceRoot":"","sources":["../../src/tools/format.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE5C,MAAM,UAAU,eAAe,CAAC,IAAa;IAC3C,MAAM,KAAK,GAAG,CAAC,QAAQ,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,eAAe,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,gBAAgB,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,WAAW,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,WAAW,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,gBAAgB,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACvU,OAAO,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1C,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { CepService } from "../providers/index.js";
|
|
2
|
+
export interface ToolResult {
|
|
3
|
+
[key: string]: unknown;
|
|
4
|
+
content: Array<{
|
|
5
|
+
type: "text";
|
|
6
|
+
text: string;
|
|
7
|
+
}>;
|
|
8
|
+
}
|
|
9
|
+
export declare function handleCepLookup(cep: string, service: CepService): Promise<ToolResult>;
|
|
10
|
+
export declare function handleCepValidate(cep: string): Promise<ToolResult>;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { cleanCep, formatCep, isValidCep } from "../utils/cep.js";
|
|
2
|
+
import { formatCepResult } from "./format.js";
|
|
3
|
+
function text(t) { return { content: [{ type: "text", text: t }] }; }
|
|
4
|
+
export async function handleCepLookup(cep, service) {
|
|
5
|
+
const clean = cleanCep(cep);
|
|
6
|
+
if (!isValidCep(clean))
|
|
7
|
+
return text(`❌ CEP inválido: ${cep}. Deve conter 8 dígitos.`);
|
|
8
|
+
try {
|
|
9
|
+
return text(formatCepResult(await service.lookup(clean)));
|
|
10
|
+
}
|
|
11
|
+
catch (err) {
|
|
12
|
+
return text(`❌ Erro ao consultar CEP: ${err instanceof Error ? err.message : String(err)}`);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export async function handleCepValidate(cep) {
|
|
16
|
+
const clean = cleanCep(cep);
|
|
17
|
+
return text(isValidCep(clean) ? `✅ CEP ${formatCep(clean)} tem formato válido.` : `❌ CEP ${cep} é inválido.`);
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=handlers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handlers.js","sourceRoot":"","sources":["../../src/tools/handlers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAElE,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE9C,SAAS,IAAI,CAAC,CAAS,IAAgB,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;AAEzF,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,GAAW,EAAE,OAAmB;IACpE,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC;IAC5B,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC,mBAAmB,GAAG,0BAA0B,CAAC,CAAC;IACtF,IAAI,CAAC;QAAC,OAAO,IAAI,CAAC,eAAe,CAAC,MAAM,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAAC,CAAC;IAClE,OAAO,GAAG,EAAE,CAAC;QAAC,OAAO,IAAI,CAAC,4BAA4B,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAAC,CAAC;AAC9G,CAAC;AACD,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,GAAW;IACjD,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC;IAC5B,OAAO,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,SAAS,CAAC,KAAK,CAAC,sBAAsB,CAAC,CAAC,CAAC,SAAS,GAAG,cAAc,CAAC,CAAC;AAChH,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface CepData {
|
|
2
|
+
cep: string;
|
|
3
|
+
logradouro: string;
|
|
4
|
+
complemento: string;
|
|
5
|
+
bairro: string;
|
|
6
|
+
cidade: string;
|
|
7
|
+
uf: string;
|
|
8
|
+
ibge: string;
|
|
9
|
+
ddd: string;
|
|
10
|
+
}
|
|
11
|
+
export interface CepProvider {
|
|
12
|
+
name: string;
|
|
13
|
+
fetchCep(cep: string): Promise<CepData>;
|
|
14
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export function cleanCep(cep) { return cep.replace(/\D/g, ""); }
|
|
2
|
+
export function formatCep(cep) {
|
|
3
|
+
const c = cleanCep(cep);
|
|
4
|
+
return c.length === 8 ? c.replace(/^(\d{5})(\d{3})$/, "$1-$2") : cep;
|
|
5
|
+
}
|
|
6
|
+
export function isValidCep(cep) {
|
|
7
|
+
const c = cleanCep(cep);
|
|
8
|
+
return c.length === 8 && !/^0{8}$/.test(c);
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=cep.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cep.js","sourceRoot":"","sources":["../../src/utils/cep.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,QAAQ,CAAC,GAAW,IAAY,OAAO,GAAG,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;AAChF,MAAM,UAAU,SAAS,CAAC,GAAW;IACnC,MAAM,CAAC,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC;IACxB,OAAO,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,kBAAkB,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;AACvE,CAAC;AACD,MAAM,UAAU,UAAU,CAAC,GAAW;IACpC,MAAM,CAAC,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC;IACxB,OAAO,CAAC,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAC7C,CAAC"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interface para backends de armazenamento de contagem de chamadas do rate limiter.
|
|
3
|
+
*/
|
|
4
|
+
export interface RateLimiterStore {
|
|
5
|
+
readonly type: "memory" | "redis";
|
|
6
|
+
/**
|
|
7
|
+
* Tenta consumir 1 unidade de rate limit para a chave no dia informado.
|
|
8
|
+
* Retorna a contagem atual após o incremento.
|
|
9
|
+
*/
|
|
10
|
+
increment(key: string, day: string, ttlSeconds: number): Promise<number> | number;
|
|
11
|
+
/** Retorna a contagem atual sem incrementar. */
|
|
12
|
+
get(key: string, day: string): Promise<number> | number;
|
|
13
|
+
/** Reseta o armazenamento. */
|
|
14
|
+
reset(): Promise<void> | void;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Armazenamento em memória local (Map).
|
|
18
|
+
* Síncrono e ideal para servidores locais (CLI, NPM Free).
|
|
19
|
+
*/
|
|
20
|
+
export declare class MemoryRateLimiterStore implements RateLimiterStore {
|
|
21
|
+
readonly type: "memory";
|
|
22
|
+
private counts;
|
|
23
|
+
increment(key: string, day: string): number;
|
|
24
|
+
get(key: string, day: string): number;
|
|
25
|
+
reset(): void;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Armazenamento distribuído via Redis.
|
|
29
|
+
* Suporta Upstash / MCPize REST API via fetch nativo ou clientes TCP (ioredis / node-redis).
|
|
30
|
+
* Possui resiliência integrada: em caso de falha no Redis, faz fallback automático para memória.
|
|
31
|
+
*/
|
|
32
|
+
export declare class RedisRateLimiterStore implements RateLimiterStore {
|
|
33
|
+
readonly type: "redis";
|
|
34
|
+
private fallbackMemory;
|
|
35
|
+
private redisClient;
|
|
36
|
+
private clientPromise;
|
|
37
|
+
private isRest;
|
|
38
|
+
private restUrl?;
|
|
39
|
+
private restToken?;
|
|
40
|
+
private redisUrl?;
|
|
41
|
+
constructor(opts?: {
|
|
42
|
+
restUrl?: string;
|
|
43
|
+
restToken?: string;
|
|
44
|
+
redisUrl?: string;
|
|
45
|
+
});
|
|
46
|
+
private getClient;
|
|
47
|
+
increment(key: string, day: string, ttlSeconds: number): Promise<number>;
|
|
48
|
+
get(key: string, day: string): Promise<number>;
|
|
49
|
+
reset(): Promise<void>;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Detecta o backend de armazenamento mais apropriado baseado nas variáveis de ambiente.
|
|
53
|
+
*/
|
|
54
|
+
export declare function detectRateLimiterStoreFromEnv(): RateLimiterStore;
|
|
55
|
+
/**
|
|
56
|
+
* Rate limiter por dia para os pacotes free ou deploy gerenciado.
|
|
57
|
+
* Armazena contagem em memória ou no Redis (detectado por variáveis de ambiente ou configurado via store).
|
|
58
|
+
*/
|
|
59
|
+
export declare class DailyRateLimiter {
|
|
60
|
+
private store;
|
|
61
|
+
private maxPerDay;
|
|
62
|
+
constructor(maxPerDayOrOpts?: number | {
|
|
63
|
+
maxPerDay?: number;
|
|
64
|
+
store?: RateLimiterStore;
|
|
65
|
+
});
|
|
66
|
+
private today;
|
|
67
|
+
/**
|
|
68
|
+
* Tenta consumir um request. Retorna true se permitido, false se limitado.
|
|
69
|
+
* Se usando store em memória, retorna boolean síncrono. Se no Redis, retorna Promise<boolean>.
|
|
70
|
+
*/
|
|
71
|
+
tryConsume(key?: string): boolean | Promise<boolean>;
|
|
72
|
+
/**
|
|
73
|
+
* Retorna quantos requests restam hoje.
|
|
74
|
+
*/
|
|
75
|
+
remaining(key?: string): number | Promise<number>;
|
|
76
|
+
/**
|
|
77
|
+
* Retorna o limite diário configurado.
|
|
78
|
+
*/
|
|
79
|
+
get limit(): number;
|
|
80
|
+
/**
|
|
81
|
+
* Reseta a contagem (útil para testes).
|
|
82
|
+
*/
|
|
83
|
+
reset(): void | Promise<void>;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Mensagem padrão de upgrade quando o rate limit é atingido.
|
|
87
|
+
*/
|
|
88
|
+
export declare function rateLimitMessage(toolName: string, limit: number): string;
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Armazenamento em memória local (Map).
|
|
3
|
+
* Síncrono e ideal para servidores locais (CLI, NPM Free).
|
|
4
|
+
*/
|
|
5
|
+
export class MemoryRateLimiterStore {
|
|
6
|
+
type = "memory";
|
|
7
|
+
counts = new Map();
|
|
8
|
+
increment(key, day) {
|
|
9
|
+
const entry = this.counts.get(key);
|
|
10
|
+
if (!entry || entry.day !== day) {
|
|
11
|
+
this.counts.set(key, { count: 1, day });
|
|
12
|
+
return 1;
|
|
13
|
+
}
|
|
14
|
+
entry.count++;
|
|
15
|
+
return entry.count;
|
|
16
|
+
}
|
|
17
|
+
get(key, day) {
|
|
18
|
+
const entry = this.counts.get(key);
|
|
19
|
+
if (!entry || entry.day !== day)
|
|
20
|
+
return 0;
|
|
21
|
+
return entry.count;
|
|
22
|
+
}
|
|
23
|
+
reset() {
|
|
24
|
+
this.counts.clear();
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Armazenamento distribuído via Redis.
|
|
29
|
+
* Suporta Upstash / MCPize REST API via fetch nativo ou clientes TCP (ioredis / node-redis).
|
|
30
|
+
* Possui resiliência integrada: em caso de falha no Redis, faz fallback automático para memória.
|
|
31
|
+
*/
|
|
32
|
+
export class RedisRateLimiterStore {
|
|
33
|
+
type = "redis";
|
|
34
|
+
fallbackMemory = new MemoryRateLimiterStore();
|
|
35
|
+
redisClient = null;
|
|
36
|
+
clientPromise = null;
|
|
37
|
+
isRest;
|
|
38
|
+
restUrl;
|
|
39
|
+
restToken;
|
|
40
|
+
redisUrl;
|
|
41
|
+
constructor(opts) {
|
|
42
|
+
this.restUrl = opts?.restUrl ?? process.env.UPSTASH_REDIS_REST_URL ?? process.env.REDIS_REST_URL;
|
|
43
|
+
this.restToken = opts?.restToken ?? process.env.UPSTASH_REDIS_REST_TOKEN ?? process.env.REDIS_REST_TOKEN;
|
|
44
|
+
this.redisUrl = opts?.redisUrl ?? process.env.REDIS_URL ?? process.env.MCPIZE_REDIS_URL;
|
|
45
|
+
this.isRest = Boolean(this.restUrl && this.restToken);
|
|
46
|
+
}
|
|
47
|
+
async getClient() {
|
|
48
|
+
if (this.isRest)
|
|
49
|
+
return null;
|
|
50
|
+
if (this.redisClient)
|
|
51
|
+
return this.redisClient;
|
|
52
|
+
if (this.clientPromise)
|
|
53
|
+
return this.clientPromise;
|
|
54
|
+
this.clientPromise = (async () => {
|
|
55
|
+
try {
|
|
56
|
+
try {
|
|
57
|
+
// @ts-ignore
|
|
58
|
+
const ioredis = await import("ioredis");
|
|
59
|
+
const Redis = ioredis.default || ioredis.Redis || ioredis;
|
|
60
|
+
const client = this.redisUrl
|
|
61
|
+
? new Redis(this.redisUrl)
|
|
62
|
+
: new Redis({
|
|
63
|
+
host: process.env.REDIS_HOST || "localhost",
|
|
64
|
+
port: Number(process.env.REDIS_PORT || 6379),
|
|
65
|
+
password: process.env.REDIS_PASSWORD,
|
|
66
|
+
});
|
|
67
|
+
client.on("error", (err) => {
|
|
68
|
+
console.error(`[rate-limiter] Redis Client Error: ${err.message || err}`);
|
|
69
|
+
});
|
|
70
|
+
this.redisClient = client;
|
|
71
|
+
return client;
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
// @ts-ignore
|
|
75
|
+
const nodeRedis = await import("redis");
|
|
76
|
+
const createClient = nodeRedis.createClient || nodeRedis.default?.createClient;
|
|
77
|
+
if (createClient) {
|
|
78
|
+
const client = this.redisUrl
|
|
79
|
+
? createClient({ url: this.redisUrl })
|
|
80
|
+
: createClient({
|
|
81
|
+
socket: {
|
|
82
|
+
host: process.env.REDIS_HOST || "localhost",
|
|
83
|
+
port: Number(process.env.REDIS_PORT || 6379),
|
|
84
|
+
},
|
|
85
|
+
password: process.env.REDIS_PASSWORD,
|
|
86
|
+
});
|
|
87
|
+
client.on("error", (err) => {
|
|
88
|
+
console.error(`[rate-limiter] node-redis Client Error: ${err.message || err}`);
|
|
89
|
+
});
|
|
90
|
+
await client.connect();
|
|
91
|
+
this.redisClient = client;
|
|
92
|
+
return client;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
// Ignora caso nenhum driver Redis esteja instalado
|
|
98
|
+
}
|
|
99
|
+
return null;
|
|
100
|
+
})();
|
|
101
|
+
return this.clientPromise;
|
|
102
|
+
}
|
|
103
|
+
async increment(key, day, ttlSeconds) {
|
|
104
|
+
const redisKey = `ratelimit:${day}:${key}`;
|
|
105
|
+
if (this.isRest && this.restUrl && this.restToken) {
|
|
106
|
+
try {
|
|
107
|
+
const incrRes = await fetch(`${this.restUrl}/INCR/${encodeURIComponent(redisKey)}`, {
|
|
108
|
+
method: "POST",
|
|
109
|
+
headers: { Authorization: `Bearer ${this.restToken}` },
|
|
110
|
+
signal: AbortSignal.timeout(5000),
|
|
111
|
+
});
|
|
112
|
+
if (incrRes.ok) {
|
|
113
|
+
const data = await incrRes.json();
|
|
114
|
+
const count = Number(data.result || 1);
|
|
115
|
+
if (count === 1) {
|
|
116
|
+
await fetch(`${this.restUrl}/EXPIRE/${encodeURIComponent(redisKey)}/${ttlSeconds}`, {
|
|
117
|
+
method: "POST",
|
|
118
|
+
headers: { Authorization: `Bearer ${this.restToken}` },
|
|
119
|
+
signal: AbortSignal.timeout(5000),
|
|
120
|
+
}).catch(() => { });
|
|
121
|
+
}
|
|
122
|
+
return count;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
catch (err) {
|
|
126
|
+
console.error(`[rate-limiter] Redis REST falhou, usando fallback em memória: ${err instanceof Error ? err.message : err}`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
try {
|
|
130
|
+
const client = await this.getClient();
|
|
131
|
+
if (client && typeof client.incr === "function") {
|
|
132
|
+
const count = await client.incr(redisKey);
|
|
133
|
+
if (count === 1 && typeof client.expire === "function") {
|
|
134
|
+
await client.expire(redisKey, ttlSeconds);
|
|
135
|
+
}
|
|
136
|
+
return Number(count);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
catch (err) {
|
|
140
|
+
console.error(`[rate-limiter] Redis TCP falhou, usando fallback em memória: ${err instanceof Error ? err.message : err}`);
|
|
141
|
+
}
|
|
142
|
+
return this.fallbackMemory.increment(key, day);
|
|
143
|
+
}
|
|
144
|
+
async get(key, day) {
|
|
145
|
+
const redisKey = `ratelimit:${day}:${key}`;
|
|
146
|
+
if (this.isRest && this.restUrl && this.restToken) {
|
|
147
|
+
try {
|
|
148
|
+
const res = await fetch(`${this.restUrl}/GET/${encodeURIComponent(redisKey)}`, {
|
|
149
|
+
headers: { Authorization: `Bearer ${this.restToken}` },
|
|
150
|
+
signal: AbortSignal.timeout(5000),
|
|
151
|
+
});
|
|
152
|
+
if (res.ok) {
|
|
153
|
+
const data = await res.json();
|
|
154
|
+
return Number(data.result || 0);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
// Fallback
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
try {
|
|
162
|
+
const client = await this.getClient();
|
|
163
|
+
if (client && typeof client.get === "function") {
|
|
164
|
+
const val = await client.get(redisKey);
|
|
165
|
+
return Number(val || 0);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
// ignore
|
|
170
|
+
}
|
|
171
|
+
return this.fallbackMemory.get(key, day);
|
|
172
|
+
}
|
|
173
|
+
async reset() {
|
|
174
|
+
this.fallbackMemory.reset();
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Detecta o backend de armazenamento mais apropriado baseado nas variáveis de ambiente.
|
|
179
|
+
*/
|
|
180
|
+
export function detectRateLimiterStoreFromEnv() {
|
|
181
|
+
const hasRest = Boolean((process.env.UPSTASH_REDIS_REST_URL || process.env.REDIS_REST_URL) &&
|
|
182
|
+
(process.env.UPSTASH_REDIS_REST_TOKEN || process.env.REDIS_REST_TOKEN));
|
|
183
|
+
const hasTcp = Boolean(process.env.REDIS_URL ||
|
|
184
|
+
process.env.MCPIZE_REDIS_URL ||
|
|
185
|
+
process.env.REDIS_HOST);
|
|
186
|
+
if (hasRest || hasTcp) {
|
|
187
|
+
return new RedisRateLimiterStore();
|
|
188
|
+
}
|
|
189
|
+
return new MemoryRateLimiterStore();
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Rate limiter por dia para os pacotes free ou deploy gerenciado.
|
|
193
|
+
* Armazena contagem em memória ou no Redis (detectado por variáveis de ambiente ou configurado via store).
|
|
194
|
+
*/
|
|
195
|
+
export class DailyRateLimiter {
|
|
196
|
+
store;
|
|
197
|
+
maxPerDay;
|
|
198
|
+
constructor(maxPerDayOrOpts) {
|
|
199
|
+
if (typeof maxPerDayOrOpts === "number") {
|
|
200
|
+
this.maxPerDay = maxPerDayOrOpts;
|
|
201
|
+
this.store = detectRateLimiterStoreFromEnv();
|
|
202
|
+
}
|
|
203
|
+
else if (typeof maxPerDayOrOpts === "object" && maxPerDayOrOpts !== null) {
|
|
204
|
+
this.maxPerDay = maxPerDayOrOpts.maxPerDay ?? 50;
|
|
205
|
+
this.store = maxPerDayOrOpts.store ?? detectRateLimiterStoreFromEnv();
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
this.maxPerDay = 50;
|
|
209
|
+
this.store = detectRateLimiterStoreFromEnv();
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
today() {
|
|
213
|
+
return new Date().toISOString().split("T")[0];
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Tenta consumir um request. Retorna true se permitido, false se limitado.
|
|
217
|
+
* Se usando store em memória, retorna boolean síncrono. Se no Redis, retorna Promise<boolean>.
|
|
218
|
+
*/
|
|
219
|
+
tryConsume(key = "global") {
|
|
220
|
+
const day = this.today();
|
|
221
|
+
if (this.store.type === "memory") {
|
|
222
|
+
const current = this.store.increment(key, day, 86400);
|
|
223
|
+
return current <= this.maxPerDay;
|
|
224
|
+
}
|
|
225
|
+
return Promise.resolve(this.store.increment(key, day, 86400)).then((current) => current <= this.maxPerDay);
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Retorna quantos requests restam hoje.
|
|
229
|
+
*/
|
|
230
|
+
remaining(key = "global") {
|
|
231
|
+
const day = this.today();
|
|
232
|
+
if (this.store.type === "memory") {
|
|
233
|
+
const current = this.store.get(key, day);
|
|
234
|
+
return Math.max(0, this.maxPerDay - current);
|
|
235
|
+
}
|
|
236
|
+
return Promise.resolve(this.store.get(key, day)).then((current) => Math.max(0, this.maxPerDay - current));
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Retorna o limite diário configurado.
|
|
240
|
+
*/
|
|
241
|
+
get limit() {
|
|
242
|
+
return this.maxPerDay;
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Reseta a contagem (útil para testes).
|
|
246
|
+
*/
|
|
247
|
+
reset() {
|
|
248
|
+
return this.store.reset();
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Mensagem padrão de upgrade quando o rate limit é atingido.
|
|
253
|
+
*/
|
|
254
|
+
export function rateLimitMessage(toolName, limit) {
|
|
255
|
+
return [
|
|
256
|
+
`⚠️ Limite diário atingido (${limit} consultas/dia no plano gratuito).`,
|
|
257
|
+
``,
|
|
258
|
+
`Para uso ilimitado de ${toolName} + Pix, NFe e WhatsApp:`,
|
|
259
|
+
`→ npm install @brazil-mcp/bundle-pro`,
|
|
260
|
+
`→ Ou acesse: https://mcpize.com/brazil-mcp`,
|
|
261
|
+
].join("\n");
|
|
262
|
+
}
|
|
263
|
+
//# sourceMappingURL=rate-limiter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limiter.js","sourceRoot":"","sources":["../../src/utils/rate-limiter.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@brazil-mcp/cep",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "MCP server para consulta de CEP e cálculo de frete no Brasil",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": { "cep-mcp": "./dist/index.js" },
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"files": ["dist"],
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsc",
|
|
11
|
+
"dev": "tsx src/index.ts",
|
|
12
|
+
"test": "vitest run tests/unit",
|
|
13
|
+
"test:flow": "vitest run tests/flow",
|
|
14
|
+
"test:all": "vitest run tests/unit tests/flow",
|
|
15
|
+
"test:integration": "vitest run tests/integration",
|
|
16
|
+
"test:coverage": "vitest run tests/unit tests/flow --coverage"
|
|
17
|
+
},
|
|
18
|
+
"keywords": ["mcp","mcp-server","brazil","cep","correios","frete","viacep"],
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
22
|
+
"zod": "^3.25.0"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@types/node": "^22.0.0",
|
|
26
|
+
"@vitest/coverage-v8": "^3.2.0",
|
|
27
|
+
"tsx": "^4.19.0",
|
|
28
|
+
"typescript": "^5.7.0",
|
|
29
|
+
"vitest": "^3.2.0"
|
|
30
|
+
}
|
|
31
|
+
}
|