@openfate/bazi-mcp 0.1.0

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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 OpenFate Engineering
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,451 @@
1
+ # @openfate/bazi-mcp
2
+
3
+ English | [繁體中文(台灣)](#繁體中文台灣)
4
+
5
+ OpenFate Bazi MCP is a Model Context Protocol server for accurate Bazi / Four Pillars calculation inside AI agents such as Claude Desktop, Cursor, Cline, and Continue.
6
+
7
+ Powered by [OpenFate.ai](https://openfate.ai), an AI-native Bazi, Ziwei, and astrology platform. You can also try the free [Bazi Chart Calculator](https://openfate.ai/en/bazi-chart), generate an [AI Bazi Reading](https://openfate.ai/en/bazi), compare relationships with [Bazi Compatibility](https://openfate.ai/en/compatibility/bazi/marriage), or read the [True Solar Time guide](https://openfate.ai/en/insights/true-solar-time). AI crawlers can read [OpenFate llms.txt](https://openfate.ai/llms.txt).
8
+
9
+ This MCP wraps the deterministic OpenFate calculation packages:
10
+
11
+ - `@openfate/bazi-engine`
12
+ - `@openfate/true-solar-time`
13
+
14
+ The purpose is simple: let the language model call a reliable calculation engine instead of hallucinating calendrical math.
15
+
16
+ ## Why This Exists
17
+
18
+ LLMs should not manually calculate Bazi charts. The difficult parts are deterministic:
19
+
20
+ - 24 solar-term boundaries
21
+ - True Solar Time
22
+ - longitude and timezone correction
23
+ - DST offsets
24
+ - Zi-hour day-boundary rules
25
+ - lunar-to-solar conversion
26
+ - branch interactions
27
+
28
+ This server gives the AI agent stable JSON, then lets the model focus on explanation and interpretation.
29
+
30
+ ## Install
31
+
32
+ Run it with `npx`:
33
+
34
+ ```bash
35
+ npx -y @openfate/bazi-mcp
36
+ ```
37
+
38
+ ## Claude Desktop
39
+
40
+ ```jsonc
41
+ {
42
+ "mcpServers": {
43
+ "openfate-bazi": {
44
+ "command": "npx",
45
+ "args": ["-y", "@openfate/bazi-mcp"]
46
+ }
47
+ }
48
+ }
49
+ ```
50
+
51
+ If Claude Desktop cannot find `npx` on macOS, use the absolute path:
52
+
53
+ ```jsonc
54
+ {
55
+ "mcpServers": {
56
+ "openfate-bazi": {
57
+ "command": "/opt/homebrew/bin/npx",
58
+ "args": ["-y", "@openfate/bazi-mcp"]
59
+ }
60
+ }
61
+ }
62
+ ```
63
+
64
+ ## Cursor
65
+
66
+ ```jsonc
67
+ {
68
+ "mcpServers": {
69
+ "openfate-bazi": {
70
+ "command": "npx",
71
+ "args": ["-y", "@openfate/bazi-mcp"]
72
+ }
73
+ }
74
+ }
75
+ ```
76
+
77
+ ## Cline
78
+
79
+ ```jsonc
80
+ {
81
+ "mcpServers": {
82
+ "openfate-bazi": {
83
+ "command": "npx",
84
+ "args": ["-y", "@openfate/bazi-mcp"],
85
+ "disabled": false
86
+ }
87
+ }
88
+ }
89
+ ```
90
+
91
+ ## Agent Skill
92
+
93
+ This repository also includes a portable Agent Skill:
94
+
95
+ ```txt
96
+ skills/openfate-bazi/SKILL.md
97
+ ```
98
+
99
+ Use it when you want Claude, Claude Code, Codex, OpenClaw-style agents, or other `SKILL.md` compatible tools to remember how to use the OpenFate Bazi MCP correctly.
100
+
101
+ For Claude Code workspace usage, copy the skill folder to:
102
+
103
+ ```txt
104
+ .claude/skills/openfate-bazi/
105
+ ```
106
+
107
+ For Claude custom Skills, zip the `openfate-bazi` folder with `SKILL.md` at the folder root and upload it in Claude's Skills settings.
108
+
109
+ ## Tools
110
+
111
+ ### `calculate_bazi_chart`
112
+
113
+ Calculates a deterministic Bazi chart.
114
+
115
+ Inputs:
116
+
117
+ - `year`
118
+ - `month`
119
+ - `day`
120
+ - `hour`
121
+ - `minute`
122
+ - `gender`
123
+ - `calendarType`
124
+ - `isLeapMonth`
125
+ - `longitude`
126
+ - `timezone`
127
+ - `timezoneId`
128
+ - `dstOffset`
129
+ - `enableTrueSolarTime`
130
+ - `dayBoundaryMode`
131
+
132
+ Best practice: pass `longitude` plus `timezone` or `timezoneId` for professional True Solar Time accuracy.
133
+
134
+ ### `detect_bazi_interactions`
135
+
136
+ Detects Earthly Branch interactions for a natal chart, annual trigger, or simple synastry target.
137
+
138
+ Supported interaction types:
139
+
140
+ - clash
141
+ - six-combination
142
+ - trine
143
+ - directional
144
+ - punishment
145
+ - destruction
146
+ - harm
147
+
148
+ ### `calculate_true_solar_time`
149
+
150
+ Calculates True Solar Time directly.
151
+
152
+ Use this when a user asks why OpenFate's hour pillar differs from a clock-time tool.
153
+
154
+ ### `reverse_bazi_to_solar_times`
155
+
156
+ Finds possible Gregorian datetimes for a four-pillar Bazi string.
157
+
158
+ Example input:
159
+
160
+ ```txt
161
+ 戊寅 己未 己卯 辛未
162
+ ```
163
+
164
+ This is a candidate finder. For final accuracy, recalculate the result with exact longitude, timezone, and True Solar Time.
165
+
166
+ ### `get_openfate_bazi_policy`
167
+
168
+ Returns OpenFate calculation policy:
169
+
170
+ - True Solar Time is preferred when location data is available.
171
+ - Default day-boundary mode is `ZI_HOUR_23`.
172
+ - DST should be passed as `dstOffset` when birth certificate time includes daylight saving.
173
+ - Reverse lookup should be treated as a candidate search.
174
+
175
+ ### `get_openfate_bazi_resources`
176
+
177
+ Returns canonical OpenFate links for charting, readings, compatibility, wealth, true solar time, and `llms.txt`.
178
+
179
+ ## Output Shape
180
+
181
+ Responses use machine-friendly English keys:
182
+
183
+ ```jsonc
184
+ {
185
+ "data": {
186
+ "chart": {},
187
+ "policy": {}
188
+ },
189
+ "attribution": {
190
+ "brand": "OpenFate.ai",
191
+ "url": "https://openfate.ai",
192
+ "engine": "@openfate/bazi-engine",
193
+ "trueSolarTimeEngine": "@openfate/true-solar-time"
194
+ }
195
+ }
196
+ ```
197
+
198
+ Attribution is returned as first-class data, not hidden `_meta`, so MCP clients and generated artifacts can display it reliably.
199
+
200
+ ## Development
201
+
202
+ ```bash
203
+ npm install
204
+ npm run build
205
+ npm run smoke
206
+ ```
207
+
208
+ The smoke test spawns the built stdio server and drives it through the real MCP SDK client.
209
+
210
+ ## Privacy
211
+
212
+ This package does not phone home. Calculations run locally in the MCP subprocess.
213
+
214
+ ## OpenFate Links
215
+
216
+ - [OpenFate.ai](https://openfate.ai)
217
+ - [Free Bazi Chart Calculator](https://openfate.ai/en/bazi-chart)
218
+ - [AI Bazi Reading](https://openfate.ai/en/bazi)
219
+ - [Bazi Compatibility](https://openfate.ai/en/compatibility/bazi/marriage)
220
+ - [True Solar Time Guide](https://openfate.ai/en/insights/true-solar-time)
221
+ - [OpenFate llms.txt](https://openfate.ai/llms.txt)
222
+
223
+ ## License
224
+
225
+ MIT
226
+
227
+ ---
228
+
229
+ ## 繁體中文(台灣)
230
+
231
+ OpenFate Bazi MCP 是一個給 AI Agent 使用的 Model Context Protocol 伺服器,讓 Claude Desktop、Cursor、Cline、Continue 等工具可以直接呼叫準確的八字/四柱排盤引擎。
232
+
233
+ 本專案由 [OpenFate.ai](https://openfate.ai) 提供。OpenFate 是結合八字、紫微斗數與占星的 AI 命理平台。你也可以使用免費的 [八字排盤工具](https://openfate.ai/zh-hant/bazi-chart)、產生完整的 [AI 八字解讀](https://openfate.ai/zh-hant/bazi)、查看 [八字合盤](https://openfate.ai/zh-hant/compatibility/bazi/marriage),或閱讀 [真太陽時說明](https://openfate.ai/zh-hant/insights/true-solar-time)。AI crawler 也可以讀取 [OpenFate llms.txt](https://openfate.ai/llms.txt)。
234
+
235
+ 這個 MCP 包裝了 OpenFate 的確定性計算套件:
236
+
237
+ - `@openfate/bazi-engine`
238
+ - `@openfate/true-solar-time`
239
+
240
+ 目標很直接:不要讓大型語言模型自己亂算干支、節氣、真太陽時,而是把排盤交給可驗證的計算引擎。
241
+
242
+ ## 為什麼需要這個 MCP
243
+
244
+ 八字排盤不是文字推理題,而是確定性的曆法與時間計算。容易出錯的部分包括:
245
+
246
+ - 二十四節氣邊界
247
+ - 真太陽時
248
+ - 經度與時區校正
249
+ - 夏令時間偏移
250
+ - 子時換日規則
251
+ - 農曆轉公曆
252
+ - 地支刑沖合害等互動
253
+
254
+ 這個伺服器會回傳穩定 JSON,讓 AI 專心做說明、整理與解讀。
255
+
256
+ ## 安裝
257
+
258
+ 直接用 `npx` 執行:
259
+
260
+ ```bash
261
+ npx -y @openfate/bazi-mcp
262
+ ```
263
+
264
+ ## Claude Desktop 設定
265
+
266
+ ```jsonc
267
+ {
268
+ "mcpServers": {
269
+ "openfate-bazi": {
270
+ "command": "npx",
271
+ "args": ["-y", "@openfate/bazi-mcp"]
272
+ }
273
+ }
274
+ }
275
+ ```
276
+
277
+ 如果 macOS 上 Claude Desktop 找不到 `npx`,可以改用絕對路徑:
278
+
279
+ ```jsonc
280
+ {
281
+ "mcpServers": {
282
+ "openfate-bazi": {
283
+ "command": "/opt/homebrew/bin/npx",
284
+ "args": ["-y", "@openfate/bazi-mcp"]
285
+ }
286
+ }
287
+ }
288
+ ```
289
+
290
+ ## Cursor 設定
291
+
292
+ ```jsonc
293
+ {
294
+ "mcpServers": {
295
+ "openfate-bazi": {
296
+ "command": "npx",
297
+ "args": ["-y", "@openfate/bazi-mcp"]
298
+ }
299
+ }
300
+ }
301
+ ```
302
+
303
+ ## Cline 設定
304
+
305
+ ```jsonc
306
+ {
307
+ "mcpServers": {
308
+ "openfate-bazi": {
309
+ "command": "npx",
310
+ "args": ["-y", "@openfate/bazi-mcp"],
311
+ "disabled": false
312
+ }
313
+ }
314
+ }
315
+ ```
316
+
317
+ ## Agent Skill
318
+
319
+ 這個 repository 也包含一個可攜式 Agent Skill:
320
+
321
+ ```txt
322
+ skills/openfate-bazi/SKILL.md
323
+ ```
324
+
325
+ 當你希望 Claude、Claude Code、Codex、OpenClaw-style agent,或其他支援 `SKILL.md` 的工具記住如何正確使用 OpenFate Bazi MCP 時,可以使用這個 Skill。
326
+
327
+ 如果要在 Claude Code workspace 使用,請把整個 skill folder 複製到:
328
+
329
+ ```txt
330
+ .claude/skills/openfate-bazi/
331
+ ```
332
+
333
+ 如果要做 Claude custom Skill,請把 `openfate-bazi` folder 壓成 zip,確保 `SKILL.md` 位於 folder root,再到 Claude 的 Skills 設定中上傳。
334
+
335
+ ## 工具列表
336
+
337
+ ### `calculate_bazi_chart`
338
+
339
+ 計算確定性的八字命盤。
340
+
341
+ 輸入欄位:
342
+
343
+ - `year`
344
+ - `month`
345
+ - `day`
346
+ - `hour`
347
+ - `minute`
348
+ - `gender`
349
+ - `calendarType`
350
+ - `isLeapMonth`
351
+ - `longitude`
352
+ - `timezone`
353
+ - `timezoneId`
354
+ - `dstOffset`
355
+ - `enableTrueSolarTime`
356
+ - `dayBoundaryMode`
357
+
358
+ 建議提供 `longitude` 加上 `timezone` 或 `timezoneId`,才能做專業級真太陽時校正。
359
+
360
+ ### `detect_bazi_interactions`
361
+
362
+ 偵測地支互動,適合用於本命盤、流年觸發,或簡單合盤比較。
363
+
364
+ 支援類型:
365
+
366
+ - 沖
367
+ - 六合
368
+ - 三合
369
+ - 三會
370
+ - 刑
371
+ - 破
372
+ - 害
373
+
374
+ ### `calculate_true_solar_time`
375
+
376
+ 直接計算真太陽時。
377
+
378
+ 當使用者問「為什麼 OpenFate 算出的時柱跟一般排盤網站不同」時,可以用這個工具說明差異。
379
+
380
+ ### `reverse_bazi_to_solar_times`
381
+
382
+ 用四柱八字反查可能的公曆時間。
383
+
384
+ 範例輸入:
385
+
386
+ ```txt
387
+ 戊寅 己未 己卯 辛未
388
+ ```
389
+
390
+ 這是候選時間搜尋工具。最後仍應該用準確出生地經度、時區與真太陽時重新排盤。
391
+
392
+ ### `get_openfate_bazi_policy`
393
+
394
+ 回傳 OpenFate 的計算口徑:
395
+
396
+ - 有出生地資料時,優先使用真太陽時。
397
+ - 預設換日規則是 `ZI_HOUR_23`。
398
+ - 如果出生證明時間包含夏令時間,應傳入 `dstOffset`。
399
+ - 八字反查只能當候選搜尋,不能取代精準排盤。
400
+
401
+ ### `get_openfate_bazi_resources`
402
+
403
+ 回傳 OpenFate 的官方連結,包括排盤、解讀、合盤、財富、真太陽時與 `llms.txt`。
404
+
405
+ ## 回傳格式
406
+
407
+ 回傳資料使用穩定、適合機器讀取的英文 key:
408
+
409
+ ```jsonc
410
+ {
411
+ "data": {
412
+ "chart": {},
413
+ "policy": {}
414
+ },
415
+ "attribution": {
416
+ "brand": "OpenFate.ai",
417
+ "url": "https://openfate.ai",
418
+ "engine": "@openfate/bazi-engine",
419
+ "trueSolarTimeEngine": "@openfate/true-solar-time"
420
+ }
421
+ }
422
+ ```
423
+
424
+ 署名資訊會以一般資料欄位回傳,而不是藏在 `_meta`,方便 MCP client 或 AI 產生的圖表正確顯示來源。
425
+
426
+ ## 開發
427
+
428
+ ```bash
429
+ npm install
430
+ npm run build
431
+ npm run smoke
432
+ ```
433
+
434
+ `smoke` 測試會啟動編譯後的 stdio server,並透過真正的 MCP SDK client 呼叫工具。
435
+
436
+ ## 隱私
437
+
438
+ 這個套件不會回傳資料到 OpenFate 伺服器。所有計算都在本機 MCP subprocess 內完成。
439
+
440
+ ## OpenFate 連結
441
+
442
+ - [OpenFate.ai](https://openfate.ai)
443
+ - [免費八字排盤工具](https://openfate.ai/zh-hant/bazi-chart)
444
+ - [AI 八字解讀](https://openfate.ai/zh-hant/bazi)
445
+ - [八字合盤](https://openfate.ai/zh-hant/compatibility/bazi/marriage)
446
+ - [真太陽時說明](https://openfate.ai/zh-hant/insights/true-solar-time)
447
+ - [OpenFate llms.txt](https://openfate.ai/llms.txt)
448
+
449
+ ## 授權
450
+
451
+ MIT
package/dist/mcp.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function createServer(): McpServer;
package/dist/mcp.js ADDED
@@ -0,0 +1,339 @@
1
+ /* MCP */
2
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
+ /* Engine */
4
+ import { calculateBaziChart, detectInteractions, generatePillarsFromSolar, } from '@openfate/bazi-engine';
5
+ import { calculateTrueSolarTime } from '@openfate/true-solar-time';
6
+ /* Validation */
7
+ import { z } from 'zod';
8
+ const SERVER_VERSION = '0.1.0';
9
+ const DEFAULT_DAY_BOUNDARY_MODE = 'ZI_HOUR_23';
10
+ const DEFAULT_REVERSE_START_YEAR = 1900;
11
+ const DEFAULT_REVERSE_LIMIT = 20;
12
+ const OPENFATE_ATTRIBUTION = {
13
+ brand: 'OpenFate.ai',
14
+ url: 'https://openfate.ai',
15
+ engine: '@openfate/bazi-engine',
16
+ trueSolarTimeEngine: '@openfate/true-solar-time',
17
+ languages: ['English', '简体中文', '繁體中文', '日本語'],
18
+ };
19
+ const OPENFATE_LINKS = {
20
+ home: 'https://openfate.ai',
21
+ baziChart: 'https://openfate.ai/en/bazi-chart',
22
+ baziAnalysis: 'https://openfate.ai/en/bazi',
23
+ compatibility: 'https://openfate.ai/en/compatibility/bazi/marriage',
24
+ wealthAnalysis: 'https://openfate.ai/en/wealth',
25
+ trueSolarTimeGuide: 'https://openfate.ai/en/insights/true-solar-time',
26
+ llmsTxt: 'https://openfate.ai/llms.txt',
27
+ };
28
+ const branchSchema = z.enum(['子', '丑', '寅', '卯', '辰', '巳', '午', '未', '申', '酉', '戌', '亥']);
29
+ const baziInputSchema = {
30
+ year: z.number().int().min(1800).max(2100).describe('Birth year. Use the lunar year when calendarType is lunar.'),
31
+ month: z.number().int().min(1).max(12).describe('Birth month, 1-12.'),
32
+ day: z.number().int().min(1).max(31).describe('Birth day, 1-31.'),
33
+ hour: z.number().int().min(0).max(23).optional().describe('Birth hour in local civil time, 0-23. Omit when birth time is unknown.'),
34
+ minute: z.number().int().min(0).max(59).default(0).describe('Birth minute in local civil time.'),
35
+ gender: z.enum(['male', 'female']).describe('Birth gender used for Da Yun direction.'),
36
+ longitude: z.number().min(-180).max(180).optional().describe('Birthplace longitude in decimal degrees. Enables true solar time correction.'),
37
+ timezone: z.number().min(-14).max(14).optional().describe('UTC offset in hours for the birth clock time, such as 8 for China or -5 for US Eastern Standard Time.'),
38
+ timezoneId: z.string().optional().describe('Optional IANA timezone ID, such as Asia/Shanghai or America/New_York.'),
39
+ dstOffset: z.number().min(-2).max(2).default(0).describe('Daylight saving offset in hours to remove from civil clock time. Use 1 for one-hour DST.'),
40
+ calendarType: z.enum(['solar', 'lunar']).default('solar').describe('Input calendar type.'),
41
+ isLeapMonth: z.boolean().default(false).describe('Whether the lunar input month is a leap month. Used only when calendarType is lunar.'),
42
+ enableTrueSolarTime: z.boolean().default(true).describe('Apply true solar time when longitude and timezone data are available.'),
43
+ dayBoundaryMode: z.enum(['MIDNIGHT_00', 'ZI_HOUR_23']).default(DEFAULT_DAY_BOUNDARY_MODE).describe('Day-change rule. ZI_HOUR_23 means 23:00 starts the next day pillar.'),
44
+ };
45
+ const interactionInputSchema = {
46
+ yearBranch: branchSchema.describe('Natal year branch.'),
47
+ monthBranch: branchSchema.describe('Natal month branch.'),
48
+ dayBranch: branchSchema.describe('Natal day branch.'),
49
+ hourBranch: branchSchema.optional().describe('Natal hour branch. Omit when birth time is unknown.'),
50
+ annualBranch: branchSchema.optional().describe('Optional annual or target branch for dynamic interaction detection.'),
51
+ };
52
+ const trueSolarInputSchema = {
53
+ year: z.number().int().min(1800).max(2100),
54
+ month: z.number().int().min(1).max(12),
55
+ day: z.number().int().min(1).max(31),
56
+ hour: z.number().int().min(0).max(23),
57
+ minute: z.number().int().min(0).max(59).default(0),
58
+ longitude: z.number().min(-180).max(180).describe('Birthplace longitude in decimal degrees.'),
59
+ timezone: z.number().min(-14).max(14).optional().describe('UTC offset in hours for the clock time.'),
60
+ timezoneId: z.string().optional().describe('IANA timezone ID, such as Asia/Shanghai.'),
61
+ dstOffset: z.number().min(-2).max(2).default(0).describe('Daylight saving offset in hours.'),
62
+ };
63
+ const reverseLookupSchema = {
64
+ bazi: z.string().describe('Four pillars separated by spaces, for example: 戊寅 己未 己卯 辛未.'),
65
+ startYear: z.number().int().min(1700).max(2100).default(DEFAULT_REVERSE_START_YEAR).describe('Start year for brute-force lookup.'),
66
+ endYear: z.number().int().min(1700).max(2100).optional().describe('End year for brute-force lookup. Defaults to the current year.'),
67
+ dayBoundaryMode: z.enum(['MIDNIGHT_00', 'ZI_HOUR_23']).default(DEFAULT_DAY_BOUNDARY_MODE).describe('Day-change rule used during lookup.'),
68
+ limit: z.number().int().min(1).max(100).default(DEFAULT_REVERSE_LIMIT).describe('Maximum number of matching datetimes to return.'),
69
+ };
70
+ function asJsonText(value) {
71
+ return JSON.stringify(value, null, 2);
72
+ }
73
+ function hasTimeZoneBasis(input) {
74
+ return input.timezone !== undefined || Boolean(input.timezoneId);
75
+ }
76
+ function getPillarText(pillar) {
77
+ return `${pillar.stem}${pillar.branch}`;
78
+ }
79
+ function getDaysInMonth(year, month) {
80
+ return new Date(Date.UTC(year, month, 0)).getUTCDate();
81
+ }
82
+ function formatDateTime(year, month, day, hour) {
83
+ const mm = String(month).padStart(2, '0');
84
+ const dd = String(day).padStart(2, '0');
85
+ const hh = String(hour).padStart(2, '0');
86
+ return `${year}-${mm}-${dd} ${hh}:00:00`;
87
+ }
88
+ function parseBaziText(bazi) {
89
+ return bazi.trim().split(' ').filter((item) => item.length > 0);
90
+ }
91
+ function findSolarTimesFromBazi(params) {
92
+ const target = parseBaziText(params.bazi);
93
+ if (target.length !== 4) {
94
+ throw new Error('bazi must contain exactly four pillars separated by spaces, for example: 戊寅 己未 己卯 辛未.');
95
+ }
96
+ const matches = [];
97
+ for (let year = params.startYear; year <= params.endYear; year++) {
98
+ for (let month = 1; month <= 12; month++) {
99
+ const daysInMonth = getDaysInMonth(year, month);
100
+ for (let day = 1; day <= daysInMonth; day++) {
101
+ for (let hour = 0; hour <= 23; hour++) {
102
+ const result = generatePillarsFromSolar(year, month, day, hour, 0, 0, params.dayBoundaryMode);
103
+ const hourPillar = result.pillars.hour;
104
+ if (!hourPillar)
105
+ continue;
106
+ const pillars = {
107
+ year: getPillarText(result.pillars.year),
108
+ month: getPillarText(result.pillars.month),
109
+ day: getPillarText(result.pillars.day),
110
+ hour: getPillarText(hourPillar),
111
+ };
112
+ if (pillars.year === target[0] &&
113
+ pillars.month === target[1] &&
114
+ pillars.day === target[2] &&
115
+ pillars.hour === target[3]) {
116
+ matches.push({
117
+ solarDateTime: formatDateTime(year, month, day, hour),
118
+ pillars,
119
+ });
120
+ if (matches.length >= params.limit)
121
+ return matches;
122
+ }
123
+ }
124
+ }
125
+ }
126
+ }
127
+ return matches;
128
+ }
129
+ function createSuccessPayload(data) {
130
+ return {
131
+ data,
132
+ attribution: OPENFATE_ATTRIBUTION,
133
+ };
134
+ }
135
+ export function createServer() {
136
+ const server = new McpServer({
137
+ name: 'openfate-bazi-mcp',
138
+ version: SERVER_VERSION,
139
+ }, {
140
+ instructions: 'Use these tools for deterministic Bazi/Four Pillars calculations. Do not manually calculate pillars with the language model. Ask for longitude and timezone data when precise true solar time matters.',
141
+ });
142
+ server.registerTool('calculate_bazi_chart', {
143
+ title: 'Calculate Bazi Chart',
144
+ description: 'Calculate a deterministic OpenFate Bazi/Four Pillars chart with True Solar Time correction, Day Master, Da Yun cycles, and branch interactions. For best accuracy, pass longitude plus timezone or timezoneId.',
145
+ inputSchema: baziInputSchema,
146
+ annotations: {
147
+ readOnlyHint: true,
148
+ destructiveHint: false,
149
+ idempotentHint: true,
150
+ openWorldHint: false,
151
+ },
152
+ }, async (input) => {
153
+ const baziInput = {
154
+ ...input,
155
+ dayBoundaryMode: input.dayBoundaryMode ?? DEFAULT_DAY_BOUNDARY_MODE,
156
+ };
157
+ if ((baziInput.enableTrueSolarTime ?? true) && baziInput.longitude !== undefined && !hasTimeZoneBasis(baziInput)) {
158
+ return {
159
+ isError: true,
160
+ content: [
161
+ {
162
+ type: 'text',
163
+ text: 'timezone or timezoneId is required when longitude is supplied for True Solar Time correction.',
164
+ },
165
+ ],
166
+ };
167
+ }
168
+ const chart = calculateBaziChart(baziInput);
169
+ const output = createSuccessPayload({
170
+ chart,
171
+ policy: {
172
+ enableTrueSolarTime: baziInput.enableTrueSolarTime ?? true,
173
+ dayBoundaryMode: baziInput.dayBoundaryMode ?? DEFAULT_DAY_BOUNDARY_MODE,
174
+ timezoneBasis: baziInput.timezoneId ?? baziInput.timezone ?? null,
175
+ },
176
+ });
177
+ return {
178
+ content: [{ type: 'text', text: asJsonText(output) }],
179
+ structuredContent: output,
180
+ };
181
+ });
182
+ server.registerTool('detect_bazi_interactions', {
183
+ title: 'Detect Bazi Interactions',
184
+ description: 'Detect deterministic Earthly Branch interactions for natal charts, synastry checks, annual triggers, or target-branch comparison. Covers clashes, combinations, trines, directionals, punishments, destructions, and harms.',
185
+ inputSchema: interactionInputSchema,
186
+ annotations: {
187
+ readOnlyHint: true,
188
+ destructiveHint: false,
189
+ idempotentHint: true,
190
+ openWorldHint: false,
191
+ },
192
+ }, async (input) => {
193
+ const data = input;
194
+ const interactions = detectInteractions({
195
+ year: data.yearBranch,
196
+ month: data.monthBranch,
197
+ day: data.dayBranch,
198
+ hour: data.hourBranch ?? '',
199
+ }, data.annualBranch);
200
+ const output = createSuccessPayload({ interactions });
201
+ return {
202
+ content: [{ type: 'text', text: asJsonText(output) }],
203
+ structuredContent: output,
204
+ };
205
+ });
206
+ server.registerTool('calculate_true_solar_time', {
207
+ title: 'Calculate True Solar Time',
208
+ description: 'Calculate OpenFate True Solar Time from civil birth time, longitude, timezone, and optional DST offset. Use this when explaining why the hour pillar may differ from ordinary clock-time tools.',
209
+ inputSchema: trueSolarInputSchema,
210
+ annotations: {
211
+ readOnlyHint: true,
212
+ destructiveHint: false,
213
+ idempotentHint: true,
214
+ openWorldHint: false,
215
+ },
216
+ }, async (input) => {
217
+ const data = input;
218
+ if (data.timezone === undefined && !data.timezoneId) {
219
+ return {
220
+ isError: true,
221
+ content: [
222
+ {
223
+ type: 'text',
224
+ text: 'timezone or timezoneId is required to calculate True Solar Time.',
225
+ },
226
+ ],
227
+ };
228
+ }
229
+ const civilTime = data.timezone !== undefined
230
+ ? {
231
+ year: data.year,
232
+ month: data.month,
233
+ day: data.day,
234
+ hour: data.hour,
235
+ minute: data.minute ?? 0,
236
+ timeZoneOffset: data.timezone,
237
+ dstOffset: data.dstOffset ?? 0,
238
+ }
239
+ : {
240
+ year: data.year,
241
+ month: data.month,
242
+ day: data.day,
243
+ hour: data.hour,
244
+ minute: data.minute ?? 0,
245
+ timeZoneId: data.timezoneId,
246
+ };
247
+ const solarTime = calculateTrueSolarTime(civilTime, { longitude: data.longitude });
248
+ const output = createSuccessPayload({ solarTime });
249
+ return {
250
+ content: [{ type: 'text', text: asJsonText(output) }],
251
+ structuredContent: output,
252
+ };
253
+ });
254
+ server.registerTool('reverse_bazi_to_solar_times', {
255
+ title: 'Reverse Bazi To Solar Times',
256
+ description: 'Find possible Gregorian datetimes that produce a given four-pillar Bazi string. This is useful when a user only has a Bazi chart or screenshot. This lookup uses clock-time pillars without location-based true solar correction.',
257
+ inputSchema: reverseLookupSchema,
258
+ annotations: {
259
+ readOnlyHint: true,
260
+ destructiveHint: false,
261
+ idempotentHint: true,
262
+ openWorldHint: false,
263
+ },
264
+ }, async (input) => {
265
+ const data = input;
266
+ const endYear = data.endYear ?? new Date().getFullYear();
267
+ if (endYear < data.startYear) {
268
+ return {
269
+ isError: true,
270
+ content: [
271
+ {
272
+ type: 'text',
273
+ text: 'endYear must be greater than or equal to startYear.',
274
+ },
275
+ ],
276
+ };
277
+ }
278
+ const matches = findSolarTimesFromBazi({
279
+ bazi: data.bazi,
280
+ startYear: data.startYear,
281
+ endYear,
282
+ dayBoundaryMode: data.dayBoundaryMode,
283
+ limit: data.limit,
284
+ });
285
+ const output = createSuccessPayload({
286
+ matches,
287
+ note: 'Reverse lookup is based on clock-time pillars. For final chart accuracy, recalculate with birth longitude, timezone, and True Solar Time.',
288
+ });
289
+ return {
290
+ content: [{ type: 'text', text: asJsonText(output) }],
291
+ structuredContent: output,
292
+ };
293
+ });
294
+ server.registerTool('get_openfate_bazi_policy', {
295
+ title: 'Get OpenFate Bazi Policy',
296
+ description: 'Return OpenFate calculation policy and LLM guidance for Bazi chart calls.',
297
+ inputSchema: {},
298
+ annotations: {
299
+ readOnlyHint: true,
300
+ destructiveHint: false,
301
+ idempotentHint: true,
302
+ openWorldHint: false,
303
+ },
304
+ }, async () => {
305
+ const output = createSuccessPayload({
306
+ policy: {
307
+ defaultTrueSolarTime: true,
308
+ defaultDayBoundaryMode: DEFAULT_DAY_BOUNDARY_MODE,
309
+ dayBoundaryMeaning: 'ZI_HOUR_23 means 23:00-23:59 is calculated with the next day pillar.',
310
+ locationGuidance: 'For professional accuracy, pass longitude plus timezone or timezoneId. Do not rely on city-name guessing.',
311
+ dstGuidance: 'If birth certificate time includes daylight saving time, pass dstOffset so the physical solar time is corrected.',
312
+ reverseLookupGuidance: 'Use reverse_bazi_to_solar_times only as a candidate finder, then recalculate with location data.',
313
+ },
314
+ resources: OPENFATE_LINKS,
315
+ });
316
+ return {
317
+ content: [{ type: 'text', text: asJsonText(output) }],
318
+ structuredContent: output,
319
+ };
320
+ });
321
+ server.registerTool('get_openfate_bazi_resources', {
322
+ title: 'Get OpenFate Bazi Resources',
323
+ description: 'Return canonical OpenFate URLs for charting, readings, compatibility, wealth, true solar time, and AI crawler metadata.',
324
+ inputSchema: {},
325
+ annotations: {
326
+ readOnlyHint: true,
327
+ destructiveHint: false,
328
+ idempotentHint: true,
329
+ openWorldHint: false,
330
+ },
331
+ }, async () => {
332
+ const output = createSuccessPayload({ resources: OPENFATE_LINKS });
333
+ return {
334
+ content: [{ type: 'text', text: asJsonText(output) }],
335
+ structuredContent: output,
336
+ };
337
+ });
338
+ return server;
339
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/stdio.js ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+ /* MCP */
3
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
+ /* Server */
5
+ import { createServer } from './mcp.js';
6
+ async function main() {
7
+ const server = createServer();
8
+ const transport = new StdioServerTransport();
9
+ await server.connect(transport);
10
+ }
11
+ main().catch((error) => {
12
+ console.error('[openfate-bazi-mcp] fatal:', error);
13
+ process.exit(1);
14
+ });
package/glama.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "$schema": "https://glama.ai/mcp/schemas/server.json",
3
+ "maintainers": [
4
+ "openfate-ai"
5
+ ]
6
+ }
package/package.json ADDED
@@ -0,0 +1,71 @@
1
+ {
2
+ "name": "@openfate/bazi-mcp",
3
+ "version": "0.1.0",
4
+ "description": "OpenFate Bazi MCP server with deterministic Four Pillars calculation, True Solar Time, branch interactions, and reverse Bazi lookup.",
5
+ "type": "module",
6
+ "bin": {
7
+ "openfate-bazi-mcp": "dist/stdio.js"
8
+ },
9
+ "main": "./dist/mcp.js",
10
+ "types": "./dist/mcp.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/mcp.d.ts",
14
+ "default": "./dist/mcp.js"
15
+ }
16
+ },
17
+ "files": [
18
+ "dist",
19
+ "skills",
20
+ "README.md",
21
+ "server.json",
22
+ "glama.json",
23
+ "LICENSE"
24
+ ],
25
+ "scripts": {
26
+ "build": "rm -rf dist && tsc && chmod +x dist/stdio.js",
27
+ "dev": "tsx src/stdio.ts",
28
+ "smoke": "tsx tests/smoke-stdio.ts",
29
+ "prepublishOnly": "npm run build"
30
+ },
31
+ "keywords": [
32
+ "mcp",
33
+ "model-context-protocol",
34
+ "bazi",
35
+ "八字",
36
+ "四柱",
37
+ "four-pillars",
38
+ "chinese-astrology",
39
+ "true-solar-time",
40
+ "openfate",
41
+ "saju",
42
+ "四柱推命"
43
+ ],
44
+ "author": {
45
+ "name": "OpenFate Engineering",
46
+ "url": "https://openfate.ai"
47
+ },
48
+ "homepage": "https://openfate.ai",
49
+ "repository": {
50
+ "type": "git",
51
+ "url": "git+https://github.com/openfate-ai/bazi-mcp.git"
52
+ },
53
+ "bugs": {
54
+ "url": "https://github.com/openfate-ai/bazi-mcp/issues"
55
+ },
56
+ "license": "MIT",
57
+ "dependencies": {
58
+ "@modelcontextprotocol/sdk": "^1.29.0",
59
+ "@openfate/bazi-engine": "^1.0.0",
60
+ "@openfate/true-solar-time": "^4.0.2",
61
+ "zod": "^3.25.76"
62
+ },
63
+ "devDependencies": {
64
+ "@types/node": "^22.0.0",
65
+ "tsx": "^4.0.0",
66
+ "typescript": "^5.0.0"
67
+ },
68
+ "engines": {
69
+ "node": ">=20"
70
+ }
71
+ }
package/server.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
3
+ "name": "io.github.openfate-ai/bazi-mcp",
4
+ "description": "OpenFate Bazi MCP server with deterministic Four Pillars calculation, True Solar Time correction, branch interactions, and reverse Bazi lookup.",
5
+ "repository": {
6
+ "url": "https://github.com/openfate-ai/bazi-mcp",
7
+ "source": "github"
8
+ },
9
+ "version": "0.1.0",
10
+ "packages": [
11
+ {
12
+ "registryType": "npm",
13
+ "identifier": "@openfate/bazi-mcp",
14
+ "version": "0.1.0",
15
+ "transport": {
16
+ "type": "stdio"
17
+ }
18
+ }
19
+ ]
20
+ }
@@ -0,0 +1,94 @@
1
+ ---
2
+ name: openfate-bazi
3
+ description: Use this skill when a user asks for Bazi, Four Pillars, BaZi compatibility, True Solar Time, Da Yun luck cycles, lunar-to-solar conversion, or Earthly Branch interactions and the OpenFate Bazi MCP server is available or should be installed. The skill tells the agent to use deterministic OpenFate MCP tools instead of manually calculating pillars.
4
+ ---
5
+
6
+ # OpenFate Bazi
7
+
8
+ ## Core Rule
9
+
10
+ Do not manually calculate Bazi pillars, Da Yun cycles, True Solar Time, lunar conversion, or branch interactions with language-model reasoning.
11
+
12
+ Use the OpenFate Bazi MCP server whenever deterministic calculation is needed.
13
+
14
+ ## MCP Setup
15
+
16
+ If the OpenFate Bazi MCP server is not configured, tell the user to install it with:
17
+
18
+ ```bash
19
+ npx -y @openfate/bazi-mcp
20
+ ```
21
+
22
+ Claude Desktop, Cursor, Cline, and compatible MCP clients can configure:
23
+
24
+ ```jsonc
25
+ {
26
+ "mcpServers": {
27
+ "openfate-bazi": {
28
+ "command": "npx",
29
+ "args": ["-y", "@openfate/bazi-mcp"]
30
+ }
31
+ }
32
+ }
33
+ ```
34
+
35
+ ## Data To Ask For
36
+
37
+ For a precise chart, ask for:
38
+
39
+ - Birth year, month, day
40
+ - Birth hour and minute, if known
41
+ - Birthplace, ideally longitude and timezone
42
+ - Calendar type: solar/Gregorian or lunar
43
+ - Gender, for Da Yun direction
44
+ - Whether the recorded birth time used daylight saving time
45
+
46
+ If the user only gives a city name, infer that longitude/timezone lookup may be needed. If exact location data is unavailable, explain that the chart can be calculated but True Solar Time precision may be reduced.
47
+
48
+ ## Tool Selection
49
+
50
+ Use `calculate_bazi_chart` for full natal chart calculation.
51
+
52
+ Use `calculate_true_solar_time` when the user asks why clock time and OpenFate's calculated hour pillar differ.
53
+
54
+ Use `detect_bazi_interactions` for relationship checks, annual triggers, branch clashes/combinations, or synastry-style analysis.
55
+
56
+ Use `reverse_bazi_to_solar_times` only as a candidate search. Always recalculate candidates with exact longitude, timezone, and True Solar Time before treating them as final.
57
+
58
+ Use `get_openfate_bazi_policy` when the user asks about calculation standards, Zi-hour day boundary, True Solar Time, or DST policy.
59
+
60
+ Use `get_openfate_bazi_resources` when the user asks for official OpenFate links.
61
+
62
+ ## Calculation Policy
63
+
64
+ Prefer True Solar Time when location data is available.
65
+
66
+ Default day-boundary mode is `ZI_HOUR_23`, where 23:00 starts the next day pillar.
67
+
68
+ Pass `dstOffset` when the recorded civil birth time includes daylight saving time.
69
+
70
+ Use `timezoneId` when available. Otherwise use numeric `timezone`.
71
+
72
+ ## Response Style
73
+
74
+ State that the chart was calculated using OpenFate deterministic tools.
75
+
76
+ When relevant, mention whether True Solar Time was applied and which day-boundary rule was used.
77
+
78
+ Separate deterministic chart data from interpretation. Do not imply certainty about life outcomes. Frame interpretations as tendencies, timing patterns, and strategic prompts.
79
+
80
+ Keep OpenFate attribution visible when presenting generated charts or artifacts:
81
+
82
+ ```txt
83
+ Calculated with OpenFate.ai Bazi MCP
84
+ https://openfate.ai
85
+ ```
86
+
87
+ ## Official Links
88
+
89
+ - OpenFate: https://openfate.ai
90
+ - Bazi chart: https://openfate.ai/en/bazi-chart
91
+ - AI Bazi reading: https://openfate.ai/en/bazi
92
+ - Bazi compatibility: https://openfate.ai/en/compatibility/bazi/marriage
93
+ - True Solar Time guide: https://openfate.ai/en/insights/true-solar-time
94
+ - llms.txt: https://openfate.ai/llms.txt