search_contratos
Search public procurement contracts on PNCP to analyze market history, supplier behavior, and agency spending. Default last 30 days, max 365 days per query.
Instructions
Search public procurement contracts (contratos) on PNCP. Useful for analyzing market history, supplier behavior, and agency spending patterns. Defaults to last 30 days when no date range is provided. Maximum date range per query: 365 days (PNCP limit); wider windows return HTTP 422. For multi-year searches, issue multiple calls.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| dataInicial | No | Start date YYYYMMDD | |
| dataFinal | No | End date YYYYMMDD | |
| cnpjOrgao | No | Filter by procuring agency CNPJ | |
| cnpjFornecedor | No | Filter by supplier CNPJ | |
| esfera | No | Filter by government sphere: 'federal', 'estadual', 'municipal', or 'distrital'. Applied client-side. | |
| palavraChave | No | Keyword filter on objetoContrato (client-side). | |
| valorMinimo | No | ||
| valorMaximo | No | ||
| pagina | No | ||
| tamanhoPagina | No |
Implementation Reference
- src/tools/search_contratos.ts:76-151 (handler)The searchContratos ToolDef object: contains the 'search_contratos' tool definition (name, description, inputSchema) and the async handler function that parses arguments, calls listContratos from the PNCP adapter, applies client-side filters (esfera, palavraChave, valorMinimo/valorMaximo), and returns summarized results.
export const searchContratos: ToolDef = { definition: { name: 'search_contratos', description: `Search public procurement contracts (contratos) on PNCP. Useful for analyzing market history, supplier behavior, and agency spending patterns. Defaults to last 30 days when no date range is provided. Maximum date range per query: ${PNCP_MAX_DATE_RANGE_DAYS} days (PNCP limit); wider windows return HTTP 422. For multi-year searches, issue multiple calls.`, inputSchema: { type: 'object', properties: { dataInicial: { type: 'string', description: 'Start date YYYYMMDD' }, dataFinal: { type: 'string', description: 'End date YYYYMMDD' }, cnpjOrgao: { type: 'string', description: 'Filter by procuring agency CNPJ' }, cnpjFornecedor: { type: 'string', description: 'Filter by supplier CNPJ' }, esfera: { type: 'string', enum: [...ESFERA_VALUES], description: "Filter by government sphere: 'federal', 'estadual', 'municipal', or 'distrital'. Applied client-side.", }, palavraChave: { type: 'string', description: 'Keyword filter on objetoContrato (client-side).', }, valorMinimo: { type: 'number' }, valorMaximo: { type: 'number' }, pagina: { type: 'integer', minimum: 1, default: 1 }, tamanhoPagina: { type: 'integer', minimum: 10, maximum: 50, default: 20 }, }, }, }, async handler(rawArgs) { const parse = ArgsSchema.safeParse(rawArgs ?? {}); if (!parse.success) return errorResult(`Invalid arguments: ${parse.error.message}`); const args = parse.data; const range = !args.dataInicial || !args.dataFinal ? defaultDateRange(30) : { dataInicial: args.dataInicial, dataFinal: args.dataFinal }; const validation = validatePncpDateRange(range.dataInicial, range.dataFinal); if (!validation.ok) { return errorResult(validation.reason); } try { const page = await listContratos({ dataInicial: range.dataInicial, dataFinal: range.dataFinal, cnpjOrgao: args.cnpjOrgao, cnpjFornecedor: args.cnpjFornecedor, pagina: args.pagina, tamanhoPagina: args.tamanhoPagina, }); const filtered = page.data.filter((c) => { if (!matchesEsfera(c, args.esfera)) return false; if (args.palavraChave && !matchesKeyword(c, args.palavraChave)) return false; if (!matchesValor(c, args)) return false; return true; }); return jsonResult({ meta: { dataInicial: range.dataInicial, dataFinal: range.dataFinal, pagina: args.pagina, totalRetornados: filtered.length, totalAntesFiltros: page.data.length, totalPncp: page.totalRegistros, }, results: filtered.map(summarize), }); } catch (err) { const msg = err instanceof PncpError ? err.message : String(err); return errorResult(`Failed to search contratos: ${msg}`); } }, }; - src/tools/search_contratos.ts:14-31 (schema)Zod schema (ArgsSchema) defining the input parameters for search_contratos: dataInicial, dataFinal, cnpjOrgao, cnpjFornecedor, esfera, palavraChave, valorMinimo, valorMaximo, pagina, tamanhoPagina.
const ArgsSchema = z.object({ dataInicial: z.string().refine(isValidPncpDate, 'Format must be YYYYMMDD').optional(), dataFinal: z.string().refine(isValidPncpDate, 'Format must be YYYYMMDD').optional(), cnpjOrgao: z .string() .regex(/^\d{14}$/, 'CNPJ must be 14 digits') .optional(), cnpjFornecedor: z .string() .regex(/^\d{14}$/, 'CNPJ must be 14 digits') .optional(), esfera: EsferaSchema.optional(), palavraChave: z.string().min(2).optional(), valorMinimo: z.number().nonnegative().optional(), valorMaximo: z.number().nonnegative().optional(), pagina: z.number().int().min(1).default(1), tamanhoPagina: z.number().int().min(10).max(50).default(20), }); - src/tools/search_contratos.ts:35-48 (helper)matchesKeyword helper: client-side keyword filter on contrato fields (objetoContrato, informacaoComplementar, orgao, fornecedor).
function matchesKeyword(c: Contrato, kw: string): boolean { const lower = kw.toLowerCase(); const haystack = [ c.objetoContrato, c.informacaoComplementar, c.orgaoEntidade?.razaoSocial, c.fornecedor?.razaoSocial, c.fornecedor?.nome, ] .filter(Boolean) .join(' ') .toLowerCase(); return haystack.includes(lower); } - src/tools/search_contratos.ts:50-56 (helper)matchesValor helper: client-side value range filter using valorGlobal/valorInicial.
function matchesValor(c: Contrato, args: Args): boolean { const v = c.valorGlobal ?? c.valorInicial; if (v == null) return args.valorMinimo == null && args.valorMaximo == null; if (args.valorMinimo != null && v < args.valorMinimo) return false; if (args.valorMaximo != null && v > args.valorMaximo) return false; return true; } - src/tools/search_contratos.ts:58-74 (helper)summarize helper: extracts a clean summary from a Contrato object for the response output.
function summarize(c: Contrato) { return { numeroControlePNCP: c.numeroControlePNCP, objeto: c.objetoContrato, valorInicial: c.valorInicial, valorGlobal: c.valorGlobal, fornecedor: c.fornecedor?.razaoSocial ?? c.fornecedor?.nome, cnpjFornecedor: c.fornecedor?.cnpj ?? c.fornecedor?.ni, orgao: c.orgaoEntidade?.razaoSocial, cnpjOrgao: c.orgaoEntidade?.cnpj, uf: c.unidadeOrgao?.ufSigla, municipio: c.unidadeOrgao?.municipioNome, dataAssinatura: c.dataAssinatura, vigenciaInicio: c.dataVigenciaInicio, vigenciaFim: c.dataVigenciaFim, }; } - src/tools/index.ts:21-49 (registration)Registration: searchContratos is imported from './search_contratos.js' and added to the allTools array (line 29), which is then mapped into a toolMap by name (line 49).
export const allTools: ToolDef[] = [ // Compras / Licitações searchLicitacoes, getLicitacao, listLicitacaoItens, listLicitacaoResultados, listLicitacaoArquivos, // Contratos searchContratos, getContratoTool, listContratoTermosTool, listContratoInstrumentosTool, // Atas RP searchAtasRp, getAtaRp, // Órgãos / Fornecedores getOrgaoTool, getFornecedorContratos, // PCA searchPca, listPcaItensTool, // CNPJ enrichment getCnpjDataTool, // Análise agregada (v0.2.0) aggregateLicitacoes, comparePeriodos, ]; export const toolMap = new Map<string, ToolDef>(allTools.map((t) => [t.definition.name, t])); - src/adapters/pncp.ts:291-315 (helper)listContratos adapter function: calls the PNCP API GET /contratos endpoint with params (dataInicial, dataFinal, cnpjOrgao, cnpjFornecedor, pagina, tamanhoPagina), parses via ContratosPageSchema, and caches with TTL_5_MIN.
export async function listContratos(params: ListContratosParams): Promise<ContratosPage> { const tamanhoPagina = clampPageSize(params.tamanhoPagina); const pagina = Math.max(params.pagina ?? 1, 1); const query: Record<string, unknown> = { pagina, tamanhoPagina }; if (params.dataInicial) query.dataInicial = params.dataInicial; if (params.dataFinal) query.dataFinal = params.dataFinal; if (params.cnpjOrgao) query.cnpjOrgao = params.cnpjOrgao; if (params.cnpjFornecedor) query.cnpjFornecedor = params.cnpjFornecedor; const cacheKey = `list:contratos:${JSON.stringify(query)}`; const cached = cache.get<ContratosPage>(cacheKey); if (cached) return cached; try { const { data } = await withRetry(() => consultaClient.get('/contratos', { params: query })); const parsed = ContratosPageSchema.parse(data); cache.set(cacheKey, parsed, TTL_5_MIN); return parsed; } catch (err) { if (err instanceof AxiosError) { throw new PncpError(describeAxiosError(err), err); } throw err; } } - src/schemas/pncp.ts:160-204 (schema)ContratoSchema (Zod) and ContratosPageSchema defining the structure of a contrato and the paginated response from PNCP.
export const ContratoSchema = z .object({ numeroControlePNCP: z.string(), numeroControlePNCPCompra: z.string().nullable().optional(), anoContrato: z.number(), sequencialContrato: z.number(), numeroContratoEmpenho: z.string().nullable().optional(), orgaoEntidade: orgaoEntidadeSchema.nullable().optional(), unidadeOrgao: unidadeOrgaoSchema.nullable().optional(), fornecedor: fornecedorSchema.nullable().optional(), objetoContrato: z.string().nullable().optional(), informacaoComplementar: z.string().nullable().optional(), valorInicial: z.number().nullable().optional(), valorGlobal: z.number().nullable().optional(), valorParcela: z.number().nullable().optional(), valorAcumulado: z.number().nullable().optional(), numeroParcelas: z.number().nullable().optional(), numeroRetificacao: z.number().nullable().optional(), dataAssinatura: z.string().nullable().optional(), dataVigenciaInicio: z.string().nullable().optional(), dataVigenciaFim: z.string().nullable().optional(), dataPublicacaoPncp: z.string().nullable().optional(), tipoContratoId: z.number().nullable().optional(), tipoContratoNome: z.string().nullable().optional(), categoriaProcessoId: z.number().nullable().optional(), categoriaProcessoNome: z.string().nullable().optional(), receitaDespesaCodigo: z.string().nullable().optional(), receitaDespesaNome: z.string().nullable().optional(), }) .passthrough(); export type Contrato = z.infer<typeof ContratoSchema>; export const ContratosPageSchema = z .object({ data: z.array(ContratoSchema).default([]), totalRegistros: z.number().optional(), totalPaginas: z.number().optional(), numeroPagina: z.number().optional(), paginasRestantes: z.number().optional(), empty: z.boolean().optional(), }) .passthrough(); export type ContratosPage = z.infer<typeof ContratosPageSchema>;