Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from bundle
Operational SendPulse skill with live-verified REST, MCP handshake, and MCP tool-call tests.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
scripts/mcp_call_tool.py
1#!/usr/bin/env python32import argparse3import json4import sys5import urllib.request67DEFAULT_URL = "https://mcp.sendpulse.com/mcp"8DEFAULT_PROTOCOL_VERSION = "2025-03-26"91011def post_json(url: str, payload: dict, headers: dict):12data = json.dumps(payload).encode("utf-8")13req = urllib.request.Request(url, data=data, method="POST")14req.add_header("Content-Type", "application/json")15req.add_header("Accept", "application/json, text/event-stream")16for k, v in headers.items():17req.add_header(k, v)18with urllib.request.urlopen(req, timeout=30) as resp:19raw = resp.read().decode("utf-8")20return resp.status, dict(resp.headers.items()), raw212223def init_session(url: str, sp_id: str, sp_secret: str, protocol_version: str):24common_headers = {25"X-SP-ID": sp_id,26"X-SP-SECRET": sp_secret,27}28initialize_payload = {29"jsonrpc": "2.0",30"id": 1,31"method": "initialize",32"params": {33"protocolVersion": protocol_version,34"capabilities": {},35"clientInfo": {"name": "sendpulse-mcp-tool-call", "version": "1.0"},36},37}38status, headers, body = post_json(url, initialize_payload, common_headers)39init_data = json.loads(body)40session_id = headers.get("Mcp-Session-Id") or headers.get("mcp-session-id")41if status != 200 or not session_id:42raise SystemExit(json.dumps({43"ok": False,44"step": "initialize",45"status": status,46"hasSessionId": bool(session_id),47"body": init_data,48}, indent=2))4950notify_headers = dict(common_headers)51notify_headers["Mcp-Session-Id"] = session_id52notify_payload = {53"jsonrpc": "2.0",54"method": "notifications/initialized",55"params": {},56}57post_json(url, notify_payload, notify_headers)58return notify_headers, init_data, session_id596061def main():62parser = argparse.ArgumentParser(description="Call a SendPulse MCP tool through raw MCP HTTP.")63parser.add_argument("--sp-id", required=True, help="SendPulse MCP/OpenAI ID header value")64parser.add_argument("--sp-secret", required=True, help="SendPulse MCP/OpenAI Secret header value")65parser.add_argument("--tool", required=True, help="Tool name to call")66parser.add_argument("--args-json", default="{}", help="Tool args as JSON object string")67parser.add_argument("--url", default=DEFAULT_URL, help="MCP URL")68parser.add_argument("--protocol-version", default=DEFAULT_PROTOCOL_VERSION, help="MCP protocol version")69parser.add_argument("--pretty", action="store_true", help="Pretty-print JSON")70args = parser.parse_args()7172try:73tool_args = json.loads(args.args_json)74except json.JSONDecodeError as e:75print(f"Invalid --args-json: {e}", file=sys.stderr)76sys.exit(2)77if not isinstance(tool_args, dict):78print("--args-json must decode to a JSON object", file=sys.stderr)79sys.exit(2)8081headers, init_data, session_id = init_session(args.url, args.sp_id, args.sp_secret, args.protocol_version)8283call_payload = {84"jsonrpc": "2.0",85"id": 2,86"method": "tools/call",87"params": {88"name": args.tool,89"arguments": tool_args,90},91}92status, _, body = post_json(args.url, call_payload, headers)93call_data = json.loads(body)9495result = {96"ok": status == 200 and "error" not in call_data,97"initialize": {98"serverInfo": init_data.get("result", {}).get("serverInfo", {}),99"protocolVersion": init_data.get("result", {}).get("protocolVersion"),100"sessionId": session_id,101},102"toolCall": {103"status": status,104"tool": args.tool,105"arguments": tool_args,106"response": call_data,107},108}109110if args.pretty:111print(json.dumps(result, indent=2))112else:113print(json.dumps(result))114115116if __name__ == "__main__":117main()118