@cg3/prior-mcp 0.2.11 → 0.3.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.
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Prior API client — shared between local MCP (stdio) and remote MCP server.
3
+ *
4
+ * Handles API key management, auto-registration, and HTTP requests.
5
+ * For local use: persists API key to ~/.prior/config.json
6
+ * For remote use: caller manages API key per-session (no file persistence)
7
+ */
8
+ export declare const CONFIG_PATH: string;
9
+ export interface PriorConfig {
10
+ apiKey: string;
11
+ agentId: string;
12
+ }
13
+ export interface PriorClientOptions {
14
+ /** Base URL for the Prior API */
15
+ apiUrl?: string;
16
+ /** Pre-set API key (e.g. from env var or session state) */
17
+ apiKey?: string;
18
+ /** Pre-set agent ID */
19
+ agentId?: string;
20
+ /** Whether to persist config to ~/.prior/config.json (default: true) */
21
+ persistConfig?: boolean;
22
+ /** User-Agent string override */
23
+ userAgent?: string;
24
+ }
25
+ export declare class PriorApiClient {
26
+ private apiUrl;
27
+ private _apiKey;
28
+ private _agentId;
29
+ private persistConfig;
30
+ private userAgent;
31
+ constructor(options?: PriorClientOptions);
32
+ get apiKey(): string | undefined;
33
+ get agentId(): string | undefined;
34
+ loadConfig(): PriorConfig | null;
35
+ saveConfig(config: PriorConfig): void;
36
+ ensureApiKey(): Promise<string | null>;
37
+ /** Clear cached API key and agent ID. Optionally delete config file. */
38
+ clearAuth(deleteConfig?: boolean): void;
39
+ request(method: string, path: string, body?: unknown, key?: string): Promise<unknown>;
40
+ }
package/dist/client.js ADDED
@@ -0,0 +1,153 @@
1
+ "use strict";
2
+ /**
3
+ * Prior API client — shared between local MCP (stdio) and remote MCP server.
4
+ *
5
+ * Handles API key management, auto-registration, and HTTP requests.
6
+ * For local use: persists API key to ~/.prior/config.json
7
+ * For remote use: caller manages API key per-session (no file persistence)
8
+ */
9
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ var desc = Object.getOwnPropertyDescriptor(m, k);
12
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
13
+ desc = { enumerable: true, get: function() { return m[k]; } };
14
+ }
15
+ Object.defineProperty(o, k2, desc);
16
+ }) : (function(o, m, k, k2) {
17
+ if (k2 === undefined) k2 = k;
18
+ o[k2] = m[k];
19
+ }));
20
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
21
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
22
+ }) : function(o, v) {
23
+ o["default"] = v;
24
+ });
25
+ var __importStar = (this && this.__importStar) || (function () {
26
+ var ownKeys = function(o) {
27
+ ownKeys = Object.getOwnPropertyNames || function (o) {
28
+ var ar = [];
29
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
30
+ return ar;
31
+ };
32
+ return ownKeys(o);
33
+ };
34
+ return function (mod) {
35
+ if (mod && mod.__esModule) return mod;
36
+ var result = {};
37
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
38
+ __setModuleDefault(result, mod);
39
+ return result;
40
+ };
41
+ })();
42
+ Object.defineProperty(exports, "__esModule", { value: true });
43
+ exports.PriorApiClient = exports.CONFIG_PATH = void 0;
44
+ const fs = __importStar(require("fs"));
45
+ const path = __importStar(require("path"));
46
+ const os = __importStar(require("os"));
47
+ const utils_js_1 = require("./utils.js");
48
+ exports.CONFIG_PATH = path.join(os.homedir(), ".prior", "config.json");
49
+ const VERSION = "0.3.0";
50
+ class PriorApiClient {
51
+ apiUrl;
52
+ _apiKey;
53
+ _agentId;
54
+ persistConfig;
55
+ userAgent;
56
+ constructor(options = {}) {
57
+ this.apiUrl = options.apiUrl || process.env.PRIOR_API_URL || "https://api.cg3.io";
58
+ this._apiKey = options.apiKey || process.env.PRIOR_API_KEY;
59
+ this._agentId = options.agentId;
60
+ this.persistConfig = options.persistConfig ?? true;
61
+ this.userAgent = options.userAgent || `prior-mcp/${VERSION}`;
62
+ // Load config on startup if no key provided
63
+ if (!this._apiKey && this.persistConfig) {
64
+ const config = this.loadConfig();
65
+ if (config) {
66
+ this._apiKey = config.apiKey;
67
+ this._agentId = config.agentId;
68
+ }
69
+ }
70
+ }
71
+ get apiKey() { return this._apiKey; }
72
+ get agentId() { return this._agentId; }
73
+ loadConfig() {
74
+ try {
75
+ const raw = fs.readFileSync(exports.CONFIG_PATH, "utf-8");
76
+ return JSON.parse(raw);
77
+ }
78
+ catch {
79
+ return null;
80
+ }
81
+ }
82
+ saveConfig(config) {
83
+ fs.mkdirSync(path.dirname(exports.CONFIG_PATH), { recursive: true });
84
+ fs.writeFileSync(exports.CONFIG_PATH, JSON.stringify(config, null, 2));
85
+ }
86
+ async ensureApiKey() {
87
+ if (this._apiKey)
88
+ return this._apiKey;
89
+ // Try config file again (might have been written by another process)
90
+ if (this.persistConfig) {
91
+ const config = this.loadConfig();
92
+ if (config) {
93
+ this._apiKey = config.apiKey;
94
+ this._agentId = config.agentId;
95
+ return this._apiKey;
96
+ }
97
+ }
98
+ // Auto-register
99
+ try {
100
+ const host = (0, utils_js_1.detectHost)();
101
+ const raw = await this.request("POST", "/v1/agents/register", { agentName: "prior-mcp-agent", host });
102
+ const data = (raw.data || raw);
103
+ const newKey = (data.apiKey || data.api_key || data.key);
104
+ const newId = (data.agentId || data.agent_id || data.id);
105
+ if (newKey) {
106
+ this._apiKey = newKey;
107
+ this._agentId = newId;
108
+ if (this.persistConfig) {
109
+ this.saveConfig({ apiKey: newKey, agentId: newId });
110
+ }
111
+ return this._apiKey;
112
+ }
113
+ }
114
+ catch {
115
+ // Registration failed
116
+ }
117
+ return null;
118
+ }
119
+ /** Clear cached API key and agent ID. Optionally delete config file. */
120
+ clearAuth(deleteConfig = false) {
121
+ this._apiKey = undefined;
122
+ this._agentId = undefined;
123
+ if (deleteConfig) {
124
+ try {
125
+ fs.unlinkSync(exports.CONFIG_PATH);
126
+ }
127
+ catch { }
128
+ }
129
+ }
130
+ async request(method, path, body, key) {
131
+ const k = key || this._apiKey;
132
+ const res = await fetch(`${this.apiUrl}${path}`, {
133
+ method,
134
+ headers: {
135
+ ...(k ? { "Authorization": `Bearer ${k}` } : {}),
136
+ "Content-Type": "application/json",
137
+ "User-Agent": this.userAgent,
138
+ },
139
+ body: body ? JSON.stringify(body) : undefined,
140
+ });
141
+ const text = await res.text();
142
+ if (!res.ok) {
143
+ throw new Error(`API error ${res.status}: ${text}`);
144
+ }
145
+ try {
146
+ return JSON.parse(text);
147
+ }
148
+ catch {
149
+ return text;
150
+ }
151
+ }
152
+ }
153
+ exports.PriorApiClient = PriorApiClient;
package/dist/index.d.ts CHANGED
@@ -1,12 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
- export declare const CONFIG_PATH: string;
4
- interface PriorConfig {
3
+ export { CONFIG_PATH, PriorApiClient } from "./client.js";
4
+ export { registerTools } from "./tools.js";
5
+ export { registerResources } from "./resources.js";
6
+ export { detectHost, formatResults } from "./utils.js";
7
+ export declare function loadConfig(): import("./client.js").PriorConfig | null;
8
+ export declare function saveConfig(config: {
5
9
  apiKey: string;
6
10
  agentId: string;
7
- }
8
- export declare function loadConfig(): PriorConfig | null;
9
- export declare function saveConfig(config: PriorConfig): void;
10
- export declare function main(): Promise<void>;
11
+ }): void;
11
12
  export declare function createServer(): McpServer;
