Granular 4-phase, 4-role GDS Service Standard assurance skills, panel orchestrator, and synthesis engine.
87%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
Low
Low-risk findings worth noting
"""MCP server for a project's GOV.UK service assessment library.
Stdio transport. The corpus is markdown the project downloaded with
scrape_gds_reports.py, usually docs/past-assessments. This plugin checkout
can also read context/. GOV.UK is not contacted at assessment time.
"""
import json
import sys
from pathlib import Path
from library_paths import find_existing
REPORTS, CATALOGUE = find_existing(Path(__file__))
CURATED = {
"discovery-lead-stop.md",
"alpha-tech-spikes.md",
"beta-ur-fail-assistive-tech.md",
"beta-tech-pass.md",
}
PHASES = ("discovery", "alpha", "beta", "live")
PASS_RATINGS = {"green", "met", "met-with-advice"}
WEAK_RATINGS = {"amber", "red", "not-met"}
MAX_LIMIT = 8
FALLBACK_PROTOCOL = "2024-11-05"
TOOLS = [
{
"name": "search_past_assessments",
"description": (
"Shortlist published UK government service assessment reports for one "
"phase. Returns catalogue rows only, not the report body. Prefer "
"standard 14. At most 8 rows."
),
"inputSchema": {
"type": "object",
"properties": {
"phase": {
"type": "string",
"enum": list(PHASES),
"description": "Discovery, alpha, beta, or live.",
},
"point": {
"type": "integer",
"minimum": 1,
"maximum": 14,
"description": "Current Service Standard point to match. Omit to list recent reports for the phase.",
},
"rating": {
"type": "string",
"enum": sorted(PASS_RATINGS | WEAK_RATINGS),
"description": "Point rating to require. Omit to mix reports that failed and passed the point.",
},
"limit": {
"type": "integer",
"minimum": 1,
"maximum": MAX_LIMIT,
"description": "Maximum rows. Defaults to 8.",
},
},
"required": ["phase"],
"additionalProperties": False,
},
},
{
"name": "get_past_assessment",
"description": (
"Return the full markdown of one ingested report. Pass the file name "
"from search_past_assessments, such as digital-waste-tracking.md."
),
"inputSchema": {
"type": "object",
"properties": {
"file": {
"type": "string",
"description": "Report file name, not a path.",
}
},
"required": ["file"],
"additionalProperties": False,
},
},
]
def normalise_rating(value):
if value is None:
return None
key = str(value).strip().lower().replace("_", "-")
aliases = {
"not met": "not-met",
"met with advice": "met-with-advice",
}
key = aliases.get(key, key)
allowed = PASS_RATINGS | WEAK_RATINGS
if key not in allowed:
raise ValueError(
f"Unknown rating {value!r}. Use one of: {', '.join(sorted(allowed))}."
)
return key
def load_phase(phase):
phase = phase.strip().lower()
if phase not in PHASES:
raise ValueError(f"Unknown phase {phase!r}. Use one of: {', '.join(PHASES)}.")
path = CATALOGUE / f"{phase}.json"
if not path.is_file():
raise FileNotFoundError(
"No past-assessment catalogue. From the project root, run "
"scrape_gds_reports.py in the gds-assessment-how-to skill. "
"It writes docs/past-assessments. Then restart the gds-assessment-reports server."
)
payload = json.loads(path.read_text(encoding="utf-8"))
return [
report
for report in payload.get("reports", [])
if report.get("file") not in CURATED and report.get("standard") == "14"
]
def row(report, point):
item = {
"file": report.get("file"),
"title": report.get("title"),
"phase": report.get("phase"),
"stage": report.get("stage"),
"result": report.get("result"),
"assessed": report.get("assessed"),
"source": report.get("source"),
"standard": report.get("standard"),
"weak_points": report.get("weak_points") or [],
}
if point is not None:
item["point"] = point
item["point_rating"] = (report.get("points") or {}).get(str(point))
return item
def search(phase, point=None, rating=None, limit=MAX_LIMIT):
limit = MAX_LIMIT if limit is None else min(max(int(limit), 1), MAX_LIMIT)
rating = normalise_rating(rating)
if point is not None:
point = int(point)
if not 1 <= point <= 14:
raise ValueError("point must be from 1 to 14.")
reports = load_phase(phase)
def point_rating(report):
return (report.get("points") or {}).get(str(point))
if point is None:
chosen = reports[:limit]
elif rating is not None:
chosen = [report for report in reports if point_rating(report) == rating][:limit]
else:
weak = [report for report in reports if point_rating(report) in WEAK_RATINGS]
passed = [report for report in reports if point_rating(report) in PASS_RATINGS]
weak_take = min(len(weak), max(1, limit // 2)) if weak and passed else min(len(weak), limit)
pass_take = min(len(passed), limit - weak_take)
chosen = weak[:weak_take] + passed[:pass_take]
return {
"phase": phase,
"point": point,
"rating": rating,
"count": len(chosen),
"reports": [row(report, point) for report in chosen],
}
def get_report(file_name):
name = Path(str(file_name)).name
if name != str(file_name).strip() or not name.endswith(".md"):
raise ValueError("Pass a markdown file name, not a path.")
path = REPORTS / name
if not path.is_file():
raise FileNotFoundError(f"No ingested report named {name}.")
return path.read_text(encoding="utf-8")
def call_tool(name, arguments):
arguments = arguments or {}
if name == "search_past_assessments":
payload = search(
arguments["phase"],
arguments.get("point"),
arguments.get("rating"),
arguments.get("limit", MAX_LIMIT),
)
return json.dumps(payload, indent=2, ensure_ascii=False)
if name == "get_past_assessment":
return get_report(arguments["file"])
raise ValueError(f"Unknown tool {name}.")
# Cursor's client uses Content-Length framing. A line of raw JSON is accepted
# too, and the reply uses whichever form the client just used.
FRAMING = "headers"
def send(message):
body = json.dumps(message, separators=(",", ":")).encode("utf-8")
if FRAMING == "line":
sys.stdout.buffer.write(body + b"\n")
else:
header = f"Content-Length: {len(body)}\r\n\r\n".encode("ascii")
sys.stdout.buffer.write(header)
sys.stdout.buffer.write(body)
sys.stdout.buffer.flush()
def read_message():
global FRAMING
first = sys.stdin.buffer.read(1)
if first == b"":
return None
if first == b"{":
FRAMING = "line"
rest = sys.stdin.buffer.readline()
if rest == b"":
return None
return json.loads((first + rest).decode("utf-8"))
FRAMING = "headers"
chunk = first
while b"\r\n\r\n" not in chunk and b"\n\n" not in chunk:
nxt = sys.stdin.buffer.read(1)
if nxt == b"":
return None
chunk += nxt
if len(chunk) > 8192:
return None
separator = b"\r\n\r\n" if b"\r\n\r\n" in chunk else b"\n\n"
header_blob, leftover = chunk.split(separator, 1)
headers = {}
for line in header_blob.decode("ascii", errors="replace").splitlines():
if ":" in line:
key, value = line.split(":", 1)
headers[key.strip().lower()] = value.strip()
length = int(headers.get("content-length", "0"))
if length <= 0:
return None
while len(leftover) < length:
nxt = sys.stdin.buffer.read(length - len(leftover))
if nxt == b"":
return None
leftover += nxt
return json.loads(leftover[:length].decode("utf-8"))
def result(request_id, value):
send({"jsonrpc": "2.0", "id": request_id, "result": value})
def error(request_id, code, message):
send(
{
"jsonrpc": "2.0",
"id": request_id,
"error": {"code": code, "message": message},
}
)
def handle(message):
if message is None or "id" not in message:
return
request_id = message.get("id")
method = message.get("method")
params = message.get("params") or {}
if method == "initialize":
requested = params.get("protocolVersion") or FALLBACK_PROTOCOL
result(
request_id,
{
"protocolVersion": requested,
"capabilities": {"tools": {"listChanged": False}},
"serverInfo": {"name": "gds-assessment-reports", "version": "1.0.0"},
},
)
return
if method == "ping":
result(request_id, {})
return
if method == "tools/list":
result(request_id, {"tools": TOOLS})
return
if method == "tools/call":
try:
text = call_tool(params.get("name"), params.get("arguments"))
except (KeyError, TypeError, ValueError, FileNotFoundError, json.JSONDecodeError) as exc:
result(
request_id,
{
"content": [{"type": "text", "text": str(exc)}],
"isError": True,
},
)
return
result(request_id, {"content": [{"type": "text", "text": text}]})
return
error(request_id, -32601, f"Method not found: {method}")
def main():
print("gds-assessment-reports: waiting for MCP client", file=sys.stderr, flush=True)
while True:
message = read_message()
if message is None:
break
try:
handle(message)
except Exception as exc:
request_id = message.get("id")
if request_id is not None:
error(request_id, -32603, str(exc))
if __name__ == "__main__":
main().tessl-plugin
skills
alpha-assessor-design
alpha-assessor-lead
alpha-assessor-technical
alpha-assessor-user-research
assessment-panel-orchestrator
assessment-report-synthesis
beta-assessor-design
beta-assessor-lead
beta-assessor-technical
beta-assessor-user-research
discovery-assessor-design
discovery-assessor-lead
discovery-assessor-technical
discovery-assessor-user-research
gds-assessment-how-to
evidence-kit
alpha
beta
accessibility
decisions
design
operations
performance
platforms
privacy
research
team
discovery
live
accessibility
decisions
design
operations
performance
privacy
research
team
technology
panel
examples
apply-for-a-blue-badge
alpha
live-assessor-design
live-assessor-lead
live-assessor-technical
live-assessor-user-research