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