@xyd-js/mcp-server 0.0.0-build-9f87f13-20250930210637
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/CHANGELOG.md +9 -0
- package/LICENSE +21 -0
- package/README.md +89 -0
- package/bin/xyd-mcp-server.mjs +4 -0
- package/demo/client/client.ts +94 -0
- package/demo/simple/openapi.json +641 -0
- package/demo/simple/server.ts +217 -0
- package/dist/index.js +190770 -0
- package/package.json +35 -0
- package/src/index.ts +18 -0
- package/src/mcp.ts +220 -0
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@xyd-js/mcp-server",
|
|
3
|
+
"version": "0.0.0-build-9f87f13-20250930210637",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "MCP server for xyd",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
"./package.json": "./package.json",
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.js"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"bin": {
|
|
14
|
+
"xyd-mcp-server": "./bin/xyd-mcp-server.mjs"
|
|
15
|
+
},
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"@modelcontextprotocol/sdk": "^1.18.1",
|
|
18
|
+
"express": "^5.1.0",
|
|
19
|
+
"@xyd-js/mcp": "0.0.0-build-9f87f13-20250930210637"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@types/express": "^4.17.21",
|
|
23
|
+
"@types/node": "^20.9.0",
|
|
24
|
+
"openapi-typescript": "^7.4.2",
|
|
25
|
+
"typescript": "^5.6.2",
|
|
26
|
+
"@types/bun": "latest"
|
|
27
|
+
},
|
|
28
|
+
"scripts": {
|
|
29
|
+
"build": "bun build src/index.ts --outdir dist --target node --format esm",
|
|
30
|
+
"dev": "bun --watch src/index.ts",
|
|
31
|
+
"start": "bun dist/index.js",
|
|
32
|
+
"demo:simple": "bun --watch demo/simple/server.ts",
|
|
33
|
+
"mcp:inspector": "npx @modelcontextprotocol/inspector http://localhost:3000/mcp"
|
|
34
|
+
}
|
|
35
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import express from "express";
|
|
2
|
+
|
|
3
|
+
import { MCPServer } from "./mcp";
|
|
4
|
+
|
|
5
|
+
const mcp = new MCPServer();
|
|
6
|
+
const app = express();
|
|
7
|
+
|
|
8
|
+
app.use(express.json());
|
|
9
|
+
|
|
10
|
+
app.post("/mcp", mcp.handleConnectionRequest);
|
|
11
|
+
app.get("/mcp", mcp.handleSessionRequest);
|
|
12
|
+
app.delete("/mcp", mcp.handleSessionRequest);
|
|
13
|
+
|
|
14
|
+
const port = process.env.PORT || 3000;
|
|
15
|
+
console.log("Running MCP server on port", port);
|
|
16
|
+
|
|
17
|
+
app.listen(port);
|
|
18
|
+
|
package/src/mcp.ts
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
import express from "express";
|
|
4
|
+
|
|
5
|
+
import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
|
|
6
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
7
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
8
|
+
|
|
9
|
+
import { mcpUniformResources, mcpUniformTools } from "@xyd-js/mcp";
|
|
10
|
+
|
|
11
|
+
// Extend Express Request type to include pendingToken
|
|
12
|
+
declare global {
|
|
13
|
+
namespace Express {
|
|
14
|
+
interface Request {
|
|
15
|
+
pendingToken?: string;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class MCPServer {
|
|
21
|
+
private transports: { [sessionId: string]: StreamableHTTPServerTransport } =
|
|
22
|
+
{};
|
|
23
|
+
|
|
24
|
+
// Store tokens by session ID
|
|
25
|
+
private sessionTokens: { [sessionId: string]: string } = {};
|
|
26
|
+
|
|
27
|
+
private uniformSource: string = "";
|
|
28
|
+
|
|
29
|
+
constructor() {
|
|
30
|
+
this.connect = this.connect.bind(this);
|
|
31
|
+
this.handleConnectionRequest = this.handleConnectionRequest.bind(this);
|
|
32
|
+
this.handleSessionRequest = this.handleSessionRequest.bind(this);
|
|
33
|
+
|
|
34
|
+
if (process.argv[2]) {
|
|
35
|
+
this.uniformSource = process.argv[2];
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
public async handleConnectionRequest(
|
|
40
|
+
req: express.Request,
|
|
41
|
+
res: express.Response
|
|
42
|
+
): Promise<void> {
|
|
43
|
+
// Check for existing session ID
|
|
44
|
+
const sessionId = req.headers["mcp-session-id"] as string | undefined;
|
|
45
|
+
let transport: StreamableHTTPServerTransport;
|
|
46
|
+
|
|
47
|
+
if (sessionId && this.transports[sessionId]) {
|
|
48
|
+
// Reuse existing transport
|
|
49
|
+
transport = this.transports[sessionId];
|
|
50
|
+
} else if (!sessionId && isInitializeRequest(req.body)) {
|
|
51
|
+
// Extract and store token
|
|
52
|
+
const authorized = req.headers["authorization"];
|
|
53
|
+
const bearer = authorized?.split("Bearer");
|
|
54
|
+
let token = "";
|
|
55
|
+
if (bearer && bearer.length > 1) {
|
|
56
|
+
token = bearer[1].trim();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
transport = await this.connect(token);
|
|
60
|
+
} else {
|
|
61
|
+
// Invalid request
|
|
62
|
+
res.status(400).json({
|
|
63
|
+
jsonrpc: "2.0",
|
|
64
|
+
error: {
|
|
65
|
+
code: -32000,
|
|
66
|
+
message: "Bad Request: No valid session ID provided",
|
|
67
|
+
},
|
|
68
|
+
id: null,
|
|
69
|
+
});
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Handle the request
|
|
74
|
+
await transport.handleRequest(req, res, req.body);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
public async handleSessionRequest(
|
|
78
|
+
req: express.Request,
|
|
79
|
+
res: express.Response
|
|
80
|
+
): Promise<void> {
|
|
81
|
+
const sessionId = req.headers["mcp-session-id"] as string | undefined;
|
|
82
|
+
if (!sessionId || !this.transports[sessionId]) {
|
|
83
|
+
res.status(400).send("Invalid or missing session ID");
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const transport = this.transports[sessionId];
|
|
88
|
+
await transport.handleRequest(req, res);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
private async connect(
|
|
92
|
+
token?: string
|
|
93
|
+
): Promise<StreamableHTTPServerTransport> {
|
|
94
|
+
// New initialization request
|
|
95
|
+
const transport = new StreamableHTTPServerTransport({
|
|
96
|
+
sessionIdGenerator: () => randomUUID(),
|
|
97
|
+
onsessioninitialized: (sessionId) => {
|
|
98
|
+
// Store the transport by session ID
|
|
99
|
+
this.transports[sessionId] = transport;
|
|
100
|
+
|
|
101
|
+
// Store the token for this session if we have one
|
|
102
|
+
if (token) {
|
|
103
|
+
this.sessionTokens[sessionId] = token;
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
// DNS rebinding protection is disabled by default for backwards compatibility. If you are running this server
|
|
107
|
+
// locally, make sure to set:
|
|
108
|
+
// enableDnsRebindingProtection: true,
|
|
109
|
+
// allowedHosts: ['127.0.0.1'],
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// Clean up transport when closed
|
|
113
|
+
transport.onclose = () => {
|
|
114
|
+
if (transport.sessionId) {
|
|
115
|
+
delete this.transports[transport.sessionId];
|
|
116
|
+
delete this.sessionTokens[transport.sessionId];
|
|
117
|
+
console.log("Cleaned up session:", transport.sessionId);
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const server = new McpServer({
|
|
122
|
+
name: "xyd-mcp-server",
|
|
123
|
+
version: "1.0.0",
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
if (this.uniformSource) {
|
|
127
|
+
await mcpUniformResources(server, this.uniformSource);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (token) {
|
|
131
|
+
await mcpUniformTools(server, this.uniformSource, token);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Add simple token tool
|
|
135
|
+
this.addSimpleTokenTool(server);
|
|
136
|
+
|
|
137
|
+
// Connect to the MCP server
|
|
138
|
+
await server.connect(transport);
|
|
139
|
+
|
|
140
|
+
return transport;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Simple method to add a token info tool
|
|
144
|
+
private addSimpleTokenTool(server: McpServer): void {
|
|
145
|
+
server.registerTool(
|
|
146
|
+
"get_token_info",
|
|
147
|
+
{
|
|
148
|
+
title: "Get Token Info",
|
|
149
|
+
description:
|
|
150
|
+
"Display information about the stored authentication token",
|
|
151
|
+
inputSchema: {},
|
|
152
|
+
},
|
|
153
|
+
async () => {
|
|
154
|
+
// Get all session info
|
|
155
|
+
const sessionCount = Object.keys(this.sessionTokens).length;
|
|
156
|
+
const sessions = Object.keys(this.sessionTokens);
|
|
157
|
+
|
|
158
|
+
if (sessionCount === 0) {
|
|
159
|
+
return {
|
|
160
|
+
content: [
|
|
161
|
+
{
|
|
162
|
+
type: "text",
|
|
163
|
+
text: "No authentication tokens found in any session",
|
|
164
|
+
},
|
|
165
|
+
],
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
let result = `Found ${sessionCount} session(s) with tokens:\n\n`;
|
|
170
|
+
|
|
171
|
+
for (const sessionId of sessions) {
|
|
172
|
+
const token = this.sessionTokens[sessionId];
|
|
173
|
+
const maskedToken =
|
|
174
|
+
token.length > 10
|
|
175
|
+
? `${token.substring(0, 6)}...${token.substring(token.length - 4)}`
|
|
176
|
+
: "***";
|
|
177
|
+
|
|
178
|
+
result += `Session: ${sessionId}\n`;
|
|
179
|
+
result += `Token (masked): ${maskedToken}\n`;
|
|
180
|
+
result += `Token length: ${token.length} characters\n\n`;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return {
|
|
184
|
+
content: [
|
|
185
|
+
{
|
|
186
|
+
type: "text",
|
|
187
|
+
text: result,
|
|
188
|
+
},
|
|
189
|
+
],
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Method to get token for a specific session
|
|
196
|
+
public getSessionToken(sessionId: string): string | undefined {
|
|
197
|
+
return this.sessionTokens[sessionId];
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Method to get active session count
|
|
201
|
+
private getActiveSessionCount(): number {
|
|
202
|
+
return Object.keys(this.transports).length;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Method to clean up a specific session
|
|
206
|
+
private cleanupSession(sessionId: string): boolean {
|
|
207
|
+
if (this.transports[sessionId]) {
|
|
208
|
+
delete this.transports[sessionId];
|
|
209
|
+
delete this.sessionTokens[sessionId];
|
|
210
|
+
return true;
|
|
211
|
+
}
|
|
212
|
+
return false;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Method to clean up all sessions
|
|
216
|
+
private cleanupAllSessions(): void {
|
|
217
|
+
this.transports = {};
|
|
218
|
+
this.sessionTokens = {};
|
|
219
|
+
}
|
|
220
|
+
}
|