Skip to main content
Glama

search_issuers_by_state

Retrieve all municipal issuers for a given state, with optional name filtering, to obtain issuer IDs for detailed bond or profile queries.

Instructions

List municipal issuers for a state (all 9k+ rows, not just page 1) and filter by name. Feed the issuer_id into get_issuer_outstanding_bonds or get_issuer_profile to drill down. IMPORTANT: the contains filter auto-expands your query into every EMMA abbreviation variant (hospital→HOSP, authority→AUTH, children→CHLDN, senior→SR, community→CMNTY, etc.) — always search in natural English, never in the telegraphic form. For hospital/senior-living/charter-school obligors, search the CONDUIT, not the obligor: e.g. CHLA bonds are filed under CALIFORNIA PUB FIN AUTH HEALTH CARE FACS REV — found by searching contains="health care facilities". See the emma://rules/EMMA_ABBREVIATIONS.md resource for the full variant map and conduit cheat sheet.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
stateYes2-letter state code
containsNoCase-insensitive name filter
limitNo

Implementation Reference

  • The main handler for the 'search_issuers_by_state' tool. Uses Playwright to navigate to EMMA's IssuerHomePage/State page, waits for the DataTable to fully load (including all client-side rows), extracts all issuer records (issuer_id, name, type, url) via jQuery DataTable API in the browser, then filters by the optional 'contains' parameter using the abbreviation-aware _query_patterns helper. Returns issuers up to the 'limit'.
    if name == "search_issuers_by_state":
        state = args["state"].strip().upper()
        async with new_page() as page:
            await page.goto(f"{EMMA_BASE}/IssuerHomePage/State?state={state}",
                            wait_until=NAV_WAIT, timeout=NAV_TIMEOUT_MS)
            # DataTable loads 9k+ rows client-side; wait for the table instance
            await page.wait_for_function(
                "() => { const j=window.jQuery||window.$; "
                "return !!(j && j('#lvIssuers').DataTable && "
                "j('#lvIssuers').DataTable().rows().count() > 0); }",
                timeout=20000,
            )
            rows = await page.evaluate(
                """() => {
                    const jq = window.jQuery || window.$;
                    const dt = jq('#lvIssuers').DataTable();
                    return dt.rows().data().toArray().map(r => ({
                        issuer_id: r.id,
                        name: r.nm || '',
                        issuer_type: r.itp || '',
                        url: 'https://emma.msrb.org/IssuerHomePage/Issuer?id=' + r.id + '&type=' + (r.tp || 'M'),
                    }));
                }"""
            )
        total_in_state = len(rows)
        contains = (args.get("contains") or "").strip()
        expanded_terms: list[str] = []
        if contains:
            patterns = _query_patterns(contains)
            expanded_terms = [p.pattern for p in patterns]
            rows = [i for i in rows if _match_query(i["name"], patterns)]
        limit = int(args.get("limit", 50))
        return {
            "state": state,
            "total_in_state": total_in_state,
            "count": len(rows),
            "query_expansion_notes": (
                "EMMA uses legacy abbreviations (HOSP, AUTH, CHLDN, CMNTY, "
                "FING, REV). Query tokens are expanded to known variants."
                if expanded_terms else None
            ),
            "expanded_regex": expanded_terms or None,
            "issuers": rows[:limit],
        }
  • server.py:506-534 (registration)
    Tool registration with name 'search_issuers_by_state', input schema (state required, contains optional, limit default 50), and description in the list_tools() function.
        name="search_issuers_by_state",
        description=(
            "List municipal issuers for a state (all 9k+ rows, not just "
            "page 1) and filter by name. Feed the issuer_id into "
            "get_issuer_outstanding_bonds or get_issuer_profile to drill "
            "down. IMPORTANT: the `contains` filter auto-expands your "
            "query into every EMMA abbreviation variant (hospital→HOSP, "
            "authority→AUTH, children→CHLDN, senior→SR, community→CMNTY, "
            "etc.) — always search in natural English, never in the "
            "telegraphic form. For hospital/senior-living/charter-school "
            "obligors, search the CONDUIT, not the obligor: e.g. CHLA "
            "bonds are filed under CALIFORNIA PUB FIN AUTH HEALTH CARE "
            "FACS REV — found by searching `contains=\"health care "
            "facilities\"`. See the emma://rules/EMMA_ABBREVIATIONS.md "
            "resource for the full variant map and conduit cheat sheet."
        ),
        inputSchema={
            "type": "object",
            "properties": {
                "state": {"type": "string", "description": "2-letter state code"},
                "contains": {
                    "type": "string",
                    "description": "Case-insensitive name filter",
                },
                "limit": {"type": "integer", "default": 50},
            },
            "required": ["state"],
        },
    ),
  • Helper functions _query_patterns and _match_query used by search_issuers_by_state to expand user queries into EMMA abbreviation variants and filter issuer names with AND semantics.
    def _query_patterns(query: str) -> list[re.Pattern[str]]:
        """Tokenize a query and return one regex per token, each matching any
        known variant of that word. All patterns must match (AND across tokens)."""
        if not query:
            return []
        # Collapse whitespace, strip punctuation except hyphens
        clean = re.sub(r"[^\w\s-]+", " ", query.lower()).strip()
        tokens = [t for t in clean.split() if t]
        patterns: list[re.Pattern[str]] = []
        for tok in tokens:
            variants: set[str] = {tok}
            if tok in MUNI_ABBREV:
                variants.update(MUNI_ABBREV[tok])
            if tok in _ABBREV_REVERSE:
                for long_form in _ABBREV_REVERSE[tok]:
                    variants.update(MUNI_ABBREV.get(long_form, []))
            # Escape each variant and combine. Word-boundary on each side so "ca"
            # doesn't match inside "cares" but does match "CA HOSP" or " CA ".
            parts = sorted(variants, key=len, reverse=True)
            pat = r"(?<![A-Za-z])(?:" + "|".join(re.escape(v) for v in parts) + r")(?![A-Za-z])"
            patterns.append(re.compile(pat, re.IGNORECASE))
        return patterns
    
    
    def _match_query(name: str, patterns: list[re.Pattern[str]]) -> bool:
        return all(p.search(name) for p in patterns)
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden of behavioral disclosure. It reveals important traits: the tool returns all rows (not paginated), the contains filter auto-expands to abbreviation variants, and conduit search rules. It is transparent about these behaviors.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is relatively long but well-structured. It front-loads the main purpose, then provides usage guidance, warnings, and examples. Every sentence adds value, though it could be slightly more concise without losing critical details.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the complexity (3 params, no output schema, no annotations), the description covers the tool's purpose, parameter behavior, usage context, and links to downstream tools. It lacks explicit mention of the output format, but the sibling tools and the purpose imply the issuer_id and other fields.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema coverage is 67% (state and contains described, limit not), but the description adds substantial value beyond the schema. It explains how the contains filter works (auto-expansion, natural English requirement) and gives conduit examples, which is critical context that the schema alone does not provide.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's function: 'List municipal issuers for a state' and differentiates it from siblings by noting that results include all rows (not just page 1) and that the output can be fed into get_issuer_outstanding_bonds or get_issuer_profile.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides explicit guidance on when to use this tool (to list issuers for a state and filter by name) and when not to use it (for hospital/senior-living/charter-school obligors, search the conduit instead). It also gives detailed examples and references an external resource for abbreviation variants.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/ark9164-create/blaylock-emma-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server