@openephemeris/mcp-server 3.2.12 → 3.3.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.
Files changed (88) hide show
  1. package/README.md +2 -2
  2. package/dist/backend/client.d.ts +23 -1
  3. package/dist/backend/client.js +144 -25
  4. package/dist/index.js +94 -2
  5. package/dist/tools/dev.js +29 -21
  6. package/dist/tools/index.d.ts +8 -0
  7. package/dist/tools/index.js +38 -1
  8. package/dist/tools/specialized/acg.js +143 -0
  9. package/dist/tools/specialized/comparative.js +153 -0
  10. package/dist/tools/specialized/eclipse.js +20 -26
  11. package/dist/tools/specialized/electional.js +182 -3
  12. package/dist/tools/specialized/ephemeris_core.js +93 -0
  13. package/dist/tools/specialized/ephemeris_extended.js +234 -0
  14. package/dist/tools/specialized/hd_group.js +107 -0
  15. package/dist/tools/specialized/human_design.js +27 -11
  16. package/dist/tools/specialized/moon.js +3 -2
  17. package/dist/tools/specialized/natal.js +36 -15
  18. package/dist/tools/specialized/progressed.js +65 -0
  19. package/dist/tools/specialized/relocation.js +14 -10
  20. package/dist/tools/specialized/returns.js +144 -0
  21. package/dist/tools/specialized/synastry.js +22 -12
  22. package/dist/tools/specialized/transits.js +78 -28
  23. package/dist/tools/specialized/venus_star_points.js +187 -0
  24. package/package.json +4 -3
  25. package/dist/scripts/dev-allowlist.js +0 -287
  26. package/dist/scripts/pack-audit.js +0 -45
  27. package/dist/scripts/schema-packs.js +0 -150
  28. package/dist/scripts/smoke-dev-profile.js +0 -25
  29. package/dist/scripts/sync-readme.js +0 -141
  30. package/dist/scripts/test-all-tools.js +0 -104
  31. package/dist/scripts/test-client.js +0 -69
  32. package/dist/scripts/test-sse-client.js +0 -221
  33. package/dist/src/backend/client.d.ts +0 -67
  34. package/dist/src/backend/client.js +0 -363
  35. package/dist/src/index.d.ts +0 -2
  36. package/dist/src/index.js +0 -184
  37. package/dist/src/schema-packs/llm.d.ts +0 -105
  38. package/dist/src/schema-packs/llm.js +0 -429
  39. package/dist/src/tools/dev.d.ts +0 -1
  40. package/dist/src/tools/dev.js +0 -195
  41. package/dist/src/tools/index.d.ts +0 -33
  42. package/dist/src/tools/index.js +0 -60
  43. package/dist/src/tools/specialized/eclipse.d.ts +0 -1
  44. package/dist/src/tools/specialized/eclipse.js +0 -50
  45. package/dist/src/tools/specialized/electional.d.ts +0 -1
  46. package/dist/src/tools/specialized/electional.js +0 -80
  47. package/dist/src/tools/specialized/human_design.d.ts +0 -1
  48. package/dist/src/tools/specialized/human_design.js +0 -69
  49. package/dist/src/tools/specialized/moon.d.ts +0 -1
  50. package/dist/src/tools/specialized/moon.js +0 -51
  51. package/dist/src/tools/specialized/natal.d.ts +0 -1
  52. package/dist/src/tools/specialized/natal.js +0 -92
  53. package/dist/src/tools/specialized/relocation.d.ts +0 -1
  54. package/dist/src/tools/specialized/relocation.js +0 -76
  55. package/dist/src/tools/specialized/synastry.d.ts +0 -1
  56. package/dist/src/tools/specialized/synastry.js +0 -73
  57. package/dist/src/tools/specialized/transits.d.ts +0 -1
  58. package/dist/src/tools/specialized/transits.js +0 -130
  59. package/dist/test/allowlist-and-tools.test.d.ts +0 -1
  60. package/dist/test/allowlist-and-tools.test.js +0 -96
  61. package/dist/test/backend-client.test.d.ts +0 -1
  62. package/dist/test/backend-client.test.js +0 -284
  63. package/dist/test/credentials.test.d.ts +0 -1
  64. package/dist/test/credentials.test.js +0 -143
  65. /package/dist/{src/auth → auth}/credentials.d.ts +0 -0
  66. /package/dist/{src/auth → auth}/credentials.js +0 -0
  67. /package/dist/{src/auth → auth}/device-auth.d.ts +0 -0
  68. /package/dist/{src/auth → auth}/device-auth.js +0 -0
  69. /package/dist/{src/server-sse.d.ts → server-sse.d.ts} +0 -0
  70. /package/dist/{src/server-sse.js → server-sse.js} +0 -0
  71. /package/dist/{src/tools → tools}/auth.d.ts +0 -0
  72. /package/dist/{src/tools → tools}/auth.js +0 -0
  73. /package/dist/{scripts/dev-allowlist.d.ts → tools/specialized/acg.d.ts} +0 -0
  74. /package/dist/{src/tools → tools}/specialized/bazi.d.ts +0 -0
  75. /package/dist/{src/tools → tools}/specialized/bazi.js +0 -0
  76. /package/dist/{src/tools → tools}/specialized/bi_wheel.d.ts +0 -0
  77. /package/dist/{src/tools → tools}/specialized/bi_wheel.js +0 -0
  78. /package/dist/{src/tools → tools}/specialized/chart_wheel.d.ts +0 -0
  79. /package/dist/{src/tools → tools}/specialized/chart_wheel.js +0 -0
  80. /package/dist/{scripts/pack-audit.d.ts → tools/specialized/comparative.d.ts} +0 -0
  81. /package/dist/{scripts/schema-packs.d.ts → tools/specialized/ephemeris_core.d.ts} +0 -0
  82. /package/dist/{scripts/smoke-dev-profile.d.ts → tools/specialized/ephemeris_extended.d.ts} +0 -0
  83. /package/dist/{scripts/sync-readme.d.ts → tools/specialized/hd_group.d.ts} +0 -0
  84. /package/dist/{scripts/test-all-tools.d.ts → tools/specialized/progressed.d.ts} +0 -0
  85. /package/dist/{scripts/test-client.d.ts → tools/specialized/returns.d.ts} +0 -0
  86. /package/dist/{src/tools → tools}/specialized/vedic.d.ts +0 -0
  87. /package/dist/{src/tools → tools}/specialized/vedic.js +0 -0
  88. /package/dist/{scripts/test-sse-client.d.ts → tools/specialized/venus_star_points.d.ts} +0 -0
