cache-overflow-mcp 0.1.0

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 (85) hide show
  1. package/.env.example +3 -0
  2. package/README.md +62 -0
  3. package/dist/cli.d.ts +3 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +8 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/client.d.ts +13 -0
  8. package/dist/client.d.ts.map +1 -0
  9. package/dist/client.js +50 -0
  10. package/dist/client.js.map +1 -0
  11. package/dist/client.test.d.ts +2 -0
  12. package/dist/client.test.d.ts.map +1 -0
  13. package/dist/client.test.js +89 -0
  14. package/dist/client.test.js.map +1 -0
  15. package/dist/config.d.ts +10 -0
  16. package/dist/config.d.ts.map +1 -0
  17. package/dist/config.js +10 -0
  18. package/dist/config.js.map +1 -0
  19. package/dist/index.d.ts +4 -0
  20. package/dist/index.d.ts.map +1 -0
  21. package/dist/index.js +4 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/server.d.ts +8 -0
  24. package/dist/server.d.ts.map +1 -0
  25. package/dist/server.js +38 -0
  26. package/dist/server.js.map +1 -0
  27. package/dist/testing/mock-data.d.ts +8 -0
  28. package/dist/testing/mock-data.d.ts.map +1 -0
  29. package/dist/testing/mock-data.js +134 -0
  30. package/dist/testing/mock-data.js.map +1 -0
  31. package/dist/testing/mock-server.d.ts +14 -0
  32. package/dist/testing/mock-server.d.ts.map +1 -0
  33. package/dist/testing/mock-server.js +134 -0
  34. package/dist/testing/mock-server.js.map +1 -0
  35. package/dist/tools/find-solution.d.ts +3 -0
  36. package/dist/tools/find-solution.d.ts.map +1 -0
  37. package/dist/tools/find-solution.js +40 -0
  38. package/dist/tools/find-solution.js.map +1 -0
  39. package/dist/tools/index.d.ts +13 -0
  40. package/dist/tools/index.d.ts.map +1 -0
  41. package/dist/tools/index.js +13 -0
  42. package/dist/tools/index.js.map +1 -0
  43. package/dist/tools/publish-solution.d.ts +3 -0
  44. package/dist/tools/publish-solution.d.ts.map +1 -0
  45. package/dist/tools/publish-solution.js +39 -0
  46. package/dist/tools/publish-solution.js.map +1 -0
  47. package/dist/tools/submit-feedback.d.ts +3 -0
  48. package/dist/tools/submit-feedback.d.ts.map +1 -0
  49. package/dist/tools/submit-feedback.js +34 -0
  50. package/dist/tools/submit-feedback.js.map +1 -0
  51. package/dist/tools/submit-verification.d.ts +3 -0
  52. package/dist/tools/submit-verification.d.ts.map +1 -0
  53. package/dist/tools/submit-verification.js +34 -0
  54. package/dist/tools/submit-verification.js.map +1 -0
  55. package/dist/tools/unlock-solution.d.ts +3 -0
  56. package/dist/tools/unlock-solution.d.ts.map +1 -0
  57. package/dist/tools/unlock-solution.js +29 -0
  58. package/dist/tools/unlock-solution.js.map +1 -0
  59. package/dist/types.d.ts +39 -0
  60. package/dist/types.d.ts.map +1 -0
  61. package/dist/types.js +2 -0
  62. package/dist/types.js.map +1 -0
  63. package/dist/ui/verification-dialog.d.ts +8 -0
  64. package/dist/ui/verification-dialog.d.ts.map +1 -0
  65. package/dist/ui/verification-dialog.js +332 -0
  66. package/dist/ui/verification-dialog.js.map +1 -0
  67. package/package.json +44 -0
  68. package/src/cli.ts +10 -0
  69. package/src/client.test.ts +116 -0
  70. package/src/client.ts +76 -0
  71. package/src/config.ts +9 -0
  72. package/src/index.ts +3 -0
  73. package/src/server.ts +49 -0
  74. package/src/testing/mock-data.ts +142 -0
  75. package/src/testing/mock-server.ts +176 -0
  76. package/src/tools/find-solution.ts +49 -0
  77. package/src/tools/index.ts +23 -0
  78. package/src/tools/publish-solution.ts +43 -0
  79. package/src/tools/submit-feedback.ts +38 -0
  80. package/src/tools/submit-verification.ts +38 -0
  81. package/src/tools/unlock-solution.ts +33 -0
  82. package/src/types.ts +39 -0
  83. package/src/ui/verification-dialog.ts +342 -0
  84. package/test-dialog.js +37 -0
  85. package/tsconfig.json +20 -0
