Skip to content

Commit 67daeca

Browse files
Enhance NASA API Handlers and Resource Management
- Refactor NASA API handlers to support dynamic resource generation - Add comprehensive resource registration for each NASA API endpoint - Improve error handling and response formatting across handlers - Rename mars-rover.ts to mars_rover.ts for consistency - Update index.ts with expanded resource templates and global tool registration - Modify setup.ts and manifest.ts to reflect handler and endpoint changes
1 parent 2ac2afa commit 67daeca

File tree

17 files changed

+1318
-213
lines changed

17 files changed

+1318
-213
lines changed

src/handlers/nasa/cmr.ts

Lines changed: 114 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { z } from 'zod';
22
import axios from 'axios';
3+
import { addResource } from '../../index';
34

45
const CMR_API_BASE_URL = 'https://cmr.earthdata.nasa.gov/search';
56

@@ -73,8 +74,16 @@ export async function nasaCmrHandler(params: CmrParams) {
7374
...otherParams
7475
} = params;
7576

77+
// Determine the correct format extension for the URL
78+
let formatExtension = format;
79+
if (format === 'json') {
80+
formatExtension = 'json';
81+
} else if (format === 'umm_json') {
82+
formatExtension = 'umm_json';
83+
}
84+
7685
// Determine search endpoint based on search type
77-
const endpoint = `/${search_type}.${format}`;
86+
const endpoint = `/${search_type}.${formatExtension}`;
7887

7988
// Construct parameters
8089
const queryParams: Record<string, any> = {
@@ -108,35 +117,123 @@ export async function nasaCmrHandler(params: CmrParams) {
108117
queryParams.include_facets = 'v2';
109118
}
110119

120+
console.log(`Searching CMR: ${CMR_API_BASE_URL}${endpoint} with params:`, queryParams);
121+
111122
// Make the request to CMR directly
112123
const response = await axios({
113124
url: `${CMR_API_BASE_URL}${endpoint}`,
114125
params: queryParams,
115126
headers: {
116-
'Client-Id': 'NASA-MCP-Server'
117-
}
127+
'Client-Id': 'NASA-MCP-Server',
128+
'Accept': format === 'json' || format === 'umm_json' ? 'application/json' : undefined
129+
},
130+
timeout: 30000 // 30 second timeout
118131
});
119132

120-
return { result: response.data };
133+
// Parse the response based on format
134+
let data;
135+
if (format === 'json' || format === 'umm_json') {
136+
data = response.data;
137+
} else {
138+
// For non-JSON formats, just return the raw text
139+
data = {
140+
raw: response.data,
141+
format: format
142+
};
143+
}
144+
145+
// Format the response to match MCP expectations
146+
let summary = '';
147+
let formattedData;
148+
149+
if (search_type === 'collections') {
150+
const collectionsCount =
151+
format === 'json' ? (data.feed?.entry?.length || 0) :
152+
format === 'umm_json' ? (data.items?.length || 0) :
153+
0;
154+
summary = `Found ${collectionsCount} NASA collections`;
155+
formattedData = data;
156+
} else {
157+
const granulesCount =
158+
format === 'json' ? (data.feed?.entry?.length || 0) :
159+
format === 'umm_json' ? (data.items?.length || 0) :
160+
0;
161+
summary = `Found ${granulesCount} data granules`;
162+
formattedData = data;
163+
}
164+
165+
// Create a resource ID
166+
const resourceParams = [];
167+
if (params.keyword) resourceParams.push(`keyword=${encodeURIComponent(params.keyword)}`);
168+
if (params.concept_id) resourceParams.push(`concept_id=${params.concept_id}`);
169+
if (temporal) resourceParams.push(`temporal=${encodeURIComponent(temporal)}`);
170+
171+
const resourceId = `nasa://cmr/${search_type}${resourceParams.length > 0 ? '?' + resourceParams.join('&') : ''}`;
172+
173+
// Register the response as a resource
174+
addResource(resourceId, {
175+
name: `NASA CMR ${search_type} search${params.keyword ? ` for "${params.keyword}"` : ''}`,
176+
mimeType: 'application/json',
177+
text: JSON.stringify(formattedData, null, 2)
178+
});
179+
180+
// If the response includes specific collections or granules, register those too
181+
if (formattedData.feed?.entry && Array.isArray(formattedData.feed.entry)) {
182+
formattedData.feed.entry.forEach((entry: any, index: number) => {
183+
if (index < 5) { // Limit to first 5 entries to avoid too many resources
184+
const entryId = entry.id || entry['concept-id'] || `${search_type}-${index}`;
185+
const entryTitle = entry.title || `NASA ${search_type} Item ${index + 1}`;
186+
187+
const entryResourceId = `nasa://cmr/${search_type}/item?id=${entryId}`;
188+
189+
addResource(entryResourceId, {
190+
name: entryTitle,
191+
mimeType: 'application/json',
192+
text: JSON.stringify(entry, null, 2)
193+
});
194+
}
195+
});
196+
}
197+
198+
return {
199+
content: [
200+
{
201+
type: "text",
202+
text: summary
203+
},
204+
{
205+
type: "text",
206+
text: JSON.stringify(formattedData, null, 2)
207+
}
208+
],
209+
isError: false
210+
};
121211
} catch (error: any) {
122212
console.error('Error in CMR handler:', error);
123213

124214
if (error.name === 'ZodError') {
125-
throw {
126-
error: {
127-
type: 'invalid_request',
128-
message: 'Invalid request parameters',
129-
details: error.errors
130-
}
215+
return {
216+
content: [
217+
{
218+
type: "text",
219+
text: `Invalid CMR request parameters: ${error.message}`
220+
}
221+
],
222+
isError: true
131223
};
132224
}
133225

134-
throw {
135-
error: {
136-
type: 'server_error',
137-
message: error.message || 'An unexpected error occurred',
138-
details: error.response?.data || null
139-
}
226+
return {
227+
content: [
228+
{
229+
type: "text",
230+
text: `Error searching NASA Common Metadata Repository: ${error.message || 'Unknown error'}`
231+
}
232+
],
233+
isError: true
140234
};
141235
}
142-
}
236+
}
237+
238+
// Export the handler function directly as default
239+
export default nasaCmrHandler;

