@tokcalc/mcp-server 0.1.0 → 0.1.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 (192) hide show
  1. package/.zscripts/build.sh +175 -0
  2. package/.zscripts/database-runtime-build.sh +33 -0
  3. package/.zscripts/dev.pid +1 -0
  4. package/.zscripts/dev.sh +154 -0
  5. package/.zscripts/mini-services-build.sh +78 -0
  6. package/.zscripts/mini-services-install.sh +65 -0
  7. package/.zscripts/mini-services-start.sh +123 -0
  8. package/.zscripts/python-runtime-build.sh +120 -0
  9. package/.zscripts/start.sh +145 -0
  10. package/CAPACITY_STUDY.md +283 -0
  11. package/CODE_OF_CONDUCT.md +55 -0
  12. package/CONTRIBUTING.md +177 -0
  13. package/Caddyfile +23 -0
  14. package/LICENSE +204 -0
  15. package/README.md +406 -86
  16. package/bun.lock +1965 -0
  17. package/components.json +21 -0
  18. package/db/custom.db +0 -0
  19. package/download/README.md +1 -0
  20. package/download/tokcalc-dark-calculator.png +0 -0
  21. package/download/tokcalc-dark-default.png +0 -0
  22. package/download/tokcalc-demo.webm +0 -0
  23. package/download/tokcalc-github-link.png +0 -0
  24. package/download/tokcalc-hydration-fixed.png +0 -0
  25. package/download/tokcalc-issue-resolved.png +0 -0
  26. package/download/tokcalc-light-mode.png +0 -0
  27. package/download/tokcalc-light-reference.png +0 -0
  28. package/download/tokcalc-long-context-qwen.png +0 -0
  29. package/download/tokcalc-long-context.png +0 -0
  30. package/download/tokcalc-og-image-preview.png +0 -0
  31. package/download/tokcalc-phase2-3.png +0 -0
  32. package/download/tokcalc-plain-english.png +0 -0
  33. package/download/tokcalc-preview.png +0 -0
  34. package/download/tokcalc-share-bvb.png +0 -0
  35. package/download/tokcalc-share-feature.png +0 -0
  36. package/download/tokcalc-tab-build-vs-buy.png +0 -0
  37. package/download/tokcalc-tab-calculator.png +0 -0
  38. package/download/tokcalc-tab-reference.png +0 -0
  39. package/eslint.config.mjs +50 -0
  40. package/examples/websocket/frontend.tsx +196 -0
  41. package/examples/websocket/server.ts +138 -0
  42. package/mini-services/.gitkeep +0 -0
  43. package/mini-services/mcp-server/README.md +86 -0
  44. package/mini-services/mcp-server/bun.lock +202 -0
  45. package/mini-services/mcp-server/index.ts +504 -0
  46. package/mini-services/mcp-server/package.json +40 -0
  47. package/next.config.ts +12 -0
  48. package/package.json +90 -32
  49. package/postcss.config.mjs +5 -0
  50. package/prisma/schema.prisma +32 -0
  51. package/public/google6f58ca6be85fa903.html +1 -0
  52. package/public/logo.svg +29 -0
  53. package/public/manifest.json +51 -0
  54. package/public/og-icon-256.png +0 -0
  55. package/public/og.png +0 -0
  56. package/public/robots.txt +25 -0
  57. package/public/sitemap.xml +23 -0
  58. package/public/tokcalc-demo.gif +0 -0
  59. package/scripts/og-template.html +120 -0
  60. package/scripts/render-og.mjs +43 -0
  61. package/server.json +21 -0
  62. package/src/app/api/pricing/aws/route.ts +186 -0
  63. package/src/app/api/pricing/azure/route.ts +168 -0
  64. package/src/app/api/pricing/gcp/route.ts +230 -0
  65. package/src/app/api/pricing/vast-ai/route.ts +164 -0
  66. package/src/app/api/route.ts +5 -0
  67. package/src/app/compare/h100-vs-h200/layout.tsx +30 -0
  68. package/src/app/compare/h100-vs-h200/page.tsx +328 -0
  69. package/src/app/globals.css +122 -0
  70. package/src/app/layout.tsx +276 -0
  71. package/src/app/page.tsx +2670 -0
  72. package/src/components/azure-live-pricing.tsx +185 -0
  73. package/src/components/benchmark-import.tsx +340 -0
  74. package/src/components/confidence-badge.tsx +116 -0
  75. package/src/components/live-pricing-comparison.tsx +241 -0
  76. package/src/components/theme-provider.tsx +11 -0
  77. package/src/components/theme-toggle.tsx +55 -0
  78. package/src/components/ui/accordion.tsx +66 -0
  79. package/src/components/ui/alert-dialog.tsx +157 -0
  80. package/src/components/ui/alert.tsx +66 -0
  81. package/src/components/ui/aspect-ratio.tsx +11 -0
  82. package/src/components/ui/avatar.tsx +53 -0
  83. package/src/components/ui/badge.tsx +46 -0
  84. package/src/components/ui/breadcrumb.tsx +109 -0
  85. package/src/components/ui/button.tsx +59 -0
  86. package/src/components/ui/calendar.tsx +213 -0
  87. package/src/components/ui/card.tsx +92 -0
  88. package/src/components/ui/carousel.tsx +241 -0
  89. package/src/components/ui/chart.tsx +353 -0
  90. package/src/components/ui/checkbox.tsx +32 -0
  91. package/src/components/ui/collapsible.tsx +33 -0
  92. package/src/components/ui/command.tsx +184 -0
  93. package/src/components/ui/context-menu.tsx +252 -0
  94. package/src/components/ui/dialog.tsx +143 -0
  95. package/src/components/ui/drawer.tsx +135 -0
  96. package/src/components/ui/dropdown-menu.tsx +257 -0
  97. package/src/components/ui/form.tsx +167 -0
  98. package/src/components/ui/hover-card.tsx +44 -0
  99. package/src/components/ui/input-otp.tsx +77 -0
  100. package/src/components/ui/input.tsx +21 -0
  101. package/src/components/ui/label.tsx +24 -0
  102. package/src/components/ui/menubar.tsx +276 -0
  103. package/src/components/ui/navigation-menu.tsx +168 -0
  104. package/src/components/ui/pagination.tsx +127 -0
  105. package/src/components/ui/popover.tsx +48 -0
  106. package/src/components/ui/progress.tsx +31 -0
  107. package/src/components/ui/radio-group.tsx +45 -0
  108. package/src/components/ui/resizable.tsx +56 -0
  109. package/src/components/ui/scroll-area.tsx +58 -0
  110. package/src/components/ui/select.tsx +185 -0
  111. package/src/components/ui/separator.tsx +28 -0
  112. package/src/components/ui/sheet.tsx +139 -0
  113. package/src/components/ui/sidebar.tsx +726 -0
  114. package/src/components/ui/skeleton.tsx +13 -0
  115. package/src/components/ui/slider.tsx +63 -0
  116. package/src/components/ui/sonner.tsx +25 -0
  117. package/src/components/ui/switch.tsx +31 -0
  118. package/src/components/ui/table.tsx +116 -0
  119. package/src/components/ui/tabs.tsx +66 -0
  120. package/src/components/ui/textarea.tsx +18 -0
  121. package/src/components/ui/toast.tsx +129 -0
  122. package/src/components/ui/toaster.tsx +35 -0
  123. package/src/components/ui/toggle-group.tsx +73 -0
  124. package/src/components/ui/toggle.tsx +47 -0
  125. package/src/components/ui/tooltip.tsx +61 -0
  126. package/src/components/vast-ai-live-pricing.tsx +176 -0
  127. package/src/hooks/use-mobile.ts +19 -0
  128. package/src/hooks/use-toast.ts +194 -0
  129. package/src/lib/benchmark-parser-sglang.ts +150 -0
  130. package/src/lib/benchmark-parser-tokcalc.ts +247 -0
  131. package/src/lib/benchmark-parser-trtllm.ts +152 -0
  132. package/src/lib/benchmark-parser-vllm.ts +198 -0
  133. package/src/lib/benchmark-schema.ts +263 -0
  134. package/src/lib/db.ts +13 -0
  135. package/src/lib/engine-presets.ts +183 -0
  136. package/src/lib/price-schema.ts +141 -0
  137. package/src/lib/token-calc.ts +808 -0
  138. package/src/lib/track.ts +31 -0
  139. package/src/lib/url-state.ts +256 -0
  140. package/src/lib/utils.ts +6 -0
  141. package/tailwind.config.ts +64 -0
  142. package/tests/database-runtime-build.sh +75 -0
  143. package/tests/python-runtime-build.sh +64 -0
  144. package/tests/python-runtime-container.sh +31 -0
  145. package/tool-results/bash_1789888171144_2c5381860539.txt +161 -0
  146. package/tool-results/bash_1789888175925_49c53ba3c61b.txt +191 -0
  147. package/tool-results/bash_1789888181202_49c53ba3c61b.txt +191 -0
  148. package/tool-results/bash_1789888195219_4a86a5c91411.txt +200 -0
  149. package/tool-results/bash_1789888203128_6cca13c71b47.txt +199 -0
  150. package/tool-results/bash_1789929256963_2a52aff0d0a8.txt +160 -0
  151. package/tool-results/read_1789888151021_69f58eec6a5b.txt +653 -0
  152. package/tool-results/read_1789888153837_1d3a8bfc2a94.txt +653 -0
  153. package/tool-results/read_1789888163087_ccc406d47505.txt +122 -0
  154. package/tool-results/read_1789888167347_67d1d7c9830a.txt +122 -0
  155. package/tool-results/read_1789929252529_d90e8f383a25.txt +285 -0
  156. package/tsconfig.json +42 -0
  157. package/upload/Pasted Content_1789887800864.txt +652 -0
  158. package/upload/Pasted Content_1789887909561.txt +652 -0
  159. package/upload/Pasted Content_1789887918428.txt +652 -0
  160. package/upload/Pasted Content_1789887959420.txt +652 -0
  161. package/upload/Pasted Content_1789888020485.txt +652 -0
  162. package/upload/Pasted Content_1789888058079.txt +652 -0
  163. package/upload/Pasted Content_1789888885033.txt +686 -0
  164. package/upload/Pasted Content_1789928912741.txt +285 -0
  165. package/upload/Pasted Content_1789928938402.txt +285 -0
  166. package/upload/Pasted Content_1789929160389.txt +285 -0
  167. package/upload/Pasted Content_1789929176660.txt +285 -0
  168. package/upload/issue_vision.json +28 -0
  169. package/upload/pasted_image_1789883175209.png +0 -0
  170. package/upload/pasted_image_1789899056690.png +0 -0
  171. package/upload/pasted_image_1789900371483.png +0 -0
  172. package/upload/pasted_image_1789900472823.png +0 -0
  173. package/upload/pasted_image_1789900490374.png +0 -0
  174. package/upload/pasted_image_1789900585552.png +0 -0
  175. package/upload/pasted_image_1789900606519.png +0 -0
  176. package/upload/pasted_image_1789901598705.png +0 -0
  177. package/upload/pasted_image_1789901613545.png +0 -0
  178. package/upload/pasted_image_1789978382674.png +0 -0
  179. package/upload/pasted_image_1789978392749.png +0 -0
  180. package/upload/pasted_image_1789978474879.png +0 -0
  181. package/upload/pasted_image_1789978523652.png +0 -0
  182. package/upload/pasted_image_1789984219089.png +0 -0
  183. package/upload/pasted_image_1789984491896.png +0 -0
  184. package/upload/pasted_image_1789985017950.png +0 -0
  185. package/upload/pasted_image_1789985036765.png +0 -0
  186. package/upload/pasted_image_1789985049848.png +0 -0
  187. package/upload/pasted_image_1790002427833.png +0 -0
  188. package/upload/pasted_image_1790002659944.png +0 -0
  189. package/upload/pasted_image_1790037038476.png +0 -0
  190. package/upload/screenshot_analysis.json +28 -0
  191. package/upload/vision_output.json +28 -0
  192. package/dist/index.js +0 -21014
