MCP基础设施成本
A. 模型上下文协议(MCP) 基于FastAPI构建的服务器,从 基础设施GraphQL API它允许AI助手和MCP兼容客户端直接通过对话查询AWS、GCP和Azure的按需计算价格。
该存储库还提供了一个配套的CLI脚本,用于从任何GitHub组织批量下载和清理Terraform模块README,这可用于用真实的IaC示例丰富LLM的上下文。
______________________________________________________________________
动机
该项目围绕两个具体目标进行设计:
1.最大限度地提高有用LLM请求的比率。 在没有结构化工具的情况下,LLM与基础设施API的交互往往会产生高比例的格式错误或冗余请求,即失败、返回空结果或消耗API配额而不提供价值的调用。通过公开类型良好、专门构建的MCP工具,而不是让模型自由调用API,服务器预先强制执行正确的参数,并仅返回可操作的数据。目标是使错误请求尽可能接近零,从而减少模型推理链中的浪费成本和噪声。
2.使较小的本地托管模型能够执行专门的基础设施任务。
\[!警告\] 这个项目是一个学术练习,不打算用于生产。它可能包含安全漏洞,应谨慎使用。
______________________________________________________________________
目录
______________________________________________________________________
运作原理
MCP Client (e.g. Claude Desktop, Cursor)
│
│ MCP over HTTP (SSE / Streamable HTTP)
▼
┌───────────────────────────────┐
│ FastAPI application │
│ • GET /infracost/prices │ ◄── query parameters
│ • GET /hello │
│ │
│ FastApiMCP layer │ ◄── auto-generates MCP tools
│ • mounted at /mcp │ from each FastAPI route
└───────────────────────────────┘
│
│ HTTPS / GraphQL
▼
Infracost Pricing API
(pricing.api.infracost.io/graphql)fastapi mcp 内省FastAPI应用程序上注册的每条路由,并自动将每条路由作为MCP工具公开。连接到的任何MCP兼容客户端 http://localhost:8000/mcp 因此可以调用 /infracost/prices 作为本机工具调用,无需任何额外的粘合代码。
______________________________________________________________________
先决条件
| 要求 | 版本 | 注释 |
|---|---|---|
| Python | ≥3.12 | |
| 紫外线 | 最新 | Python包管理器 |
| Node.js | ≥18(可选) | 仅MCP检查器需要 |
| Infrascost API密钥 | - | 免费层位于 基础设施.io |
| GitHub个人访问令牌 | -- | 可选--仅README fetcher脚本需要 |
______________________________________________________________________
安装
1.克隆存储库
git clone https://github.com/ripitchip/MCP-Infracost.git
cd MCP-Infracost2.安装依赖项
该项目使用 uv 作为其包管理器。所有运行时依赖关系都在中声明 pyproject.toml.
uv pip install -e .Core dependencies
| 包装 | 版本 | 用途 |
|---|---|---|
fastapi[standard] | ≥0.129.0 | HTTP框架 |
fastapi-mcp | ≥0.4.0 | FastAPI路由上的MCP层 |
mcp-proxy | ≥0.11.0 | MCP代理实用程序 |
uvicorn | ≥0.41.0 | ASGI服务器 |
python-dotenv | ≥ 1.0.1 | .env 文件加载 |
3.配置环境变量
复制示例文件并至少填写 INFRACOST_API_KEY:
cp .env.example .env看 环境变量 每个变量的完整描述。
______________________________________________________________________
环境变量
| 变量 | 必填 | 默认 | 描述 |
|---|---|---|---|
INFRACOST_API_KEY | 是 | - | API密钥,用于验证对Infrascost GraphQL定价API的请求。免费获得一个 基础设施.io. |
INFRACOST_API_URL | 没有 | https://pricing.api.infracost.io/graphql | Infrascost API的GraphQL端点。仅当您运行自托管实例时才覆盖。 |
GITHUB_TOKEN | 无 | - | README fetcher脚本用于发出经过身份验证的GitHub API请求的个人访问令牌(5 000 req/h与60 req/h未经身份验证)。只有 public_repo 需要读取范围。 |
______________________________________________________________________
启动服务器
uv run src/main.py服务器正在监听 0.0.0.0:8000 默认情况下。以下端点立即可用:
| 路径 | 描述 |
|---|---|
GET /hello | 问候/吸烟测试路线 |
GET /infracost/prices | 云定价查询 |
POST /tflint/validate | Terraform植绒和验证 |
POST /tflint/check-syntax | 地形格式检查 |
GET /tflint/status | 工具可用性状态 |
GET /mcp | MCP端点(SSE或流式HTTP) |
GET /docs | 自动生成的Swagger用户界面 |
GET /redoc | 自动生成的ReDoc用户界面 |
交互式检查MCP工具
这 MCP检查员 提供基于浏览器的UI,用于浏览和调用服务器公开的工具:
npx @modelcontextprotocol/inspector http://localhost:8000/mcp然后打开终端中打印的URL(通常 http://localhost:6274).
______________________________________________________________________
API 参考
GET /hello
一个简单的问候端点,可用作烟雾测试,以确认服务器是否可访问。
查询参数
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
name | string | "World" | 问候语中应包含的姓名。 |
示例
GET /hello?name=Alice{ "message": "Hello Alice!" }______________________________________________________________________
GET /infracost/prices
查询Infracost GraphQL API以获取按需计算定价,并返回多达五个匹配的产品条目。
查询参数
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
provider | string | "aws" | 云提供商。接受值: aws, gcp, azure 不区分大小写 |
location | string | "france" | 逻辑区域别名 或 原始提供商地区代码。内置别名: france, europe, us。请参阅下表,了解每个别名解析到每个提供程序的区域。 |
cores | integer | 2 | vCPU的最小数量。用作AWS和Azure的筛选器属性。在以下情况下忽略GCP instance_type 提供。 |
instance_type | string | _(提供程序默认值)_ | 精确的实例/机器类型标识符。省略时,将使用特定于提供程序的默认值(m5.large 对于AWS, n2-standard-2 对于GCP, Standard_D2s_v5 Azure)。 |
os | string | "Linux" | 操作系统筛选器。接受值: Linux, Windows适用于AWS和Azure;忽略GCP。 |
区域别名解析
| 别名 | AWS | GCP | Azure |
|---|---|---|---|
france | eu-west-3 | europe-west9 | francecentral |
europe | eu-central-1 | europe-west1 | westeurope |
us | us-east-1 | us-central1 | eastus |
任何不是可识别别名的值都会转发到API as-is,使您能够传递原始区域代码,例如 ap-southeast-1 直接。
响应架构
{
"provider": "aws",
"results_count": 3,
"results": [
{
"attributes": [
{ "key": "instanceType", "value": "m5.large" },
{ "key": "vcpu", "value": "2" },
{ "key": "memory", "value": "8 GiB" }
],
"prices": [{ "USD": "0.096", "unit": "Hrs" }]
}
]
}| 字段 | 描述 |
|---|---|
provider | 被查询的提供者从请求中返回。 |
results_count | 截断前Infracost API返回的匹配产品的总数。 |
results | 高达 5 匹配的产品条目,每个条目包含 attributes (描述资源的键/值对)和 prices (按需每单位美元价格)。 |
错误响应
| 条件 | 响应 |
|---|---|
| 不支持的提供程序 | { "error": "Provider non supporté" } |
失踪 INFRACOST_API_KEY | { "error": "INFRACOST_API_KEY is not set" } |
| 网络或API错误 | { "error": "" } |
例子
# AWS m5.large in Paris
GET /infracost/prices?provider=aws&location=france&instance_type=m5.large
# Azure 4-core VM in West Europe running Windows
GET /infracost/prices?provider=azure&location=europe&cores=4&os=Windows
# GCP n2-standard-4 in the US
GET /infracost/prices?provider=gcp&location=us&instance_type=n2-standard-4______________________________________________________________________
地形林(/tflint)
POST /tflint/validate
使用以下命令验证和删除Terraform文件 tflint 或 terraform validate.
请求: { "content": "", "filename": "main.tf" }\ 答复: { "valid": bool, "message": string, "errors": [], "warnings": [] }
POST /tflint/check-syntax
使用检查代码格式 terraform fmt.
请求: { "content": "", "filename": "main.tf" }\ 答复: { "formatted": bool, "message": string, "output": string }
GET /tflint/status
检查系统上可用的棉绒工具。
答复: { "available_tools": { "terraform": bool, "tflint": bool }, "primary_tool": string }
______________________________________________________________________
MCP集成
MCP端点安装在 /mcp每个FastAPI路由都会自动反映为MCP工具 fastapi-mcp,表示中描述的参数名称、类型和描述 API 参考 从MCP客户端调用该工具时同样适用。
连接克劳德桌面
将以下块添加到您的 claude_desktop_config.json (通常位于 ~/Library/Application Support/Claude/claude_desktop_config.json 在macOS上):
{
"mcpServers": {
"infracost": {
"command": "npx",
"args": ["mcp-remote", "http://localhost:8000/mcp"]
}
}
}重新启动克劳德桌面。这 get_infrastructure_prices 和 say_hello 工具将出现在工具选择器中。
连接Cursor或其他HTTP原生MCP客户端
直接将客户指向:
http://localhost:8000/mcp______________________________________________________________________
Terraform README获取器
scripts/fetch_terraform_readmes.py 抓取属于GitHub组织的每个公共存储库,下载每个 README.md,去除所有噪声(徽章、横幅、页脚部分),并将原始和清理后的版本写入磁盘 downloads/extractN/.
生成的已清理文档语料库旨在供LLM作为基础上下文使用——请参见 在MCP服务器上使用语料库 在......下面
用法
uv run scripts/fetch_terraform_readmes.py [OPTIONS]选项
| 选项 | 类型 | 默认值 | 描述 |
|---|---|---|---|
--org | string | terraform-aws-modules | GitHub组织爬行。属于该组织的所有公共存储库都将被处理。 |
--root | string | 存储库根 | 工作区根的绝对或相对路径。这 downloads/ 目录是在此路径下创建的。默认为脚本的父目录。 |
--include-archived | 旗帜 | false | 设置后,将包括存档的存储库。默认情况下,它们会被跳过。 |
GITHUB_TOKEN 从环境中读取(或从 .env).如果没有它,请求将不经过身份验证,并受到GitHub每小时60个请求的速率限制,对于大型组织来说,这将很快耗尽。使用令牌后,限制将提高到每小时5000个请求。只需要默认的读取范围(无特殊权限)。
如何获取README文件
该脚本使用没有外部依赖关系的GitHub REST API-只有Python的标准库 urllib.
for each repository in the organisation
│
├─ GET /orgs/{org}/repos?per_page=100&page=N&type=public
│ Paginated until an empty page is returned.
│ Archived repos are filtered out unless --include-archived is set.
│
└─ GET /repos/{org}/{repo}/readme
GitHub returns the file metadata including the content
encoded as a base64 string.
The script base64-decodes it to recover the raw Markdown text.速率限制标题(X-RateLimit-Remaining, X-RateLimit-Reset)根据每一项要求进行检查。如果限制已用尽,脚本将引发一个描述性错误,其中包括窗口重置前的秒数,允许您在等待后继续。
如何清理README
原始的Terraform模块README通常充斥着CI徽章、营销横幅和管理页脚部分,这些部分作为LLM上下文没有任何价值。这 clean_readme() 函数应用一系列确定性转换,将每个文档减少到信息最密集的部分:
Raw Markdown text
│
▼
1. Locate the first H1 title line (# …)
→ Keep the title.
→ Keep the first non-empty, non-heading paragraph line below it
as the module description. Skip badge/banner lines.
│
▼
2. Locate content start
→ Preferred: the line immediately after a ## Usage heading.
→ Fallback 1: the first H3 heading (### …).
→ Fallback 2: the line after the H1 title.
│
▼
3. Locate content end
→ Scan forward from the content-start position.
→ Stop at the first H2/H3 heading whose normalised title matches
any entry in the footer set:
authors · author · license · maintainers · maintainer ·
contributing · contribution · support · changelog ·
additional information · security
│
▼
4. Filter body lines
→ Drop any line that is a badge or banner:
• starts with [![ or ![
• contains shields.io
• contains "badge" and an http(s) URL
→ Drop the ## Usage heading line itself (its content is kept).
│
▼
5. Reassemble
→ Concatenate: [title + description] + [filtered body]
→ Collapse runs of consecutive blank lines to a single blank line.
→ Strip leading and trailing blank lines.
│
▼
Cleaned Markdown text前/后示例 (为简洁起见截断):
# terraform-aws-vpc
[](https://…) ← removed
[](https://…) ← removed
A Terraform module for creating AWS VPCs.
## Usage ← heading removed, content kept
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
name = "my-vpc"
cidr = "10.0.0.0/16"
}
## Authors ← stop here — footer removed
…成为:
# terraform-aws-vpc
A Terraform module for creating AWS VPCs.
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
name = "my-vpc"
cidr = "10.0.0.0/16"
}输出结构
每次运行都会在下创建一个新的编号目录 downloads/ 以便连续运行永远不会覆盖之前的输出:
downloads/
└── extractN/
├── README.md # Human-readable index of processed / skipped repos
├── summary.json # Machine-readable run metadata (timestamps, counts, paths)
└── /
├── README.original.md # Raw README exactly as returned by GitHub
└── README.cleaned.md # Cleaned, example-focused extractsummary.json 架构:
{
"organization": "terraform-aws-modules",
"output": "downloads/extract1",
"repository_count": 42,
"generated_at": 1708444800,
"processed": [
{
"repo": "terraform-aws-vpc",
"readme_path": "README.md",
"original_file": "downloads/extract1/terraform-aws-vpc/README.original.md",
"cleaned_file": "downloads/extract1/terraform-aws-vpc/README.cleaned.md"
}
],
"skipped": [{ "repo": "terraform-aws-legacy", "reason": "archived" }]
}例子
# Fetch all public repos from terraform-aws-modules (default)
uv run scripts/fetch_terraform_readmes.py
# Fetch from a different organisation, including archived repos
uv run scripts/fetch_terraform_readmes.py --org hashicorp --include-archived
# Override the output root directory
uv run scripts/fetch_terraform_readmes.py --root /tmp/my-workspace______________________________________________________________________
在MCP服务器上使用语料库
清理后的README是简单的Markdown文件——小、专注、无噪音。在回答有关Terraform模块的问题时,它们非常适合用作LLM的基础上下文。下面是将语料库与此MCP服务器集成的三种越来越复杂的模式。
模式1——静态系统提示注入
最简单的方法:阅读一个或多个 README.cleaned.md 在服务器启动时创建文件,并将其内容注入到每次LLM调用时附带的系统提示中。
┌── MCP server ─────────────────────────────────────────────┐
│ On start-up: load downloads/extract1/*/README.cleaned.md │
│ Build a combined system prompt: │
│ "You are an IaC cost assistant. Here is the │
│ documentation for the available Terraform modules: │
│ " │
└────────────────────────────────────────────────────────────┘权衡: 实施起来微不足道;仅适用于小语料库(几个模块),因为整个语料库必须适合模型的上下文窗口。
模式2——检索增强生成(RAG)
对于较大的语料库(数十到数百个模块),嵌入每个模块 README.cleaned.md 在索引时将其放入向量存储中。在查询时,检索语义上最相关的文档,并仅将这些文档注入上下文。
Index time (run once after each fetch)
cleaned READMEs → text chunker → embedding model → vector store
Query time (per MCP tool call)
user query → embedding model → vector store similarity search
→ top-K relevant README chunks
→ injected as context into the LLM prompt
→ LLM generates answer grounded in module documentation新的MCP路由可能会暴露这种能力:
GET /infracost/context?query=create+a+VPC+with+public+subnets
→ returns: { "chunks": [ { "repo": "terraform-aws-vpc", "content": "…" } ] }MCP客户端(例如Claude)可以调用 get_infracost_context 在调用之前获取相关文档 get_infracost_prices 估算成本——将这两个工具链接到一个推理步骤中。
模式3——工具增强上下文(当前架构扩展)
无需矢量存储。添加一个专用的MCP工具,用于读取 downloads/ 在请求时访问目录,并返回已命名模块的已清理README。LLM决定何时调用它。
@router.get("/infracost/module-doc")
async def get_module_doc(module: str = Query(...)) -> dict:
"""Return the cleaned README for a Terraform module by name."""
path = Path("downloads") / "extract1" / module / "README.cleaned.md"
if not path.exists():
return {"error": f"No documentation found for module '{module}'"}
return {"module": module, "documentation": path.read_text()}fastapi-mcp 将自动将其公开为 get_module_doc 工具。典型的多步骤交互如下:
User: "How much does the terraform-aws-vpc module cost to run in Paris?"
LLM: [calls get_module_doc(module="terraform-aws-vpc")]
→ reads the cleaned README to understand required inputs
[calls get_infracost_prices(provider="aws", location="france",
instance_type="…")]
→ retrieves live on-demand pricing
[synthesises both results into a final answer]这种模式使架构保持无状态和简单,同时允许LLM将其答案建立在真实的模块文档中。
______________________________________________________________________
许可证
该项目根据MIT许可证获得许可。看 许可证 文件以获取详细信息。
