@openephemeris/mcp-server 3.13.6 → 3.13.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/README.md +2 -2
  2. package/dist/backend/client.js +27 -9
  3. package/dist/index.js +33 -32
  4. package/dist/prompts.d.ts +26 -0
  5. package/dist/prompts.js +942 -0
  6. package/dist/tools/apps/bi-wheel-app.js +286 -32
  7. package/dist/tools/apps/bodygraph-app.js +361 -12
  8. package/dist/tools/apps/chart-wheel-app.js +64 -30
  9. package/dist/tools/apps/location-tools.d.ts +1 -0
  10. package/dist/tools/apps/location-tools.js +52 -0
  11. package/dist/tools/index.js +1 -0
  12. package/dist/tools/specialized/acg.js +2 -0
  13. package/dist/tools/specialized/bazi.js +1 -0
  14. package/dist/tools/specialized/bi_wheel.js +1 -0
  15. package/dist/tools/specialized/chart_wheel.js +1 -0
  16. package/dist/tools/specialized/comparative.js +4 -0
  17. package/dist/tools/specialized/eclipse.js +1 -0
  18. package/dist/tools/specialized/electional.js +4 -0
  19. package/dist/tools/specialized/ephemeris_core.js +2 -0
  20. package/dist/tools/specialized/ephemeris_extended.js +8 -0
  21. package/dist/tools/specialized/hd_bodygraph.js +1 -0
  22. package/dist/tools/specialized/hd_cycles.js +2 -0
  23. package/dist/tools/specialized/hd_group.js +2 -0
  24. package/dist/tools/specialized/human_design.js +1 -0
  25. package/dist/tools/specialized/moon.js +2 -0
  26. package/dist/tools/specialized/natal.js +1 -0
  27. package/dist/tools/specialized/progressed.js +1 -0
  28. package/dist/tools/specialized/relocation.js +1 -0
  29. package/dist/tools/specialized/returns.js +3 -0
  30. package/dist/tools/specialized/synastry.js +1 -0
  31. package/dist/tools/specialized/transits.js +1 -0
  32. package/dist/tools/specialized/vedic.js +1 -0
  33. package/dist/tools/specialized/venus_star_points.js +6 -0
  34. package/dist/ui/bi-wheel.html +7124 -60
  35. package/dist/ui/bodygraph.html +7057 -508
  36. package/dist/ui/chart-wheel.html +7355 -565
  37. package/package.json +1 -1
@@ -178,10 +178,17 @@ function buildHdModelPayload(data, birthParams) {
178
178
  });
179
179
  }
180
180
  // Personality / Design gates — API nests them under personality.activations / design.activations
181
- const rawP = data.personality?.activations ?? data.personality_gates ?? data.conscious_gates ?? [];
182
- const rawD = data.design?.activations ?? data.design_gates ?? data.unconscious_gates ?? [];
183
- const personality_gates = [...new Set(extractGateNums(rawP))].sort((a, b) => a - b);
184
- const design_gates = [...new Set(extractGateNums(rawD))].sort((a, b) => a - b);
181
+ // Ensure we pick up the map if available or the array
182
+ let rawP = data.personality?.activations ?? data.personality_activations ?? data.personality_gates ?? data.conscious_gates ?? {};
183
+ let rawD = data.design?.activations ?? data.design_activations ?? data.design_gates ?? data.unconscious_gates ?? {};
184
+ // if rawP/D are objects instead of arrays, extract values for gates array
185
+ const pGateList = Array.isArray(rawP) ? rawP : Object.values(rawP);
186
+ const dGateList = Array.isArray(rawD) ? rawD : Object.values(rawD);
187
+ const personality_gates = [...new Set(extractGateNums(pGateList))].sort((a, b) => a - b);
188
+ const design_gates = [...new Set(extractGateNums(dGateList))].sort((a, b) => a - b);
189
+ // keep the maps
190
+ const personality_activations = !Array.isArray(rawP) && typeof rawP === 'object' ? rawP : undefined;
191
+ const design_activations = !Array.isArray(rawD) && typeof rawD === 'object' ? rawD : undefined;
185
192
  return {
186
193
  type,
187
194
  profile,
@@ -193,6 +200,8 @@ function buildHdModelPayload(data, birthParams) {
193
200
  active_channels,
194
201
  personality_gates,
195
202
  design_gates,
203
+ personality_activations,
204
+ design_activations,
196
205
  _birth_params: birthParams,
197
206
  };
198
207
  }
@@ -242,6 +251,12 @@ registerTool({
242
251
  type: "string",
243
252
  description: "Location name for display only (e.g. 'New York, NY').",
244
253
  },
254
+ timezone: {
255
+ type: "string",
256
+ description: "IANA timezone name for the birth location (e.g. 'America/New_York'). " +
257
+ "Highly recommended for accuracy if providing local birth time without a UTC offset. " +
258
+ "Enables correct UTC conversion for precise Personality and Design positions.",
259
+ },
245
260
  },
