@tokcalc/mcp-server 0.1.1 → 0.1.3

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 +92 -35
  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,5 @@
1
+ const config = {
2
+ plugins: ["@tailwindcss/postcss"],
3
+ };
4
+
5
+ export default config;
@@ -0,0 +1,32 @@
1
+ // This is your Prisma schema file,
2
+ // learn more about it in the docs: https://pris.ly/d/prisma-schema
3
+
4
+ // Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?
5
+ // Try Prisma Accelerate: https://pris.ly/cli/accelerate-init
6
+
7
+ generator client {
8
+ provider = "prisma-client-js"
9
+ }
10
+
11
+ datasource db {
12
+ provider = "sqlite"
13
+ url = env("DATABASE_URL")
14
+ }
15
+
16
+ model User {
17
+ id String @id @default(cuid())
18
+ email String @unique
19
+ name String?
20
+ createdAt DateTime @default(now())
21
+ updatedAt DateTime @updatedAt
22
+ }
23
+
24
+ model Post {
25
+ id String @id @default(cuid())
26
+ title String
27
+ content String?
28
+ published Boolean @default(false)
29
+ authorId String
30
+ createdAt DateTime @default(now())
31
+ updatedAt DateTime @updatedAt
32
+ }
@@ -0,0 +1 @@
1
+ google-site-verification: google6f58ca6be85fa903.html
@@ -0,0 +1,29 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
3
+ viewBox="0 0 30 30" style="enable-background:new 0 0 30 30;" xml:space="preserve">
4
+ <defs>
5
+ <style type="text/css">
6
+ .st194{fill:#2D2D2D;stroke:#FFFFFF;stroke-width:0.6317;stroke-miterlimit:10;}
7
+ .st23{fill:#FFFFFF;}
8
+
9
+ .z-breathe {
10
+ animation: breathe 2.5s ease-in-out infinite;
11
+ }
12
+
13
+ @keyframes breathe {
14
+ 0%, 100% { opacity: 0.7; }
15
+ 50% { opacity: 1; }
16
+ }
17
+ </style>
18
+ </defs>
19
+
20
+ <g>
21
+ <path class="st194" d="M24.51,28.51H5.49c-2.21,0-4-1.79-4-4V5.49c0-2.21,1.79-4,4-4h19.03c2.21,0,4,1.79,4,4v19.03
22
+ C28.51,26.72,26.72,28.51,24.51,28.51z"/>
23
+ <g class="z-breathe">
24
+ <path class="st23" d="M15.47,7.1l-1.3,1.85c-0.2,0.29-0.54,0.47-0.9,0.47h-7.1V7.09C6.16,7.1,15.47,7.1,15.47,7.1z"/>
25
+ <polygon class="st23" points="24.3,7.1 13.14,22.91 5.7,22.91 16.86,7.1"/>
26
+ <path class="st23" d="M14.53,22.91l1.31-1.86c0.2-0.29,0.54-0.47,0.9-0.47h7.09v2.33H14.53z"/>
27
+ </g>
28
+ </g>
29
+ </svg>
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "tokcalc — LLM serving capacity planner",
3
+ "short_name": "tokcalc",
4
+ "description": "Plan your LLM deployment before you rent the GPUs. Open-source capacity planner: model fit, KV-cache, throughput, latency, multi-GPU scaling, cost — with transparent formulas.",
5
+ "start_url": "/",
6
+ "display": "standalone",
7
+ "background_color": "#0a0e0a",
8
+ "theme_color": "#0a0e0a",
9
+ "orientation": "any",
10
+ "categories": ["developer", "productivity", "utilities"],
11
+ "icons": [
12
+ {
13
+ "src": "/og-icon-256.png",
14
+ "sizes": "192x192",
15
+ "type": "image/png",
16
+ "purpose": "any"
17
+ },
18
+ {
19
+ "src": "/og-icon-256.png",
20
+ "sizes": "256x256",
21
+ "type": "image/png",
22
+ "purpose": "any"
23
+ },
24
+ {
25
+ "src": "/og-icon-256.png",
26
+ "sizes": "512x512",
27
+ "type": "image/png",
28
+ "purpose": "any maskable"
29
+ }
30
+ ],
31
+ "shortcuts": [
32
+ {
33
+ "name": "Calculator",
34
+ "short_name": "Calc",
35
+ "description": "Open the LLM capacity calculator",
36
+ "url": "/#t=calculator"
37
+ },
38
+ {
39
+ "name": "Build vs Buy",
40
+ "short_name": "BvB",
41
+ "description": "Compare self-hosting to API pricing",
42
+ "url": "/#t=build-vs-buy"
43
+ },
44
+ {
45
+ "name": "Reference catalog",
46
+ "short_name": "Ref",
47
+ "description": "Browse all models, GPUs, and quantization formats",
48
+ "url": "/#t=reference"
49
+ }
50
+ ]
51
+ }
Binary file
package/public/og.png ADDED
Binary file
@@ -0,0 +1,25 @@
1
+ # Crawl everything
2
+ User-agent: *
3
+ Allow: /
4
+
5
+ # Block the dev API route from indexing
6
+ Disallow: /api/
7
+
8
+ # Explicitly allow social preview crawlers (redundant, but explicit)
9
+ User-agent: Twitterbot
10
+ Allow: /
11
+
12
+ User-agent: facebookexternalhit
13
+ Allow: /
14
+
15
+ User-agent: LinkedInBot
16
+ Allow: /
17
+
18
+ User-agent: Discordbot
19
+ Allow: /
20
+
21
+ User-agent: Slackbot
22
+ Allow: /
23
+
24
+ # Sitemap
25
+ Sitemap: https://tokcalc.vercel.app/sitemap.xml
@@ -0,0 +1,23 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
3
+ <url>
4
+ <loc>https://tokcalc.vercel.app/</loc>
5
+ <changefreq>weekly</changefreq>
6
+ <priority>1.0</priority>
7
+ </url>
8
+ <url>
9
+ <loc>https://tokcalc.vercel.app/compare/h100-vs-h200</loc>
10
+ <changefreq>monthly</changefreq>
11
+ <priority>0.9</priority>
12
+ </url>
13
+ <url>
14
+ <loc>https://tokcalc.vercel.app/compare/gguf-q4-k-m-vs-q5-k-m</loc>
15
+ <changefreq>monthly</changefreq>
16
+ <priority>0.8</priority>
17
+ </url>
18
+ <url>
19
+ <loc>https://tokcalc.vercel.app/self-host-vs-openai-api</loc>
20
+ <changefreq>monthly</changefreq>
21
+ <priority>0.8</priority>
22
+ </url>
23
+ </urlset>
Binary file
@@ -0,0 +1,120 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <style>
6
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@600;700;800;900&family=JetBrains+Mono:wght@500;700&display=swap');
7
+
8
+ * { margin: 0; padding: 0; box-sizing: border-box; }
9
+ html, body { width: 1200px; height: 630px; overflow: hidden; }
10
+ body {
11
+ font-family: 'Inter', -apple-system, sans-serif;
12
+ /* Solid dark background — no gradients (LinkedIn compression destroys subtle gradients).
13
+ Slight green tint stays on-brand without losing contrast. */
14
+ background: #0a0e0a;
15
+ color: #ffffff;
16
+ display: flex;
17
+ flex-direction: column;
18
+ padding: 80px 88px;
19
+ }
20
+
21
+ /* Top row: BIG logo + brand name. Logo is 96px (was 56px) — visible at 520px card */
22
+ .header { display: flex; align-items: center; gap: 24px; }
23
+ .logo-mark {
24
+ width: 96px; height: 96px; border-radius: 22px;
25
+ background: linear-gradient(135deg, #34d399 0%, #059669 100%);
26
+ display: flex; align-items: center; justify-content: center;
27
+ /* Subtle shadow only — no glow effect (gets destroyed by compression) */
28
+ box-shadow: 0 12px 40px rgba(16, 185, 129, 0.5);
29
+ }
30
+ .logo-mark svg { width: 52px; height: 52px; }
31
+ .brand {
32
+ font-size: 64px; font-weight: 900; letter-spacing: -0.05em;
33
+ color: #ffffff; line-height: 1;
34
+ }
35
+ .brand-sub {
36
+ font-size: 22px; color: #34d399; font-weight: 600;
37
+ margin-left: 20px; padding-left: 20px;
38
+ border-left: 2px solid rgba(52, 211, 153, 0.4);
39
+ line-height: 1.2;
40
+ }
41
+
42
+ /* Hero headline — MASSIVE. 88px so even at 520px card width it's ~38px = readable.
43
+ Pure white on near-black = max contrast, survives LinkedIn compression. */
44
+ .hero {
45
+ margin-top: 40px;
46
+ font-size: 88px;
47
+ font-weight: 900;
48
+ line-height: 1.05;
49
+ letter-spacing: -0.045em;
50
+ color: #ffffff;
51
+ }
52
+ .hero .accent {
53
+ color: #34d399; /* Saturated emerald, not subtle — survives compression */
54
+ }
55
+
56
+ /* Tagline — 28px (was 18px). Single line, no metric clutter. */
57
+ .tagline {
58
+ margin-top: 24px;
59
+ font-size: 28px;
60
+ color: #d4d4d8; /* Light gray, not dark gray — stays readable */
61
+ font-weight: 500;
62
+ line-height: 1.35;
63
+ max-width: 1000px;
64
+ }
65
+
66
+ /* Footer: bold URL. No chips, no metric pills — those get destroyed by compression.
67
+ Just one big readable brand line. */
68
+ .footer {
69
+ margin-top: auto;
70
+ display: flex;
71
+ align-items: center;
72
+ justify-content: space-between;
73
+ padding-top: 32px;
74
+ border-top: 2px solid rgba(52, 211, 153, 0.25);
75
+ }
76
+ .url {
77
+ font-family: 'JetBrains Mono', monospace;
78
+ font-size: 32px;
79
+ color: #34d399;
80
+ font-weight: 700;
81
+ letter-spacing: -0.02em;
82
+ }
83
+ .tagline-pill {
84
+ font-size: 24px;
85
+ color: #ffffff;
86
+ font-weight: 700;
87
+ background: rgba(52, 211, 153, 0.15);
88
+ border: 2px solid rgba(52, 211, 153, 0.4);
89
+ padding: 10px 20px;
90
+ border-radius: 8px;
91
+ }
92
+ </style>
93
+ </head>
94
+ <body>
95
+ <div class="header">
96
+ <div class="logo-mark">
97
+ <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
98
+ <path d="M12 2L12 6M12 18L12 22M4.93 4.93L7.76 7.76M16.24 16.24L19.07 19.07M2 12L6 12M18 12L22 12M4.93 19.07L7.76 16.24M16.24 7.76L19.07 4.93"
99
+ stroke="#042f1c" stroke-width="3" stroke-linecap="round"/>
100
+ <circle cx="12" cy="12" r="3" fill="#042f1c"/>
101
+ </svg>
102
+ </div>
103
+ <div class="brand">tokcalc</div>
104
+ <div class="brand-sub">LLM serving<br>capacity planner</div>
105
+ </div>
106
+
107
+ <div class="hero">
108
+ Plan your LLM deployment<br>
109
+ <span class="accent">before you rent the GPUs.</span>
110
+ </div>
111
+ <div class="tagline">
112
+ Open-source capacity planner: model fit, KV-cache, throughput, latency, multi-GPU scaling, and cost.
113
+ </div>
114
+
115
+ <div class="footer">
116
+ <div class="url">tokcalc.dev</div>
117
+ <div class="tagline-pill">Open source · 35 models · 30 GPUs</div>
118
+ </div>
119
+ </body>
120
+ </html>
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Render tokcalc OG image via Playwright snapshot of an HTML template.
3
+ * Outputs:
4
+ * - /home/z/my-project/public/og.png (2400x1260 @ 2x scale, for all social platforms)
5
+ *
6
+ * The og-icon-256.png is generated separately if needed (see git history for the
7
+ * original render script with the icon step).
8
+ *
9
+ * Usage: node scripts/render-og.mjs
10
+ */
11
+ import { chromium } from 'playwright';
12
+ import { fileURLToPath } from 'url';
13
+ import path from 'path';
14
+
15
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
16
+ const TEMPLATE = path.join(__dirname, 'og-template.html');
17
+ const OUT_OG = path.join(__dirname, '..', 'public', 'og.png');
18
+
19
+ async function main() {
20
+ const browser = await chromium.launch();
21
+ const context = await browser.newContext({
22
+ viewport: { width: 1200, height: 630 },
23
+ deviceScaleFactor: 2, // 2x for crisp retina display
24
+ });
25
+
26
+ const page = await context.newPage();
27
+ await page.goto('file://' + TEMPLATE, { waitUntil: 'networkidle' });
28
+ // Give web fonts a moment to settle (Inter / JetBrains Mono from Google Fonts CDN)
29
+ await page.waitForTimeout(1000);
30
+ await page.screenshot({
31
+ path: OUT_OG,
32
+ type: 'png',
33
+ clip: { x: 0, y: 0, width: 1200, height: 630 },
34
+ });
35
+ console.log('✓ OG image:', OUT_OG);
36
+
37
+ await browser.close();
38
+ }
39
+
40
+ main().catch(err => {
41
+ console.error(err);
42
+ process.exit(1);
43
+ });
package/server.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
3
+ "name": "io.github.stevecrates489-commits/tokcalc",
4
+ "title": "TokCalc MCP Server",
5
+ "description": "LLM capacity planning tools to estimate VRAM, compute, latency, and GPU topology for agents.",
6
+ "version": "0.1.3",
7
+ "repository": {
8
+ "url": "https://github.com/stevecrates489-commits/tokcalc.git",
9
+ "source": "github"
10
+ },
11
+ "packages": [
12
+ {
13
+ "registryType": "npm",
14
+ "identifier": "@tokcalc/mcp-server",
15
+ "version": "0.1.3",
16
+ "transport": {
17
+ "type": "stdio"
18
+ }
19
+ }
20
+ ]
21
+ }
@@ -0,0 +1,186 @@
1
+ /**
2
+ * AWS EC2 pricing proxy — uses public bulk pricing files (NO IAM credentials required).
3
+ *
4
+ * Per the user's research: AWS publishes public pricing JSON files at:
5
+ * https://pricing.us-east-1.amazonaws.com/offers/v1.0/aws/AmazonEC2/current/us-east-1/index.json
6
+ *
7
+ * This file is large (~50-100MB) but contains all EC2 pricing for us-east-1.
8
+ * This serverless function:
9
+ * 1. Fetches the bulk file (with 8s timeout — might fail on slow connections)
10
+ * 2. Streams-parses to extract only GPU instance types (p5, p4d, g5, g6, etc.)
11
+ * 3. Caches in memory for 24 hours
12
+ * 4. Returns clean JSON with per-GPU pricing
13
+ *
14
+ * No IAM credentials, no API key, no payment setup — just public data.
15
+ */
16
+
17
+ import { NextResponse } from "next/server";
18
+
19
+ // GPU instance type prefixes — filter the massive EC2 catalog
20
+ const GPU_INSTANCE_PREFIXES = ["p5", "p5e", "p6", "p4d", "p4de", "p3", "g5", "g6", "g4dn"];
21
+
22
+ // Known GPU counts per instance type
23
+ const GPU_COUNTS: Record<string, number> = {
24
+ "p5.48xlarge": 8, "p5e.48xlarge": 8, "p6.48xlarge": 8,
25
+ "p4d.24xlarge": 8, "p4de.24xlarge": 8,
26
+ "p3.2xlarge": 1, "p3.8xlarge": 4, "p3.16xlarge": 8,
27
+ "g5.xlarge": 1, "g5.2xlarge": 1, "g5.4xlarge": 1, "g5.8xlarge": 1, "g5.12xlarge": 4, "g5.16xlarge": 1, "g5.48xlarge": 8,
28
+ "g6.xlarge": 1, "g6.2xlarge": 1, "g6.4xlarge": 1, "g6.8xlarge": 1, "g6.12xlarge": 4, "g6.16xlarge": 1, "g6.48xlarge": 8,
29
+ "g4dn.xlarge": 1, "g4dn.2xlarge": 1, "g4dn.4xlarge": 1, "g4dn.8xlarge": 1, "g4dn.12xlarge": 4, "g4dn.16xlarge": 1,
30
+ };
31
+
32
+ // Known GPU model per instance type
33
+ const GPU_MODEL: Record<string, string> = {
34
+ p5: "H100", p5e: "H200", p6: "B200",
35
+ p4d: "A100 40GB", p4de: "A100 80GB",
36
+ p3: "V100",
37
+ g5: "A10G", g6: "L4", g4dn: "T4",
38
+ };
39
+
40
+ let cache: { data: CleanGpuPrice[]; timestamp: number } | null = null;
41
+ const CACHE_TTL_MS = 24 * 60 * 60 * 1000;
42
+
43
+ interface CleanGpuPrice {
44
+ gpu: string;
45
+ instanceType: string;
46
+ pricePerGpuHour: number;
47
+ instancePriceHour: number;
48
+ gpuCount: number;
49
+ region: string;
50
+ retrievedAt: string;
51
+ }
52
+
53
+ function getGpuModel(instanceType: string): string | null {
54
+ const prefix = Object.keys(GPU_MODEL).find((p) => instanceType.startsWith(p));
55
+ return prefix ? GPU_MODEL[prefix] : null;
56
+ }
57
+
58
+ function getGpuCount(instanceType: string): number {
59
+ return GPU_COUNTS[instanceType] || 1;
60
+ }
61
+
62
+ export async function GET() {
63
+ if (cache && Date.now() - cache.timestamp < CACHE_TTL_MS) {
64
+ return NextResponse.json({
65
+ source: "aws",
66
+ cached: true,
67
+ retrievedAt: new Date(cache.timestamp).toISOString(),
68
+ count: cache.data.length,
69
+ prices: cache.data,
70
+ });
71
+ }
72
+
73
+ try {
74
+ // Fetch the AWS EC2 bulk pricing file for us-east-1
75
+ // This is a large file (~50-100MB) but we only need GPU instances
76
+ const url = "https://pricing.us-east-1.amazonaws.com/offers/v1.0/aws/AmazonEC2/current/us-east-1/index.json";
77
+
78
+ const response = await fetch(url, {
79
+ signal: AbortSignal.timeout(8000), // 8 second timeout
80
+ headers: { "Accept": "application/json" },
81
+ });
82
+
83
+ if (!response.ok) {
84
+ throw new Error(`AWS pricing API returned ${response.status}`);
85
+ }
86
+
87
+ // Parse the large JSON — this is memory-intensive but works on Vercel's 1GB limit
88
+ const data = await response.json() as {
89
+ products?: Record<string, {
90
+ productFamily?: string;
91
+ attributes?: {
92
+ instanceType?: string;
93
+ location?: string;
94
+ operatingSystem?: string;
95
+ tenancy?: string;
96
+ capacitystatus?: string;
97
+ };
98
+ }>;
99
+ terms?: {
100
+ OnDemand?: Record<string, {
101
+ priceDimensions?: Record<string, {
102
+ pricePerUnit?: { USD?: string };
103
+ }>;
104
+ }>;
105
+ };
106
+ };
107
+
108
+ const products = data.products || {};
109
+ const onDemandTerms = data.terms?.OnDemand || {};
110
+
111
+ const prices: CleanGpuPrice[] = [];
112
+
113
+ // Iterate products and find GPU instance types
114
+ for (const [sku, product] of Object.entries(products)) {
115
+ const attrs = product.attributes || {};
116
+ const instanceType = attrs.instanceType || "";
117
+
118
+ // Skip non-GPU instances
119
+ if (!GPU_INSTANCE_PREFIXES.some((p) => instanceType.startsWith(p))) continue;
120
+
121
+ // Filter: Linux, Shared tenancy, Used capacity (on-demand)
122
+ if (attrs.operatingSystem !== "Linux") continue;
123
+ if (attrs.tenancy !== "Shared") continue;
124
+ if (attrs.capacitystatus !== "Used") continue;
125
+
126
+ // Find the OnDemand price for this SKU
127
+ const term = onDemandTerms[sku];
128
+ if (!term?.priceDimensions) continue;
129
+
130
+ const priceDims = Object.values(term.priceDimensions);
131
+ const usdPrice = priceDims[0]?.pricePerUnit?.USD;
132
+ if (!usdPrice) continue;
133
+
134
+ const instancePriceHour = parseFloat(usdPrice);
135
+ if (isNaN(instancePriceHour) || instancePriceHour <= 0) continue;
136
+
137
+ const gpuModel = getGpuModel(instanceType);
138
+ const gpuCount = getGpuCount(instanceType);
139
+ if (!gpuModel) continue;
140
+
141
+ const pricePerGpuHour = Math.round((instancePriceHour / gpuCount) * 100) / 100;
142
+
143
+ prices.push({
144
+ gpu: gpuModel,
145
+ instanceType,
146
+ pricePerGpuHour,
147
+ instancePriceHour: Math.round(instancePriceHour * 100) / 100,
148
+ gpuCount,
149
+ region: "us-east-1",
150
+ retrievedAt: new Date().toISOString(),
151
+ });
152
+ }
153
+
154
+ // Deduplicate: keep cheapest per-GPU price
155
+ const seen = new Map<string, CleanGpuPrice>();
156
+ for (const p of prices) {
157
+ const key = p.gpu;
158
+ if (!seen.has(key) || seen.get(key)!.pricePerGpuHour > p.pricePerGpuHour) {
159
+ seen.set(key, p);
160
+ }
161
+ }
162
+
163
+ const deduped = Array.from(seen.values()).sort((a, b) => a.pricePerGpuHour - b.pricePerGpuHour);
164
+
165
+ cache = { data: deduped, timestamp: Date.now() };
166
+
167
+ return NextResponse.json({
168
+ source: "aws",
169
+ cached: false,
170
+ retrievedAt: new Date().toISOString(),
171
+ count: deduped.length,
172
+ prices: deduped,
173
+ note: "On-demand Linux pricing from AWS public bulk files. No IAM credentials required. Region: us-east-1.",
174
+ });
175
+ } catch (error) {
176
+ console.error("AWS pricing fetch error:", error);
177
+ return NextResponse.json(
178
+ {
179
+ source: "aws",
180
+ error: "Failed to fetch AWS bulk pricing. The file is large (~50MB) and may have timed out. Cached results from a previous fetch may still be available.",
181
+ fallback: "Use static pricing from tokcalc's GPU catalog or Azure/Vast.ai live pricing.",
182
+ },
183
+ { status: 502 },
184
+ );
185
+ }
186
+ }