@@ -0,0 +1,21 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema.json",
3
+ "style": "new-york",
4
+ "rsc": true,
5
+ "tsx": true,
6
+ "tailwind": {
7
+ "config": "",
8
+ "css": "src/app/globals.css",
9
+ "baseColor": "neutral",
10
+ "cssVariables": true,
11
+ "prefix": ""
12
+ },
13
+ "aliases": {
14
+ "components": "@/components",
15
+ "utils": "@/lib/utils",
16
+ "ui": "@/components/ui",
17
+ "lib": "@/lib",
18
+ "hooks": "@/hooks"
19
+ },
20
+ "iconLibrary": "lucide"
21
+ }
package/db/custom.db ADDED
Binary file
@@ -0,0 +1 @@
1
+ Here are all the generated files.
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,50 @@
1
+ import nextCoreWebVitals from "eslint-config-next/core-web-vitals";
2
+ import nextTypescript from "eslint-config-next/typescript";
3
+ import { dirname } from "path";
4
+ import { fileURLToPath } from "url";
5
+
6
+ const __filename = fileURLToPath(import.meta.url);
7
+ const __dirname = dirname(__filename);
8
+
9
+ const eslintConfig = [...nextCoreWebVitals, ...nextTypescript, {
10
+ rules: {
11
+ // TypeScript rules
12
+ "@typescript-eslint/no-explicit-any": "off",
13
+ "@typescript-eslint/no-unused-vars": "off",
14
+ "@typescript-eslint/no-non-null-assertion": "off",
15
+ "@typescript-eslint/ban-ts-comment": "off",
16
+ "@typescript-eslint/prefer-as-const": "off",
17
+ "@typescript-eslint/no-unused-disable-directive": "off",
18
+
19
+ // React rules
20
+ "react-hooks/exhaustive-deps": "off",
21
+ "react-hooks/purity": "off",
22
+ "react/no-unescaped-entities": "off",
23
+ "react/display-name": "off",
24
+ "react/prop-types": "off",
25
+ "react-compiler/react-compiler": "off",
26
+
27
+ // Next.js rules
28
+ "@next/next/no-img-element": "off",
29
+ "@next/next/no-html-link-for-pages": "off",
30
+
31
+ // General JavaScript rules
32
+ "prefer-const": "off",
33
+ "no-unused-vars": "off",
34
+ "no-console": "off",
35
+ "no-debugger": "off",
36
+ "no-empty": "off",
37
+ "no-irregular-whitespace": "off",
38
+ "no-case-declarations": "off",
39
+ "no-fallthrough": "off",
40
+ "no-mixed-spaces-and-tabs": "off",
41
+ "no-redeclare": "off",
42
+ "no-undef": "off",
43
+ "no-unreachable": "off",
44
+ "no-useless-escape": "off",
45
+ },
46
+ }, {
47
+ ignores: ["node_modules/**", ".next/**", "out/**", "build/**", "next-env.d.ts", "examples/**", "skills", "mini-services/mcp-server/dist/**"]
48
+ }];
49
+
50
+ export default eslintConfig;
@@ -0,0 +1,196 @@
1
+ 'use client';
2
+
3
+ import { useEffect, useState } from 'react';
4
+ import { io } from 'socket.io-client';
5
+ import { Button } from '@/components/ui/button';
6
+ import { Input } from '@/components/ui/input';
7
+ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
8
+ import { ScrollArea } from '@/components/ui/scroll-area';
9
+
10
+ type User = {
11
+ id: string;
12
+ username: string;
13
+ }
14
+
15
+ type Message = {
16
+ id: string;
17
+ username: string;
18
+ content: string;
19
+ timestamp: Date | string;
20
+ type: 'user' | 'system';
21
+ }
22
+
23
+ export default function SocketDemo() {
24
+ const [messages, setMessages] = useState<Message[]>([]);
25
+ const [inputMessage, setInputMessage] = useState('');
26
+ const [username, setUsername] = useState('');
27
+ const [isUsernameSet, setIsUsernameSet] = useState(false);
28
+ const [socket, setSocket] = useState<any>(null);
29
+ const [isConnected, setIsConnected] = useState(false);
30
+ const [users, setUsers] = useState<User[]>([]);
31
+
32
+ useEffect(() => {
33
+ // Connect to websocket server
34
+ // Never use PORT in the URL, alyways use XTransformPort
35
+ // DO NOT change the path, it is used by Caddy to forward the request to the correct port
36
+ const socketInstance = io('/?XTransformPort=3003', {
37
+ transports: ['websocket', 'polling'],
38
+ forceNew: true,
39
+ reconnection: true,
40
+ reconnectionAttempts: 5,
41
+ reconnectionDelay: 1000,
42
+ timeout: 10000
43
+ })
44
+
45
+ setSocket(socketInstance);
46
+
47
+ socketInstance.on('connect', () => {
48
+ setIsConnected(true);
49
+ });
50
+
51
+ socketInstance.on('disconnect', () => {
52
+ setIsConnected(false);
53
+ });
54
+
55
+ socketInstance.on('message', (msg: Message) => {
56
+ setMessages(prev => [...prev, msg]);
57
+ });
58
+
59
+ socketInstance.on('user-joined', (data: { user: User; message: Message }) => {
60
+ setMessages(prev => [...prev, data.message]);
61
+ setUsers(prev => {
62
+ if (!prev.find(u => u.id === data.user.id)) {
63
+ return [...prev, data.user];
64
+ }
65
+ return prev;
66
+ });
67
+ });
68
+
69
+ socketInstance.on('user-left', (data: { user: User; message: Message }) => {
70
+ setMessages(prev => [...prev, data.message]);
71
+ setUsers(prev => prev.filter(u => u.id !== data.user.id));
72
+ });
73
+
74
+ socketInstance.on('users-list', (data: { users: User[] }) => {
75
+ setUsers(data.users);
76
+ });
77
+
78
+ return () => {
79
+ socketInstance.disconnect();
80
+ };
81
+ }, []);
82
+
83
+ const handleJoin = () => {
84
+ if (socket && username.trim() && isConnected) {
85
+ socket.emit('join', { username: username.trim() });
86
+ setIsUsernameSet(true);
87
+ }
88
+ };
89
+
90
+ const sendMessage = () => {
91
+ if (socket && inputMessage.trim() && username.trim()) {
92
+ socket.emit('message', {
93
+ content: inputMessage.trim(),
94
+ username: username.trim()
95
+ });
96
+ setInputMessage('');
97
+ }
98
+ };
99
+
100
+ const handleKeyPress = (e: React.KeyboardEvent) => {
101
+ if (e.key === 'Enter') {
102
+ sendMessage();
103
+ }
104
+ };
105
+
106
+ return (
107
+ <div className="container mx-auto p-4 max-w-2xl">
108
+ <Card>
109
+ <CardHeader>
110
+ <CardTitle className="flex items-center justify-between">
111
+ WebSocket Demo
112
+ <span className={`text-sm px-2 py-1 rounded ${isConnected ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'}`}>
113
+ {isConnected ? 'Connected' : 'Disconnected'}
114
+ </span>
115
+ </CardTitle>
116
+ </CardHeader>
117
+ <CardContent className="space-y-4">
118
+ {!isUsernameSet ? (
119
+ <div className="space-y-2">
120
+ <Input
121
+ value={username}
122
+ onChange={(e) => setUsername(e.target.value)}
123
+ onKeyPress={(e) => {
124
+ if (e.key === 'Enter') {
125
+ handleJoin();
126
+ }
127
+ }}
128
+ placeholder="Enter your username..."
129
+ disabled={!isConnected}
130
+ className="flex-1"
131
+ />
132
+ <Button
133
+ onClick={handleJoin}
134
+ disabled={!isConnected || !username.trim()}
135
+ className="w-full"
136
+ >
137
+ Join Chat
138
+ </Button>
139
+ </div>
140
+ ) : (
141
+ <>
142
+ <ScrollArea className="h-80 w-full border rounded-md p-4">
143
+ <div className="space-y-2">
144
+ {messages.length === 0 ? (
145
+ <p className="text-gray-500 text-center">No messages yet</p>
146
+ ) : (
147
+ messages.map((msg) => (
148
+ <div key={msg.id} className="border-b pb-2 last:border-b-0">
149
+ <div className="flex justify-between items-start">
150
+ <div className="flex-1">
151
+ <p className={`text-sm font-medium ${msg.type === 'system'
152
+ ? 'text-blue-600 italic'
153
+ : 'text-gray-700'
154
+ }`}>
155
+ {msg.username}
156
+ </p>
157
+ <p className={`${msg.type === 'system'
158
+ ? 'text-blue-500 italic'
159
+ : 'text-gray-900'
160
+ }`}>
161
+ {msg.content}
162
+ </p>
163
+ </div>
164
+ <span className="text-xs text-gray-500">
165
+ {new Date(msg.timestamp).toLocaleTimeString()}
166
+ </span>
167
+ </div>
168
+ </div>
169
+ ))
170
+ )}
171
+ </div>
172
+ </ScrollArea>
173
+
174
+ <div className="flex space-x-2">
175
+ <Input
176
+ value={inputMessage}
177
+ onChange={(e) => setInputMessage(e.target.value)}
178
+ onKeyPress={handleKeyPress}
179
+ placeholder="Type a message..."
180
+ disabled={!isConnected}
181
+ className="flex-1"
182
+ />
183
+ <Button
184
+ onClick={sendMessage}
185
+ disabled={!isConnected || !inputMessage.trim()}
186
+ >
187
+ Send
188
+ </Button>
189
+ </div>
190
+ </>
191
+ )}
192
+ </CardContent>
193
+ </Card>
194
+ </div>
195
+ );
196
+ }
@@ -0,0 +1,138 @@
1
+ import { createServer } from 'http'
2
+ import { Server } from 'socket.io'
3
+
4
+ const httpServer = createServer()
5
+ const io = new Server(httpServer, {
6
+ // DO NOT change the path, it is used by Caddy to forward the request to the correct port
7
+ path: '/',
8
+ cors: {
9
+ origin: "*",
10
+ methods: ["GET", "POST"]
11
+ },
12
+ pingTimeout: 60000,
13
+ pingInterval: 25000,
14
+ })
15
+
16
+ interface User {
17
+ id: string
18
+ username: string
19
+ }
20
+
21
+ interface Message {
22
+ id: string
23
+ username: string
24
+ content: string
25
+ timestamp: Date
26
+ type: 'user' | 'system'
27
+ }
28
+
29
+ const users = new Map<string, User>()
30
+
31
+ const generateMessageId = () => Math.random().toString(36).substr(2, 9)
32
+
33
+ const createSystemMessage = (content: string): Message => ({
34
+ id: generateMessageId(),
35
+ username: 'System',
36
+ content,
37
+ timestamp: new Date(),
38
+ type: 'system'
39
+ })
40
+
41
+ const createUserMessage = (username: string, content: string): Message => ({
42
+ id: generateMessageId(),
43
+ username,
44
+ content,
45
+ timestamp: new Date(),
46
+ type: 'user'
47
+ })
48
+
49
+ io.on('connection', (socket) => {
50
+ console.log(`User connected: ${socket.id}`)
51
+
52
+ // Add test event handler
53
+ socket.on('test', (data) => {
54
+ console.log('Received test message:', data)
55
+ socket.emit('test-response', {
56
+ message: 'Server received test message',
57
+ data: data,
58
+ timestamp: new Date().toISOString()
59
+ })
60
+ })
61
+
62
+ socket.on('join', (data: { username: string }) => {
63
+ const { username } = data
64
+
65
+ // Create user object
66
+ const user: User = {
67
+ id: socket.id,
68
+ username
69
+ }
70
+
71
+ // Add to user list
72
+ users.set(socket.id, user)
73
+
74
+ // Send join message to all users
75
+ const joinMessage = createSystemMessage(`${username} joined the chat room`)
76
+ io.emit('user-joined', { user, message: joinMessage })
77
+
78
+ // Send current user list to new user
79
+ const usersList = Array.from(users.values())
80
+ socket.emit('users-list', { users: usersList })
81
+
82
+ console.log(`${username} joined the chat room, current online users: ${users.size}`)
83
+ })
84
+
85
+ socket.on('message', (data: { content: string; username: string }) => {
86
+ const { content, username } = data
87
+ const user = users.get(socket.id)
88
+
89
+ if (user && user.username === username) {
90
+ const message = createUserMessage(username, content)
91
+ io.emit('message', message)
92
+ console.log(`${username}: ${content}`)
93
+ }
94
+ })
95
+
96
+ socket.on('disconnect', () => {
97
+ const user = users.get(socket.id)
98
+
99
+ if (user) {
100
+ // Remove from user list
101
+ users.delete(socket.id)
102
+
103
+ // Send leave message to all users
104
+ const leaveMessage = createSystemMessage(`${user.username} left the chat room`)
105
+ io.emit('user-left', { user: { id: socket.id, username: user.username }, message: leaveMessage })
106
+
107
+ console.log(`${user.username} left the chat room, current online users: ${users.size}`)
108
+ } else {
109
+ console.log(`User disconnected: ${socket.id}`)
110
+ }
111
+ })
112
+
113
+ socket.on('error', (error) => {
114
+ console.error(`Socket error (${socket.id}):`, error)
115
+ })
116
+ })
117
+
118
+ const PORT = 3003
119
+ httpServer.listen(PORT, () => {
120
+ console.log(`WebSocket server running on port ${PORT}`)
121
+ })
122
+
123
+ // Graceful shutdown
124
+ process.on('SIGTERM', () => {
125
+ console.log('Received SIGTERM signal, shutting down server...')
126
+ httpServer.close(() => {
127
+ console.log('WebSocket server closed')
128
+ process.exit(0)
129
+ })
130
+ })
131
+
132
+ process.on('SIGINT', () => {
133
+ console.log('Received SIGINT signal, shutting down server...')
134
+ httpServer.close(() => {
135
+ console.log('WebSocket server closed')
136
+ process.exit(0)
137
+ })
138
+ })
File without changes
@@ -0,0 +1,86 @@
1
+ # @tokcalc/mcp-server
2
+
3
+ **LLM serving capacity planner for AI agents.**
4
+
5
+ Open-source MCP (Model Context Protocol) server that lets AI agents (Cursor, Claude Desktop, Cline) estimate LLM serving capacity — model fit, KV cache, throughput, latency, multi-GPU topology, and cost.
6
+
7
+ ## Tools
8
+
9
+ | Tool | What it does |
10
+ |---|---|
11
+ | `estimate_capacity` | VRAM/KV/throughput/latency/cost for one config |
12
+ | `compare_gpus` | Ranked GPU comparison for one workload |
13
+ | `recommend_topology` | TP/CP topology recommendation |
14
+ | `estimate_api_vs_self_host` | Break-even analysis |
15
+ | `list_models` | Discover supported model IDs (35 models) |
16
+ | `list_gpus` | Discover supported GPU IDs (30 GPUs) |
17
+
18
+ All tools are **read-only** — no side effects, no cloud credentials, no deployments.
19
+
20
+ ## Install
21
+
22
+ ### Claude Desktop
23
+
24
+ Add to `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows):
25
+
26
+ ```json
27
+ {
28
+ "mcpServers": {
29
+ "tokcalc": {
30
+ "command": "npx",
31
+ "args": ["-y", "@tokcalc/mcp-server"]
32
+ }
33
+ }
34
+ }
35
+ ```
36
+
37
+ Restart Claude Desktop. The `tokcalc` server will be available as an MCP tool source.
38
+
39
+ ### Cursor
40
+
41
+ Add to `.cursor/mcp.json` in your project:
42
+
43
+ ```json
44
+ {
45
+ "mcpServers": {
46
+ "tokcalc": {
47
+ "command": "npx",
48
+ "args": ["-y", "@tokcalc/mcp-server"]
49
+ }
50
+ }
51
+ }
52
+ ```
53
+
54
+ ### Cline (VS Code)
55
+
56
+ Add the same config to Cline's MCP settings.
57
+
58
+ ## Example prompts
59
+
60
+ Ask your AI agent:
61
+
62
+ > "I need to serve Llama 3.3 70B at 32K context for 50 concurrent users. What GPU topology do you recommend, and how much will it cost per month?"
63
+
64
+ > "Compare H100 vs H200 for serving Qwen 2.5 72B in FP8 with continuous batching."
65
+
66
+ > "At what daily request volume does self-hosting Llama 70B on H200 beat the GPT-4o API?"
67
+
68
+ The agent calls `list_models` → `list_gpus` → `recommend_topology` → `estimate_capacity` and returns a structured plan with throughput ranges, latency, VRAM, cost, and confidence levels.
69
+
70
+ ## Supported models (35)
71
+
72
+ Llama 3/3.1/3.3, Llama 4 Scout/Maverick, Mistral 7B, Mixtral 8x7B/8x22B, Mistral Large 3, Pixtral 12B, Codestral, Qwen 2/2.5/3 (incl. MoE + VL), DeepSeek V3/R1/Coder V2, Gemma 2, Phi-3/4, SmolLM2, Falcon 3, OLMo 2, BGE-M3, E5, GTE.
73
+
74
+ ## Supported GPUs (30)
75
+
76
+ NVIDIA H100/H200/B200/B300, A100, L40S, L4, T4, V100, RTX 4090/3090/5090, RTX PRO 6000 Blackwell, AMD MI300X/MI325X, Intel Gaudi 3, Google TPU v5p/Trillium, Groq LPU, Cerebras CS-3, Apple M2/M3/M4 Ultra/Max.
77
+
78
+ ## License
79
+
80
+ Apache 2.0 — same as the main tokcalc project.
81
+
82
+ ## Links
83
+
84
+ - [Live calculator](https://tokcalc.vercel.app)
85
+ - [GitHub](https://github.com/stevecrates489-commits/tokcalc)
86
+ - [CONTRIBUTING](https://github.com/stevecrates489-commits/tokcalc/blob/main/CONTRIBUTING.md)