@mcpc-tech/core 0.2.0-beta.10

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/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@mcpc-tech/core",
3
+ "version": "0.2.0-beta.10",
4
+ "homepage": "https://jsr.io/@mcpc/core",
5
+ "type": "module",
6
+ "exports": {
7
+ "./types/*": "./types/*",
8
+ ".": {
9
+ "import": "./mod.mjs",
10
+ "types": "./types/mod.d.ts"
11
+ },
12
+ "./plugins": {
13
+ "import": "./plugins.mjs"
14
+ },
15
+ "./src/plugins/search-tool": {
16
+ "import": "./src/plugins/search-tool.mjs"
17
+ },
18
+ "./src/plugins/large-result": {
19
+ "import": "./src/plugins/large-result.mjs"
20
+ }
21
+ },
22
+ "dependencies": {
23
+ "jsonrepair": "^3.13.0",
24
+ "@modelcontextprotocol/sdk": "^1.8.0",
25
+ "ai": "^4.3.4",
26
+ "json-schema-traverse": "^1.0.0",
27
+ "cheerio": "^1.0.0",
28
+ "ajv": "^8.17.1",
29
+ "@segment/ajv-human-errors": "^2.15.0",
30
+ "ajv-formats": "^3.0.1",
31
+ "@mcpc-tech/ripgrep-napi": "^0.0.4"
32
+ },
33
+ "main": "./mod.mjs",
34
+ "types": "./types/mod.d.ts",
35
+ "scripts": {
36
+ "start": "node mod.mjs"
37
+ }
38
+ }
package/plugins.mjs ADDED
@@ -0,0 +1,289 @@
1
+ // ../__mcpc__core_0.2.0-beta.10/node_modules/@mcpc/core/src/plugins/search-tool.js
2
+ import rg from "@mcpc-tech/ripgrep-napi";
3
+ import { tmpdir } from "node:os";
4
+ import { jsonSchema } from "ai";
5
+ import { resolve } from "node:path";
6
+ import { relative } from "node:path";
7
+ function createSearchPlugin(options = {}) {
8
+ const maxResults = options.maxResults || 20;
9
+ const maxOutputSize = options.maxOutputSize || 5e3;
10
+ const allowedSearchDir = options.allowedDir || tmpdir();
11
+ const timeoutMs = options.timeoutMs || 3e4;
12
+ const global = options.global ?? true;
13
+ return {
14
+ name: "plugin-search",
15
+ configureServer: (server) => {
16
+ server.tool("search-tool-result", `Search for text patterns in files and directories. Use this to find specific content, code, or information within files. Provide a simple literal string or a regular expression. If your pattern is a regex, ensure it's valid; otherwise use quotes or escape special characters to treat it as a literal string.
17
+ Only search within the allowed directory: ${allowedSearchDir}`, jsonSchema({
18
+ type: "object",
19
+ properties: {
20
+ pattern: {
21
+ type: "string",
22
+ description: "Text to search for. Can be a plain string or a regular expression. For regexes, don't include delimiters (e.g. use `^foo` not `/^foo/`). If you get a regex parse error, try escaping special chars or using a simpler literal search."
23
+ },
24
+ path: {
25
+ type: "string",
26
+ description: "File or folder path to limit the search (optional). Must be within the allowed directory."
27
+ },
28
+ maxResults: {
29
+ type: "number",
30
+ description: "Maximum number of matches to return (optional). Lower this to reduce output size and runtime."
31
+ }
32
+ },
33
+ required: [
34
+ "pattern"
35
+ ]
36
+ }), async (args) => {
37
+ const isBroad = (raw) => {
38
+ const t = (raw ?? "").trim();
39
+ if (!t) return true;
40
+ if (/^[*.\s]{2,}$/.test(t)) return true;
41
+ if (t === ".*" || t === "." || t === "^.*$") return true;
42
+ if (/^\^?\.\*\$?$/.test(t)) return true;
43
+ if (/^\\s?\*+$/.test(t)) return true;
44
+ return false;
45
+ };
46
+ const appendMatchSafely = (current, addition, limit) => {
47
+ if ((current + addition).length > limit) {
48
+ return {
49
+ current,
50
+ added: false
51
+ };
52
+ }
53
+ return {
54
+ current: current + addition,
55
+ added: true
56
+ };
57
+ };
58
+ try {
59
+ const requestedPath = args.path || allowedSearchDir;
60
+ const limit = args.maxResults || maxResults;
61
+ if (args.path) {
62
+ const resolvedRequested = resolve(args.path);
63
+ const resolvedAllowed = resolve(allowedSearchDir);
64
+ const relativePath = relative(resolvedAllowed, resolvedRequested);
65
+ if (relativePath && relativePath.startsWith("..")) {
66
+ return {
67
+ content: [
68
+ {
69
+ type: "text",
70
+ text: `\u274C Path "${args.path}" not allowed. Must be within: ${allowedSearchDir}`
71
+ }
72
+ ],
73
+ isError: true
74
+ };
75
+ }
76
+ }
77
+ const searchPath = requestedPath;
78
+ const rawPattern = args.pattern ?? "";
79
+ if (isBroad(rawPattern)) {
80
+ return {
81
+ content: [
82
+ {
83
+ type: "text",
84
+ text: `\u274C Search pattern too broad: "${rawPattern}"
85
+ Provide a more specific pattern (e.g. include a filename fragment, a keyword, or limit with the "path" parameter). Avoid patterns that only contain wildcards like "*" or ".*".`
86
+ }
87
+ ],
88
+ isError: true
89
+ };
90
+ }
91
+ let timeoutId;
92
+ const timeoutPromise = new Promise((_, reject) => {
93
+ timeoutId = setTimeout(() => {
94
+ reject(new Error(`Search timeout after ${timeoutMs}ms`));
95
+ }, timeoutMs);
96
+ });
97
+ console.log(`Searching for "${args.pattern}" in ${searchPath}`);
98
+ const searchPromise = new Promise((resolve2, reject) => {
99
+ try {
100
+ const result2 = rg.search(args.pattern, [
101
+ searchPath
102
+ ]);
103
+ resolve2(result2);
104
+ } catch (error) {
105
+ reject(error);
106
+ }
107
+ });
108
+ const result = await Promise.race([
109
+ searchPromise,
110
+ timeoutPromise
111
+ ]);
112
+ if (timeoutId) clearTimeout(timeoutId);
113
+ if (!result.success || !result.matches?.length) {
114
+ return {
115
+ content: [
116
+ {
117
+ type: "text",
118
+ text: `No matches found for: "${args.pattern}"
119
+
120
+ Try:
121
+ - **Simpler pattern** or \`*\`
122
+ - Check if files exist in: ${searchPath}
123
+ - Use specific file path`
124
+ }
125
+ ]
126
+ };
127
+ }
128
+ const matches = result.matches.slice(0, limit);
129
+ let output = `Found ${result.matches.length} matches (showing up to ${matches.length}):
130
+
131
+ `;
132
+ let matchesIncluded = 0;
133
+ for (const match of matches) {
134
+ const baseMatchText = `**${match.path}:${match.lineNumber}**
135
+ `;
136
+ const fullMatchText = `${baseMatchText}\`\`\`
137
+ ${match.line}
138
+ \`\`\`
139
+
140
+ `;
141
+ const res = appendMatchSafely(output, fullMatchText, maxOutputSize);
142
+ if (!res.added) {
143
+ if (matchesIncluded === 0) {
144
+ const remainingSpace = maxOutputSize - output.length - 100;
145
+ if (remainingSpace > 50) {
146
+ const truncatedLine = match.line.slice(0, remainingSpace);
147
+ output += `${baseMatchText}\`\`\`
148
+ ${truncatedLine}...
149
+ \`\`\`
150
+
151
+ `;
152
+ output += `\u26A0\uFE0F Content truncated
153
+ `;
154
+ matchesIncluded++;
155
+ } else {
156
+ output += `\u26A0\uFE0F Content too large, use specific file path
157
+ `;
158
+ }
159
+ }
160
+ break;
161
+ }
162
+ output = res.current;
163
+ matchesIncluded++;
164
+ }
165
+ if (matchesIncluded < matches.length) {
166
+ output += `
167
+ \u26A0\uFE0F Showing ${matchesIncluded}/${matches.length} matches (size limit)
168
+ `;
169
+ output += `
170
+ For more results:
171
+ `;
172
+ output += `- Use specific pattern: "${args.pattern} keyword"
173
+ `;
174
+ output += `- Search specific file: {"pattern": "${args.pattern}", "path": "/file.txt"}
175
+ `;
176
+ output += `- Use fewer results: {"maxResults": 5}`;
177
+ }
178
+ return {
179
+ content: [
180
+ {
181
+ type: "text",
182
+ text: output
183
+ }
184
+ ]
185
+ };
186
+ } catch (error) {
187
+ const errorMsg = error instanceof Error ? error.message : String(error);
188
+ const isTimeout = errorMsg.includes("timeout");
189
+ return {
190
+ content: [
191
+ {
192
+ type: "text",
193
+ text: `Search error: ${errorMsg}
194
+
195
+ ${isTimeout ? `Timeout after ${timeoutMs}ms. Try simpler pattern or smaller directory.` : `Check pattern syntax or directory exists.`}`
196
+ }
197
+ ]
198
+ };
199
+ }
200
+ }, {
201
+ internal: !global
202
+ });
203
+ }
204
+ };
205
+ }
206
+ var defaultSearchPlugin = createSearchPlugin({
207
+ global: true,
208
+ maxResults: 20,
209
+ maxOutputSize: 5e3,
210
+ caseSensitive: false,
211
+ timeoutMs: 3e4
212
+ });
213
+ var search_tool_default = defaultSearchPlugin;
214
+
215
+ // ../__mcpc__core_0.2.0-beta.10/node_modules/@mcpc/core/src/plugins/large-result.js
216
+ import { mkdtemp, writeFile } from "node:fs/promises";
217
+ import { join } from "node:path";
218
+ import { tmpdir as tmpdir2 } from "node:os";
219
+ function createLargeResultPlugin(options = {}) {
220
+ const maxSize = options.maxSize || 8e3;
221
+ const previewSize = options.previewSize || 4e3;
222
+ let tempDir = options.tempDir || null;
223
+ const serversConfigured = /* @__PURE__ */ new WeakSet();
224
+ const searchConfig = {
225
+ maxResults: options.search?.maxResults || 15,
226
+ maxOutputSize: options.search?.maxOutputSize || 4e3,
227
+ global: true
228
+ };
229
+ return {
230
+ name: "plugin-large-result-handler",
231
+ configureServer: async (server) => {
232
+ if (!serversConfigured.has(server)) {
233
+ const searchPlugin = createSearchPlugin(searchConfig);
234
+ await server.addPlugin(searchPlugin);
235
+ serversConfigured.add(server);
236
+ }
237
+ },
238
+ transformTool: (tool, context) => {
239
+ const originalExecute = tool.execute;
240
+ tool.execute = async (args) => {
241
+ const result = await originalExecute(args);
242
+ const resultText = JSON.stringify(result);
243
+ if (resultText.length <= maxSize) {
244
+ return result;
245
+ }
246
+ if (!tempDir) {
247
+ tempDir = await mkdtemp(join(tmpdir2(), "mcpc-results-"));
248
+ }
249
+ const safeToolName = encodeURIComponent(context.toolName ?? "tool");
250
+ const fileName = `${safeToolName}-${Date.now()}.txt`;
251
+ const filePath = join(tempDir, fileName);
252
+ await writeFile(filePath, resultText);
253
+ const preview = resultText.slice(0, previewSize);
254
+ return {
255
+ content: [
256
+ {
257
+ type: "text",
258
+ text: `**Result too large (${resultText.length} chars), saved to file**
259
+
260
+ \u{1F4C1} **File:** ${filePath}
261
+ \u{1F4CA} **Size:** ${(resultText.length / 1024).toFixed(1)} KB
262
+
263
+ **Preview (${previewSize} chars):**
264
+ \`\`\`
265
+ ${preview}
266
+ \`\`\`
267
+
268
+ **To read/understand the full content:**
269
+ - Use the \`search-tool-result\` tool with pattern: \`search-tool-result {"pattern": "your-search-term"}\`
270
+ - Search supports regex patterns for advanced queries`
271
+ }
272
+ ]
273
+ };
274
+ };
275
+ return tool;
276
+ }
277
+ };
278
+ }
279
+ var defaultLargeResultPlugin = createLargeResultPlugin({
280
+ maxSize: 8e3,
281
+ previewSize: 4e3
282
+ });
283
+ var large_result_default = defaultLargeResultPlugin;
284
+ export {
285
+ createLargeResultPlugin,
286
+ createSearchPlugin,
287
+ large_result_default as defaultLargeResultPlugin,
288
+ search_tool_default as defaultSearchPlugin
289
+ };
@@ -0,0 +1,290 @@
1
+ // ../__mcpc__core_0.2.0-beta.10/node_modules/@mcpc/core/src/plugins/large-result.ts
2
+ import { mkdtemp, writeFile } from "node:fs/promises";
3
+ import { join } from "node:path";
4
+ import { tmpdir as tmpdir2 } from "node:os";
5
+
6
+ // ../__mcpc__core_0.2.0-beta.10/node_modules/@mcpc/core/src/plugins/search-tool.js
7
+ import rg from "@mcpc-tech/ripgrep-napi";
8
+ import { tmpdir } from "node:os";
9
+ import { jsonSchema } from "ai";
10
+ import { resolve } from "node:path";
11
+ import { relative } from "node:path";
12
+ function createSearchPlugin(options = {}) {
13
+ const maxResults = options.maxResults || 20;
14
+ const maxOutputSize = options.maxOutputSize || 5e3;
15
+ const allowedSearchDir = options.allowedDir || tmpdir();
16
+ const timeoutMs = options.timeoutMs || 3e4;
17
+ const global = options.global ?? true;
18
+ return {
19
+ name: "plugin-search",
20
+ configureServer: (server) => {
21
+ server.tool("search-tool-result", `Search for text patterns in files and directories. Use this to find specific content, code, or information within files. Provide a simple literal string or a regular expression. If your pattern is a regex, ensure it's valid; otherwise use quotes or escape special characters to treat it as a literal string.
22
+ Only search within the allowed directory: ${allowedSearchDir}`, jsonSchema({
23
+ type: "object",
24
+ properties: {
25
+ pattern: {
26
+ type: "string",
27
+ description: "Text to search for. Can be a plain string or a regular expression. For regexes, don't include delimiters (e.g. use `^foo` not `/^foo/`). If you get a regex parse error, try escaping special chars or using a simpler literal search."
28
+ },
29
+ path: {
30
+ type: "string",
31
+ description: "File or folder path to limit the search (optional). Must be within the allowed directory."
32
+ },
33
+ maxResults: {
34
+ type: "number",
35
+ description: "Maximum number of matches to return (optional). Lower this to reduce output size and runtime."
36
+ }
37
+ },
38
+ required: [
39
+ "pattern"
40
+ ]
41
+ }), async (args) => {
42
+ const isBroad = (raw) => {
43
+ const t = (raw ?? "").trim();
44
+ if (!t) return true;
45
+ if (/^[*.\s]{2,}$/.test(t)) return true;
46
+ if (t === ".*" || t === "." || t === "^.*$") return true;
47
+ if (/^\^?\.\*\$?$/.test(t)) return true;
48
+ if (/^\\s?\*+$/.test(t)) return true;
49
+ return false;
50
+ };
51
+ const appendMatchSafely = (current, addition, limit) => {
52
+ if ((current + addition).length > limit) {
53
+ return {
54
+ current,
55
+ added: false
56
+ };
57
+ }
58
+ return {
59
+ current: current + addition,
60
+ added: true
61
+ };
62
+ };
63
+ try {
64
+ const requestedPath = args.path || allowedSearchDir;
65
+ const limit = args.maxResults || maxResults;
66
+ if (args.path) {
67
+ const resolvedRequested = resolve(args.path);
68
+ const resolvedAllowed = resolve(allowedSearchDir);
69
+ const relativePath = relative(resolvedAllowed, resolvedRequested);
70
+ if (relativePath && relativePath.startsWith("..")) {
71
+ return {
72
+ content: [
73
+ {
74
+ type: "text",
75
+ text: `\u274C Path "${args.path}" not allowed. Must be within: ${allowedSearchDir}`
76
+ }
77
+ ],
78
+ isError: true
79
+ };
80
+ }
81
+ }
82
+ const searchPath = requestedPath;
83
+ const rawPattern = args.pattern ?? "";
84
+ if (isBroad(rawPattern)) {
85
+ return {
86
+ content: [
87
+ {
88
+ type: "text",
89
+ text: `\u274C Search pattern too broad: "${rawPattern}"
90
+ Provide a more specific pattern (e.g. include a filename fragment, a keyword, or limit with the "path" parameter). Avoid patterns that only contain wildcards like "*" or ".*".`
91
+ }
92
+ ],
93
+ isError: true
94
+ };
95
+ }
96
+ let timeoutId;
97
+ const timeoutPromise = new Promise((_, reject) => {
98
+ timeoutId = setTimeout(() => {
99
+ reject(new Error(`Search timeout after ${timeoutMs}ms`));
100
+ }, timeoutMs);
101
+ });
102
+ console.log(`Searching for "${args.pattern}" in ${searchPath}`);
103
+ const searchPromise = new Promise((resolve2, reject) => {
104
+ try {
105
+ const result2 = rg.search(args.pattern, [
106
+ searchPath
107
+ ]);
108
+ resolve2(result2);
109
+ } catch (error) {
110
+ reject(error);
111
+ }
112
+ });
113
+ const result = await Promise.race([
114
+ searchPromise,
115
+ timeoutPromise
116
+ ]);
117
+ if (timeoutId) clearTimeout(timeoutId);
118
+ if (!result.success || !result.matches?.length) {
119
+ return {
120
+ content: [
121
+ {
122
+ type: "text",
123
+ text: `No matches found for: "${args.pattern}"
124
+
125
+ Try:
126
+ - **Simpler pattern** or \`*\`
127
+ - Check if files exist in: ${searchPath}
128
+ - Use specific file path`
129
+ }
130
+ ]
131
+ };
132
+ }
133
+ const matches = result.matches.slice(0, limit);
134
+ let output = `Found ${result.matches.length} matches (showing up to ${matches.length}):
135
+
136
+ `;
137
+ let matchesIncluded = 0;
138
+ for (const match of matches) {
139
+ const baseMatchText = `**${match.path}:${match.lineNumber}**
140
+ `;
141
+ const fullMatchText = `${baseMatchText}\`\`\`
142
+ ${match.line}
143
+ \`\`\`
144
+
145
+ `;
146
+ const res = appendMatchSafely(output, fullMatchText, maxOutputSize);
147
+ if (!res.added) {
148
+ if (matchesIncluded === 0) {
149
+ const remainingSpace = maxOutputSize - output.length - 100;
150
+ if (remainingSpace > 50) {
151
+ const truncatedLine = match.line.slice(0, remainingSpace);
152
+ output += `${baseMatchText}\`\`\`
153
+ ${truncatedLine}...
154
+ \`\`\`
155
+
156
+ `;
157
+ output += `\u26A0\uFE0F Content truncated
158
+ `;
159
+ matchesIncluded++;
160
+ } else {
161
+ output += `\u26A0\uFE0F Content too large, use specific file path
162
+ `;
163
+ }
164
+ }
165
+ break;
166
+ }
167
+ output = res.current;
168
+ matchesIncluded++;
169
+ }
170
+ if (matchesIncluded < matches.length) {
171
+ output += `
172
+ \u26A0\uFE0F Showing ${matchesIncluded}/${matches.length} matches (size limit)
173
+ `;
174
+ output += `
175
+ For more results:
176
+ `;
177
+ output += `- Use specific pattern: "${args.pattern} keyword"
178
+ `;
179
+ output += `- Search specific file: {"pattern": "${args.pattern}", "path": "/file.txt"}
180
+ `;
181
+ output += `- Use fewer results: {"maxResults": 5}`;
182
+ }
183
+ return {
184
+ content: [
185
+ {
186
+ type: "text",
187
+ text: output
188
+ }
189
+ ]
190
+ };
191
+ } catch (error) {
192
+ const errorMsg = error instanceof Error ? error.message : String(error);
193
+ const isTimeout = errorMsg.includes("timeout");
194
+ return {
195
+ content: [
196
+ {
197
+ type: "text",
198
+ text: `Search error: ${errorMsg}
199
+
200
+ ${isTimeout ? `Timeout after ${timeoutMs}ms. Try simpler pattern or smaller directory.` : `Check pattern syntax or directory exists.`}`
201
+ }
202
+ ]
203
+ };
204
+ }
205
+ }, {
206
+ internal: !global
207
+ });
208
+ }
209
+ };
210
+ }
211
+ var defaultSearchPlugin = createSearchPlugin({
212
+ global: true,
213
+ maxResults: 20,
214
+ maxOutputSize: 5e3,
215
+ caseSensitive: false,
216
+ timeoutMs: 3e4
217
+ });
218
+
219
+ // ../__mcpc__core_0.2.0-beta.10/node_modules/@mcpc/core/src/plugins/large-result.ts
220
+ function createLargeResultPlugin(options = {}) {
221
+ const maxSize = options.maxSize || 8e3;
222
+ const previewSize = options.previewSize || 4e3;
223
+ let tempDir = options.tempDir || null;
224
+ const serversConfigured = /* @__PURE__ */ new WeakSet();
225
+ const searchConfig = {
226
+ maxResults: options.search?.maxResults || 15,
227
+ maxOutputSize: options.search?.maxOutputSize || 4e3,
228
+ global: true
229
+ };
230
+ return {
231
+ name: "plugin-large-result-handler",
232
+ configureServer: async (server) => {
233
+ if (!serversConfigured.has(server)) {
234
+ const searchPlugin = createSearchPlugin(searchConfig);
235
+ await server.addPlugin(searchPlugin);
236
+ serversConfigured.add(server);
237
+ }
238
+ },
239
+ transformTool: (tool, context) => {
240
+ const originalExecute = tool.execute;
241
+ tool.execute = async (args) => {
242
+ const result = await originalExecute(args);
243
+ const resultText = JSON.stringify(result);
244
+ if (resultText.length <= maxSize) {
245
+ return result;
246
+ }
247
+ if (!tempDir) {
248
+ tempDir = await mkdtemp(join(tmpdir2(), "mcpc-results-"));
249
+ }
250
+ const safeToolName = encodeURIComponent(context.toolName ?? "tool");
251
+ const fileName = `${safeToolName}-${Date.now()}.txt`;
252
+ const filePath = join(tempDir, fileName);
253
+ await writeFile(filePath, resultText);
254
+ const preview = resultText.slice(0, previewSize);
255
+ return {
256
+ content: [
257
+ {
258
+ type: "text",
259
+ text: `**Result too large (${resultText.length} chars), saved to file**
260
+
261
+ \u{1F4C1} **File:** ${filePath}
262
+ \u{1F4CA} **Size:** ${(resultText.length / 1024).toFixed(1)} KB
263
+
264
+ **Preview (${previewSize} chars):**
265
+ \`\`\`
266
+ ${preview}
267
+ \`\`\`
268
+
269
+ **To read/understand the full content:**
270
+ - Use the \`search-tool-result\` tool with pattern: \`search-tool-result {"pattern": "your-search-term"}\`
271
+ - Search supports regex patterns for advanced queries`
272
+ }
273
+ ]
274
+ };
275
+ };
276
+ return tool;
277
+ }
278
+ };
279
+ }
280
+ var defaultLargeResultPlugin = createLargeResultPlugin({
281
+ maxSize: 8e3,
282
+ previewSize: 4e3
283
+ });
284
+ var createPlugin = createLargeResultPlugin;
285
+ var large_result_default = defaultLargeResultPlugin;
286
+ export {
287
+ createLargeResultPlugin,
288
+ createPlugin,
289
+ large_result_default as default
290
+ };