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.
- package/package.json +9 -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.
|
|
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": [
|
|
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
|
|
77
|
-
if (
|
|
78
|
-
await
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
162
|
+
res.json({ status: "ok", server: "asrai-mcp", version });
|
|
131
163
|
});
|
|
132
164
|
|
|
133
165
|
// ── Start ─────────────────────────────────────────────────────────────────────
|