@openephemeris/mcp-server 3.3.0 → 3.3.1

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/dist/index.js CHANGED
@@ -5,7 +5,7 @@ import { fileURLToPath } from "node:url";
5
5
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
6
6
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
7
7
  import { CallToolRequestSchema, ListToolsRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
8
- import { initTools, toolRegistry } from "./tools/index.js";
8
+ import { initTools, toolRegistry, formatToolResponse } from "./tools/index.js";
9
9
  function resolveServerVersion() {
10
10
  try {
11
11
  const here = path.dirname(fileURLToPath(import.meta.url));
@@ -104,53 +104,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
104
104
  try {
105
105
  const result = await tool.handler(request.params.arguments ?? {});
106
106
  const durationMs = Date.now() - startTime;
107
- console.error(`[MCP] Success: ${toolName} (${durationMs}ms)`);
108
- // Intercept binary backend responses (like Chart Wheels) to return native MCP Images
109
- if (result &&
110
- typeof result === "object" &&
111
- "encoding" in result &&
112
- result.encoding === "base64" &&
113
- "data_base64" in result &&
114
- "content_type" in result) {
115
- const binResp = result;
116
- console.error(`[MCP] 🎨 Returning image (${binResp.content_length} bytes) for ${toolName}`);
117
- return {
118
- content: [
119
- {
120
- type: "image",
121
- data: binResp.data_base64,
122
- mimeType: binResp.content_type,
123
- },
124
- ],
125
- };
126
- }
127
- const jsonStr = JSON.stringify(result, null, 2);
128
- // Safety limit: ~100kb JSON is roughly 20-30k tokens.
129
- // Usually happens if LLMs ask for 50+ years of transits or raw ephemeris arrays.
130
- if (jsonStr.length > 100_000) {
131
- console.error(`[MCP] ⚠️ Warning: Payload too large (${(jsonStr.length / 1024).toFixed(1)}kb) for ${toolName}`);
132
- return {
133
- content: [
134
- {
135
- type: "text",
136
- text: JSON.stringify({
137
- success: false,
138
- error: "PAYLOAD_TOO_LARGE",
139
- message: `The requested query produced too much data (${(jsonStr.length / 1024).toFixed(1)}kb). Please significantly narrow your request parameters (e.g., fewer years, smaller coordinate bounding box, etc.) to fit within context limits.`
140
- }, null, 2),
141
- },
142
- ],
143
- isError: true,
144
- };
145
- }
146
- return {
147
- content: [
148
- {
149
- type: "text",
150
- text: jsonStr,
151
- },
152
- ],
153
- };
107
+ return formatToolResponse(toolName, result, durationMs);
154
108
  }
155
109
  catch (error) {
156
110
  const durationMs = Date.now() - startTime;
@@ -20,7 +20,7 @@ import axios from "axios";
20
20
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
21
21
  import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
22
22
  import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
23
- import { initTools, toolRegistry } from "./tools/index.js";
23
+ import { initTools, toolRegistry, formatToolResponse } from "./tools/index.js";
24
24
  import { backendClient } from "./backend/client.js";
25
25
  // ---------------------------------------------------------------------------
26
26
  // Helpers
@@ -108,13 +108,16 @@ function createMcpServer() {
108
108
  throw new Error(`Unknown tool: ${toolName}`);
109
109
  }
110
110
  try {
111
+ const startTime = Date.now();
112
+ console.error(`[MCP] Executing tool: ${toolName}`);
111
113
  const result = await tool.handler(request.params.arguments ?? {});
112
- return {
113
- content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
114
- };
114
+ const durationMs = Date.now() - startTime;
115
+ return formatToolResponse(toolName, result, durationMs);
115
116
  }
116
117
  catch (error) {
118
+ const startTime = Date.now(); // Not strictly accurate for errors but acceptable for logging structure if needed
117
119
  const errorMessage = error instanceof Error ? error.message : String(error);
120
+ console.error(`[MCP] \u274c Failed: ${toolName} - ${errorMessage}`);
118
121
  return {
119
122
  content: [
120
123
  {
@@ -31,3 +31,28 @@ export declare function validateRequired(args: any, requiredKeys: string[]): voi
31
31
  * Throws an error if only one of the coordinate pair is provided.
32
32
  */
33
33
  export declare function validateCoordinates(args: any, latKey: string, lonKey: string): void;
34
+ /**
35
+ * Formats a raw tool response into native MCP content blocks.
36
+ * Intercepts binary image responses (like Chart Wheels) and returns them as native MCP Images.
37
+ * Applies a safety limit to JSON payloads to prevent crashing LLM contexts.
38
+ */
39
+ export declare function formatToolResponse(toolName: string, result: any, durationMs: number): {
40
+ content: {
41
+ type: string;
42
+ data: string;
43
+ mimeType: string;
44
+ }[];
45
+ isError?: undefined;
46
+ } | {
47
+ content: {
48
+ type: string;
49
+ text: string;
50
+ }[];
51
+ isError: boolean;
52
+ } | {
53
+ content: {
54
+ type: string;
55
+ text: string;
56
+ }[];
57
+ isError?: undefined;
58
+ };
@@ -66,3 +66,57 @@ export function validateCoordinates(args, latKey, lonKey) {
66
66
  throw new Error(`Both ${latKey} and ${lonKey} must be provided together, or both omitted.`);
67
67
  }
68
68
  }
69
+ /**
70
+ * Formats a raw tool response into native MCP content blocks.
71
+ * Intercepts binary image responses (like Chart Wheels) and returns them as native MCP Images.
72
+ * Applies a safety limit to JSON payloads to prevent crashing LLM contexts.
73
+ */
74
+ export function formatToolResponse(toolName, result, durationMs) {
75
+ // Intercept binary backend responses (like Chart Wheels) to return native MCP Images
76
+ if (result &&
77
+ typeof result === "object" &&
78
+ "encoding" in result &&
79
+ result.encoding === "base64" &&
80
+ "data_base64" in result &&
81
+ "content_type" in result) {
82
+ const binResp = result;
83
+ console.error(`[MCP] 🎨 Returning image (${binResp.content_length} bytes) for ${toolName} (${durationMs}ms)`);
84
+ return {
85
+ content: [
86
+ {
87
+ type: "image",
88
+ data: binResp.data_base64,
89
+ mimeType: binResp.content_type,
90
+ },
91
+ ],
92
+ };
93
+ }
94
+ const jsonStr = JSON.stringify(result, null, 2);
95
+ // Safety limit: ~500kb JSON is roughly 100k+ tokens.
96
+ // Usually happens if LLMs ask for 50+ years of transits or raw ephemeris arrays.
97
+ if (jsonStr.length > 500_000) {
98
+ console.error(`[MCP] ⚠️ Warning: Payload too large (${(jsonStr.length / 1024).toFixed(1)}kb) for ${toolName}`);
99
+ return {
100
+ content: [
101
+ {
102
+ type: "text",
103
+ text: JSON.stringify({
104
+ success: false,
105
+ error: "PAYLOAD_TOO_LARGE",
106
+ message: `The requested query produced too much data (${(jsonStr.length / 1024).toFixed(1)}kb). Please significantly narrow your request parameters (e.g., fewer years, smaller coordinate bounding box, etc.) to fit within context limits.`
107
+ }, null, 2),
108
+ },
109
+ ],
110
+ isError: true,
111
+ };
112
+ }
113
+ console.error(`[MCP] ✅ Success: ${toolName} (${durationMs}ms)`);
114
+ return {
115
+ content: [
116
+ {
117
+ type: "text",
118
+ text: jsonStr,
119
+ },
120
+ ],
121
+ };
122
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openephemeris/mcp-server",
3
- "version": "3.3.0",
3
+ "version": "3.3.1",
4
4
  "description": "Model Context Protocol server for the Open Ephemeris astronomical computation API",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",