cache-overflow-mcp 0.3.3 → 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.
- package/.env.example +3 -3
- package/AGENTS.md +24 -3
- package/README.md +59 -0
- package/TROUBLESHOOTING.md +219 -0
- package/dist/cli.js +13 -1
- package/dist/cli.js.map +1 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +54 -10
- package/dist/client.js.map +1 -1
- package/dist/config.d.ts +3 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +5 -0
- package/dist/config.js.map +1 -1
- package/dist/logger.d.ts +16 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +127 -0
- package/dist/logger.js.map +1 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +50 -7
- package/dist/server.js.map +1 -1
- package/dist/testing/mock-data.js +40 -40
- package/dist/ui/verification-dialog.d.ts.map +1 -1
- package/dist/ui/verification-dialog.js +307 -268
- package/dist/ui/verification-dialog.js.map +1 -1
- package/package.json +3 -2
- package/scripts/view-logs.js +125 -0
- package/src/cli.ts +23 -10
- package/src/client.test.ts +116 -116
- package/src/client.ts +122 -76
- package/src/config.ts +14 -9
- package/src/index.ts +3 -3
- package/src/logger.ts +150 -0
- package/src/server.ts +49 -7
- package/src/testing/mock-data.ts +142 -142
- package/src/testing/mock-server.ts +176 -176
- package/src/tools/index.ts +23 -23
- package/src/types.ts +39 -39
- package/src/ui/verification-dialog.ts +382 -342
- package/tsconfig.json +20 -20
- package/dist/tools/get-balance.d.ts +0 -3
- package/dist/tools/get-balance.d.ts.map +0 -1
- package/dist/tools/get-balance.js +0 -34
- 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
|
+
}
|
package/src/tools/index.ts
CHANGED
|
@@ -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 };
|