"""ShopDB MCP server (read-only v1). Exposes a curated set of ShopDB *read* endpoints as MCP tools so an LLM client (Claude Desktop / Claude Code / an agent) can query the asset database directly. Tools are generated from docs/openapi.json - keep that spec current (scripts/gen_openapi.py) and this server tracks it. This is a SEPARATE tool from the shopdb app: it runs wherever you can reach the API over HTTPS and calls it with a scoped Personal Access Token. It never needs to run on the (air-gapped) prod box. Safety: - Read-only. Only GET endpoints in the CURATED set below become tools; every write (POST/PUT/PATCH/DELETE) and everything off the allowlist is excluded. - Auth is a scoped PAT you supply. Give it the least scope needed (read). - The app audits API calls (AuditLog), so tool activity is traceable. Run: pip install -r mcp/requirements.txt export SHOPDB_BASE="https://tsgwp00525.wjs.geaerospace.net/shopdb" export SHOPDB_TOKEN="" # Bearer # or: export SHOPDB_API_KEY="" python mcp/shopdb_mcp.py # stdio transport Widen later (add write tools) by extending CURATED / adding RouteMaps for the verbs you want, behind a token that actually has those scopes. """ import json import os import httpx from fastmcp import FastMCP from fastmcp.server.openapi import MCPType, RouteMap _HERE = os.path.dirname(os.path.abspath(__file__)) _SPEC = os.path.join(_HERE, '..', 'docs', 'openapi.json') BASE = os.environ.get('SHOPDB_BASE', 'https://tsgwp00525.wjs.geaerospace.net/shopdb') TOKEN = os.environ.get('SHOPDB_TOKEN') API_KEY = os.environ.get('SHOPDB_API_KEY') # High-value read surfaces only - keeps the tool count small so the model picks # well. Add more prefixes here as needed. CURATED = (r'^/api/(search|assets|reports|printers|computers|machines|network|' r'measuringtools|applications|knowledgebase|locations|vendors|' r'businessunits|dashboard)(/|$)') def build_server(): if not (TOKEN or API_KEY): raise SystemExit('Set SHOPDB_TOKEN (Bearer PAT) or SHOPDB_API_KEY.') headers = {} if TOKEN: headers['Authorization'] = 'Bearer ' + TOKEN if API_KEY: headers['X-API-Key'] = API_KEY client = httpx.AsyncClient(base_url=BASE, headers=headers, timeout=30.0) spec = json.load(open(_SPEC)) route_maps = [ # curated read endpoints -> tools RouteMap(methods=['GET'], pattern=CURATED, mcp_type=MCPType.TOOL), # everything else (writes + off-list reads) -> not exposed RouteMap(mcp_type=MCPType.EXCLUDE), ] return FastMCP.from_openapi( openapi_spec=spec, client=client, name='ShopDB (read-only)', route_maps=route_maps, ) if __name__ == '__main__': build_server().run()