@kaitoy/nasa-images-mcp-server 1.0.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/.env.example ADDED
@@ -0,0 +1,12 @@
1
+ # Optional: NASA API Key for higher rate limits
2
+ # Get your key from: https://api.nasa.gov/
3
+ NASA_API_KEY=
4
+
5
+ # Server port
6
+ PORT=3000
7
+
8
+ # MCP server host (optional)
9
+ MCP_HOST=localhost
10
+
11
+ # MCP allowed hosts (optional, comma-separated list)
12
+ MCP_ALLOWED_HOSTS=localhost,127.0.0.1,example.com
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 kaitoy
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,347 @@
1
+ # NASA Images MCP Server
2
+
3
+ A project to search NASA's image library using an MCP server and display images in an interactive UI.
4
+
5
+ **🌟 Features:**
6
+ - **Streamable HTTP Transport**: Uses the latest MCP HTTP transport protocol
7
+ - **Image Search**: Search by keywords using NASA's Images API
8
+ - **Interactive UI**: Beautiful image viewer using MCP Apps Pattern
9
+ - **Image Navigation**: Display search results sequentially
10
+ - **Session Management**: Stateful session management
11
+
12
+ ![screenshot](nasa-images-mcp.gif)
13
+
14
+ ## Requirements
15
+
16
+ - Node.js 18 or higher
17
+ - npm or yarn
18
+ - NASA API Key (get free at [nasa.gov/api](https://api.nasa.gov/))
19
+
20
+ ## Installation
21
+
22
+ 1. Clone the repository:
23
+ ```bash
24
+ cd nasa-images-mcp-server
25
+ ```
26
+
27
+ 2. Install dependencies:
28
+ ```bash
29
+ npm install
30
+ ```
31
+
32
+ 3. Set up environment variables:
33
+ ```bash
34
+ cp .env.example .env
35
+ ```
36
+
37
+ Edit the `.env` file to configure your NASA API key:
38
+ ```env
39
+ NASA_API_KEY=your_api_key_here
40
+ PORT=3000
41
+ ```
42
+
43
+ 4. Build:
44
+ ```bash
45
+ npm run build
46
+ ```
47
+
48
+ ## Getting a NASA API Key
49
+
50
+ 1. Visit [https://api.nasa.gov/](https://api.nasa.gov/)
51
+ 2. Enter your name and email address in the form
52
+ 3. The API key will be sent to your email immediately
53
+ 4. Add it to your `.env` file
54
+
55
+ **Rate Limits:**
56
+ - DEMO_KEY: 30 requests/hour, 50 requests/day
57
+ - Registered key: 1000 requests/hour
58
+
59
+ ## Usage
60
+
61
+ ### Start in HTTP Server Mode
62
+
63
+ ```bash
64
+ npm start
65
+ ```
66
+
67
+ The server will start at `http://localhost:3000`.
68
+
69
+ ### Start on a Different Port
70
+
71
+ ```bash
72
+ PORT=3001 npm start
73
+ ```
74
+
75
+ ## About Streamable HTTP Transport
76
+
77
+ This server uses **Streamable HTTP Transport**, which is the latest transport layer for running the MCP protocol over HTTP.
78
+
79
+ **Key Features:**
80
+ - āœ… **Stateful Sessions**: Issues a unique session ID for each client
81
+ - āœ… **SSE Support**: Supports push notifications from the server via Server-Sent Events
82
+ - āœ… **JSON-RPC over HTTP**: Communication via standard HTTP request/response
83
+ - āœ… **Session Management**: Automatic session lifecycle management
84
+
85
+ ## Integration with MCP Clients
86
+
87
+ ### Connecting with MCP Inspector
88
+
89
+ You can test the server using [MCP Inspector](https://github.com/modelcontextprotocol/inspector):
90
+
91
+ ```bash
92
+ npx @modelcontextprotocol/inspector http://localhost:3000/mcp
93
+ ```
94
+
95
+ ### Using with Custom MCP Clients
96
+
97
+ Steps to connect from a custom client:
98
+
99
+ 1. **Initialize Request** (POST /mcp):
100
+ ```bash
101
+ curl -X POST http://localhost:3000/mcp \
102
+ -H "Content-Type: application/json" \
103
+ -H "Accept: application/json, text/event-stream" \
104
+ -d '{
105
+ "jsonrpc": "2.0",
106
+ "id": 1,
107
+ "method": "initialize",
108
+ "params": {
109
+ "protocolVersion": "2024-11-05",
110
+ "capabilities": {},
111
+ "clientInfo": {
112
+ "name": "my-client",
113
+ "version": "1.0.0"
114
+ }
115
+ }
116
+ }'
117
+ ```
118
+
119
+ The response headers will include `mcp-session-id`.
120
+
121
+ 2. **Subsequent Requests**:
122
+ ```bash
123
+ curl -X POST http://localhost:3000/mcp \
124
+ -H "Content-Type: application/json" \
125
+ -H "Accept: application/json, text/event-stream" \
126
+ -H "mcp-session-id: <your-session-id>" \
127
+ -d '{
128
+ "jsonrpc": "2.0",
129
+ "id": 2,
130
+ "method": "tools/list"
131
+ }'
132
+ ```
133
+
134
+ 3. **SSE Stream** (GET /mcp):
135
+ ```bash
136
+ curl -X GET "http://localhost:3000/mcp" \
137
+ -H "mcp-session-id: <your-session-id>" \
138
+ -H "Accept: text/event-stream"
139
+ ```
140
+
141
+ ## Endpoints
142
+
143
+ | Endpoint | Method | Description |
144
+ |----------|--------|-------------|
145
+ | `/` | GET | Server information and endpoint list |
146
+ | `/health` | GET | Health check (includes active session count) |
147
+ | `/mcp` | POST | JSON-RPC requests (initialization, tool calls, etc.) |
148
+ | `/mcp` | GET | SSE stream (for server notifications) |
149
+ | `/mcp` | DELETE | Close session |
150
+
151
+ ## Available Tools
152
+
153
+ ### 1. `search_nasa_images`
154
+
155
+ Search NASA's image library.
156
+
157
+ **Parameters:**
158
+ - `query` (string, required): Search query (e.g., "apollo 11", "mars rover")
159
+
160
+ **Usage Example (JSON-RPC):**
161
+ ```json
162
+ {
163
+ "jsonrpc": "2.0",
164
+ "id": 3,
165
+ "method": "tools/call",
166
+ "params": {
167
+ "name": "search_nasa_images",
168
+ "arguments": {
169
+ "query": "apollo 11 moon landing"
170
+ }
171
+ }
172
+ }
173
+ ```
174
+
175
+ ### 2. `get_next_image`
176
+
177
+ Display the next image from the current search results.
178
+
179
+ **Parameters:**
180
+ None
181
+
182
+ **Usage Example (JSON-RPC):**
183
+ ```json
184
+ {
185
+ "jsonrpc": "2.0",
186
+ "id": 4,
187
+ "method": "tools/call",
188
+ "params": {
189
+ "name": "get_next_image",
190
+ "arguments": {}
191
+ }
192
+ }
193
+ ```
194
+
195
+ ## Available Resources
196
+
197
+ ### `nasa-image://current`
198
+
199
+ Get the URL of the currently displayed image.
200
+
201
+ **MIME Type:** `text/uri-list`
202
+
203
+ ### `ui://nasa-images/viewer`
204
+
205
+ HTML UI resource for the image viewer.
206
+
207
+ **MIME Type:** `text/html;profile=mcp-app`
208
+
209
+ ## Architecture
210
+
211
+ ```
212
+ ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
213
+ │ MCP Client (Inspector, Custom App) │
214
+ │ │
215
+ │ ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” │
216
+ │ │ HTTP Client │ │
217
+ │ │ - POST /mcp (JSON-RPC requests) │ │
218
+ │ │ - GET /mcp (SSE stream) │ │
219
+ │ ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ │
220
+ │ ↕ │
221
+ ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜
222
+ │ HTTP/SSE
223
+ ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
224
+ │ NASA Images MCP Server (Express) │
225
+ │ ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”“ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” │
226
+ │ │ StreamableHTTPServerTransport │ │
227
+ │ │ - Session management │ │
228
+ │ │ - JSON-RPC over HTTP │ │
229
+ │ │ - SSE for notifications │ │
230
+ │ ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ │
231
+ │ ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” │
232
+ │ │ McpServer (MCP Apps Pattern) │ │
233
+ │ │ - registerAppTool(search_nasa...) │ │
234
+ │ │ - registerAppTool(get_next_image) │ │
235
+ │ │ - registerAppResource(viewer UI) │ │
236
+ │ │ - registerResource(image URL) │ │
237
+ │ ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ │
238
+ │ ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” │
239
+ │ │ Session Manager │ │
240
+ │ │ - Manage search state per user │ │
241
+ │ ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ │
242
+ │ ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” │
243
+ │ │ NASA API Client │ │
244
+ │ │ - Image search │ │
245
+ │ │ - Image URL retrieval │ │
246
+ │ ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ │
247
+ ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜
248
+ │ HTTPS
249
+ ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
250
+ │ NASA Images API │
251
+ │ https://images-api.nasa.gov/search │
252
+ ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜
253
+ ```
254
+
255
+ ### Main Components
256
+
257
+ - **src/index.ts**: Express server and transport management
258
+ - **src/mcp-server.ts**: MCP server logic (tool and resource registration)
259
+ - **src/session-manager.ts**: Session management (search state persistence)
260
+ - **src/nasa-api.ts**: NASA API client
261
+ - **src/types.ts**: TypeScript type definitions
262
+
263
+ ## Development
264
+
265
+ ### Watch Mode
266
+
267
+ ```bash
268
+ npm run watch
269
+ ```
270
+
271
+ Automatically rebuilds when files change.
272
+
273
+ ### Development Mode
274
+
275
+ ```bash
276
+ npm run dev
277
+ ```
278
+
279
+ Builds and immediately starts the server.
280
+
281
+ ## Troubleshooting
282
+
283
+ ### Port Already in Use
284
+
285
+ ```
286
+ Error: listen EADDRINUSE: address already in use
287
+ ```
288
+
289
+ **Solution**: Specify a different port:
290
+ ```bash
291
+ PORT=3001 npm start
292
+ ```
293
+
294
+ ### "Cannot find module" Error
295
+
296
+ Run:
297
+ ```bash
298
+ npm run build
299
+ ```
300
+
301
+ to compile TypeScript.
302
+
303
+ ### API Rate Limit Error
304
+
305
+ - If using `DEMO_KEY`, obtain your own API key from [api.nasa.gov](https://api.nasa.gov/)
306
+ - Limited to 30 requests per hour
307
+
308
+ ### "Not Acceptable" Error
309
+
310
+ The client must accept both MIME types:
311
+ - `application/json`
312
+ - `text/event-stream`
313
+
314
+ ```bash
315
+ curl -H "Accept: application/json, text/event-stream" ...
316
+ ```
317
+
318
+ ### Session Not Found
319
+
320
+ Save the `mcp-session-id` header returned after the initialization request and send it as the `mcp-session-id` header in subsequent requests.
321
+
322
+ ## Security Notes
323
+
324
+ - This server has CORS enabled (for development)
325
+ - In production, implement proper CORS settings and rate limiting
326
+ - Manage NASA API keys via environment variables; do not hardcode them
327
+
328
+ ## License
329
+
330
+ MIT
331
+
332
+ ## References
333
+
334
+ - [Model Context Protocol Specification](https://modelcontextprotocol.io/)
335
+ - [MCP TypeScript SDK](https://github.com/modelcontextprotocol/typescript-sdk)
336
+ - [MCP Apps Extension](https://github.com/modelcontextprotocol/ext-apps)
337
+ - [Streamable HTTP Transport](https://modelcontextprotocol.io/specification/draft/transport/http)
338
+ - [NASA Images API](https://images.nasa.gov/docs/images.nasa.gov_api_docs.pdf)
339
+ - [NASA Open APIs](https://api.nasa.gov/)
340
+
341
+ ## Contributing
342
+
343
+ Pull requests are welcome! Bug reports and feature requests are accepted via Issues.
344
+
345
+ ## Support
346
+
347
+ If you encounter issues, please create an Issue or refer to the NASA API support page.
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
package/build/index.js ADDED
@@ -0,0 +1,254 @@
1
+ import express from 'express';
2
+ import cors from 'cors';
3
+ import dotenv from 'dotenv';
4
+ import morgan from 'morgan';
5
+ import { randomUUID } from 'crypto';
6
+ import { createMcpExpressApp } from '@modelcontextprotocol/sdk/server/express.js';
7
+ import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
8
+ import { InMemoryEventStore } from '@modelcontextprotocol/sdk/examples/shared/inMemoryEventStore.js';
9
+ import { createMCPServer } from './mcp-server.js';
10
+ import { SessionManager } from './session-manager.js';
11
+ dotenv.config();
12
+ const PORT = process.env.PORT || 3000;
13
+ const MCP_HOST = process.env.MCP_HOST;
14
+ const MCP_ALLOWED_HOSTS = process.env.MCP_ALLOWED_HOSTS?.split(',').map(h => h.trim()).filter(h => h);
15
+ // JSON-RPC error codes
16
+ const JSONRPC_ERROR_CODE = {
17
+ INVALID_REQUEST: -32600,
18
+ SESSION_NOT_FOUND: -32001,
19
+ INTERNAL_ERROR: -32603,
20
+ };
21
+ // Create Express app using createMcpExpressApp
22
+ const app = createMcpExpressApp({
23
+ ...(MCP_HOST && { host: MCP_HOST }),
24
+ ...(MCP_ALLOWED_HOSTS && MCP_ALLOWED_HOSTS.length > 0 && { allowedHosts: MCP_ALLOWED_HOSTS })
25
+ });
26
+ // Output access logs in httpd log format (skip /health endpoint)
27
+ app.use(morgan('combined', {
28
+ skip: (req, _res) => req.path === '/health'
29
+ }));
30
+ // CORS settings (expose MCP-related headers)
31
+ app.use(cors({
32
+ exposedHeaders: ['mcp-session-id', 'mcp-protocol-version', 'last-event-id'],
33
+ origin: '*'
34
+ }));
35
+ // JSON parser (apply to all endpoints except MCP endpoint)
36
+ // StreamableHTTPServerTransport handles streams directly for MCP endpoint,
37
+ // so applying express.json() would consume the stream
38
+ app.use((req, res, next) => {
39
+ if (req.path === '/mcp') {
40
+ // Skip JSON parser for MCP endpoint
41
+ next();
42
+ }
43
+ else {
44
+ // Apply JSON parser to other endpoints
45
+ express.json()(req, res, next);
46
+ }
47
+ });
48
+ const sessionManager = new SessionManager();
49
+ const transports = new Map();
50
+ /**
51
+ * MCP endpoint (POST - JSON-RPC requests)
52
+ * Issues a session ID on the first connection, then processes requests with that session ID
53
+ */
54
+ app.post('/mcp', async (req, res) => {
55
+ try {
56
+ // Get session ID from header
57
+ const sessionId = req.headers['mcp-session-id'];
58
+ let transport;
59
+ if (sessionId && transports.has(sessionId)) {
60
+ transport = transports.get(sessionId);
61
+ }
62
+ else if (!sessionId) {
63
+ // Create new session only for initialize request when session ID is not present
64
+ if (!req.body || req.body.method !== 'initialize') {
65
+ res.status(400).json({
66
+ jsonrpc: '2.0',
67
+ id: req.body?.id || null,
68
+ error: {
69
+ code: JSONRPC_ERROR_CODE.INVALID_REQUEST,
70
+ message: 'Invalid Request: Session is not initialized.',
71
+ },
72
+ });
73
+ return;
74
+ }
75
+ // Create EventStore for each session (for managing and replaying event history)
76
+ const eventStore = new InMemoryEventStore();
77
+ const newSessionId = randomUUID();
78
+ transport = new StreamableHTTPServerTransport({
79
+ sessionIdGenerator: () => newSessionId,
80
+ eventStore: eventStore,
81
+ onsessioninitialized: (newSessionId) => {
82
+ console.log(`✨ Session initialized: ${newSessionId}`);
83
+ transports.set(newSessionId, transport);
84
+ },
85
+ });
86
+ transport.onclose = () => {
87
+ const sid = transport.sessionId;
88
+ if (sid && transports.has(sid)) {
89
+ console.log(`šŸ”Œ Transport closed for the session: ${sid}`);
90
+ transports.delete(sid);
91
+ }
92
+ };
93
+ let server = createMCPServer(sessionManager, newSessionId);
94
+ // Connect server and transport (onsessioninitialized is called at this point)
95
+ await server.connect(transport);
96
+ }
97
+ else {
98
+ res.status(400).json({
99
+ jsonrpc: '2.0',
100
+ id: req.body?.id || null,
101
+ error: {
102
+ code: JSONRPC_ERROR_CODE.SESSION_NOT_FOUND,
103
+ message: 'Invalid Request: Session not found.',
104
+ },
105
+ });
106
+ return;
107
+ }
108
+ await transport.handleRequest(req, res, req.body);
109
+ }
110
+ catch (error) {
111
+ console.error('Error handling POST /mcp:', error);
112
+ if (!res.headersSent) {
113
+ res.status(500).json({
114
+ jsonrpc: '2.0',
115
+ id: null,
116
+ error: {
117
+ code: JSONRPC_ERROR_CODE.INTERNAL_ERROR,
118
+ message: `Internal Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
119
+ },
120
+ });
121
+ }
122
+ }
123
+ });
124
+ /**
125
+ * MCP endpoint (GET - SSE stream)
126
+ * Event stream from server to client
127
+ */
128
+ app.get('/mcp', async (req, res) => {
129
+ try {
130
+ // Get session ID from header
131
+ const sessionId = req.headers['mcp-session-id'];
132
+ if (!sessionId || !transports.has(sessionId)) {
133
+ res.status(400).json({
134
+ jsonrpc: '2.0',
135
+ id: null,
136
+ error: {
137
+ code: JSONRPC_ERROR_CODE.SESSION_NOT_FOUND,
138
+ message: 'Invalid Request: Session not found',
139
+ }
140
+ });
141
+ return;
142
+ }
143
+ const lastEventId = req.headers['last-event-id'];
144
+ if (lastEventId) {
145
+ console.log(`Client reconnecting with Last-Event-ID: ${lastEventId}`);
146
+ }
147
+ else {
148
+ console.log(`Establishing new SSE stream for session ${sessionId}`);
149
+ }
150
+ // Handle SSE stream
151
+ await transports.get(sessionId)?.handleRequest(req, res);
152
+ }
153
+ catch (error) {
154
+ console.error('Error handling GET /mcp:', error);
155
+ if (!res.headersSent) {
156
+ res.status(500).json({
157
+ jsonrpc: '2.0',
158
+ id: null,
159
+ error: {
160
+ code: JSONRPC_ERROR_CODE.INTERNAL_ERROR,
161
+ message: `Internal Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
162
+ },
163
+ });
164
+ }
165
+ }
166
+ });
167
+ /**
168
+ * Session deletion endpoint (optional)
169
+ */
170
+ app.delete('/mcp', async (req, res) => {
171
+ // Get session ID from header
172
+ const sessionId = req.headers['mcp-session-id'];
173
+ if (!sessionId || !transports.has(sessionId)) {
174
+ res
175
+ .status(400)
176
+ .json({
177
+ jsonrpc: '2.0',
178
+ id: null,
179
+ error: {
180
+ code: JSONRPC_ERROR_CODE.SESSION_NOT_FOUND,
181
+ message: 'Invalid Request: Session not found',
182
+ }
183
+ });
184
+ return;
185
+ }
186
+ try {
187
+ // Handle SSE stream
188
+ await transports.get(sessionId)?.handleRequest(req, res);
189
+ }
190
+ catch (error) {
191
+ console.error('Error handling DELETE /mcp:', error);
192
+ if (!res.headersSent) {
193
+ res.status(500).json({
194
+ jsonrpc: '2.0',
195
+ id: null,
196
+ error: {
197
+ code: JSONRPC_ERROR_CODE.INTERNAL_ERROR,
198
+ message: `Internal Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
199
+ },
200
+ });
201
+ }
202
+ }
203
+ });
204
+ /**
205
+ * Health check
206
+ */
207
+ app.get('/health', (req, res) => {
208
+ res.json({
209
+ status: 'ok',
210
+ activeSessions: transports.size,
211
+ timestamp: new Date().toISOString()
212
+ });
213
+ });
214
+ /**
215
+ * Root endpoint
216
+ */
217
+ app.get('/', (_req, res) => {
218
+ res.json({
219
+ name: 'NASA Images MCP Server',
220
+ version: '1.0.0',
221
+ endpoints: {
222
+ 'POST /mcp': 'Send JSON-RPC requests',
223
+ 'GET /mcp': 'SSE stream for server notifications',
224
+ 'DELETE /mcp': 'Close a session',
225
+ 'GET /health': 'Health check'
226
+ },
227
+ documentation: 'https://github.com/modelcontextprotocol/specification'
228
+ });
229
+ });
230
+ // Start server
231
+ app.listen(PORT, () => {
232
+ console.log(`✨ NASA Images MCP Server is running!`);
233
+ console.log(`🌐 HTTP Server: http://localhost:${PORT}`);
234
+ console.log(`šŸ”Œ MCP Endpoint: http://localhost:${PORT}/mcp`);
235
+ console.log(`šŸ’š Health Check: http://localhost:${PORT}/health`);
236
+ console.log(`\nšŸ“ To use with MCP clients, configure them to connect to: http://localhost:${PORT}/mcp`);
237
+ });
238
+ // Graceful shutdown
239
+ process.on('SIGINT', async () => {
240
+ console.log('\nšŸ›‘ Shutting down gracefully...');
241
+ // Close all sessions
242
+ for (const sessionId in transports) {
243
+ try {
244
+ await transports.get(sessionId)?.close();
245
+ transports.delete(sessionId);
246
+ console.log(`āœ“ Closed a transport for the session: ${sessionId}`);
247
+ }
248
+ catch (error) {
249
+ console.error(`āœ— Error closing a transport for the session ${sessionId}:`, error);
250
+ }
251
+ }
252
+ process.exit(0);
253
+ });
254
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { SessionManager } from './session-manager.js';
3
+ export declare function createMCPServer(sessionManager: SessionManager, sessionId: string): McpServer;
4
+ //# sourceMappingURL=mcp-server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-server.d.ts","sourceRoot":"","sources":["../src/mcp-server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAGpE,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAQtD,wBAAgB,eAAe,CAAC,cAAc,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,aA4JhF"}