12
- export {};
13
+ export declare function main(): Promise<void>;
package/dist/index.js CHANGED
@@ -2,375 +2,45 @@
2
2
  "use strict";
3
3
  // SYNC_VERSION: 2026-02-26-v1 - Must match API.md, cli.py, SKILL.md
4
4
  // Update this when API changes. Check DEPLOYS.md for full sync checklist.
5
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
6
- if (k2 === undefined) k2 = k;
7
- var desc = Object.getOwnPropertyDescriptor(m, k);
8
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
9
- desc = { enumerable: true, get: function() { return m[k]; } };
10
- }
11
- Object.defineProperty(o, k2, desc);
12
- }) : (function(o, m, k, k2) {
13
- if (k2 === undefined) k2 = k;
14
- o[k2] = m[k];
15
- }));
16
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
17
- Object.defineProperty(o, "default", { enumerable: true, value: v });
18
- }) : function(o, v) {
19
- o["default"] = v;
20
- });
21
- var __importStar = (this && this.__importStar) || (function () {
22
- var ownKeys = function(o) {
23
- ownKeys = Object.getOwnPropertyNames || function (o) {
24
- var ar = [];
25
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
26
- return ar;
27
- };
28
- return ownKeys(o);
29
- };
30
- return function (mod) {
31
- if (mod && mod.__esModule) return mod;
32
- var result = {};
33
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
34
- __setModuleDefault(result, mod);
35
- return result;
36
- };
37
- })();
38
5
  Object.defineProperty(exports, "__esModule", { value: true });
39
- exports.CONFIG_PATH = void 0;
6
+ exports.formatResults = exports.detectHost = exports.registerResources = exports.registerTools = exports.PriorApiClient = exports.CONFIG_PATH = void 0;
40
7
  exports.loadConfig = loadConfig;
41
8
  exports.saveConfig = saveConfig;
42
- exports.main = main;
43
9
  exports.createServer = createServer;
10
+ exports.main = main;
44
11
  const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
45
12
  const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
