Skip to main content
Glama
phdwight
by phdwight

pse-edge-mcp

CI Release PyPI Python 3.14 License: MIT

An MCP server exposing Philippine Stock Exchange data from the PSE Edge portal — quotes, price history, disclosures, financial reports, and market data — to Claude and any other MCP client.

Unofficial. PSE Edge has no public API; this project speaks to the same endpoints the portal's own pages use. It is not affiliated with or endorsed by the PSE. Data is provided as-is for personal/research use, with no warranty.

Contents

Related MCP server: VNStock MCP Server

Features

  • 13 read tools + 1 action tool covering quotes, price history, disclosures (metadata, full-text, and detail), company profiles, financials, dividends, indices, and market summary — plus an attachment resource, two prompts with symbol completion, and tool annotations.

  • Deliberately gentle on PSE Edge: every unique query hits it at most once per day, and prices follow a strict market-boundary freeze.

  • OAuth 2.1 + passkeys for humans (no passwords anywhere), client_credentials for headless agents — both opt-in; stdio needs nothing.

  • Postgres optional: zero-config in-memory for local stdio, or a shared cache + an ever-deepening EOD archive when DATABASE_URL is set.

  • Loud on drift: a nightly canary validates live pages against the real models and alerts only on failure; a restyled page raises an error, never partial data.

  • Multi-arch container image (amd64 + arm64), gated on necessity — the image contains exactly the runtime dependency closure and nothing else.

Quick start

Claude Desktop / Claude Code (stdio):

uvx pse-edge-mcp
{
  "mcpServers": {
    "pse-edge": { "command": "uvx", "args": ["pse-edge-mcp"] }
  }
}

A hosted deployment (auth on) is a normal OAuth 2.1 protected resource — modern clients need only the URL and drive the whole flow themselves (details below):

{
  "mcpServers": {
    "pse-edge": { "url": "https://your-host.example.com/mcp" }
  }
}

Docker (HTTP + Postgres):

cp .env.example .env   # set POSTGRES_PASSWORD
docker compose up --build

Design: end-of-day prices, fetch-once everything else

Four layers, one direction of dependency. Every read goes through FreezeService.get() with an explicit per-domain policy — no tool ever touches the HTTP client directly. ★ marks the invariant the whole design exists to protect, and it guards prices only.

flowchart TD
    C["MCP client"] -- "tool call" --> S["<b>server.py</b> · MCP boundary<br/>validate args · delegate · shape reply"]
    S --> R["<b>repositories.py</b> · one per domain<br/>cache key · freeze read · parse · endpoint routing"]
    R --> F["<b>service.py</b> · FreezeService ★<br/>3 policies · miss → fetch once"]
    F --> P["<b>client.py</b> · PseEdgeClient<br/>throttled HTTPS · single-flight · 2 dialects"]
    P --> E["PSE Edge<br/>edge.pse.com.ph"]

★ Market-boundary freeze — prices only. A cached stock price is never refetched while the market is open (09:30–15:00 Asia/Manila, trading days) — the last close answers, flagged stale. A price nobody has ever asked for is the one exception: fetched once mid-session and served as identity + previous_close only (every session-moving field withheld), with stale: true plus a meta.note saying it is not a realtime value; the settled figures replace it after the close.

Policy

Applies to

Behaviour

EOD-frozen

get_stock_quote, get_price_history

A cached price is never refetched during a session; a never-cached key is fetched once and surfaces only previous_close, labelled not-realtime for the whole session. Also the default policy, so an unlabelled read can only over-protect PSE Edge.

daily-refresh

Companies, disclosures, profiles, financials, dividends, indices, summary

First ask fetches at any hour — once, deduplicated across concurrent callers; every repeat of the same query answers from storage until the next 15:00 close.

immutable

Disclosure detail by edge_no, attachment bytes

The object never changes upstream. Fetched once ever; valid_until: null.

If PSE Edge is unreachable and an expired entry exists, tools serve it flagged meta.stale: true rather than discarding real data for an error. EDGE_UNAVAILABLE means unreachable and nothing cached.

Every data tool returns the same envelope — meta is the freshness contract:

{
  "data": { /* …StockQuote… */ },
  "meta": {
    "as_of":       "2026-08-06T15:00:00+08:00",  // ISO-8601, Asia/Manila
    "valid_until": "2026-08-07T15:00:00+08:00",  // null when immutable
    "from_cache":  false,
    "stale":       false,                        // true = not a settled EOD value
    "data_policy": "EOD-frozen",                 // "daily-refresh" / "immutable" elsewhere
    "note":        null                          // freshness caveat, e.g. "not a realtime value"
  }
}

Tools, resources, prompts

Tool

Description

search_companies(query)

Find PSE-listed companies by name or ticker

validate_symbol(symbol)

Cheap yes/no check that a ticker exists, with its company name and id

get_stock_quote(symbol)

Latest EOD quote: price, change, 52-wk range, market cap, full field set

get_price_history(symbol, start_date?, end_date?)

Daily OHLC series from Edge's chart endpoint

search_disclosures(symbol?, start_date?, end_date?, template?, page?)

Disclosure metadata, market-wide or per company; 50/page with exact totals

search_disclosure_fulltext(keyword, ...)

Search the text inside disclosure attachments, with snippets

get_disclosure(edge_no, max_files?)

