Metadata-Version: 2.4
Name: agentladle-mcp-hkexnews
Version: 0.1.0
Summary: MCP server for HKEXnews Hong Kong announcement download, parsing and search
Project-URL: Homepage, https://github.com/agentladle/mcp-hkexnews
Project-URL: Repository, https://github.com/agentladle/mcp-hkexnews
Project-URL: Issues, https://github.com/agentladle/mcp-hkexnews/issues
Author: AgentLadle
License-Expression: MIT
License-File: LICENSE
Keywords: announcements,hkex,hkexnews,hong-kong,mcp
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Financial and Insurance Industry
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Office/Business :: Financial
Requires-Python: >=3.10
Requires-Dist: beautifulsoup4>=4.12.0
Requires-Dist: httpx>=0.27.0
Requires-Dist: mcp[cli]>=1.0.0
Requires-Dist: openpyxl>=3.1.0
Requires-Dist: pymupdf>=1.24.0
Requires-Dist: pyyaml>=6.0
Description-Content-Type: text/markdown

# AgentLadle MCP HKEXnews

**English** | [中文](README_zh.md)

> 🇨🇳 **China A-Share Annual Reports** — Cloud-hosted MCP for Shanghai & Shenzhen listed companies. [Read more](Chinese-A-share-MCP-README.md) | [Get API Key](https://agentladle.com/register)

A [MCP (Model Context Protocol)](https://modelcontextprotocol.io/) server that provides tools for **discovering, downloading, parsing, and searching** Hong Kong listed company announcements from [HKEXnews](https://www.hkexnews.hk).

It enables AI assistants (Claude, Cursor, etc.) to access HKEXnews announcement data through 6 structured tools — from discovering available announcements to keyword-searching within their pages.

> **Scope (v0.1):** Announcements and disclosures except full periodic report PDFs (Annual / Interim / Quarterly Report and ESG Report under `t1=40000`). Performance announcements (Final / Interim / Quarterly Results) are included.

## Features

- **6 MCP tools** for HKEXnews announcement data: state-driven retrieval (search directly, fallback to download/parse only when needed)
- **PDF document parsing** using [PyMuPDF](https://pymupdf.readthedocs.io/) — physical page extraction into page-split JSON
- **Local keyword search** with TF + position-boost scoring, zero external search dependencies
- **Idempotent** — already-downloaded/parsed files are automatically skipped
- **Zero-config install** — one line to add to your MCP client, no clone or manual setup needed
- **Pure Python**, cross-platform (Windows / macOS / Linux)

## Prerequisites

- **Python 3.10+** — [Download Python](https://www.python.org/downloads/)
- **uv** — [Install uv](https://docs.astral.sh/uv/getting-started/installation/)

> **Note:** After installing uv, restart your terminal and MCP client (e.g. Cherry Studio) to ensure the `uv` command is recognized.

## Quick Start

Add to your MCP client configuration (Claude Desktop, Cursor, etc.):

```json
{
  "mcpServers": {
    "mcp-hkexnews": {
      "command": "uvx",
      "args": ["agentladle-mcp-hkexnews"]
    }
  }
}
```

That's it. `uvx` will automatically download the package and its dependencies from PyPI — no clone, no manual install, no path configuration.

### Alternative: pip install

If you prefer managing the environment yourself:

```bash
pip install agentladle-mcp-hkexnews
```

Then configure:

```json
{
  "mcpServers": {
    "mcp-hkexnews": {
      "command": "agentladle-mcp-hkexnews"
    }
  }
}
```

### Alternative: Run from source (local development)

Clone the repository and run directly:

```bash
git clone https://github.com/agentladle/mcp-hkexnews.git
```

Then configure your MCP client:

```json
{
  "mcpServers": {
    "mcp-hkexnews": {
      "command": "uv",
      "args": ["run", "--directory", "/path/to/mcp-hkexnews", "agentladle-mcp-hkexnews"]
    }
  }
}
```

Replace `/path/to/mcp-hkexnews` with the actual path to the cloned repository.

## Data Flow

```
HKEXnews API                      Local Files (~/.agentladle/mcp-hkexnews/data/)
──────────────                    ──────────────────────────────
activestock_sehk_e.json  ──→     companies.json               (stock_code→stockId mapping)
ListOfSecurities.xlsx      ──→         │
tierone/tiertwo JSON       ──→     tiers.json                   (headline category mapping)
                                     │
titleSearchServlet.do    ──→        pdf/{LOCAL_KEY}/            (Tool 2: primary PDF/HTML + manifest)
                                     │
PyMuPDF parsing          ──→        json/*.json                 (Tool 3: parse, page-split)
                                     │
Local TF search          ──→        search results              (Tool 4: keyword search)
Page range read          ──→        page content                (Tool 5: read pages)
```

## Tools

| # | Tool | Description |
|---|------|-------------|
| 1 | `list_hkexnews_announcements` | Discover available HKEXnews announcements for a company |
| 2 | `download_hkexnews_announcement` | Download announcement PDF (HTML fallback); idempotent |
| 3 | `parse_hkexnews_announcement` | Parse PDF/HTML into page-split JSON using PyMuPDF |
| 4 | `keyword_search` | Full-text keyword search with TF relevance scoring |
| 5 | `get_announcement_pages` | Read announcement content by page number range |
| 6 | `lookup_stock_code` | **Diagnostic**: look up stock_code→stockId mapping when resolution fails |

### Tool 1: `list_hkexnews_announcements`

List available HKEXnews announcements for a company. Use this tool ONLY when the exact date/title is unspecified by the user, or when a download attempt fails due to an ambiguous match. Excludes full periodic report PDFs (Annual / Interim / Quarterly Report and ESG Report under `t1=40000`).

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `stock_code` | string | ✅ | 5-digit HK stock code, e.g. `"00700"` |
| `category` | string | ❌ | HKEX t1/t2 code or tier name, e.g. `"Inside Information"`, `"13500"`, `"20000"`. Omit to list all in-scope categories |
| `start_date` | string | ❌ | Start date `YYYY-MM-DD` |
| `end_date` | string | ❌ | End date `YYYY-MM-DD` |
| `title_keyword` | string | ❌ | Title keyword filter |
| `limit` | int | ❌ | Max announcements to return, default 10, max 50 |

### Tool 2: `download_hkexnews_announcement`

Download a specific HKEXnews announcement from www1.hkexnews.hk. Prefer `local_key` from `list_hkexnews_announcements` when available. Idempotent.

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `stock_code` | string | ✅ | 5-digit HK stock code, e.g. `"00700"` |
| `release_date` | string | ❌ | Release date `YYYY-MM-DD` (optional if `local_key` provided) |
| `title_keyword` | string | ❌ | Title substring to disambiguate same-day announcements |
| `category` | string | ❌ | Optional category filter |
| `news_id` | string | ❌ | HKEXnews NEWS_ID if known |
| `local_key` | string | ❌ | Exact local bundle key from list results |

### Tool 3: `parse_hkexnews_announcement`

Parse a downloaded announcement PDF/HTML into page-split JSON. Uses PyMuPDF for PDF physical-page text extraction.

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `local_key` | string | ✅ | Bundle key returned by list/download, e.g. `"00700_13500_2026-03-15_a1b2c3d4"` |

### Tool 4: `keyword_search`

Full-text keyword search across all pages. Results ranked by TF + position-boost score.

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `local_key` | string | ✅ | Bundle key |
| `keywords` | string[] | ✅ | 1–5 search keywords |
| `match_mode` | string | ❌ | `"ANY"` (default, any keyword matches) / `"ALL"` (all must match) |
| `max_results` | int | ❌ | Max results to return, default 5, max 50 |

### Tool 5: `get_announcement_pages`

Read full page content by page number range.

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `local_key` | string | ✅ | Bundle key |
| `start_page` | int | ✅ | Start page number (1-based) |
| `page_count` | int | ❌ | Number of pages to return, default 3, max 5 |

### Tool 6: `lookup_stock_code`

Diagnostic tool: look up stock_code→stockId mapping. Use only when `download_hkexnews_announcement` / `list_hkexnews_announcements` returns `Stock code not found`. Bypasses the session failed-code cache.

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `stock_code` | string | ✅ | 5-digit HK stock code, e.g. `"00700"` |
| `refresh` | bool | ❌ | Force re-download of HKEX company mappings (default: `false`) |

## Configuration

On first run, a default config file is created at `~/.agentladle/mcp-hkexnews/config.yaml`:

```yaml
paths:
  data_dir: "~/.agentladle/mcp-hkexnews/data"
  pdf_dir: "~/.agentladle/mcp-hkexnews/data/pdf"
  json_dir: "~/.agentladle/mcp-hkexnews/data/json"

download:
  delay_between_requests: 0.3
  min_file_size: 500
  list_row_range: 100
  list_max_pages: 5

company:
  cache_ttl_days: 7

tiers:
  cache_ttl_days: 7
```

## Data Directory Structure

```
~/.agentladle/mcp-hkexnews/
├── config.yaml                        # Configuration (auto-created)
└── data/
    ├── companies.json                 # stock_code→stockId mapping (auto-downloaded & cached)
    ├── tiers.json                     # HKEX headline category mapping (auto-downloaded & cached)
    ├── pdf/                           # Downloaded announcement bundles
    │   ├── 00700_13500_2026-03-15_a1b2c3d4/
    │   │   ├── primary.pdf
    │   │   └── manifest.json
    │   └── ...
    └── json/                          # Parsed page-split JSON
        ├── 00700_13500_2026-03-15_a1b2c3d4.json
        └── ...
```

**File naming convention:** `{STOCK_CODE}_{T2_CODE}_{RELEASE_DATE}_{ID_HASH}`

## Example Usage

The tools are designed with an **EAFP (Easier to Ask for Forgiveness than Permission)** approach. AI assistants should attempt to retrieve data directly and rely on errors to trigger downloads.

**Scenario A: File already exists locally (Shortest Path)**
```
User: "Search 00700 inside information for buyback"

1. keyword_search(local_key="00700_50100_2026-07-09_a1b2c3d4", keywords=["buyback", "repurchase"])
   → Returns page snippets matching the keywords immediately.
```

**Scenario B: File missing (Fallback triggered)**
```
User: "What did Tencent announce in its latest inside information?"

1. list_hkexnews_announcements(stock_code="00700", category="Inside Information", limit=3)
   → Returns local_key / release_date / title.

2. keyword_search(local_key="...", keywords=["inside information"])
   → Error: File not found.

3. download_hkexnews_announcement(stock_code="00700", local_key="...")
   → Downloads PDF to ~/.agentladle/mcp-hkexnews/data/pdf/

4. parse_hkexnews_announcement(local_key="...")
   → Parses into JSON.

5. keyword_search(local_key="...", keywords=["inside information"])
   → Retries search and returns data.
```

## Tech Stack

| Component | Choice | Purpose |
|-----------|--------|---------|
| MCP Framework | `mcp` (FastMCP) | MCP server with stdio transport |
| HTTP Client | `httpx` | HKEXnews API requests & file downloads |
| PDF Parsing | `pymupdf` + `beautifulsoup4` | PDF page text extraction; HTML fallback |
| Search | Python built-in | TF + position-boost scoring |
| Config | `pyyaml` | YAML configuration file |
| Securities List | `openpyxl` | Parse HKEX ListOfSecurities.xlsx |

## Project Structure

```
src/mcp_hkexnews/
├── __init__.py
├── server.py                 # MCP Server entry point
├── config.py                 # Config loading (~/.agentladle/mcp-hkexnews/config.yaml, singleton cached)
├── models.py                 # Data models
├── categories.py             # Announcement category blacklist
├── response.py               # Unified JSON responses
├── instances.py              # Service singletons
├── tools/
│   ├── list_announcements.py # Tool 1: list_hkexnews_announcements
│   ├── download.py           # Tool 2: download_hkexnews_announcement
│   ├── parse.py              # Tool 3: parse_hkexnews_announcement
│   ├── search.py             # Tool 4: keyword_search
│   ├── page.py               # Tool 5: get_announcement_pages
│   └── lookup.py             # Tool 6: lookup_stock_code
└── services/
    ├── company.py            # HKEX activestock + ListOfSecurities + stock_code→stockId
    ├── tiers.py              # HKEX tierone/tiertwo category cache
    ├── downloader.py         # HKEXnews titleSearch + PDF download
    ├── parser.py             # PDF/HTML→JSON parsing (PyMuPDF)
    ├── searcher.py           # Local JSON search + TF scoring
    └── keys.py               # local_key helpers
```

## License

MIT
