asrai-mcp 0.5.0 → 0.5.2

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 (2) hide show
  1. package/package.json +9 -2
  2. package/src/sse-server.js +49 -17
package/package.json CHANGED
@@ -1,8 +1,15 @@
1
1
  {
2
2
  "name": "asrai-mcp",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "description": "Asrai crypto analysis MCP server — pay-per-use via x402 on Base. Zero install: just npx.",
5
- "keywords": ["mcp", "crypto", "asrai", "x402", "trading", "signals"],
5
+ "keywords": [
6
+ "mcp",
7
+ "crypto",
8
+ "asrai",
9
+ "x402",
10
+ "trading",
11
+ "signals"
12
+ ],
6
13
  "homepage": "https://asrai.me/agents",
7
14
  "repository": {
8
15
  "type": "git",
package/src/sse-server.js CHANGED
@@ -20,9 +20,13 @@
20
20
 
21
21
  import express from "express";
22
22
  import { randomBytes, randomUUID } from "node:crypto";
23
+ import { createRequire } from "node:module";
23
24
  import { privateKeyToAccount } from "viem/accounts";
25
+
26
+ const { version } = createRequire(import.meta.url)("../package.json");
24
27
  import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
25
28
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
29
+ import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
26
30
  import { createServer } from "./server.js";
27
31
  import { connectionStorage } from "./tools.js";
28
32
 
@@ -30,6 +34,7 @@ const app = express();
30
34
  app.use(express.json());
31
35
 
32
36
  // Track active SSE transports by session ID (for POST /messages routing)
37
+ // Stores { transport, key } so the key is available when POST /messages arrives
33
38
  const sseTransports = {};
34
39
 
35
40
  // Track active streamable HTTP transports by session ID
@@ -59,7 +64,7 @@ app.get("/sse", async (req, res) => {
59
64
  if (!key) return;
60
65
 
61
66
  const transport = new SSEServerTransport("/messages", res);
62
- sseTransports[transport.sessionId] = transport;
67
+ sseTransports[transport.sessionId] = { transport, key };
63
68
 
64
69
  res.on("close", () => {
65
70
  delete sseTransports[transport.sessionId];
@@ -73,9 +78,11 @@ app.get("/sse", async (req, res) => {
73
78
 
74
79
  app.post("/messages", async (req, res) => {
75
80
  const sessionId = req.query.sessionId;
76
- const transport = sseTransports[sessionId];
77
- if (transport) {
78
- await transport.handlePostMessage(req, res);
81
+ const session = sseTransports[sessionId];
82
+ if (session) {
83
+ await connectionStorage.run({ key: session.key, spend: 0 }, async () => {
84
+ await session.transport.handlePostMessage(req, res);
85
+ });
79
86
  } else {
80
87
  res.status(400).send("No active session found.");
81
88
  }
@@ -83,10 +90,19 @@ app.post("/messages", async (req, res) => {
83
90
 
84
91
  // ── HTTP Streamable transport (recommended) ───────────────────────────────────
85
92
 
86
- app.all("/mcp", async (req, res) => {
93
+ // POST /mcp initialize new session or handle existing session message
94
+ app.post("/mcp", async (req, res) => {
87
95
  const sessionId = req.headers["mcp-session-id"];
88
96
 
89
- if (!sessionId) {
97
+ if (sessionId) {
98
+ // Existing session — route to stored transport
99
+ const session = streamableTransports[sessionId];
100
+ if (!session) return res.status(404).json({ jsonrpc: "2.0", error: { code: -32000, message: "Session not found" }, id: null });
101
+
102
+ await connectionStorage.run({ key: session.key, spend: 0 }, async () => {
103
+ await session.transport.handleRequest(req, res, req.body);
104
+ });
105
+ } else if (isInitializeRequest(req.body)) {
90
106
  // New session — extract and validate key
91
107
  const key = extractKey(req, res, "/mcp");
92
108
  if (!key) return;
@@ -98,26 +114,42 @@ app.all("/mcp", async (req, res) => {
98
114
  },
99
115
  });
100
116
 
101
- res.on("close", () => {
117
+ transport.onclose = () => {
102
118
  if (transport.sessionId) delete streamableTransports[transport.sessionId];
103
- });
119
+ };
104
120
 
105
121
  await connectionStorage.run({ key, spend: 0 }, async () => {
106
122
  const server = createServer();
107
123
  await server.connect(transport);
108
- await transport.handleRequest(req, res);
124
+ await transport.handleRequest(req, res, req.body);
109
125
  });
110
126
  } else {
111
- // Existing session look up stored transport + key
112
- const session = streamableTransports[sessionId];
113
- if (!session) return res.status(404).send("Session not found.");
114
-
115
- await connectionStorage.run({ key: session.key, spend: 0 }, async () => {
116
- await session.transport.handleRequest(req, res);
117
- });
127
+ res.status(400).json({ jsonrpc: "2.0", error: { code: -32000, message: "No valid session ID" }, id: null });
118
128
  }
119
129
  });
120
130
 
131
+ // GET /mcp — SSE stream for server-initiated messages (existing sessions only)
132
+ app.get("/mcp", async (req, res) => {
133
+ const sessionId = req.headers["mcp-session-id"];
134
+ const session = streamableTransports[sessionId];
135
+ if (!session) return res.status(400).send("Invalid or missing session ID");
136
+
137
+ await connectionStorage.run({ key: session.key, spend: 0 }, async () => {
138
+ await session.transport.handleRequest(req, res);
139
+ });
140
+ });
141
+
142
+ // DELETE /mcp — session termination
143
+ app.delete("/mcp", async (req, res) => {
144
+ const sessionId = req.headers["mcp-session-id"];
145
+ const session = streamableTransports[sessionId];
146
+ if (!session) return res.status(400).send("Invalid or missing session ID");
147
+
148
+ await connectionStorage.run({ key: session.key, spend: 0 }, async () => {
149
+ await session.transport.handleRequest(req, res, req.body);
150
+ });
151
+ });
152
+
121
153
  // ── Utility routes ────────────────────────────────────────────────────────────
122
154
 
123
155
  app.post("/generate-wallet", (_req, res) => {
@@ -127,7 +159,7 @@ app.post("/generate-wallet", (_req, res) => {
127
159
  });
128
160
 
129
161
  app.get("/health", (_req, res) => {
130
- res.json({ status: "ok", server: "asrai-mcp", version: "0.5.0" });
162
+ res.json({ status: "ok", server: "asrai-mcp", version });
131
163
  });
132
164
 
133
165
  // ── Start ─────────────────────────────────────────────────────────────────────