src/handlers/nasa/donki.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { z } from 'zod';
22
import { nasaApiRequest } from '../../utils/api-client';
33
import { DonkiParams } from '../setup';
4+
import { addResource } from '../../index';
45

56
/**
67
* Handle requests for NASA's Space Weather Database Of Notifications, Knowledge, Information (DONKI) API
@@ -34,6 +35,19 @@ export async function nasaDonkiHandler(params: DonkiParams) {
3435
// Call the NASA DONKI API
3536
const result = await nasaApiRequest(endpoint, queryParams);
3637

38+
// Create a resource ID and register the resource
39+
const dateParams = [];
40+
if (startDate) dateParams.push(`start=${startDate}`);
41+
if (endDate) dateParams.push(`end=${endDate}`);
42+
43+
const resourceId = `nasa://donki/${type}${dateParams.length > 0 ? '?' + dateParams.join('&') : ''}`;
44+
45+
addResource(resourceId, {
46+
name: `DONKI ${type.toUpperCase()} Space Weather Data${startDate ? ` from ${startDate}` : ''}${endDate ? ` to ${endDate}` : ''}`,
47+
mimeType: 'application/json',
48+
text: JSON.stringify(result, null, 2)
49+
});
50+
3751
// Return the result
3852
return { result };
3953
} catch (error: any) {
@@ -57,4 +71,7 @@ export async function nasaDonkiHandler(params: DonkiParams) {
5771
}
5872
};
5973
}
60-
}
74+
}
75+
76+
// Export the handler function directly as default
77+
export default nasaDonkiHandler;

src/handlers/nasa/eonet.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { z } from 'zod';
22
import axios from 'axios';
33
import { nasaApiRequest } from '../../utils/api-client';
44
import { EonetParams } from '../setup';
5+
import { addResource } from '../../index';
56

67
// Define the EONET API base URL
78
const EONET_API_BASE_URL = 'https://eonet.gsfc.nasa.gov/api';
@@ -59,12 +60,33 @@ export async function nasaEonetHandler(params: EonetParams) {
5960
timeout: 10000
6061
});
6162

63+
// Register the response as a resource
64+
const resourceId = `nasa://eonet/events?days=${broadParams.days}&status=${broadParams.status}`;
65+
addResource(resourceId, {
66+
name: `EONET Events (${broadParams.days} days, ${broadParams.status} status)`,
67+
mimeType: 'application/json',
68+
text: JSON.stringify(broadResponse.data, null, 2)
69+
});
70+
6271
return {
6372
result: broadResponse.data,
6473
note: 'Used broader search criteria due to no events found with original parameters'
6574
};
6675
}
6776

77+
// Register the response as a resource
78+
const resourceParams = [];
79+
if (days) resourceParams.push(`days=${days}`);
80+
if (category) resourceParams.push(`category=${category}`);
81+
if (status) resourceParams.push(`status=${status}`);
82+
83+
const resourceId = `nasa://eonet/events${category ? '/categories/' + category : ''}?${resourceParams.join('&')}`;
84+
addResource(resourceId, {
85+
name: `EONET Events${category ? ' (' + category + ')' : ''}`,
86+
mimeType: 'application/json',
87+
text: JSON.stringify(response.data, null, 2)
88+
});
89+
6890
// Return the original result
6991
return { result: response.data };
7092
} catch (error: any) {
@@ -88,4 +110,7 @@ export async function nasaEonetHandler(params: EonetParams) {
88110
}
89111
};
90112
}
91-
}
113+
}
114+
115+
// Export the handler function directly as default
116+
export default nasaEonetHandler;

0 commit comments

Comments
 (0)