@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.
- package/README.md +2 -2
- package/dist/backend/client.d.ts +23 -1
- package/dist/backend/client.js +144 -25
- package/dist/index.js +94 -2
- package/dist/tools/dev.js +29 -21
- package/dist/tools/index.d.ts +8 -0
- package/dist/tools/index.js +38 -1
- package/dist/tools/specialized/acg.js +143 -0
- package/dist/tools/specialized/comparative.js +153 -0
- package/dist/tools/specialized/eclipse.js +20 -26
- package/dist/tools/specialized/electional.js +182 -3
- package/dist/tools/specialized/ephemeris_core.js +93 -0
- package/dist/tools/specialized/ephemeris_extended.js +234 -0
- package/dist/tools/specialized/hd_group.js +107 -0
- package/dist/tools/specialized/human_design.js +27 -11
- package/dist/tools/specialized/moon.js +3 -2
- package/dist/tools/specialized/natal.js +36 -15
- package/dist/tools/specialized/progressed.js +65 -0
- package/dist/tools/specialized/relocation.js +14 -10
- package/dist/tools/specialized/returns.js +144 -0
- package/dist/tools/specialized/synastry.js +22 -12
- package/dist/tools/specialized/transits.js +78 -28
- package/dist/tools/specialized/venus_star_points.js +187 -0
- package/package.json +4 -3
- package/dist/scripts/dev-allowlist.js +0 -287
- package/dist/scripts/pack-audit.js +0 -45
- package/dist/scripts/schema-packs.js +0 -150
- package/dist/scripts/smoke-dev-profile.js +0 -25
- package/dist/scripts/sync-readme.js +0 -141
- package/dist/scripts/test-all-tools.js +0 -104
- package/dist/scripts/test-client.js +0 -69
- package/dist/scripts/test-sse-client.js +0 -221
- package/dist/src/backend/client.d.ts +0 -67
- package/dist/src/backend/client.js +0 -363
- package/dist/src/index.d.ts +0 -2
- package/dist/src/index.js +0 -184
- package/dist/src/schema-packs/llm.d.ts +0 -105
- package/dist/src/schema-packs/llm.js +0 -429
- package/dist/src/tools/dev.d.ts +0 -1
- package/dist/src/tools/dev.js +0 -195
- package/dist/src/tools/index.d.ts +0 -33
- package/dist/src/tools/index.js +0 -60
- package/dist/src/tools/specialized/eclipse.d.ts +0 -1
- package/dist/src/tools/specialized/eclipse.js +0 -50
- package/dist/src/tools/specialized/electional.d.ts +0 -1
- package/dist/src/tools/specialized/electional.js +0 -80
- package/dist/src/tools/specialized/human_design.d.ts +0 -1
- package/dist/src/tools/specialized/human_design.js +0 -69
- package/dist/src/tools/specialized/moon.d.ts +0 -1
- package/dist/src/tools/specialized/moon.js +0 -51
- package/dist/src/tools/specialized/natal.d.ts +0 -1
- package/dist/src/tools/specialized/natal.js +0 -92
- package/dist/src/tools/specialized/relocation.d.ts +0 -1
- package/dist/src/tools/specialized/relocation.js +0 -76
- package/dist/src/tools/specialized/synastry.d.ts +0 -1
- package/dist/src/tools/specialized/synastry.js +0 -73
- package/dist/src/tools/specialized/transits.d.ts +0 -1
- package/dist/src/tools/specialized/transits.js +0 -130
- package/dist/test/allowlist-and-tools.test.d.ts +0 -1
- package/dist/test/allowlist-and-tools.test.js +0 -96
- package/dist/test/backend-client.test.d.ts +0 -1
- package/dist/test/backend-client.test.js +0 -284
- package/dist/test/credentials.test.d.ts +0 -1
- package/dist/test/credentials.test.js +0 -143
- /package/dist/{src/auth → auth}/credentials.d.ts +0 -0
- /package/dist/{src/auth → auth}/credentials.js +0 -0
- /package/dist/{src/auth → auth}/device-auth.d.ts +0 -0
- /package/dist/{src/auth → auth}/device-auth.js +0 -0
- /package/dist/{src/server-sse.d.ts → server-sse.d.ts} +0 -0
- /package/dist/{src/server-sse.js → server-sse.js} +0 -0
- /package/dist/{src/tools → tools}/auth.d.ts +0 -0
- /package/dist/{src/tools → tools}/auth.js +0 -0
- /package/dist/{scripts/dev-allowlist.d.ts → tools/specialized/acg.d.ts} +0 -0
- /package/dist/{src/tools → tools}/specialized/bazi.d.ts +0 -0
- /package/dist/{src/tools → tools}/specialized/bazi.js +0 -0
- /package/dist/{src/tools → tools}/specialized/bi_wheel.d.ts +0 -0
- /package/dist/{src/tools → tools}/specialized/bi_wheel.js +0 -0
- /package/dist/{src/tools → tools}/specialized/chart_wheel.d.ts +0 -0
- /package/dist/{src/tools → tools}/specialized/chart_wheel.js +0 -0
- /package/dist/{scripts/pack-audit.d.ts → tools/specialized/comparative.d.ts} +0 -0
- /package/dist/{scripts/schema-packs.d.ts → tools/specialized/ephemeris_core.d.ts} +0 -0
- /package/dist/{scripts/smoke-dev-profile.d.ts → tools/specialized/ephemeris_extended.d.ts} +0 -0
- /package/dist/{scripts/sync-readme.d.ts → tools/specialized/hd_group.d.ts} +0 -0
- /package/dist/{scripts/test-all-tools.d.ts → tools/specialized/progressed.d.ts} +0 -0
- /package/dist/{scripts/test-client.d.ts → tools/specialized/returns.d.ts} +0 -0
- /package/dist/{src/tools → tools}/specialized/vedic.d.ts +0 -0
- /package/dist/{src/tools → tools}/specialized/vedic.js +0 -0
- /package/dist/{scripts/test-sse-client.d.ts → tools/specialized/venus_star_points.d.ts} +0 -0
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
import { toolRegistry, initTools } from "../src/tools/index.js";
|
|
2
|
-
import { backendClient } from "../src/backend/client.js";
|
|
3
|
-
const API_KEY = process.env.OPENEPHEMERIS_API_KEY || "";
|
|
4
|
-
const API_URL = process.env.OPENEPHEMERIS_API_URL || "https://api.openephemeris.com";
|
|
5
|
-
// Hardcoded valid inputs matching the MCP Tool Zod Schemas (not the REST API JSON structures)
|
|
6
|
-
const toolFixtures = {
|
|
7
|
-
dev_call: { method: "GET", path: "/health", query: {} },
|
|
8
|
-
dev_list_allowed: {},
|
|
9
|
-
ephemeris_natal_chart: {
|
|
10
|
-
datetime: "1990-04-15T14:30:00Z",
|
|
11
|
-
latitude: 41.87,
|
|
12
|
-
longitude: -87.62,
|
|
13
|
-
house_system: "placidus"
|
|
14
|
-
},
|
|
15
|
-
ephemeris_transits: {
|
|
16
|
-
natal_datetime: "1990-04-15T14:30:00Z",
|
|
17
|
-
natal_latitude: 41.87,
|
|
18
|
-
natal_longitude: -87.62,
|
|
19
|
-
start_date: "2026-03-20",
|
|
20
|
-
end_date: "2026-03-27",
|
|
21
|
-
transiting_planets: ["saturn"]
|
|
22
|
-
},
|
|
23
|
-
ephemeris_moon_phase: { datetime: "2026-03-20T12:00:00Z" },
|
|
24
|
-
ephemeris_next_eclipse: { date: "2026-03-20", eclipse_type: "solar", latitude: 41.87, longitude: -87.62 },
|
|
25
|
-
ephemeris_synastry: {
|
|
26
|
-
person_a_datetime: "1990-04-15T14:30:00Z",
|
|
27
|
-
person_a_latitude: 41.87,
|
|
28
|
-
person_a_longitude: -87.62,
|
|
29
|
-
person_b_datetime: "1992-08-20T10:00:00Z",
|
|
30
|
-
person_b_latitude: 34.05,
|
|
31
|
-
person_b_longitude: -118.24
|
|
32
|
-
},
|
|
33
|
-
ephemeris_relocation: {
|
|
34
|
-
natal_datetime: "1990-04-15T14:30:00Z",
|
|
35
|
-
natal_latitude: 41.87,
|
|
36
|
-
natal_longitude: -87.62,
|
|
37
|
-
relocation_latitude: 51.5,
|
|
38
|
-
relocation_longitude: -0.12
|
|
39
|
-
},
|
|
40
|
-
ephemeris_electional: {
|
|
41
|
-
start_date: "2026-03-20T00:00:00Z",
|
|
42
|
-
end_date: "2026-03-30T00:00:00Z",
|
|
43
|
-
latitude: 41.87,
|
|
44
|
-
longitude: -87.62,
|
|
45
|
-
aspects: ["sun trine moon"]
|
|
46
|
-
},
|
|
47
|
-
human_design_chart: {
|
|
48
|
-
datetime: "1990-04-15T14:30:00Z",
|
|
49
|
-
latitude: 41.87,
|
|
50
|
-
longitude: -87.62
|
|
51
|
-
}
|
|
52
|
-
};
|
|
53
|
-
async function main() {
|
|
54
|
-
if (!API_KEY) {
|
|
55
|
-
console.error("Please set OPENEPHEMERIS_API_KEY");
|
|
56
|
-
process.exit(1);
|
|
57
|
-
}
|
|
58
|
-
console.log(`Starting recursive test loop against: ${API_URL}`);
|
|
59
|
-
await initTools("dev");
|
|
60
|
-
// Use the default singleton imported from `backendClient`
|
|
61
|
-
backendClient.setApiKey(API_KEY);
|
|
62
|
-
const passed = [];
|
|
63
|
-
const failed = [];
|
|
64
|
-
for (const [name, tool] of Object.entries(toolRegistry)) {
|
|
65
|
-
if (name.startsWith("auth_"))
|
|
66
|
-
continue;
|
|
67
|
-
console.log(`\nTesting tool: ${name}`);
|
|
68
|
-
const args = toolFixtures[name];
|
|
69
|
-
if (!args) {
|
|
70
|
-
console.log(` ⏭️ Skipped (no fixture data)`);
|
|
71
|
-
continue;
|
|
72
|
-
}
|
|
73
|
-
try {
|
|
74
|
-
// Just run the tool handler directly
|
|
75
|
-
const parsedArgs = args;
|
|
76
|
-
// Set up the singleton backendClient API key so the imported modules use it
|
|
77
|
-
backendClient.setApiKey(API_KEY);
|
|
78
|
-
const result = await tool.handler(parsedArgs);
|
|
79
|
-
if (result.isError) {
|
|
80
|
-
throw new Error(`Tool responded with isError=true.\n Output: ${JSON.stringify(result.content)}`);
|
|
81
|
-
}
|
|
82
|
-
const snippet = result.content?.[0]?.text?.substring(0, 80).replace(/\n/g, "") || "No content";
|
|
83
|
-
console.log(` ✅ Passed (${snippet}...)`);
|
|
84
|
-
passed.push(name);
|
|
85
|
-
}
|
|
86
|
-
catch (e) {
|
|
87
|
-
let errorMsg = e.message;
|
|
88
|
-
if (e.errors) {
|
|
89
|
-
// Zod validation error on testing inputs
|
|
90
|
-
errorMsg = "Schema Validation Failed: " + JSON.stringify(e.errors.map((err) => `${err.path.join('.')}: ${err.message}`));
|
|
91
|
-
}
|
|
92
|
-
console.log(` ❌ Failed: ${errorMsg}`);
|
|
93
|
-
failed.push({ name, error: errorMsg });
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
console.log(`\n${"-".repeat(50)}`);
|
|
97
|
-
console.log(`Results: ${passed.length} passed, ${failed.length} failed`);
|
|
98
|
-
if (failed.length > 0) {
|
|
99
|
-
console.log("Failed tools:");
|
|
100
|
-
failed.forEach(f => console.log(` - ${f.name}: ${f.error}`));
|
|
101
|
-
process.exit(1);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
main().catch(console.log);
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
2
|
-
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import { fileURLToPath } from "node:url";
|
|
5
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
6
|
-
async function main() {
|
|
7
|
-
console.log("🚀 Starting MCP Integration Test...");
|
|
8
|
-
// Path to the primary entrypoint
|
|
9
|
-
const serverPath = path.resolve(__dirname, "../src/index.ts");
|
|
10
|
-
// Spin up the stdio transport simulating a client like Claude/Cursor
|
|
11
|
-
const transport = new StdioClientTransport({
|
|
12
|
-
command: "npx",
|
|
13
|
-
args: ["tsx", serverPath],
|
|
14
|
-
env: {
|
|
15
|
-
...process.env,
|
|
16
|
-
// Pass the profile explicitly for predictable responses
|
|
17
|
-
OPENEPHEMERIS_PROFILE: "dev",
|
|
18
|
-
},
|
|
19
|
-
});
|
|
20
|
-
const client = new Client({ name: "test-client", version: "1.0.0" }, { capabilities: {} });
|
|
21
|
-
console.log("🔌 Connecting to local MCP server via stdio...");
|
|
22
|
-
await client.connect(transport);
|
|
23
|
-
console.log("✅ Handshake complete. Protocol negotiated successfully.");
|
|
24
|
-
// Test 1: List Tools
|
|
25
|
-
console.log("\n📡 Polling server tools...");
|
|
26
|
-
const toolsResponse = await client.listTools();
|
|
27
|
-
console.log(`✅ Discovered ${toolsResponse.tools.length} tools.`);
|
|
28
|
-
// Verify core tools exist
|
|
29
|
-
const toolNames = toolsResponse.tools.map((t) => t.name);
|
|
30
|
-
const coreTools = ["auth.login", "dev.call", "ephemeris.moon_phase", "ephemeris.natal_chart"];
|
|
31
|
-
for (const name of coreTools) {
|
|
32
|
-
if (!toolNames.includes(name)) {
|
|
33
|
-
throw new Error(`❌ Missing expected tool: ${name}`);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
// Test 2: Call an open endpoint that doesn't strictly require API Key for simple responses,
|
|
37
|
-
// or handle the graceful 401 prompt gracefully if it does.
|
|
38
|
-
// Tools returning standard 401s with markdown prompts are still valid successful MCP JSON-RPC calls.
|
|
39
|
-
console.log("\n⚙️ Testing 'ephemeris.moon_phase' tool call...");
|
|
40
|
-
try {
|
|
41
|
-
const result = await client.callTool({
|
|
42
|
-
name: "ephemeris.moon_phase",
|
|
43
|
-
arguments: {}, // Defaults to current time
|
|
44
|
-
});
|
|
45
|
-
// An MCP tool response is usually { content: [ { type: 'text', text: '...' } ], isError: boolean }
|
|
46
|
-
const res = result;
|
|
47
|
-
if (res.isError) {
|
|
48
|
-
console.log("⚠️ Tool execution returned an expected application error (e.g. 401 Unauthorized expected without valid credentials):\n");
|
|
49
|
-
console.log(res.content);
|
|
50
|
-
}
|
|
51
|
-
else {
|
|
52
|
-
console.log("✅ Tool executed successfully! Response preview:");
|
|
53
|
-
const content = res.content[0];
|
|
54
|
-
console.log(content.text ? content.text.slice(0, 300) + "..." : res.content);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
catch (err) {
|
|
58
|
-
console.error("❌ Exception during tool execution:", err);
|
|
59
|
-
process.exit(1);
|
|
60
|
-
}
|
|
61
|
-
console.log("\n🎉 Integration Test Passed. Client and Server are communicating perfectly.");
|
|
62
|
-
// Cleanup gracefully
|
|
63
|
-
await transport.close();
|
|
64
|
-
process.exit(0);
|
|
65
|
-
}
|
|
66
|
-
main().catch((err) => {
|
|
67
|
-
console.error("Fatal Error:", err);
|
|
68
|
-
process.exit(1);
|
|
69
|
-
});
|
|
@@ -1,221 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* test-sse-client.ts — Integration tests for the Remote SSE MCP server.
|
|
3
|
-
*
|
|
4
|
-
* Usage:
|
|
5
|
-
* Internal (local):
|
|
6
|
-
* Start the SSE server first: npm run dev:sse
|
|
7
|
-
* Then run: npx tsx scripts/test-sse-client.ts
|
|
8
|
-
*
|
|
9
|
-
* External (production):
|
|
10
|
-
* OPENEPHEMERIS_SSE_URL=https://mcp.openephemeris.com \
|
|
11
|
-
* OPENEPHEMERIS_API_KEY=opene-xxx \
|
|
12
|
-
* npx tsx scripts/test-sse-client.ts
|
|
13
|
-
*
|
|
14
|
-
* Tests:
|
|
15
|
-
* 1. Health endpoint responds correctly
|
|
16
|
-
* 2. SSE connection without API key is rejected (401)
|
|
17
|
-
* 3. SSE connection with invalid API key is rejected (403)
|
|
18
|
-
* 4. SSE connection + MCP handshake succeeds with valid key
|
|
19
|
-
* 5. Tool discovery returns expected core tools
|
|
20
|
-
* 6. Tool call (moon phase) executes end-to-end
|
|
21
|
-
*/
|
|
22
|
-
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
23
|
-
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
|
|
24
|
-
import axios from "axios";
|
|
25
|
-
const SSE_BASE = process.env.OPENEPHEMERIS_SSE_URL?.replace(/\/+$/, "") ||
|
|
26
|
-
"http://localhost:3001";
|
|
27
|
-
const API_KEY = process.env.OPENEPHEMERIS_API_KEY || "";
|
|
28
|
-
const passed = [];
|
|
29
|
-
const failed = [];
|
|
30
|
-
function ok(name) {
|
|
31
|
-
passed.push(name);
|
|
32
|
-
console.log(` ✅ ${name}`);
|
|
33
|
-
}
|
|
34
|
-
function fail(name, err) {
|
|
35
|
-
failed.push(name);
|
|
36
|
-
console.error(` ❌ ${name}: ${err instanceof Error ? err.message : String(err)}`);
|
|
37
|
-
}
|
|
38
|
-
// ---------------------------------------------------------------------------
|
|
39
|
-
// Test 1: Health endpoint
|
|
40
|
-
// ---------------------------------------------------------------------------
|
|
41
|
-
async function testHealth() {
|
|
42
|
-
const name = "Health endpoint responds";
|
|
43
|
-
try {
|
|
44
|
-
const res = await axios.get(`${SSE_BASE}/health`, { timeout: 5_000 });
|
|
45
|
-
if (res.data.ok !== true)
|
|
46
|
-
throw new Error(`Expected ok:true, got ${JSON.stringify(res.data)}`);
|
|
47
|
-
if (!res.data.version)
|
|
48
|
-
throw new Error("Missing version in health response");
|
|
49
|
-
if (res.data.transport !== "sse")
|
|
50
|
-
throw new Error(`Expected transport:sse, got ${res.data.transport}`);
|
|
51
|
-
ok(name);
|
|
52
|
-
}
|
|
53
|
-
catch (err) {
|
|
54
|
-
fail(name, err);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
// ---------------------------------------------------------------------------
|
|
58
|
-
// Test 2: No API key → 401
|
|
59
|
-
// ---------------------------------------------------------------------------
|
|
60
|
-
async function testNoApiKey() {
|
|
61
|
-
const name = "SSE without API key returns 401";
|
|
62
|
-
try {
|
|
63
|
-
const res = await axios.get(`${SSE_BASE}/sse`, {
|
|
64
|
-
timeout: 5_000,
|
|
65
|
-
validateStatus: () => true,
|
|
66
|
-
});
|
|
67
|
-
if (res.status !== 401)
|
|
68
|
-
throw new Error(`Expected 401, got ${res.status}`);
|
|
69
|
-
if (res.data.error !== "api_key_required")
|
|
70
|
-
throw new Error(`Unexpected error code: ${res.data.error}`);
|
|
71
|
-
ok(name);
|
|
72
|
-
}
|
|
73
|
-
catch (err) {
|
|
74
|
-
fail(name, err);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
// ---------------------------------------------------------------------------
|
|
78
|
-
// Test 3: Invalid API key → 403 (or accepted if backend can't validate)
|
|
79
|
-
// ---------------------------------------------------------------------------
|
|
80
|
-
async function testInvalidApiKey() {
|
|
81
|
-
const name = "SSE with invalid API key is handled";
|
|
82
|
-
try {
|
|
83
|
-
const controller = new AbortController();
|
|
84
|
-
// Give the server 3s to respond with 403 or start SSE
|
|
85
|
-
const timeout = setTimeout(() => controller.abort(), 3_000);
|
|
86
|
-
const res = await axios.get(`${SSE_BASE}/sse?apiKey=opene-clearly-invalid-key-000`, {
|
|
87
|
-
validateStatus: () => true,
|
|
88
|
-
signal: controller.signal,
|
|
89
|
-
// Disable response buffering so the SSE stream doesn't hang
|
|
90
|
-
responseType: "stream",
|
|
91
|
-
});
|
|
92
|
-
clearTimeout(timeout);
|
|
93
|
-
if (res.status === 403) {
|
|
94
|
-
// Backend validated and rejected the key — ideal
|
|
95
|
-
ok(name + " (403 rejected)");
|
|
96
|
-
}
|
|
97
|
-
else if (res.status === 200) {
|
|
98
|
-
// Backend couldn't distinguish keys or endpoint doesn't require auth —
|
|
99
|
-
// SSE stream started. This is acceptable, the tool calls will fail later.
|
|
100
|
-
console.log(` ⚠️ ${name}: Key accepted (backend may not validate at this layer)`);
|
|
101
|
-
passed.push(name);
|
|
102
|
-
}
|
|
103
|
-
else {
|
|
104
|
-
ok(name + ` (${res.status})`);
|
|
105
|
-
}
|
|
106
|
-
// Clean up the stream
|
|
107
|
-
try {
|
|
108
|
-
res.data?.destroy?.();
|
|
109
|
-
}
|
|
110
|
-
catch { /* ignore */ }
|
|
111
|
-
}
|
|
112
|
-
catch (err) {
|
|
113
|
-
if (err.code === "ERR_CANCELED" || err.name === "CanceledError") {
|
|
114
|
-
// The SSE stream started (200) and we aborted — that's valid
|
|
115
|
-
console.log(` ⚠️ ${name}: SSE stream started (key was accepted), aborted cleanly`);
|
|
116
|
-
passed.push(name);
|
|
117
|
-
}
|
|
118
|
-
else {
|
|
119
|
-
fail(name, err);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
// ---------------------------------------------------------------------------
|
|
124
|
-
// Test 4-6: Full SSE client connection + tool discovery + tool call
|
|
125
|
-
// ---------------------------------------------------------------------------
|
|
126
|
-
async function testFullSseFlow() {
|
|
127
|
-
if (!API_KEY) {
|
|
128
|
-
const skip = "Full SSE flow (skipped — no OPENEPHEMERIS_API_KEY set)";
|
|
129
|
-
console.log(` ⏭️ ${skip}`);
|
|
130
|
-
return;
|
|
131
|
-
}
|
|
132
|
-
// Test 4: Connect
|
|
133
|
-
const connectName = "SSE connection + MCP handshake";
|
|
134
|
-
let client = null;
|
|
135
|
-
let transport = null;
|
|
136
|
-
try {
|
|
137
|
-
const sseUrl = new URL(`${SSE_BASE}/sse?apiKey=${API_KEY}`);
|
|
138
|
-
transport = new SSEClientTransport(sseUrl);
|
|
139
|
-
client = new Client({ name: "sse-test-client", version: "1.0.0" }, { capabilities: {} });
|
|
140
|
-
await client.connect(transport);
|
|
141
|
-
ok(connectName);
|
|
142
|
-
}
|
|
143
|
-
catch (err) {
|
|
144
|
-
fail(connectName, err);
|
|
145
|
-
return; // Can't continue without a connection
|
|
146
|
-
}
|
|
147
|
-
// Test 5: Tool discovery
|
|
148
|
-
const discoveryName = "Tool discovery returns core tools";
|
|
149
|
-
try {
|
|
150
|
-
const tools = await client.listTools();
|
|
151
|
-
const names = tools.tools.map((t) => t.name);
|
|
152
|
-
const required = ["auth_login", "dev_call", "ephemeris_moon_phase", "ephemeris_natal_chart"];
|
|
153
|
-
const missing = required.filter((r) => !names.includes(r));
|
|
154
|
-
if (missing.length > 0)
|
|
155
|
-
throw new Error(`Missing tools: ${missing.join(", ")}`);
|
|
156
|
-
console.log(` Found ${tools.tools.length} tools`);
|
|
157
|
-
ok(discoveryName);
|
|
158
|
-
}
|
|
159
|
-
catch (err) {
|
|
160
|
-
fail(discoveryName, err);
|
|
161
|
-
}
|
|
162
|
-
// Test 6: Tool call
|
|
163
|
-
const callName = "Tool call (moon_phase) returns data";
|
|
164
|
-
try {
|
|
165
|
-
const result = await client.callTool({
|
|
166
|
-
name: "ephemeris_moon_phase",
|
|
167
|
-
arguments: {},
|
|
168
|
-
});
|
|
169
|
-
const res = result;
|
|
170
|
-
if (res.isError) {
|
|
171
|
-
// A 401 error is acceptable if the server's own key isn't set
|
|
172
|
-
const text = res.content?.[0]?.text || "";
|
|
173
|
-
if (text.includes("401") || text.includes("auth")) {
|
|
174
|
-
console.log(` ⚠️ Tool returned auth error (server service key may not be configured)`);
|
|
175
|
-
ok(callName + " (auth expected)");
|
|
176
|
-
}
|
|
177
|
-
else {
|
|
178
|
-
throw new Error(`Tool returned error: ${text.slice(0, 200)}`);
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
else {
|
|
182
|
-
const text = res.content?.[0]?.text || "";
|
|
183
|
-
console.log(` Response preview: ${text.slice(0, 150)}...`);
|
|
184
|
-
ok(callName);
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
catch (err) {
|
|
188
|
-
fail(callName, err);
|
|
189
|
-
}
|
|
190
|
-
// Cleanup
|
|
191
|
-
try {
|
|
192
|
-
await transport?.close();
|
|
193
|
-
}
|
|
194
|
-
catch {
|
|
195
|
-
// ignore
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
// ---------------------------------------------------------------------------
|
|
199
|
-
// Runner
|
|
200
|
-
// ---------------------------------------------------------------------------
|
|
201
|
-
async function main() {
|
|
202
|
-
console.log(`\n🧪 SSE MCP Server Integration Tests`);
|
|
203
|
-
console.log(` Target: ${SSE_BASE}`);
|
|
204
|
-
console.log(` API Key: ${API_KEY ? API_KEY.slice(0, 10) + "..." : "(none — full flow skipped)"}\n`);
|
|
205
|
-
await testHealth();
|
|
206
|
-
await testNoApiKey();
|
|
207
|
-
await testInvalidApiKey();
|
|
208
|
-
await testFullSseFlow();
|
|
209
|
-
console.log(`\n${"─".repeat(50)}`);
|
|
210
|
-
console.log(` Passed: ${passed.length} | Failed: ${failed.length}`);
|
|
211
|
-
if (failed.length > 0) {
|
|
212
|
-
console.error(`\n❌ ${failed.length} test(s) failed.`);
|
|
213
|
-
process.exit(1);
|
|
214
|
-
}
|
|
215
|
-
console.log(`\n🎉 All tests passed!`);
|
|
216
|
-
process.exit(0);
|
|
217
|
-
}
|
|
218
|
-
main().catch((err) => {
|
|
219
|
-
console.error("Fatal:", err);
|
|
220
|
-
process.exit(1);
|
|
221
|
-
});
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
export interface BackendConfig {
|
|
2
|
-
baseURL: string;
|
|
3
|
-
userId?: string;
|
|
4
|
-
jwt?: string;
|
|
5
|
-
serviceKey?: string;
|
|
6
|
-
apiKey?: string;
|
|
7
|
-
/** Back-compat: previous name for JWT token (Bearer). */
|
|
8
|
-
authToken?: string;
|
|
9
|
-
}
|
|
10
|
-
export type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
|
|
11
|
-
export interface BackendRequestOptions {
|
|
12
|
-
params?: Record<string, unknown>;
|
|
13
|
-
data?: unknown;
|
|
14
|
-
headers?: Record<string, string>;
|
|
15
|
-
/** Override per-request timeout in milliseconds. */
|
|
16
|
-
timeoutMs?: number;
|
|
17
|
-
}
|
|
18
|
-
export interface BinaryBackendResponse {
|
|
19
|
-
content_type: string;
|
|
20
|
-
content_length: number;
|
|
21
|
-
encoding: "base64";
|
|
22
|
-
data_base64: string;
|
|
23
|
-
}
|
|
24
|
-
export declare class BackendError extends Error {
|
|
25
|
-
readonly status: number;
|
|
26
|
-
readonly code: string;
|
|
27
|
-
readonly retryable: boolean;
|
|
28
|
-
readonly upgradeUrl?: string | undefined;
|
|
29
|
-
constructor(message: string, status: number, code: string, retryable: boolean, upgradeUrl?: string | undefined);
|
|
30
|
-
}
|
|
31
|
-
export declare class BackendClient {
|
|
32
|
-
private client;
|
|
33
|
-
private userId;
|
|
34
|
-
private jwt?;
|
|
35
|
-
private serviceKey?;
|
|
36
|
-
private apiKey?;
|
|
37
|
-
private credentialManager;
|
|
38
|
-
private _pendingAuthFlow;
|
|
39
|
-
private _authFlowResult;
|
|
40
|
-
constructor(config: BackendConfig);
|
|
41
|
-
/**
|
|
42
|
-
* Override the API key at runtime. Used by the SSE server to inject the
|
|
43
|
-
* per-session user's key so that all tool calls are authenticated and
|
|
44
|
-
* metered against the correct user account.
|
|
45
|
-
*/
|
|
46
|
-
setApiKey(key: string): void;
|
|
47
|
-
/**
|
|
48
|
-
* Start the device auth flow in the background if not already running.
|
|
49
|
-
* Called automatically when no credentials are found in the interceptor.
|
|
50
|
-
* Idempotent — only starts once per client lifetime.
|
|
51
|
-
*/
|
|
52
|
-
private autoStartDeviceAuth;
|
|
53
|
-
private expectsBinaryResponse;
|
|
54
|
-
private toBuffer;
|
|
55
|
-
private decodePayload;
|
|
56
|
-
private extractMessage;
|
|
57
|
-
private normalizeContentType;
|
|
58
|
-
private isBinaryContentType;
|
|
59
|
-
/** Maps an AxiosError to a structured BackendError. */
|
|
60
|
-
private mapError;
|
|
61
|
-
get<T>(path: string, params?: Record<string, any>, timeoutMs?: number): Promise<T>;
|
|
62
|
-
post<T>(path: string, data?: any, timeoutMs?: number): Promise<T>;
|
|
63
|
-
delete<T>(path: string): Promise<T>;
|
|
64
|
-
request<T>(method: HttpMethod, path: string, options?: BackendRequestOptions): Promise<T>;
|
|
65
|
-
getUserId(): string;
|
|
66
|
-
}
|
|
67
|
-
export declare const backendClient: BackendClient;
|