Add landing page, referral system, usage quotas, search API management, and yearly pricing
- Separate workspace landing from login for better UX - Referral system rewards both parties with Pro days - Quota enforcement prevents abuse without breaking endpoints - 7-day free trial with auto-downgrade on expiry - Admin-managed search provider config (SearXNG, Bing) - 15% discount on annual subscriptions - MCP search server wrapping opencode search - Fix discovery module field name mismatch causing 422
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
node_modules/
|
||||
dist/
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "opencode-search-mcp",
|
||||
"version": "1.0.0",
|
||||
"description": "MCP server wrapping opencode search capabilities",
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"start": "node dist/index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.0.0",
|
||||
"@opencode-ai/sdk": "^1.14.41",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.0.0",
|
||||
"typescript": "^5.7.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
||||
import { createOpencodeClient } from "@opencode-ai/sdk";
|
||||
import { z } from "zod";
|
||||
|
||||
const server = new McpServer({
|
||||
name: "opencode-search",
|
||||
version: "1.0.0",
|
||||
});
|
||||
|
||||
const client = createOpencodeClient({
|
||||
baseUrl: process.env.OPENCODE_URL || "http://127.0.0.1:4096",
|
||||
});
|
||||
|
||||
server.registerTool(
|
||||
"search_files",
|
||||
{
|
||||
title: "Search Files",
|
||||
description: "Search for files and directories by name in the opencode workspace",
|
||||
inputSchema: z.object({
|
||||
query: z.string(),
|
||||
directory: z.string().optional(),
|
||||
limit: z.number().min(1).max(200).optional(),
|
||||
}),
|
||||
},
|
||||
async ({ query, directory, limit }) => {
|
||||
try {
|
||||
const results = await (client.find.files as any)({
|
||||
query: {
|
||||
query,
|
||||
directory: directory || undefined,
|
||||
limit: limit || 50,
|
||||
},
|
||||
});
|
||||
return {
|
||||
content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
|
||||
};
|
||||
} catch (e) {
|
||||
return {
|
||||
content: [{ type: "text", text: `Error: ${(e as Error).message}` }],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
"search_text",
|
||||
{
|
||||
title: "Search Text",
|
||||
description: "Search for text content within files using regex patterns",
|
||||
inputSchema: z.object({
|
||||
pattern: z.string(),
|
||||
directory: z.string().optional(),
|
||||
}),
|
||||
},
|
||||
async ({ pattern, directory }) => {
|
||||
try {
|
||||
const results = await (client.find.text as any)({
|
||||
query: {
|
||||
pattern,
|
||||
directory: directory || undefined,
|
||||
},
|
||||
});
|
||||
return {
|
||||
content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
|
||||
};
|
||||
} catch (e) {
|
||||
return {
|
||||
content: [{ type: "text", text: `Error: ${(e as Error).message}` }],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
"search_symbols",
|
||||
{
|
||||
title: "Search Symbols",
|
||||
description: "Search for code symbols in the workspace",
|
||||
inputSchema: z.object({
|
||||
pattern: z.string(),
|
||||
directory: z.string().optional(),
|
||||
limit: z.number().min(1).max(200).optional(),
|
||||
}),
|
||||
},
|
||||
async ({ pattern, directory, limit }) => {
|
||||
try {
|
||||
const results = await (client.find.symbols as any)({
|
||||
query: {
|
||||
pattern,
|
||||
directory: directory || undefined,
|
||||
limit: limit || 50,
|
||||
},
|
||||
});
|
||||
return {
|
||||
content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
|
||||
};
|
||||
} catch (e) {
|
||||
return {
|
||||
content: [{ type: "text", text: `Error: ${(e as Error).message}` }],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
"get_workspace_path",
|
||||
{
|
||||
title: "Get Workspace Path",
|
||||
description: "Get the current opencode workspace path info",
|
||||
inputSchema: z.object({}),
|
||||
},
|
||||
async () => {
|
||||
try {
|
||||
const path = await client.path.get();
|
||||
return {
|
||||
content: [{ type: "text", text: JSON.stringify(path, null, 2) }],
|
||||
};
|
||||
} catch (e) {
|
||||
return {
|
||||
content: [{ type: "text", text: `Error: ${(e as Error).message}` }],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
async function main() {
|
||||
const transport = new StdioServerTransport();
|
||||
await server.connect(transport);
|
||||
}
|
||||
|
||||
main().catch((e) => {
|
||||
console.error("MCP server error:", e);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"outDir": "dist",
|
||||
"rootDir": "src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": ["src/**/*"]
|
||||
}
|
||||
Reference in New Issue
Block a user