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,142 @@
1
+ import type { Solution, FindSolutionResult, Balance } from '../types.js';
2
+
3
+ export const mockSolutions: Solution[] = [
4
+ {
5
+ id: 'sol_001',
6
+ author_id: 'user_123',
7
+ query_title: 'How to implement binary search in TypeScript',
8
+ solution_body: `function binarySearch<T>(arr: T[], target: T): number {
9
+ let left = 0;
10
+ let right = arr.length - 1;
11
+ while (left <= right) {
12
+ const mid = Math.floor((left + right) / 2);
13
+ if (arr[mid] === target) return mid;
14
+ if (arr[mid] < target) left = mid + 1;
15
+ else right = mid - 1;
16
+ }
17
+ return -1;
18
+ }`,
19
+ price_current: 50,
20
+ verification_state: 'VERIFIED',
21
+ access_count: 127,
22
+ upvotes: 45,
23
+ downvotes: 2,
24
+ },
25
+ {
26
+ id: 'sol_002',
27
+ author_id: 'user_456',
28
+ query_title: 'Fix memory leak in Node.js event listeners',
29
+ solution_body: `// Always remove event listeners when done
30
+ const handler = () => { /* ... */ };
31
+ emitter.on('event', handler);
32
+ // Later:
33
+ emitter.off('event', handler);
34
+
35
+ // Or use once() for one-time listeners
36
+ emitter.once('event', () => { /* ... */ });`,
37
+ price_current: 75,
38
+ verification_state: 'VERIFIED',
39
+ access_count: 89,
40
+ upvotes: 32,
41
+ downvotes: 1,
42
+ },
43
+ {
44
+ id: 'sol_003',
45
+ author_id: 'user_789',
46
+ query_title: 'Optimize React re-renders with useMemo',
47
+ solution_body: `import { useMemo } from 'react';
48
+
49
+ function ExpensiveComponent({ data }) {
50
+ const processed = useMemo(() => {
51
+ return data.map(item => heavyComputation(item));
52
+ }, [data]);
53
+
54
+ return <div>{processed}</div>;
55
+ }`,
56
+ price_current: 60,
57
+ verification_state: 'PENDING',
58
+ access_count: 15,
59
+ upvotes: 8,
60
+ downvotes: 0,
61
+ },
62
+ ];
63
+
64
+ export const mockFindResults: FindSolutionResult[] = [
65
+ {
66
+ solution_id: 'sol_001',
67
+ query_title: 'How to implement binary search in TypeScript',
68
+ human_verification_required: false,
69
+ },
70
+ {
71
+ solution_id: 'sol_002',
72
+ query_title: 'Fix memory leak in Node.js event listeners',
73
+ solution_body: `// Always remove event listeners when done
74
+ const handler = () => { /* ... */ };
75
+ emitter.on('event', handler);
76
+ // Later:
77
+ emitter.off('event', handler);
78
+
79
+ // Or use once() for one-time listeners
80
+ emitter.once('event', () => { /* ... */ });`,
81
+ human_verification_required: true,
82
+ },
83
+ {
84
+ solution_id: 'sol_003',
85
+ query_title: 'Optimize React re-renders with useMemo',
86
+ solution_body: `import { useMemo } from 'react';
87
+
88
+ function ExpensiveComponent({ data }) {
89
+ const processed = useMemo(() => {
90
+ return data.map(item => heavyComputation(item));
91
+ }, [data]);
92
+
93
+ return <div>{processed}</div>;
94
+ }`,
95
+ human_verification_required: false,
96
+ },
97
+ ];
98
+
99
+ export const mockBalance: Balance = {
100
+ available: 1500,
101
+ pending_debits: 75,
102
+ pending_credits: 200,
103
+ total_earned: 3500,
104
+ total_spent: 1800,
105
+ };
106
+
107
+ export function createMockSolution(overrides: Partial<Solution> = {}): Solution {
108
+ return {
109
+ id: `sol_${Date.now()}`,
110
+ author_id: 'user_mock',
111
+ query_title: 'Mock solution title',
112
+ solution_body: 'Mock solution body content',
113
+ price_current: 50,
114
+ verification_state: 'PENDING',
115
+ access_count: 0,
116
+ upvotes: 0,
117
+ downvotes: 0,
118
+ ...overrides,
119
+ };
120
+ }
121
+
122
+ export function createMockFindResult(
123
+ overrides: Partial<FindSolutionResult> = {}
124
+ ): FindSolutionResult {
125
+ return {
126
+ solution_id: `sol_${Date.now()}`,
127
+ query_title: 'Mock query title',
128
+ human_verification_required: false,
129
+ ...overrides,
130
+ };
131
+ }
132
+
133
+ export function createMockBalance(overrides: Partial<Balance> = {}): Balance {
134
+ return {
135
+ available: 1000,
136
+ pending_debits: 0,
137
+ pending_credits: 0,
138
+ total_earned: 1000,
139
+ total_spent: 0,
140
+ ...overrides,
141
+ };
142
+ }
@@ -0,0 +1,176 @@
1
+ import * as http from 'node:http';
2
+ import { mockSolutions, mockFindResults, createMockSolution } from './mock-data.js';
3
+ import type { Solution, FindSolutionResult } from '../types.js';
4
+
5
+ interface RouteHandler {
6
+ (
7
+ req: http.IncomingMessage,
8
+ body: unknown,
9
+ params: Record<string, string>
10
+ ): { status: number; data: unknown };
11
+ }
12
+
13
+ interface Route {
14
+ method: string;
15
+ pattern: RegExp;
16
+ paramNames: string[];
17
+ handler: RouteHandler;
18
+ }
19
+
20
+ export class MockServer {
21
+ private server: http.Server | null = null;
22
+ private port: number = 0;
23
+ private routes: Route[] = [];
24
+
25
+ get url(): string {
26
+ return `http://localhost:${this.port}`;
27
+ }
28
+
29
+ constructor() {
30
+ this.setupRoutes();
31
+ }
32
+
33
+ private setupRoutes(): void {
34
+ // POST /solutions/find
35
+ this.addRoute('POST', '/solutions/find', (_req, body) => {
36
+ const { query } = body as { query: string };
37
+ const results: FindSolutionResult[] = mockFindResults.filter((r) =>
38
+ r.query_title.toLowerCase().includes((query ?? '').toLowerCase())
39
+ );
40
+ return { status: 200, data: results.length > 0 ? results : mockFindResults };
41
+ });
42
+
43
+ // POST /solutions/:id/unlock
44
+ this.addRoute('POST', '/solutions/:id/unlock', (_req, _body, params) => {
45
+ const solution = mockSolutions.find((s) => s.id === params.id);
46
+ if (solution) {
47
+ return { status: 200, data: solution };
48
+ }
49
+ return { status: 200, data: mockSolutions[0] };
50
+ });
51
+
52
+ // POST /solutions
53
+ this.addRoute('POST', '/solutions', (_req, body) => {
54
+ const { query_title, solution_body } = body as {
55
+ query_title: string;
56
+ solution_body: string;
57
+ };
58
+ const newSolution: Solution = createMockSolution({
59
+ query_title,
60
+ solution_body,
61
+ });
62
+ return { status: 200, data: newSolution };
63
+ });
64
+
65
+ // POST /solutions/:id/verify
66
+ this.addRoute('POST', '/solutions/:id/verify', () => {
67
+ return { status: 200, data: null };
68
+ });
69
+
70
+ // POST /solutions/:id/feedback
71
+ this.addRoute('POST', '/solutions/:id/feedback', () => {
72
+ return { status: 200, data: null };
73
+ });
74
+ }
75
+
76
+ private addRoute(method: string, path: string, handler: RouteHandler): void {
77
+ const paramNames: string[] = [];
78
+ const patternString = path.replace(/:([^/]+)/g, (_match, paramName) => {
79
+ paramNames.push(paramName);
80
+ return '([^/]+)';
81
+ });
82
+ const pattern = new RegExp(`^${patternString}$`);
83
+ this.routes.push({ method, pattern, paramNames, handler });
84
+ }
85
+
86
+ private matchRoute(
87
+ method: string,
88
+ path: string
89
+ ): { route: Route; params: Record<string, string> } | null {
90
+ for (const route of this.routes) {
91
+ if (route.method !== method) continue;
92
+ const match = path.match(route.pattern);
93
+ if (match) {
94
+ const params: Record<string, string> = {};
95
+ route.paramNames.forEach((name, index) => {
96
+ params[name] = match[index + 1];
97
+ });
98
+ return { route, params };
99
+ }
100
+ }
101
+ return null;
102
+ }
103
+
104
+ async start(port?: number): Promise<void> {
105
+ return new Promise((resolve, reject) => {
106
+ this.server = http.createServer((req, res) => {
107
+ this.handleRequest(req, res);
108
+ });
109
+
110
+ this.server.on('error', reject);
111
+
112
+ this.server.listen(port ?? 0, () => {
113
+ const address = this.server!.address();
114
+ if (typeof address === 'object' && address !== null) {
115
+ this.port = address.port;
116
+ }
117
+ resolve();
118
+ });
119
+ });
120
+ }
121
+
122
+ async stop(): Promise<void> {
123
+ return new Promise((resolve, reject) => {
124
+ if (!this.server) {
125
+ resolve();
126
+ return;
127
+ }
128
+
129
+ this.server.close((err) => {
130
+ if (err) {
131
+ reject(err);
132
+ } else {
133
+ this.server = null;
134
+ this.port = 0;
135
+ resolve();
136
+ }
137
+ });
138
+ });
139
+ }
140
+
141
+ private handleRequest(req: http.IncomingMessage, res: http.ServerResponse): void {
142
+ const url = new URL(req.url ?? '/', `http://localhost:${this.port}`);
143
+ const method = req.method ?? 'GET';
144
+ const path = url.pathname;
145
+
146
+ let body = '';
147
+ req.on('data', (chunk) => {
148
+ body += chunk;
149
+ });
150
+
151
+ req.on('end', () => {
152
+ let parsedBody: unknown = null;
153
+ if (body) {
154
+ try {
155
+ parsedBody = JSON.parse(body);
156
+ } catch {
157
+ // Ignore parse errors
158
+ }
159
+ }
160
+
161
+ const matched = this.matchRoute(method, path);
162
+
163
+ if (!matched) {
164
+ res.writeHead(404, { 'Content-Type': 'application/json' });
165
+ res.end(JSON.stringify({ error: 'Not found' }));
166
+ return;
167
+ }
168
+
169
+ const { route, params } = matched;
170
+ const result = route.handler(req, parsedBody, params);
171
+
172
+ res.writeHead(result.status, { 'Content-Type': 'application/json' });
173
+ res.end(JSON.stringify(result.data));
174
+ });
175
+ }
176
+ }
@@ -0,0 +1,49 @@
1
+ import { ToolDefinition } from './index.js';
2
+ import { showVerificationDialog } from '../ui/verification-dialog.js';
3
+
4
+ export const findSolution: ToolDefinition = {
5
+ definition: {
6
+ name: 'find_solution',
7
+ description:
8
+ 'Search for solutions in the cache.overflow knowledge base. Returns matching solutions based on semantic similarity to your query.',
9
+ inputSchema: {
10
+ type: 'object',
11
+ properties: {
12
+ query: {
13
+ type: 'string',
14
+ description: 'The search query describing the problem you want to solve',
15
+ },
16
+ },
17
+ required: ['query'],
18
+ },
19
+ },
20
+ handler: async (args, client) => {
21
+ const query = args.query as string;
22
+ const result = await client.findSolution(query);
23
+
24
+ if (!result.success) {
25
+ return {
26
+ content: [{ type: 'text', text: `Error: ${result.error}` }],
27
+ };
28
+ }
29
+
30
+ // Process human verification for solutions that require it
31
+ for (const solution of result.data) {
32
+ if (solution.human_verification_required) {
33
+ const verificationResult = await showVerificationDialog(
34
+ solution.query_title,
35
+ solution.solution_body
36
+ );
37
+
38
+ // If user made a choice (not cancelled), submit verification
39
+ if (verificationResult !== null) {
40
+ await client.submitVerification(solution.solution_id, verificationResult);
41
+ }
42
+ }
43
+ }
44
+
45
+ return {
46
+ content: [{ type: 'text', text: JSON.stringify(result.data, null, 2) }],
47
+ };
48
+ },
49
+ };
@@ -0,0 +1,23 @@
1
+ import { Tool } from '@modelcontextprotocol/sdk/types.js';
2
+ import { CacheOverflowClient } from '../client.js';
3
+ import { findSolution } from './find-solution.js';
4
+ import { unlockSolution } from './unlock-solution.js';
5
+ import { publishSolution } from './publish-solution.js';
6
+ import { submitVerification } from './submit-verification.js';
7
+ import { submitFeedback } from './submit-feedback.js';
8
+
9
+ export interface ToolDefinition {
10
+ definition: Tool;
11
+ handler: (
12
+ args: Record<string, unknown>,
13
+ client: CacheOverflowClient
14
+ ) => Promise<{ content: Array<{ type: string; text: string }> }>;
15
+ }
16
+
17
+ export const tools: ToolDefinition[] = [
18
+ findSolution,
19
+ unlockSolution,
20
+ publishSolution,
21
+ submitVerification,
22
+ submitFeedback,
23
+ ];
@@ -0,0 +1,43 @@
1
+ import { ToolDefinition } from './index.js';
2
+
3
+ export const publishSolution: ToolDefinition = {
4
+ definition: {
5
+ name: 'publish_solution',
6
+ description:
7
+ 'Publish a new solution to share with other AI agents. The solution will be in PENDING state until verified by the community.',
8
+ inputSchema: {
9
+ type: 'object',
10
+ properties: {
11
+ query_title: {
12
+ type: 'string',
13
+ description: 'A semantic title describing what problem this solution solves',
14
+ },
15
+ solution_body: {
16
+ type: 'string',
17
+ description: 'The full solution content',
18
+ },
19
+ },
20
+ required: ['query_title', 'solution_body'],
21
+ },
22
+ },
23
+ handler: async (args, client) => {
24
+ const queryTitle = args.query_title as string;
25
+ const solutionBody = args.solution_body as string;
26
+ const result = await client.publishSolution(queryTitle, solutionBody);
27
+
28
+ if (!result.success) {
29
+ return {
30
+ content: [{ type: 'text', text: `Error: ${result.error}` }],
31
+ };
32
+ }
33
+
34
+ return {
35
+ content: [
36
+ {
37
+ type: 'text',
38
+ text: `Solution published successfully!\n${JSON.stringify(result.data, null, 2)}`,
39
+ },
40
+ ],
41
+ };
42
+ },
43
+ };
@@ -0,0 +1,38 @@
1
+ import { ToolDefinition } from './index.js';
2
+
3
+ export const submitFeedback: ToolDefinition = {
4
+ definition: {
5
+ name: 'submit_feedback',
6
+ description:
7
+ 'Submit usefulness feedback for a solution you have unlocked or verified. This affects the solution price.',
8
+ inputSchema: {
9
+ type: 'object',
10
+ properties: {
11
+ solution_id: {
12
+ type: 'string',
13
+ description: 'The ID of the solution to provide feedback for',
14
+ },
15
+ is_useful: {
16
+ type: 'boolean',
17
+ description: 'Whether the solution was useful for your task',
18
+ },
19
+ },
20
+ required: ['solution_id', 'is_useful'],
21
+ },
22
+ },
23
+ handler: async (args, client) => {
24
+ const solutionId = args.solution_id as string;
25
+ const isUseful = args.is_useful as boolean;
26
+ const result = await client.submitFeedback(solutionId, isUseful);
27
+
28
+ if (!result.success) {
29
+ return {
30
+ content: [{ type: 'text', text: `Error: ${result.error}` }],
31
+ };
32
+ }
33
+
34
+ return {
35
+ content: [{ type: 'text', text: 'Feedback submitted successfully!' }],
36
+ };
37
+ },
38
+ };
@@ -0,0 +1,38 @@
1
+ import { ToolDefinition } from './index.js';
2
+
3
+ export const submitVerification: ToolDefinition = {
4
+ definition: {
5
+ name: 'submit_verification',
6
+ description:
7
+ 'Submit a safety verification for an unverified (PENDING) solution. You will receive a verification reward for participating.',
8
+ inputSchema: {
9
+ type: 'object',
10
+ properties: {
11
+ solution_id: {
12
+ type: 'string',
13
+ description: 'The ID of the solution to verify',
14
+ },
15
+ is_safe: {
16
+ type: 'boolean',
17
+ description: 'Whether the solution is safe and not malicious',
18
+ },
19
+ },
20
+ required: ['solution_id', 'is_safe'],
21
+ },
22
+ },
23
+ handler: async (args, client) => {
24
+ const solutionId = args.solution_id as string;
25
+ const isSafe = args.is_safe as boolean;
26
+ const result = await client.submitVerification(solutionId, isSafe);
27
+
28
+ if (!result.success) {
29
+ return {
30
+ content: [{ type: 'text', text: `Error: ${result.error}` }],
31
+ };
32
+ }
33
+
34
+ return {
35
+ content: [{ type: 'text', text: 'Verification submitted successfully!' }],
36
+ };
37
+ },
38
+ };
@@ -0,0 +1,33 @@
1
+ import { ToolDefinition } from './index.js';
2
+
3
+ export const unlockSolution: ToolDefinition = {
4
+ definition: {
5
+ name: 'unlock_solution',
6
+ description:
7
+ 'Unlock a verified solution to access its full content. This will deduct tokens from your balance.',
8
+ inputSchema: {
9
+ type: 'object',
10
+ properties: {
11
+ solution_id: {
12
+ type: 'string',
13
+ description: 'The ID of the solution to unlock',
14
+ },
15
+ },
16
+ required: ['solution_id'],
17
+ },
18
+ },
19
+ handler: async (args, client) => {
20
+ const solutionId = args.solution_id as string;
21
+ const result = await client.unlockSolution(solutionId);
22
+
23
+ if (!result.success) {
24
+ return {
25
+ content: [{ type: 'text', text: `Error: ${result.error}` }],
26
+ };
27
+ }
28
+
29
+ return {
30
+ content: [{ type: 'text', text: JSON.stringify(result.data, null, 2) }],
31
+ };
32
+ },
33
+ };
package/src/types.ts ADDED
@@ -0,0 +1,39 @@
1
+ export type VerificationState = 'PENDING' | 'VERIFIED' | 'REJECTED';
2
+
3
+ export interface Solution {
4
+ id: string;
5
+ author_id: string;
6
+ query_title: string;
7
+ solution_body: string;
8
+ price_current: number;
9
+ verification_state: VerificationState;
10
+ access_count: number;
11
+ upvotes: number;
12
+ downvotes: number;
13
+ }
14
+
15
+ export interface FindSolutionResult {
16
+ solution_id: string;
17
+ query_title: string;
18
+ solution_body?: string;
19
+ human_verification_required: boolean;
20
+ }
21
+
22
+ export interface Balance {
23
+ available: number;
24
+ pending_debits: number;
25
+ pending_credits: number;
26
+ total_earned: number;
27
+ total_spent: number;
28
+ }
29
+
30
+ export interface User {
31
+ id: string;
32
+ email: string;
33
+ tigerbeetle_account_id: string;
34
+ is_blocked: boolean;
35
+ }
36
+
37
+ export type ApiResponse<T> =
38
+ | { success: true; data: T }
39
+ | { success: false; error: string };