ether-to-astro 1.0.2 → 1.2.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/.github/ISSUE_TEMPLATE/bug-report.yml +87 -0
- package/.github/ISSUE_TEMPLATE/capability-request.yml +117 -0
- package/.github/ISSUE_TEMPLATE/config.yml +5 -0
- package/.github/ISSUE_TEMPLATE/paper-cut.yml +59 -0
- package/.github/pull_request_template.md +1 -0
- package/.github/workflows/release.yml +2 -2
- package/.github/workflows/test.yml +2 -2
- package/AGENTS.md +46 -1
- package/DEVELOPER.md +78 -0
- package/README.md +128 -75
- package/SETUP.md +100 -41
- package/dist/astro-service.d.ts +51 -2
- package/dist/astro-service.js +660 -56
- package/dist/cli.js +31 -0
- package/dist/entrypoint.d.ts +13 -0
- package/dist/entrypoint.js +78 -0
- package/dist/ephemeris.d.ts +15 -0
- package/dist/ephemeris.js +33 -0
- package/dist/formatter.d.ts +5 -1
- package/dist/formatter.js +4 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +63 -114
- package/dist/loader.d.ts +1 -1
- package/dist/loader.js +61 -23
- package/dist/mcp-alias.d.ts +2 -0
- package/dist/mcp-alias.js +8 -0
- package/dist/time-utils.d.ts +8 -0
- package/dist/time-utils.js +16 -0
- package/dist/tool-registry.js +111 -5
- package/dist/tool-result.d.ts +8 -0
- package/dist/tool-result.js +39 -0
- package/dist/types.d.ts +79 -1
- package/docs/product/adrs/0001-mcp-vs-skill-boundary.md +96 -0
- package/docs/product/architecture-boundaries.md +223 -0
- package/docs/product/product-tenets.md +174 -0
- package/docs/releases/1.2.0-draft.md +48 -0
- package/package.json +7 -7
- package/skills/.curated/daily-brief/SKILL.md +75 -0
- package/skills/.curated/electional-overlay/SKILL.md +67 -0
- package/skills/.curated/weekly-overview/SKILL.md +73 -0
- package/skills/.system/write-skill/SKILL.md +90 -0
- package/src/astro-service.ts +861 -60
- package/src/cli.ts +84 -0
- package/src/entrypoint.ts +118 -0
- package/src/ephemeris.ts +44 -0
- package/src/formatter.ts +13 -1
- package/src/index.ts +77 -121
- package/src/loader.ts +69 -25
- package/src/mcp-alias.ts +10 -0
- package/src/time-utils.ts +18 -0
- package/src/tool-registry.ts +129 -9
- package/src/tool-result.ts +44 -0
- package/src/types.ts +91 -1
- package/tests/unit/astro-service.test.ts +751 -5
- package/tests/unit/cli-commands.test.ts +13 -0
- package/tests/unit/entrypoint.test.ts +67 -0
- package/tests/unit/error-mapping.test.ts +20 -0
- package/tests/unit/formatter.test.ts +6 -0
- package/tests/unit/tool-registry.test.ts +114 -2
- package/setup.sh +0 -21
package/dist/tool-registry.js
CHANGED
|
@@ -58,9 +58,109 @@ export const MCP_TOOL_SPECS = [
|
|
|
58
58
|
return { kind: 'state', data: result.data, text: result.text, natalChart: result.chart };
|
|
59
59
|
},
|
|
60
60
|
},
|
|
61
|
+
{
|
|
62
|
+
name: 'get_rising_sign_windows',
|
|
63
|
+
description: 'Get local-time windows for which zodiac signs are rising on a given date and location. Returns deterministic intervals only (no ranking or interpretation).',
|
|
64
|
+
inputSchema: {
|
|
65
|
+
type: 'object',
|
|
66
|
+
properties: {
|
|
67
|
+
date: {
|
|
68
|
+
type: 'string',
|
|
69
|
+
description: 'Target local date (YYYY-MM-DD)',
|
|
70
|
+
},
|
|
71
|
+
latitude: { type: 'number', description: 'Latitude in decimal degrees (-90 to 90)' },
|
|
72
|
+
longitude: {
|
|
73
|
+
type: 'number',
|
|
74
|
+
description: 'Longitude in decimal degrees (-180 to 180)',
|
|
75
|
+
},
|
|
76
|
+
timezone: {
|
|
77
|
+
type: 'string',
|
|
78
|
+
description: 'IANA timezone (e.g., America/New_York)',
|
|
79
|
+
},
|
|
80
|
+
mode: {
|
|
81
|
+
type: 'string',
|
|
82
|
+
enum: ['approximate', 'exact'],
|
|
83
|
+
description: 'Boundary mode. approximate uses coarser stepping; exact refines sign-change boundaries.',
|
|
84
|
+
default: 'approximate',
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
required: ['date', 'latitude', 'longitude', 'timezone'],
|
|
88
|
+
},
|
|
89
|
+
requiresNatalChart: false,
|
|
90
|
+
execute: (ctx, args) => {
|
|
91
|
+
const result = ctx.service.getRisingSignWindows({
|
|
92
|
+
date: args.date,
|
|
93
|
+
latitude: args.latitude,
|
|
94
|
+
longitude: args.longitude,
|
|
95
|
+
timezone: args.timezone,
|
|
96
|
+
mode: args.mode,
|
|
97
|
+
});
|
|
98
|
+
return { kind: 'state', data: result.data, text: result.text };
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
name: 'get_electional_context',
|
|
103
|
+
description: 'Get stateless electional context for a specific local date, time, and location. Returns deterministic timing facts such as ascendant, sect/day-night classification, Moon phase, applying aspects, and optional ascendant-ruler basics. This tool does not require a natal chart and is separate from get_transits.',
|
|
104
|
+
inputSchema: {
|
|
105
|
+
type: 'object',
|
|
106
|
+
properties: {
|
|
107
|
+
date: {
|
|
108
|
+
type: 'string',
|
|
109
|
+
description: 'Target local date (YYYY-MM-DD)',
|
|
110
|
+
},
|
|
111
|
+
time: {
|
|
112
|
+
type: 'string',
|
|
113
|
+
description: 'Target local time (HH:mm or HH:mm:ss). DST-ambiguous or nonexistent local times are rejected.',
|
|
114
|
+
},
|
|
115
|
+
timezone: {
|
|
116
|
+
type: 'string',
|
|
117
|
+
description: 'IANA timezone (e.g., America/New_York)',
|
|
118
|
+
},
|
|
119
|
+
latitude: { type: 'number', description: 'Latitude in decimal degrees (-90 to 90)' },
|
|
120
|
+
longitude: {
|
|
121
|
+
type: 'number',
|
|
122
|
+
description: 'Longitude in decimal degrees (-180 to 180)',
|
|
123
|
+
},
|
|
124
|
+
house_system: {
|
|
125
|
+
type: 'string',
|
|
126
|
+
enum: ['P', 'K', 'W', 'R'],
|
|
127
|
+
description: 'House system used for ascendant extraction: P=Placidus (default), K=Koch, W=Whole Sign, R=Regiomontanus.',
|
|
128
|
+
},
|
|
129
|
+
include_ruler_basics: {
|
|
130
|
+
type: 'boolean',
|
|
131
|
+
description: 'Include ascendant-ruler position, speed, and retrograde flag. Defaults to false.',
|
|
132
|
+
},
|
|
133
|
+
include_planetary_applications: {
|
|
134
|
+
type: 'boolean',
|
|
135
|
+
description: 'Include applying major aspects between current planets. Defaults to true.',
|
|
136
|
+
},
|
|
137
|
+
orb_degrees: {
|
|
138
|
+
type: 'number',
|
|
139
|
+
description: 'Orb for electional aspect detection in degrees. Defaults to 3 and must be between 0.1 and 10.',
|
|
140
|
+
default: 3,
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
required: ['date', 'time', 'timezone', 'latitude', 'longitude'],
|
|
144
|
+
},
|
|
145
|
+
requiresNatalChart: false,
|
|
146
|
+
execute: (ctx, args) => {
|
|
147
|
+
const result = ctx.service.getElectionalContext({
|
|
148
|
+
date: args.date,
|
|
149
|
+
time: args.time,
|
|
150
|
+
timezone: args.timezone,
|
|
151
|
+
latitude: args.latitude,
|
|
152
|
+
longitude: args.longitude,
|
|
153
|
+
house_system: args.house_system,
|
|
154
|
+
include_ruler_basics: args.include_ruler_basics,
|
|
155
|
+
include_planetary_applications: args.include_planetary_applications,
|
|
156
|
+
orb_degrees: args.orb_degrees,
|
|
157
|
+
});
|
|
158
|
+
return { kind: 'state', data: result.data, text: result.text };
|
|
159
|
+
},
|
|
160
|
+
},
|
|
61
161
|
{
|
|
62
162
|
name: 'get_transits',
|
|
63
|
-
description: 'Get transits (aspects between current/future planets and natal chart).
|
|
163
|
+
description: 'Get transits (aspects between current/future planets and natal chart). Each transit includes additive placement metadata for both sides (sign, degree, house) so clients can render activation context without reconstructing house logic. Supports mode=snapshot (single-day), mode=best_hit (multi-day compressed preview), and mode=forecast (day-grouped output). If mode is omitted, legacy behavior is preserved: days_ahead=0 resolves to snapshot and days_ahead>0 resolves to best_hit.',
|
|
64
164
|
inputSchema: {
|
|
65
165
|
type: 'object',
|
|
66
166
|
properties: {
|
|
@@ -79,9 +179,14 @@ export const MCP_TOOL_SPECS = [
|
|
|
79
179
|
},
|
|
80
180
|
days_ahead: {
|
|
81
181
|
type: 'number',
|
|
82
|
-
description: 'Number of days to look ahead
|
|
182
|
+
description: 'Number of days to look ahead. In snapshot mode only the start day is used. If mode is omitted, legacy behavior is preserved: 0 resolves to snapshot and values > 0 resolve to best_hit.',
|
|
83
183
|
default: 0,
|
|
84
184
|
},
|
|
185
|
+
mode: {
|
|
186
|
+
type: 'string',
|
|
187
|
+
enum: ['snapshot', 'best_hit', 'forecast'],
|
|
188
|
+
description: 'Transit output mode: snapshot=single-day, best_hit=compressed preview across range, forecast=day-grouped output. If omitted, legacy behavior is preserved.',
|
|
189
|
+
},
|
|
85
190
|
max_orb: {
|
|
86
191
|
type: 'number',
|
|
87
192
|
description: 'Maximum orb in degrees to include. Defaults to 8.',
|
|
@@ -104,6 +209,7 @@ export const MCP_TOOL_SPECS = [
|
|
|
104
209
|
categories: args.categories,
|
|
105
210
|
include_mundane: args.include_mundane,
|
|
106
211
|
days_ahead: args.days_ahead,
|
|
212
|
+
mode: args.mode,
|
|
107
213
|
max_orb: args.max_orb,
|
|
108
214
|
exact_only: args.exact_only,
|
|
109
215
|
applying_only: args.applying_only,
|
|
@@ -139,7 +245,7 @@ export const MCP_TOOL_SPECS = [
|
|
|
139
245
|
inputSchema: { type: 'object', properties: {} },
|
|
140
246
|
requiresNatalChart: false,
|
|
141
247
|
execute: (ctx, args) => {
|
|
142
|
-
const timezone = args.timezone
|
|
248
|
+
const timezone = ctx.service.resolveReportingTimezone(args.timezone, ctx.natalChart?.location?.timezone);
|
|
143
249
|
const result = ctx.service.getRetrogradePlanets(timezone);
|
|
144
250
|
return { kind: 'state', data: result.data, text: result.text };
|
|
145
251
|
},
|
|
@@ -160,7 +266,7 @@ export const MCP_TOOL_SPECS = [
|
|
|
160
266
|
inputSchema: { type: 'object', properties: {} },
|
|
161
267
|
requiresNatalChart: false,
|
|
162
268
|
execute: (ctx, args) => {
|
|
163
|
-
const timezone = args.timezone
|
|
269
|
+
const timezone = ctx.service.resolveReportingTimezone(args.timezone, ctx.natalChart?.location?.timezone);
|
|
164
270
|
const result = ctx.service.getAsteroidPositions(timezone);
|
|
165
271
|
return { kind: 'state', data: result.data, text: result.text };
|
|
166
272
|
},
|
|
@@ -171,7 +277,7 @@ export const MCP_TOOL_SPECS = [
|
|
|
171
277
|
inputSchema: { type: 'object', properties: {} },
|
|
172
278
|
requiresNatalChart: false,
|
|
173
279
|
execute: (ctx, args) => {
|
|
174
|
-
const timezone = args.timezone
|
|
280
|
+
const timezone = ctx.service.resolveReportingTimezone(args.timezone, ctx.natalChart?.location?.timezone);
|
|
175
281
|
const result = ctx.service.getNextEclipses(timezone);
|
|
176
282
|
return { kind: 'state', data: result.data, text: result.text };
|
|
177
283
|
},
|
package/dist/tool-result.d.ts
CHANGED
|
@@ -125,6 +125,14 @@ export declare function mcpError(error: ToolIssue): {
|
|
|
125
125
|
* agent-recoverable error codes with suggested fixes.
|
|
126
126
|
*/
|
|
127
127
|
export declare function mapSweError(context: string, err: unknown, details?: Record<string, unknown>): ToolIssue;
|
|
128
|
+
/**
|
|
129
|
+
* Map generic error messages to structured tool issue codes.
|
|
130
|
+
*
|
|
131
|
+
* @remarks
|
|
132
|
+
* Primarily used at the MCP boundary when unknown errors are caught
|
|
133
|
+
* and need a best-effort classification.
|
|
134
|
+
*/
|
|
135
|
+
export declare function mapToolErrorMessageToCode(errorMessage: string): ToolIssueCode;
|
|
128
136
|
/**
|
|
129
137
|
* Create a structured error for missing rise/set events
|
|
130
138
|
*
|
package/dist/tool-result.js
CHANGED
|
@@ -107,6 +107,45 @@ export function mapSweError(context, err, details) {
|
|
|
107
107
|
details: { ...details, rawMessage: message },
|
|
108
108
|
};
|
|
109
109
|
}
|
|
110
|
+
/**
|
|
111
|
+
* Map generic error messages to structured tool issue codes.
|
|
112
|
+
*
|
|
113
|
+
* @remarks
|
|
114
|
+
* Primarily used at the MCP boundary when unknown errors are caught
|
|
115
|
+
* and need a best-effort classification.
|
|
116
|
+
*/
|
|
117
|
+
export function mapToolErrorMessageToCode(errorMessage) {
|
|
118
|
+
if (errorMessage.includes('Invalid date format') ||
|
|
119
|
+
errorMessage.includes('Invalid calendar date') ||
|
|
120
|
+
errorMessage.includes('Invalid month') ||
|
|
121
|
+
errorMessage.includes('Invalid day') ||
|
|
122
|
+
errorMessage.includes('days_ahead') ||
|
|
123
|
+
errorMessage.includes('max_orb') ||
|
|
124
|
+
errorMessage.includes('missing julianDay') ||
|
|
125
|
+
errorMessage.includes('Invalid mode') ||
|
|
126
|
+
errorMessage.includes('Invalid latitude') ||
|
|
127
|
+
errorMessage.includes('Invalid longitude')) {
|
|
128
|
+
return 'INVALID_INPUT';
|
|
129
|
+
}
|
|
130
|
+
if (errorMessage.includes('Invalid timezone') || errorMessage.includes('timezone')) {
|
|
131
|
+
return 'INVALID_TIMEZONE';
|
|
132
|
+
}
|
|
133
|
+
if (errorMessage.includes('Invalid house system')) {
|
|
134
|
+
return 'INVALID_HOUSE_SYSTEM';
|
|
135
|
+
}
|
|
136
|
+
if (errorMessage.includes('Ephemeris') || errorMessage.includes('ephemeris')) {
|
|
137
|
+
return 'EPHEMERIS_COMPUTE_FAILED';
|
|
138
|
+
}
|
|
139
|
+
if (errorMessage.includes('write') ||
|
|
140
|
+
errorMessage.includes('ENOENT') ||
|
|
141
|
+
errorMessage.includes('EACCES')) {
|
|
142
|
+
return 'FILE_WRITE_FAILED';
|
|
143
|
+
}
|
|
144
|
+
if (errorMessage.includes('render') || errorMessage.includes('chart')) {
|
|
145
|
+
return 'CHART_RENDER_FAILED';
|
|
146
|
+
}
|
|
147
|
+
return 'INTERNAL_ERROR';
|
|
148
|
+
}
|
|
110
149
|
/**
|
|
111
150
|
* Create a structured error for missing rise/set events
|
|
112
151
|
*
|
package/dist/types.d.ts
CHANGED
|
@@ -54,6 +54,12 @@ export interface NatalChart {
|
|
|
54
54
|
* For polar latitudes (>66°), Whole Sign ('W') may be used as fallback.
|
|
55
55
|
*/
|
|
56
56
|
houseSystem?: HouseSystem;
|
|
57
|
+
/**
|
|
58
|
+
* Explicit house system requested when the natal chart was created
|
|
59
|
+
* @remarks
|
|
60
|
+
* This stays undefined when the caller relied on runtime defaults.
|
|
61
|
+
*/
|
|
62
|
+
requestedHouseSystem?: HouseSystem;
|
|
57
63
|
/**
|
|
58
64
|
* UTC equivalent of birth time
|
|
59
65
|
* @remarks
|
|
@@ -181,6 +187,18 @@ export interface TransitData extends BaseTransit {
|
|
|
181
187
|
* May be undefined if aspect is not within orb or exact time not calculated
|
|
182
188
|
*/
|
|
183
189
|
exactTime?: string;
|
|
190
|
+
/** Zodiac sign of the transiting planet */
|
|
191
|
+
transitSign?: string;
|
|
192
|
+
/** Degree of the transiting planet within its sign (0-30) */
|
|
193
|
+
transitDegree?: number;
|
|
194
|
+
/** House occupied by the transiting planet at the transit calculation time */
|
|
195
|
+
transitHouse?: number;
|
|
196
|
+
/** Zodiac sign of the natal planet */
|
|
197
|
+
natalSign?: string;
|
|
198
|
+
/** Degree of the natal planet within its sign (0-30) */
|
|
199
|
+
natalDegree?: number;
|
|
200
|
+
/** House occupied by the natal planet in the natal chart */
|
|
201
|
+
natalHouse?: number;
|
|
184
202
|
}
|
|
185
203
|
/**
|
|
186
204
|
* Response wrapper for transit data
|
|
@@ -192,8 +210,12 @@ export interface TransitData extends BaseTransit {
|
|
|
192
210
|
export interface TransitResponse {
|
|
193
211
|
/** ISO date of the transit calculation (YYYY-MM-DD) */
|
|
194
212
|
date: string;
|
|
195
|
-
/** Timezone used for
|
|
213
|
+
/** Timezone used for reporting and user-facing day labels */
|
|
196
214
|
timezone: string;
|
|
215
|
+
/** Natal/local timezone used for date interpretation and astro calculations */
|
|
216
|
+
calculation_timezone?: string;
|
|
217
|
+
/** Timezone used for reporting and user-facing day labels */
|
|
218
|
+
reporting_timezone?: string;
|
|
197
219
|
/** Array of all active transits for the date */
|
|
198
220
|
transits: TransitData[];
|
|
199
221
|
}
|
|
@@ -211,6 +233,7 @@ export interface PlanetPositionResponse {
|
|
|
211
233
|
/** Array of planet positions */
|
|
212
234
|
positions: PlanetPosition[];
|
|
213
235
|
}
|
|
236
|
+
export type ElectionalHouseSystem = Extract<HouseSystem, 'P' | 'K' | 'W' | 'R'>;
|
|
214
237
|
/**
|
|
215
238
|
* Types of astrological aspects
|
|
216
239
|
*
|
|
@@ -219,6 +242,61 @@ export interface PlanetPositionResponse {
|
|
|
219
242
|
* specific types of interactions and energies.
|
|
220
243
|
*/
|
|
221
244
|
export type AspectType = 'conjunction' | 'opposition' | 'square' | 'trine' | 'sextile';
|
|
245
|
+
export type ElectionalPhaseName = 'new' | 'crescent' | 'first_quarter' | 'gibbous' | 'full' | 'disseminating' | 'last_quarter' | 'balsamic';
|
|
246
|
+
export interface ElectionalAspect {
|
|
247
|
+
from_body: PlanetName;
|
|
248
|
+
to_body: PlanetName;
|
|
249
|
+
aspect: AspectType;
|
|
250
|
+
orb: number;
|
|
251
|
+
applying: boolean;
|
|
252
|
+
exact_at_utc?: string;
|
|
253
|
+
}
|
|
254
|
+
export interface ElectionalContextResponse {
|
|
255
|
+
input: {
|
|
256
|
+
date: string;
|
|
257
|
+
time: string;
|
|
258
|
+
timezone: string;
|
|
259
|
+
latitude: number;
|
|
260
|
+
longitude: number;
|
|
261
|
+
house_system: ElectionalHouseSystem;
|
|
262
|
+
instant_utc: string;
|
|
263
|
+
jd_ut: number;
|
|
264
|
+
};
|
|
265
|
+
ascendant: {
|
|
266
|
+
longitude: number;
|
|
267
|
+
sign: string;
|
|
268
|
+
degree_in_sign: number;
|
|
269
|
+
};
|
|
270
|
+
sect: {
|
|
271
|
+
is_day_chart: boolean;
|
|
272
|
+
sun_altitude_degrees: number;
|
|
273
|
+
classification: 'day' | 'night';
|
|
274
|
+
};
|
|
275
|
+
moon: {
|
|
276
|
+
longitude: number;
|
|
277
|
+
sign: string;
|
|
278
|
+
phase_angle: number;
|
|
279
|
+
phase_name: ElectionalPhaseName;
|
|
280
|
+
is_void_of_course: boolean | null;
|
|
281
|
+
applying_aspects?: ElectionalAspect[];
|
|
282
|
+
};
|
|
283
|
+
applying_aspects?: ElectionalAspect[];
|
|
284
|
+
ruler_basics?: {
|
|
285
|
+
asc_sign_ruler: {
|
|
286
|
+
body: PlanetName;
|
|
287
|
+
longitude: number;
|
|
288
|
+
sign: string;
|
|
289
|
+
speed: number;
|
|
290
|
+
is_retrograde: boolean;
|
|
291
|
+
};
|
|
292
|
+
};
|
|
293
|
+
meta: {
|
|
294
|
+
deterministic: true;
|
|
295
|
+
requires_natal: false;
|
|
296
|
+
warnings: string[];
|
|
297
|
+
deferred_features: string[];
|
|
298
|
+
};
|
|
299
|
+
}
|
|
222
300
|
/**
|
|
223
301
|
* Aspect definitions with angles and default orbs
|
|
224
302
|
*
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# ADR 0001: MCP Vs Skill Boundary
|
|
2
|
+
|
|
3
|
+
- Status: Accepted
|
|
4
|
+
- Date: 2026-03-28
|
|
5
|
+
|
|
6
|
+
## Context
|
|
7
|
+
|
|
8
|
+
`ether-to-astro` is evolving from a set of astrology utilities into infrastructure for AI-native workflows.
|
|
9
|
+
|
|
10
|
+
Recent product exploration introduced requests for:
|
|
11
|
+
|
|
12
|
+
- daily and weekly transit reporting,
|
|
13
|
+
- mundane baseline plus natal modifier reasoning,
|
|
14
|
+
- electional support,
|
|
15
|
+
- Whole Sign-first house activation reporting,
|
|
16
|
+
- and preference-aware planning workflows.
|
|
17
|
+
|
|
18
|
+
Without an explicit boundary, it would be easy to grow the MCP surface into a large collection of personalized reporting tools. That would make the product harder to maintain, harder to reuse, and harder to reason about.
|
|
19
|
+
|
|
20
|
+
## Decision
|
|
21
|
+
|
|
22
|
+
We will keep the boundary as follows:
|
|
23
|
+
|
|
24
|
+
- `MCP tools` own reusable astrological computation and structured primitives.
|
|
25
|
+
- `Skills or workflows` own synthesis, ranking, personalization, and report generation.
|
|
26
|
+
- `MCP prompts` may expose common workflows for discovery, but they do not become the main home for product logic.
|
|
27
|
+
- `Profiles or preferences` own durable user-specific output choices and heuristics.
|
|
28
|
+
|
|
29
|
+
## Consequences
|
|
30
|
+
|
|
31
|
+
### Positive
|
|
32
|
+
|
|
33
|
+
- MCP stays focused and reusable.
|
|
34
|
+
- Skills can evolve quickly without destabilizing the computational layer.
|
|
35
|
+
- Personalized reporting can improve without forcing product-wide API churn.
|
|
36
|
+
- New agents and clients get a clearer mental model of the system.
|
|
37
|
+
|
|
38
|
+
### Negative
|
|
39
|
+
|
|
40
|
+
- Some workflows will require multiple calls instead of one large convenience tool.
|
|
41
|
+
- Skill authors must still do synthesis work rather than relying on all-in-one MCP endpoints.
|
|
42
|
+
- We need stronger docs and examples so the boundary remains easy to apply.
|
|
43
|
+
|
|
44
|
+
## Decision Rules
|
|
45
|
+
|
|
46
|
+
Add a capability to MCP when:
|
|
47
|
+
|
|
48
|
+
- it is deterministic or mostly deterministic,
|
|
49
|
+
- it is computation-heavy,
|
|
50
|
+
- reusable across multiple workflows,
|
|
51
|
+
- and risky or repetitive to reconstruct client-side.
|
|
52
|
+
|
|
53
|
+
Keep a capability in a skill when:
|
|
54
|
+
|
|
55
|
+
- it is interpretation-heavy,
|
|
56
|
+
- it is deterministic but mainly encodes workflow policy or ranking,
|
|
57
|
+
- user-specific,
|
|
58
|
+
- likely to evolve rapidly,
|
|
59
|
+
- or achievable by a competent LLM in two calls to stable primitives.
|
|
60
|
+
|
|
61
|
+
## Examples
|
|
62
|
+
|
|
63
|
+
### Belongs In MCP
|
|
64
|
+
|
|
65
|
+
- transit forecast data grouped by day
|
|
66
|
+
- mundane aspects
|
|
67
|
+
- house activation metadata
|
|
68
|
+
- electional primitives
|
|
69
|
+
- rising-sign windows
|
|
70
|
+
|
|
71
|
+
### Belongs In Skills
|
|
72
|
+
|
|
73
|
+
- daily briefing
|
|
74
|
+
- weekly overview synthesis
|
|
75
|
+
- “top influences” ranking
|
|
76
|
+
- best-use guidance
|
|
77
|
+
- personalized electional scoring
|
|
78
|
+
|
|
79
|
+
### Does Not Belong In MCP
|
|
80
|
+
|
|
81
|
+
- one-user workflow tags
|
|
82
|
+
- customized work or life advice
|
|
83
|
+
- generic prose-generation tools that a skill could perform from structured data
|
|
84
|
+
|
|
85
|
+
## Follow-Up
|
|
86
|
+
|
|
87
|
+
This ADR implies:
|
|
88
|
+
|
|
89
|
+
- future MCP proposals should justify why the capability cannot stay in a skill,
|
|
90
|
+
- new reporting workflows should start life outside MCP,
|
|
91
|
+
- and docs should make the two-call skill model the default design assumption.
|
|
92
|
+
|
|
93
|
+
## Related Docs
|
|
94
|
+
|
|
95
|
+
- [Product Tenets](/Users/salted/Code/astro-mcp/docs/product/product-tenets.md)
|
|
96
|
+
- [Architecture Boundaries](/Users/salted/Code/astro-mcp/docs/product/architecture-boundaries.md)
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
# Architecture Boundaries
|
|
2
|
+
|
|
3
|
+
This document translates the product tenets into concrete repository and interface boundaries.
|
|
4
|
+
|
|
5
|
+
## System Layers
|
|
6
|
+
|
|
7
|
+
The product has four distinct layers:
|
|
8
|
+
|
|
9
|
+
1. `MCP tools`
|
|
10
|
+
Source of truth for reusable astrological computation.
|
|
11
|
+
2. `MCP prompts`
|
|
12
|
+
Thin public entry points that help clients discover or initiate common workflows.
|
|
13
|
+
3. `Assistant skills or workflows`
|
|
14
|
+
AI-native orchestration, synthesis, ranking, and formatting built on top of MCP tools.
|
|
15
|
+
4. `Profiles or preferences`
|
|
16
|
+
User- or workflow-specific settings that shape output without polluting generic tool contracts.
|
|
17
|
+
|
|
18
|
+
## The Boundary In One Sentence
|
|
19
|
+
|
|
20
|
+
MCP computes and exposes facts. Skills decide what matters, how to rank it, and how to present it.
|
|
21
|
+
|
|
22
|
+
## Determinism Rule
|
|
23
|
+
|
|
24
|
+
Deterministic, generic astrology outputs are strong candidates for MCP.
|
|
25
|
+
|
|
26
|
+
This includes:
|
|
27
|
+
|
|
28
|
+
- added flags on existing tools,
|
|
29
|
+
- expanded date windows,
|
|
30
|
+
- grouped forecast output,
|
|
31
|
+
- generic derived astro signals,
|
|
32
|
+
- and reusable metadata that multiple clients would otherwise reconstruct themselves.
|
|
33
|
+
|
|
34
|
+
Determinism is not sufficient by itself.
|
|
35
|
+
|
|
36
|
+
Deterministic outputs should still stay out of MCP when they primarily encode:
|
|
37
|
+
|
|
38
|
+
- personalized policy,
|
|
39
|
+
- workflow-specific scoring,
|
|
40
|
+
- subjective ranking,
|
|
41
|
+
- or narrative conclusions.
|
|
42
|
+
|
|
43
|
+
## What Counts As A Primitive
|
|
44
|
+
|
|
45
|
+
A primitive is a structured output that:
|
|
46
|
+
|
|
47
|
+
- represents reusable astrological truth,
|
|
48
|
+
- is deterministic or mostly deterministic for the same inputs,
|
|
49
|
+
- is stable across multiple workflows,
|
|
50
|
+
- and does not depend on one user’s preferred interpretation style.
|
|
51
|
+
|
|
52
|
+
Examples of good primitives:
|
|
53
|
+
|
|
54
|
+
- transit events
|
|
55
|
+
- date-grouped forecast windows
|
|
56
|
+
- mundane aspects
|
|
57
|
+
- house activations
|
|
58
|
+
- Moon condition
|
|
59
|
+
- ASC sign and ruler condition
|
|
60
|
+
- rising-sign windows
|
|
61
|
+
|
|
62
|
+
Examples of non-primitives:
|
|
63
|
+
|
|
64
|
+
- “top 3 influences today”
|
|
65
|
+
- “best time to influence leadership”
|
|
66
|
+
- “high opportunity” versus “mixed-intense”
|
|
67
|
+
- custom tags like “report readout” or “spiritual ritual”
|
|
68
|
+
|
|
69
|
+
## MCP Tool Design Rules
|
|
70
|
+
|
|
71
|
+
### Prefer Generic Modes Over Tool Proliferation
|
|
72
|
+
|
|
73
|
+
Good:
|
|
74
|
+
|
|
75
|
+
- one transit tool with explicit modes such as `snapshot`, `best_hit`, and `forecast`
|
|
76
|
+
- one electional-context tool with optional include flags for rising windows, Moon condition, and house context
|
|
77
|
+
|
|
78
|
+
Bad:
|
|
79
|
+
|
|
80
|
+
- `get_daily_transits`
|
|
81
|
+
- `get_weekly_transits`
|
|
82
|
+
- `get_best_transits_for_work`
|
|
83
|
+
- `get_spiritual_transits`
|
|
84
|
+
|
|
85
|
+
### Favor Structured Data Over Prose
|
|
86
|
+
|
|
87
|
+
Good MCP output:
|
|
88
|
+
|
|
89
|
+
- date-grouped JSON
|
|
90
|
+
- exact-time metadata
|
|
91
|
+
- sign, degree, house, and applying/separating state
|
|
92
|
+
|
|
93
|
+
Bad MCP output:
|
|
94
|
+
|
|
95
|
+
- opinionated text summaries
|
|
96
|
+
- personalized advice
|
|
97
|
+
- user-specific ranking labels baked into the contract
|
|
98
|
+
|
|
99
|
+
### Keep Personalization Out Of MCP
|
|
100
|
+
|
|
101
|
+
Do not hardcode into MCP:
|
|
102
|
+
|
|
103
|
+
- one user’s preferred house system for reporting
|
|
104
|
+
- one user’s preferred electional rulers
|
|
105
|
+
- one user’s work or life categories
|
|
106
|
+
- one user’s “good” and “bad” framing
|
|
107
|
+
|
|
108
|
+
Instead:
|
|
109
|
+
|
|
110
|
+
- expose the raw or lightly structured inputs,
|
|
111
|
+
- then let skills and preferences shape the output.
|
|
112
|
+
|
|
113
|
+
## Skill Design Rules
|
|
114
|
+
|
|
115
|
+
Skills should:
|
|
116
|
+
|
|
117
|
+
- call the smallest useful set of MCP primitives,
|
|
118
|
+
- avoid reimplementing astro logic,
|
|
119
|
+
- own synthesis and narrative structure,
|
|
120
|
+
- and encode workflow-level decisions such as section order, ranking, tone, and user-preference application.
|
|
121
|
+
|
|
122
|
+
The default target is a one-call or two-call skill flow.
|
|
123
|
+
|
|
124
|
+
Skill-layer examples in this repo are primarily boundary examples and workflow incubators.
|
|
125
|
+
|
|
126
|
+
They should not be read as automatic commitments to build or maintain every listed workflow as a first-class product artifact.
|
|
127
|
+
|
|
128
|
+
### Good Skill Flow
|
|
129
|
+
|
|
130
|
+
1. Call forecast primitives.
|
|
131
|
+
2. Optionally call electional primitives for narrowed windows.
|
|
132
|
+
3. Synthesize the report.
|
|
133
|
+
|
|
134
|
+
### Bad Skill Flow
|
|
135
|
+
|
|
136
|
+
- dozens of tiny MCP calls that should have been a reusable primitive
|
|
137
|
+
- custom astro math in the skill
|
|
138
|
+
- pretending uncertain interpretation is hard computation
|
|
139
|
+
|
|
140
|
+
## Prompt Design Rules
|
|
141
|
+
|
|
142
|
+
Prompts are optional adapters, not the core product logic.
|
|
143
|
+
|
|
144
|
+
Use prompts when you want:
|
|
145
|
+
|
|
146
|
+
- discoverability in MCP-aware clients,
|
|
147
|
+
- a stable public workflow entry point,
|
|
148
|
+
- and a generic wrapper around one or more tools.
|
|
149
|
+
|
|
150
|
+
Do not use prompts as a substitute for:
|
|
151
|
+
|
|
152
|
+
- a missing MCP primitive,
|
|
153
|
+
- or a real skill/workflow spec.
|
|
154
|
+
|
|
155
|
+
## Preference Design Rules
|
|
156
|
+
|
|
157
|
+
Profiles or preferences should contain durable output preferences and user heuristics.
|
|
158
|
+
|
|
159
|
+
Examples:
|
|
160
|
+
|
|
161
|
+
- reporting timezone
|
|
162
|
+
- preferred house system for interpretation
|
|
163
|
+
- favored electional filters
|
|
164
|
+
- preferred report sections
|
|
165
|
+
- recurring tags or domain labels
|
|
166
|
+
|
|
167
|
+
Preferences should not:
|
|
168
|
+
|
|
169
|
+
- alter astro computation silently,
|
|
170
|
+
- fork the meaning of a generic MCP tool,
|
|
171
|
+
- or create multiple incompatible interpretations of the same primitive.
|
|
172
|
+
|
|
173
|
+
## When To Add A New MCP Primitive
|
|
174
|
+
|
|
175
|
+
Add a new MCP primitive when all or most of the following are true:
|
|
176
|
+
|
|
177
|
+
- it is deterministic or mostly deterministic,
|
|
178
|
+
- it is computational,
|
|
179
|
+
- it is reusable,
|
|
180
|
+
- multiple skills or clients need it,
|
|
181
|
+
- it would be brittle to reconstruct client-side,
|
|
182
|
+
- and it reduces total system complexity.
|
|
183
|
+
|
|
184
|
+
## When Not To Add A New MCP Primitive
|
|
185
|
+
|
|
186
|
+
Do not add a new MCP primitive when:
|
|
187
|
+
|
|
188
|
+
- it mainly produces prose,
|
|
189
|
+
- it mainly encodes policy rather than astro fact,
|
|
190
|
+
- it mainly reflects one user’s preferences,
|
|
191
|
+
- the same result can be achieved by a competent LLM in two calls,
|
|
192
|
+
- or it would create a specialized tool for a narrow workflow instead of strengthening a generic primitive.
|
|
193
|
+
|
|
194
|
+
## Current Repo Implications
|
|
195
|
+
|
|
196
|
+
Based on the current product direction:
|
|
197
|
+
|
|
198
|
+
### Strong candidates for MCP
|
|
199
|
+
|
|
200
|
+
- range-aware transit forecast data
|
|
201
|
+
- mundane aspect data
|
|
202
|
+
- house-aware transit activations
|
|
203
|
+
- electional primitives
|
|
204
|
+
- rising-sign window helpers
|
|
205
|
+
|
|
206
|
+
### Candidate workflow experiments after primitive stabilization
|
|
207
|
+
|
|
208
|
+
- daily brief generation
|
|
209
|
+
- weekly overview synthesis
|
|
210
|
+
- electional overlay and ranking
|
|
211
|
+
- action-oriented summaries
|
|
212
|
+
- user-specific framing and categorization
|
|
213
|
+
|
|
214
|
+
### Strong candidates for docs or examples
|
|
215
|
+
|
|
216
|
+
- reference workflows that show how to combine forecast plus electional context
|
|
217
|
+
- examples of what lives in skills versus MCP
|
|
218
|
+
- preference contract guidance for user-facing reporting
|
|
219
|
+
|
|
220
|
+
## Related Docs
|
|
221
|
+
|
|
222
|
+
- [Product Tenets](/Users/salted/Code/astro-mcp/docs/product/product-tenets.md)
|
|
223
|
+
- [ADR 0001: MCP Vs Skill Boundary](/Users/salted/Code/astro-mcp/docs/product/adrs/0001-mcp-vs-skill-boundary.md)
|