Skip to content

Commit a42f0e2

Browse files
Cleaned up and fixed errors in the MCP servers, moving forward with Claude Desktop testing
1 parent 17e4944 commit a42f0e2

16 files changed

+1599
-69
lines changed

examples/README.md

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ chmod +x examples/startup.sh
6464

6565
This script will:
6666
1. Install dependencies if needed
67-
2. Compile TypeScript files if they exist
67+
2. Use the pre-compiled JavaScript files or compile TypeScript files if needed
6868
3. Start all three servers in separate terminals or background processes
6969

7070
### Option 2: Manual Startup
@@ -82,13 +82,14 @@ node examples/main-mcp-server.js
8282
node examples/reputation-mcp-server.js
8383
```
8484

85-
If using TypeScript files, compile them first:
85+
### Important Note About Custom Implementations
8686

87-
```bash
88-
npx tsc examples/client-mcp-server.ts --esModuleInterop --resolveJsonModule --target es2020 --module esnext --moduleResolution node
89-
npx tsc examples/main-mcp-server.ts --esModuleInterop --resolveJsonModule --target es2020 --module esnext --moduleResolution node
90-
npx tsc examples/reputation-mcp-server.ts --esModuleInterop --resolveJsonModule --target es2020 --module esnext --moduleResolution node
91-
```
87+
The examples use custom implementations in the `examples/shared` directory:
88+
89+
- `websocket-transport.js/ts`: Server-side WebSocket transport implementation
90+
- `certificate-utils.js/ts`: Certificate generation utilities
91+
92+
These are required because the SDK currently does not provide these implementations directly.
9293

9394
### Using with Claude Desktop
9495

examples/claude_config.txt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,4 +105,7 @@ If you have issues:
105105
1. Make sure all three servers are running
106106
2. Check that the URLs in config.json match your server addresses
107107
3. Verify your firewall allows connections to ports 3001, 3002, and 3003
108-
4. Check the server consoles for connection attempts
108+
4. Check the server consoles for connection attempts
109+
5. If you see errors about missing WebSocketTransport or generateCertificate:
110+
- Ensure you have the custom implementations in examples/shared directory
111+
- Run the servers using the startup.sh script which properly compiles dependencies

examples/client-mcp-server.js

Lines changed: 314 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,314 @@
1+
/**
2+
* Client MCP Server
3+
* Helps clients generate certificates and coupons for use with the Main MCP Server.
4+
* Provides tools for certificate generation, coupon creation and communication with other servers.
5+
*/
6+
import { McpServer } from '../src/server/mcp.js';
7+
import { WebSocketTransport } from './shared/websocket-transport.js';
8+
import { WebSocketServer } from 'ws';
9+
import { createServer } from 'http';
10+
import { z } from 'zod';
11+
import crypto from 'crypto';
12+
import { fileURLToPath } from 'url';
13+
import path from 'path';
14+
import fs from 'fs';
15+
import { Client } from '../src/client/index.js';
16+
import WebSocket from 'ws';
17+
// Import coupon-related utilities
18+
import { generateCertificate } from './shared/certificate-utils.js';
19+
import { createCoupon } from '../src/coupon/create.js';
20+
// Get __dirname equivalent in ES modules
21+
const __filename = fileURLToPath(import.meta.url);
22+
const __dirname = path.dirname(__filename);
23+
// Ensure certs directory exists
24+
const certsDir = path.join(__dirname, 'certs');
25+
if (!fs.existsSync(certsDir)) {
26+
fs.mkdirSync(certsDir, { recursive: true });
27+
}
28+
// In-memory store for client certificates
29+
const clientCertificates = new Map();
30+
// Create server info
31+
const serverInfo = {
32+
name: 'Client MCP Server',
33+
version: '1.0.0',
34+
vendor: 'MCP SDK Demo'
35+
};
36+
// Create HTTP server for the WebSocket
37+
const httpServer = createServer((req, res) => {
38+
if (req.url === '/') {
39+
res.writeHead(200, { 'Content-Type': 'text/html' });
40+
res.end(`
41+
<html>
42+
<head><title>Client MCP Server</title></head>
43+
<body>
44+
<h1>Client MCP Server</h1>
45+
<p>This server helps clients generate and manage coupons.</p>
46+
<p>Connect to the WebSocket endpoint at: ws://localhost:3001/mcp</p>
47+
</body>
48+
</html>
49+
`);
50+
}
51+
else {
52+
res.writeHead(404);
53+
res.end('Not found');
54+
}
55+
});
56+
// Create WebSocket server
57+
const wss = new WebSocketServer({ server: httpServer });
58+
// Create MCP server
59+
const mcpServer = new McpServer(serverInfo);
60+
// Register tool to generate a client certificate
61+
mcpServer.tool('generateClientCertificate', 'Generate a new client certificate for coupon creation', {
62+
commonName: z.string().min(1),
63+
organization: z.string().optional(),
64+
email: z.string().email().optional()
65+
}, async (args) => {
66+
try {
67+
// Generate a new RSA key pair
68+
const privateKey = crypto.generateKeyPairSync('rsa', {
69+
modulusLength: 2048,
70+
publicKeyEncoding: {
71+
type: 'spki',
72+
format: 'pem'
73+
},
74+
privateKeyEncoding: {
75+
type: 'pkcs8',
76+
format: 'pem'
77+
}
78+
}).privateKey;
79+
// Create certificate details
80+
const subjectInfo = {
81+
commonName: args.commonName,
82+
organization: args.organization || 'MCP Demo',
83+
organizationalUnit: 'Client',
84+
locality: 'Internet',
85+
state: 'Worldwide',
86+
country: 'US',
87+
emailAddress: args.email || `${args.commonName.toLowerCase().replace(/\s+/g, '.')}@example.com`
88+
};
89+
// Generate self-signed certificate
90+
const certificate = await generateCertificate({
91+
subject: subjectInfo,
92+
issuer: subjectInfo, // Self-signed
93+
privateKey,
94+
serialNumber: crypto.randomBytes(16).toString('hex'),
95+
validityDays: 365
96+
});
97+
// Store certificate
98+
const clientId = crypto.randomUUID();
99+
clientCertificates.set(clientId, {
100+
id: clientId,
101+
privateKey,
102+
certificate,
103+
createdAt: new Date().toISOString()
104+
});
105+
return {
106+
content: [
107+
{
108+
type: 'text',
109+
text: `Certificate generated successfully!\n\nYour Client ID: ${clientId}\n\nUse this ID to create coupons and send requests to the Main MCP Server.`
110+
}
111+
]
112+
};
113+
}
114+
catch (error) {
115+
const errorMessage = error instanceof Error ? error.message : String(error);
116+
return {
117+
content: [
118+
{
119+
type: 'text',
120+
text: `Error generating certificate: ${errorMessage}`
121+
}
122+
],
123+
isError: true
124+
};
125+
}
126+
});
127+
// Register tool to create a coupon
128+
mcpServer.tool('createCoupon', 'Create a signed coupon for use with the Main MCP Server', {
129+
clientId: z.string().uuid(),
130+
recipient: z.string().min(1),
131+
purpose: z.string().optional(),
132+
expiresInMinutes: z.number().int().positive().optional()
133+
}, async (args) => {
134+
try {
135+
// Get client certificate
136+
const clientInfo = clientCertificates.get(args.clientId);
137+
if (!clientInfo) {
138+
throw new Error(`Client ID ${args.clientId} not found. Generate a certificate first.`);
139+
}
140+
// Set expiry time
141+
const expiryMinutes = args.expiresInMinutes || 60; // Default 1 hour
142+
const expiresAt = new Date(Date.now() + (expiryMinutes * 60 * 1000)).toISOString();
143+
// Create coupon
144+
const couponInput = {
145+
issuer: {
146+
commonName: clientInfo.certificate.subject.commonName,
147+
organization: clientInfo.certificate.subject.organization,
148+
organizationalUnit: clientInfo.certificate.subject.organizationalUnit
149+
},
150+
recipient: {
151+
commonName: args.recipient,
152+
organization: 'Main MCP Server',
153+
organizationalUnit: 'Server'
154+
},
155+
issuerCertificate: clientInfo.certificate,
156+
expiresAt,
157+
data: {
158+
purpose: args.purpose || 'mcp-request',
159+
issuedBy: 'Client MCP Server',
160+
clientId: args.clientId,
161+
timestamp: new Date().toISOString()
162+
}
163+
};
164+
const coupon = await createCoupon(couponInput, clientInfo.privateKey);
165+
return {
166+
content: [
167+
{
168+
type: 'text',
169+
text: `Coupon created successfully!\n\n` +
170+
`To use this coupon with the Main MCP Server, copy the following coupon object:\n\n` +
171+
`\`\`\`json\n${JSON.stringify(coupon, null, 2)}\n\`\`\``
172+
}
173+
]
174+
};
175+
}
176+
catch (error) {
177+
const errorMessage = error instanceof Error ? error.message : String(error);
178+
return {
179+
content: [
180+
{
181+
type: 'text',
182+
text: `Error creating coupon: ${errorMessage}`
183+
}
184+
],
185+
isError: true
186+
};
187+
}
188+
});
189+
// Register tool to list client certificates
190+
mcpServer.tool('listClientCertificates', 'List all client certificates generated by this server', async () => {
191+
const clients = Array.from(clientCertificates.entries()).map(([id, info]) => ({
192+
id,
193+
commonName: info.certificate.subject.commonName,
194+
organization: info.certificate.subject.organization,
195+
createdAt: info.createdAt
196+
}));
197+
return {
198+
content: [
199+
{
200+
type: 'text',
201+
text: `Client Certificates (${clients.length}):\n\n${JSON.stringify(clients, null, 2)}`
202+
}
203+
]
204+
};
205+
});
206+
// Register tool to send a request to the Main MCP Server
207+
mcpServer.tool('sendToMainServer', 'Send a request with a coupon to the Main MCP Server', {
208+
coupon: z.object({}).passthrough(),
209+
toolName: z.string().min(1),
210+
toolArgs: z.object({}).passthrough().optional()
211+
}, async (args) => {
212+
try {
213+
const mainServerUrl = 'ws://localhost:3002/mcp';
214+
// Connect to the Main MCP Server
215+
const ws = new WebSocket(mainServerUrl);
216+
const result = await new Promise((resolve, reject) => {
217+
// Handle connection error
218+
ws.on('error', (error) => {
219+
reject(`Failed to connect to Main MCP Server: ${error.message}`);
220+
});
221+
// Handle connection
222+
ws.on('open', () => {
223+
// Create MCP client
224+
const client = new Client();
225+
// Connect to the Main MCP Server
226+
client.connect({
227+
send: (data) => {
228+
ws.send(data);
229+
},
230+
onMessage: (callback) => {
231+
ws.on('message', (data) => {
232+
callback(data.toString());
233+
});
234+
},
235+
close: () => {
236+
ws.close();
237+
}
238+
}).then(() => {
239+
// Call the requested tool with the coupon
240+
client.callTool(args.toolName, args.toolArgs || {}, {
241+
coupon: args.coupon
242+
}).then((response) => {
243+
// Close connection
244+
client.close();
245+
// Return the response
246+
resolve(response);
247+
}).catch((error) => {
248+
client.close();
249+
reject(`Error calling tool: ${error.message}`);
250+
});
251+
}).catch((error) => {
252+
ws.close();
253+
reject(`Error connecting to Main MCP Server: ${error.message}`);
254+
});
255+
});
256+
// Set timeout
257+
setTimeout(() => {
258+
if (ws.readyState !== WebSocket.OPEN) {
259+
reject('Connection to Main MCP Server timed out');
260+
}
261+
}, 5000);
262+
});
263+
return {
264+
content: [
265+
{
266+
type: 'text',
267+
text: `Request sent successfully!\n\nResponse from Main MCP Server:\n${JSON.stringify(result, null, 2)}`
268+
}
269+
]
270+
};
271+
}
272+
catch (error) {
273+
const errorMessage = typeof error === 'string' ? error :
274+
(error instanceof Error ? error.message : String(error));
275+
return {
276+
content: [
277+
{
278+
type: 'text',
279+
text: `Error sending request: ${errorMessage}`
280+
}
281+
],
282+
isError: true
283+
};
284+
}
285+
});
286+
// Handle WebSocket connections
287+
wss.on('connection', (ws, req) => {
288+
console.log('Client connected');
289+
// Only handle connections to the MCP endpoint
290+
if (req.url !== '/mcp') {
291+
console.log('Connection rejected: wrong path');
292+
ws.close(1003, 'Connection path not supported');
293+
return;
294+
}
295+
// Create transport for this connection
296+
const transport = new WebSocketTransport(ws);
297+
// Connect the MCP server to this transport
298+
mcpServer.connect(transport)
299+
.catch((error) => {
300+
console.error('Error connecting to transport:', error);
301+
ws.close(1011, 'Server error');
302+
});
303+
// Handle disconnect
304+
ws.on('close', () => {
305+
console.log('Client disconnected');
306+
});
307+
});
308+
// Start the server
309+
const PORT = 3001;
310+
httpServer.listen(PORT, () => {
311+
console.log(`Client MCP Server running on http://localhost:${PORT}`);
312+
console.log(`WebSocket endpoint: ws://localhost:${PORT}/mcp`);
313+
console.log(`Use this server to generate client certificates and coupons`);
314+
});

examples/client-mcp-server.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* Provides tools for certificate generation, coupon creation and communication with other servers.
55
*/
66
import { McpServer } from '../src/server/mcp.js';
7-
import { WebSocketTransport } from '../src/shared/transport.js';
7+
import { WebSocketTransport } from './shared/websocket-transport.js';
88
import { WebSocketServer } from 'ws';
99
import { createServer, IncomingMessage, ServerResponse } from 'http';
1010
import { z } from 'zod';
@@ -16,7 +16,7 @@ import { Client } from '../src/client/index.js';
1616
import WebSocket from 'ws';
1717

1818
// Import coupon-related utilities
19-
import { generateCertificate } from '../src/coupon/sign.js';
19+
import { generateCertificate } from './shared/certificate-utils.js';
2020
import { createCoupon } from '../src/coupon/create.js';
2121
import { Certificate, Coupon } from '../src/types/coupon.js';
2222

examples/coupon-server.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/**
22
* Coupon Server Example
3-
* Demonstrates working with manual coupon processing.
3+
* Demonstrates working with maSo whatnual coupon processing.
44
* Handles coupon verification and storage in a typical server environment.
55
*/
66
import express from 'express';

0 commit comments

Comments
 (0)