246
261
  required: ["datetime"],
247
262
  },
@@ -286,16 +301,9 @@ registerTool({
286
301
  content: [
287
302
  { type: "text", text: summary },
288
303
  { type: "text", text: JSON.stringify({ ...modelPayload, server_version: SERVER_VERSION }) },
289
- {
290
- type: "resource",
291
- resource: {
292
- uri: BODYGRAPH_RESOURCE_URI,
293
- mimeType: BODYGRAPH_MIME_TYPE,
294
- text: getBodygraphBundle(),
295
- },
296
- },
297
304
  ],
298
305
  _meta: {
306
+ "ui/resourceUri": BODYGRAPH_RESOURCE_URI,
299
307
  ui: { resourceUri: BODYGRAPH_RESOURCE_URI },
300
308
  },
301
309
  };
@@ -304,6 +312,49 @@ registerTool({
304
312
  return { content: [{ type: "text", text: summary }] };
305
313
  },
306
314
  });
315
+ // ── Tool: bodygraph_recalculate ──────────────────────────────────────────────
316
+ registerTool({
317
+ name: "bodygraph_recalculate",
318
+ description: "Recalculates a Human Design bodygraph with new parameters.",
319
+ inputSchema: {
320
+ type: "object",
321
+ properties: {
322
+ datetime: { type: "string" },
323
+ latitude: { type: "number" },
324
+ longitude: { type: "number" },
325
+ location: { type: "string" },
326
+ timezone: {
327
+ type: "string",
328
+ description: "IANA timezone name (e.g. 'America/New_York') for accurate UTC conversion.",
329
+ },
330
+ },
331
+ required: ["datetime"],
332
+ },
333
+ annotations: { title: "Recalculate Bodygraph", readOnlyHint: true, openWorldHint: false },
334
+ _meta: { ui: { resourceUri: BODYGRAPH_RESOURCE_URI, visibility: ["app"] } },
335
+ handler: async (args) => {
336
+ const client = getActiveClient();
337
+ const datetime = ensureTimezone(String(args.datetime));
338
+ const body = {
339
+ birth_datetime_utc: datetime,
340
+ };
341
+ if (args.latitude != null)
342
+ body.latitude = args.latitude;
343
+ if (args.longitude != null)
344
+ body.longitude = args.longitude;
345
+ const chartData = await client.request("POST", "/human-design/chart", {
346
+ data: body,
347
+ });
348
+ const chart = chartData.chart ?? chartData;
349
+ const modelPayload = buildHdModelPayload(chart, {
350
+ datetime,
351
+ location: args.location ?? null,
352
+ });
353
+ return {
354
+ content: [{ type: "text", text: JSON.stringify({ ...modelPayload, server_version: SERVER_VERSION }) }],
355
+ };
356
+ },
357
+ });
307
358
  // ── Tool: hd_on_center_click ──────────────────────────────────────────────
