AI tools

agentlist.com

AgentList Discovery

Notes an AI agent reads before it works on agentlist.com: what to open, what to call, what breaks. Written by browser-harness contributors after real runs, and kept on GitHub under the MIT license.

Give it to your agent

Any coding agent

Start the task with this line. The Markdown version is the same notes without the page around them.

Read https://browser-use.com/skills/agentlist.md, then <your task>

Browser Harness

Save the files into your workspace and turn domain skills on. The code below calls its helpers, such as http_get and js. What Browser Harness is.

d="${BH_AGENT_WORKSPACE:-$HOME/.config/browser-harness/agent-workspace}/domain-skills/agentlist"
mkdir -p "$d" && cd "$d"
curl -sLO https://raw.githubusercontent.com/browser-use/browser-harness/main/agent-workspace/domain-skills/agentlist/discovery.md
export BH_DOMAIN_SKILLS=1

https://agentlist.com - public directory of skills, agents, MCPs, configs, and paid services. Field-tested with browser-harness on 2026-05-03.

Do This First

Use the public API for read-only discovery. It is faster and returns full listing content without browser UI parsing.

import json
 
listings = json.loads(http_get("https://agentlist.com/api/listings?category=skill&q=github&limit=10"))
for item in listings:
    print(item["title"], item["id"], item["vote_count"])

The web UI is useful for visual verification, voting, signing in with passkeys, and submitting listings.

Public API

All read endpoints below worked without authentication.

import json
 
# Search everything
items = json.loads(http_get("https://agentlist.com/api/listings?q=github&limit=10"))
 
# Filter by category
skills = json.loads(http_get("https://agentlist.com/api/listings?category=skill&q=browser&limit=10"))
agents = json.loads(http_get("https://agentlist.com/api/listings?category=agent&limit=10"))
mcps = json.loads(http_get("https://agentlist.com/api/listings?category=mcp&limit=10"))
configs = json.loads(http_get("https://agentlist.com/api/listings?category=config&limit=10"))
paid = json.loads(http_get("https://agentlist.com/api/listings?category=paid&limit=10"))
 
# Sort and paginate
new_items = json.loads(http_get("https://agentlist.com/api/listings?sort=new&skip=0&limit=20"))
top_items = json.loads(http_get("https://agentlist.com/api/listings?sort=top&limit=20"))
trending_items = json.loads(http_get("https://agentlist.com/api/listings?sort=trending&limit=20"))
 
# Fetch one listing
listing = json.loads(http_get("https://agentlist.com/api/listings/6ea8f4f2-83cf-4625-a3cd-98b49d49a7b2"))

Useful fields seen on listing objects:

  • id, category, title, description, content
  • author_pubkey, vote_count, fetch_count, viewer_has_voted
  • created_at, updated_at, reviewed_at, reviewed_by
  • MCP/config/paid-specific fields may be present: repo_url, api_spec_url, transport, package, tools, target_tool, config_format, filename_hint, api_base_url, pricing_info, payment_method

Raw Skill Content

Use /raw/{id} when you only need the agent-readable content, not metadata.

skill_md = http_get("https://agentlist.com/raw/6ea8f4f2-83cf-4625-a3cd-98b49d49a7b2")

For skill-folder loaders, the hosted skills.agentlist.com path also works:

skill_md = http_get("https://skills.agentlist.com/skill/6ea8f4f2-83cf-4625-a3cd-98b49d49a7b2/SKILL.md")

Detail pages display a "Load in your local or cloud based agent" box with the folder URL:

https://skills.agentlist.com/skill/{id}/

Browser Navigation

new_tab("https://agentlist.com")
wait_for_load()
wait(1)
print(page_info())

The homepage is server-rendered enough to read immediately after load. It contains:

  • Category buttons: Skills, Agents, MCPs, Configs, Paid For
  • Sort controls: Top, New, Trending
  • Search input: input[type=search]
  • Main listing table with headers #, Title, Author, Date, Votes

Extract visible rows:

import json
 
rows = json.loads(js(r"""
JSON.stringify(Array.from(document.querySelectorAll("table:first-of-type tbody tr")).map(tr => {
  const cells = Array.from(tr.children).map(td => td.innerText.trim());
  return {
    rank: cells[0],
    title_description: cells[1],
    author: cells[2],
    date: cells[3],
    votes: cells[4]
  };
}))
"""))

Listing title links use /listing/{uuid}:

links = json.loads(js(r"""
JSON.stringify(Array.from(document.querySelectorAll('a[href^="/listing/"], a[href*="/listing/"]')).map(a => ({
  text: a.innerText.trim(),
  href: a.href
})))
"""))

UI Search Gotcha

fill_input("input[type=search]", "github") double-entered characters during testing (github became ggiitthhuubb). Prefer direct JS value assignment plus an input event, or use the API.

js("""
const input = document.querySelector('input[type=search]');
input.value = 'github';
input.dispatchEvent(new Event('input', {bubbles: true}));
""")
wait(1)
print(page_info())  # URL becomes https://agentlist.com/?q=github

Listing Detail Pages

Detail URL pattern:

https://agentlist.com/listing/{id}

On skill detail pages, the rendered content includes raw code blocks, API notes, discussion, and a load URL. Detail pages also keep the homepage table lower on the page, so use scoped selectors when extracting the detail body.

new_tab("https://agentlist.com/listing/6ea8f4f2-83cf-4625-a3cd-98b49d49a7b2")
wait_for_load()
wait(1)
 
data = json.loads(js(r"""
JSON.stringify({
  title: document.querySelector(".listing-title, h2")?.innerText || null,
  load_urls: Array.from(document.querySelectorAll("code")).map(e => e.innerText.trim()).filter(t => t.includes("skills.agentlist.com/skill/")),
  code_blocks: Array.from(document.querySelectorAll("pre, code")).map(e => e.innerText.slice(0, 300)).slice(0, 20)
})
"""))

The top-right user pill and Sign out button can appear when already authenticated. Do not assume a logged-out session.

Submit Page

Submit URL:

https://agentlist.com/submit

Observed fields:

  • #title - title input
  • #description - short description input
  • input[type=url] - GitHub import URL
  • first textarea - Markdown content
  • #scripts_index_html - optional JavaScript skill payload

Observed category buttons:

buttons = json.loads(js(r"""
JSON.stringify(Array.from(document.querySelectorAll("button.category-btn")).map(b => ({
  text: b.innerText.trim(),
  selected: b.classList.contains("selected")
})))
"""))

The GitHub import button is button.btn.btn-secondary with text Import. The final submit button has text Submit listing. Submissions are signed with the user's Nostr identity; do not submit or vote unless the user explicitly asked for that exact action.

discovery.md · updated May 3, 2026 · 571 words

Sites change. These notes describe what worked when they were last updated, not a guarantee they still do. If something broke, the fix is a pull request to browser-harness.