#!/usr/bin/env python3 import base64, csv, hashlib, html, io, json, re, sys, uuid from datetime import datetime, timezone, timedelta from urllib.parse import quote, unquote, urlparse, parse_qs, urlencode from pathlib import Path ROOT = Path(__file__).resolve().parent MANIFEST = json.loads((ROOT / "toolbox-manifest.json").read_text()) PAYMENT = json.loads((ROOT / "payment-required.json").read_text()) TOOLS = [(item["name"], item["description"]) for item in MANIFEST["tools"]] def respond(msg_id, result=None, error=None): payload = {"jsonrpc":"2.0", "id":msg_id} if error: payload["error"] = error else: payload["result"] = result print(json.dumps(payload, separators=(",", ":")), flush=True) def text_result(value): return {"content":[{"type":"text", "text":str(value)}]} def slug(text, sep="-"): return re.sub(r"[^a-zA-Z0-9]+", sep, text.strip().lower()).strip(sep) def j(text): return json.loads(text or "null") def records(text): data = j(text) if not isinstance(data, list): raise ValueError("Expected JSON array") return data def csv_records(text, delimiter=","): return list(csv.DictReader(io.StringIO(text), delimiter=delimiter)) def records_to_delimited(rows, delimiter=","): fields = sorted({k for row in rows for k in row.keys()}) buf = io.StringIO(); writer = csv.DictWriter(buf, fieldnames=fields, delimiter=delimiter) writer.writeheader(); writer.writerows(rows) return buf.getvalue().strip() def schema_type(value): if isinstance(value, bool): return "boolean" if isinstance(value, int) or isinstance(value, float): return "number" if isinstance(value, list): return "array" if isinstance(value, dict): return "object" if value is None: return "null" return "string" def call_tool(name, args): args = args or {}; text = args.get("text", "") n = int(args.get("n", args.get("limit", 10))) sep = args.get("separator", "\n") if name == "slugify": return text_result(slug(text)) if name == "snake_case": return text_result(slug(text, "_")) if name == "kebab_case": return text_result(slug(text, "-")) if name == "title_case": return text_result(text.title()) if name == "upper": return text_result(text.upper()) if name == "lower": return text_result(text.lower()) if name == "reverse_text": return text_result(text[::-1]) if name == "normalize_spaces": return text_result(re.sub(r"\s+", " ", text).strip()) if name == "dedupe_lines": seen=[] for line in text.splitlines(): if line not in seen: seen.append(line) return text_result("\n".join(seen)) if name == "sort_lines": return text_result("\n".join(sorted(text.splitlines()))) if name == "trim_lines": return text_result("\n".join(line.strip() for line in text.splitlines())) if name == "count_words": return text_result(json.dumps({"words":len(re.findall(r"\S+", text)), "characters":len(text)})) if name == "truncate": return text_result(text[:n]) if name == "prefix_lines": return text_result("\n".join(args.get("prefix", "") + line for line in text.splitlines())) if name == "suffix_lines": return text_result("\n".join(line + args.get("suffix", "") for line in text.splitlines())) if name == "number_lines": return text_result("\n".join(f"{i+1}. {line}" for i, line in enumerate(text.splitlines()))) if name == "extract_urls": return text_result(json.dumps(re.findall(r"https?://[^\s)\]>]+", text), indent=2)) if name == "extract_emails": return text_result(json.dumps(re.findall(r"[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}", text), indent=2)) if name == "extract_hashtags": return text_result(json.dumps(re.findall(r"#[\w-]+", text), indent=2)) if name == "extract_mentions": return text_result(json.dumps(re.findall(r"@[\w.-]+", text), indent=2)) if name == "regex_find": return text_result(json.dumps(re.findall(args.get("pattern", ""), text), indent=2)) if name == "regex_replace": return text_result(re.sub(args.get("pattern", ""), args.get("replacement", ""), text)) if name == "split_text": return text_result(json.dumps(text.split(args.get("separator", "\n")), indent=2)) if name == "join_items": return text_result(args.get("separator", "\n").join(str(x) for x in j(text))) if name == "json_pretty": return text_result(json.dumps(j(text), indent=2, ensure_ascii=False)) if name == "json_minify": return text_result(json.dumps(j(text), separators=(",", ":"), ensure_ascii=False)) if name == "json_keys": return text_result(json.dumps(list(j(text).keys()), indent=2)) if name == "json_get": return text_result(json.dumps(j(text).get(args.get("key")), indent=2)) if name == "json_set": data=j(text); data[args.get("key")] = args.get("value"); return text_result(json.dumps(data, indent=2)) if name == "json_array_length": return text_result(len(j(text))) if name == "json_filter_keys": data=j(text); keys=args.get("keys", []); return text_result(json.dumps({k:data.get(k) for k in keys}, indent=2)) if name == "json_merge": data=j(text); other=args.get("other", {}); data.update(other); return text_result(json.dumps(data, indent=2)) if name == "csv_to_json": return text_result(json.dumps(csv_records(text), indent=2, ensure_ascii=False)) if name == "json_to_csv": return text_result(records_to_delimited(records(text))) if name == "tsv_to_json": return text_result(json.dumps(csv_records(text, "\t"), indent=2, ensure_ascii=False)) if name == "json_to_tsv": return text_result(records_to_delimited(records(text), "\t")) if name == "csv_headers": return text_result(json.dumps(next(csv.reader(io.StringIO(text))), indent=2)) if name == "csv_column": col=args.get("column"); return text_result(json.dumps([r.get(col, "") for r in csv_records(text)], indent=2)) if name == "base64_encode": return text_result(base64.b64encode(text.encode()).decode()) if name == "base64_decode": return text_result(base64.b64decode(text).decode()) if name == "hex_encode": return text_result(text.encode().hex()) if name == "hex_decode": return text_result(bytes.fromhex(text.strip()).decode()) if name == "sha256": return text_result(hashlib.sha256(text.encode()).hexdigest()) if name == "sha1": return text_result(hashlib.sha1(text.encode()).hexdigest()) if name == "md5": return text_result(hashlib.md5(text.encode()).hexdigest()) if name == "uuid4": return text_result(str(uuid.uuid4())) if name == "url_encode": return text_result(quote(text)) if name == "url_decode": return text_result(unquote(text)) if name == "parse_url": u=urlparse(text); return text_result(json.dumps({"scheme":u.scheme,"netloc":u.netloc,"path":u.path,"query":u.query,"fragment":u.fragment}, indent=2)) if name == "build_query": return text_result(urlencode(j(text))) if name == "parse_query": return text_result(json.dumps(parse_qs(text.lstrip("?")), indent=2)) if name == "html_escape": return text_result(html.escape(text)) if name == "html_unescape": return text_result(html.unescape(text)) if name == "strip_html_tags": return text_result(re.sub(r"<[^>]+>", "", text)) if name == "markdown_to_plain": return text_result(re.sub(r"[`*_>#-]", "", re.sub(r"\[([^\]]+)\]\([^)]+\)", r"\1", text))) if name == "strip_markdown_links": return text_result(re.sub(r"\[([^\]]+)\]\([^)]+\)", r"\1", text)) if name == "markdown_table": rows=records(text); fields=sorted({k for row in rows for k in row.keys()}); lines=["| "+" | ".join(fields)+" |", "| "+" | ".join(["---"]*len(fields))+" |"] lines += ["| "+" | ".join(str(row.get(f,"")) for f in fields)+" |" for row in rows] return text_result("\n".join(lines)) if name == "markdown_checklist": items = j(text) if text.strip().startswith("[") else text.splitlines(); return text_result("\n".join(f"- [ ] {x}" for x in items)) if name == "markdown_link": return text_result(f"[{args.get('label', text)}]({args.get('url', '')})") if name == "markdown_code_fence": return text_result(f"```{args.get('language','')}\n{text}\n```") if name == "color_hex_to_rgb": v=text.strip().lstrip("#"); return text_result(json.dumps({"r":int(v[0:2],16),"g":int(v[2:4],16),"b":int(v[4:6],16)})) if name == "rgb_to_hex": return text_result(f"#{int(args.get('r',0)):02x}{int(args.get('g',0)):02x}{int(args.get('b',0)):02x}") if name == "color_invert_hex": v=text.strip().lstrip("#"); return text_result(f"#{255-int(v[0:2],16):02x}{255-int(v[2:4],16):02x}{255-int(v[4:6],16):02x}") if name == "timestamp_now": return text_result(datetime.now(timezone.utc).isoformat()) if name == "date_today": return text_result(datetime.now(timezone.utc).date().isoformat()) if name == "iso_to_unix": return text_result(int(datetime.fromisoformat(text.replace('Z','+00:00')).timestamp())) if name == "unix_to_iso": return text_result(datetime.fromtimestamp(float(text), timezone.utc).isoformat()) if name == "add_days": return text_result((datetime.fromisoformat(text) + timedelta(days=int(args.get('days', 0)))).date().isoformat()) if name == "list_unique": out=[] for x in j(text): if x not in out: out.append(x) return text_result(json.dumps(out, indent=2)) if name == "list_sort": return text_result(json.dumps(sorted(j(text)), indent=2)) if name == "list_reverse": return text_result(json.dumps(list(reversed(j(text))), indent=2)) if name == "list_take": return text_result(json.dumps(j(text)[:n], indent=2)) if name == "list_drop": return text_result(json.dumps(j(text)[n:], indent=2)) if name == "list_chunk": data=j(text); return text_result(json.dumps([data[i:i+n] for i in range(0,len(data),n)], indent=2)) if name == "list_count_values": counts={} for x in j(text): counts[str(x)] = counts.get(str(x), 0) + 1 return text_result(json.dumps(counts, indent=2)) if name == "key_value_to_json": out={} for line in text.splitlines(): if "=" in line: k,v=line.split("=",1); out[k.strip()]=v.strip() return text_result(json.dumps(out, indent=2)) if name == "json_to_key_value": return text_result("\n".join(f"{k}={v}" for k,v in j(text).items())) if name == "env_example_from_json": return text_result("\n".join(f"{k.upper()}=" for k in j(text).keys())) if name == "robots_txt": return text_result("User-agent: *\nAllow: /\n") if name == "sitemap_urlset": return text_result(""+"".join(f"{html.escape(u)}" for u in j(text))+"") if name == "http_headers_from_text": return text_result(json.dumps(dict(line.split(":",1) for line in text.splitlines() if ":" in line), indent=2)) if name == "openapi_minimal": return text_result(json.dumps({"openapi":"3.1.0","info":{"title":text or "API","version":"1.0.0"},"paths":{}}, indent=2)) if name == "json_schema_from_example": return text_result(json.dumps({"type":"object","properties":{k:{"type":schema_type(v)} for k,v in j(text).items()}}, indent=2)) if name == "package_json_minimal": return text_result(json.dumps({"name":slug(text or "package"),"version":"1.0.0","type":"module"}, indent=2)) if name == "python_requirements_sort": return text_result("\n".join(sorted(x for x in text.splitlines() if x.strip()))) if name == "gitignore_basic": return text_result("__pycache__/\n.env\nnode_modules/\ndist/\n.DS_Store\n") if name == "dockerfile_python": return text_result("FROM python:3.12-slim\nWORKDIR /app\nCOPY . .\nCMD [\"python3\", \"app.py\"]\n") if name == "readme_skeleton": return text_result(f"# {text or 'Project'}\n\n## Install\n\n## Usage\n\n## License\n") if name == "changelog_entry": return text_result(f"## {datetime.now(timezone.utc).date().isoformat()}\n\n- {text or 'Changed'}\n") if name == "semver_bump": major,minor,patch=[int(x) for x in text.split('.')]; part=args.get('part','patch') if part=='major': major,minor,patch=major+1,0,0 elif part=='minor': minor,patch=minor+1,0 else: patch+=1 return text_result(f"{major}.{minor}.{patch}") if name == "agent_manifest_minimal": return text_result(json.dumps({"name":text or "agent-resource","version":"1.0.0","tools":[]}, indent=2)) if name == "payment_required": return text_result(json.dumps(PAYMENT, indent=2)) if name == "purchase_offer_minimal": return text_result(json.dumps({"price":{"amount":"1","currency":"USDT","network":"BSC/BEP20"},"deliverable":{"name":text or "resource"}}, indent=2)) if name == "nostr_tags_basic": return text_result(json.dumps([["t", x.strip()] for x in text.split(',') if x.strip()], indent=2)) if name == "jsonld_dataset_minimal": return text_result(json.dumps({"@context":"https://schema.org","@type":"Dataset","name":text or "Dataset"}, indent=2)) if name == "mcp_tool_schema": return text_result(json.dumps({"name":slug(text or "tool", "_"),"description":text or "Tool","inputSchema":{"type":"object","properties":{}}}, indent=2)) if name == "prompt_template_fill": data=args.get('vars', {}) return text_result(re.sub(r"\{\{\s*([a-zA-Z0-9_]+)\s*\}\}", lambda m: str(data.get(m.group(1), m.group(0))), text)) if name == "prompt_variables": return text_result(json.dumps(sorted(set(re.findall(r"\{\{\s*([a-zA-Z0-9_]+)\s*\}\}", text))), indent=2)) if name == "estimate_tokens_rough": return text_result(max(1, len(text)//4)) if name == "safe_filename": return text_result(slug(text)[:120] or "file") raise ValueError(f"Unknown tool: {name}") def tool_schema(name, description): return {"name":name,"description":description,"inputSchema":{"type":"object","properties":{"text":{"type":"string"},"pattern":{"type":"string"},"replacement":{"type":"string"},"separator":{"type":"string"},"key":{"type":"string"},"value":{},"other":{"type":"object"},"keys":{"type":"array","items":{"type":"string"}},"column":{"type":"string"},"n":{"type":"integer"},"limit":{"type":"integer"},"prefix":{"type":"string"},"suffix":{"type":"string"},"r":{"type":"integer"},"g":{"type":"integer"},"b":{"type":"integer"},"days":{"type":"integer"},"part":{"type":"string"},"vars":{"type":"object"},"label":{"type":"string"},"url":{"type":"string"},"language":{"type":"string"}}}} def main(): for line in sys.stdin: if not line.strip(): continue try: msg=json.loads(line); msg_id=msg.get('id'); method=msg.get('method'); params=msg.get('params') or {} if method=='initialize': respond(msg_id, {"protocolVersion":"2024-11-05","capabilities":{"tools":{}},"serverInfo":{"name":"agent-toolbox-100","version":"1.0.0"}}) elif method=='tools/list': respond(msg_id, {"tools":[tool_schema(n,d) for n,d in TOOLS]}) elif method=='tools/call': respond(msg_id, call_tool(params.get('name'), params.get('arguments') or {})) elif method=='notifications/initialized': continue else: respond(msg_id, error={"code":-32601,"message":f"Method not found: {method}"}) except Exception as exc: respond(None, error={"code":-32000,"message":str(exc)}) if __name__ == '__main__': main()