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.
- package/.env.example +3 -0
- package/README.md +62 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +8 -0
- package/dist/cli.js.map +1 -0
- package/dist/client.d.ts +13 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +50 -0
- package/dist/client.js.map +1 -0
- package/dist/client.test.d.ts +2 -0
- package/dist/client.test.d.ts.map +1 -0
- package/dist/client.test.js +89 -0
- package/dist/client.test.js.map +1 -0
- package/dist/config.d.ts +10 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +10 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/server.d.ts +8 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +38 -0
- package/dist/server.js.map +1 -0
- package/dist/testing/mock-data.d.ts +8 -0
- package/dist/testing/mock-data.d.ts.map +1 -0
- package/dist/testing/mock-data.js +134 -0
- package/dist/testing/mock-data.js.map +1 -0
- package/dist/testing/mock-server.d.ts +14 -0
- package/dist/testing/mock-server.d.ts.map +1 -0
- package/dist/testing/mock-server.js +134 -0
- package/dist/testing/mock-server.js.map +1 -0
- package/dist/tools/find-solution.d.ts +3 -0
- package/dist/tools/find-solution.d.ts.map +1 -0
- package/dist/tools/find-solution.js +40 -0
- package/dist/tools/find-solution.js.map +1 -0
- package/dist/tools/index.d.ts +13 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +13 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/publish-solution.d.ts +3 -0
- package/dist/tools/publish-solution.d.ts.map +1 -0
- package/dist/tools/publish-solution.js +39 -0
- package/dist/tools/publish-solution.js.map +1 -0
- package/dist/tools/submit-feedback.d.ts +3 -0
- package/dist/tools/submit-feedback.d.ts.map +1 -0
- package/dist/tools/submit-feedback.js +34 -0
- package/dist/tools/submit-feedback.js.map +1 -0
- package/dist/tools/submit-verification.d.ts +3 -0
- package/dist/tools/submit-verification.d.ts.map +1 -0
- package/dist/tools/submit-verification.js +34 -0
- package/dist/tools/submit-verification.js.map +1 -0
- package/dist/tools/unlock-solution.d.ts +3 -0
- package/dist/tools/unlock-solution.d.ts.map +1 -0
- package/dist/tools/unlock-solution.js +29 -0
- package/dist/tools/unlock-solution.js.map +1 -0
- package/dist/types.d.ts +39 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/ui/verification-dialog.d.ts +8 -0
- package/dist/ui/verification-dialog.d.ts.map +1 -0
- package/dist/ui/verification-dialog.js +332 -0
- package/dist/ui/verification-dialog.js.map +1 -0
- package/package.json +44 -0
- package/src/cli.ts +10 -0
- package/src/client.test.ts +116 -0
- package/src/client.ts +76 -0
- package/src/config.ts +9 -0
- package/src/index.ts +3 -0
- package/src/server.ts +49 -0
- package/src/testing/mock-data.ts +142 -0
- package/src/testing/mock-server.ts +176 -0
- package/src/tools/find-solution.ts +49 -0
- package/src/tools/index.ts +23 -0
- package/src/tools/publish-solution.ts +43 -0
- package/src/tools/submit-feedback.ts +38 -0
- package/src/tools/submit-verification.ts +38 -0
- package/src/tools/unlock-solution.ts +33 -0
- package/src/types.ts +39 -0
- package/src/ui/verification-dialog.ts +342 -0
- package/test-dialog.js +37 -0
- 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 };
|