cache-overflow-mcp 0.3.4 → 0.3.5

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 (43) hide show
  1. package/.env.example +3 -3
  2. package/AGENTS.md +24 -3
  3. package/README.md +59 -0
  4. package/TROUBLESHOOTING.md +219 -0
  5. package/dist/cli.js +13 -1
  6. package/dist/cli.js.map +1 -1
  7. package/dist/client.d.ts.map +1 -1
  8. package/dist/client.js +53 -9
  9. package/dist/client.js.map +1 -1
  10. package/dist/config.d.ts +3 -0
  11. package/dist/config.d.ts.map +1 -1
  12. package/dist/config.js +5 -0
  13. package/dist/config.js.map +1 -1
  14. package/dist/logger.d.ts +16 -0
  15. package/dist/logger.d.ts.map +1 -0
  16. package/dist/logger.js +127 -0
  17. package/dist/logger.js.map +1 -0
  18. package/dist/server.d.ts.map +1 -1
  19. package/dist/server.js +50 -7
  20. package/dist/server.js.map +1 -1
  21. package/dist/testing/mock-data.js +40 -40
  22. package/dist/ui/verification-dialog.d.ts.map +1 -1
  23. package/dist/ui/verification-dialog.js +307 -268
  24. package/dist/ui/verification-dialog.js.map +1 -1
  25. package/package.json +3 -2
  26. package/scripts/view-logs.js +125 -0
  27. package/src/cli.ts +23 -10
  28. package/src/client.test.ts +116 -116
  29. package/src/client.ts +122 -76
  30. package/src/config.ts +14 -9
  31. package/src/index.ts +3 -3
  32. package/src/logger.ts +150 -0
  33. package/src/server.ts +49 -7
  34. package/src/testing/mock-data.ts +142 -142
  35. package/src/testing/mock-server.ts +176 -176
  36. package/src/tools/index.ts +23 -23
  37. package/src/types.ts +39 -39
  38. package/src/ui/verification-dialog.ts +382 -342
  39. package/tsconfig.json +20 -20
  40. package/dist/tools/get-balance.d.ts +0 -3
  41. package/dist/tools/get-balance.d.ts.map +0 -1
  42. package/dist/tools/get-balance.js +0 -34
  43. package/dist/tools/get-balance.js.map +0 -1
@@ -1,176 +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
- }
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
+ }
@@ -1,23 +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
- ];
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
+ ];
package/src/types.ts CHANGED
@@ -1,39 +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 };
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 };