One disclosure's details plus attachment and body-HTML links; attachments capped at max_files (default 20) with an honest truncation flag. Each attachment carries a resource_uri — read the file's bytes via MCP resources/read (pse-edge://attachment/<file_id>, cached immutably, 10 MB cap)

get_company_profile(symbol)

Sector, incorporation, auditor, transfer agent, contacts

get_financial_highlights(symbol)

Annual + quarterly balance sheet and income statement

get_dividends_and_rights(symbol)

Declared dividends and stock rights, linked to their disclosures

get_indices()

PSEi and the 7 sector indices, with signed daily change

get_market_summary()

Index levels plus PSE Edge's homepage disclosure feeds

get_server_version()

The deployed version of this MCP server itself (matches /health)

send_email(subject, body)

Email yourself a note (auth-enabled deployments only)

Beyond tools, the server exposes the attachment resource above, two prompts (market_recap, company_briefing(symbol) — the symbol argument autocompletes from PSE Edge's own lookup), and MCP tool annotations so hosts can auto-approve the read-only tools. It is described for the MCP Registry in server.json.

send_email is the only tool that acts rather than reads. It has no recipient argument: the message always goes to the account that authenticated the session, so it cannot be used as a relay and there is nothing for prompt injection to redirect — which matters because this server returns disclosure text the operator does not control. It appears only on deployments with auth enabled (there is no verified address otherwise), the body is escaped rather than rendered as HTML, and it is capped at 20 messages per user per day.

Disclosure tools return metadata and links only — this server never downloads or parses attachments (beyond the explicit resource read), so your MCP client can fetch the returned URLs itself if it needs the files. Note that Edge's own full-text index is partial (roughly 2023–2025 at last check), so search_disclosure_fulltext is not a substitute for search_disclosures; it reports this limit in its results.

Financial figures are returned exactly as PSE Edge prints them and are never rescaled — Edge's own units labels are inconsistent between its annual and quarterly sections, so each period reports its currency_units for you to check. Index changes are signed here even though Edge prints them unsigned (it shows direction only as a colour and an arrow).

Architecture

The layers

Layer

Owns

Never

server.py — MCP boundary

Argument validation, delegation, reply shaping. Error mapping happens once in reply(); action tools go through act() (no freshness meta).

Domain logic, cache keys, parsing, endpoint choices

repositories.py — domain layer

One repository per data domain: the cache key, the freeze read, the parse, the Pydantic model. Endpoint routing lives here.

Depending on the concrete client — only on the protocols below

service.py / sources.py — policy & seams

FreezeService enforces the per-read policy and wraps every result in Served[T] (value + as_of, valid_until, from_cache, stale). sources.py declares the five narrow per-domain source protocols; FrozenCache is the cache seam.

client.py / parsers.py — edge of the world

Pure HTTP, MCP-agnostic: token-bucket throttle, single-flight, retries; two request dialects (JSON-body POST for chart .ax endpoints, form-encoded POST returning HTML fragments for search.ax). Parsers turn HTML/JSON into dicts; any shape drift raises EndpointChangedError — loud, never partial.

Core class map

Five repositories cover the whole tool surface. Each consumes a narrow source protocol — the concrete client satisfies all five, but no repository knows that, so each is testable with a few-line fake and no HTTP mocking.

Repository

Methods → models

Consumes

Policy / note

CompanyRepository

search, resolve, try_resolveCompanyHit

CompanySource

daily-refresh · resolves symbol → company_id for every other repo

QuoteRepository

quoteStockQuote · historyPriceHistory

QuoteSource

EOD-frozen ★ — the only market-gated domain · bars archived

DisclosureRepository

search, fulltext, detail, attachment

DisclosureSource

searches daily-refresh; detail/attachment immutable

CompanyInfoRepository

profile, financials, dividends_and_rights

CompanyInfoSource

daily-refresh · financial units passed through verbatim

MarketRepository

indices, summary

MarketSource

daily-refresh · index signs derived from Edge's ▲/▼ glyph

NotificationService (action)

send(user, subject, body)SentEmail

recipient comes from the bearer token, never an argument

Protocols and swappable implementations

One switch picks the column: DATABASE_URL unset → in-memory / Null; set → Postgres. Postgres modules import lazily, so a lean install never pays for them.

Protocol

DATABASE_URL unset

DATABASE_URL set

Storage

InMemoryStorage

PostgresStorage

Archive

NullArchive

PostgresArchive

UsageSink

NullUsageRecorder

PostgresUsageSink

AuthStore

PostgresAuthStore

EmailSender

ConsoleEmailSender

ZeptoMailSender (when ZEPTOMAIL_API_KEY is set)

HTTP composition — built once, in asgi.py

flowchart LR
    H["HealthApp<br/>/health · /health/ready"] --> A["AuthApp<br/>/oauth/* · signup · /account · /privacy"]
    A --> M["AuthMiddleware<br/>bearer validation · quotas · usage"]
    M --> MCP["MCP app<br/>the tool surface"]

/health is liveness and never touches the database; /health/ready is readiness. Behind AuthApp: OAuthService (DCR · PKCE-only · refresh families), PasskeyService (WebAuthn + web sessions), TokenService (opaque pse_ tokens, SHA-256 at rest).

Error family — one root, mapped once in reply()

Error

Meaning

SymbolNotFoundError

SYMBOL_NOT_FOUND

InvalidArgumentError

INVALID_ARGUMENT — bad input, caught at the boundary

EndpointChangedError

Edge redesigned a page — loud, never partial

EdgeUnavailableError

Upstream unreachable and nothing cached

MarketOpenNoCacheError

Retained for client compatibility; no longer raised

ActionUnavailableError

Action tool needs auth enabled

ActionRateLimitedError

20 emails / user / day

Watchdog

A nightly canary (pse-edge-canary, plus a compose service) fetches live pages bypassing the cache and validates the same Pydantic models the repositories build — a 200 with a restyled table is exactly the failure it exists to catch. It still refuses to run while the market is open (the ★ invariant outranks it), emails PSE_OPERATOR_EMAIL only on failure, and exits non-zero so cron notices.

Golden path: one request traced

get_stock_quote("SM") after market close, cold cache:

  1. server.pyvalidation.py checks the symbol shape (bad input → INVALID_ARGUMENT), then reply() wraps the repository call — the only place errors become MCP error payloads.

  2. QuoteRepository.quote("SM") — resolves SMcompany_id through CompanyRepository, picks the endpoint, builds the cache key. Tools never see any of this.

  3. FreezeService.get(key, fetch, policy="EOD-frozen") — fresh cache entry → serve it. Market open + cached → serve the last close flagged stale, never refetch. Market open + never cached → fetch once, label stale: true + note for the whole session. Market closed + miss → fetch. Fetch fails but an expired entry exists → serve it flagged stale.

  4. PseEdgeClient.fetch_stock_data_page(company_id) — token bucket (1 req/s), single-flight dedupe, retries. Wire dates are MM-dd-yyyy; the JSON-vs-form dialect is chosen per endpoint.

  5. parsers.pyStockQuote — HTML → dict → validated Pydantic model. Any drift in Edge's markup raises EndpointChangedError.

  6. cache.py / archive.py — the entry freezes until the next 15:00 close; daily bars archive opportunistically (a dead database never fails a read).

Connecting to a hosted server

A deployment with auth on is a normal OAuth 2.1 protected resource, so a modern MCP client needs only the URL — it discovers everything else and drives the whole flow itself.

What happens on first connect

Nothing here is manual except the two browser steps in bold.

  1. The client POSTs to /mcp with no token and gets 401 carrying WWW-Authenticate: Bearer resource_metadata="…/.well-known/oauth-protected-resource". That header is the entire bootstrap: it tells the client where to look next.

  2. It fetches that document, learns which authorization server guards this resource, then reads /.well-known/oauth-authorization-server for the endpoints.

  3. It registers itself at /oauth/register (RFC 7591) — no client secret, no operator involvement, no pre-shared credentials. It gets back a client_id.

  4. It opens /oauth/authorize in a browser with a PKCE challenge (S256 required).

  5. The user signs up or signs in. New users land on /signup, agree to the (deliberately tiny) data policy, give an email, and receive a link; the link shows a confirm page whose button enrolls a passkey at /enroll — the confirm step exists so a mail scanner's prefetch cannot spend the link. Returning users hit /login and use the passkey they already have. No password exists anywhere in the system.

  6. The user approves the client on a consent screen naming it.

  7. The browser returns to the client with a single-use code; the client exchanges it at /oauth/token with its PKCE verifier and receives an access token (15 min) and a single-use refresh token (24 h).

  8. The client calls /mcp with Authorization: Bearer … and refreshes silently from then on. The user is not asked again.

client ──POST /mcp──────────────▶ 401 + WWW-Authenticate
       ──GET  /.well-known/… ───▶ metadata
       ──POST /oauth/register ──▶ client_id
       ──GET  /oauth/authorize ─▶ browser: signup/login → passkey → consent
       ◀─────────────────────────  ?code=…
       ──POST /oauth/token ─────▶ access (15 min) + refresh (24 h)
       ──POST /mcp + Bearer ────▶ tools

Refresh tokens rotate on every use, and replaying a rotated one revokes that whole session family (RFC 9700 §4.14) — a stolen refresh token gets one use before the theft is detected and the session dies.

Headless agents (client_credentials)

For a LangGraph app, the Anthropic Messages API MCP connector, or any agent that cannot open a browser. No redirect, no passkey, no consent screen — a client id and secret.

1. Provision. Two routes, same result:

  • From the web (needs no shell — the practical choice on a NAS): set PSE_ADMIN_EMAILS to your account's email, sign in, and a Machine clients panel appears on /account with create and revoke controls. Access is gated to that allowlist — a normal signup never sees it.

  • From the CLI: pse-edge-admin create-machine-client --name langgraph-app.

Either way client_id and client_secret are shown once. Only the secret's SHA-256 is stored, so it cannot be recovered — only revoked and reissued (from the same account page, or pse-edge-admin revoke-machine-client <client_id>).

2. Mint a token:

curl -s -X POST https://pse.sakayandgo.com/oauth/token \
  -d grant_type=client_credentials \
  -d client_id=$CLIENT_ID -d client_secret=$CLIENT_SECRET \
  -d scope=mcp -d resource=https://pse.sakayandgo.com/mcp
{"access_token": "pse_…", "token_type": "Bearer", "expires_in": 3600, "scope": "mcp"}

HTTP Basic works too (curl -u "$CLIENT_ID:$CLIENT_SECRET"), which is what most SDKs send. No refresh token is issued — the client already holds a long-lived secret and simply re-requests when the hour is up.

3. Use it:

curl -s -X POST https://pse.sakayandgo.com/mcp \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"1.0"}}}'

Revoke with pse-edge-admin revoke-machine-client <client_id>, which kills the secret, every token it minted, and the backing service account in one step.

Registering does not grant this. /oauth/register is open to the internet, so a client that registers itself — even declaring grant_types: ["client_credentials"] and sending a secret — is refused with unauthorized_client. Authorization comes from a client_type column only the admin CLI writes, never from anything a registrant says about itself.

Give each agent its own machine client: quotas are per client, so a runaway job throttles itself, and revoking one does not touch the others.

Building an app on top of this? examples/langgraph_client.py is a working client for the multi-tenant case — your app authenticates as itself with one machine client, your users never see this server. It carries an httpx.Auth that mints and refreshes the 1-hour token (verified: concurrent calls mint once; a stale token recovers on 401), plus the agent instructions worth pasting into a system prompt. Note it needs mcp<2langchain-mcp-adapters does not yet import against the 2.x SDK.

If your client does not do OAuth yet

The operator issues a token directly, and the user pastes it into a header. Same server, no browser:

pse-edge-admin create-user you@example.com
pse-edge-admin issue-token you@example.com --note laptop   # plaintext shown once
curl -X POST https://your-host.example.com/mcp \
  -H "Authorization: Bearer pse_..." \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

This is also the only route on a LAN-only deployment: passkeys need a secure context, so plain http cannot enroll one.

What a user can see and remove

/account shows everything held about them — email, passkeys, active tokens, hourly usage counts. POST /account/delete erases it immediately and completely, with no approval step. /privacy states what is collected and for how long. Usage counts are deleted after 90 days.

Run with Docker Compose (HTTP + Postgres)

cp .env.example .env   # set POSTGRES_PASSWORD
docker compose up --build

Serves streamable HTTP on :8000, with Postgres 18 as shared cache and archive. A one-shot migrate service applies the Alembic schema before the app starts.

HTTP mode is stateless with plain JSON responses by default. This server is read-only tools over data the freeze policy holds still, and it uses none of the features MCP sessions exist to enable — no notifications, no resource subscriptions, no sampling, no elicitation, no progress — so every request is self-contained. That means any replica can serve any request behind plain round-robin: no sticky routing, no per-session memory, no event store. Without SSE, idle clients hold no connection either. Use --stateful if you need MCP sessions and --sse for event-stream framing; they are independent flags.

Bearer auth and quotas (opt-in)

Set PSE_AUTH_REQUIRED=1 (needs DATABASE_URL) and every HTTP request must carry Authorization: Bearer <token>. Users arrive either way described in Connecting to a hosted server — self-service through OAuth 2.1 and passkeys, or an operator-issued token. PKCE is mandatory (S256 only) and no password exists anywhere in the system.

Tokens are opaque and stored only as SHA-256 hashes. Revocation (pse-edge-admin revoke-token … / disable-user …) takes effect within the validation cache's TTL — 60 s by default, which is precisely the revocation-latency budget. Per-user quotas (default 60/min, 2,000/day, overridable per user) are counted in-process and answer HTTP 429 with Retry-After; with N replicas the effective ceiling is up to N× nominal, which is fine for abuse prevention. stdio mode never authenticates — it runs on your own machine.

Operators get pse-edge-admin delete-user and purge-usage (cron the latter daily), and delete-user uses the same erasure code path as the user's own delete button, so the two cannot drift apart.

Postgres is optional. Without DATABASE_URL the server uses an in-memory cache and keeps no archive — the zero-config path for local stdio use. With it set, replicas share one cache (the freeze still means one upstream fetch per boundary however many processes run), and every read accumulates into an EOD archive (daily bars and disclosures) that deepens over time at zero extra cost to PSE Edge. Nothing crawls — the archive fills solely from fetches you already made.

# applying the schema by hand, outside compose
DATABASE_URL=postgresql+asyncpg://user:pass@host/db uv run alembic upgrade head

Configuration

Everything is environment-sourced into one frozen Settings object. Two variables change the shape of the system: DATABASE_URL picks the storage column, and PSE_AUTH_REQUIRED turns on the whole auth stack (and the send_email tool with it).

Variable

Default

What it governs

Upstream — protect PSE Edge

PSE_EDGE_BASE_URL

https://edge.pse.com.ph

Upstream portal root

PSE_THROTTLE_RPS / PSE_THROTTLE_BURST

1.0 / 2

Token-bucket rate toward Edge

PSE_TIMEOUT_SEC / PSE_RETRY_ATTEMPTS

20 / 3

Per-request timeout and retries

Storage — the one switch

DATABASE_URL

unset

Unset → in-memory cache + NullArchive. Set → shared Postgres cache + archive + auth tables (schema via Alembic only)

PSE_DB_POOL_SIZE / PSE_DB_MAX_OVERFLOW

5 / 10

Connection pool

Auth — opt-in, needs DATABASE_URL

PSE_AUTH_REQUIRED

0

Bearer auth + quotas + OAuth/passkeys; stdio never authenticates

PSE_TOKEN_CACHE_TTL

60

The revocation-latency budget — nothing else

PSE_QUOTA_PER_MIN / PSE_QUOTA_PER_DAY

60 / 2000

Per-user quotas, counted in-process (per worker)

PSE_PUBLIC_URL

http://localhost:8000

Real external https URL — drives WebAuthn rp_id, email links, OAuth issuer; a wrong value breaks passkeys

PSE_ACCESS_TTL_MIN / PSE_REFRESH_TTL_HOURS

15 / 24

Token lifetimes; the refresh token is single-use and reuse revokes the family. PSE_REFRESH_UNUSED_TTL_HOURS (24) caps a family that never rotated

PSE_ADMIN_EMAILS

empty

Operator allowlist for the /account machine-client panel; never derived from user input

Email & operations

ZEPTOMAIL_API_KEY

unset

Unset → ConsoleEmailSender; set → ZeptoMail

PSE_EMAIL_FROM

no-reply@localhost

Sender address (ZeptoMail verifies exact domains)

PSE_OPERATOR_EMAIL

unset

Canary failure alerts — failures only, never "all fine"

PSE_USAGE_RETENTION_DAYS

90

Usage log retention (aggregated per user-hour, never per request)

Server

PSE_STATEFUL / PSE_SSE

0 / 0

MCP session & response mode

PSE_LOG_JSON / PSE_LOG_LEVEL

0 / INFO

Both formatters timestamp and redact; INFO logs refusals only

Container image

Every merge to main publishes an image:

docker pull ghcr.io/phdwight/pse-edge-mcp:latest      # or :<version>, :sha-<sha>
# multi-arch: linux/amd64 and linux/arm64
docker run --rm -p 8000:8000 ghcr.io/phdwight/pse-edge-mcp:latest   # streamable HTTP
docker run --rm -i --entrypoint pse-edge-mcp ghcr.io/phdwight/pse-edge-mcp:latest  # stdio

Both architectures are gated before publishing, on native runners. The rule is necessity, not size: the image must contain exactly the resolved runtime dependency closure and nothing else — no build toolchain, no package manager, no dev dependencies, no bytecode caches, no source tree — plus a secret scan and a smoke test that the server starts and registers its tools. A stray dependency fails the build; a large but genuinely required one does not. Image size is reported for information and never gated.

Production

One file, compose.nas.yaml, for a NAS or any single Docker host, in two stages. It pulls the published image rather than building, so production runs the artifact CI gated. Stage 1 is LAN-only and needs nothing from Cloudflare:

docker compose -f compose.nas.yaml up -d                     # http://<nas-ip>:8200
docker compose -f compose.nas.yaml --profile tunnel up -d    # + public hostname

The tunnel profile starts cloudflared, which dials out — so there is no port forwarding, no ACME, and nothing for CGNAT to break; Cloudflare terminates TLS at its edge. Set CLOUDFLARE_TUNNEL_TOKEN, PSE_PUBLIC_URL and PSE_LAN_BIND=127.0.0.1 in .env alongside it — the last moves the stage 1 LAN port onto loopback, which is the only way to unpublish it, because Compose merges ports additively.

Both stages give auth on by default, daily backups, a daily retention purge, and no published database port. Health probes are /health (liveness) and /health/ready (readiness). The app is importable for other servers: uvicorn pse_edge_mcp.asgi:app --workers 4.

See docs/deploy.md for the full guide, including the two settings most worth getting right: pin PSE_IMAGE_TAG rather than tracking :latest, and make PSE_PUBLIC_URL the real external https URL, because WebAuthn binds every passkey to the origin it was enrolled under.

Development

uv sync --all-extras
uv run pytest
uv run ruff check .

Tests run entirely against recorded fixtures — CI never touches PSE Edge.

New to the codebase? docs/walkthrough.md is the developer and architect walkthrough: the request lifecycle, the freeze policy, the layering, how to add a tool or a whole data domain, and a symptom-to-cause debugging table. Also available as a PDF. For the one-page visual version of the Architecture section — classes, protocols, the data path, the config matrix — open docs/reference-card.html in any browser; it is fully self-contained and works offline. Every design decision is recorded in docs/plan.md, and the verified endpoint map lives in docs/endpoints.md.

Contributing

Issues and pull requests are welcome. The ground rules:

  • Work lands on develop and reaches main by pull request; main is protected and requires all three CI checks (test, image (amd64), image (arm64)).

  • Tests never touch PSE Edge — new endpoints need new recorded fixtures in tests/fixtures/.

  • New tools follow the layering above: a new data domain is a new repository plus thin tools, never fetch/parse logic in server.py.

  • Bumping version in pyproject.toml makes the next merge cut a GitHub Release with a matching immutable image tag; roll CHANGELOG.md in the same PR.

License

MIT


MCP Registry identity: mcp-name: io.github.phdwight/pse-edge-mcp

Available Tools

13 tools
get_company_profileCompany profileA
Read-only
Inspect

Get a PSE-listed company's profile: sector, incorporation, auditor, contacts.

Includes sector and subsector, incorporation date, corporate life, number of directors, fiscal year end, stockholders' meeting schedule, external auditor, transfer agent, business address, phone, fax, email and website. Every label on the page is also returned verbatim in raw_fields.

ParametersJSON Schema
NameRequiredDescriptionDefault
symbolYes

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.1/5.0
Behavior4/5

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

Annotations already declare readOnlyHint=true, so the safe read-only nature is known. The description adds valuable behavioral detail: 'Every label on the page is also returned verbatim in raw_fields', which is not in the schema and gives insight into the output structure.

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 opens with a clear, front-loaded summary sentence, followed by a detailed field list and the raw_fields note. It is informative without redundant fluff, though the field enumeration is slightly lengthy.

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?

For a read-only, single-parameter tool with an output schema, the description is sufficiently complete. It covers the scope (PSE-listed), returns a comprehensive field list, and highlights the raw_fields behavior. No critical usage context is missing.

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

Parameters3/5

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

The schema has 0% description coverage for the single parameter 'symbol'. The description compensates partially by mentioning 'PSE-listed company', implying symbol refers to a PSE ticker, but it gives no format, examples, or validation guidance, leaving some ambiguity.

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: 'Get a PSE-listed company's profile' and enumerates the specific contents (sector, incorporation, auditor, contacts). This distinguishes it from sibling tools like get_stock_quote and get_financial_highlights, making the purpose unambiguous.

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

Usage Guidelines4/5

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

Clear context: the tool is for retrieving a company profile for PSE-listed stocks. It does not explicitly state when not to use it or point to alternatives, but the sibling tool names and the clarity of purpose make the appropriate usage obvious.

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

get_disclosureDisclosure detailA
Read-only
Inspect

Get one disclosure's details and attachment links by its edge_no.

edge_no is the 32-character hex id returned by search_disclosures. Returns the company, template, date, related documents, and URLs for each attachment plus the rendered body HTML. To read a file's contents, use the attachment's resource_uri (pse-edge://attachment/) via resources/read — the tools themselves stay metadata-only; download_url remains for callers that can fetch URLs directly.

At most max_files attachments are returned (default 20, max 100). attachments_total always reports how many exist; if attachments_truncated is true, call again with a higher max_files — the disclosure is cached, so the repeat costs nothing upstream.

A published disclosure never changes, so these results are cached permanently (meta.data_policy is "immutable").

ParametersJSON Schema
NameRequiredDescriptionDefault
edge_noYes
max_filesNo

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A5/5.0
Behavior5/5

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

Beyond the readOnlyHint annotation, the description discloses that published disclosures are immutable and cached permanently, that the tool is metadata-only (no file bytes), and that repeated calls cost nothing upstream. This significantly enriches the behavioral model.

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

Conciseness5/5

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

The description is front-loaded with a clear purpose sentence, then logically progresses to parameter semantics, usage alternatives, and caching behavior. Every sentence adds value; there is no fluff or redundancy.

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

Completeness5/5

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

It covers the input provenance, return content, pagination, caching, immutability, and a clear note that file content requires resources/read. Even with an output schema present, this description leaves no relevant gap for an AI agent to select and invoke the tool correctly.

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 input schema has zero description coverage, but the description defines edge_no as the 32-character hex ID from search_disclosures and explains max_files with default (20) and max (100), plus the truncation behavior. This fully compensates for the bare schema.

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 starts with a specific verb+resource: 'Get one disclosure's details and attachment links by its edge_no.' It clearly scopes the tool to retrieving a single disclosure by ID, which distinguishes it from the search siblings (e.g., search_disclosures, search_disclosure_fulltext).

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?

It explicitly tells the caller where to obtain edge_no (from search_disclosures) and when to use resources/read instead for file contents. It also gives guidance on handling truncation by increasing max_files, which is actionable usage context.

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

get_dividends_and_rightsDividends & rightsA
Read-only
Inspect

Get a company's declared dividends and stock rights offers.

Dividends carry the security and dividend type, the rate as printed, and the ex-dividend, record and payment dates. Rights carry the entitlement ratio, offer price, ex-rights date and offer period. Each record includes the edge_no of the disclosure that announced it, so you can pass it to get_disclosure for the notice itself. Empty lists mean Edge lists none for this company.

ParametersJSON Schema
NameRequiredDescriptionDefault
symbolYes

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.2/5.0
Behavior4/5

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

Annotations already declare readOnlyHint=true and openWorldHint=false. The description adds valuable behavioral context by explaining that empty lists mean Edge lists none for this company, and that each record includes an edge_no linking to the announcement disclosure. This goes beyond the annotations and does not contradict them.

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

Conciseness5/5

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

The description is a single focused paragraph, front-loaded with the core purpose. Each subsequent detail—field lists, edge_no linkage, empty-list interpretation—adds value without redundancy. Every sentence earns its place.

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

Completeness5/5

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

Given the tool's simplicity (one parameter, output schema present, readOnly, openWorld false), the description covers all relevant aspects: what data is returned, how to link to the disclosure, and how to interpret empty results. This is sufficient for an agent to correctly invoke and understand the tool's output.

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

Parameters2/5

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

The schema has a single parameter 'symbol' with no description, and schema description coverage is 0%. The description only says 'a company's' and does not explain the symbol format, validation, or any additional semantics, leaving the parameter under-specified and requiring the agent to infer what 'symbol' means from the context.

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 opens with 'Get a company's declared dividends and stock rights offers,' using a specific verb and resource that clearly distinguishes it from sibling tools like get_stock_quote or get_disclosure. It further elaborates on the fields included for dividends and rights, making the tool's scope unmistakable.

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

Usage Guidelines4/5

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

The description implies when to use this tool by stating it retrieves dividend and rights data. It also provides a clear usage tip: pass the returned edge_no to get_disclosure for the full notice. However, it doesn't explicitly mention alternatives or when not to use the tool, so it falls short of a 5.

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

get_financial_highlightsFinancial highlightsA
Read-only
Inspect

Get the financial highlights PSE Edge publishes for a company.

Returns an annual and a quarterly section, each with a balance sheet and an income statement, using Edge's own line-item labels and column headings (annual compares Current/Previous Year; quarterly compares Period Ended against the audited fiscal year, and its income statement has four columns including year-to-date).

IMPORTANT — units: figures are returned exactly as Edge prints them and are never rescaled. Each period carries its own currency_units label, and the two sections disagree in practice (observed: annual "Php (in thousands)" while quarterly said "Php (in Millions)" for the same company, with the same figure appearing in both). Read currency_units before quoting any number, and say the scale is uncertain rather than presenting these as exact peso amounts. Only the highlights Edge serves as data are here — this server does not parse filed PDF statements.

ParametersJSON Schema
NameRequiredDescriptionDefault
symbolYes

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.4/5.0
Behavior5/5

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

The description goes well beyond the readOnlyHint annotation by disclosing critical behavioral details: figures are never rescaled, each period has its own currency_units label, the two sections may disagree (with a concrete observed example), and the scale should be treated as uncertain if labels conflict. This is exactly the kind of contextual information an agent needs to avoid misinterpreting results.

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

Conciseness5/5

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

The description is efficiently structured: purpose first, then return structure, then the critical units warning, then scope clarification. Each sentence earns its place, and the units warning is front-loaded for safety. Despite being relatively long, it is highly informative and not verbose.

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

Completeness5/5

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

Given the tool's complexity (annual vs quarterly sections, currency unit inconsistencies, line-item labels), the description covers the key aspects of return format, data source limitations, and the critical unit caveat. Since an output schema exists, return values are further specified, making the description complete for an agent to invoke the tool correctly and interpret results safely.

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

Parameters2/5

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

The schema has only one parameter 'symbol' with no description. The tool description does not explain the symbol parameter's format or examples, despite low schema coverage (0%). The phrase 'for a company' indirectly implies the parameter is a company identifier, but it does not compensate for the lack of explicit parameter documentation.

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 states a specific verb ('Get') and resource ('financial highlights PSE Edge publishes for a company'), and details the return structure (annual/quarterly sections with balance sheet and income statement). This clearly distinguishes it from siblings like get_stock_quote or get_company_profile.

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

Usage Guidelines4/5

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

The description gives clear context about data scope and an important exclusion ('Only the highlights Edge serves as data are here — this server does not parse filed PDF statements'). This implicitly tells when not to use the tool, but it does not name alternative tools explicitly, leaving some ambiguity for agents unfamiliar with sibling tools.

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

get_indicesMarket indicesA
Read-only
Inspect

Get PSEi and the PSE sector index levels with their daily change.

Covers PSEi, All Shares, Financials, Industrial, Holding Firms, Property, Services and Mining and Oil. change and change_percent are signed, and direction is "up"/"down"/"flat" — PSE Edge prints these unsigned and shows direction only as a colour and an arrow, so the signs here are derived from that. Fetched at most once per boundary window and served from storage until the next 15:00 Manila close — meta.as_of says when the snapshot was taken.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.6/5.0
Behavior5/5

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

Annotations already signal read-only (readOnlyHint=true) and deterministic (openWorldHint=false), but the description adds significant behavioral context: the sign conventions (change and change_percent are signed, direction is up/down/flat derived from PSE Edge's color/arrow), the caching policy (at most once per boundary window), and the meta.as_of field for snapshot timing. This substantially goes beyond the annotations, delivering high transparency.

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 moderately sized (4 sentences) and front-loaded with the main purpose. Each subsequent sentence adds necessary detail (coverage, sign semantics, caching) without fluff. It could be slightly more compact, but it remains efficient and relevant, earning a 4 rather than a 5.

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

Completeness5/5

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

With no parameters and an output schema present, the description covers all essential contextual aspects: what data is returned, which indices are included, the interpretation of direction fields, and the staleness/caching behavior. The existence of an output schema handles return field details, so the description is complete for an agent to select and invoke the tool correctly.

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

Parameters4/5

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

The tool has zero parameters, and the schema coverage is 100% (vacuously). The baseline for zero-parameter tools is 4 per the rubric. The description adds no parameter info (not needed), so the score reflects the absence of complexity.

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 retrieves PSEi and PSE sector index levels with daily change. The verb 'Get' and specific resource 'PSEi and sector index levels' is explicit, and it naturally distinguishes from siblings like get_stock_quote (single stock) and get_market_summary (broader market metrics).

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

Usage Guidelines4/5

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

The description clearly indicates the scope (which indices are covered) and the context (cached until 15:00 Manila close), which helps decide when to use it. However, it does not explicitly mention when not to use it or suggest alternatives, so it falls short of a 'when/when-not' distinction. Still, the clarity of purpose provides strong implicit guidance.

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

get_market_summaryMarket summaryA
Read-only
Inspect

Get a market-wide snapshot: index levels plus PSE Edge's homepage feeds.

feeds is keyed by Edge's own group labels — Company Announcements, Financial Reports, Other Reports, Listing Notices, Disclosure Notices, and the most-viewed disclosures for Today and This Week. Each entry carries its symbol, timestamp, circular number and edge_no for get_disclosure.

Note: PSE Edge publishes no gainers/losers/most-active data anywhere, so this cannot include them — say so rather than implying the data is missing or stale.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.6/5.0
Behavior5/5

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

Annotations already declare readOnlyHint=true and openWorldHint=false, but the description adds substantial context: how feeds are keyed (group labels), what each entry carries (symbol, timestamp, circular number, edge_no), and a critical limitation (PSE Edge publishes no gainers/losers/most-active data anywhere). This goes beyond the structured annotations and helps the agent interpret results and avoid misstatements.

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 first sentence is a concise summary. The second paragraph lists the feed keys and their fields, which is necessary detail given the tool's output scope. The limitation note is important and earns its place. Slightly dense but well-organized; no filler.

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

Completeness5/5

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

Given the output schema exists, the description doesn't need to list return fields, but it still provides essential context: feed structure, key labels, and the data limitation. With zero parameters and a rich output description, an agent has enough to invoke the tool and interpret results without ambiguity.

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

Parameters4/5

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

The input schema has zero parameters, and the description itself doesn't mention parameters, which is appropriate. With no parameters, the schema coverage is trivially 100% and the baseline is 4. The description adds no param semantics because none exist.

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 uses a specific verb ('Get') and resource ('market-wide snapshot') with concrete scope: index levels plus PSE Edge's homepage feeds. This clearly differentiates it from siblings like get_indices (indices only) and search_disclosures (disclosure search), so purpose is unambiguous.

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

Usage Guidelines4/5

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

The description provides clear context for when to use this tool (for a market-wide snapshot) and includes an important behavioral instruction: if asked for gainers/losers, explicitly state PSE Edge doesn't publish them rather than implying missing data. It doesn't explicitly name alternatives or exclusions, but the context is enough for an agent to select this tool appropriately.

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

get_price_historyPrice history (OHLC)A
Read-only
Inspect

Get daily OHLC price history for a PSE stock symbol.

Dates are ISO format (YYYY-MM-DD). Defaults to the last ~6 months. Data comes from PSE Edge's own chart endpoint and is EOD-frozen. If the market is open and this exact range has never been cached, a one-time fetch may include today's still-moving bar, flagged stale=true with an explanatory meta.note.

ParametersJSON Schema
NameRequiredDescriptionDefault
symbolYes
end_dateNo
start_dateNo

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.8/5.0
Behavior5/5

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

Beyond the readOnlyHint annotation, the description adds valuable behavioral details: the data is EOD-frozen from PSE Edge's chart endpoint, and a one-time fetch may include a live bar flagged stale=true with an explanatory meta.note. This discloses caching and freshness behavior not captured by annotations.

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

Conciseness5/5

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

The description is compact and front-loaded: a one-sentence purpose, followed by a terse paragraph of relevant details. Every sentence adds value—date format, default window, data source, staleness caveat—with no redundancy.

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

Completeness5/5

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

The tool has an output schema and simple parameters; the description covers the essential context: data source, time zone or date format, default window, and the stale-bar edge case. This is complete for a read-only historical data tool.

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?

Schema coverage for parameters is 0%, but the description compensates by documenting date format (ISO YYYY-MM-DD), default range (~6 months), and symbol identity (PSE stock symbol). This gives the agent meaning for all three parameters beyond the bare schema.

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 opens with a specific verb-resource statement: 'Get daily OHLC price history for a PSE stock symbol.' This clearly identifies the tool's function and scope, and differentiates it from sibling tools like get_stock_quote (current quote) and search_companies.

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

Usage Guidelines4/5

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

The description establishes clear usage context: daily OHLC historical data for PSE symbols, with default date range and ISO format. It does not explicitly name alternative tools or state when not to use it, but the context is sufficient for an agent to select it for historical OHLC requests.

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

get_server_versionServer versionA
Read-only
Inspect

Get the deployed version of this MCP server (pse-edge-mcp).

Reports the running server's own release version — the same value the /health endpoint and serverInfo carry — not PSE Edge data. There is no meta block: meta is a data-freshness contract, and a version has no as_of or valid_until.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.7/5.0
Behavior5/5

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

Beyond the readOnlyHint annotation, the description explains a subtle behavioral detail: there is no meta block because meta is a data-freshness contract and a version has no as_of or valid_until. This adds valuable context not available from annotations alone.

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

Conciseness5/5

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

The description is compact and front-loaded, with the primary purpose in the first sentence. The subsequent sentences add relevant detail about what the version refers to and the absence of a meta block, with no redundancy or filler.

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

Completeness5/5

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

For a simple, parameterless tool with an output schema and safe read-only annotations, the description fully covers the tool's purpose, scope, and return behavior. It even explains why a typical data-freshness field is absent, making it complete for an agent.

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

Parameters4/5

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

The tool has zero parameters, and the schema is empty (100% coverage). The description correctly notes the absence of a meta block but doesn't need to explain parameters since there are none. Baseline for zero parameters is 4, and this description adequately addresses the tool's scope.

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: 'Get the deployed version of this MCP server (pse-edge-mcp).' It explicitly distinguishes itself from sibling data tools by noting it returns the server's own release version, not PSE Edge data.

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

Usage Guidelines4/5

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

The description provides clear context for when to use this tool: when you need the server's deployed version. It also clarifies that this is not for PSE Edge data, effectively excluding data-related use cases, and points to alternative sources (health endpoint, serverInfo) for the same value.

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

get_stock_quoteStock quote (EOD)A
Read-only
Inspect

Get the latest end-of-day quote for a PSE stock symbol (e.g. SM, AREIT, BDO).

Includes price, change, 52-week range, market cap, shares, and the full set of fields PSE Edge publishes. Data is EOD-frozen (see meta). If the market is open and this symbol has never been cached, a one-time fetch serves identity plus previous_close ONLY (the last settled price before the session — every other field is null), flagged stale=true with a meta.note saying the value is not realtime — relay that caveat when presenting the price.

ParametersJSON Schema
NameRequiredDescriptionDefault
symbolYes

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.5/5.0
Behavior5/5

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

Beyond the readOnlyHint annotation, the description discloses the EOD-frozen nature of the data and details the special case where an uncached symbol during market hours returns only identity plus previous_close with null fields and stale=true. This is substantial additional behavioral context that the annotation alone does not provide.

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

Conciseness5/5

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

The description is compact yet covers the core function, included fields, and a critical caching caveat in a single well-organized paragraph. Every sentence adds actionable information without filler or redundant repetition of the schema.

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

Completeness5/5

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

The description fully covers the tool's behavior, including the edge-case stale response and a pointer to the meta field in the output schema. Since an output schema exists, return values are documented, and the description complements it with usage-level detail.

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

Parameters4/5

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

The schema's symbol parameter has no description (schema description coverage 0%), but the tool description specifies it is a PSE stock symbol and gives valid examples (SM, AREIT, BDO). This compensates for the missing schema description and gives the agent concrete formatting guidance.

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 opens with a specific verb phrase 'Get the latest end-of-day quote for a PSE stock symbol' and includes concrete examples (SM, AREIT, BDO). This clearly distinguishes it from siblings like get_price_history by focusing on the latest EOD snapshot rather than historical time series.

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

Usage Guidelines3/5

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

The description implies usage for obtaining current EOD quotes but does not explicitly discuss when to prefer this tool over alternatives like get_price_history. No exclusions or when-not-to-use guidance is provided, leaving the agent to infer the appropriate context from the sibling names.

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

search_companiesSearch companiesA
Read-only
Inspect

Search PSE-listed companies by name or ticker symbol.

Returns matches with company_id, name, symbol. Use the symbol with the other tools.

ParametersJSON Schema
NameRequiredDescriptionDefault
queryYes

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.6/5.0
Behavior4/5

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

Annotations already declare readOnlyHint=true, so the safety profile is known. The description adds valuable context by specifying the return fields (company_id, name, symbol) and how to use the output with other tools, going beyond what annotations provide.

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

Conciseness5/5

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

The description is two sentences, front-loaded with the purpose, then gives output and usage guidance. Every sentence earns its place with no redundant or vague wording.

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

Completeness5/5

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

For a simple one-parameter search tool with an output schema, the description covers purpose, input semantics, output structure, and integration with sibling tools. It is complete and leaves no major gaps.

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 provides no description for the 'query' parameter, but the description fully defines it as a name or ticker symbol, completely covering the semantics. This compensates for the 0% schema description coverage.

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 a specific verb+resource: 'Search PSE-listed companies by name or ticker symbol.' It distinguishes this tool from sibling tools like search_disclosures or get_stock_quote by focusing on company lookup and explicitly mentioning the use of the symbol with other tools.

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

Usage Guidelines4/5

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

The phrase 'Use the symbol with the other tools' gives clear context that this is the entry point for obtaining company symbols, but it does not explicitly mention when not to use it or name alternatives such as search_disclosures. It implies usage well but lacks explicit exclusions.

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

search_disclosure_fulltextFull-text disclosure searchA
Read-only
Inspect

Full-text search inside disclosure attachments, returning matching snippets.

Use this to find wording within filings ("share buyback", "force majeure"). For "what did company X disclose recently", use search_disclosures instead.

IMPORTANT: PSE Edge's own full-text index is partial and lags behind — at last verification it covered roughly 2023-2025 and held nothing from 2026. Results are relevance-ordered (not chronological), 10 per page. The result includes a coverage_note; relay that limitation rather than reporting "no disclosures exist".

ParametersJSON Schema
NameRequiredDescriptionDefault
pageNo
symbolNo
keywordYes
end_dateNo
start_dateNo
subject_titleNo

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.7/5.0
Behavior5/5

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

The description discloses critical behavioral traits beyond the readOnlyHint and openWorldHint annotations: the partial index coverage (2023–2025, nothing from 2026), relevance-ordered results, 10 per page, and the inclusion of a coverage_note. It also instructs the agent to relay the limitation, which is rich, actionable context.

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

Conciseness5/5

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

The description is appropriately sized and front-loaded with the core function, followed by usage guidance and a key limitation note. Every sentence provides value, and the structure is easy to parse.

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

Completeness5/5

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

Given the tool's complexity (6 params, output schema, annotations), the description covers the essential context: purpose, alternative usage, index limitations, ordering, pagination, and the coverage_note. With an output schema present, the description need not elaborate on return values beyond what is already provided.

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

Parameters3/5

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

Schema description coverage is 0%, so the description carries the burden. It adds meaning for the main parameter 'keyword' by indicating the search term for wording inside filings, but it does not explain optional parameters like symbol, dates, or subject_title. The schema titles provide minimal semantics, so the description only partially compensates for the lack of parameter documentation.

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 performs full-text search inside disclosure attachments and returns matching snippets, distinguishing it from sibling search_disclosures by explicitly noting the alternative use case. The verb 'search' and resource 'disclosure attachments' are specific and unambiguous.

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?

Explicit usage guidance is given: 'Use this to find wording within filings' and 'For "what did company X disclose recently", use search_disclosures instead.' This directly instructs when to use this tool versus the sibling, satisfying the criteria for alternatives and exclusions.

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

search_disclosuresSearch disclosuresA
Read-only
Inspect

Search PSE company disclosures (announcements, material information, notices).

Returns disclosure metadata — pass a hit's edge_no to get_disclosure for attachment links. 50 results per page; the result reports total/pages/has_more so you can request the next page directly.

  • symbol only: that company's full disclosure history, newest first.

  • date range (ISO YYYY-MM-DD): all companies' disclosures in that window, or one company's if symbol is also given. Defaults to the last 30 days when neither symbol nor dates are supplied.

  • template: filter by disclosure type as free text, e.g. "Press Release", "Cash Dividend", "Material Information".

A given search hits PSE Edge once and is then served from storage until the next 15:00 Manila close, so a disclosure filed today appears when its query is first asked — or after the close if that query was already cached (see meta).

ParametersJSON Schema
NameRequiredDescriptionDefault
pageNo
symbolNo
end_dateNo
templateNo
start_dateNo

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A5/5.0
Behavior5/5

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

Annotations declare readOnlyHint and openWorldHint, but the description adds significant behavioral context: caching until next 15:00 Manila close, pagination specifics (total/pages/has_more), and the fact that a query may serve stale data if already cached. No contradiction with annotations.

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

Conciseness5/5

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

The description is appropriately sized, uses bullet points for clarity, and every sentence adds value. It is well-structured and front-loaded with the core purpose, then expands into usage and caching details without redundancy.

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

Completeness5/5

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

Given the tool's complexity (5 optional parameters, pagination, caching, output schema), the description covers all essential aspects: what it returns, how to paginate, parameter semantics, and behavioral nuances. The output schema exists and the description complements it without needing to explain return values in depth.

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?

Schema coverage is 0%, so the description fully compensates by explaining all five parameters: symbol (company's full history), start_date/end_date (ISO YYYY-MM-DD, date range), template (free-text filter), and page (implicitly via 'request the next page directly'). Every parameter is semantically clarified.

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 searches PSE company disclosures (announcements, material information, notices), specifies it returns disclosure metadata, and distinguishes it from get_disclosure by noting edge_no is for attachment links. This is a specific verb+resource+scope with clear differentiation from siblings.

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?

Provides detailed usage patterns: symbol-only, date range, template filter, defaults when neither symbol nor dates are supplied, pagination approach (50 results per page, next page directly), and caching behavior. It also implies alternatives (get_disclosure for attachments) and clarifies when results appear relative to the Manila close.

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

validate_symbolValidate ticker symbolA
Read-only
Inspect

Check whether a ticker symbol is a real PSE-listed company. Cheap.

Use this — NOT search_companies — when you only need to know whether a symbol is valid before calling another tool, or to confirm a symbol a user typed. Returns valid true/false plus the company name and id when it exists, instead of the ranked list of near-matches search_companies returns.

Matching is exact and case-insensitive: "areit" and "AREIT" both resolve, while "ARE" does not match "AREIT". An unknown symbol is valid: false with null fields, not an error.

Cached after the first lookup and refreshed daily (see meta).

ParametersJSON Schema
NameRequiredDescriptionDefault
symbolYes

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.9/5.0
Behavior5/5

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

The description goes well beyond the readOnlyHint annotation by detailing exact matching behavior ('case-insensitive... "areit" and "AREIT" both resolve'), unknown-symbol handling ('valid: false with null fields, not an error'), and caching ('Cached after the first lookup and refreshed daily'). These are non-obvious behaviors an agent needs to know.

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

Conciseness5/5

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

The description is compact yet information-dense, using short paragraphs and a front-loaded purpose statement. Every sentence adds value—purpose, cost hint, usage alternative, return behavior, matching rules, and caching—with no repetition or filler.

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

Completeness5/5

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

Despite having an output schema, the description still explains the key return components (valid flag, company name/id, null fields) and error behavior. It also covers caching, performance, and provides differentiation from the most relevant sibling, making it complete for decision-making.

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

Parameters4/5

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

With 0% schema coverage, the description compensates by defining the sole parameter ('ticker symbol') and illustrating semantics with case-sensitivity examples. It does not provide exhaustive format restrictions, but the examples and matching rules give sufficient meaning for correct use.

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 opens with a clear, specific purpose: 'Check whether a ticker symbol is a real PSE-listed company.' It uses a strong verb ('check') and explicitly differentiates from the sibling tool search_companies by contrasting the return type (valid true/false vs. ranked list of near-matches).

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?

Usage guidance is explicit: 'Use this — NOT search_companies — when you only need to know whether a symbol is valid before calling another tool, or to confirm a symbol a user typed.' It also names the alternative and explains why this tool is preferred, giving clear decision criteria.

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

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections. Dates show when Glama detected each change.

  1. 2 tool updatesv0.16.1
    • Changedget_disclosure1 field changed
      • addedInput schema / properties / max_files
        Added value: +{
        +  "default": 20,
        +  "title": "Max Files",
        +  "type": "integer"
        +}
    • Addedget_server_version
  2. 12 tool updatesv0.10.0
    • First observedget_company_profile
    • First observedget_disclosure
    • First observedget_dividends_and_rights
    • First observedget_financial_highlights
    • First observedget_indices
    • First observedget_market_summary
    • First observedget_price_history
    • First observedget_stock_quote
    • First observedsearch_companies
    • First observedsearch_disclosure_fulltext
    • First observedsearch_disclosures
    • First observedvalidate_symbol

TDQS

A4.6/5.0
Disambiguation5/5

Each tool targets a distinct resource and action: search vs. validate, quote vs. history, disclosure search vs. full-text search, and separate tools for profile, financials, dividends, indices, and market summary. There is no overlap in purpose; even similar tools like validate_symbol and search_companies are explicitly differentiated in descriptions.

Naming Consistency5/5

All tools follow a consistent verb_noun snake_case pattern: search_, get_, validate_. The verbs are predictable and the nouns clearly describe the target resource. No mixed conventions or vague names.

Tool Count5/5

13 tools is well within the ideal 3-15 range for a domain-specific data server. Each tool addresses a distinct aspect of PSE data (company lookup, quotes, history, disclosures, financials, dividends, indices, market summary), and none feel redundant or unnecessary.

Completeness5/5

The tool surface comprehensively covers the PSE Edge data domain: company identification, validation, pricing data, price history, disclosure search (metadata and full-text), disclosure retrieval, company profile, financial highlights, dividends/rights, index levels, and a market-wide summary. Workflows from search to detail retrieval are complete, and the only non-data tool (get_server_version) is a standard meta operation.

Maintenance

ActivityMaintained
ResponsivenessSyncing

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    A
    maintenance
    Read-only MCP server for Stockbit (IDX market data) providing broker summary/bandarmology, quotes, top movers, orderbook, fundamentals, and sentiment using your own session. Unofficial and does not place orders.
    8
    41
    710
    23
    MIT

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/phdwight/pse-edge-mcp'

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