M0 e M1 revisados e fixes implementados

This commit is contained in:
Marcos Lohmann
2026-09-14 08:18:53 -03:00
parent 439f60598e
commit f3efcc3414
72 changed files with 4831 additions and 128 deletions

View File

@@ -1,10 +1,11 @@
"""Example Sales-domain MCP server (EP-06-T02, walking-skeleton subset).
"""Example Sales-domain MCP server (EP-06-T02).
Read-only tools (risk tier R1, see README.md §17): customer.search,
customer.get, order.get, inventory.check. Backed by static mock data -- this
server is deliberately "dumb": it never authorizes anything (ADR-023 in
docs/RI-PLANNING.md). Authorization happens exclusively upstream, in the
Gateway/PEP (EP-05) before a call ever reaches this process.
customer.get, order.get, inventory.check. Write tools (R2): quote.create,
quote.update. Backed by static mock data -- this server is deliberately
"dumb": it never authorizes anything (ADR-023 in docs/RI-PLANNING.md).
Authorization happens exclusively upstream, in the Gateway/PEP (EP-05)
before a call ever reaches this process.
Run directly for local development:
@@ -13,10 +14,14 @@ Run directly for local development:
from __future__ import annotations
import itertools
import os
from mcp.server.mcpserver import MCPServer
from starlette.types import ASGIApp
from emcp_bus.audit.bypass_detection import BackendCredentialGate
from emcp_bus.audit.sink import JSONLFileAuditSink
from emcp_bus.common.otel import setup_tracing
setup_tracing("sales-domain-mcp-server")
@@ -37,6 +42,9 @@ _INVENTORY: dict[str, int] = {
"sku-200": 0,
}
_QUOTES: dict[str, dict[str, object]] = {}
_quote_ids = itertools.count(1)
@server.tool(name="sales.customer.search", description="Search sales customers by name fragment.")
def customer_search(query: str) -> dict[str, object]:
@@ -65,10 +73,50 @@ def inventory_check(sku: str) -> dict[str, object]:
return {"sku": sku, "available": _INVENTORY.get(sku, 0)}
@server.tool(name="sales.quote.create", description="Create a sales quote for a customer.")
def quote_create(customer_id: str, amount: float, region: str = "BR-SP") -> dict[str, object]:
quote_id = f"quote-{next(_quote_ids):03d}"
quote = {
"quote_id": quote_id,
"customer_id": customer_id,
"amount": amount,
"region": region,
"status": "draft",
}
_QUOTES[quote_id] = quote
return quote
@server.tool(name="sales.quote.update", description="Update an existing sales quote.")
def quote_update(quote_id: str, amount: float | None = None) -> dict[str, object]:
quote = _QUOTES.get(quote_id)
if quote is None:
return {"error": "not_found", "quote_id": quote_id}
if amount is not None:
quote["amount"] = amount
return quote
def main() -> None:
host = os.environ.get("SALES_DOMAIN_HOST", "127.0.0.1")
port = int(os.environ.get("SALES_DOMAIN_PORT", "8100"))
server.run(transport="streamable-http", host=host, port=port)
expected_token = os.environ.get("SALES_DOMAIN_SERVICE_TOKEN")
audit_log_path = os.environ.get("EMCP_AUDIT_LOG_PATH", "/tmp/emcp-audit.jsonl")
app: ASGIApp = server.streamable_http_app(host=host)
if expected_token:
# EP-05-T07: reject anything that didn't come through the Gateway's
# outbound identity (EP-07-T01) with the matching service credential.
app = BackendCredentialGate(
app,
expected_token=expected_token,
backend="sales-domain",
audit_sink=JSONLFileAuditSink(audit_log_path),
)
import uvicorn
uvicorn.run(app, host=host, port=port, log_level="info")
if __name__ == "__main__":