Astro MCP
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Astro MCPWhat's my natal chart for 1990-06-15 20:00 in LA?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Astro MCP (@lhk714/astro-mcp)
Deterministic modern Western astrology natal-chart Model Context Protocol (MCP) server: an
astronomy-engineephemeris verified against JPL Horizons, honest degradation when the birth time is unknown, and Chinese-first output.
0. Scope: this is MODERN astrology
This project implements modern Western astrology, not traditional / Hellenistic astrology. That decision drives most of the defaults below, so it needs to be stated before anything else:
Choice | Belongs to |
Uranus, Neptune, Pluto | Modern (classical astrology uses only the seven visible bodies) |
Chiron, Lilith | Modern |
Orbs keyed to aspect type | Modern (classical astrology keys orbs to each body's own light — moiety) |
Placidus houses by default | Modern mainstream |
Kept: whole-sign as an option, day/night (sect) for the Part of Fortune | Classical elements this project still keeps |
Explicitly out of scope: triplicity/term/face (the finer three layers of essential dignity), classical moiety orbs, Arabic parts other than the Part of Fortune, antiscia, and rulership-based (七曜/命主) interpretation. If you need traditional technique, this is not the tool — better that than a silent, wrong approximation.
Related MCP server: Swiss Ephemeris MCP Server
🌟 Overview
Western astrology's ephemeris ecosystem has two real engines: Swiss Ephemeris (AGPL, incompatible with an MIT-licensed npm package unless every consumer also open-sources) and astronomy-engine (MIT, but with no built-in asteroid ephemeris, no true lunar node, and no Lilith). auseklis wraps astronomy-engine but ships several defects (hard-coded zero latitude/declination for nodes and Lilith, a naive Ascendant formula that returns the Descendant above the polar circle, a small-body integration that drifts 100+ degrees over decades).
This project keeps what auseklis gets right (its house solvers and ayanamsa tables — see Credits) and rebuilds what it doesn't: real small-body ephemerides via GravitySimulator seeded from JPL state vectors, a true lunar node derived from the Moon's actual orbital angular momentum, a real (non-zero) Lilith latitude, and an east-horizon-corrected Ascendant that stays valid inside the polar circle.
Birth Wall Clock + IANA Timezone (1990-06-15 20:00 America/Los_Angeles)
│
▼
wall-clock → UTC instant
(DST-safe: spring gap errors, fall fold via dstFold)
│
▼
Sidereal time + geographic location
│
┌───────────┴───────────┐
▼ ▼
Ascendant / Midheaven Body ephemerides
(east-horizon corrected, (astronomy-engine majors,
valid past the polar GravitySimulator small
circle) bodies, true node, Lilith)
│ │
▼ ▼
House cusps Signs / houses / aspects /
(Placidus, falls back essential dignity
to Porphyry above the
polar circle)Unlike Zi Wei Dou Shu or Bazi, Western astrology's clock does not need a true-solar-time correction: house cusps are driven by sidereal time, which already bakes in longitude. Applying a second longitude correction on top would double-count it — the single largest and easiest-to-miss difference from this project's sibling servers (see Sibling servers below).
🎯 The core promise: never fake a birth time
A large share of real users do not know their exact birth time. Most tools either refuse outright or silently assume 12:00 noon — producing an Ascendant that looks completely normal and is almost certainly wrong (the Ascendant moves about 1 degree every 4 minutes; a wrong guess of even 20 minutes can move it into the wrong sign). This project never does that. Instead there are three input modes, selected by whether clockTime / clockTimeRange is present:
Mode | Trigger | Behaviour |
A — exact |
| Full chart, no degradation. |
B — window |
| The Ascendant/Midheaven/houses/Part of Fortune reduce to candidate sign segments with sub-range timestamps, found by bisection ( |
C — date only | Neither given |
|
partOfFortune deserves a special callout: it is neither "an angle" nor "a house" field, so it is the one most likely to be forgotten and returned anyway with a fabricated Ascendant baked in. Its day/night (sect) determination and its starting point both depend on the Ascendant, so it is omitted in modes B and C without exception.
🚀 Quickstart
bunx @lhk714/astro-mcp@latestnpx -y @lhk714/astro-mcp@latest⚙️ MCP Client Configuration
{
"mcpServers": {
"astro": {
"command": "npx",
"args": ["-y", "@lhk714/astro-mcp@latest"]
}
}
}🛠️ Tools
1. calculate_natal
Requires solarDate and a location (place, or longitude + latitude + timezone). clockTime and clockTimeRange are optional and mutually exclusive (see the three modes above); dstFold (0/1) disambiguates a DST fall-back repeated hour.
Conventions — every default below is a specific school's choice, not a neutral one, and is echoed back in diagnostics:
Parameter | Default | Alternative(s) | Belongs to |
|
|
| Modern mainstream, and specifically the convention used by the dominant Chinese-language astrology tools (占星之门/测测/爱星盘) — not |
|
|
| Tropical is the Western mainstream; the two ayanamsas are exposed for Vedic-adjacent use. |
|
|
| Modern preference — |
|
|
| Popular astrology's convention; |
|
|
| Only the five majors (conjunction/sextile/square/trine/opposition) by default. |
|
|
| Minority convention — and the exact spot where |
Orbs | conjunction/opposition 8°, square/trine 7°, sextile 6°, minors 2–3° |
| Modern convention: orbs keyed to aspect type, not to each body's own light (classical moiety). Override any of them per request; the table actually applied comes back in |
|
|
| On by default: standard in modern psychological astrology, with real usage in Chinese social media. |
|
|
| Off by default: low mainstream usage; kept behind one flag since it shares Chiron's own code path. |
|
|
| Off by default: the South Node sits exactly 180° from the North Node, so any aspect to it automatically mirrors one to the North Node at the same orb — reporting both doubles the node rows for zero new fact (astro.com/astro-seek/TimePassages/爱星盘 all hide it by default). Suppression is mirror-aware: an aspect whose 180°-complement is not itself a currently active aspect type (e.g. quintile's 108° complement) is kept rather than dropped, since no equivalent North Node fact would survive to replace it. The South Node's own sign/house/overlay position is unaffected — only its aspects. |
Essential dignity | domicile/exaltation/detriment/fall only | — | The four dignities modern astrology kept from classical technique. Triplicity/term/face are not implemented (out of scope, see §0). Outer-planet rulerships (Uranus→Aquarius, Neptune→Pisces, Pluto→Scorpio) are a modern convention with no classical consensus, and are marked |
|
|
| Chinese-first output (signs, bodies, aspects, dignities, diagnostic prose), matching |
Not supported: a default of clockTime: 12:00 when the birth time is unknown — see The core promise. There is no such default; omit clockTime/clockTimeRange entirely to get the honestly-degraded chart instead.
2. calculate_synastry
合盘 — aspects and house overlays between two natal charts, personA and personB (each the same birth-input shape as calculate_natal — place/longitude/latitude/timezone/dstFold/solarDate/clockTime(-Range) — unknown-time behavior included). The convention switches (houseSystem/zodiac/node/lilith/orbs/minorAspects/declinationAspects/asteroids/chiron/southNodeAspects/lang) apply to both charts and are set and reported once, at the top level — personA/personB do not accept them at all; passing one there is a schema error, not a silent no-op. (An earlier revision let a per-person houseSystem parse and then get silently overridden by the top-level default, and let a per-person orbs leak into the cross-chart aspects while that person's own natal aspects kept the default — the same diagnostics.orbs table then described two different calculations. Rejecting the field outright where it cannot apply is the fix.)
A known time window (clockTimeRange) on either side degrades that side's houses to a candidate list rather than deleting them — overlays.aInB/overlays.bInA entries report houseCandidates instead of a single house — the same "degrade, don't delete" promise calculate_natal's own mode B makes. Only a whole-day-unknown side (no clockTime/clockTimeRange at all) loses that direction's overlay entirely.
House overlays are directional, the same "never fake a birth time" promise extended to a second chart: overlays.aInB places A's bodies into B's houses, which needs at least a known birth time for B (exact or a window); overlays.bInA is the reverse. If a side's time is entirely unknown, only the overlay that needed THAT side's houses is omitted (see diagnostics.omitted) — the other direction still returns normally. Ascendant/Midheaven aspects only exist for whichever side has an exact time, and any aspect touching a Moon on an unknown-time side is flagged uncertain: true (the Moon moves 12–15°/day). applying is never included in the aspect list — two natal charts are each frozen at their own birth instant, so "approaching exactness" across two different epochs isn't a meaningful thing to report.
3. calculate_transits
行运 — where the sky stands right now (or at any given instant) against a natal chart. Takes the exact same flat birth-input fields as calculate_natal, plus an optional target: { solarDate, clockTime, dstFold? }: the instant to compute the transiting sky for. Omit target entirely for "now" (diagnostics.targetSource/diagnostics.targetUtc say which). A target before the birth instant is rejected — that would not be a transit.
The transiting sky is always exact (the target instant is always known); every degradation lives on the natal side. A natal time window (clockTimeRange) degrades transiting[].natalHouse to transiting[].natalHouseCandidates (a candidate list) rather than deleting it. Only an entirely unknown (date-only) natal birth time omits transiting[].natalHouse from every entry (not null — an absent field) and drops aspects to the natal Ascendant/Midheaven; planet-to-planet aspects to the natal chart still work either way, and any surviving aspect touching the natal Moon is flagged uncertain: true whenever the natal time is not exact.
4. find_retrograde
水逆 and friends — retrograde periods for one body within a calendar window (up to 5 years), with no birth data at all: a pure ephemeris query. Each period reports its exact station-retrograde/station-direct instants (startsUtc/endsUtc) and the zodiac sign the retrograde begins in (startSign).
The Sun and Moon are refused outright — "Sun/Moon never retrograde" — rather than silently returning an empty period list, which would read as "not retrograde this window" instead of "this concept doesn't apply here". A window over 5 years is refused rather than grinding through a slow day-by-day scan; split it into smaller windows.
5. lookup_location
Resolves an English city name to longitude, latitude, and IANA timezone across 7,329 cities in 227 countries — the same database ziwei-mcp/bazi-mcp use. Same-named cities are refused with the candidate list, never guessed — not at a 60× population gap ("Los Angeles", California vs Chile's Bío-Bío region), and not when they share a timezone (Columbus OH and GA are both America/New_York, 7.5° of latitude apart, which moves the Ascendant outright). Entries at genuinely identical coordinates still resolve — Kansas City MO and KS are adjacent and share a data point, and recognising that two records describe one location is a fact about the data, not a guess about intent. See Refusals are a contract.
🚢 Releasing
Publishing is automated and runs on a tag:
# bump the version in package.json first, then
git tag -a v0.1.2 -m "v0.1.2" && git push origin v0.1.2The workflow refuses to publish unless the tag matches package.json's
version, and refuses workflow_dispatch runs outright — a manual run has no
tag, so it would publish whatever happens to sit on the branch. It authenticates
through npm's OIDC trusted publishing rather than a long-lived NPM_TOKEN, so
there is no secret to rotate or leak, and provenance is attached automatically.
🚫 Refusals are a contract, not prose
An ambiguous birth place is not an exception — it is a normal outcome the calling agent is expected to handle by asking the user. So it comes back as a structured isError result rather than a thrown error, and the agent never has to parse English to find out what matched:
{
"code": "ambiguous_place", // also: unknown_place, incomplete_coordinates
"message": "…", // still readable, for a human or an LLM
"matched": 4, // TRUE hit count, so a capped list never reads as exhaustive
"candidates": [ { "name": "San Jose", "province": "California", "country": "US",
"latitude": 37.3, "longitude": -121.85, "timezone": "America/Los_Angeles" } ]
}Same-name cities are never resolved by picking the likely one — not even at a 60× population gap ("Los Angeles", US vs Chile), and not even when they share a timezone (Columbus OH and GA are both America/New_York but 7.5° of latitude apart, which moves the Ascendant outright). Entries at genuinely identical coordinates still resolve: recognising that two records describe one location is a fact about the data, not a guess about intent.
Candidates carry identifying fields only. Population is deliberately absent — nobody knows their birthplace by its population; it is a likelihood prior, and publishing it would move the guess this server refuses to make into the agent's prompt, turning a neutral "Ohio or Georgia?" into "Ohio, right?".
Call lookup_location first when a name might be ambiguous; it is cheaper than a refused chart call.
🧭 What this server will not tell you
It calculates; it does not infer. Positions, cusps, aspects, dignities and combinations are table lookups and astronomy — any correct implementation reaches the same answer. Interpretation is not, and this server does not attempt it: no compatibility scores, no "strong/weak" verdicts, no rankings, nothing that would rest on a weight invented here.
The one place a convention is unavoidable — orbs — is a declared input rather
than a hidden judgment: the defaults are the common modern set, every one is
overridable via orbs, and the table actually applied comes back in
diagnostics.orbs. Given the same orbs, any implementation agrees.
Bring your own interpretive layer, or an astrology knowledge base. The facts are all here, and the facts are the part that can be got right.
📏 Accuracy
Measured against JPL Horizons (QUANTITIES=31, light-time-corrected), across 1900–2050–2100:
Major bodies (Sun–Pluto) and lunar nodes: ≤ 1 arcminute across the whole range. The Moon specifically needed
SetDeltaTFunction(DeltaT_JplHorizons)instead ofastronomy-engine's default Delta-T model to hold that bound at 2100 (0.02′ vs 1.31′ with the default model).Small bodies (Chiron + the four asteroids, via
GravitySimulatorseeded from JPL state vectors): ≤ 0.4 arcminutes, comfortably inside the 1.5′ budget the tests enforce. Chiron uses a 4-day integration step; the four inner asteroids (much faster, much closer orbits) need a 0.25-day step to hold that bound — a 4-day step was measured up to 2° off for them, an entire wrong sign.Seeds are stored at 11 epochs (every 20 years, 1900–2100) rather than at J2000 alone, so no integration ever runs more than ~10 years. That was originally a performance fix — a 1900 chart with asteroids took 4.7 s and risked tripping an MCP client timeout, and now takes ~450 ms — but a shorter integration also drifts less, which is why the figures below improved along with the speed.
scripts/pull-seeds.tsregenerates the whole table from JPL.
Body | 1900 | 1950 | 1990 | 2026 | 2050 | 2100 | Step |
Chiron | 0.33′ | 0.32′ | 0.32′ | 0.15′ | 0.28′ | 0.26′ | 4 days |
Ceres | 0.35′ | 0.26′ | 0.26′ | 0.16′ | 0.37′ | 0.15′ | 0.25 days |
Pallas | 0.37′ | 0.16′ | 0.39′ | 0.22′ | 0.05′ | 0.22′ | 0.25 days |
Juno | 0.35′ | 0.04′ | 0.28′ | 0.30′ | 0.27′ | 0.33′ | 0.25 days |
Vesta | 0.04′ | 0.33′ | 0.20′ | 0.16′ | 0.12′ | 0.28′ | 0.25 days |
The true lunar node is independently self-checked against astronomy-engine's own node-crossing events (SearchMoonNode/NextMoonNode): the Moon's ecliptic longitude at a crossing must equal the true node's longitude (or its antipode) to within 1 arcsecond — measured max deviation 0.01″.
🧭 Sibling servers: ziwei-mcp / bazi-mcp
This project does not calculate Zi Wei Dou Shu or Bazi (四柱/八字) — use @lhk714/ziwei-mcp (source) or @lhk714/bazi-mcp (source) for those.
The shared birth-input fields (place, longitude, timezone, dstFold, solarDate, clockTime) and the geographic-resolution layer (lookup_location) are deliberately identical across all three servers, so a request built from the same birth data resolves to the same UTC instant and the same location in every one of them — a Western, Zi Wei, and Bazi chart for one person stay aligned. latitude is the one field this project adds beyond the shared contract: Western astrology's Ascendant and houses need it, and neither sibling does.
What is not shared, on purpose: this project drops the true-solar-time correction those two apply to the clock (see Overview above) — Western house cusps are driven by sidereal time, which already accounts for longitude, so re-applying a longitude correction here would double-count it, not refine it.
🙏 Credits
astronomy-engine(MIT) — the ephemeris engine for every body position,GravitySimulatorfor small bodies, and sidereal time / obliquity / nutation for the angles and house cusps.auseklis(MIT) —src/ephemeris/vendor/houses.ts(the Placidus/Porphyry/Equal/Whole-Sign house solvers, including the polar-circle fallback) andsrc/ephemeris/vendor/sidereal.ts(Lahiri/Fagan-Bradley ayanamsa) are adapted from it, logic unchanged. Full attribution and MIT license text in NOTICE. Its Ascendant formula and small-body/node/Lilith handling are not reused — see §0 and the Overview above for why.
Known limitations
No support for births before 1900 (aligned with
ziwei-mcp/bazi-mcp; pre-1883 civil time zones are not well-defined either).Five small bodies only (Chiron + Ceres/Pallas/Juno/Vesta), not the full minor-planet catalogue — the same code path, so more could be added.
No fixed stars, no Arabic parts beyond the Part of Fortune, no antiscia.
Two ayanamsas only (Lahiri, Fagan-Bradley) — Krishnamurti and others are not implemented.
Synastry and transits are implemented (
calculate_synastry/calculate_transits); progressions and returns (solar/lunar) are not yet.
📜 License
MIT.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- AlicenseNot gradedqualityAmaintenanceA personal astrological server that provides high-precision tools for generating natal charts, transits, and relationship charts using a queryable SQLite database. It enables users to manage multiple profiles, track historical transits, and perform electional astrology for planning events.AGPL 3.0
- FlicenseAqualityDmaintenanceProvides astronomical calculations using the Swiss Ephemeris library, including planetary positions, houses, chart points, and asteroids for any date and location.48
- AlicenseNot gradedqualityDmaintenanceCalculates astrological birth charts including planetary positions, house placements, and aspects based on birth date, time, and location.8741MIT
- FlicenseNot gradedqualityCmaintenanceProvides real astrology and tarot computations using actual ephemeris and a 78-card deck, returning structured data such as natal charts, synastry, transits, and tarot draws without relying on an LLM for astrological facts.
Related MCP Connectors
Sub-arcsecond ephemeris and astrology on NASA JPL DE440: natal, transits, eclipses, Human Design.
BaZi (Chinese Four Pillars) chart calculator. Structured chart data only, no predictions.
Offline observational astronomy: positions, rise/set, moon phases, eclipses, and seasons.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
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/listen-hai/astro-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server