gumcp-client — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited gumcp-client (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
⚠️ Deprecated — will be removed soon. For new code use the gumloop-sdk skill.gumcp_clientstill works, so existing/saved scripts keep running; seeMIGRATION.mdto port one.
Only when you hit existing get_client() / gumcp_client code in a saved script or trigger you must read or maintain. Never write new gumcp_client — use the gumloop-sdk skill for all new work; see MIGRATION.md to port an old script.
The gumcp_client package is pre-installed and get_client() is automatically available in every sandbox Python execution — you do not need to import or define it.
import json
# get_client() is pre-defined — just use it directly
with get_client() as client:
raw = client.call_tool("slack__list_channels", {})
channels = [json.loads(item) for item in raw]State persistence: Imports, data variables, and function/class definitions all persist between sandbox executions. You can define a helper in one call and use it in the next.
call_tool() returns list[str] where each string is a JSON-encoded result item. Some tools return one item, others return multiple (e.g. Gmail returns one per email). Always parse every item:
with get_client() as client:
raw = client.call_tool("gmail__read_emails", {"max_results": 5})
results = [json.loads(item) for item in raw]
# results is now a list of parsed dicts -- one per email
for email in results:
print(email["subject"])For single-result tools, the list has one item:
with get_client() as client:
raw = client.call_tool("slack__send_message", {
"channel": "#general",
"text": "Hello from the sandbox!"
})
result = json.loads(raw[0]) # single result
print(result)Tool slugs use the server__tool_name format (double underscore): slack__send_message, gmail__read_emails, gsheets__read_spreadsheet.
Prefer the top-level tool_discovery tool for discovery. Use list_tools() only inside a script that is already running for custom processing.
list_tools() returns a dict with a "tools" key containing a list of tool definitions:
with get_client() as client:
result = client.list_tools()
for tool in result["tools"]:
print(tool["name"], "-", tool["description"])Discovery is done with the top-level tool_discovery tool, NOT the sandbox. Do not enter the sandbox to list tools or find servers. These helper scripts remain only as a legacy live fallback, usable when tool_discovery returns nothing for a server visible in <servers> (e.g. a still-indexing gumstack/custom server):
# List all available tools across connected integrations
python3 /home/user/skills/gumcp-client/scripts/list_tools.py
# Call a tool directly from the command line
python3 /home/user/skills/gumcp-client/scripts/call_tool.py slack__send_message '{"channel": "#general", "text": "Hello!"}'
# List resources on a server
python3 /home/user/skills/gumcp-client/scripts/list_resources.pyServer IDs in call_tool() must match the actual server ID, not the display name. Use the top-level tool_discovery tool to discover the exact server IDs. Some common non-obvious mappings: Google BigQuery = gbigquery, Google Calendar = gcalendar, Google Sheets = gsheets, Google Docs = gdocs, Google Drive = gdrive.
Only use server IDs that are listed as connected in your environment.
Never write processing logic against a response you haven't seen. Tool responses vary wildly in structure -- nested objects, lists of dicts, unexpected field names. Writing a batch script blind leads to key errors and wasted executions.
Step 1: Explore the response shape with a single call.
with get_client() as client:
raw = client.call_tool("apollo__enrich_person", {"email": "[email protected]"})
sample = json.loads(raw[0])
print(json.dumps(sample, indent=2))Step 2: Now that you know the field paths, write targeted extraction.
with get_client() as client:
contacts = ["[email protected]", "[email protected]", "[email protected]"]
for email in contacts:
raw = client.call_tool("apollo__enrich_person", {"email": email})
data = json.loads(raw[0])
# You know these paths exist because you inspected the response
print(f"{email}: {data['person']['title']} at {data['person']['organization']['name']}")This matters most for batch operations -- if you're processing 50 items and your field path is wrong, you waste the entire run. Inspect one, then process many.
Before writing a script, decompose the user's request into ordered steps with dependencies:
Example: "Fetch open PRs from GitHub and post a summary to Slack #engineering"
All patterns below use get_client() which is automatically available (see Import and Setup above).
Use when a tool returns paged results (look for next_cursor, next_page_token, or offset in responses).
with get_client() as client:
all_items = []
cursor = None
while True:
args = {"per_page": 100}
if cursor:
args["cursor"] = cursor
raw = client.call_tool("github__list_issues", args)
data = json.loads(raw[0])
items = data.get("issues", [])
all_items.extend(items)
cursor = data.get("next_cursor")
if not cursor or not items:
break
print(f"Fetched {len(all_items)} total items")Use when processing many items (50+) where partial failure shouldn't lose progress.
CHECKPOINT = "/home/user/processed.json"
def load_checkpoint():
try:
with open(CHECKPOINT) as f:
return set(json.load(f))
except FileNotFoundError:
return set()
def save_checkpoint(done):
with open(CHECKPOINT, "w") as f:
json.dump(list(done), f)
with get_client() as client:
done = load_checkpoint()
contacts = [...] # your full list
for email in contacts:
if email in done:
continue
try:
client.call_tool("gmail__send_email", {"to": email, "subject": "Update", "body": "..."})
done.add(email)
save_checkpoint(done)
except Exception as e:
print(f"Failed {email}: {e}")
print(f"Completed {len(done)}/{len(contacts)}")Use when calling tools that may intermittently fail (rate limits, transient errors).
import time
def call_with_retry(client, tool, args, max_retries=3):
for attempt in range(max_retries):
try:
raw = client.call_tool(tool, args)
return json.loads(raw[0])
except Exception as e:
if attempt == max_retries - 1:
raise
wait = 2 ** attempt
print(f"Retry {attempt + 1}/{max_retries} after {wait}s: {e}")
time.sleep(wait)
with get_client() as client:
data = call_with_retry(client, "apollo__enrich_person", {"email": "[email protected]"})Use when making many independent calls (e.g., enriching a list of contacts) where order doesn't matter.
from concurrent.futures import ThreadPoolExecutor, as_completed
with get_client() as client:
emails = ["[email protected]", "[email protected]", "[email protected]"]
def enrich(email):
raw = client.call_tool("apollo__enrich_person", {"email": email})
return email, json.loads(raw[0])
results = {}
with ThreadPoolExecutor(max_workers=5) as pool:
futures = {pool.submit(enrich, e): e for e in emails}
for future in as_completed(futures):
try:
email, data = future.result()
results[email] = data
except Exception as e:
print(f"Failed {futures[future]}: {e}")
print(f"Enriched {len(results)}/{len(emails)}")pip install for Python packages.Runnable helper scripts for quick tool discovery and execution from the shell.
Full API reference for the Client class, including all methods, error handling, and advanced patterns.
gumcp_client package is pip-installed and available globally.server__tool_name format (double underscore) for routing.tool_discovery first to find available tool names and their required arguments.with statement or call client.close() to clean up connections.~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.