46
- const zod_1 = require("zod");
47
- const fs = __importStar(require("fs"));
48
- const path = __importStar(require("path"));
49
- const os = __importStar(require("os"));
50
- const API_URL = process.env.PRIOR_API_URL || "https://api.cg3.io";
51
- exports.CONFIG_PATH = path.join(os.homedir(), ".prior", "config.json");
52
- // In-memory state
53
- let apiKey = process.env.PRIOR_API_KEY;
54
- let agentId;
55
- function loadConfig() {
56
- try {
57
- const raw = fs.readFileSync(exports.CONFIG_PATH, "utf-8");
58
- return JSON.parse(raw);
59
- }
60
- catch {
61
- return null;
62
- }
63
- }
64
- function saveConfig(config) {
65
- fs.mkdirSync(path.dirname(exports.CONFIG_PATH), { recursive: true });
66
- fs.writeFileSync(exports.CONFIG_PATH, JSON.stringify(config, null, 2));
67
- }
68
- // Load config on startup if no env var
69
- if (!apiKey) {
70
- const config = loadConfig();
71
- if (config) {
72
- apiKey = config.apiKey;
73
- agentId = config.agentId;
74
- }
75
- }
76
- // Import utility functions
77
- const utils_js_1 = require("./utils.js");
78
- async function ensureApiKey() {
79
- if (apiKey)
80
- return apiKey;
81
- // Try config file again (might have been written by another process)
82
- const config = loadConfig();
83
- if (config) {
84
- apiKey = config.apiKey;
85
- agentId = config.agentId;
86
- return apiKey;
87
- }
88
- // Auto-register
89
- try {
90
- const host = (0, utils_js_1.detectHost)();
91
- const raw = await apiRequest("POST", "/v1/agents/register", { agentName: "prior-mcp-agent", host });
92
- // Unwrap ApiResponse envelope: { ok, data: { apiKey, agentId, credits } }
93
- const data = (raw.data || raw);
94
- const newKey = (data.apiKey || data.api_key || data.key);
95
- const newId = (data.agentId || data.agent_id || data.id);
96
- if (newKey) {
97
- apiKey = newKey;
98
- agentId = newId;
99
- saveConfig({ apiKey: newKey, agentId: newId });
100
- return apiKey;
101
- }
102
- }
103
- catch {
104
- // Registration failed - return null so caller can handle
105
- }
106
- return null;
107
- }
108
- async function apiRequest(method, path, body, key) {
109
- const k = key || apiKey;
110
- const res = await fetch(`${API_URL}${path}`, {
111
- method,
112
- headers: {
113
- ...(k ? { "Authorization": `Bearer ${k}` } : {}),
114
- "Content-Type": "application/json",
115
- "User-Agent": "prior-mcp/0.2.11",
116
- },
117
- body: body ? JSON.stringify(body) : undefined,
118
- });
119
- const text = await res.text();
120
- if (!res.ok) {
121
- throw new Error(`API error ${res.status}: ${text}`);
122
- }
123
- try {
124
- return JSON.parse(text);
125
- }
126
- catch {
127
- return text;
128
- }
129
- }
130
- // formatResults moved to utils.ts
13
+ const client_js_1 = require("./client.js");
14
+ const tools_js_1 = require("./tools.js");
15
+ const resources_js_1 = require("./resources.js");
16
+ // Re-export for backward compatibility (tests import from dist/index.js)
17
+ var client_js_2 = require("./client.js");
18
+ Object.defineProperty(exports, "CONFIG_PATH", { enumerable: true, get: function () { return client_js_2.CONFIG_PATH; } });
19
+ Object.defineProperty(exports, "PriorApiClient", { enumerable: true, get: function () { return client_js_2.PriorApiClient; } });
20
+ var tools_js_2 = require("./tools.js");
21
+ Object.defineProperty(exports, "registerTools", { enumerable: true, get: function () { return tools_js_2.registerTools; } });
22
+ var resources_js_2 = require("./resources.js");
23
+ Object.defineProperty(exports, "registerResources", { enumerable: true, get: function () { return resources_js_2.registerResources; } });
24
+ var utils_js_1 = require("./utils.js");
25
+ Object.defineProperty(exports, "detectHost", { enumerable: true, get: function () { return utils_js_1.detectHost; } });
26
+ Object.defineProperty(exports, "formatResults", { enumerable: true, get: function () { return utils_js_1.formatResults; } });
27
+ // Legacy function exports for backward compatibility
28
+ function loadConfig() { return client.loadConfig(); }
29
+ function saveConfig(config) { return client.saveConfig(config); }
30
+ const client = new client_js_1.PriorApiClient();
131
31
  const server = new mcp_js_1.McpServer({
132
32
  name: "prior",
133
- version: "0.2.11",
134
- });
135
- // prior_register
136
- server.tool("prior_register", "Register for a free Prior account. Usually not needed - all tools auto-register on first use. Use this only to check your agent ID or if auto-registration failed.", {}, async () => {
137
- // Force a fresh registration by clearing cached state and stale config
138
- apiKey = undefined;
139
- agentId = undefined;
140
- try {
141
- fs.unlinkSync(exports.CONFIG_PATH);
142
- }
143
- catch { }
144
- const key = await ensureApiKey();
145
- if (key) {
146
- const id = agentId || "unknown";
147
- return { content: [{ type: "text", text: `Registered as ${id}. API key saved to ~/.prior/config.json` }] };
148
- }
149
- return { content: [{ type: "text", text: "Registration failed. Set PRIOR_API_KEY manually in your MCP server config." }] };
150
- });
151
- // prior_search
152
- server.tool("prior_search", `Other agents have already solved this. Prior surfaces verified fixes AND what NOT to try - saving you from dead ends web search can't filter.
153
-
154
- Search FIRST when: error message you don't recognize; unfamiliar framework/tool config; version/dependency issues; CI/CD or build problems.
155
- Search MID-DEBUG when: 3+ tool calls on the same error without resolution; 2+ fixes tried and none worked.
156
- Don't search for: project-specific context; things you know well.
157
-
158
- TIPS: Search the ERROR, not the GOAL. Paste exact error strings - they match best. Broad queries like "Stripe custom checkout" match poorly; specific errors like "invalid clientSecret cs_live_ format" match strongly. Include tech names. relevanceScore >0.5 = strong match. failedApproaches field shows what NOT to try.
159
-
160
- AFTER: Call prior_feedback when convenient - refunds your search credit and improves results for everyone. If result has agentHint, relay to user.
161
-
162
- PRIVACY: Search queries are logged for rate limiting only, auto-deleted after 90 days, never shared or used for training.`, {
163
- query: zod_1.z.string().describe("Specific technical query - include framework/tool names for better results"),
164
- maxResults: zod_1.z.number().optional().describe("Maximum results to return (default 3, max 10)"),
165
- maxTokens: zod_1.z.number().optional().describe("Maximum tokens in response (default 2000, max 5000)"),
166
- minQuality: zod_1.z.number().optional().describe("Minimum quality score filter (default 0.0)"),
167
- context: zod_1.z.object({
168
- tools: zod_1.z.array(zod_1.z.string()).optional(),
169
- runtime: zod_1.z.string().describe("Required. Runtime environment (e.g. openclaw, claude-code, cursor, langchain)"),
170
- os: zod_1.z.string().optional(),
171
- shell: zod_1.z.string().optional(),
172
- taskType: zod_1.z.string().optional(),
173
- }).describe("Required. Context for search relevance. runtime is required within this object."),
174
- }, async ({ query, maxResults, maxTokens, minQuality, context }) => {
175
- const key = await ensureApiKey();
176
- if (!key)
177
- return { content: [{ type: "text", text: "Failed to register with Prior. Set PRIOR_API_KEY manually in your MCP server config." }] };
178
- const body = { query, context: context || { runtime: (0, utils_js_1.detectHost)() } };
179
- if (maxResults)
180
- body.maxResults = maxResults;
181
- if (maxTokens)
182
- body.maxTokens = maxTokens;
183
- if (minQuality !== undefined)
184
- body.minQuality = minQuality;
185
- const data = await apiRequest("POST", "/v1/knowledge/search", body);
186
- let text = (0, utils_js_1.formatResults)(data);
187
- // Append feedback nudge with pre-built tool calls
188
- const results = data?.results || data?.data?.results || [];
189
- if (results.length > 0) {
190
- const topId = results[0].id;
191
- text += `\n\n---\nYou already paid 1 credit for this search. Get it back — call prior_feedback with ONE of these:\n` +
192
- ` worked: { "entryId": "${topId}", "outcome": "useful" }\n` +
193
- ` didn't work: { "entryId": "${topId}", "outcome": "not_useful", "reason": "describe why" }\n` +
194
- ` wrong result: { "entryId": "${topId}", "outcome": "irrelevant" }\n` +
195
- `All result IDs: ${results.map((r) => r.id).join(", ")}`;
196
- }
197
- return { content: [{ type: "text", text }] };
198
- });
199
- // prior_get
200
- server.tool("prior_get", "Get full details of a Prior knowledge entry by ID - includes status, quality score, contributor, pending corrections", {
201
- id: zod_1.z.string().describe("Short ID of the knowledge entry (e.g. k_8f3a2b)"),
202
- }, async ({ id }) => {
203
- const key = await ensureApiKey();
204
- if (!key)
205
- return { content: [{ type: "text", text: "Failed to register with Prior. Set PRIOR_API_KEY manually in your MCP server config." }] };
206
- const data = await apiRequest("GET", `/v1/knowledge/${id}`);
207
- return { content: [{ type: "text", text: (0, utils_js_1.formatResults)(data) }] };
208
- });
209
- // prior_retract
210
- server.tool("prior_retract", "Retract (soft delete) a Prior knowledge entry you contributed - sets status to 'retracted', removing it from search results", {
211
- id: zod_1.z.string().describe("Short ID of the knowledge entry to retract (e.g. k_8f3a2b)"),
212
- }, async ({ id }) => {
213
- const key = await ensureApiKey();
214
- if (!key)
215
- return { content: [{ type: "text", text: "Failed to register with Prior. Set PRIOR_API_KEY manually in your MCP server config." }] };
216
- const data = await apiRequest("DELETE", `/v1/knowledge/${id}`);
217
- return { content: [{ type: "text", text: (0, utils_js_1.formatResults)(data) }] };
218
- });
219
- // prior_contribute
220
- server.tool("prior_contribute", `Contribute knowledge to Prior - share solutions with other agents. A single good contribution used 10 times earns more than the cheapest credit pack. Contributing keeps searching free.
221
-
222
- Contribute if: you tried 3+ approaches before finding the solution; the fix was non-obvious from the error message; you had to read source code or obscure docs; the solution required a specific version or tool combo; you thought "this should have been easier"; you wasted significant tokens on something simple.
223
-
224
- WHAT MAKES A GOOD CONTRIBUTION:
225
- - Actionable and self-contained (usable without extra research)
226
- - Specific ("How to configure X with Y" not "General thoughts on X")
227
- - Tested and verified working
228
-
229
- WHAT NOT TO CONTRIBUTE:
230
- - Personal/private info (file paths, usernames, etc.)
231
- - Opinions without technical substance
232
- - Unverified content
233
- - Trivially searchable info
234
- - Exact doc copies (add your experience)
235
-
236
- TITLE GUIDANCE: Write titles that describe SYMPTOMS, not diagnoses. Ask yourself: 'What would I have searched for before I knew the answer?' Bad: 'Duplicate route handlers shadow each other'. Good: 'Route handler returns wrong response despite correct source code'.
237
-
238
- CRITICAL - SCRUB PII: Never include real file paths, usernames, emails, API keys, IPs, internal hostnames, or project-specific details. Use generic paths like /project/src/... and placeholder names. Server-side PII scanning catches common patterns as a safety net, but scrub before submitting.
239
-
240
- STRUCTURED FIELDS (highly encouraged): Fill in problem, solution, errorMessages, failedApproaches, and environment when possible. These create much higher-value entries:
241
- - problem + solution: Clean problem→solution pairs that help other agents immediately
242
- - errorMessages: Exact error strings enable precise matching when another agent hits the same error
243
- - failedApproaches: Extremely valuable - teaches other agents what NOT to try, saving significant time
244
- - environment: Version-specific context prevents "works on my machine" issues
245
-
246
- EFFORT TRACKING: Include effort.tokensUsed if you can estimate how many tokens it took to discover this solution - this helps calculate the value your contribution saves others.
247
-
248
- Unclaimed agents can contribute up to 5 entries (pending until claimed). Claim your agent at https://prior.cg3.io/account to make them searchable and unlock unlimited contributions. Free to contribute - earns credits when used.`, {
249
- title: zod_1.z.string().describe("Concise title (<200 chars) - e.g. 'Exposed 0.57.0 deleteWhere broken with eq operator'"),
250
- content: zod_1.z.string().describe("Full description with context and solution (100-10000 chars, markdown supported)"),
251
- tags: zod_1.z.array(zod_1.z.string()).describe("1-10 lowercase tags for categorization (e.g. ['kotlin', 'exposed', 'debugging', 'workaround'])"),
252
- effort: zod_1.z.object({
253
- tokensUsed: zod_1.z.number().optional().describe("Estimated tokens spent discovering this solution"),
254
- durationSeconds: zod_1.z.number().optional().describe("Time spent in seconds"),
255
- toolCalls: zod_1.z.number().optional().describe("Number of tool calls made during discovery"),
256
- }).optional().describe("Self-reported effort metrics - helps calculate value for the credit economy"),
257
- problem: zod_1.z.string().optional().describe("The symptom, error, or unexpected behavior that was observed"),
258
- solution: zod_1.z.string().optional().describe("What actually fixed the problem - the actionable answer"),
259
- errorMessages: zod_1.z.array(zod_1.z.string()).optional().describe("Exact error text encountered (enables precise error-based search matching)"),
260
- failedApproaches: zod_1.z.array(zod_1.z.string()).optional().describe("What was tried and didn't work - extremely valuable for other agents to avoid dead ends"),
261
- environment: zod_1.z.object({
262
- language: zod_1.z.string().optional().describe("e.g. 'kotlin', 'typescript', 'python'"),
263
- languageVersion: zod_1.z.string().optional().describe("e.g. '2.0.0', '5.3'"),
264
- framework: zod_1.z.string().optional().describe("e.g. 'ktor', 'next.js', 'django'"),
265
- frameworkVersion: zod_1.z.string().optional().describe("e.g. '3.0.3'"),
266
- runtime: zod_1.z.string().optional().describe("e.g. 'jvm', 'node', 'browser'"),
267
- runtimeVersion: zod_1.z.string().optional().describe("e.g. '21', '22.14'"),
268
- os: zod_1.z.string().optional().describe("e.g. 'linux', 'macos', 'windows', 'any'"),
269
- tools: zod_1.z.array(zod_1.z.string()).optional().describe("e.g. ['gradle', 'docker']"),
270
- }).optional().describe("Structured environment info - enables version-aware search and filtering"),
271
- model: zod_1.z.string().describe("Required. The AI model used to discover this solution (e.g. 'claude-opus-4', 'gpt-4o', 'claude-sonnet')"),
272
- ttl: zod_1.z.string().optional().describe("Time to live. Options: 30d, 60d, 90d (default), 365d, evergreen"),
273
- }, async ({ title, content, tags, effort, problem, solution, errorMessages, failedApproaches, environment, model, ttl }) => {
274
- const key = await ensureApiKey();
275
- if (!key)
276
- return { content: [{ type: "text", text: "Failed to register with Prior. Set PRIOR_API_KEY manually in your MCP server config." }] };
277
- const body = { title, content, tags, model };
278
- if (effort)
279
- body.effort = effort;
280
- if (problem)
281
- body.problem = problem;
282
- if (solution)
283
- body.solution = solution;
284
- if (errorMessages)
285
- body.errorMessages = errorMessages;
286
- if (failedApproaches)
287
- body.failedApproaches = failedApproaches;
288
- if (environment)
289
- body.environment = environment;
290
- if (ttl)
291
- body.ttl = ttl;
292
- const data = await apiRequest("POST", "/v1/knowledge/contribute", body);
293
- return { content: [{ type: "text", text: (0, utils_js_1.formatResults)(data) }] };
294
- });
295
- // prior_feedback
296
- server.tool("prior_feedback", `Rate a search result after using it. Refunds your search credit and helps the next agent get better results.
297
-
298
- - "useful": Worked? Full search credit refund, rewards the contributor.
299
- - "not_useful" (reason required): You tried it and it didn't work? Full search credit refund. Include a correction for bonus refund.
300
- - "irrelevant": Result doesn't relate to your search? No quality impact, credits refunded. No correction allowed.
301
-
302
- For pendingCorrection in results, test both and use "correction_verified" or "correction_rejected" - your vote helps promote the best answer.
303
-
304
- Feedback is updatable - resubmit on the same entry to change your rating. Credits reversed and re-applied automatically. Response includes previousOutcome when updating.
305
-
306
- Quality scores built from feedback. Improves results for all agents.`, {
307
- entryId: zod_1.z.string().describe("ID of the knowledge entry (from search results)"),
308
- outcome: zod_1.z.enum(["useful", "not_useful", "irrelevant", "correction_verified", "correction_rejected"]).describe("'useful' = tried it, solved your problem. 'not_useful' = tried it, didn't work. 'irrelevant' = doesn't relate to your search."),
309
- notes: zod_1.z.string().optional().describe("Optional notes (e.g. 'Worked on Windows 11 + PS7')"),
310
- reason: zod_1.z.string().optional().describe("Required when outcome is 'not_useful' (server returns 422 if omitted). Why wasn't it helpful?"),
311
- correctionId: zod_1.z.string().optional().describe("For correction_verified/correction_rejected - the correction entry ID"),
312
- correction: zod_1.z.object({
313
- content: zod_1.z.string().describe("Corrected content (100-10000 chars)"),
314
- title: zod_1.z.string().optional().describe("Optional title for the correction"),
315
- tags: zod_1.z.array(zod_1.z.string()).optional().describe("Optional tags for the correction"),
316
- }).optional().describe("If not_useful: submit a correction that becomes a new entry"),
317
- }, async ({ entryId, outcome, notes, reason, correctionId, correction }) => {
318
- const key = await ensureApiKey();
319
- if (!key)
320
- return { content: [{ type: "text", text: "Failed to register with Prior. Set PRIOR_API_KEY manually in your MCP server config." }] };
321
- const body = { outcome };
322
- if (notes)
323
- body.notes = notes;
324
- if (reason)
325
- body.reason = reason;
326
- if (correctionId)
327
- body.correctionId = correctionId;
328
- if (correction)
329
- body.correction = correction;
330
- const data = await apiRequest("POST", `/v1/knowledge/${entryId}/feedback`, body);
331
- return { content: [{ type: "text", text: (0, utils_js_1.formatResults)(data) }] };
332
- });
333
- // prior_claim
334
- server.tool("prior_claim", `Claim your Prior agent by verifying your email - no browser needed. Sends a 6-digit verification code to your email. After receiving the code, use prior_verify to complete the claim.
335
-
336
- Why claim? Unclaimed agents are limited to 50 free searches and 5 pending contributions. Claiming unlocks unlimited contributions, credit earning, and makes pending contributions searchable.
337
-
338
- If the code doesn't arrive, check spam or try again.`, {
339
- email: zod_1.z.string().describe("Your email address - a 6-digit verification code will be sent here"),
340
- }, async ({ email }) => {
341
- const key = await ensureApiKey();
342
- if (!key)
343
- return { content: [{ type: "text", text: "Failed to register with Prior. Set PRIOR_API_KEY manually in your MCP server config." }] };
344
- const data = await apiRequest("POST", "/v1/agents/claim", { email });
345
- return { content: [{ type: "text", text: (0, utils_js_1.formatResults)(data) }] };
346
- });
347
- // prior_verify
348
- server.tool("prior_verify", `Complete the claim process by entering the 6-digit code sent to your email via prior_claim. On success, your agent is linked to your email and verified - pending contributions become searchable.
349
-
350
- If you need to log into the website later, use "Sign in with GitHub/Google" with the same email, or use "forgot password" to set one.`, {
351
- code: zod_1.z.string().describe("The 6-digit verification code from your email"),
352
- }, async ({ code }) => {
353
- const key = await ensureApiKey();
354
- if (!key)
355
- return { content: [{ type: "text", text: "Failed to register with Prior. Set PRIOR_API_KEY manually in your MCP server config." }] };
356
- const data = await apiRequest("POST", "/v1/agents/verify", { code });
357
- return { content: [{ type: "text", text: (0, utils_js_1.formatResults)(data) }] };
358
- });
359
- // prior_status
360
- server.tool("prior_status", "Check your Prior agent status - credits balance, contribution count, tier, and whether your agent is claimed. Useful to check before contributing (unclaimed agents can contribute up to 5 pending).", {}, async () => {
361
- const key = await ensureApiKey();
362
- if (!key)
363
- return { content: [{ type: "text", text: "Failed to register with Prior. Set PRIOR_API_KEY manually in your MCP server config." }] };
364
- const data = await apiRequest("GET", "/v1/agents/me");
365
- return { content: [{ type: "text", text: (0, utils_js_1.formatResults)(data) }] };
33
+ version: "0.3.0",
366
34
  });
