api-to-mcp-forge 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- api_to_mcp/__init__.py +6 -0
- api_to_mcp/__main__.py +5 -0
- api_to_mcp/cli.py +167 -0
- api_to_mcp/server.py +2368 -0
- api_to_mcp/workspace.py +111 -0
- api_to_mcp_forge-0.1.0.dist-info/METADATA +128 -0
- api_to_mcp_forge-0.1.0.dist-info/RECORD +11 -0
- api_to_mcp_forge-0.1.0.dist-info/WHEEL +4 -0
- api_to_mcp_forge-0.1.0.dist-info/entry_points.txt +2 -0
- api_to_mcp_forge-0.1.0.dist-info/licenses/LICENSE +202 -0
- api_to_mcp_forge-0.1.0.dist-info/licenses/NOTICE +5 -0
api_to_mcp/__init__.py
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"""api-to-mcp: turn API documentation (a docs URL, an OpenAPI/Swagger file, an HTML page, pasted text) into an MCP
|
|
2
|
+
server. The judgement (reading a platform's docs and writing an evidence-only entry) belongs to the model driving the
|
|
3
|
+
skill; the deterministic parts (templates, lint, run config, tests, live checks) are tools here, so any MCP client can
|
|
4
|
+
run them. Entries run with platform-mcp-hub (`platform-mcp-hub serve --entry <file>`)."""
|
|
5
|
+
|
|
6
|
+
__version__ = "0.1.0"
|
api_to_mcp/__main__.py
ADDED
api_to_mcp/cli.py
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"""api-to-mcp command line: the same tools the MCP server offers, for a terminal or a script. Every command prints the
|
|
2
|
+
tool's JSON result and exits 1 when the result is an error.
|
|
3
|
+
|
|
4
|
+
api-to-mcp mcp [--http [--port N]] run the MCP server (stdio by default; the Claude Code plugin uses this)
|
|
5
|
+
api-to-mcp doctor [--fix] prerequisites, workspace, install commands
|
|
6
|
+
api-to-mcp workspace where entries are saved
|
|
7
|
+
api-to-mcp search <text> [--category C] existing entries (yours and platform-mcp-hub's)
|
|
8
|
+
api-to-mcp get <category> <id> one entry's JSON
|
|
9
|
+
api-to-mcp template [generic | <category>] skeleton, example and adapter contract (default generic)
|
|
10
|
+
api-to-mcp ingest <url | file | -> [--filter F] summarise an OpenAPI/Swagger/Postman/RAML/WSDL/GraphQL/... description
|
|
11
|
+
api-to-mcp read-docs <url> [--offset N] [--reader] readable text of a documentation page
|
|
12
|
+
api-to-mcp draft <spec url|file> <id> [--operations a,b] [--filter F] [--limit N] [--docs-url URL]
|
|
13
|
+
draft a generic entry from an OpenAPI/Swagger description
|
|
14
|
+
api-to-mcp save <category> <id> <entry.json | -> save an entry into the workspace
|
|
15
|
+
api-to-mcp lint <category> <id>
|
|
16
|
+
api-to-mcp generate <category> <id> run config (or a checkout's registry metadata) + contract test template
|
|
17
|
+
api-to-mcp try <category> <id> <verb> ['<json args>']
|
|
18
|
+
api-to-mcp test <category> <id> lint + contract tests + stdio smoke of both runtimes
|
|
19
|
+
api-to-mcp verify <category> <id> [--lang py|ts|both] [--plan '<json>'] [--record]
|
|
20
|
+
api-to-mcp serve <category> <id> [--http ...] start the entry (platform-mcp-hub serve --entry <file>)
|
|
21
|
+
api-to-mcp call <tool> '<json arguments>' any tool by name
|
|
22
|
+
|
|
23
|
+
Global options (before the command): --home DIR (your workspace; API_TO_MCP_HOME) or --checkout DIR (a platform-mcp
|
|
24
|
+
checkout, to contribute an entry upstream; API_TO_MCP_CHECKOUT).
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from __future__ import annotations
|
|
28
|
+
|
|
29
|
+
import asyncio
|
|
30
|
+
import json
|
|
31
|
+
import os
|
|
32
|
+
import sys
|
|
33
|
+
|
|
34
|
+
from . import __version__
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _pop_flag(argv: list[str], flag: str) -> bool:
|
|
38
|
+
if flag in argv:
|
|
39
|
+
argv.remove(flag)
|
|
40
|
+
return True
|
|
41
|
+
return False
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _pop_value(argv: list[str], flag: str) -> str | None:
|
|
45
|
+
if flag in argv:
|
|
46
|
+
i = argv.index(flag)
|
|
47
|
+
if i + 1 >= len(argv):
|
|
48
|
+
raise SystemExit(f"api-to-mcp: {flag} needs a value")
|
|
49
|
+
v = argv[i + 1]
|
|
50
|
+
del argv[i:i + 2]
|
|
51
|
+
return v
|
|
52
|
+
return None
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _json_arg(text: str) -> object:
|
|
56
|
+
if text == "-":
|
|
57
|
+
text = sys.stdin.read()
|
|
58
|
+
elif os.path.isfile(text):
|
|
59
|
+
with open(text, encoding="utf-8") as f:
|
|
60
|
+
text = f.read()
|
|
61
|
+
try:
|
|
62
|
+
return json.loads(text)
|
|
63
|
+
except ValueError as exc:
|
|
64
|
+
raise SystemExit(f"api-to-mcp: not JSON: {exc}")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _call(tool: str, args: dict) -> int:
|
|
68
|
+
from . import server
|
|
69
|
+
res = asyncio.run(server.server.call_tool(tool, args))
|
|
70
|
+
payload = res.structured_content if res.structured_content is not None else {"text": res.content[0].text if res.content else ""}
|
|
71
|
+
print(json.dumps(payload, indent=1, ensure_ascii=False, default=str))
|
|
72
|
+
return 1 if res.is_error else 0
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _need(argv: list[str], n: int, usage: str) -> list[str]:
|
|
76
|
+
if len(argv) < n:
|
|
77
|
+
raise SystemExit(f"usage: api-to-mcp {usage}")
|
|
78
|
+
return argv
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def main(argv: list[str] | None = None) -> int:
|
|
82
|
+
import logging
|
|
83
|
+
logging.getLogger("httpx").setLevel(logging.WARNING) # one line per request on stderr is noise in a terminal
|
|
84
|
+
argv = list(sys.argv[1:] if argv is None else argv)
|
|
85
|
+
home, checkout = _pop_value(argv, "--home"), _pop_value(argv, "--checkout")
|
|
86
|
+
if home:
|
|
87
|
+
os.environ["API_TO_MCP_HOME"] = home
|
|
88
|
+
if checkout:
|
|
89
|
+
os.environ["API_TO_MCP_CHECKOUT"] = checkout
|
|
90
|
+
if not argv or argv[0] in ("-h", "--help", "help"):
|
|
91
|
+
print(__doc__.strip())
|
|
92
|
+
return 0 if argv else 2
|
|
93
|
+
if argv[0] in ("-V", "--version", "version"):
|
|
94
|
+
print(f"api-to-mcp {__version__}")
|
|
95
|
+
return 0
|
|
96
|
+
cmd, rest = argv[0], argv[1:]
|
|
97
|
+
if cmd == "mcp":
|
|
98
|
+
from . import server
|
|
99
|
+
server.main(rest)
|
|
100
|
+
return 0
|
|
101
|
+
if cmd == "doctor":
|
|
102
|
+
return _call("doctor", {"fix": _pop_flag(rest, "--fix")})
|
|
103
|
+
if cmd == "workspace":
|
|
104
|
+
return _call("workspace", {})
|
|
105
|
+
if cmd == "search":
|
|
106
|
+
category = _pop_value(rest, "--category")
|
|
107
|
+
_need(rest, 1, "search <text> [--category C]")
|
|
108
|
+
return _call("catalog_search", {"query": " ".join(rest), **({"category": category} if category else {})})
|
|
109
|
+
if cmd == "get":
|
|
110
|
+
c, i = _need(rest, 2, "get <category> <id>")[:2]
|
|
111
|
+
return _call("catalog_get", {"category": c, "id": i})
|
|
112
|
+
if cmd == "template":
|
|
113
|
+
return _call("template_entry", {"category": rest[0] if rest else "generic"})
|
|
114
|
+
if cmd == "draft":
|
|
115
|
+
ops = _pop_value(rest, "--operations")
|
|
116
|
+
flt = _pop_value(rest, "--filter")
|
|
117
|
+
lim = _pop_value(rest, "--limit")
|
|
118
|
+
docs = _pop_value(rest, "--docs-url")
|
|
119
|
+
src, pid = _need(rest, 2, "draft <spec url | file> <id> [--operations a,b] [--filter F] [--limit N] [--docs-url URL]")[:2]
|
|
120
|
+
return _call("draft_entry", {"source": src, "id": pid, **({"operations": [o.strip() for o in ops.split(",") if o.strip()]} if ops else {}),
|
|
121
|
+
**({"filter": flt} if flt else {}), **({"limit": int(lim)} if lim else {}), **({"docs_url": docs} if docs else {})})
|
|
122
|
+
if cmd == "ingest":
|
|
123
|
+
flt = _pop_value(rest, "--filter")
|
|
124
|
+
src = _need(rest, 1, "ingest <url | file | -> [--filter F]")[0]
|
|
125
|
+
return _call("ingest_openapi", {"source": sys.stdin.read() if src == "-" else src, **({"filter": flt} if flt else {})})
|
|
126
|
+
if cmd == "read-docs":
|
|
127
|
+
offset = _pop_value(rest, "--offset")
|
|
128
|
+
reader = _pop_flag(rest, "--reader")
|
|
129
|
+
url = _need(rest, 1, "read-docs <url> [--offset N] [--reader]")[0]
|
|
130
|
+
return _call("read_docs", {"url": url, "reader": reader, **({"offset": int(offset)} if offset else {})})
|
|
131
|
+
if cmd == "save":
|
|
132
|
+
c, i, src = _need(rest, 3, "save <category> <id> <entry.json | ->")[:3]
|
|
133
|
+
entry = _json_arg(src)
|
|
134
|
+
if isinstance(entry, dict) and isinstance(entry.get("entry"), dict) and "drafted" in entry:
|
|
135
|
+
entry = entry["entry"] # the output of `api-to-mcp draft`, saved as it is
|
|
136
|
+
return _call("save_entry", {"category": c, "id": i, "entry": entry})
|
|
137
|
+
if cmd in ("lint", "generate", "test"):
|
|
138
|
+
c, i = _need(rest, 2, f"{cmd} <category> <id>")[:2]
|
|
139
|
+
return _call({"lint": "lint_entry", "generate": "generate_server", "test": "test_server"}[cmd], {"category": c, "id": i})
|
|
140
|
+
if cmd == "try":
|
|
141
|
+
c, i, verb = _need(rest, 3, "try <category> <id> <verb> ['<json args>']")[:3]
|
|
142
|
+
return _call("try_tool", {"category": c, "id": i, "verb": verb, "arguments": _json_arg(rest[3]) if len(rest) > 3 else {}})
|
|
143
|
+
if cmd == "verify":
|
|
144
|
+
lang = _pop_value(rest, "--lang") or "both"
|
|
145
|
+
plan = _pop_value(rest, "--plan")
|
|
146
|
+
record = _pop_flag(rest, "--record")
|
|
147
|
+
c, i = _need(rest, 2, "verify <category> <id> [--lang py|ts|both] [--plan JSON] [--record]")[:2]
|
|
148
|
+
return _call("live_verify", {"category": c, "id": i, "lang": lang, "record": record, **({"plan": _json_arg(plan)} if plan else {})})
|
|
149
|
+
if cmd == "serve":
|
|
150
|
+
c, i = _need(rest, 2, "serve <category> <id> [--http ...]")[:2]
|
|
151
|
+
from . import server
|
|
152
|
+
path = server.WS.catalog / c / f"{i}.json"
|
|
153
|
+
if not path.is_file():
|
|
154
|
+
raise SystemExit(f"api-to-mcp: no entry {path}")
|
|
155
|
+
os.execvpe(server.WS.python(), [server.WS.python(), "-m", "platform_mcp_hub", "serve", "--entry", str(path), *rest[2:]], server.WS.gate_env(dict(os.environ)))
|
|
156
|
+
if cmd == "call":
|
|
157
|
+
tool = _need(rest, 1, "call <tool> '<json arguments>'")[0]
|
|
158
|
+
args = _json_arg(rest[1]) if len(rest) > 1 else {}
|
|
159
|
+
if not isinstance(args, dict):
|
|
160
|
+
raise SystemExit("api-to-mcp: the arguments must be a JSON object")
|
|
161
|
+
return _call(tool, args)
|
|
162
|
+
print(f"api-to-mcp: unknown command {cmd!r}; run `api-to-mcp --help`", file=sys.stderr)
|
|
163
|
+
return 2
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def entry() -> None:
|
|
167
|
+
sys.exit(main())
|