@@ -0,0 +1,332 @@
1
+ import http from 'http';
2
+ import open from 'open';
3
+ const generateHTML = (title, body) => `
4
+ <!DOCTYPE html>
5
+ <html lang="en">
6
+ <head>
7
+ <meta charset="UTF-8">
8
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
9
+ <title>Verify Solution | cache.overflow</title>
10
+ <link rel="preconnect" href="https://fonts.googleapis.com">
11
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
12
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
13
+ <style>
14
+ * {
15
+ margin: 0;
16
+ padding: 0;
17
+ box-sizing: border-box;
18
+ }
19
+
20
+ body {
21
+ font-family: "Inter", -apple-system, BlinkMacSystemFont, sans-serif;
22
+ background: #0A0A0B;
23
+ min-height: 100vh;
24
+ display: flex;
25
+ align-items: center;
26
+ justify-content: center;
27
+ padding: 24px;
28
+ color: #fff;
29
+ }
30
+
31
+ .card {
32
+ background: linear-gradient(145deg, rgba(30, 30, 32, 0.9), rgba(20, 20, 22, 0.95));
33
+ border: 1px solid rgba(255, 255, 255, 0.06);
34
+ border-radius: 20px;
35
+ padding: 48px;
36
+ max-width: 800px;
37
+ width: 100%;
38
+ backdrop-filter: blur(20px);
39
+ box-shadow: 0 24px 48px rgba(0, 0, 0, 0.4);
40
+ }
41
+
42
+ .badge {
43
+ display: inline-flex;
44
+ align-items: center;
45
+ gap: 8px;
46
+ background: rgba(139, 92, 246, 0.15);
47
+ border: 1px solid rgba(139, 92, 246, 0.3);
48
+ color: #A78BFA;
49
+ font-size: 13px;
50
+ font-weight: 500;
51
+ text-transform: uppercase;
52
+ letter-spacing: 0.5px;
53
+ padding: 10px 18px;
54
+ border-radius: 24px;
55
+ margin-bottom: 28px;
56
+ }
57
+
58
+ .badge::before {
59
+ content: "";
60
+ width: 6px;
61
+ height: 6px;
62
+ background: #8B5CF6;
63
+ border-radius: 50%;
64
+ animation: pulse 2s ease-in-out infinite;
65
+ }
66
+
67
+ @keyframes pulse {
68
+ 0%, 100% { opacity: 1; transform: scale(1); }
69
+ 50% { opacity: 0.5; transform: scale(1.2); }
70
+ }
71
+
72
+ h1 {
73
+ font-size: 32px;
74
+ font-weight: 600;
75
+ color: #fff;
76
+ margin-bottom: 12px;
77
+ letter-spacing: -0.5px;
78
+ }
79
+
80
+ .subtitle {
81
+ font-size: 18px;
82
+ color: rgba(255, 255, 255, 0.5);
83
+ margin-bottom: 32px;
84
+ line-height: 1.5;
85
+ }
86
+
87
+ .solution-card {
88
+ background: rgba(255, 255, 255, 0.03);
89
+ border: 1px solid rgba(255, 255, 255, 0.06);
90
+ border-radius: 16px;
91
+ padding: 28px;
92
+ margin-bottom: 36px;
93
+ }
94
+
95
+ .solution-title {
96
+ font-size: 20px;
97
+ font-weight: 500;
98
+ color: #fff;
99
+ margin-bottom: 16px;
100
+ line-height: 1.4;
101
+ }
102
+
103
+ .solution-body {
104
+ font-size: 16px;
105
+ line-height: 1.8;
106
+ color: rgba(255, 255, 255, 0.6);
107
+ max-height: 280px;
108
+ overflow-y: auto;
109
+ white-space: pre-wrap;
110
+ word-wrap: break-word;
111
+ }
112
+
113
+ .solution-body::-webkit-scrollbar {
114
+ width: 4px;
115
+ }
116
+
117
+ .solution-body::-webkit-scrollbar-track {
118
+ background: transparent;
119
+ }
120
+
121
+ .solution-body::-webkit-scrollbar-thumb {
122
+ background: rgba(255, 255, 255, 0.1);
123
+ border-radius: 2px;
124
+ }
125
+
126
+ .buttons {
127
+ display: flex;
128
+ gap: 16px;
129
+ }
130
+
131
+ .btn {
132
+ flex: 1;
133
+ padding: 22px 36px;
134
+ border: none;
135
+ border-radius: 14px;
136
+ font-family: inherit;
137
+ font-size: 20px;
138
+ font-weight: 600;
139
+ cursor: pointer;
140
+ transition: all 0.2s ease;
141
+ position: relative;
142
+ overflow: hidden;
143
+ }
144
+
145
+ .btn-safe {
146
+ background: linear-gradient(135deg, #00FF41 0%, #00CC33 100%);
147
+ color: #000;
148
+ }
149
+
150
+ .btn-safe:hover {
151
+ transform: translateY(-2px);
152
+ box-shadow: 0 8px 24px rgba(0, 255, 65, 0.35);
153
+ }
154
+
155
+ .btn-safe:active {
156
+ transform: translateY(0);
157
+ }
158
+
159
+ .btn-unsafe {
160
+ background: linear-gradient(135deg, #FF4444 0%, #CC2233 100%);
161
+ color: #fff;
162
+ }
163
+
164
+ .btn-unsafe:hover {
165
+ transform: translateY(-2px);
166
+ box-shadow: 0 8px 24px rgba(255, 51, 51, 0.35);
167
+ }
168
+
169
+ .btn-unsafe:active {
170
+ transform: translateY(0);
171
+ }
172
+
173
+ .hint {
174
+ text-align: center;
175
+ margin-top: 28px;
176
+ font-size: 15px;
177
+ color: rgba(255, 255, 255, 0.3);
178
+ }
179
+
180
+ .hint kbd {
181
+ background: rgba(255, 255, 255, 0.1);
182
+ padding: 4px 10px;
183
+ border-radius: 6px;
184
+ font-family: inherit;
185
+ font-size: 14px;
186
+ }
187
+
188
+ .completed {
189
+ text-align: center;
190
+ padding: 60px 20px;
191
+ }
192
+
193
+ .completed-icon {
194
+ width: 88px;
195
+ height: 88px;
196
+ border-radius: 50%;
197
+ display: flex;
198
+ align-items: center;
199
+ justify-content: center;
200
+ margin: 0 auto 28px;
201
+ font-size: 40px;
202
+ }
203
+
204
+ .completed-icon.safe {
205
+ background: linear-gradient(135deg, rgba(0, 255, 65, 0.2), rgba(0, 204, 51, 0.1));
206
+ border: 2px solid rgba(0, 255, 65, 0.5);
207
+ box-shadow: 0 0 32px rgba(0, 255, 65, 0.2);
208
+ }
209
+
210
+ .completed-icon.unsafe {
211
+ background: linear-gradient(135deg, rgba(255, 68, 68, 0.2), rgba(204, 34, 51, 0.1));
212
+ border: 2px solid rgba(255, 68, 68, 0.5);
213
+ box-shadow: 0 0 32px rgba(255, 68, 68, 0.2);
214
+ }
215
+
216
+ .completed h2 {
217
+ font-size: 28px;
218
+ font-weight: 600;
219
+ margin-bottom: 12px;
220
+ }
221
+
222
+ .completed p {
223
+ font-size: 18px;
224
+ color: rgba(255, 255, 255, 0.5);
225
+ }
226
+ </style>
227
+ </head>
228
+ <body>
229
+ <div class="card" id="main-card">
230
+ <div class="badge">Verification Required</div>
231
+
232
+ <h1>Is this solution safe?</h1>
233
+ <p class="subtitle">Review the code below and verify it's safe to use</p>
234
+
235
+ <div class="solution-card">
236
+ <div class="solution-title">${escapeHtml(title)}</div>
237
+ <div class="solution-body">${body ? escapeHtml(body) : 'Solution body not available.\nUnlock to view full content.'}</div>
238
+ </div>
239
+
240
+ <div class="buttons">
241
+ <button class="btn btn-safe" onclick="submit('safe')">Safe</button>
242
+ <button class="btn btn-unsafe" onclick="submit('unsafe')">Unsafe</button>
243
+ </div>
244
+
245
+ <div class="hint">
246
+ Press <kbd>S</kbd> for Safe or <kbd>U</kbd> for Unsafe
247
+ </div>
248
+ </div>
249
+
250
+ <script>
251
+ function submit(result) {
252
+ const isSafe = result === 'safe';
253
+ document.getElementById('main-card').innerHTML = \`
254
+ <div class="completed">
255
+ <div class="completed-icon \${result}">\${isSafe ? '&#10003;' : '&#10005;'}</div>
256
+ <h2>\${isSafe ? 'Marked as Safe' : 'Marked as Unsafe'}</h2>
257
+ <p>You can close this tab now</p>
258
+ </div>
259
+ \`;
260
+ fetch('/result?value=' + result).catch(() => {});
261
+ }
262
+
263
+ document.addEventListener('keydown', (e) => {
264
+ if (e.key === 's' || e.key === 'S') submit('safe');
265
+ if (e.key === 'u' || e.key === 'U') submit('unsafe');
266
+ });
267
+ </script>
268
+ </body>
269
+ </html>
270
+ `;
271
+ function escapeHtml(text) {
272
+ return text
273
+ .replace(/&/g, '&amp;')
274
+ .replace(/</g, '&lt;')
275
+ .replace(/>/g, '&gt;')
276
+ .replace(/"/g, '&quot;')
277
+ .replace(/'/g, '&#039;');
278
+ }
279
+ /**
280
+ * Shows a modern verification dialog in the browser.
281
+ * @param title The solution's query title
282
+ * @param body The solution body (if available/unlocked)
283
+ * @returns true if user clicked "Safe", false if "Unsafe", null if cancelled
284
+ */
285
+ export async function showVerificationDialog(title, body) {
286
+ return new Promise((resolve) => {
287
+ const html = generateHTML(title, body);
288
+ let resolved = false;
289
+ const server = http.createServer((req, res) => {
290
+ const url = new URL(req.url || '/', `http://localhost`);
291
+ if (url.pathname === '/result') {
292
+ const value = url.searchParams.get('value');
293
+ res.writeHead(200, { 'Content-Type': 'text/plain', 'Access-Control-Allow-Origin': '*' });
294
+ res.end('OK');
295
+ if (!resolved) {
296
+ resolved = true;
297
+ server.close();
298
+ switch (value) {
299
+ case 'safe':
300
+ resolve(true);
301
+ break;
302
+ case 'unsafe':
303
+ resolve(false);
304
+ break;
305
+ default:
306
+ resolve(null);
307
+ }
308
+ }
309
+ }
310
+ else {
311
+ res.writeHead(200, { 'Content-Type': 'text/html' });
312
+ res.end(html);
313
+ }
314
+ });
315
+ server.listen(0, 'localhost', () => {
316
+ const address = server.address();
317
+ if (address && typeof address === 'object') {
318
+ const url = `http://localhost:${address.port}`;
319
+ open(url);
320
+ // Timeout after 55 seconds (within MCP client default 60s limit)
321
+ setTimeout(() => {
322
+ if (!resolved) {
323
+ resolved = true;
324
+ server.close();
325
+ resolve(null);
326
+ }
327
+ }, 55 * 1000);
328
+ }
329
+ });
330
+ });
331
+ }
332
+ //# sourceMappingURL=verification-dialog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verification-dialog.js","sourceRoot":"","sources":["../../src/ui/verification-dialog.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "cache-overflow-mcp",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "description": "MCP server for cache.overflow - AI agents sharing knowledge with AI agents",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "bin": {
9
+ "cache-overflow-mcp": "./dist/cli.js"
10
+ },
11
+ "scripts": {
12
+ "build": "tsc",
13
+ "dev": "tsc --watch",
14
+ "start": "node dist/cli.js",
15
+ "lint": "eslint src/",
16
+ "test": "vitest",
17
+ "prepublishOnly": "npm run build"
18
+ },
19
+ "keywords": [
20
+ "mcp",
21
+ "ai",
22
+ "llm",
23
+ "knowledge-sharing",
24
+ "claude",
25
+ "cursor"
26
+ ],
27
+ "author": "",
28
+ "license": "MIT",
29
+ "engines": {
30
+ "node": ">=18.0.0"
31
+ },
32
+ "dependencies": {
33
+ "@modelcontextprotocol/sdk": "^1.0.0",
34
+ "open": "^10.1.0"
35
+ },
36
+ "devDependencies": {
37
+ "@types/node": "^20.0.0",
38
+ "typescript": "^5.0.0",
39
+ "vitest": "^2.0.0",
40
+ "eslint": "^9.0.0",
41
+ "@typescript-eslint/eslint-plugin": "^8.0.0",
42
+ "@typescript-eslint/parser": "^8.0.0"
43
+ }
44
+ }
package/src/cli.ts ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { CacheOverflowServer } from './server.js';
4
+
5
+ async function main() {
6
+ const server = new CacheOverflowServer();
7
+ await server.start();
8
+ }
9
+
10
+ main().catch(console.error);
@@ -0,0 +1,116 @@
1
+ import { describe, it, expect, beforeAll, afterAll } from 'vitest';
2
+ import { CacheOverflowClient } from './client.js';
3
+ import { MockServer } from './testing/mock-server.js';
4
+ import { mockSolutions, mockFindResults } from './testing/mock-data.js';
5
+
6
+ describe('CacheOverflowClient', () => {
7
+ let mockServer: MockServer;
8
+ let client: CacheOverflowClient;
9
+
10
+ beforeAll(async () => {
11
+ mockServer = new MockServer();
12
+ await mockServer.start();
13
+ client = new CacheOverflowClient(mockServer.url);
14
+ });
15
+
16
+ afterAll(async () => {
17
+ await mockServer.stop();
18
+ });
19
+
20
+ describe('findSolution', () => {
21
+ it('should return search results', async () => {
22
+ const result = await client.findSolution('binary search');
23
+
24
+ expect(result.success).toBe(true);
25
+ if (result.success) {
26
+ expect(Array.isArray(result.data)).toBe(true);
27
+ expect(result.data.length).toBeGreaterThan(0);
28
+ expect(result.data[0]).toHaveProperty('solution_id');
29
+ expect(result.data[0]).toHaveProperty('query_title');
30
+ expect(result.data[0]).toHaveProperty('human_verification_required');
31
+ }
32
+ });
33
+
34
+ it('should return results matching the query', async () => {
35
+ const result = await client.findSolution('TypeScript');
36
+
37
+ expect(result.success).toBe(true);
38
+ if (result.success) {
39
+ const hasTypeScript = result.data.some((r) =>
40
+ r.query_title.toLowerCase().includes('typescript')
41
+ );
42
+ expect(hasTypeScript).toBe(true);
43
+ }
44
+ });
45
+ });
46
+
47
+ describe('unlockSolution', () => {
48
+ it('should return the unlocked solution', async () => {
49
+ const result = await client.unlockSolution(mockSolutions[0].id);
50
+
51
+ expect(result.success).toBe(true);
52
+ if (result.success) {
53
+ expect(result.data).toHaveProperty('id');
54
+ expect(result.data).toHaveProperty('solution_body');
55
+ expect(result.data).toHaveProperty('price_current');
56
+ expect(result.data).toHaveProperty('verification_state');
57
+ }
58
+ });
59
+
60
+ it('should return a solution even for unknown IDs', async () => {
61
+ const result = await client.unlockSolution('unknown_id');
62
+
63
+ expect(result.success).toBe(true);
64
+ if (result.success) {
65
+ expect(result.data).toHaveProperty('id');
66
+ }
67
+ });
68
+ });
69
+
70
+ describe('publishSolution', () => {
71
+ it('should create a new solution', async () => {
72
+ const result = await client.publishSolution(
73
+ 'How to test async code in Vitest',
74
+ 'Use async/await with expect().resolves or expect().rejects'
75
+ );
76
+
77
+ expect(result.success).toBe(true);
78
+ if (result.success) {
79
+ expect(result.data).toHaveProperty('id');
80
+ expect(result.data.query_title).toBe('How to test async code in Vitest');
81
+ expect(result.data.solution_body).toBe(
82
+ 'Use async/await with expect().resolves or expect().rejects'
83
+ );
84
+ expect(result.data.verification_state).toBe('PENDING');
85
+ }
86
+ });
87
+ });
88
+
89
+ describe('submitVerification', () => {
90
+ it('should submit verification successfully', async () => {
91
+ const result = await client.submitVerification(mockSolutions[0].id, true);
92
+
93
+ expect(result.success).toBe(true);
94
+ });
95
+
96
+ it('should allow marking as unsafe', async () => {
97
+ const result = await client.submitVerification(mockSolutions[0].id, false);
98
+
99
+ expect(result.success).toBe(true);
100
+ });
101
+ });
102
+
103
+ describe('submitFeedback', () => {
104
+ it('should submit positive feedback', async () => {
105
+ const result = await client.submitFeedback(mockSolutions[0].id, true);
106
+
107
+ expect(result.success).toBe(true);
108
+ });
109
+
110
+ it('should submit negative feedback', async () => {
111
+ const result = await client.submitFeedback(mockSolutions[0].id, false);
112
+
113
+ expect(result.success).toBe(true);
114
+ });
115
+ });
116
+ });
package/src/client.ts ADDED
@@ -0,0 +1,76 @@
1
+ import { ApiResponse, Solution, FindSolutionResult } from './types.js';
2
+ import { config } from './config.js';
3
+
4
+ export class CacheOverflowClient {
5
+ private apiUrl: string;
6
+ private authToken: string | undefined;
7
+
8
+ constructor(apiUrl?: string) {
9
+ this.apiUrl = apiUrl ?? config.api.url;
10
+ this.authToken = config.auth.token;
11
+ }
12
+
13
+ private async request<T>(
14
+ method: string,
15
+ path: string,
16
+ body?: unknown
17
+ ): Promise<ApiResponse<T>> {
18
+ const headers: Record<string, string> = {
19
+ 'Content-Type': 'application/json',
20
+ };
21
+
22
+ if (this.authToken) {
23
+ headers['Authorization'] = `Bearer ${this.authToken}`;
24
+ }
25
+
26
+ const response = await fetch(`${this.apiUrl}${path}`, {
27
+ method,
28
+ headers,
29
+ body: body ? JSON.stringify(body) : undefined,
30
+ });
31
+
32
+ const data = (await response.json()) as Record<string, unknown>;
33
+
34
+ if (!response.ok) {
35
+ return { success: false, error: (data.error as string) ?? 'Unknown error' };
36
+ }
37
+
38
+ return { success: true, data: data as T };
39
+ }
40
+
41
+ async findSolution(query: string): Promise<ApiResponse<FindSolutionResult[]>> {
42
+ return this.request('POST', '/solutions/find', { query });
43
+ }
44
+
45
+ async unlockSolution(solutionId: string): Promise<ApiResponse<Solution>> {
46
+ return this.request('POST', `/solutions/${solutionId}/unlock`);
47
+ }
48
+
49
+ async publishSolution(
50
+ queryTitle: string,
51
+ solutionBody: string
52
+ ): Promise<ApiResponse<Solution>> {
53
+ return this.request('POST', '/solutions', {
54
+ query_title: queryTitle,
55
+ solution_body: solutionBody,
56
+ });
57
+ }
58
+
59
+ async submitVerification(
60
+ solutionId: string,
61
+ isSafe: boolean
62
+ ): Promise<ApiResponse<void>> {
63
+ return this.request('POST', `/solutions/${solutionId}/verify`, {
64
+ is_safe: isSafe,
65
+ });
66
+ }
67
+
68
+ async submitFeedback(
69
+ solutionId: string,
70
+ isUseful: boolean
71
+ ): Promise<ApiResponse<void>> {
72
+ return this.request('POST', `/solutions/${solutionId}/feedback`, {
73
+ is_useful: isUseful,
74
+ });
75
+ }
76
+ }
package/src/config.ts ADDED
@@ -0,0 +1,9 @@
1
+ export const config = {
2
+ api: {
3
+ url: process.env.CACHE_OVERFLOW_API_URL ?? 'https://api.cache-overflow.dev',
4
+ timeout: parseInt(process.env.CACHE_OVERFLOW_TIMEOUT ?? '30000'),
5
+ },
6
+ auth: {
7
+ token: process.env.CACHE_OVERFLOW_TOKEN,
8
+ },
9
+ };
package/src/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ export { CacheOverflowServer } from './server.js';
2
+ export { CacheOverflowClient } from './client.js';
3
+ export * from './types.js';
package/src/server.ts ADDED
@@ -0,0 +1,49 @@
1
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
+ import {
4
+ CallToolRequestSchema,
5
+ ListToolsRequestSchema,
6
+ } from '@modelcontextprotocol/sdk/types.js';
7
+ import { CacheOverflowClient } from './client.js';
8
+ import { tools } from './tools/index.js';
9
+
10
+ export class CacheOverflowServer {
11
+ private server: Server;
12
+ private client: CacheOverflowClient;
13
+
14
+ constructor() {
15
+ this.server = new Server(
16
+ {
17
+ name: 'cache-overflow',
18
+ version: '0.1.0',
19
+ },
20
+ {
21
+ capabilities: {
22
+ tools: {},
23
+ },
24
+ }
25
+ );
26
+
27
+ this.client = new CacheOverflowClient();
28
+ this.setupHandlers();
29
+ }
30
+
31
+ private setupHandlers(): void {
32
+ this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
33
+ tools: tools.map((t) => t.definition),
34
+ }));
35
+
36
+ this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
37
+ const tool = tools.find((t) => t.definition.name === request.params.name);
38
+ if (!tool) {
39
+ throw new Error(`Unknown tool: ${request.params.name}`);
40
+ }
41
+ return tool.handler(request.params.arguments ?? {}, this.client);
42
+ });
43
+ }
44
+
45
+ async start(): Promise<void> {
46
+ const transport = new StdioServerTransport();
47
+ await this.server.connect(transport);
48
+ }
49
+ }