308
359
  registerTool({
309
360
  name: "hd_on_center_click",
@@ -648,3 +699,301 @@ const HD_CHANNEL_DATA = {
648
699
  "59-6": { name: "Intimacy", circuit: "Tribal (Defence)", energy_type: "generated", description: "The channel of intimacy — penetrating auras for mating and reproduction. Emotional authority governing who is let past one's auric boundaries." },
649
700
  "60-3": { name: "Mutation", circuit: "Individual (Knowing)", energy_type: "generated", description: "The channel of mutation — energy that transforms through accepted limitation. Initiates new patterns in a discontinuous, pulse-like fashion." },
650
701
  };
702
+ // ── Planet biographical themes (shared across tools) ─────────────────────────────
703
+ const HD_PLANET_HD_THEMES = {
704
+ Sun: {
705
+ conscious: "The Sun represents your **core life purpose and identity** in HD. The Personality Sun gate is the most important " +
706
+ "activation in your chart — it is the primary frequency your aura broadcasts into the world. " +
707
+ "This is the theme you actively embody and are consciously directing through life.",
708
+ unconscious: "The **Design Sun** is the most powerful unconscious driver in your chart. " +
709
+ "Calculated 88 days before birth (the prenatal solar arc), it represents a deeply embedded " +
710
+ "genetic frequency — the way your body moves through the world without conscious awareness. " +
711
+ "Others will often notice this quality before you do.",
712
+ },
713
+ Earth: {
714
+ conscious: "**Earth** grounds and stabilises your Sun energy. The Personality Earth gate shows the theme " +
715
+ "that anchors you in material reality — what brings you into your body and provides stability " +
716
+ "when life becomes unsteady. Working with your Earth gate is a powerful grounding practice.",
717
+ unconscious: "The **Design Earth** reveals an unconscious grounding force — the energetic counterbalance " +
718
+ "to your Design Sun. This theme provides biological stability and is deeply wired into your body's functioning.",
719
+ },
720
+ Moon: {
721
+ conscious: "The **Moon** shows the emotional and instinctual flavour of your Personality. " +
722
+ "This gate reveals the needs, changes, and rhythms you are consciously aware of — " +
723
+ "what nourishes you emotionally and how you respond to the cycles of life.",
724
+ unconscious: "The **Design Moon** governs your body's unconscious biological rhythms. " +
725
+ "It influences your physical cycles, instinctive reactions, and the deeper tides of your body consciousness " +
726
+ "that operate beneath the surface of your awareness.",
727
+ },
728
+ Mercury: {
729
+ conscious: "**Mercury** shows how your Personality mind communicates. This gate reveals the flavour of your " +
730
+ "thinking, speaking, and processing — the distinct frequency through which you engage ideas and share your perspective.",
731
+ unconscious: "**Design Mercury** represents the unconscious communication patterns embedded in your body. " +
732
+ "These are ways of processing and articulating that others see in you before you notice them in yourself.",
733
+ },
734
+ Venus: {
735
+ conscious: "**Venus** in Human Design represents your Personality's set of values — what you love, what you value, " +
736
+ "and the principles that guide your aesthetic and ethical choices. This is the code of your heart's standards.",
737
+ unconscious: "**Design Venus** reveals deep, unconscious values embedded in the body — " +
738
+ "the instinctive sense of beauty and rightness that operates beneath conscious awareness, " +
739
+ "often influencing what you are drawn to without knowing why.",
740
+ },
741
+ Mars: {
742
+ conscious: "**Mars** reveals the quality of your immature, unconditioned energy — the raw drive that fuels your actions. " +
743
+ "In HD it represents a key aspect of the immaturity we carry, which through experience matures " +
744
+ "into its highest expression. This shows what drives you to act and assert in the world.",
745
+ unconscious: "**Design Mars** shows the unconscious somatic drive — an instinctive, body-level impulse that moves you " +
746
+ "to act before the mind has a chance to deliberate. This energy can surprise even yourself.",
747
+ },
748
+ Jupiter: {
749
+ conscious: "**Jupiter** represents the law of your Personality — the karmic gifts and the areas of life where " +
750
+ "higher wisdom, abundance, and justice flow. It shows where and how you expand beyond the self-limiting " +
751
+ "patterns of your conditioning.",
752
+ unconscious: "**Design Jupiter** shows the unconscious grace embedded in your design — the karmic correction " +
753
+ "your body naturally applies when you are living true to your nature.",
754
+ },
755
+ Saturn: {
756
+ conscious: "**Saturn** in HD is the law of the Personality — the karmic discipline and structure that teaches " +
757
+ "through limitation. This is where and how you are tested — and where, when discipline is met, " +
758
+ "mastery and authority are earned.",
759
+ unconscious: "**Design Saturn** shows the unconscious discipline encoded in the body — restrictions and structures " +
760
+ "that operate below awareness, shaping what the body instinctively avoids or seeks to master.",
761
+ },
762
+ Uranus: {
763
+ conscious: "**Uranus** represents the Personality's awakening theme — the particular flavour of disruption, " +
764
+ "innovation, and revolution that is uniquely yours. Where this gate activates, you may be ahead of your time.",
765
+ unconscious: "**Design Uranus** shows the collective mutation frequency encoded in the body — " +
766
+ "an unconscious genius that may awaken suddenly, or a restlessness that drives the body toward change without obvious reason.",
767
+ },
768
+ Neptune: {
769
+ conscious: "**Neptune** represents the Personality's visionary or confusional theme — where dreams, ideals, " +
770
+ "and illusions are most acutely felt. This gate shows where you seek transcendence and how you navigate glamour vs. clarity.",
771
+ unconscious: "**Design Neptune** shows a deep unconscious spiritual frequency — " +
772
+ "a channel through which higher ideals and subtle realities filter into the body below the threshold of awareness.",
773
+ },
774
+ Pluto: {
775
+ conscious: "**Pluto** represents the Personality's evolutionary theme — the irreversible transformation that defines " +
776
+ "your generation's karmic task. This gate shows where you are destined to encounter, survive, and embody profound change.",
777
+ unconscious: "**Design Pluto** shows the collective transformation frequency embedded deep in the body — " +
778
+ "an unconscious driver of evolutionary pressure that often manifests as crisis, catharsis, or deep cellular-level transformation.",
779
+ },
780
+ "North Node": {
781
+ conscious: "The **North Node** in Human Design represents your Personality's karmic direction — " +
782
+ "the evolutionary frontier you are meant to move toward in this lifetime. " +
783
+ "This gate describes the territory of growth, the unfamiliar that calls you forward.",
784
+ unconscious: "The **Design North Node** shows the unconscious genetic direction — " +
785
+ "the biological path forward that the body moves toward instinctively, often without conscious recognition.",
786
+ },
787
+ "South Node": {
788
+ conscious: "The **South Node** represents the Personality's karmic past — " +
789
+ "the themes of prior experience and natural gifts that can become comfortable traps if over-relied upon. " +
790
+ "This gate shows the territory of ingrained habit and the origin point of this life's journey.",
791
+ unconscious: "The **Design South Node** shows the unconscious genetic inheritance — " +
792
+ "deeply instilled somatic patterns and reflexes from ancestral or past-life experience, operating beneath awareness.",
793
+ },
794
+ Chiron: {
795
+ conscious: "**Chiron** in Human Design is the Wounded Healer archetype — " +
796
+ "the gate where deepest vulnerability resides, and where the greatest potential for mastery and healing lives. " +
797
+ "The wound and the gift are inseparable here.",
798
+ unconscious: "**Design Chiron** shows an unconscious wound carried in the body — " +
799
+ "a somatic tenderness that, when honoured rather than suppressed, becomes the source of profound empathy and healing capacity.",
800
+ },
801
+ };
802
+ // ── Tool: hd_on_planet_click ─────────────────────────────────────────────────────────
803
+ registerTool({
804
+ name: "hd_on_planet_click",
805
+ description: "Event handler called when the user clicks a planet row in the Personality or Design sidebar column " +
806
+ "of the Human Design Bodygraph. " +
807
+ "Returns the planet's archetypal meaning in Human Design, its Personality vs Design context " +
808
+ "(conscious vs unconscious), the gate/line it activates, and an interpretation of that placement. " +
809
+ "This tool is called automatically by the bodygraph UI; you do not need to call it directly.",
810
+ inputSchema: {
811
+ type: "object",
812
+ properties: {
813
+ planet: {
814
+ type: "string",
815
+ description: "Planet name (e.g. 'Sun', 'Moon', 'Mars', 'Chiron').",
816
+ },
817
+ gate: {
818
+ type: "number",
819
+ description: "Gate number this planet activates (1–64).",
820
+ },
821
+ line: {
822
+ type: "number",
823
+ description: "I Ching line number (1–6).",
824
+ },
825
+ column: {
826
+ type: "string",
827
+ enum: ["personality", "design"],
828
+ description: "Which column the planet is in — 'personality' (conscious/black) or 'design' (unconscious/red).",
829
+ },
830
+ },
831
+ required: ["planet", "column"],
832
+ },
833
+ annotations: { title: "Planet Sidebar Interpretation", readOnlyHint: true, openWorldHint: false },
834
+ _meta: { ui: { resourceUri: BODYGRAPH_RESOURCE_URI, visibility: ["app"] } },
835
+ handler: async (args) => {
836
+ const planet = String(args.planet);
837
+ const gate = args.gate != null ? Number(args.gate) : null;
838
+ const line = args.line != null ? Number(args.line) : null;
839
+ const column = String(args.column ?? "personality");
840
+ const isPersonality = column === "personality";
841
+ const columnLabel = isPersonality ? "Personality (Conscious)" : "Design (Unconscious)";
842
+ const columnColor = isPersonality ? "🔴" : "⚪";
843
+ const lineRef = line != null ? `.${line}` : "";
844
+ const gateRef = gate != null ? ` → Gate ${gate}${lineRef}` : "";
845
+ const gateInfo = gate != null ? HD_GATE_DATA[gate] : null;
846
+ const planetHD = HD_PLANET_HD_THEMES[planet];
847
+ const planetContext = planetHD
848
+ ? (isPersonality ? planetHD.conscious : planetHD.unconscious)
849
+ : `${capitalize(planet)} activates the themes of the gate it touches in this column.`;
850
+ const gateContext = gateInfo
851
+ ? (`\n\n**Gate ${gate}${lineRef} — ${gateInfo.name}**\n` +
852
+ `${gateInfo.keynote}\n\n` +
853
+ `**Gift:** ${gateInfo.gift} \n**Shadow:** ${gateInfo.shadow} \n**Circuit:** ${gateInfo.circuit}` +
854
+ (line != null && gateInfo.lines?.[line] ? `\n\n**Line ${line}:** ${gateInfo.lines[line]}` : ""))
855
+ : (gate != null ? `\n\nGate ${gate}${lineRef} activates in this column.` : "");
856
+ return {
857
+ content: [{
858
+ type: "text",
859
+ text: `**${capitalize(planet)}${gateRef}** ${columnColor} *${columnLabel}*\n\n` +
860
+ planetContext +
861
+ gateContext,
862
+ }],
863
+ };
864
+ },
865
+ });
866
+ // ── Tool: hd_on_variable_click ───────────────────────────────────────────────────────
867
+ registerTool({
868
+ name: "hd_on_variable_click",
869
+ description: "Event handler called when the user clicks a Variable arrow in the Human Design Bodygraph. " +
870
+ "The Variable describes the four arrow directions attached to the Personality Sun/Earth " +
871
+ "and Design Sun/Earth positions, encoding the PHS (Primary Health System) — the optimal conditions " +
872
+ "for the body-mind to receive information and make decisions. " +
873
+ "Returns an interpretation of Digestion (right/left Personality Sun arrow), " +
874
+ "Perspective (right/left Personality Earth arrow), " +
875
+ "Environment (right/left Design Sun arrow), and Motivation (right/left Design Earth arrow). " +
876
+ "This tool is called automatically by the bodygraph UI; you do not need to call it directly.",
877
+ inputSchema: {
878
+ type: "object",
879
+ properties: {
880
+ variable: {
881
+ type: "string",
882
+ enum: ["digestion", "perspective", "environment", "motivation"],
883
+ description: "Which of the four Variable arrows was clicked.",
884
+ },
885
+ direction: {
886
+ type: "string",
887
+ enum: ["left", "right"],
888
+ description: "Arrow direction: 'right' = Right Variable (passive/receptive), 'left' = Left Variable (active/assertive).",
889
+ },
890
+ tone: {
891
+ type: "number",
892
+ description: "The specific tone (1–6) of this Variable, if available from the activation.",
893
+ },
894
+ color: {
895
+ type: "number",
896
+ description: "The specific color (1–6) of this Variable, if available from the activation.",
897
+ },
898
+ },
899
+ required: ["variable", "direction"],
900
+ },
901
+ annotations: { title: "Variable Arrow Interpretation", readOnlyHint: true, openWorldHint: false },
902
+ _meta: { ui: { resourceUri: BODYGRAPH_RESOURCE_URI, visibility: ["app"] } },
903
+ handler: async (args) => {
904
+ const variable = String(args.variable).toLowerCase();
905
+ const direction = String(args.direction).toLowerCase();
906
+ const tone = args.tone != null ? Number(args.tone) : null;
907
+ const color = args.color != null ? Number(args.color) : null;
908
+ const VARIABLE_DATA = {
909
+ digestion: {
910
+ right: "**Right Digestion** (Passive/Receptive) \u2014 Your body receives nourishment best in a passive, open way. " +
911
+ "This means eating in environments that feel calm and familiar, allowing appetite to arise naturally, " +
912
+ "and avoiding forced, rushed, or emotionally charged eating. Quality, timing, and atmosphere matter enormously. " +
913
+ "Right Digestion types are often sensitive to the subtleties of environment — " +
914
+ "the right conditions allow optimal absorption of both food and information.",
915
+ left: "**Left Digestion** (Active/Assertive) \u2014 Your body operates best when you engage actively with what you consume. " +
916
+ "This means initiating eating rather than waiting, eating in variety and stimulation, " +
917
+ "and being drawn by appetite and desire rather than routine. " +
918
+ "Left Digestion types often thrive with more novelty, conversation, and engagement at the table — " +
919
+ "your body needs to be activated, not passively nourished.",
920
+ tones: {
921
+ 1: "Tone 1 — Consecutive: best with orderly, sequential eating — simple and unvaried meals in a fixed rhythm.",
922
+ 2: "Tone 2 — Alternating: eat a variety of foods but alternate types — not all mixed together.",
923
+ 3: "Tone 3 — Cyclic: allow the body to cycle through periods of different dietary focuses.",
924
+ 4: "Tone 4 — Calm: eat only when the emotional atmosphere is calm and undisturbed.",
925
+ 5: "Tone 5 — Open: eat in open spaces or with good air circulation; avoid enclosed or stagnant settings.",
926
+ 6: "Tone 6 — Closed: eat in private, quiet, familiar environments — protected from public exposure.",
927
+ },
928
+ },
929
+ perspective: {
930
+ right: "**Right Perspective** (Personal) \u2014 Your mind is designed to receive wisdom and truth through personal, " +
931
+ "lived experience. This is a *Receptive* perspective — the mind is best employed reading the world " +
932
+ "from within your own direct experience rather than abstractly theorizing about what should be.",
933
+ left: "**Left Perspective** (Transpersonal) \u2014 Your mind is designed to understand patterns beyond the personal. " +
934
+ "This is an *Active* perspective — the mind is naturally drawn toward understanding systems, " +
935
+ "humanity, and abstract principles. You often see what others need before seeing your own needs.",
936
+ },
937
+ environment: {
938
+ right: "**Right Environment** (Passive/Receptive) \u2014 Your body thrives in environments that are " +
939
+ "familiar, stable, and comfortable — places that feel safe and known. " +
940
+ "This is a Receptive environment variable: your nervous system functions best in predictable territory " +
941
+ "rather than constant novelty. Forcing yourself into overly stimulating or unknown environments " +
942
+ "can deplete your health and decision-making capacity over time.",
943
+ left: "**Left Environment** (Active/Assertive) \u2014 Your body thrives by moving through diverse, " +
944
+ "stimulating environments. This is an Active environment variable: you are designed to encounter " +
945
+ "variety, new places, and fresh stimulation. Being too long in the same environment can lead to " +
946
+ "stagnation, restlessness, or decreased vitality.",
947
+ tones: {
948
+ 1: "Tone 1 — Caves: you do best in enclosed, cozy, cave-like environments with shelter overhead.",
949
+ 2: "Tone 2 — Markets: you thrive in places of exchange and activity — social, commercial, varied.",
950
+ 3: "Tone 3 — Kitchens: hearth-centered environments — where nourishment and community gather.",
951
+ 4: "Tone 4 — Mountains: elevated, expansive, airy environments with altitude and horizon.",
952
+ 5: "Tone 5 — Valleys: sheltered, low, protected geographies — river valleys, low land, sheltered bays.",
953
+ 6: "Tone 6 — Shores: boundary environments — coast, lakeside, edge-of-land — where elements meet.",
954
+ },
955
+ },
956
+ motivation: {
957
+ right: "**Right Motivation** (Receptive) \u2014 Your decision-making and life direction are best guided " +
958
+ "by following the inner call of your Strategy and Authority — moving toward what naturally draws you " +
959
+ "in rather than forcing or pursuing. Your correct motivation is essentially trustful and receptive.",
960
+ left: "**Left Motivation** (Active) \u2014 Your decision-making is best guided by active, intentional pursuit " +
961
+ "of what aligns with your values and strategy. You are designed to initiate and drive toward what matters to you — " +
962
+ "though always filtered through your Authority to ensure the body’s wisdom is guiding the mind.",
963
+ tones: {
964
+ 1: "Tone 1 — Fear: the motivation to move away from what limits or threatens, turning fear into healthy caution.",
965
+ 2: "Tone 2 — Hope: the motivation toward a better, more expanded future possibility.",
966
+ 3: "Tone 3 — Desire: the motivation driven by genuine wanting and attraction.",
967
+ 4: "Tone 4 — Need: the motivation arising from a felt sense of lack or necessity.",
968
+ 5: "Tone 5 — Guilt: the motivation arising from responsibility to others and the tribe.",
969
+ 6: "Tone 6 — Innocence: the motivation of pure openness, not knowing, and beginner’s mind.",
970
+ },
971
+ },
972
+ };
973
+ const info = VARIABLE_DATA[variable];
974
+ if (!info) {
975
+ return {
976
+ content: [{ type: "text", text: `**Variable: ${capitalize(variable)} (${capitalize(direction)})**\n\nVariable data not available for this arrow.` }],
977
+ };
978
+ }
979
+ const main = direction === "right" ? info.right : info.left;
980
+ const toneNote = tone != null && info.tones?.[tone]
981
+ ? `\n\n**Your Specific Tone (${tone}):** ${info.tones[tone]}`
982
+ : "";
983
+ const colorNote = color != null
984
+ ? `\n\n**Color ${color}** subtly modulates this Variable's expression — these nuances are covered in advanced PHS reading.`
985
+ : "";
986
+ const label = `${capitalize(variable)} Arrow — ${capitalize(direction)} Variable`;
987
+ const icon = variable === "digestion" ? "🔵" : variable === "perspective" ? "🟢" : variable === "environment" ? "⚪" : "🔴";
988
+ return {
989
+ content: [{
990
+ type: "text",
991
+ text: `**${label}** ${icon}\n\n` +
992
+ main + toneNote + colorNote +
993
+ "\n\n*The Variable is a refinement layer within the Primary Health System (PHS). " +
994
+ "It describes the optimal conditions for the body-brain to receive and process information correctly. " +
995
+ "Living according to your Variable is an advanced practice that supports deeper health, clarity, and cognitive accuracy.*",
996
+ }],
997
+ };
998
+ },
999
+ });
@@ -182,16 +182,9 @@ registerTool({
182
182
  content: [
183
183
  { type: "text", text: summary },
184
184
  { type: "text", text: JSON.stringify({ ...modelPayload, server_version: SERVER_VERSION }) },
185
- {
186
- type: "resource",
187
- resource: {
188
- uri: CHART_WHEEL_RESOURCE_URI,
189
- mimeType: CHART_WHEEL_MIME_TYPE,
190
- text: getChartWheelBundle(),
191
- },
192
- },
193
185
  ],
194
186
  _meta: {
187
+ "ui/resourceUri": CHART_WHEEL_RESOURCE_URI,
195
188
  ui: { resourceUri: CHART_WHEEL_RESOURCE_URI },
196
189
  },
197
190
  };
@@ -312,13 +305,13 @@ registerTool({
312
305
  // ── Tool: chart_wheel_recalculate ─────────────────────────────────────────
313
306
  registerTool({
314
307
  name: "chart_wheel_recalculate",
315
- description: "Recalculates the natal chart using a different house system. " +
316
- "Called automatically by the chart wheel UI when the user switches house systems. " +
317
- "Returns updated chart data including new house cusps and planet house assignments.",
308
+ description: "Recalculates the natal chart, solar return, or progressed chart with new parameters (house system, date, location). " +
309
+ "Called automatically by the chart wheel UI when the user updates the controls. " +
310
+ "Returns updated chart data including new house cusps and planet assignments.",
318
311
  inputSchema: {
319
312
  type: "object",
320
313
  properties: {
321
- datetime: { type: "string", description: "ISO 8601 datetime string" },
314
+ datetime: { type: "string", description: "ISO 8601 natal datetime string" },
322
315
  timezone: { type: "string", description: "IANA timezone name (e.g. 'America/New_York')" },
323
316
  latitude: { type: "number", description: "Birth latitude in decimal degrees (positive = North)" },
324
317
  longitude: { type: "number", description: "Birth longitude in decimal degrees (positive = East)" },
@@ -328,6 +321,15 @@ registerTool({
328
321
  enum: ["placidus", "whole_sign", "equal", "koch", "regiomontanus", "campanus"],
329
322
  description: "House system to recalculate with (e.g. 'whole_sign')",
330
323
  },
324
+ chart_type: {
325
+ type: "string",
326
+ enum: ["natal", "solar_return", "progressed"],
327
+ description: "Type of chart to calculate. Defaults to natal."
328
+ },
329
+ target_datetime: {
330
+ type: "string",
331
+ description: "ISO 8601 target datetime string. Reguired for solar_return or progressed calculations."
332
+ }
331
333
  },
332
334
  required: ["datetime", "latitude", "longitude", "house_system"],
333
335
  },
@@ -339,23 +341,34 @@ registerTool({
339
341
  const datetime = String(args.datetime);
340
342
  const lat = args.latitude;
341
343
  const lon = args.longitude;
342
- const natalBody = buildNatalBody(datetime, lat, lon, houseSystem, args.timezone);
344
+ const chartType = args.chart_type ?? "natal";
345
+ const baseBody = buildNatalBody(datetime, lat, lon, houseSystem, args.timezone);
346
+ let endpoint = "/ephemeris/natal-chart";
347
+ let requestBody = baseBody;
348
+ if (chartType === "solar_return") {
349
+ endpoint = "/predictive/returns/solar";
350
+ requestBody.configuration.target_datetime = { iso: args.target_datetime ?? datetime };
351
+ }
352
+ else if (chartType === "progressed") {
353
+ endpoint = "/predictive/progressed";
354
+ requestBody.configuration.target_datetime = { iso: args.target_datetime ?? datetime };
355
+ requestBody.configuration.method = "secondary";
356
+ }
343
357
  // Fetch updated chart JSON only — chart renders client-side in the iframe
344
- const chartData = await client.post("/ephemeris/natal-chart", natalBody);
345
- // Return only chart data (no SVG) the UI calls chart_wheel_fetch_svg
346
- const payload = {
347
- ...chartData,
348
- _birth_params: {
349
- datetime,
350
- timezone: args.timezone ?? null,
351
- latitude: lat ?? null,
352
- longitude: lon ?? null,
353
- location: args.location ?? null,
354
- },
355
- house_system: houseSystem,
356
- };
358
+ const chartData = await client.post(endpoint, requestBody);
359
+ // Normalise through buildModelPayload so house-switch results use the same
360
+ // format as the initial response.
361
+ const payload = buildModelPayload(chartData, {
362
+ datetime,
363
+ timezone: args.timezone ?? null,
364
+ latitude: lat ?? null,
365
+ longitude: lon ?? null,
366
+ location: args.location ?? null,
367
+ chartType: chartType,
368
+ targetDatetime: args.target_datetime ?? null
369
+ }, houseSystem);
357
370
  return {
358
- content: [{ type: "text", text: JSON.stringify(payload) }],
371
+ content: [{ type: "text", text: JSON.stringify({ ...payload, server_version: SERVER_VERSION }) }],
359
372
  };
360
373
  },
361
374
  });
@@ -443,7 +456,7 @@ function buildModelPayload(chartData, birthParams, houseSystem, svgBase) {
443
456
  speed: (p.longitude_speed ?? p.speed),
444
457
  retrograde: Boolean(p.is_retrograde ?? p.retrograde),
445
458
  sign: (p.sign_name ?? p.sign),
446
- house: (p.house),
459
+ house: (p.house_number ?? p.house),
447
460
  }));
448
461
  }
449
462
  else if (Array.isArray(chartData.planets)) {
@@ -452,8 +465,29 @@ function buildModelPayload(chartData, birthParams, houseSystem, svgBase) {
452
465
  }
453
466
  // ── 2. Compute aspects from longitude data (API doesn't return them) ──────
454
467
  const aspects = computeAspects(planetsArray);
468
+ // ── 3. Extract angles — API nests ascendant/MC under an `angles` key ──────
469
+ const rawAngles = chartData.angles;
470
+ const rawHouseObj = chartData.houses;
471
+ const ascendant = rawAngles?.ascendant ??
472
+ chartData.ascendant ??
473
+ rawHouseObj?.cusps?.[0] ??
474
+ 0;
475
+ const midheaven = rawAngles?.midheaven ??
476
+ chartData.midheaven ??
477
+ rawHouseObj?.cusps?.[9] ??
478
+ 0;
479
+ // ── 4. Convert houses object → HouseData[] array ──────────────────────────
480
+ // API returns { cusps: [lon0, lon1, …lon11], system: "P" }.
481
+ // The chart-wheel UI expects HouseData[] = [{ number:1, cusp_lon }, …].
482
+ const housesArray = (rawHouseObj?.cusps ?? []).map((cusp, i) => ({
483
+ number: i + 1,
484
+ cusp_lon: cusp,
485
+ }));
455
486
  return {
456
- ...chartData,
487
+ ascendant,
488
+ midheaven,
489
+ chart_ruler: chartData.chart_ruler,
490
+ houses: housesArray,
457
491
  // Typed array replaces the keyed object — the renderer can now iterate it
458
492
  planets: planetsArray,
459
493
  // Structured aspect array — powers highlighting and info panel
@@ -473,7 +507,7 @@ function buildChartSummary(data, location) {
473
507
  : Object.entries(raw).map(([name, p]) => ({
474
508
  name,
475
509
  sign: (p.sign_name ?? p.sign),
476
- house: p.house,
510
+ house: (p.house_number ?? p.house),
477
511
  retrograde: Boolean(p.is_retrograde ?? p.retrograde),
478
512
  longitude: p.longitude,
479
513
  longitude_speed: p.longitude_speed,
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,52 @@
1
+ import { registerTool, validateRequired } from "../index.js";
2
+ import { getActiveClient } from "../../backend/client.js";
3
+ registerTool({
4
+ name: "location_search",
5
+ description: "App-only tool for getting location autocomplete suggestions. Returns latitude, longitude, and IANA timezone.",
6
+ inputSchema: {
7
+ type: "object",
8
+ properties: {
9
+ query: {
10
+ type: "string",
11
+ description: "Search query for location, e.g. 'Los Angeles'",
12
+ },
13
+ },
14
+ required: ["query"],
15
+ additionalProperties: false,
16
+ },
17
+ _meta: {
18
+ ui: {
19
+ visibility: ["app"],
20
+ },
21
+ },
22
+ handler: async (args) => {
23
+ validateRequired(args, ["query"]);
24
+ return await getActiveClient().request("GET", "/location/autocomplete", {
25
+ params: { q: args.query },
26
+ });
27
+ },
28
+ });
29
+ registerTool({
30
+ name: "timezone_resolve",
31
+ description: "App-only tool for resolving IANA timezone by latitude and longitude.",
32
+ inputSchema: {
33
+ type: "object",
34
+ properties: {
35
+ latitude: { type: "number" },
36
+ longitude: { type: "number" },
37
+ },
38
+ required: ["latitude", "longitude"],
39
+ additionalProperties: false,
40
+ },
41
+ _meta: {
42
+ ui: {
43
+ visibility: ["app"],
44
+ },
45
+ },
46
+ handler: async (args) => {
47
+ validateRequired(args, ["latitude", "longitude"]);
48
+ return await getActiveClient().request("GET", "/timezone/lookup", {
49
+ params: { lat: args.latitude.toString(), lon: args.longitude.toString() },
50
+ });
51
+ },
52
+ });
@@ -74,6 +74,7 @@ export async function initTools(profile) {
74
74
  await import("./apps/chart-wheel-app.js");
75
75
  await import("./apps/bodygraph-app.js");
76
76
  await import("./apps/bi-wheel-app.js");
77
+ await import("./apps/location-tools.js");
77
78
  }
78
79
  }
79
80
  /**
@@ -54,6 +54,7 @@ registerTool({
54
54
  required: ["birth_datetime", "birth_latitude", "birth_longitude"],
55
55
  additionalProperties: false,
56
56
  },
57
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
57
58
  handler: async (args) => {
58
59
  validateRequired(args, ["birth_datetime", "birth_latitude", "birth_longitude"]);
59
60
  const body = {
@@ -146,6 +147,7 @@ registerTool({
146
147
  required: ["birth_datetime", "birth_latitude", "birth_longitude", "query_latitude", "query_longitude"],
147
148
  additionalProperties: false,
148
149
  },
150
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
149
151
  handler: async (args) => {
150
152
  validateRequired(args, ["birth_datetime", "birth_latitude", "birth_longitude", "query_latitude", "query_longitude"]);
151
153
  const body = {