@@ -1,287 +0,0 @@
1
- import fs from "node:fs";
2
- import path from "node:path";
3
- import crypto from "node:crypto";
4
- import { fileURLToPath } from "node:url";
5
- import axios from "axios";
6
- function normalizeTier(raw) {
7
- if (typeof raw !== "string")
8
- return null;
9
- const value = raw.trim().toLowerCase();
10
- if (value === "free" ||
11
- value === "all" ||
12
- value === "free/all" ||
13
- value === "anonymous" ||
14
- value === "anonymous/all" ||
15
- value === "public" ||
16
- value === "public/all")
17
- return "free";
18
- if (value === "dev" || value === "developer")
19
- return "developer";
20
- if (value === "pro")
21
- return "pro";
22
- if (value === "production")
23
- return "production";
24
- if (value === "admin" || value === "internal")
25
- return "admin";
26
- return null;
27
- }
28
- function isCommercialTier(tierNormalized) {
29
- // Dev MCP should expose all commercially available tiers up through production.
30
- return tierNormalized === "free" || tierNormalized === "developer" || tierNormalized === "pro" || tierNormalized === "production";
31
- }
32
- function sha256(text) {
33
- return crypto.createHash("sha256").update(text).digest("hex");
34
- }
35
- function readJsonFile(filePath) {
36
- const raw = fs.readFileSync(filePath, "utf-8");
37
- return { raw, json: JSON.parse(raw) };
38
- }
39
- function writeJsonFile(filePath, obj) {
40
- fs.writeFileSync(filePath, JSON.stringify(obj, null, 2) + "\n", "utf-8");
41
- }
42
- function normalizeTag(tag) {
43
- return tag.trim().toLowerCase();
44
- }
45
- function isDeniedByPrefix(pathname, prefixes) {
46
- return prefixes.some((p) => pathname.startsWith(p));
47
- }
48
- function hasDeniedTag(tags, denyTags) {
49
- const deny = new Set(denyTags.map(normalizeTag));
50
- return tags.some((t) => deny.has(normalizeTag(t)));
51
- }
52
- function getDefaults() {
53
- const here = path.dirname(fileURLToPath(import.meta.url)); // .../mcp-server/scripts
54
- const mcpServerRoot = path.resolve(here, "..");
55
- const repoRoot = path.resolve(mcpServerRoot, "..");
56
- return {
57
- mcpServerRoot,
58
- repoRoot,
59
- allowlistPath: path.join(mcpServerRoot, "config", "dev-allowlist.json"),
60
- openapiPath: path.join(repoRoot, "go-sidecar", "openapi.json"),
61
- };
62
- }
63
- function parseArgs(argv) {
64
- const args = {
65
- openapiPath: undefined,
66
- allowlistPath: undefined,
67
- write: false,
68
- check: false,
69
- };
70
- for (let i = 0; i < argv.length; i++) {
71
- const a = argv[i];
72
- if (a === "--openapi")
73
- args.openapiPath = argv[++i];
74
- else if (a === "--allowlist")
75
- args.allowlistPath = argv[++i];
76
- else if (a === "--write")
77
- args.write = true;
78
- else if (a === "--check")
79
- args.check = true;
80
- else if (a === "--help" || a === "-h") {
81
- console.log([
82
- "Dev allowlist helper (astromcp-dev-allowlist-v1)",
83
- "",
84
- "Usage:",
85
- " tsx scripts/dev-allowlist.ts --check",
86
- " tsx scripts/dev-allowlist.ts --write",
87
- "",
88
- "Options:",
89
- " --openapi <path> Path to OpenAPI JSON (default: ../go-sidecar/openapi.json)",
90
- " --allowlist <path> Path to allowlist JSON (default: config/dev-allowlist.json)",
91
- " --check Validate allowlist against OpenAPI + deny rules (exit 1 on failure)",
92
- " --write Update allowlist metadata + candidates + auto-allow commercial-tier operations",
93
- ].join("\n"));
94
- process.exit(0);
95
- }
96
- }
97
- return args;
98
- }
99
- function asHttpMethod(m) {
100
- const up = m.toUpperCase();
101
- if (up === "GET" || up === "POST" || up === "PUT" || up === "PATCH" || up === "DELETE")
102
- return up;
103
- return null;
104
- }
105
- function buildCandidates(openapi, denyPrefixes, denyTags) {
106
- const candidatesGet = [];
107
- const candidatesNonGet = [];
108
- const index = new Map();
109
- for (const [pathname, pathItem] of Object.entries(openapi.paths || {})) {
110
- // OpenAPI keys can include templated params; we keep as-is.
111
- if (denyPrefixes.length && isDeniedByPrefix(pathname, denyPrefixes))
112
- continue;
113
- for (const [methodKey, operation] of Object.entries(pathItem || {})) {
114
- const method = asHttpMethod(methodKey);
115
- if (!method)
116
- continue;
117
- const tags = Array.isArray(operation?.tags) ? operation.tags : [];
118
- if (denyTags.length && hasDeniedTag(tags, denyTags))
119
- continue;
120
- const tier = typeof operation?.["x-meridian-tier"] === "string" ? String(operation["x-meridian-tier"]) : undefined;
121
- const tier_normalized = tier ? normalizeTier(tier) ?? undefined : undefined;
122
- const candidate = {
123
- method,
124
- path: pathname,
125
- tags,
126
- tier,
127
- tier_normalized,
128
- operationId: typeof operation?.operationId === "string" ? operation.operationId : undefined,
129
- summary: typeof operation?.summary === "string" ? operation.summary : undefined,
130
- };
131
- const key = `${method} ${pathname}`;
132
- index.set(key, candidate);
133
- if (method === "GET")
134
- candidatesGet.push(candidate);
135
- else
136
- candidatesNonGet.push(candidate);
137
- }
138
- }
139
- candidatesGet.sort((a, b) => (a.path + a.method).localeCompare(b.path + b.method));
140
- candidatesNonGet.sort((a, b) => (a.path + a.method).localeCompare(b.path + b.method));
141
- return { candidatesGet, candidatesNonGet, index };
142
- }
143
- function buildCommercialAllow(candidates, existingAllow) {
144
- // Build a lookup of existing notes/metadata to preserve across regen
145
- const existingMeta = new Map();
146
- for (const e of existingAllow) {
147
- if (e.note)
148
- existingMeta.set(`${e.method} ${e.path}`, { note: e.note });
149
- }
150
- const allow = candidates
151
- .filter((c) => isCommercialTier(c.tier_normalized))
152
- .map((c) => {
153
- const key = `${c.method} ${c.path}`;
154
- const entry = { method: c.method, path: c.path };
155
- const meta = existingMeta.get(key);
156
- if (meta?.note)
157
- entry.note = meta.note;
158
- return entry;
159
- });
160
- // Deterministic ordering for diffs.
161
- allow.sort((a, b) => (a.path + a.method).localeCompare(b.path + b.method));
162
- return allow;
163
- }
164
- function validateAllowlist(allowlist, index, denyPrefixes, denyTags) {
165
- const errors = [];
166
- const warnings = [];
167
- for (const entry of allowlist.allow) {
168
- if (!entry || typeof entry !== "object") {
169
- errors.push(`Invalid allow entry (not an object): ${JSON.stringify(entry)}`);
170
- continue;
171
- }
172
- const method = entry.method;
173
- const pathname = entry.path;
174
- if (!asHttpMethod(method)) {
175
- errors.push(`Invalid method in allow entry: ${JSON.stringify(entry)}`);
176
- continue;
177
- }
178
- if (typeof pathname !== "string" || !pathname.startsWith("/")) {
179
- errors.push(`Invalid path in allow entry: ${JSON.stringify(entry)}`);
180
- continue;
181
- }
182
- if (denyPrefixes.length && isDeniedByPrefix(pathname, denyPrefixes)) {
183
- errors.push(`Allowlisted entry violates deny prefix rule: ${method} ${pathname}`);
184
- continue;
185
- }
186
- const key = `${method.toUpperCase()} ${pathname}`;
187
- const candidate = index.get(key);
188
- if (!candidate) {
189
- errors.push(`Allowlisted entry not found in OpenAPI: ${key}`);
190
- continue;
191
- }
192
- if (!isCommercialTier(candidate.tier_normalized)) {
193
- errors.push(`Allowlisted entry is not a commercial-tier operation: ${key} (x-meridian-tier=${candidate.tier ?? "<missing>"})`);
194
- continue;
195
- }
196
- if (denyTags.length && hasDeniedTag(candidate.tags, denyTags)) {
197
- errors.push(`Allowlisted entry violates deny tag rule: ${key} (tags=${candidate.tags.join(",")})`);
198
- continue;
199
- }
200
- // Method gating reminder: non-GET is allowed only by explicit allowlist (which it is),
201
- // but we keep a warning so reviewers notice.
202
- if (method.toUpperCase() !== "GET") {
203
- warnings.push(`Non-GET allowlisted: ${key}`);
204
- }
205
- }
206
- return { errors, warnings };
207
- }
208
- async function getOpenApiDocument(inputPathOrUrl) {
209
- if (inputPathOrUrl.startsWith("http")) {
210
- console.log(`Fetching OpenAPI from URL: ${inputPathOrUrl}`);
211
- const response = await axios.get(inputPathOrUrl);
212
- const json = response.data;
213
- return { raw: JSON.stringify(json), json };
214
- }
215
- const raw = fs.readFileSync(inputPathOrUrl, "utf-8");
216
- return { raw, json: JSON.parse(raw) };
217
- }
218
- async function main() {
219
- const defaults = getDefaults();
220
- const args = parseArgs(process.argv.slice(2));
221
- const allowlistPath = path.resolve(args.allowlistPath || defaults.allowlistPath);
222
- const openapiPathOrUrl = args.openapiPath || process.env.ASTROMCP_OPENAPI_URL || defaults.openapiPath;
223
- const { raw: allowRaw, json: allowlist } = readJsonFile(allowlistPath);
224
- if (!allowlist || allowlist.schema !== "astromcp-dev-allowlist-v1" || !Array.isArray(allowlist.allow)) {
225
- throw new Error(`Invalid allowlist schema at ${allowlistPath}`);
226
- }
227
- const denyPrefixes = allowlist.deny?.path_prefixes ?? [];
228
- const denyTags = allowlist.deny?.tags ?? [];
229
- const { raw: openapiRaw, json: openapi } = await getOpenApiDocument(openapiPathOrUrl);
230
- if (!openapi || typeof openapi !== "object" || !openapi.paths) {
231
- throw new Error(`Invalid OpenAPI document at ${openapiPathOrUrl}`);
232
- }
233
- const openapiHash = sha256(openapiRaw);
234
- const allowlistHash = sha256(allowRaw);
235
- const { candidatesGet, candidatesNonGet, index } = buildCandidates(openapi, denyPrefixes, denyTags);
236
- const computedAllow = buildCommercialAllow([...candidatesGet, ...candidatesNonGet], allowlist.allow);
237
- const allowlistForValidation = args.write ? { ...allowlist, allow: computedAllow } : allowlist;
238
- const { errors, warnings } = validateAllowlist(allowlistForValidation, index, denyPrefixes, denyTags);
239
- const report = {
240
- ok: errors.length === 0,
241
- allowlistPath,
242
- openapiPath: openapiPathOrUrl,
243
- openapi_sha256: openapiHash,
244
- allowlist_sha256: allowlistHash,
245
- counts: {
246
- allow: allowlistForValidation.allow.length,
247
- candidates_get: candidatesGet.length,
248
- candidates_non_get: candidatesNonGet.length,
249
- errors: errors.length,
250
- warnings: warnings.length,
251
- },
252
- errors,
253
- warnings,
254
- };
255
- if (args.write) {
256
- const now = new Date().toISOString();
257
- const updated = {
258
- ...allowlist,
259
- generated_from: openapiPathOrUrl,
260
- last_generated_at: now,
261
- openapi_sha256: openapiHash,
262
- allow: computedAllow,
263
- candidates_get: candidatesGet.map((c) => ({ method: c.method, path: c.path, tags: c.tags, operationId: c.operationId })),
264
- candidates_non_get: candidatesNonGet.map((c) => ({ method: c.method, path: c.path, tags: c.tags, operationId: c.operationId })),
265
- validation: {
266
- ok: errors.length === 0,
267
- errors,
268
- warnings,
269
- },
270
- };
271
- writeJsonFile(allowlistPath, updated);
272
- }
273
- if (args.check) {
274
- if (!report.ok) {
275
- console.error(JSON.stringify(report, null, 2));
276
- process.exit(1);
277
- }
278
- // Print a small success line for CI.
279
- console.log(JSON.stringify(report, null, 2));
280
- return;
281
- }
282
- console.log(JSON.stringify(report, null, 2));
283
- }
284
- main().catch((err) => {
285
- console.error(err instanceof Error ? err.message : String(err));
286
- process.exit(1);
287
- });
@@ -1,45 +0,0 @@
1
- import { execSync } from "node:child_process";
2
- function main() {
3
- const raw = execSync("npm pack --dry-run --json", {
4
- encoding: "utf-8",
5
- stdio: ["ignore", "pipe", "inherit"],
6
- });
7
- const parsed = JSON.parse(raw);
8
- if (!Array.isArray(parsed) || parsed.length === 0) {
9
- throw new Error("Unable to parse npm pack --dry-run --json output.");
10
- }
11
- const pack = parsed[0];
12
- const filePaths = new Set(pack.files.map((f) => f.path));
13
- const required = ["dist/index.js", "config/dev-allowlist.json", "README.md", "package.json"];
14
- const missing = required.filter((f) => !filePaths.has(f));
15
- if (missing.length > 0) {
16
- throw new Error(`Pack audit failed: required files missing: ${missing.join(", ")}`);
17
- }
18
- const bannedPatterns = [
19
- /^src\//,
20
- /^test\//,
21
- /^scripts\//,
22
- /^test-output/,
23
- /^diff\.txt$/,
24
- /^tsconfig(?:\.test)?\.json$/,
25
- /^package-lock\.json$/,
26
- /^node_modules\//,
27
- ];
28
- const banned = pack.files
29
- .map((f) => f.path)
30
- .filter((p) => bannedPatterns.some((re) => re.test(p)));
31
- if (banned.length > 0) {
32
- throw new Error(`Pack audit failed: unexpected files in tarball: ${banned.join(", ")}`);
33
- }
34
- const distFiles = pack.files.filter((f) => f.path.startsWith("dist/"));
35
- if (distFiles.length === 0) {
36
- throw new Error("Pack audit failed: no dist/* files present in tarball.");
37
- }
38
- console.log(JSON.stringify({
39
- ok: true,
40
- filename: pack.filename,
41
- file_count: pack.files.length,
42
- dist_file_count: distFiles.length,
43
- }, null, 2));
44
- }
45
- main();
@@ -1,150 +0,0 @@
1
- import fs from "node:fs";
2
- import path from "node:path";
3
- import process from "node:process";
4
- import { fileURLToPath } from "node:url";
5
- import { LLM_V2_ASPECTS_SCHEMA, LLM_V2_DICT, LLM_V2_POINTS_SCHEMA, LlmV2PayloadSchema, llmV2JsonSchema, } from "../src/schema-packs/llm.js";
6
- function parseMode(argv) {
7
- if (argv.includes("--write"))
8
- return "write";
9
- if (argv.includes("--check"))
10
- return "check";
11
- throw new Error("Usage: tsx scripts/schema-packs.ts --write|--check");
12
- }
13
- function ensureDir(dirPath) {
14
- fs.mkdirSync(dirPath, { recursive: true });
15
- }
16
- function readJsonFile(filePath) {
17
- const raw = fs.readFileSync(filePath, "utf-8");
18
- return JSON.parse(raw);
19
- }
20
- function writeJsonFile(filePath, obj) {
21
- const raw = JSON.stringify(obj, null, 2) + "\n";
22
- fs.writeFileSync(filePath, raw, "utf-8");
23
- }
24
- function stableStringify(obj) {
25
- // Deterministic enough for our use: we always write with JSON.stringify(..., null, 2)
26
- // and compare against the same formatting.
27
- return JSON.stringify(obj, null, 2) + "\n";
28
- }
29
- function validateFixture(filePath) {
30
- const payload = readJsonFile(filePath);
31
- const parsed = LlmV2PayloadSchema.safeParse(payload);
32
- if (!parsed.success) {
33
- const msg = parsed.error.issues
34
- .slice(0, 25)
35
- .map((i) => `${i.path.join(".")}: ${i.message}`)
36
- .join("\n");
37
- throw new Error(`Invalid llm fixture: ${filePath}\n${msg}`);
38
- }
39
- }
40
- const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url));
41
- const MCP_ROOT = path.resolve(SCRIPT_DIR, "..");
42
- const PACK_DIR = path.join(MCP_ROOT, "schema-packs", "llm");
43
- const FIXTURES_DIR = path.join(PACK_DIR, "fixtures");
44
- const SCHEMA_PATH = path.join(PACK_DIR, "schema.json");
45
- const REPO_ROOT = path.resolve(MCP_ROOT, "..");
46
- const CANDIDATE_FIXTURES = [
47
- path.join(REPO_ROOT, "tmp", "llm_fe_sample.json"),
48
- path.join(REPO_ROOT, "tmp", "llm_payload_user_2025-12-21.json"),
49
- ];
50
- function copyFirstExistingFixture() {
51
- for (const candidate of CANDIDATE_FIXTURES) {
52
- if (!fs.existsSync(candidate))
53
- continue;
54
- const dest = path.join(FIXTURES_DIR, path.basename(candidate));
55
- fs.copyFileSync(candidate, dest);
56
- return dest;
57
- }
58
- return null;
59
- }
60
- function writeFallbackFixture() {
61
- const fallbackPath = path.join(FIXTURES_DIR, "minimal-natal.json");
62
- const minimalPayload = {
63
- output_mode: "llm",
64
- chart: {
65
- chart_type: "natal",
66
- subject: {
67
- datetime: "2026-01-01T00:00:00Z",
68
- jd: null,
69
- lat: 0,
70
- lon: 0,
71
- },
72
- },
73
- dict: LLM_V2_DICT,
74
- present: {
75
- point_count: 1,
76
- kinds: {
77
- planet: 1,
78
- },
79
- },
80
- points_schema: LLM_V2_POINTS_SCHEMA,
81
- points: [["sun", "planet", 0, 280, 0, 1, 0, 9, 10, null, 23, 0, "none", null, null, 0]],
82
- aspects_schema: LLM_V2_ASPECTS_SCHEMA,
83
- aspects: [],
84
- counts: {
85
- point_count: 1,
86
- aspect_count: 0,
87
- },
88
- };
89
- writeJsonFile(fallbackPath, minimalPayload);
90
- return fallbackPath;
91
- }
92
- function listFixtures() {
93
- if (!fs.existsSync(FIXTURES_DIR))
94
- return [];
95
- return fs
96
- .readdirSync(FIXTURES_DIR)
97
- .filter((f) => f.toLowerCase().endsWith(".json"))
98
- .map((f) => path.join(FIXTURES_DIR, f));
99
- }
100
- function checkSchemaFile() {
101
- if (!fs.existsSync(SCHEMA_PATH)) {
102
- throw new Error(`Missing schema file: ${SCHEMA_PATH} (run --write)`);
103
- }
104
- const disk = readJsonFile(SCHEMA_PATH);
105
- const expected = llmV2JsonSchema;
106
- const diskRaw = fs.readFileSync(SCHEMA_PATH, "utf-8");
107
- const expectedRaw = stableStringify(expected);
108
- if (diskRaw !== expectedRaw) {
109
- throw new Error(`schema.json drift detected (run --write): ${SCHEMA_PATH}`);
110
- }
111
- }
112
- function main() {
113
- const mode = parseMode(process.argv.slice(2));
114
- if (mode === "write") {
115
- ensureDir(FIXTURES_DIR);
116
- ensureDir(PACK_DIR);
117
- writeJsonFile(SCHEMA_PATH, llmV2JsonSchema);
118
- if (listFixtures().length === 0) {
119
- const copied = copyFirstExistingFixture();
120
- if (copied) {
121
- validateFixture(copied);
122
- // eslint-disable-next-line no-console
123
- console.log(`Copied fixture: ${copied}`);
124
- }
125
- else {
126
- const fallback = writeFallbackFixture();
127
- validateFixture(fallback);
128
- // eslint-disable-next-line no-console
129
- console.log(`Wrote fallback fixture: ${fallback}`);
130
- }
131
- }
132
- for (const fixture of listFixtures()) {
133
- validateFixture(fixture);
134
- }
135
- // eslint-disable-next-line no-console
136
- console.log(`Wrote schema pack: ${PACK_DIR}`);
137
- return;
138
- }
139
- // check
140
- checkSchemaFile();
141
- const fixtures = listFixtures();
142
- if (fixtures.length === 0) {
143
- throw new Error(`No fixtures found in: ${FIXTURES_DIR}`);
144
- }
145
- for (const fixture of fixtures)
146
- validateFixture(fixture);
147
- // eslint-disable-next-line no-console
148
- console.log(`Schema pack OK (fixtures=${fixtures.length})`);
149
- }
150
- main();
@@ -1,25 +0,0 @@
1
- import { initTools, toolRegistry } from "../src/tools/index.js";
2
- async function main() {
3
- await initTools("dev");
4
- const list = toolRegistry["dev.list_allowed"];
5
- const call = toolRegistry["dev.call"];
6
- if (!list || !call) {
7
- throw new Error("Dev tools not registered (expected dev.list_allowed and dev.call)");
8
- }
9
- const allowed = await list.handler({});
10
- console.log("ALLOWED_TOOLS:");
11
- console.log(JSON.stringify(Object.keys(toolRegistry), null, 2));
12
- console.log("\nALLOWLIST:");
13
- console.log(JSON.stringify(allowed, null, 2));
14
- // Smoke call a simple endpoint that should exist without extra payload.
15
- const result = await call.handler({
16
- method: "GET",
17
- path: "/health",
18
- });
19
- console.log("\nDEV_CALL_RESULT:");
20
- console.log(JSON.stringify(result, null, 2));
21
- }
22
- main().catch((err) => {
23
- console.error(err instanceof Error ? err.stack || err.message : String(err));
24
- process.exit(1);
25
- });
@@ -1,141 +0,0 @@
1
- import fs from "node:fs";
2
- import path from "node:path";
3
- import { fileURLToPath } from "node:url";
4
- import { initTools, toolRegistry } from "../src/tools/index.js";
5
- const CURSOR_BEGIN = "<!-- GENERATED:CURSOR_INSTALL:BEGIN -->";
6
- const CURSOR_END = "<!-- GENERATED:CURSOR_INSTALL:END -->";
7
- const SNAPSHOT_BEGIN = "<!-- GENERATED:RUNTIME_SNAPSHOT:BEGIN -->";
8
- const SNAPSHOT_END = "<!-- GENERATED:RUNTIME_SNAPSHOT:END -->";
9
- function parseMode(argv) {
10
- if (argv.includes("--write"))
11
- return "write";
12
- if (argv.includes("--check"))
13
- return "check";
14
- throw new Error("Usage: tsx scripts/sync-readme.ts --write|--check");
15
- }
16
- function replaceSection(content, beginMarker, endMarker, body) {
17
- const start = content.indexOf(beginMarker);
18
- const end = content.indexOf(endMarker);
19
- if (start === -1 || end === -1 || end < start) {
20
- throw new Error(`Missing marker block: ${beginMarker} ... ${endMarker}`);
21
- }
22
- const before = content.slice(0, start + beginMarker.length);
23
- const after = content.slice(end);
24
- return `${before}\n${body.trim()}\n${after}`;
25
- }
26
- function getRepoPaths() {
27
- const scriptDir = path.dirname(fileURLToPath(import.meta.url));
28
- const mcpRoot = path.resolve(scriptDir, "..");
29
- return {
30
- readmePath: path.join(mcpRoot, "README.md"),
31
- allowlistPath: path.join(mcpRoot, "config", "dev-allowlist.json"),
32
- };
33
- }
34
- function loadAllowlist(allowlistPath) {
35
- const raw = fs.readFileSync(allowlistPath, "utf-8");
36
- const parsed = JSON.parse(raw);
37
- if (parsed.schema !== "astromcp-dev-allowlist-v1" || !Array.isArray(parsed.allow)) {
38
- throw new Error(`Invalid allowlist at ${allowlistPath}`);
39
- }
40
- return parsed;
41
- }
42
- function toCursorDeepLink() {
43
- const config = {
44
- command: "npx",
45
- args: ["-y", "@openephemeris/mcp-server"],
46
- env: {
47
- OPENEPHEMERIS_PROFILE: "dev",
48
- OPENEPHEMERIS_BACKEND_URL: "https://api.openephemeris.com",
49
- OPENEPHEMERIS_API_KEY: "YOUR_API_KEY_HERE",
50
- },
51
- };
52
- const configJson = JSON.stringify(config, null, 2);
53
- const encoded = Buffer.from(JSON.stringify(config), "utf-8").toString("base64");
54
- const url = `cursor://anysphere.cursor-deeplink/mcp/install?name=openephemeris&config=${encoded}`;
55
- return { url, configJson };
56
- }
57
- function buildMethodCounts(entries) {
58
- const counts = { GET: 0, POST: 0, PUT: 0, PATCH: 0, DELETE: 0 };
59
- for (const e of entries) {
60
- counts[e.method] += 1;
61
- }
62
- return counts;
63
- }
64
- function buildFamilyRows(entries) {
65
- const families = new Map();
66
- for (const e of entries) {
67
- const family = e.path.split("/").filter(Boolean)[0] || "root";
68
- const existing = families.get(family) || [];
69
- existing.push(e);
70
- families.set(family, existing);
71
- }
72
- const rows = [...families.entries()]
73
- .map(([family, items]) => {
74
- const sorted = [...items].sort((a, b) => `${a.path}:${a.method}`.localeCompare(`${b.path}:${b.method}`));
75
- const sample = sorted.slice(0, 2).map((e) => `\`${e.method} ${e.path}\``).join(", ");
76
- return { family, count: items.length, sample };
77
- })
78
- .sort((a, b) => a.family.localeCompare(b.family));
79
- return rows;
80
- }
81
- async function buildRuntimeSnapshot(allow) {
82
- await initTools("dev");
83
- const toolNames = Object.keys(toolRegistry).sort((a, b) => a.localeCompare(b));
84
- const typedTools = toolNames.filter((t) => !t.startsWith("dev."));
85
- const genericTools = toolNames.filter((t) => t.startsWith("dev."));
86
- const methodCounts = buildMethodCounts(allow);
87
- const familyRows = buildFamilyRows(allow);
88
- const familiesTable = [
89
- "| Family | Operations | Example |",
90
- "|---|---:|---|",
91
- ...familyRows.map((row) => `| \`${row.family}\` | ${row.count} | ${row.sample} |`),
92
- ].join("\n");
93
- return [
94
- "## Runtime Snapshot (Generated)",
95
- "",
96
- "Generated by `npm run sync:readme` from `config/dev-allowlist.json` and the live tool registry.",
97
- "",
98
- `- Allowlisted operations: **${allow.length}**`,
99
- `- Methods: \`GET=${methodCounts.GET}\`, \`POST=${methodCounts.POST}\`, \`PUT=${methodCounts.PUT}\`, \`PATCH=${methodCounts.PATCH}\`, \`DELETE=${methodCounts.DELETE}\``,
100
- `- Registered tools (\`OPENEPHEMERIS_PROFILE=dev\`): **${toolNames.length}**`,
101
- `- Typed tools: ${typedTools.map((t) => `\`${t}\``).join(", ")}`,
102
- `- Generic tools: ${genericTools.map((t) => `\`${t}\``).join(", ")}`,
103
- "",
104
- "### Allowlist Families",
105
- "",
106
- familiesTable,
107
- ].join("\n");
108
- }
109
- async function main() {
110
- const mode = parseMode(process.argv.slice(2));
111
- const { readmePath, allowlistPath } = getRepoPaths();
112
- const allowlist = loadAllowlist(allowlistPath);
113
- const { url, configJson } = toCursorDeepLink();
114
- const runtimeSnapshot = await buildRuntimeSnapshot(allowlist.allow);
115
- const cursorSection = [
116
- `[![Install in Cursor](https://img.shields.io/badge/Install%20in-Cursor-1f6feb)](${url})`,
117
- "",
118
- "> Replace `YOUR_API_KEY_HERE` in Cursor MCP settings with your API key from https://openephemeris.com/dashboard.",
119
- "",
120
- "Cursor deeplink payload:",
121
- "```json",
122
- configJson,
123
- "```",
124
- ].join("\n");
125
- const original = fs.readFileSync(readmePath, "utf-8");
126
- let next = replaceSection(original, CURSOR_BEGIN, CURSOR_END, cursorSection);
127
- next = replaceSection(next, SNAPSHOT_BEGIN, SNAPSHOT_END, runtimeSnapshot);
128
- if (mode === "check") {
129
- if (next !== original) {
130
- throw new Error("README drift detected. Run `npm run sync:readme`.");
131
- }
132
- console.log("README sync OK");
133
- return;
134
- }
135
- fs.writeFileSync(readmePath, next, "utf-8");
136
- console.log("README updated");
137
- }
138
- main().catch((err) => {
139
- console.error(err instanceof Error ? err.message : String(err));
140
- process.exit(1);
141
- });