Back to Projects

    MCP Servers — Making Pakistani Financial Data LLM-Callable

    MCP Servers for Pakistani financial data is a set of open-source Model Context Protocol servers built by Syed Husnain Haider Bukhari. psx-mcp exposes Pakistan Stock Exchange quotes, dividends, announcements and indices as typed tools any AI assistant can call; opportunity-mcp does the same for scholarships and fellowships; psx-dividend-alert sends Telegram buy-deadline alerts.

    2026
    Creator & Maintainer
    12 Technologies
    PythonFastMCP (MCP Python SDK)httpxBeautifulSoup4PydanticSQLite FTS5feedparserselectolaxdateparserNode.jsPuppeteerGitHub Actions
    MCP Servers — Making Pakistani Financial Data LLM-Callable

    Verify it yourself

    • psx-mcp on GitHubMCP server for Pakistan Stock Exchange quotes, dividends, announcements and indices. MIT licensed.
    • psx-mcp on PyPIVersion 0.1.1, installable with pip install psx-mcp or uvx psx-mcp.
    • opportunity-mcp on GitHubMCP server for scholarship, fellowship and internship search over a local SQLite FTS5 index.
    • opportunity-mcp on PyPIVersion 0.1.1, with a companion opportunity-mcp-refresh command that builds the index.
    • psx-dividend-alert on GitHubNode.js Telegram alerter with T+2 buy-deadline logic and a pure, unit-tested classifier.

    Introduction

    Model Context Protocol is an open standard that lets an AI client discover and call external tools through a defined interface, so the model can fetch data instead of guessing at it. An MCP server declares its tools once, with names, argument types and docstrings, and every compatible client gets the same surface. I built three projects on that standard, two of them MCP servers, to make Pakistani public data reachable from an assistant.

    The Challenge

    Pakistan Stock Exchange data is public but only usefully available as HTML on the PSX Data Portal. Every existing tool for it is a library: you import it, write code, and get a dataframe back. That is fine for engineers and useless for the person who actually wants to know whether they can still buy MEBL and collect the dividend. The naive alternative, pasting a scraped page into a chat prompt, fails in three specific ways: the parse is unversioned so nobody can tell when it silently broke, the token cost scales with page size rather than with the answer, and the model has to re-derive settlement math from raw text every single time. The same shape of problem exists for scholarship listings, where students keep ten aggregator tabs open and copy deadlines into a spreadsheet by hand.

    The Solution

    I wrote psx-mcp, a Python MCP server on FastMCP that scrapes the PSX Data Portal with httpx and BeautifulSoup and returns normalised JSON. It exposes nine tools including get_quote, get_upcoming_dividends, get_buy_deadline, get_dividend_history, get_announcements, search_symbols, get_indices, get_market_status and screen_dividend_stocks, three read-only resources under the psx:// scheme, and three templated prompts for dividend analysis, portfolio review and opportunity screening. opportunity-mcp reuses the pattern for youth opportunities: seven RSS adapters feed Pydantic-validated records into a SQLite FTS5 index, and six tools query that index. psx-dividend-alert is the Node.js companion that polls the same PSX feeds on a timer and pushes Telegram alerts classified as NEW, UPCOMING, URGENT, PASSED or DECLARED.

    Technical Deep Dive

    1

    The docstring is the contract. In FastMCP, the function docstring becomes the tool description the model reads when deciding what to call. So every tool in psx-mcp documents its arguments, its return shape and its caveats inline. get_quote states that PSX data is delayed roughly five minutes; screen_dividend_stocks explicitly says it filters by payout percentage of face value, not by true yield, and tells the model to call get_quote per symbol if real yield is needed. That caveat matters because PSX quotes payouts as a percentage of face value, typically Rs 10, so a headline number like 175 percent is not a yield figure at all.

    2

    Buy-deadline math lives in one pure module. dividend_calc.py implements subtract_trading_days and buy_deadline, where the deadline is book-closure start minus two trading days, skipping weekends and a holiday set. classify_dividend returns a structured status object with PASSED, URGENT_TODAY, URGENT_TOMORROW, APPROACHING or UPCOMING plus a plain-English message, so the model receives a decision rather than a date it has to reason about.

    3

    The Node.js alerter mirrors that math in src/trading-calendar.js and keeps the classifier a pure function with no I/O. Dates are parsed as UTC midnight via Date.UTC so day-of-week arithmetic stays deterministic regardless of the host timezone, which matters when Karachi is UTC+5 and the deployment target might be anywhere. The classifier takes the current date as a parameter and the alerter tick takes an injected clock, which is what lets the tests pin a specific date and assert which alert kind should fire.

    4

    Deduplication is a fingerprint, not a diff. In psx-dividend-alert a payout row is keyed as SYMBOL@bcFrom, and the persisted state records which alert kinds have already been sent for that fingerprint. A row can therefore progress NEW to UPCOMING to URGENT to PASSED across polls without ever firing the same kind twice, even though the scraper re-reads the whole table on every tick.

    5

    opportunity-mcp separates adapters from the query engine. An adapter knows one site and yields Pydantic Opportunity objects; the index knows nothing about sites and only searches normalised rows. The SQLite schema uses an FTS5 virtual table with porter unicode61 tokenisation kept in sync by insert, update and delete triggers, so search stays consistent without application-level bookkeeping.

    6

    Politeness is enforced in code, not in a policy document. The adapter base class fetches robots.txt with httpx under an identifying User-Agent that carries the project URL, then parses it with urllib.robotparser. It deliberately does not use RobotFileParser.read, because Python's default urllib User-Agent gets 403'd by Cloudflare-protected sites and the parser reads that as disallow-all, a false negative that would silently kill working sources.

    7

    Refresh happens in CI, never on user query. A GitHub Actions cron runs every six hours, rebuilds the SQLite index and publishes it as a release artifact tagged index-N. The MCP server itself only reads a local database, which keeps tool calls fast and keeps source sites from being hit once per user question.

    8

    Transport is a flag, not a rewrite. psx-mcp's entrypoint accepts --transport stdio, http or sse, mapping http to streamable HTTP with configurable host and port. Claude Desktop and Cursor get stdio; a hosted agent gets an HTTP endpoint from the same code path.

    Key Features

    Nine typed PSX tools plus resources and prompts

    Quotes, upcoming dividends with computed buy deadlines, per-symbol buy deadline, dividend history, corporate announcements with PDF links, fuzzy symbol search, index levels, market status and a payout screener. Three psx:// resources expose market status, indices and upcoming dividends as read-only context, and three prompts template full dividend-analysis workflows.

    T+2 settlement and trading-calendar awareness

    The last day you can buy and still be entitled to a dividend is book-closure start minus two trading days, skipping weekends and exchange holidays. Both the Python server and the Node.js alerter compute this rather than reporting the ex-dividend date, which is the number people actually get wrong.

    Local FTS5 index for opportunity search

    opportunity-mcp ships a single-file SQLite database with an FTS5 virtual table and typed columns for deadline, funding level, study level, host country and opportunity type. Six tools cover full-text search with filters, single-record lookup, newest listings, upcoming deadlines, source stats and on-demand refresh.

    Telegram alerts with escalating urgency

    psx-dividend-alert fires DECLARED on a board-meeting announcement when the announcements feed is enabled, NEW when a payout row first appears, UPCOMING within a configurable lead time, URGENT on the last day or the day before, and PASSED once the window closes. Optional price lookup adds a yield line and powers a minimum-yield filter.

    Published and installable, not just a repo

    psx-mcp and opportunity-mcp are both on PyPI at version 0.1.1 with server.json manifests targeting the MCP Registry schema, and opportunity-mcp is also listed on Smithery. Installation for an end user is one uvx or pip command plus a few lines of client config.

    Deployment that fits a small box

    psx-dividend-alert ships a multi-stage Dockerfile running as a non-root user on Debian's Chromium, a docker-compose file that mounts config read-only and persists state in a named volume, and a PM2 ecosystem config with restart backoff and a memory ceiling. CLI flags cover backfill and single-tick cron runs.

    Results & Impact

    • psx-mcp is live on PyPI at 0.1.1, and the README documents drop-in JSON config blocks for both Claude Desktop and Cursor. Its CI runs ruff and pytest across Python 3.10, 3.11 and 3.12 on pushes and pull requests to main.
    • opportunity-mcp is live on PyPI at 0.1.1 and published to Smithery as sayedhusnainhader/opportunity-mcp, with seven RSS adapters registered. The README marks six sources live and notes that the Scholars4Dev adapter is live but its upstream feed is currently empty.
    • psx-dividend-alert has unit tests covering the classifier, the trading-day calendar, the scraper parser, the state store and the end-to-end alerter with an injected clock, all runnable with node --test and no network access.
    • The buy-deadline calculation is the verifiable core: both implementations encode the same rule, book-closure start minus two trading days skipping weekends and any configured holiday, and both ship their 2026 holiday list empty pending the official PSX calendar.
    • Every project is MIT licensed and read-only against PSX. There is no code path in any of the three that places an order, and both READMEs carry the PSX personal-use data notice.

    Lessons Learned

    "A good MCP tool surface is narrow, typed and honest. Nine tools that each answer one question beat one do_everything tool with a mode argument, because the model picks by name and description before it ever sees your data. The clearest example in psx-mcp is the NOTE in the screener docstring spelling out that payout percentage of face value is not dividend yield."

    "Return decisions, not raw rows. Handing the model a book-closure date and hoping it derives T+2 settlement is how you get confidently wrong answers. Handing it a status of URGENT_TODAY with the message that the trade must execute before market close removes the arithmetic from the model's job entirely."

    "Refresh cadence belongs outside the tool call. Once I moved opportunity-mcp's fetching into a six-hourly CI job that publishes a database snapshot, tool calls became local reads, source sites stopped seeing per-question traffic, and the ethical story became something I could write down without hedging."

    "Scraping is the fragile layer and you should say so. The holiday lists in both PSX projects ship intentionally empty because the exchange publishes the official calendar late in the year, and both READMEs say so plainly: without the real dates the math still skips weekends correctly but goes wrong during Eid and other holiday weeks. Documenting the failure mode is cheaper than pretending it does not exist."

    Conclusion

    These three repositories are a small, opinionated argument about how to give a language model access to real data: define typed tools with careful descriptions, do the domain arithmetic in tested code, and keep fetching on a schedule you control. The same pattern transfers to any internal data source a team wants an assistant to reason over.

    Frequently asked questions

    What is MCP and why not just scrape inside the prompt?
    Model Context Protocol is an open standard for exposing tools an AI client can discover and call. Scraping inside a prompt costs tokens proportional to page size, breaks silently when the HTML changes, and forces the model to redo domain arithmetic every turn. A typed tool has a versioned contract, returns only the fields that matter, and can be unit tested.
    Are these open source, and what do they cost to run?
    All three are MIT licensed and public on GitHub. psx-mcp and opportunity-mcp install from PyPI and run locally next to your MCP client, so there is no server bill. psx-dividend-alert is a Node process that the README says runs on a small VPS, a Raspberry Pi or a laptop. The only external dependency is a free Telegram bot token.
    How does the buy-deadline logic handle holidays and weekends?
    The deadline is book-closure start minus two trading days, because PSX settles T+2. Both implementations step backwards day by day, skipping Saturdays, Sundays and any date in the configured holiday set. The holiday list ships empty because the exchange publishes its calendar late in the year, so the math is correct except during holiday weeks until you fill it in.
    Can this work with my stack, or is it Claude-only?
    Any MCP-compatible client works. psx-mcp and opportunity-mcp both speak stdio, which is what Claude Desktop, Cursor, Windsurf and Continue expect. psx-mcp additionally runs with --transport http for streamable HTTP, so a hosted agent or a custom orchestration layer can call it over a network endpoint instead of a subprocess.
    Is the market data real-time?
    No. psx-mcp reads the public PSX Data Portal, which is delayed by roughly five minutes, and the get_quote tool says so in its own response. psx-dividend-alert polls on a configurable interval defaulting to sixty minutes, with the README asking users not to go below fifteen. Neither is suitable for intraday trading, and neither is designed to be.
    Can I add a new data source?
    Yes. In opportunity-mcp an adapter is a class that yields Pydantic-validated Opportunity objects, registered by adding one entry to the SOURCES list; the README describes a new source as a roughly fifty-line pull request. In psx-mcp you add a function to scraper.py and expose it in server.py with an mcp.tool decorator and a clear docstring.
    Can it place trades or manage money?
    No, and that is a deliberate boundary. There is no order-placement path anywhere in the three repositories. The tools read public pages and return structured data; the alerter tells you when a buy window is closing. Position sizing, stock selection and the decision to trade stay entirely with the user.

    Interested in a Similar Project?

    Let's discuss how I can help bring your ideas to life.

    Get in Touch

    Let's Create a Revolution