agent-card-starter 0.2.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.
agent_card/__init__.py ADDED
@@ -0,0 +1,2 @@
1
+ """agent-card-starter: make a small business readable for AI assistants. Standard library only."""
2
+ __version__ = "0.2.0"
agent_card/__main__.py ADDED
@@ -0,0 +1,5 @@
1
+ import sys
2
+
3
+ from agent_card.cli import main
4
+
5
+ sys.exit(main())
agent_card/cli.py ADDED
@@ -0,0 +1,105 @@
1
+ """Command line: `agent-card validate FILE` and `agent-card init`. Standard library only."""
2
+ import argparse
3
+ import json
4
+ import os
5
+ import re
6
+ import sys
7
+ from importlib import resources
8
+
9
+ from agent_card import __version__, validator
10
+
11
+
12
+ def _template() -> str:
13
+ return resources.files("agent_card").joinpath("data/agent-card.template.json").read_text(encoding="utf-8")
14
+
15
+
16
+ def _slug(text: str) -> str:
17
+ s = re.sub(r"[^a-z0-9]+", "-", text.lower().replace("ä", "ae").replace("ö", "oe").replace("ü", "ue").replace("ß", "ss")).strip("-")
18
+ return s or "leistung"
19
+
20
+
21
+ def skills_from(items) -> list:
22
+ """`--skill "Name"` or `--skill "Name: one sentence"`. Ids are made from the name and stay stable."""
23
+ out, seen = [], set()
24
+ for raw in items:
25
+ name, _, desc = raw.partition(":")
26
+ name, desc = name.strip(), desc.strip()
27
+ if not name:
28
+ continue
29
+ sid, n = _slug(name), 2
30
+ while sid in seen:
31
+ sid, n = "%s-%d" % (_slug(name), n), n + 1
32
+ seen.add(sid)
33
+ out.append({"id": sid, "name": name, "description": desc or name + ".", "tags": [_slug(name)]})
34
+ return out
35
+
36
+
37
+ def build_card(name: str, description: str, website: str, endpoint: str, skills=None) -> dict:
38
+ """Fill the template. Values go through json so quotes in names cannot break the file."""
39
+ card = json.loads(_template())
40
+
41
+ def fill(x):
42
+ if isinstance(x, str):
43
+ return x.replace("{name}", name).replace("{description}", description).replace("{website}", website).replace("{endpoint}", endpoint)
44
+ if isinstance(x, list):
45
+ return [fill(i) for i in x]
46
+ if isinstance(x, dict):
47
+ return {k: fill(v) for k, v in x.items()}
48
+ return x
49
+ card = fill(card)
50
+ if skills:
51
+ card["skills"] = skills + card["skills"] # the inquiry skill stays last
52
+ return card
53
+
54
+
55
+ def cmd_init(a) -> int:
56
+ website = a.website.rstrip("/")
57
+ endpoint = a.endpoint or website + "/mcp"
58
+ card = build_card(a.name, a.description, website, endpoint, skills_from(a.skill))
59
+ errors, warnings = validator.validate(card, "agent-card")
60
+ if errors:
61
+ for m in errors:
62
+ print("ERROR " + m)
63
+ return 1
64
+ target = os.path.join(a.out, ".well-known", "agent-card.json")
65
+ if os.path.exists(target) and not a.force:
66
+ print("ERROR %s exists already (use --force to overwrite)" % target)
67
+ return 1
68
+ os.makedirs(os.path.dirname(target), exist_ok=True)
69
+ with open(target, "w", encoding="utf-8") as f:
70
+ json.dump(card, f, ensure_ascii=False, indent=2)
71
+ f.write("\n")
72
+ for m in warnings:
73
+ print("WARN " + m)
74
+ print("Wrote %s" % target)
75
+ print("Next: add one skill per service you really offer, keep prices and phone numbers out,")
76
+ print("point supportedInterfaces at an endpoint you really operate, run `agent-card validate %s`," % target)
77
+ print("then publish the file at %s/.well-known/agent-card.json" % website)
78
+ return 0
79
+
80
+
81
+ def main(argv=None) -> int:
82
+ ap = argparse.ArgumentParser(prog="agent-card", description="Make a small business readable for AI assistants.")
83
+ ap.add_argument("--version", action="version", version="agent-card-starter " + __version__)
84
+ sub = ap.add_subparsers(dest="cmd", required=True)
85
+ v = sub.add_parser("validate", help="check an agent card, server card or catalog (offline)")
86
+ v.add_argument("file")
87
+ v.add_argument("--type", choices=["auto", "agent-card", "server-card", "catalog"], default="auto")
88
+ v.add_argument("--strict", action="store_true", help="treat warnings as errors")
89
+ i = sub.add_parser("init", help="write a first agent card to <out>/.well-known/agent-card.json")
90
+ i.add_argument("--name", required=True, help="business name")
91
+ i.add_argument("--description", required=True, help="one or two sentences: what you do and where")
92
+ i.add_argument("--website", required=True, help="https URL of your website")
93
+ i.add_argument("--endpoint", default="", help="https URL of an endpoint you really operate (default: <website>/mcp)")
94
+ i.add_argument("--skill", action="append", default=[], metavar="NAME[: SENTENCE]", help="one service you really offer, repeat for more")
95
+ i.add_argument("--out", default=".", help="folder to write into (default: current folder)")
96
+ i.add_argument("--force", action="store_true", help="overwrite an existing card")
97
+ a = ap.parse_args(argv)
98
+ if a.cmd == "validate":
99
+ extra = [a.file, "--type", a.type] + (["--strict"] if a.strict else [])
100
+ return validator.main(extra)
101
+ return cmd_init(a)
102
+
103
+
104
+ if __name__ == "__main__":
105
+ sys.exit(main())
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "{name}",
3
+ "description": "{description}",
4
+ "version": "1.0.0",
5
+ "supportedInterfaces": [
6
+ {
7
+ "url": "{endpoint}",
8
+ "protocolBinding": "MCP",
9
+ "protocolVersion": "2025-06-18"
10
+ }
11
+ ],
12
+ "provider": {
13
+ "organization": "{name}",
14
+ "url": "{website}"
15
+ },
16
+ "documentationUrl": "{website}",
17
+ "capabilities": {
18
+ "streaming": false,
19
+ "pushNotifications": false
20
+ },
21
+ "defaultInputModes": ["application/json"],
22
+ "defaultOutputModes": ["application/json"],
23
+ "skills": [
24
+ {
25
+ "id": "anfrage",
26
+ "name": "Anfrage hinterlassen",
27
+ "description": "Eine Anfrage anlegen. Ein Mensch im Betrieb meldet sich zurück. Keine Zusage und kein Preis durch den Assistenten.",
28
+ "tags": ["anfrage", "kontakt"]
29
+ }
30
+ ]
31
+ }
@@ -0,0 +1,272 @@
1
+ #!/usr/bin/env python3
2
+ """Validate an A2A Agent Card, an MCP Server Card or an AI catalog (offline, stdlib only).
3
+
4
+ Usage:
5
+ agent-card validate path/to/agent-card.json (or: python3 tools/validate_agent_card.py ...)
6
+ python3 tools/validate_agent_card.py card.json --type server-card --strict
7
+
8
+ Exit code 0 = valid (warnings allowed), 1 = errors found (or warnings with --strict).
9
+
10
+ This is a pragmatic subset check of the required fields, not a full JSON Schema
11
+ validation. Standards move: check the official specs before you rely on it.
12
+ Nützlich zum schnellen Prüfen der Pflichtfelder, ersetzt keine Volltest-Validierung.
13
+ """
14
+ from __future__ import annotations
15
+
16
+ import argparse
17
+ import json
18
+ import re
19
+ import sys
20
+ from typing import Any, List, Tuple
21
+
22
+ KNOWN_BINDINGS = {"JSONRPC", "GRPC", "HTTP+JSON", "MCP"}
23
+ SEMVER = re.compile(r"^\d+\.\d+\.\d+([-+][0-9A-Za-z.-]+)?$")
24
+ MEDIA_TYPE = re.compile(r"^[A-Za-z0-9.+-]+/[A-Za-z0-9.+-]+$")
25
+ EURO = re.compile(r"(€|\bEUR\b|\bEuro\b)", re.IGNORECASE)
26
+ PHONE = re.compile(r"(\+\d{2}[\d\s/()-]{6,}|\b0\d{2,5}[\s/-]?\d{4,})")
27
+ EMAIL = re.compile(r"[\w.+-]+@[\w-]+\.[\w.-]+")
28
+ CATALOG_TYPES = {"application/a2a-agent-card+json", "application/mcp-server-card+json"}
29
+
30
+
31
+ def strip_comments(text: str) -> str:
32
+ """Remove // line comments and /* block */ comments outside of strings (JSONC)."""
33
+ out: List[str] = []
34
+ i, n = 0, len(text)
35
+ in_str = False
36
+ while i < n:
37
+ c = text[i]
38
+ if in_str:
39
+ out.append(c)
40
+ if c == "\\" and i + 1 < n:
41
+ out.append(text[i + 1])
42
+ i += 1
43
+ elif c == '"':
44
+ in_str = False
45
+ elif c == '"':
46
+ in_str = True
47
+ out.append(c)
48
+ elif text.startswith("//", i):
49
+ while i < n and text[i] != "\n":
50
+ i += 1
51
+ continue
52
+ elif text.startswith("/*", i):
53
+ end = text.find("*/", i + 2)
54
+ i = n if end == -1 else end + 2
55
+ continue
56
+ else:
57
+ out.append(c)
58
+ i += 1
59
+ return "".join(out)
60
+
61
+
62
+ def load(path: str) -> Any:
63
+ with open(path, encoding="utf-8") as fh:
64
+ return json.loads(strip_comments(fh.read()))
65
+
66
+
67
+ def _is_str(v: Any) -> bool:
68
+ return isinstance(v, str) and v.strip() != ""
69
+
70
+
71
+ def _https(v: Any) -> bool:
72
+ return _is_str(v) and v.startswith("https://") and " " not in v
73
+
74
+
75
+ def detect_type(doc: Any) -> str:
76
+ if isinstance(doc, dict):
77
+ if "entries" in doc:
78
+ return "catalog"
79
+ if "remotes" in doc or "$schema" in doc:
80
+ return "server-card"
81
+ return "agent-card"
82
+
83
+
84
+ def _texts(doc: Any):
85
+ if isinstance(doc, dict):
86
+ for k, v in doc.items():
87
+ if k in ("url", "$schema", "websiteUrl", "documentationUrl", "identifier"):
88
+ continue
89
+ yield from _texts(v)
90
+ elif isinstance(doc, list):
91
+ for v in doc:
92
+ yield from _texts(v)
93
+ elif isinstance(doc, str):
94
+ yield doc
95
+
96
+
97
+ def content_warnings(doc: Any) -> List[str]:
98
+ w: List[str] = []
99
+ for t in _texts(doc):
100
+ if EURO.search(t):
101
+ w.append("price/currency in card text (%r): prices belong to a human offer" % t[:40])
102
+ if PHONE.search(t):
103
+ w.append("phone number in card text (%r)" % t[:40])
104
+ if EMAIL.search(t):
105
+ w.append("e-mail address in card text (%r)" % t[:40])
106
+ return w
107
+
108
+
109
+ def validate_agent_card(d: Any) -> Tuple[List[str], List[str]]:
110
+ e: List[str] = []
111
+ w: List[str] = []
112
+ if not isinstance(d, dict):
113
+ return ["top level must be a JSON object"], w
114
+ for k in ("name", "description", "version"):
115
+ if not _is_str(d.get(k)):
116
+ e.append("%s: required, non-empty string" % k)
117
+ if _is_str(d.get("version")) and not SEMVER.match(d["version"]):
118
+ w.append("version %r is not semver (x.y.z)" % d["version"])
119
+ ifaces = d.get("supportedInterfaces")
120
+ if not isinstance(ifaces, list) or not ifaces:
121
+ e.append("supportedInterfaces: required, at least one entry")
122
+ else:
123
+ for i, it in enumerate(ifaces):
124
+ p = "supportedInterfaces[%d]" % i
125
+ if not isinstance(it, dict):
126
+ e.append(p + ": must be an object")
127
+ continue
128
+ if not _https(it.get("url")):
129
+ e.append(p + ".url: required, https URL")
130
+ if not _is_str(it.get("protocolBinding")):
131
+ e.append(p + ".protocolBinding: required")
132
+ elif it["protocolBinding"] not in KNOWN_BINDINGS:
133
+ w.append("%s.protocolBinding %r not in %s" % (p, it["protocolBinding"], sorted(KNOWN_BINDINGS)))
134
+ if not _is_str(it.get("protocolVersion")):
135
+ e.append(p + ".protocolVersion: required")
136
+ prov = d.get("provider")
137
+ if not isinstance(prov, dict):
138
+ w.append("provider: recommended (organization, url)")
139
+ else:
140
+ if not _is_str(prov.get("organization")):
141
+ e.append("provider.organization: required when provider is given")
142
+ if not _https(prov.get("url")):
143
+ e.append("provider.url: required when provider is given, https URL")
144
+ if "documentationUrl" in d and not _https(d["documentationUrl"]):
145
+ e.append("documentationUrl: must be an https URL")
146
+ if not isinstance(d.get("capabilities"), dict):
147
+ e.append("capabilities: required object (may be empty)")
148
+ for k in ("defaultInputModes", "defaultOutputModes"):
149
+ v = d.get(k)
150
+ if not isinstance(v, list) or not v:
151
+ e.append("%s: required, non-empty list of media types" % k)
152
+ else:
153
+ for m in v:
154
+ if not (isinstance(m, str) and MEDIA_TYPE.match(m)):
155
+ e.append("%s: %r is not a media type" % (k, m))
156
+ skills = d.get("skills")
157
+ if not isinstance(skills, list) or not skills:
158
+ e.append("skills: required, at least one skill")
159
+ else:
160
+ seen = set()
161
+ for i, s in enumerate(skills):
162
+ p = "skills[%d]" % i
163
+ if not isinstance(s, dict):
164
+ e.append(p + ": must be an object")
165
+ continue
166
+ for k in ("id", "name", "description"):
167
+ if not _is_str(s.get(k)):
168
+ e.append("%s.%s: required, non-empty string" % (p, k))
169
+ tags = s.get("tags")
170
+ if not isinstance(tags, list) or not tags or not all(_is_str(t) for t in tags):
171
+ e.append(p + ".tags: required, non-empty list of strings")
172
+ if _is_str(s.get("id")):
173
+ if s["id"] in seen:
174
+ e.append("%s.id %r is duplicated" % (p, s["id"]))
175
+ seen.add(s["id"])
176
+ w.extend(content_warnings(d))
177
+ return e, w
178
+
179
+
180
+ def validate_server_card(d: Any) -> Tuple[List[str], List[str]]:
181
+ e: List[str] = []
182
+ w: List[str] = []
183
+ if not isinstance(d, dict):
184
+ return ["top level must be a JSON object"], w
185
+ for k in ("$schema", "name", "version", "description"):
186
+ if not _is_str(d.get(k)):
187
+ e.append("%s: required, non-empty string" % k)
188
+ if _is_str(d.get("version")) and not SEMVER.match(d["version"]):
189
+ w.append("version %r is not semver (x.y.z)" % d["version"])
190
+ if "websiteUrl" in d and not _https(d["websiteUrl"]):
191
+ e.append("websiteUrl: must be an https URL")
192
+ remotes = d.get("remotes")
193
+ if remotes is None:
194
+ w.append("remotes: none given, assistants cannot connect")
195
+ elif not isinstance(remotes, list) or not remotes:
196
+ e.append("remotes: must be a non-empty list")
197
+ else:
198
+ for i, r in enumerate(remotes):
199
+ p = "remotes[%d]" % i
200
+ if not isinstance(r, dict):
201
+ e.append(p + ": must be an object")
202
+ continue
203
+ if r.get("type") not in ("streamable-http", "sse"):
204
+ e.append(p + ".type: must be 'streamable-http' or 'sse'")
205
+ if not _https(r.get("url")):
206
+ e.append(p + ".url: required, https URL")
207
+ w.extend(content_warnings(d))
208
+ return e, w
209
+
210
+
211
+ def validate_catalog(d: Any) -> Tuple[List[str], List[str]]:
212
+ e: List[str] = []
213
+ w: List[str] = []
214
+ if not isinstance(d, dict):
215
+ return ["top level must be a JSON object"], w
216
+ if not _is_str(d.get("specVersion")):
217
+ e.append("specVersion: required")
218
+ entries = d.get("entries")
219
+ if not isinstance(entries, list) or not entries:
220
+ e.append("entries: required, at least one entry")
221
+ return e, w
222
+ ids = set()
223
+ for i, it in enumerate(entries):
224
+ p = "entries[%d]" % i
225
+ if not isinstance(it, dict):
226
+ e.append(p + ": must be an object")
227
+ continue
228
+ if not _is_str(it.get("identifier")):
229
+ e.append(p + ".identifier: required")
230
+ elif it["identifier"] in ids:
231
+ e.append("%s.identifier %r is duplicated" % (p, it["identifier"]))
232
+ else:
233
+ ids.add(it["identifier"])
234
+ if it.get("type") not in CATALOG_TYPES:
235
+ w.append("%s.type %r is not one of %s" % (p, it.get("type"), sorted(CATALOG_TYPES)))
236
+ if not _https(it.get("url")):
237
+ e.append(p + ".url: required, https URL")
238
+ return e, w
239
+
240
+
241
+ def validate(doc: Any, kind: str = "auto") -> Tuple[List[str], List[str]]:
242
+ if kind == "auto":
243
+ kind = detect_type(doc)
244
+ return {"agent-card": validate_agent_card, "server-card": validate_server_card,
245
+ "catalog": validate_catalog}[kind](doc)
246
+
247
+
248
+ def main(argv: List[str] = None) -> int:
249
+ ap = argparse.ArgumentParser(description=__doc__.split("\n")[0])
250
+ ap.add_argument("file")
251
+ ap.add_argument("--type", choices=["auto", "agent-card", "server-card", "catalog"], default="auto")
252
+ ap.add_argument("--strict", action="store_true", help="treat warnings as errors")
253
+ a = ap.parse_args(argv)
254
+ try:
255
+ doc = load(a.file)
256
+ except (OSError, ValueError) as ex:
257
+ print("ERROR cannot read %s: %s" % (a.file, ex))
258
+ return 1
259
+ errors, warnings = validate(doc, a.type)
260
+ for m in errors:
261
+ print("ERROR " + m)
262
+ for m in warnings:
263
+ print("WARN " + m)
264
+ if errors or (a.strict and warnings):
265
+ print("INVALID: %d error(s), %d warning(s)" % (len(errors), len(warnings)))
266
+ return 1
267
+ print("OK: %s (%d warning(s))" % (a.file, len(warnings)))
268
+ return 0
269
+
270
+
271
+ if __name__ == "__main__":
272
+ sys.exit(main())
@@ -0,0 +1,171 @@
1
+ Metadata-Version: 2.4
2
+ Name: agent-card-starter
3
+ Version: 0.2.0
4
+ Summary: Make a small business readable for AI assistants: write and check an A2A agent card or MCP server card. Standard library only.
5
+ Author-email: flowboxx <kontakt@flowboxx.de>
6
+ License: MIT
7
+ Project-URL: Homepage, https://flowboxx.de/
8
+ Project-URL: Source, https://github.com/flowboxx/agent-card-starter
9
+ Project-URL: Issues, https://github.com/flowboxx/agent-card-starter/issues
10
+ Keywords: a2a,agent-card,mcp,mcp-server-card,well-known,small-business
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Topic :: Internet :: WWW/HTTP
15
+ Requires-Python: >=3.9
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+ Dynamic: license-file
19
+
20
+ # agent-card-starter
21
+
22
+ [![tests](../../actions/workflows/ci.yml/badge.svg)](../../actions/workflows/ci.yml)
23
+ ![license MIT](https://img.shields.io/badge/license-MIT-blue)
24
+ ![python 3.9+](https://img.shields.io/badge/python-3.9%2B-blue)
25
+
26
+ **In 60 seconds:** AI assistants read a small JSON file to learn what your business does. This tool writes that file for you and checks it. Offline, no account, no dependencies.
27
+
28
+ ```bash
29
+ pip install git+https://github.com/flowboxx/agent-card-starter
30
+ agent-card init --name "Muster Roofing Ltd" --description "Roofer in Musterstadt, repairs and new roofs." --website https://www.example.com --skill "Flat roof sealing"
31
+ agent-card validate .well-known/agent-card.json --strict
32
+ ```
33
+
34
+ Upload the file to `https://<your-domain>/.well-known/agent-card.json` and you are readable. Nothing here guarantees that an assistant will mention you.
35
+
36
+ **In 60 Sekunden:** KI-Assistenten lesen eine kleine Datei, um zu erfahren, was Ihr Betrieb macht. Dieses Werkzeug schreibt die Datei und prüft sie. Ohne Netz, ohne Konto, ohne Fremdpakete. Befehle siehe oben, die Datei kommt nach `https://<Ihre-Domain>/.well-known/agent-card.json`.
37
+
38
+ <!-- Picture idea: a 20 second terminal recording (init, then validate with OK) of the three commands above. Fictional business only. -->
39
+
40
+ Starter kit to make a small business readable and reachable for AI assistants:
41
+ an A2A agent card, an MCP server card, a minimal MCP example server and a validator.
42
+ Standard library only, Python 3.9+, MIT licensed.
43
+
44
+ Starter-Set, damit ein kleiner Betrieb für KI-Assistenten lesbar und erreichbar wird:
45
+ Agentenkarte (A2A), Server-Karte (MCP), ein minimaler MCP-Beispielserver und ein Prüfskript.
46
+ Nur Python-Standardbibliothek, ab Python 3.9, MIT-Lizenz.
47
+
48
+ Maintained by [flowboxx](https://flowboxx.de/?ref=github), an agency that builds AI doors for businesses.
49
+ Gepflegt von [flowboxx](https://flowboxx.de/?ref=github), einer Agentur, die KI-Türen für Betriebe baut.
50
+
51
+ ---
52
+
53
+ ## English
54
+
55
+ ### What is in here
56
+
57
+ | Path | What it is |
58
+ | --- | --- |
59
+ | `examples/well-known/agent-card.json` | A2A agent card for a fictional "Beispiel Dachdecker GmbH" |
60
+ | `examples/well-known/agent.json` | Byte-identical copy for older readers |
61
+ | `examples/agent-card.annotated.jsonc` | Same card with comments (documentation only) |
62
+ | `examples/mcp-server-card.json` | MCP server card for the same business |
63
+ | `templates/` | Blank-ish templates: server card and AI catalog |
64
+ | `server/mcp_server.py` | MCP example server over stdio: tools `leistungen` and `anfrage_anlegen` (stub) |
65
+ | `agent_card/` | The `agent-card` command: `init` writes a first card, `validate` checks card, server card and catalog (offline) |
66
+ | `tools/validate_agent_card.py` | Same validator as a plain script |
67
+ | `tests/` | pytest suite |
68
+
69
+ ### Quick start
70
+
71
+ ```bash
72
+ agent-card init --name "Your Business" --description "What you do and where." --website https://www.example.com
73
+ agent-card validate .well-known/agent-card.json --strict
74
+ # from a clone, without installing:
75
+ python3 tools/validate_agent_card.py examples/well-known/agent-card.json
76
+ python3 tools/validate_agent_card.py examples/mcp-server-card.json --strict
77
+ printf '{"jsonrpc":"2.0","id":1,"method":"tools/list"}\n' | python3 server/mcp_server.py
78
+ python3 -m pip install pytest && python3 -m pytest -q
79
+ docker build -t agent-card-starter . && docker run -i --rm agent-card-starter # MCP over stdio
80
+ ```
81
+
82
+ ### Use it for your business
83
+
84
+ 1. Copy `examples/well-known/agent-card.json`, replace name, description, provider, skills.
85
+ 2. List only services you really offer. One skill per service, stable ids.
86
+ 3. Keep prices, phone numbers and promises out of the card. A human answers those.
87
+ 4. Run the validator, publish the file at `https://<your-domain>/.well-known/agent-card.json`.
88
+ 5. Point `supportedInterfaces` at an endpoint you really operate. The example server
89
+ speaks MCP over stdio for learning. A public endpoint needs HTTP transport,
90
+ rate limiting, spam protection and a privacy notice. `anfrage_anlegen` is a stub on purpose.
91
+
92
+ ### Notes on the standards
93
+
94
+ - The agent card follows the A2A v1.0 field names (`supportedInterfaces`, `skills`, `provider`).
95
+ - The server card follows the MCP Server Card proposal (SEP-2127). It is an extension and
96
+ the schema may still change. Check the current schema before you publish.
97
+ - The validator checks required fields and a few sanity rules. It is not a full JSON Schema validation.
98
+ - Nothing here guarantees that an assistant will mention or recommend you.
99
+
100
+ ---
101
+
102
+ ## Deutsch
103
+
104
+ ### Was drin ist
105
+
106
+ | Pfad | Inhalt |
107
+ | --- | --- |
108
+ | `examples/well-known/agent-card.json` | A2A-Agentenkarte für die fiktive "Beispiel Dachdecker GmbH" |
109
+ | `examples/well-known/agent.json` | Byte-gleiche Kopie für ältere Leser |
110
+ | `examples/agent-card.annotated.jsonc` | Dieselbe Karte mit Kommentaren (nur zur Erklärung) |
111
+ | `examples/mcp-server-card.json` | MCP-Server-Karte für denselben Betrieb |
112
+ | `templates/` | Vorlagen: Server-Karte und KI-Katalog |
113
+ | `server/mcp_server.py` | MCP-Beispielserver über stdio: Werkzeuge `leistungen` und `anfrage_anlegen` (Stub) |
114
+ | `agent_card/` | Der Befehl `agent-card`: `init` schreibt eine erste Karte, `validate` prüft Karte, Server-Karte und Katalog (ohne Netz) |
115
+ | `tools/validate_agent_card.py` | Dasselbe Prüfskript als einfache Datei |
116
+ | `tests/` | pytest-Tests |
117
+
118
+ ### Schnellstart
119
+
120
+ ```bash
121
+ agent-card init --name "Ihr Betrieb" --description "Was Sie tun und wo." --website https://www.example.com
122
+ agent-card validate .well-known/agent-card.json --strict
123
+ # aus einem Klon, ohne Installation:
124
+ python3 tools/validate_agent_card.py examples/well-known/agent-card.json
125
+ python3 tools/validate_agent_card.py examples/mcp-server-card.json --strict
126
+ printf '{"jsonrpc":"2.0","id":1,"method":"tools/list"}\n' | python3 server/mcp_server.py
127
+ python3 -m pip install pytest && python3 -m pytest -q
128
+ docker build -t agent-card-starter . && docker run -i --rm agent-card-starter # MCP over stdio
129
+ ```
130
+
131
+ ### Für den eigenen Betrieb nutzen
132
+
133
+ 1. `examples/well-known/agent-card.json` kopieren, Name, Beschreibung, Anbieter und Fähigkeiten ersetzen.
134
+ 2. Nur Leistungen eintragen, die Sie wirklich anbieten. Eine Fähigkeit je Leistung, stabile Ids.
135
+ 3. Keine Preise, Telefonnummern oder Zusagen in die Karte. Das beantwortet ein Mensch.
136
+ 4. Prüfskript laufen lassen, Datei unter `https://<Ihre-Domain>/.well-known/agent-card.json` ablegen.
137
+ 5. `supportedInterfaces` auf einen Endpunkt zeigen lassen, den Sie wirklich betreiben. Der Beispielserver
138
+ spricht MCP über stdio zum Lernen. Ein öffentlicher Endpunkt braucht HTTP, Mengenbegrenzung,
139
+ Spam-Schutz und einen Datenschutzhinweis. `anfrage_anlegen` ist absichtlich ein Stub und speichert nichts.
140
+
141
+ ### Hinweise zu den Standards
142
+
143
+ - Die Agentenkarte folgt den Feldnamen von A2A v1.0 (`supportedInterfaces`, `skills`, `provider`).
144
+ - Die Server-Karte folgt dem MCP-Vorschlag Server Card (SEP-2127). Das ist eine Erweiterung,
145
+ das Schema kann sich noch ändern. Vor dem Veröffentlichen das aktuelle Schema prüfen.
146
+ - Das Prüfskript prüft Pflichtfelder und einige Plausibilitäten, keine vollständige JSON-Schema-Validierung.
147
+ - Nichts hier garantiert, dass ein Assistent Sie nennt oder empfiehlt.
148
+
149
+ ---
150
+
151
+ ## Built with this? Tell us / Damit gebaut? Erzählen Sie es uns
152
+
153
+ If you published a card with this kit, show it in [Discussions, Show and tell](../../discussions/categories/show-and-tell). We read every one. If you like, add the badge to your README:
154
+
155
+ Wenn Sie mit diesem Kit eine Karte veröffentlicht haben, zeigen Sie sie unter [Discussions, Show and tell](../../discussions/categories/show-and-tell). Das Abzeichen für Ihre README:
156
+
157
+ ```markdown
158
+ [![built with agent-card-starter](https://img.shields.io/badge/built%20with-agent--card--starter-2b6cb0)](https://github.com/flowboxx/agent-card-starter)
159
+ ```
160
+
161
+ ---
162
+
163
+ ## As is / Ohne Gewähr
164
+
165
+ This is a starting point, provided "as is" under the MIT license, without warranty of any kind. Standards around agent cards and server cards are young and change. Check the current specifications before you publish. Nothing here is legal advice, and nothing guarantees that an assistant will mention or recommend your business. Changes are listed in [CHANGELOG.md](CHANGELOG.md), how to help is in [CONTRIBUTING.md](CONTRIBUTING.md).
166
+
167
+ Dies ist ein Ausgangspunkt, bereitgestellt "wie besehen" unter der MIT-Lizenz, ohne Gewähr. Die Standards rund um Agentenkarten und Server-Karten sind jung und ändern sich. Prüfen Sie vor dem Veröffentlichen die aktuellen Spezifikationen. Nichts hier ist Rechtsberatung, und nichts garantiert, dass ein Assistent Ihren Betrieb nennt oder empfiehlt. Änderungen stehen in [CHANGELOG.md](CHANGELOG.md), Mitmachen steht in [CONTRIBUTING.md](CONTRIBUTING.md).
168
+
169
+ MIT License, see `LICENSE`. Questions and contributions welcome via issues. Fragen und Beiträge gern als Issue.
170
+
171
+ Built and maintained by [flowboxx](https://flowboxx.de/?ref=github), Germany.
@@ -0,0 +1,11 @@
1
+ agent_card/__init__.py,sha256=ORgqAZYtC0VgENTUF4RUU3tQYnzKNCmLdc1Yg4RhFAw,121
2
+ agent_card/__main__.py,sha256=luiLj9IADr6Ex8TNsHdq3fmYeFmmxWGwgmFMGOyl2GE,62
3
+ agent_card/cli.py,sha256=JTk0klqEpDaNc9dG3sFAUTr340wqub_0E5DUOvdx4_g,4654
4
+ agent_card/validator.py,sha256=3Yf6Fq59GXaxkIDc8MDsc9dkEuyGX88Z8GEdrHf_oc0,10356
5
+ agent_card/data/agent-card.template.json,sha256=ck1-mmmWLFlbuQZIr66EacYiiKEaCyt8v7t7FZihsPE,769
6
+ agent_card_starter-0.2.0.dist-info/licenses/LICENSE,sha256=va0C7WxTZdn3cANATOM4RIwhYveIH0Unpv_aipbV2Ks,1065
7
+ agent_card_starter-0.2.0.dist-info/METADATA,sha256=agnyX4WbawiNEX6b0bK-6XeWGo_rgtq2ZabFfbro-lo,9765
8
+ agent_card_starter-0.2.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
9
+ agent_card_starter-0.2.0.dist-info/entry_points.txt,sha256=JIGFWzWnUt83no3m7YcJVX5M03lH7-6gNL9HTqzv-ck,51
10
+ agent_card_starter-0.2.0.dist-info/top_level.txt,sha256=pcy9JqHeVQ6Nx0FZHU-_VFeIRyL1Gf8g_z1cTEP0cj8,11
11
+ agent_card_starter-0.2.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ agent-card = agent_card.cli:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 flowboxx
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ agent_card