sercop-mcp
Version:
Model Context Protocol (MCP) server for proxying the SERCOP Open Data API of Ecuador. Provides MCP tools for public procurement data.
88 lines (87 loc) • 3.79 kB
JavaScript
// MCP server for SERCOP Open Data API
// Author: Andrés Villenas, Automayzer.ai
//
// This file implements a Model Context Protocol (MCP) server that proxies the SERCOP Open Data API of Ecuador.
// It exposes MCP tools for listing datasets, searching contracting processes, and retrieving process details by OCID.
//
// Tools:
// - list-datasets: Lists all available datasets from the SERCOP API.
// - search-processes: Searches contracting processes by keyword, year, and optional filters (buyer, supplier, page).
// - get-process-by-ocid: Retrieves a contracting process by its OCID identifier.
//
// Usage:
// - Build: npx tsc
// - Run: node build/index.js (or use the CLI if installed globally)
//
// References:
// - Model Context Protocol: https://modelcontextprotocol.io/quickstart/server
// - SERCOP Open Data API: https://datosabiertos.compraspublicas.gob.ec/PLATAFORMA/datos-abiertos/api
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
const API_BASE = "https://datosabiertos.compraspublicas.gob.ec/PLATAFORMA/api";
const server = new McpServer({
name: "sercop-mcp",
version: "1.0.0",
capabilities: {
resources: {},
tools: {},
},
});
async function fetchSercop(endpoint) {
try {
const response = await fetch(`${API_BASE}${endpoint}`);
if (!response.ok)
throw new Error(`HTTP error: ${response.status}`);
return (await response.json());
}
catch (err) {
console.error("SERCOP API error:", err);
return null;
}
}
// Tool: Search contracting processes by keyword
server.tool("search-processes", "Search contracting processes by keyword, year, and optional filters (buyer, supplier, page)", {
year: z.number().int().min(2015).describe("Year of the contracting process (e.g., 2015 to current year)"),
search: z.string().min(3).describe("Keyword to search (at least 3 characters)"),
page: z.number().int().min(1).optional().describe("Page number (optional, >0)"),
buyer: z.string().min(3).optional().describe("Buyer institution keyword (optional, at least 3 characters)"),
supplier: z.string().min(3).optional().describe("Supplier keyword (optional, at least 3 characters)"),
}, async ({ year, search, page, buyer, supplier }) => {
const params = new URLSearchParams({
year: String(year),
search,
});
if (page)
params.append("page", String(page));
if (buyer)
params.append("buyer", buyer);
if (supplier)
params.append("supplier", supplier);
const data = await fetchSercop(`/search_ocds?${params.toString()}`);
if (!data) {
return { content: [{ type: "text", text: "Failed to fetch contracting processes." }] };
}
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
});
// Tool: Search contracting process by OCID
server.tool("get-process-by-ocid", "Get a contracting process by its OCID identifier", {
ocid: z.string().min(1).describe("The OCID identifier of the contracting process"),
}, async ({ ocid }) => {
const params = new URLSearchParams({ ocid });
const data = await fetchSercop(`/record?${params.toString()}`);
if (!data) {
return { content: [{ type: "text", text: "Failed to fetch process by OCID." }] };
}
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
});
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("SERCOP MCP Server running on stdio");
}
main().catch((err) => {
console.error("Fatal error in main():", err);
process.exit(1);
});