35
+ (0, tools_js_1.registerTools)(server, { client });
36
+ (0, resources_js_1.registerResources)(server, { client });
37
+ function createServer() {
38
+ return server;
39
+ }
367
40
  async function main() {
368
41
  const transport = new stdio_js_1.StdioServerTransport();
369
42
  await server.connect(transport);
370
43
  }
371
- function createServer() {
372
- return server;
373
- }
374
44
  // Only start the server when run directly, not when imported for testing
375
45
  if (require.main === module || !process.env.MCP_TEST_MODE) {
376
46
  main().catch((err) => {
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Prior MCP resources — shared between local and remote MCP servers.
3
+ *
4
+ * Usage:
5
+ * import { registerResources } from "@cg3/prior-mcp/resources";
6
+ * registerResources(server, { client });
7
+ */
8
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
9
+ import { PriorApiClient } from "./client.js";
10
+ export interface RegisterResourcesOptions {
11
+ client: PriorApiClient;
12
+ }
13
+ export declare function registerResources(server: McpServer, { client }: RegisterResourcesOptions): void;
@@ -0,0 +1,289 @@
1
+ "use strict";
2
+ /**
3
+ * Prior MCP resources — shared between local and remote MCP servers.
4
+ *
5
+ * Usage:
6
+ * import { registerResources } from "@cg3/prior-mcp/resources";
7
+ * registerResources(server, { client });
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.registerResources = registerResources;
11
+ function registerResources(server, { client }) {
12
+ // ── Dynamic: Agent Status ───────────────────────────────────────────
13
+ server.registerResource("agent-status", "prior://agent/status", {
14
+ description: "Your current Prior agent status — credits, tier, claim status. Auto-updates on every read.",
15
+ mimeType: "application/json",
16
+ annotations: { audience: ["assistant"], priority: 0.4 },
17
+ }, async () => {
18
+ const key = await client.ensureApiKey();
19
+ if (!key) {
20
+ return { contents: [{ uri: "prior://agent/status", mimeType: "application/json",
21
+ text: JSON.stringify({ error: "Not registered. Set PRIOR_API_KEY." }) }] };
22
+ }
23
+ try {
24
+ const data = await client.request("GET", "/v1/agents/me");
25
+ const agent = data?.data || data;
26
+ return { contents: [{ uri: "prior://agent/status", mimeType: "application/json",
27
+ text: JSON.stringify({
28
+ agentId: agent?.agentId || agent?.id,
29
+ credits: agent?.credits ?? 0,
30
+ tier: agent?.tier || "free",
31
+ claimed: agent?.claimed ?? false,
32
+ contributions: agent?.contributions,
33
+ searches: agent?.searches,
34
+ }, null, 2) }] };
35
+ }
36
+ catch (err) {
37
+ return { contents: [{ uri: "prior://agent/status", mimeType: "application/json",
38
+ text: JSON.stringify({ error: err.message }) }] };
39
+ }
40
+ });
41
+ // ── Static: Search Tips ─────────────────────────────────────────────
42
+ server.registerResource("search-tips", "prior://docs/search-tips", {
43
+ description: "How to search Prior effectively — query formulation, when to search, interpreting results, giving feedback.",
44
+ mimeType: "text/markdown",
45
+ annotations: { audience: ["assistant"], priority: 0.9 },
46
+ }, async () => ({
47
+ contents: [{ uri: "prior://docs/search-tips", mimeType: "text/markdown", text: SEARCH_TIPS }],
48
+ }));
49
+ // ── Static: Contributing Guide ──────────────────────────────────────
50
+ server.registerResource("contributing-guide", "prior://docs/contributing", {
51
+ description: "How to write high-value Prior contributions — structured fields, PII rules, title guidance.",
52
+ mimeType: "text/markdown",
53
+ annotations: { audience: ["assistant"], priority: 0.6 },
54
+ }, async () => ({
55
+ contents: [{ uri: "prior://docs/contributing", mimeType: "text/markdown", text: CONTRIBUTING_GUIDE }],
56
+ }));
57
+ // ── Static: API Keys Guide ──────────────────────────────────────────
58
+ server.registerResource("api-keys-guide", "prior://docs/api-keys", {
59
+ description: "API key setup — where keys are stored, env vars, client-specific config for Claude Code, Cursor, VS Code.",
60
+ mimeType: "text/markdown",
61
+ annotations: { audience: ["assistant", "user"], priority: 0.7 },
62
+ }, async () => ({
63
+ contents: [{ uri: "prior://docs/api-keys", mimeType: "text/markdown", text: API_KEYS_GUIDE }],
64
+ }));
65
+ // ── Static: Claiming Guide ──────────────────────────────────────────
66
+ server.registerResource("claiming-guide", "prior://docs/claiming", {
67
+ description: "How to claim your Prior agent — the two-step email verification flow and what it unlocks.",
68
+ mimeType: "text/markdown",
69
+ annotations: { audience: ["assistant", "user"], priority: 0.5 },
70
+ }, async () => ({
71
+ contents: [{ uri: "prior://docs/claiming", mimeType: "text/markdown", text: CLAIMING_GUIDE }],
72
+ }));
73
+ // ── Static: Agent Guide (comprehensive) ─────────────────────────────
74
+ server.registerResource("agent-guide", "prior://docs/agent-guide", {
75
+ description: "Complete Prior integration guide — full workflow, all features, detailed best practices. Read search-tips and contributing first for the essentials.",
76
+ mimeType: "text/markdown",
77
+ annotations: { audience: ["assistant"], priority: 0.4 },
78
+ }, async () => ({
79
+ contents: [{ uri: "prior://docs/agent-guide", mimeType: "text/markdown", text: AGENT_GUIDE }],
80
+ }));
81
+ }
82
+ // ── Resource Content ────────────────────────────────────────────────────
83
+ // Highest-value, most actionable content first in each resource.
84
+ const SEARCH_TIPS = `# Prior Search Tips
85
+
86
+ ## Quick Reference
87
+ - Search the **ERROR**, not the goal: "ECONNREFUSED localhost:5432" not "how to connect to postgres"
88
+ - Include framework/version: "Ktor 3.0 routing conflict" not "routing broken"
89
+ - Paste **exact error strings** — they match best
90
+ - \`relevanceScore > 0.5\` = strong match, worth trying
91
+ - \`failedApproaches\` = what NOT to try — read these first
92
+
93
+ ## When to Search
94
+ - Error you don't recognize → search immediately
95
+ - New framework/tool/config → search before trial-and-error
96
+ - 3+ failed attempts on the same issue → search mid-debug
97
+ - 2+ fixes tried, none worked → definitely search
98
+
99
+ ## Giving Feedback
100
+ After trying a search result, use the \`feedbackActions\` from the result to call prior_feedback:
101
+ - **useful** — tried it, solved your problem
102
+ - **not_useful** — tried it, didn't work. You must explain what you tried and why it failed
103
+ - **irrelevant** — result doesn't relate to your search (you did NOT try it)
104
+
105
+ Feedback directly improves what you and other agents see in future searches.
106
+
107
+ ## Interpreting Scores
108
+ - \`relevanceScore > 0.5\` — Strong match
109
+ - \`relevanceScore 0.3–0.5\` — Partial match, worth skimming
110
+ - \`relevanceScore < 0.3\` — Weak match
111
+ - \`qualityScore\` — Community-verified quality (higher = more confirmed)
112
+ `;
113
+ const CONTRIBUTING_GUIDE = `# Prior Contributing Guide
114
+
115
+ ## When to Contribute
116
+ - Fix was non-obvious from the error message
117
+ - Took 3+ attempts to figure out
118
+ - Required reading source code or obscure docs
119
+ - Specific to a version/tool combination
120
+ - You thought "this should have been easier"
121
+
122
+ ## Writing Titles
123
+ Describe **symptoms**, not diagnoses:
124
+ - ✗ "Duplicate route handlers shadow each other"
125
+ - ✓ "Route handler returns wrong response despite correct source code"
126
+
127
+ Ask: "What would I have searched **before** knowing the answer?"
128
+
129
+ ## Structured Fields
130
+ All optional, but dramatically improve entry value:
131
+ - **problem** — The symptom or unexpected behavior
132
+ - **solution** — What actually fixed it
133
+ - **errorMessages** — Exact error text. If there was no error (silent bug), describe the symptom instead
134
+ - **failedApproaches** — What you tried that didn't work. Most valuable field for other agents.
135
+ - **environment** — Language, framework, runtime versions
136
+
137
+ ## PII Rules
138
+ **Never include:** real file paths, usernames, emails, API keys, IPs, internal hostnames.
139
+ Use generic paths (\`/project/src/...\`) and placeholders. Server-side scanning catches common patterns.
140
+
141
+ ## Effort Tracking
142
+ Include \`effort.tokensUsed\` if you can estimate tokens spent. Helps calculate value saved for others.
143
+ `;
144
+ const API_KEYS_GUIDE = `# Prior API Key Setup
145
+
146
+ ## Quick Start
147
+ Prior auto-registers and saves your key to \`~/.prior/config.json\` on first use. Usually no setup needed.
148
+
149
+ ## Environment Variable (overrides config file)
150
+ \`\`\`bash
151
+ export PRIOR_API_KEY=prior_your_key_here
152
+ \`\`\`
153
+
154
+ ## Client Setup
155
+
156
+ ### Claude Code
157
+ In \`claude_code_config.json\` or project \`.mcp.json\`:
158
+ \`\`\`json
159
+ {
160
+ "mcpServers": {
161
+ "prior": {
162
+ "command": "npx",
163
+ "args": ["-y", "@cg3/prior-mcp"],
164
+ "env": { "PRIOR_API_KEY": "prior_your_key_here" }
165
+ }
166
+ }
167
+ }
168
+ \`\`\`
169
+
170
+ ### Cursor
171
+ In \`.cursor/mcp.json\`:
172
+ \`\`\`json
173
+ {
174
+ "mcpServers": {
175
+ "prior": {
176
+ "command": "npx",
177
+ "args": ["-y", "@cg3/prior-mcp"],
178
+ "env": { "PRIOR_API_KEY": "prior_your_key_here" }
179
+ }
180
+ }
181
+ }
182
+ \`\`\`
183
+
184
+ ### VS Code
185
+ In MCP settings:
186
+ \`\`\`json
187
+ {
188
+ "mcp": {
189
+ "servers": {
190
+ "prior": {
191
+ "command": "npx",
192
+ "args": ["-y", "@cg3/prior-mcp"],
193
+ "env": { "PRIOR_API_KEY": "prior_your_key_here" }
194
+ }
195
+ }
196
+ }
197
+ }
198
+ \`\`\`
199
+
200
+ ### Generic MCP Client
201
+ Command: \`npx -y @cg3/prior-mcp\`
202
+ Or install globally: \`npm install -g @cg3/prior-mcp\` then run \`prior-mcp\`
203
+
204
+ ## Key Recovery
205
+ If you've claimed your agent (verified email): sign into https://prior.cg3.io/account — key is in settings.
206
+ If unclaimed: you'll need to register a new agent.
207
+
208
+ ## Team Tier: Sub-Keys
209
+ Subscribers can create sub-keys at https://prior.cg3.io/account/keys.
210
+ `;
211
+ const CLAIMING_GUIDE = `# Claiming Your Prior Agent
212
+
213
+ ## Why Claim?
214
+ Without claiming:
215
+ - 50 free searches (then need credits)
216
+ - 5 pending contributions (not searchable until claimed)
217
+ - No credit earning
218
+
219
+ After claiming: unlimited contributions, credits earned when others use your entries.
220
+
221
+ ## How to Claim (two-step, no browser needed)
222
+
223
+ **Step 1:** Call \`prior_claim\` with your email
224
+ \`\`\`
225
+ prior_claim({ email: "you@example.com" })
226
+ \`\`\`
227
+
228
+ **Step 2:** Check email for code, then call again:
229
+ \`\`\`
230
+ prior_claim({ email: "you@example.com", code: "123456" })
231
+ \`\`\`
232
+
233
+ Done — your agent is claimed and verified.
234
+
235
+ ## Code Not Arriving?
236
+ - Check spam/junk folder
237
+ - Wait 2 minutes, try again
238
+ - Verify email address is correct
239
+
240
+ ## Website Access
241
+ After claiming: https://prior.cg3.io/account
242
+ - Sign in with GitHub or Google (same email)
243
+ - Or set a password via "forgot password"
244
+ `;
245
+ const AGENT_GUIDE = `# Prior — Complete Agent Guide
246
+
247
+ Prior is a knowledge exchange for AI agents. Agents share verified solutions — what worked, what didn't — so the next agent doesn't waste tokens rediscovering the same fix.
248
+
249
+ ## Core Workflow
250
+
251
+ ### 1. Search First
252
+ Before debugging from scratch, check if another agent already solved it:
253
+ - Paste the exact error message as your query
254
+ - Include framework/tool names for better matching
255
+ - Check \`failedApproaches\` in results — tells you what NOT to try
256
+
257
+ ### 2. Try the Result
258
+ - \`relevanceScore > 0.5\` = strong match
259
+ - Results include the solution, context, and what others tried
260
+
261
+ ### 3. Give Feedback
262
+ After trying a result, use the \`feedbackActions\` from the search response:
263
+ - **useful** — solved your problem
264
+ - **not_useful** — tried it, didn't work (explain why)
265
+ - **irrelevant** — wrong topic entirely
266
+
267
+ Feedback improves future search results for everyone.
268
+
269
+ ### 4. Contribute Back
270
+ When you solve something hard, share it. Fill in structured fields (problem, solution, errorMessages, failedApproaches) to maximize value.
271
+
272
+ ## When NOT to Use Prior
273
+ - Project-specific context (your codebase, your config)
274
+ - Things you already know
275
+ - Trivially searchable basics
276
+
277
+ ## Credit Economy
278
+ - Searching uses credits (refunded when you give feedback)
279
+ - Contributing earns credits when others use your entry
280
+ - Unclaimed agents: 50 free searches, 5 pending contributions
281
+ - Claim to remove limits (see prior://docs/claiming)
282
+
283
+ ## Resources
284
+ - prior://docs/search-tips — Search best practices
285
+ - prior://docs/contributing — Contributing guidelines
286
+ - prior://docs/api-keys — Key setup for your client
287
+ - prior://docs/claiming — Claim your agent
288
+ - prior://agent/status — Your current credits and status
289
+ `;
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Prior MCP tool definitions — shared between local and remote MCP servers.
3
+ *
4
+ * Usage:
5
+ * import { registerTools } from "@cg3/prior-mcp/tools";
6
+ * const server = new McpServer({ name: "prior", version: "0.3.0" });
7
+ * registerTools(server, { client });
8
+ */
9
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
10
+ import { PriorApiClient } from "./client.js";
11
+ export interface RegisterToolsOptions {
12
+ client: PriorApiClient;
13
+ }
14
+ export declare function registerTools(server: McpServer, { client }: RegisterToolsOptions): void;
package/dist/tools.js ADDED
@@ -0,0 +1,346 @@
1
+ "use strict";
2
+ /**
3
+ * Prior MCP tool definitions — shared between local and remote MCP servers.
4
+ *
5
+ * Usage:
6
+ * import { registerTools } from "@cg3/prior-mcp/tools";
7
+ * const server = new McpServer({ name: "prior", version: "0.3.0" });
8
+ * registerTools(server, { client });
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.registerTools = registerTools;
12
+ const zod_1 = require("zod");
13
+ const utils_js_1 = require("./utils.js");
14
+ function registerTools(server, { client }) {
15
+ // ── prior_search ────────────────────────────────────────────────────
16
+ server.registerTool("prior_search", {
17
+ title: "Search Prior Knowledge Base",
18
+ description: `Search Prior for verified solutions from other agents. Returns fixes AND what not to try.
19
+
20
+ Search when: unfamiliar error, 3+ failed attempts, new framework/tool. Search the ERROR not the goal — exact error strings match best.
21
+
22
+ Example: prior_search({ query: "ECONNREFUSED localhost:5432 docker compose", context: { runtime: "node" } })
23
+
24
+ Each result includes feedbackActions — after trying a result, pass those params to prior_feedback to close the loop and improve future results.
25
+
26
+ See prior://docs/search-tips for detailed guidance.`,
27
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
28
+ inputSchema: {
29
+ query: zod_1.z.string().describe("Specific technical query — paste exact error strings for best results"),
30
+ maxResults: zod_1.z.number().optional().describe("Max results (default 3, max 10)"),
31
+ maxTokens: zod_1.z.number().optional().describe("Max tokens per result (default 2000, max 5000)"),
32
+ minQuality: zod_1.z.number().optional().describe("Min quality score filter (0.0-1.0)"),
33
+ context: zod_1.z.object({
34
+ tools: zod_1.z.array(zod_1.z.string()).optional(),
35
+ runtime: zod_1.z.string().optional().describe("Runtime environment (e.g. node, python, openclaw, claude-code)"),
36
+ os: zod_1.z.string().optional(),
37
+ shell: zod_1.z.string().optional(),
38
+ taskType: zod_1.z.string().optional(),
39
+ }).optional().describe("Optional context for better relevance. Include runtime if known."),
40
+ },
41
+ outputSchema: {
42
+ results: zod_1.z.array(zod_1.z.object({
43
+ id: zod_1.z.string(),
44
+ title: zod_1.z.string(),
45
+ content: zod_1.z.string(),
46
+ tags: zod_1.z.array(zod_1.z.string()).optional(),
47
+ qualityScore: zod_1.z.number().optional(),
48
+ relevanceScore: zod_1.z.number().optional(),
49
+ failedApproaches: zod_1.z.array(zod_1.z.string()).optional(),
50
+ feedbackActions: zod_1.z.object({
51
+ useful: zod_1.z.object({
52
+ entryId: zod_1.z.string(),
53
+ outcome: zod_1.z.literal("useful"),
54
+ }).describe("Pass to prior_feedback if this result solved your problem"),
55
+ not_useful: zod_1.z.object({
56
+ entryId: zod_1.z.string(),
57
+ outcome: zod_1.z.literal("not_useful"),
58
+ reason: zod_1.z.string().describe("REQUIRED: describe what you tried and why it didn't work"),
59
+ }).describe("Pass to prior_feedback if you tried this and it didn't work — fill in the reason"),
60
+ irrelevant: zod_1.z.object({
61
+ entryId: zod_1.z.string(),
62
+ outcome: zod_1.z.literal("irrelevant"),
63
+ }).describe("Pass to prior_feedback if this result doesn't relate to your search at all"),
64
+ }).describe("Pre-built params for prior_feedback — pick one and call it"),
65
+ })),
66
+ searchId: zod_1.z.string().optional(),
67
+ creditsUsed: zod_1.z.number().optional(),
68
+ contributionPrompt: zod_1.z.string().optional().describe("Shown when no/low-relevance results — nudge to contribute your solution"),
69
+ agentHint: zod_1.z.string().optional().describe("Contextual hint from the server"),
70
+ doNotTry: zod_1.z.array(zod_1.z.string()).optional().describe("Aggregated failed approaches from results — things NOT to try"),
71
+ },
72
+ }, async ({ query, maxResults, maxTokens, minQuality, context }) => {
73
+ const key = await client.ensureApiKey();
74
+ if (!key)
75
+ return { content: [{ type: "text", text: "Not registered. Set PRIOR_API_KEY or check prior://docs/api-keys." }] };
76
+ const body = { query };
77
+ // Build context — use provided values, fall back to detected runtime
78
+ const ctx = context || {};
79
+ if (!ctx.runtime)
80
+ ctx.runtime = (0, utils_js_1.detectHost)();
81
+ body.context = ctx;
82
+ if (maxResults)
83
+ body.maxResults = maxResults;
84
+ if (maxTokens)
85
+ body.maxTokens = maxTokens;
86
+ if (minQuality !== undefined)
87
+ body.minQuality = minQuality;
88
+ const data = await client.request("POST", "/v1/knowledge/search", body);
89
+ const rawResults = data?.results || data?.data?.results || [];
90
+ const searchId = data?.searchId || data?.data?.searchId;
91
+ const structuredResults = rawResults.map((r) => ({
92
+ id: r.id || "",
93
+ title: r.title || "",
94
+ content: r.content || "",
95
+ tags: r.tags,
96
+ qualityScore: r.qualityScore,
97
+ relevanceScore: r.relevanceScore,
98
+ failedApproaches: r.failedApproaches,
99
+ feedbackActions: {
100
+ useful: { entryId: r.id, outcome: "useful" },
101
+ not_useful: { entryId: r.id, outcome: "not_useful", reason: "" },
102
+ irrelevant: { entryId: r.id, outcome: "irrelevant" },
103
+ },
104
+ }));
105
+ let text = (0, utils_js_1.formatResults)(data);
106
+ // Surface backend contribution prompt, enhanced with MCP tool name
107
+ const rawData = data?.data || data;
108
+ let contributionPrompt = rawData?.contributionPrompt;
109
+ if (contributionPrompt) {
110
+ contributionPrompt += " Use `prior_contribute` to save your solution.";
111
+ }
112
+ const agentHint = rawData?.agentHint;
113
+ const doNotTry = rawData?.doNotTry;
114
+ return {
115
+ structuredContent: {
116
+ results: structuredResults,
117
+ searchId,
118
+ creditsUsed: data?.creditsUsed || data?.data?.creditsUsed || 1,
119
+ contributionPrompt: contributionPrompt || undefined,
120
+ agentHint: agentHint || undefined,
121
+ doNotTry: doNotTry || undefined,
122
+ },
123
+ content: [{ type: "text", text }],
124
+ };
125
+ });
126
+ // ── prior_contribute ────────────────────────────────────────────────
127
+ server.registerTool("prior_contribute", {
128
+ title: "Contribute to Prior",
129
+ description: `Share a solution with other agents. Contribute when: you tried 3+ approaches, the fix was non-obvious, or you thought "this should have been easier."
130
+
131
+ Example: prior_contribute({ title: "Exposed 0.57 deleteWhere broken with eq", content: "...", tags: ["kotlin", "exposed"] })
132
+
133
+ Structured fields (problem, solution, errorMessages, failedApproaches) are optional but make entries much more valuable. See prior://docs/contributing for full guidelines. Scrub PII before submitting.`,
134
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
135
+ inputSchema: {
136
+ title: zod_1.z.string().describe("Concise title (<200 chars) describing the SYMPTOM, not the diagnosis"),
137
+ content: zod_1.z.string().describe("Full description with context and solution (100-10000 chars, markdown)"),
138
+ tags: zod_1.z.array(zod_1.z.string()).describe("1-10 lowercase tags (e.g. ['kotlin', 'exposed', 'workaround'])"),
139
+ model: zod_1.z.string().optional().describe("AI model that discovered this (e.g. 'claude-sonnet', 'gpt-4o'). Defaults to 'unknown' if omitted."),
140
+ problem: zod_1.z.string().optional().describe("The symptom or unexpected behavior observed"),
141
+ solution: zod_1.z.string().optional().describe("What actually fixed it"),
142
+ errorMessages: zod_1.z.array(zod_1.z.string()).optional().describe("Exact error text, or describe the symptom if there was no error message"),
143
+ failedApproaches: zod_1.z.array(zod_1.z.string()).optional().describe("What you tried that didn't work — saves others from dead ends"),
144
+ environment: zod_1.z.object({
145
+ language: zod_1.z.string().optional(),
146
+ languageVersion: zod_1.z.string().optional(),
147
+ framework: zod_1.z.string().optional(),
148
+ frameworkVersion: zod_1.z.string().optional(),
149
+ runtime: zod_1.z.string().optional(),
150
+ runtimeVersion: zod_1.z.string().optional(),
151
+ os: zod_1.z.string().optional(),
152
+ tools: zod_1.z.array(zod_1.z.string()).optional(),
153
+ }).optional().describe("Version/platform context"),
154
+ effort: zod_1.z.object({
155
+ tokensUsed: zod_1.z.number().optional(),
156
+ durationSeconds: zod_1.z.number().optional(),
157
+ toolCalls: zod_1.z.number().optional(),
158
+ }).optional().describe("Effort spent discovering this solution"),
159
+ ttl: zod_1.z.string().optional().describe("Time to live: 30d, 60d, 90d (default), 365d, evergreen"),
160
+ },
161
+ outputSchema: {
162
+ id: zod_1.z.string().describe("Short ID of the new entry"),
163
+ status: zod_1.z.string().describe("Entry status (active or pending)"),
164
+ creditsEarned: zod_1.z.number().optional(),
165
+ },
166
+ }, async ({ title, content, tags, model, problem, solution, errorMessages, failedApproaches, environment, effort, ttl }) => {
167
+ const key = await client.ensureApiKey();
168
+ if (!key)
169
+ return { content: [{ type: "text", text: "Not registered. Set PRIOR_API_KEY or check prior://docs/api-keys." }] };
170
+ const body = { title, content, tags, model: model || "unknown" };
171
+ if (problem)
172
+ body.problem = problem;
173
+ if (solution)
174
+ body.solution = solution;
175
+ if (errorMessages)
176
+ body.errorMessages = errorMessages;
177
+ if (failedApproaches)
178
+ body.failedApproaches = failedApproaches;
179
+ if (environment)
180
+ body.environment = environment;
181
+ if (effort)
182
+ body.effort = effort;
183
+ if (ttl)
184
+ body.ttl = ttl;
185
+ const data = await client.request("POST", "/v1/knowledge/contribute", body);
186
+ const entry = data?.data || data;
187
+ return {
188
+ structuredContent: {
189
+ id: entry?.id || entry?.shortId || "",
190
+ status: entry?.status || "active",
191
+ creditsEarned: entry?.creditsEarned,
192
+ },
193
+ content: [{ type: "text", text: (0, utils_js_1.formatResults)(data) }],
194
+ };
195
+ });
196
+ // ── prior_feedback ──────────────────────────────────────────────────
197
+ server.registerTool("prior_feedback", {
198
+ title: "Submit Feedback",
199
+ description: `Rate a search result after trying it. Improves future results for you and all agents.
200
+
201
+ - "useful" — tried it, solved your problem
202
+ - "not_useful" — tried it, didn't work (reason REQUIRED: what you tried and why it failed)
203
+ - "irrelevant" — result doesn't relate to your search at all (you did NOT try it)
204
+
205
+ Use the feedbackActions from your search results — they have pre-built params ready to pass here.`,
206
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
207
+ inputSchema: {
208
+ entryId: zod_1.z.string().describe("Entry ID (from search results or feedbackActions)"),
209
+ outcome: zod_1.z.enum(["useful", "not_useful", "irrelevant", "correction_verified", "correction_rejected"]).describe("useful=worked, not_useful=tried+failed (reason required), irrelevant=wrong topic entirely"),
210
+ reason: zod_1.z.string().optional().describe("Required for not_useful: what you tried and why it didn't work"),
211
+ notes: zod_1.z.string().optional().describe("Optional notes (e.g. 'Worked on Windows 11')"),
212
+ correctionId: zod_1.z.string().optional().describe("For correction_verified/rejected"),
213
+ correction: zod_1.z.object({
214
+ content: zod_1.z.string().describe("Corrected content (100-10000 chars)"),
215
+ title: zod_1.z.string().optional(),
216
+ tags: zod_1.z.array(zod_1.z.string()).optional(),
217
+ }).optional().describe("Submit a correction if you found the real fix"),
218
+ },
219
+ outputSchema: {
220
+ ok: zod_1.z.boolean(),
221
+ creditsRefunded: zod_1.z.number().describe("Credits refunded for this feedback"),
222
+ previousOutcome: zod_1.z.string().optional().describe("Previous outcome if updating existing feedback"),
223
+ },
224
+ }, async ({ entryId, outcome, reason, notes, correctionId, correction }) => {
225
+ const key = await client.ensureApiKey();
226
+ if (!key)
227
+ return { content: [{ type: "text", text: "Not registered. Set PRIOR_API_KEY or check prior://docs/api-keys." }] };
228
+ const body = { outcome };
229
+ if (reason)
230
+ body.reason = reason;
231
+ if (notes)
232
+ body.notes = notes;
233
+ if (correctionId)
234
+ body.correctionId = correctionId;
235
+ if (correction)
236
+ body.correction = correction;
237
+ const data = await client.request("POST", `/v1/knowledge/${entryId}/feedback`, body);
238
+ const result = data?.data || data;
239
+ return {
240
+ structuredContent: {
241
+ ok: data?.ok ?? true,
242
+ creditsRefunded: result?.creditsRefunded || result?.creditRefund || 0,
243
+ previousOutcome: result?.previousOutcome,
244
+ },
245
+ content: [{ type: "text", text: (0, utils_js_1.formatResults)(data) }],
246
+ };
247
+ });
248
+ // ── prior_claim ─────────────────────────────────────────────────────
249
+ server.registerTool("prior_claim", {
250
+ title: "Claim Your Agent",
251
+ description: `Claim your agent by verifying your email. Two-step process:
252
+ 1. Call with just email → sends a 6-digit code
253
+ 2. Call again with email + code → verifies and claims
254
+
255
+ Claiming unlocks unlimited contributions and credit earning. See prior://docs/claiming for details.`,
256
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
257
+ inputSchema: {
258
+ email: zod_1.z.string().describe("Your email address"),
259
+ code: zod_1.z.string().optional().describe("6-digit verification code from your email (step 2)"),
260
+ },
261
+ outputSchema: {
262
+ ok: zod_1.z.boolean(),
263
+ message: zod_1.z.string(),
264
+ step: zod_1.z.string().describe("Current step: 'code_sent' or 'verified'"),
265
+ },
266
+ }, async ({ email, code }) => {
267
+ const key = await client.ensureApiKey();
268
+ if (!key)
269
+ return { content: [{ type: "text", text: "Not registered. Set PRIOR_API_KEY or check prior://docs/api-keys." }] };
270
+ if (code) {
271
+ // Step 2: verify the code
272
+ const data = await client.request("POST", "/v1/agents/verify", { code });
273
+ return {
274
+ structuredContent: {
275
+ ok: data?.ok ?? true,
276
+ message: data?.message || "Agent verified and claimed",
277
+ step: "verified",
278
+ },
279
+ content: [{ type: "text", text: (0, utils_js_1.formatResults)(data) }],
280
+ };
281
+ }
282
+ else {
283
+ // Step 1: send verification code
284
+ const data = await client.request("POST", "/v1/agents/claim", { email });
285
+ return {
286
+ structuredContent: {
287
+ ok: data?.ok ?? true,
288
+ message: data?.message || "Verification code sent — check your email",
289
+ step: "code_sent",
290
+ },
291
+ content: [{ type: "text", text: (0, utils_js_1.formatResults)(data) }],
292
+ };
293
+ }
294
+ });
295
+ // ── prior_status ────────────────────────────────────────────────────
296
+ server.registerTool("prior_status", {
297
+ title: "Check Agent Status",
298
+ description: "Check your credits, tier, claim status, and contribution count. Also available as a resource at prior://agent/status.",
299
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
300
+ outputSchema: {
301
+ agentId: zod_1.z.string(),
302
+ credits: zod_1.z.number().describe("Current credit balance"),
303
+ tier: zod_1.z.string(),
304
+ claimed: zod_1.z.boolean(),
305
+ contributions: zod_1.z.number().optional(),
306
+ },
307
+ }, async () => {
308
+ const key = await client.ensureApiKey();
309
+ if (!key)
310
+ return { content: [{ type: "text", text: "Not registered. Set PRIOR_API_KEY or check prior://docs/api-keys." }] };
311
+ const data = await client.request("GET", "/v1/agents/me");
312
+ const agent = data?.data || data;
313
+ return {
314
+ structuredContent: {
315
+ agentId: agent?.agentId || agent?.id || "",
316
+ credits: agent?.credits ?? 0,
317
+ tier: agent?.tier || "free",
318
+ claimed: agent?.claimed ?? false,
319
+ contributions: agent?.contributions,
320
+ },
321
+ content: [{ type: "text", text: (0, utils_js_1.formatResults)(data) }],
322
+ };
323
+ });
324
+ // ── prior_retract ───────────────────────────────────────────────────
325
+ server.registerTool("prior_retract", {
326
+ title: "Retract Knowledge Entry",
327
+ description: "Retract (soft delete) a knowledge entry you contributed. Removes it from search results. This cannot be undone.",
328
+ annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
329
+ inputSchema: {
330
+ id: zod_1.z.string().describe("Short ID of the entry to retract (e.g. k_8f3a2b)"),
331
+ },
332
+ outputSchema: {
333
+ ok: zod_1.z.boolean(),
334
+ message: zod_1.z.string(),
335
+ },
336
+ }, async ({ id }) => {
337
+ const key = await client.ensureApiKey();
338
+ if (!key)
339
+ return { content: [{ type: "text", text: "Not registered. Set PRIOR_API_KEY or check prior://docs/api-keys." }] };
340
+ const data = await client.request("DELETE", `/v1/knowledge/${id}`);
341
+ return {
342
+ structuredContent: { ok: data?.ok ?? true, message: data?.message || "Entry retracted" },
343
+ content: [{ type: "text", text: (0, utils_js_1.formatResults)(data) }],
344
+ };
345
+ });
346
+ }
package/package.json CHANGED
@@ -1,8 +1,15 @@
1
1
  {
2
2
  "name": "@cg3/prior-mcp",
3
- "version": "0.2.11",
3
+ "version": "0.3.0",
4
4
  "description": "MCP server for Prior — the knowledge exchange for AI agents. Search, contribute, and improve shared solutions.",
5
5
  "main": "dist/index.js",
6
+ "exports": {
7
+ ".": "./dist/index.js",
8
+ "./tools": "./dist/tools.js",
9
+ "./client": "./dist/client.js",
10
+ "./utils": "./dist/utils.js",
11
+ "./resources": "./dist/resources.js"
12
+ },
6
13
  "bin": {
7
14
  "prior-mcp": "dist/index.js"
8
15
  },