@easynet/agent-tool 1.0.59 → 1.0.61
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/dist/api/expose/index.d.ts +1 -1
- package/dist/api/expose/index.d.ts.map +1 -1
- package/dist/api/expose/mcp-build/build.d.ts +1 -3
- package/dist/api/expose/mcp-build/build.d.ts.map +1 -1
- package/dist/api/expose/mcp-build/index.d.ts +2 -2
- package/dist/api/expose/mcp-build/index.d.ts.map +1 -1
- package/dist/api/expose/mcp-build/run.d.ts +1 -3
- package/dist/api/expose/mcp-build/run.d.ts.map +1 -1
- package/dist/api/main.cjs +19 -15
- package/dist/api/main.js +8 -4
- package/dist/build.cjs +31 -0
- package/dist/build.cjs.map +1 -0
- package/dist/build.d.ts +13 -0
- package/dist/build.d.ts.map +1 -0
- package/dist/build.js +6 -0
- package/dist/build.js.map +1 -0
- package/dist/chunk-45S2HPVU.js +463 -0
- package/dist/chunk-45S2HPVU.js.map +1 -0
- package/dist/{chunk-Y75CRPVF.js → chunk-5J27MF7S.js} +11 -12
- package/dist/chunk-5J27MF7S.js.map +1 -0
- package/dist/{chunk-JXYANBTH.cjs → chunk-HK4GTFTQ.cjs} +57 -1645
- package/dist/chunk-HK4GTFTQ.cjs.map +1 -0
- package/dist/chunk-JNIWNSCQ.cjs +494 -0
- package/dist/chunk-JNIWNSCQ.cjs.map +1 -0
- package/dist/{chunk-DPOLJN7F.cjs → chunk-NMZ4IMEW.cjs} +22 -25
- package/dist/chunk-NMZ4IMEW.cjs.map +1 -0
- package/dist/{chunk-A5C2MUNA.js → chunk-NVT4X4CB.js} +41 -1600
- package/dist/chunk-NVT4X4CB.js.map +1 -0
- package/dist/chunk-OG5ZSXQ5.cjs +1099 -0
- package/dist/chunk-OG5ZSXQ5.cjs.map +1 -0
- package/dist/{chunk-WQMHMPNC.cjs → chunk-PYCCJF7C.cjs} +2 -68
- package/dist/chunk-PYCCJF7C.cjs.map +1 -0
- package/dist/{chunk-IWM5B5DU.js → chunk-QPKBEU64.js} +4 -3
- package/dist/chunk-QPKBEU64.js.map +1 -0
- package/dist/chunk-QXQ4477T.js +49 -0
- package/dist/chunk-QXQ4477T.js.map +1 -0
- package/dist/chunk-RZTTO5MQ.js +65 -0
- package/dist/chunk-RZTTO5MQ.js.map +1 -0
- package/dist/{chunk-FCYBA7PR.js → chunk-WUMLZERG.js} +3 -62
- package/dist/chunk-WUMLZERG.js.map +1 -0
- package/dist/chunk-XPGHS4W7.cjs +73 -0
- package/dist/chunk-XPGHS4W7.cjs.map +1 -0
- package/dist/chunk-YRFUGA3C.js +1072 -0
- package/dist/chunk-YRFUGA3C.js.map +1 -0
- package/dist/chunk-ZDSZHEQU.cjs +52 -0
- package/dist/chunk-ZDSZHEQU.cjs.map +1 -0
- package/dist/{chunk-MUBZV65R.cjs → chunk-ZH5MH3AK.cjs} +16 -15
- package/dist/chunk-ZH5MH3AK.cjs.map +1 -0
- package/dist/core/runtime.cjs +6 -5
- package/dist/core/runtime.js +2 -1
- package/dist/extension.cjs +359 -0
- package/dist/extension.cjs.map +1 -0
- package/dist/extension.d.ts +6 -0
- package/dist/extension.d.ts.map +1 -0
- package/dist/extension.js +341 -0
- package/dist/extension.js.map +1 -0
- package/dist/index.cjs +17 -609
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -25
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -520
- package/dist/index.js.map +1 -1
- package/dist/security.cjs +193 -0
- package/dist/security.cjs.map +1 -0
- package/dist/security.d.ts +6 -0
- package/dist/security.d.ts.map +1 -0
- package/dist/security.js +182 -0
- package/dist/security.js.map +1 -0
- package/dist/utils/cli/index.cjs +25 -21
- package/dist/utils/cli/index.cjs.map +1 -1
- package/dist/utils/cli/index.js +13 -9
- package/dist/utils/cli/index.js.map +1 -1
- package/package.json +16 -2
- package/dist/chunk-A5C2MUNA.js.map +0 -1
- package/dist/chunk-DPOLJN7F.cjs.map +0 -1
- package/dist/chunk-FCYBA7PR.js.map +0 -1
- package/dist/chunk-IWM5B5DU.js.map +0 -1
- package/dist/chunk-JXYANBTH.cjs.map +0 -1
- package/dist/chunk-MUBZV65R.cjs.map +0 -1
- package/dist/chunk-WQMHMPNC.cjs.map +0 -1
- package/dist/chunk-Y75CRPVF.js.map +0 -1
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { ToolRegistry, createTaggedError, withRetry } from './chunk-FCYBA7PR.js';
|
|
2
|
-
import { normalizeToolName, DEFAULT_OUTPUT_SCHEMA } from './chunk-ODEHUAR4.js';
|
|
3
1
|
import { enrichSpecWithCanonicalSchema } from './chunk-NTWOVFEY.js';
|
|
4
|
-
import
|
|
2
|
+
import { MCP_KIND, DirectoryScanner } from './chunk-YRFUGA3C.js';
|
|
3
|
+
import { setSandboxValidationEnabled } from './chunk-QXQ4477T.js';
|
|
4
|
+
import { ToolRegistry } from './chunk-WUMLZERG.js';
|
|
5
|
+
import { createTaggedError, withRetry } from './chunk-RZTTO5MQ.js';
|
|
6
|
+
import { normalizeToolName } from './chunk-ODEHUAR4.js';
|
|
5
7
|
import { readFileSync, existsSync, statSync } from 'fs';
|
|
6
|
-
import
|
|
7
|
-
import { resolve, dirname, join, normalize, basename, isAbsolute, extname, relative } from 'path';
|
|
8
|
+
import { resolve, dirname, join, isAbsolute, basename } from 'path';
|
|
8
9
|
import { parseYamlContent, resolveConfigPath } from '@easynet/agent-common';
|
|
9
10
|
import Ajv from 'ajv';
|
|
10
11
|
import addFormats from 'ajv-formats';
|
|
@@ -12,11 +13,8 @@ import { bulkhead, circuitBreaker, handleAll, ConsecutiveBreaker } from 'cockati
|
|
|
12
13
|
import { EventEmitter } from 'eventemitter3';
|
|
13
14
|
import { v4 } from 'uuid';
|
|
14
15
|
import pTimeout from 'p-timeout';
|
|
15
|
-
import { realpath, access, readdir, readFile, stat } from 'fs/promises';
|
|
16
|
-
import * as ts2 from 'typescript';
|
|
17
16
|
import { createRequire } from 'module';
|
|
18
17
|
import { pathToFileURL } from 'url';
|
|
19
|
-
import yaml from 'js-yaml';
|
|
20
18
|
import { resolveLatestVersionFromRegistry, ensurePackageInCache, getPackageEntryPath, importFromCache } from '@easynet/agent-common/npm';
|
|
21
19
|
import { createServer } from 'http';
|
|
22
20
|
|
|
@@ -197,13 +195,13 @@ function expandToolDescriptorsToRegistryNames(descriptors, registryNames) {
|
|
|
197
195
|
out.push(s);
|
|
198
196
|
continue;
|
|
199
197
|
}
|
|
200
|
-
const
|
|
201
|
-
if (
|
|
202
|
-
const packagePrefix =
|
|
198
|
+
const path = parseToolPath(s);
|
|
199
|
+
if (path) {
|
|
200
|
+
const packagePrefix = path.protocol === "npm" ? npmDescriptorToPackagePrefix(s) : path.protocol === "file" ? fileDescriptorToPackagePrefix(s) : "";
|
|
203
201
|
const prefixWithDot = packagePrefix ? packagePrefix + "." : "";
|
|
204
202
|
if (prefixWithDot) {
|
|
205
|
-
if (
|
|
206
|
-
const suffix = "." +
|
|
203
|
+
if (path.toolName) {
|
|
204
|
+
const suffix = "." + path.toolName;
|
|
207
205
|
for (const r of registryNames) {
|
|
208
206
|
if (r.startsWith(prefixWithDot) && r.endsWith(suffix) && !seen.has(r)) {
|
|
209
207
|
seen.add(r);
|
|
@@ -232,9 +230,9 @@ function resolveToolDescriptor(descriptor) {
|
|
|
232
230
|
return s;
|
|
233
231
|
}
|
|
234
232
|
function fileDescriptorToPackagePrefix(descriptor) {
|
|
235
|
-
const
|
|
236
|
-
if (!
|
|
237
|
-
const pathPart = `${
|
|
233
|
+
const path = parseToolPath(descriptor.trim());
|
|
234
|
+
if (!path || path.protocol !== "file") return "";
|
|
235
|
+
const pathPart = `${path.scope}/${path.packageWithVersion}`;
|
|
238
236
|
const normalized = normalizeToolName(pathPart);
|
|
239
237
|
if (!normalized) return "";
|
|
240
238
|
return normalizeToolName("file." + normalized);
|
|
@@ -534,17 +532,17 @@ var PolicyEngine = class {
|
|
|
534
532
|
*/
|
|
535
533
|
extractStringValues(args, keyPatterns) {
|
|
536
534
|
const results = [];
|
|
537
|
-
const
|
|
535
|
+
const walk = (obj) => {
|
|
538
536
|
for (const [key, val] of Object.entries(obj)) {
|
|
539
537
|
const lowerKey = key.toLowerCase();
|
|
540
538
|
if (typeof val === "string" && keyPatterns.some((p) => lowerKey.includes(p))) {
|
|
541
539
|
results.push(val);
|
|
542
540
|
} else if (val && typeof val === "object" && !Array.isArray(val)) {
|
|
543
|
-
|
|
541
|
+
walk(val);
|
|
544
542
|
}
|
|
545
543
|
}
|
|
546
544
|
};
|
|
547
|
-
|
|
545
|
+
walk(args);
|
|
548
546
|
return results;
|
|
549
547
|
}
|
|
550
548
|
};
|
|
@@ -729,30 +727,30 @@ function summarizeValue(value, maxLen) {
|
|
|
729
727
|
}
|
|
730
728
|
function extractUrls(obj) {
|
|
731
729
|
const urls = [];
|
|
732
|
-
const
|
|
730
|
+
const walk = (val) => {
|
|
733
731
|
if (typeof val === "string" && /^https?:\/\//i.test(val)) {
|
|
734
732
|
urls.push(val);
|
|
735
733
|
} else if (val && typeof val === "object") {
|
|
736
734
|
for (const v of Object.values(val)) {
|
|
737
|
-
|
|
735
|
+
walk(v);
|
|
738
736
|
}
|
|
739
737
|
}
|
|
740
738
|
};
|
|
741
|
-
|
|
739
|
+
walk(obj);
|
|
742
740
|
return urls.slice(0, 10);
|
|
743
741
|
}
|
|
744
742
|
function extractFilePaths(obj) {
|
|
745
743
|
const paths = [];
|
|
746
|
-
const
|
|
744
|
+
const walk = (val) => {
|
|
747
745
|
if (typeof val === "string" && (val.startsWith("/") || val.startsWith("./")) && val.includes(".")) {
|
|
748
746
|
paths.push(val);
|
|
749
747
|
} else if (val && typeof val === "object") {
|
|
750
748
|
for (const v of Object.values(val)) {
|
|
751
|
-
|
|
749
|
+
walk(v);
|
|
752
750
|
}
|
|
753
751
|
}
|
|
754
752
|
};
|
|
755
|
-
|
|
753
|
+
walk(obj);
|
|
756
754
|
return paths.slice(0, 10);
|
|
757
755
|
}
|
|
758
756
|
var EventLog = class {
|
|
@@ -1719,1563 +1717,6 @@ function buildInputSchemaHint(inputSchema) {
|
|
|
1719
1717
|
if (names.length === 0) return null;
|
|
1720
1718
|
return `This tool expects input property ${names.length === 1 ? `'${names[0]}'` : `one of [${names.map((n) => `'${n}'`).join(", ")}]`}. Use the exact property names from the tool schema.`;
|
|
1721
1719
|
}
|
|
1722
|
-
var sandboxValidationEnabled = false;
|
|
1723
|
-
function setSandboxValidationEnabled(enabled) {
|
|
1724
|
-
sandboxValidationEnabled = enabled;
|
|
1725
|
-
}
|
|
1726
|
-
async function resolveSandboxedPath2(inputPath, sandboxRoot) {
|
|
1727
|
-
let normalizedRoot;
|
|
1728
|
-
try {
|
|
1729
|
-
normalizedRoot = await realpath(resolve(sandboxRoot));
|
|
1730
|
-
} catch {
|
|
1731
|
-
normalizedRoot = normalize(resolve(sandboxRoot));
|
|
1732
|
-
}
|
|
1733
|
-
const resolved = resolve(normalizedRoot, inputPath);
|
|
1734
|
-
let real;
|
|
1735
|
-
try {
|
|
1736
|
-
await access(resolved);
|
|
1737
|
-
real = await realpath(resolved);
|
|
1738
|
-
} catch {
|
|
1739
|
-
const parentDir = dirname(resolved);
|
|
1740
|
-
let realParent;
|
|
1741
|
-
try {
|
|
1742
|
-
await access(parentDir);
|
|
1743
|
-
realParent = await realpath(parentDir);
|
|
1744
|
-
} catch {
|
|
1745
|
-
realParent = normalize(parentDir);
|
|
1746
|
-
}
|
|
1747
|
-
real = resolve(realParent, basename(resolved));
|
|
1748
|
-
}
|
|
1749
|
-
if (sandboxValidationEnabled && !isWithinRoot(real, normalizedRoot)) {
|
|
1750
|
-
throw createTaggedError(
|
|
1751
|
-
"PATH_OUTSIDE_SANDBOX",
|
|
1752
|
-
`Path "${inputPath}" resolves to "${real}" which is outside sandbox "${normalizedRoot}"`,
|
|
1753
|
-
{ inputPath, resolvedPath: real, sandboxRoot: normalizedRoot }
|
|
1754
|
-
);
|
|
1755
|
-
}
|
|
1756
|
-
return real;
|
|
1757
|
-
}
|
|
1758
|
-
function isWithinRoot(path7, root) {
|
|
1759
|
-
const normalizedPath = normalize(path7);
|
|
1760
|
-
const normalizedRoot = normalize(root);
|
|
1761
|
-
return normalizedPath === normalizedRoot || normalizedPath.startsWith(normalizedRoot + "/");
|
|
1762
|
-
}
|
|
1763
|
-
async function findDirsContainingFile(rootPath, fileName) {
|
|
1764
|
-
const found = [];
|
|
1765
|
-
await collectDirsWithFile(rootPath, fileName, found);
|
|
1766
|
-
return found;
|
|
1767
|
-
}
|
|
1768
|
-
async function collectDirsWithFile(dir, fileName, acc) {
|
|
1769
|
-
let entries;
|
|
1770
|
-
try {
|
|
1771
|
-
const e = await readdir(dir, { withFileTypes: true });
|
|
1772
|
-
entries = e.map((x) => ({
|
|
1773
|
-
name: x.name,
|
|
1774
|
-
isDirectory: x.isDirectory(),
|
|
1775
|
-
isFile: x.isFile()
|
|
1776
|
-
}));
|
|
1777
|
-
} catch {
|
|
1778
|
-
return;
|
|
1779
|
-
}
|
|
1780
|
-
if (entries.some((x) => x.isFile && x.name === fileName)) acc.push(dir);
|
|
1781
|
-
for (const entry of entries) {
|
|
1782
|
-
if (!entry.isDirectory || entry.name === "node_modules" || entry.name.startsWith(".")) continue;
|
|
1783
|
-
await collectDirsWithFile(join(dir, entry.name), fileName, acc);
|
|
1784
|
-
}
|
|
1785
|
-
}
|
|
1786
|
-
function pathToToolName(sourcePath, programName) {
|
|
1787
|
-
const normalized = sourcePath.replace(/\\/g, "/").replace(/\.(ts|tsx|js|mjs|json)$/i, "");
|
|
1788
|
-
const segments = normalized.split("/").filter(Boolean);
|
|
1789
|
-
return segments.length === 0 ? programName : `${segments.join(".")}.${programName}`;
|
|
1790
|
-
}
|
|
1791
|
-
function buildOutputSchemaFromReturnType(node, typeChecker, onWarn) {
|
|
1792
|
-
const sig = typeChecker.getSignatureFromDeclaration(node);
|
|
1793
|
-
if (!sig) {
|
|
1794
|
-
onWarn?.("Could not get signature for return type, using object");
|
|
1795
|
-
return { type: "object", additionalProperties: true };
|
|
1796
|
-
}
|
|
1797
|
-
let returnType = typeChecker.getReturnTypeOfSignature(sig);
|
|
1798
|
-
if (returnType.getSymbol?.()?.getName() === "Promise") {
|
|
1799
|
-
const typeArgs = returnType.typeArguments;
|
|
1800
|
-
if (typeArgs?.[0]) returnType = typeArgs[0];
|
|
1801
|
-
}
|
|
1802
|
-
const schema = typeToJsonSchema(returnType, typeChecker, onWarn);
|
|
1803
|
-
const hasProps = typeof schema === "object" && schema.type === "object" && Object.keys(schema.properties ?? {}).length > 0;
|
|
1804
|
-
return hasProps ? schema : { type: "object", additionalProperties: true };
|
|
1805
|
-
}
|
|
1806
|
-
function buildInputSchemaFromParams(node, typeChecker, onWarn) {
|
|
1807
|
-
const properties = {};
|
|
1808
|
-
const required = [];
|
|
1809
|
-
if (!node.parameters.length) {
|
|
1810
|
-
return { schema: { type: "object", properties: {} }, required: [] };
|
|
1811
|
-
}
|
|
1812
|
-
for (const param of node.parameters) {
|
|
1813
|
-
const name = param.name.getText();
|
|
1814
|
-
if (name.startsWith("_") && name.length <= 2) continue;
|
|
1815
|
-
const sym = param.symbol;
|
|
1816
|
-
const paramType = sym ? typeChecker.getTypeOfSymbolAtLocation(sym, param) : typeChecker.getTypeAtLocation(param);
|
|
1817
|
-
const isOptional = !!param.questionToken || param.initializer !== void 0;
|
|
1818
|
-
const propSchema = typeToJsonSchema(paramType, typeChecker, onWarn);
|
|
1819
|
-
properties[name] = propSchema;
|
|
1820
|
-
if (!isOptional) required.push(name);
|
|
1821
|
-
}
|
|
1822
|
-
const paramNames = Object.keys(properties);
|
|
1823
|
-
if (paramNames.length === 1) {
|
|
1824
|
-
const soleName = paramNames[0];
|
|
1825
|
-
const inner = properties[soleName];
|
|
1826
|
-
if (inner && typeof inner === "object" && inner.type === "object" && inner.properties && typeof inner.properties === "object") {
|
|
1827
|
-
return {
|
|
1828
|
-
schema: {
|
|
1829
|
-
type: "object",
|
|
1830
|
-
properties: inner.properties,
|
|
1831
|
-
...Array.isArray(inner.required) && inner.required.length > 0 ? { required: inner.required } : {},
|
|
1832
|
-
...inner.additionalProperties !== void 0 ? { additionalProperties: inner.additionalProperties } : {}
|
|
1833
|
-
},
|
|
1834
|
-
required: inner.required ?? []
|
|
1835
|
-
};
|
|
1836
|
-
}
|
|
1837
|
-
}
|
|
1838
|
-
return {
|
|
1839
|
-
schema: {
|
|
1840
|
-
type: "object",
|
|
1841
|
-
properties,
|
|
1842
|
-
...required.length > 0 ? { required } : {}
|
|
1843
|
-
},
|
|
1844
|
-
required
|
|
1845
|
-
};
|
|
1846
|
-
}
|
|
1847
|
-
function typeToJsonSchema(type, typeChecker, onWarn) {
|
|
1848
|
-
const flags = type.flags;
|
|
1849
|
-
if (flags & ts2.TypeFlags.String) return { type: "string" };
|
|
1850
|
-
if (flags & ts2.TypeFlags.Number) return { type: "number" };
|
|
1851
|
-
if (flags & ts2.TypeFlags.Boolean) return { type: "boolean" };
|
|
1852
|
-
if (flags & ts2.TypeFlags.BooleanLiteral) return { type: "boolean" };
|
|
1853
|
-
if (flags & ts2.TypeFlags.Null) return { type: "null" };
|
|
1854
|
-
if (flags & ts2.TypeFlags.Undefined || flags & ts2.TypeFlags.Void) return {};
|
|
1855
|
-
if (flags & ts2.TypeFlags.Any || flags & ts2.TypeFlags.Unknown) {
|
|
1856
|
-
onWarn?.(`Unsupported type: any/unknown, using empty schema`);
|
|
1857
|
-
return {};
|
|
1858
|
-
}
|
|
1859
|
-
if (type.isUnion?.()) {
|
|
1860
|
-
const union = type;
|
|
1861
|
-
const types = union.types;
|
|
1862
|
-
const withoutUndef = types.filter(
|
|
1863
|
-
(t) => !(t.flags & ts2.TypeFlags.Undefined) && !(t.flags & ts2.TypeFlags.Void)
|
|
1864
|
-
);
|
|
1865
|
-
if (withoutUndef.length === 1) return typeToJsonSchema(withoutUndef[0], typeChecker, onWarn);
|
|
1866
|
-
if (withoutUndef.length === 0) return {};
|
|
1867
|
-
const stringEnumValues = [];
|
|
1868
|
-
let allStringLiterals = true;
|
|
1869
|
-
for (const t of withoutUndef) {
|
|
1870
|
-
if (t.flags & ts2.TypeFlags.StringLiteral) {
|
|
1871
|
-
const lit = t;
|
|
1872
|
-
if (typeof lit.value === "string") stringEnumValues.push(lit.value);
|
|
1873
|
-
} else {
|
|
1874
|
-
allStringLiterals = false;
|
|
1875
|
-
break;
|
|
1876
|
-
}
|
|
1877
|
-
}
|
|
1878
|
-
if (allStringLiterals && stringEnumValues.length > 0) {
|
|
1879
|
-
return { type: "string", enum: [...new Set(stringEnumValues)] };
|
|
1880
|
-
}
|
|
1881
|
-
let allBooleanLiterals = true;
|
|
1882
|
-
for (const t of withoutUndef) {
|
|
1883
|
-
if (!(t.flags & ts2.TypeFlags.BooleanLiteral)) {
|
|
1884
|
-
allBooleanLiterals = false;
|
|
1885
|
-
break;
|
|
1886
|
-
}
|
|
1887
|
-
}
|
|
1888
|
-
if (allBooleanLiterals) return { type: "boolean" };
|
|
1889
|
-
}
|
|
1890
|
-
if (flags & ts2.TypeFlags.StringLiteral) {
|
|
1891
|
-
const lit = type;
|
|
1892
|
-
if (typeof lit.value === "string") {
|
|
1893
|
-
return { type: "string", enum: [lit.value] };
|
|
1894
|
-
}
|
|
1895
|
-
return { type: "string" };
|
|
1896
|
-
}
|
|
1897
|
-
if (typeChecker.isArrayType(type)) {
|
|
1898
|
-
const typeRef = type;
|
|
1899
|
-
const typeArgs = typeRef.typeArguments;
|
|
1900
|
-
const itemType = typeArgs?.[0];
|
|
1901
|
-
const items = itemType ? typeToJsonSchema(itemType, typeChecker, onWarn) : {};
|
|
1902
|
-
return { type: "array", items: Object.keys(items).length ? items : {} };
|
|
1903
|
-
}
|
|
1904
|
-
const str = typeChecker.typeToString(type);
|
|
1905
|
-
if (str === "string") return { type: "string" };
|
|
1906
|
-
if (str === "number") return { type: "number" };
|
|
1907
|
-
if (str === "boolean") return { type: "boolean" };
|
|
1908
|
-
if (str.endsWith("[]")) {
|
|
1909
|
-
const inner = str.slice(0, -2).trim();
|
|
1910
|
-
const itemType = inner === "string" ? { type: "string" } : inner === "number" ? { type: "number" } : {};
|
|
1911
|
-
return { type: "array", items: itemType };
|
|
1912
|
-
}
|
|
1913
|
-
if (type.getProperties && type.getProperties().length >= 0) {
|
|
1914
|
-
const props = type.getProperties();
|
|
1915
|
-
const properties = {};
|
|
1916
|
-
const required = [];
|
|
1917
|
-
for (const p of props) {
|
|
1918
|
-
const decl = p.valueDeclaration;
|
|
1919
|
-
const propType = decl ? typeChecker.getTypeAtLocation(decl) : typeChecker.getTypeOfSymbolAtLocation(p, p.valueDeclaration);
|
|
1920
|
-
const optional = decl && ts2.isPropertySignature(decl) ? !!decl.questionToken : false;
|
|
1921
|
-
properties[p.name] = typeToJsonSchema(propType, typeChecker, onWarn);
|
|
1922
|
-
if (!optional) required.push(p.name);
|
|
1923
|
-
}
|
|
1924
|
-
return { type: "object", properties, ...required.length ? { required } : {} };
|
|
1925
|
-
}
|
|
1926
|
-
onWarn?.(`Unsupported type: ${str}, using object`);
|
|
1927
|
-
return { type: "object" };
|
|
1928
|
-
}
|
|
1929
|
-
|
|
1930
|
-
// src/tools/function/types.ts
|
|
1931
|
-
var FUNCTION_KIND = "function";
|
|
1932
|
-
|
|
1933
|
-
// src/tools/skill/types.ts
|
|
1934
|
-
var SKILL_KIND = "skill";
|
|
1935
|
-
var SKILL_DIR_NAME = "skill";
|
|
1936
|
-
|
|
1937
|
-
// src/tools/n8n/types.ts
|
|
1938
|
-
var N8N_KIND = "n8n";
|
|
1939
|
-
|
|
1940
|
-
// src/tools/mcp/mcpSpecToToolSpec.ts
|
|
1941
|
-
var DEFAULT_OUTPUT = { type: "object", additionalProperties: true };
|
|
1942
|
-
function mcpSpecToToolSpec(spec, projectPath) {
|
|
1943
|
-
const base = {
|
|
1944
|
-
name: spec.name,
|
|
1945
|
-
version: "1.0.0",
|
|
1946
|
-
kind: spec.kind,
|
|
1947
|
-
description: spec.description,
|
|
1948
|
-
inputSchema: spec.inputSchema ?? DEFAULT_OUTPUT,
|
|
1949
|
-
outputSchema: "outputSchema" in spec && spec.outputSchema ? spec.outputSchema : DEFAULT_OUTPUT_SCHEMA,
|
|
1950
|
-
capabilities: [],
|
|
1951
|
-
_meta: spec._meta,
|
|
1952
|
-
...spec.kind === N8N_KIND && "webhookUrl" in spec && spec.webhookUrl ? { endpoint: spec.webhookUrl } : {}
|
|
1953
|
-
};
|
|
1954
|
-
if (spec.kind === FUNCTION_KIND && "sourcePath" in spec && "exportName" in spec) {
|
|
1955
|
-
base._meta = {
|
|
1956
|
-
...base._meta,
|
|
1957
|
-
sourcePath: spec.sourcePath,
|
|
1958
|
-
exportName: spec.exportName,
|
|
1959
|
-
...projectPath && { projectPath }
|
|
1960
|
-
};
|
|
1961
|
-
}
|
|
1962
|
-
if (spec.kind === SKILL_KIND && "sourcePath" in spec && projectPath) {
|
|
1963
|
-
base._meta = { ...base._meta, sourcePath: spec.sourcePath, projectPath };
|
|
1964
|
-
}
|
|
1965
|
-
if (spec.kind === N8N_KIND && "sourcePath" in spec && projectPath) {
|
|
1966
|
-
base._meta = { ...base._meta, sourcePath: spec.sourcePath, projectPath };
|
|
1967
|
-
}
|
|
1968
|
-
return base;
|
|
1969
|
-
}
|
|
1970
|
-
|
|
1971
|
-
// src/tools/function/scanner.ts
|
|
1972
|
-
var TOOL_TAG = "@tool";
|
|
1973
|
-
var EFFECT_VALUES = ["none", "local_write", "external_write", "destructive"];
|
|
1974
|
-
function scanForTools(options) {
|
|
1975
|
-
const projectPath = path.resolve(options.projectPath);
|
|
1976
|
-
const tsconfigPath = options.tsconfigPath ?? path.join(projectPath, "tsconfig.json");
|
|
1977
|
-
const include = options.include ?? ["**/*.ts"];
|
|
1978
|
-
const errors = [];
|
|
1979
|
-
const warnings = [];
|
|
1980
|
-
let config;
|
|
1981
|
-
let configPathResolved = path.resolve(projectPath, tsconfigPath);
|
|
1982
|
-
if (!fs.existsSync(configPathResolved)) {
|
|
1983
|
-
configPathResolved = path.join(projectPath, "tsconfig.json");
|
|
1984
|
-
}
|
|
1985
|
-
if (fs.existsSync(configPathResolved)) {
|
|
1986
|
-
const configFile = ts2.readConfigFile(configPathResolved, ts2.sys.readFile);
|
|
1987
|
-
if (configFile.error) {
|
|
1988
|
-
errors.push({ file: configPathResolved, message: String(configFile.error.messageText) });
|
|
1989
|
-
return { specs: [], errors, warnings };
|
|
1990
|
-
}
|
|
1991
|
-
const parsed = ts2.parseJsonConfigFileContent(
|
|
1992
|
-
configFile.config,
|
|
1993
|
-
ts2.sys,
|
|
1994
|
-
path.dirname(configPathResolved)
|
|
1995
|
-
);
|
|
1996
|
-
if (parsed.errors.length) {
|
|
1997
|
-
for (const e of parsed.errors) {
|
|
1998
|
-
errors.push({ file: e.file?.fileName ?? "tsconfig", message: String(e.messageText) });
|
|
1999
|
-
}
|
|
2000
|
-
return { specs: [], errors, warnings };
|
|
2001
|
-
}
|
|
2002
|
-
config = parsed;
|
|
2003
|
-
} else {
|
|
2004
|
-
config = {
|
|
2005
|
-
options: {
|
|
2006
|
-
target: ts2.ScriptTarget.ES2022,
|
|
2007
|
-
module: ts2.ModuleKind.ESNext,
|
|
2008
|
-
moduleResolution: ts2.ModuleResolutionKind.NodeNext,
|
|
2009
|
-
strict: true,
|
|
2010
|
-
skipLibCheck: true,
|
|
2011
|
-
noEmit: true
|
|
2012
|
-
},
|
|
2013
|
-
fileNames: resolveGlob(projectPath, include),
|
|
2014
|
-
errors: []
|
|
2015
|
-
};
|
|
2016
|
-
}
|
|
2017
|
-
const program = ts2.createProgram(config.fileNames, config.options);
|
|
2018
|
-
const typeChecker = program.getTypeChecker();
|
|
2019
|
-
const specs = [];
|
|
2020
|
-
for (const sourceFile of program.getSourceFiles()) {
|
|
2021
|
-
const fileName = sourceFile.fileName;
|
|
2022
|
-
if (fileName.includes("node_modules") || fileName.endsWith(".d.ts")) continue;
|
|
2023
|
-
if (!config.fileNames.some((f) => path.resolve(f) === path.resolve(fileName))) continue;
|
|
2024
|
-
ts2.forEachChild(sourceFile, (node) => {
|
|
2025
|
-
const decl = getExportedFunctionDeclaration(node);
|
|
2026
|
-
if (!decl) return;
|
|
2027
|
-
const func = decl.func;
|
|
2028
|
-
const name = decl.name;
|
|
2029
|
-
if (!name) return;
|
|
2030
|
-
const host = getJSDocHost(func);
|
|
2031
|
-
if (!hasToolTag(host)) return;
|
|
2032
|
-
const jsDoc = getJSDocComments(host);
|
|
2033
|
-
const description = getDescription(jsDoc);
|
|
2034
|
-
if (!description) {
|
|
2035
|
-
warnings.push({ file: fileName, message: `Tool ${name}: missing description, using humanized name` });
|
|
2036
|
-
}
|
|
2037
|
-
const sideEffect = getEffect(host);
|
|
2038
|
-
const onWarn = (msg) => warnings.push({ file: fileName, message: `${name}: ${msg}` });
|
|
2039
|
-
const { schema } = buildInputSchemaFromParams(func, typeChecker, onWarn);
|
|
2040
|
-
const inputSchema = Object.keys(schema.properties ?? {}).length > 0 ? schema : { type: "object", properties: {} };
|
|
2041
|
-
const outputSchema = buildOutputSchemaFromReturnType(func, typeChecker, onWarn);
|
|
2042
|
-
const sourcePath = path.relative(projectPath, fileName) || path.basename(fileName);
|
|
2043
|
-
const toolName = pathToToolName(sourcePath, name);
|
|
2044
|
-
specs.push({
|
|
2045
|
-
kind: FUNCTION_KIND,
|
|
2046
|
-
name: toolName,
|
|
2047
|
-
description: description || humanize(name),
|
|
2048
|
-
inputSchema,
|
|
2049
|
-
outputSchema,
|
|
2050
|
-
_meta: { hitl: { sideEffect } },
|
|
2051
|
-
sourcePath,
|
|
2052
|
-
exportName: name
|
|
2053
|
-
});
|
|
2054
|
-
});
|
|
2055
|
-
}
|
|
2056
|
-
return { specs, errors, warnings };
|
|
2057
|
-
}
|
|
2058
|
-
function resolveGlob(projectPath, patterns) {
|
|
2059
|
-
const result = [];
|
|
2060
|
-
const seen = /* @__PURE__ */ new Set();
|
|
2061
|
-
const add = (f) => {
|
|
2062
|
-
const abs = path.resolve(f);
|
|
2063
|
-
if (f.endsWith(".ts") && !f.endsWith(".d.ts") && !seen.has(abs)) {
|
|
2064
|
-
seen.add(abs);
|
|
2065
|
-
result.push(abs);
|
|
2066
|
-
}
|
|
2067
|
-
};
|
|
2068
|
-
for (const p of patterns) {
|
|
2069
|
-
const full = path.join(projectPath, p);
|
|
2070
|
-
if (full.includes("*")) {
|
|
2071
|
-
const baseDir = full.replace(/\*\*\/.*$/, "").replace(/\*.*$/, "").replace(/\/?$/, "") || ".";
|
|
2072
|
-
const dir = path.resolve(projectPath, baseDir);
|
|
2073
|
-
if (fs.existsSync(dir)) walk(dir, add);
|
|
2074
|
-
} else {
|
|
2075
|
-
const resolved = path.resolve(projectPath, full);
|
|
2076
|
-
if (fs.existsSync(resolved)) {
|
|
2077
|
-
if (fs.statSync(resolved).isFile()) add(resolved);
|
|
2078
|
-
else walk(resolved, add);
|
|
2079
|
-
}
|
|
2080
|
-
}
|
|
2081
|
-
}
|
|
2082
|
-
if (result.length > 0) return result;
|
|
2083
|
-
const srcDir = path.join(projectPath, "src");
|
|
2084
|
-
if (fs.existsSync(srcDir)) return walkCollect(srcDir);
|
|
2085
|
-
return [];
|
|
2086
|
-
}
|
|
2087
|
-
function walkCollect(dir) {
|
|
2088
|
-
const out = [];
|
|
2089
|
-
walk(dir, (fullPath) => {
|
|
2090
|
-
if (fullPath.endsWith(".ts") && !fullPath.endsWith(".d.ts")) out.push(path.resolve(fullPath));
|
|
2091
|
-
});
|
|
2092
|
-
return out;
|
|
2093
|
-
}
|
|
2094
|
-
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", "generated", "dist"]);
|
|
2095
|
-
function walk(dir, visit) {
|
|
2096
|
-
try {
|
|
2097
|
-
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
2098
|
-
for (const e of entries) {
|
|
2099
|
-
const full = path.join(dir, e.name);
|
|
2100
|
-
if (e.isDirectory() && !SKIP_DIRS.has(e.name)) walk(full, visit);
|
|
2101
|
-
else if (e.isFile()) visit(full);
|
|
2102
|
-
}
|
|
2103
|
-
} catch {
|
|
2104
|
-
}
|
|
2105
|
-
}
|
|
2106
|
-
function getExportedFunctionDeclaration(node, _sourceFile) {
|
|
2107
|
-
if (ts2.isFunctionDeclaration(node) && node.name) {
|
|
2108
|
-
const exported = (ts2.getModifiers(node) ?? []).some((m) => m.kind === ts2.SyntaxKind.ExportKeyword);
|
|
2109
|
-
if (exported) return { func: node, name: node.name.getText() };
|
|
2110
|
-
return null;
|
|
2111
|
-
}
|
|
2112
|
-
if (ts2.isVariableStatement(node)) {
|
|
2113
|
-
const exported = (ts2.getModifiers(node) ?? []).some((m) => m.kind === ts2.SyntaxKind.ExportKeyword);
|
|
2114
|
-
if (!exported) return null;
|
|
2115
|
-
for (const decl of node.declarationList.declarations) {
|
|
2116
|
-
let init = decl.initializer;
|
|
2117
|
-
while (init && (ts2.isParenthesizedExpression(init) || ts2.isAsExpression(init)))
|
|
2118
|
-
init = init.expression;
|
|
2119
|
-
if (init && ts2.isArrowFunction(init)) {
|
|
2120
|
-
const name = decl.name.getText();
|
|
2121
|
-
return { func: init, name };
|
|
2122
|
-
}
|
|
2123
|
-
if (init && ts2.isFunctionExpression(init)) {
|
|
2124
|
-
const name = decl.name.getText();
|
|
2125
|
-
return { func: init, name };
|
|
2126
|
-
}
|
|
2127
|
-
}
|
|
2128
|
-
}
|
|
2129
|
-
return null;
|
|
2130
|
-
}
|
|
2131
|
-
function getJSDocHost(node) {
|
|
2132
|
-
const parent = node.parent;
|
|
2133
|
-
if (ts2.isVariableDeclaration(parent)) {
|
|
2134
|
-
const gp = parent.parent;
|
|
2135
|
-
if (ts2.isVariableDeclarationList(gp) && gp.parent && ts2.isVariableStatement(gp.parent)) return gp.parent;
|
|
2136
|
-
}
|
|
2137
|
-
return node;
|
|
2138
|
-
}
|
|
2139
|
-
function getJSDocComments(host) {
|
|
2140
|
-
const all = ts2.getJSDocCommentsAndTags(host);
|
|
2141
|
-
return all.filter((t) => ts2.isJSDoc(t));
|
|
2142
|
-
}
|
|
2143
|
-
function hasToolTag(host) {
|
|
2144
|
-
const tags = ts2.getJSDocTags(host);
|
|
2145
|
-
for (const tag of tags) {
|
|
2146
|
-
const name = tag.tagName?.getText() ?? "";
|
|
2147
|
-
if (name === "tool") return true;
|
|
2148
|
-
}
|
|
2149
|
-
const all = ts2.getJSDocCommentsAndTags(host);
|
|
2150
|
-
for (const t of all) {
|
|
2151
|
-
if (ts2.isJSDoc(t)) {
|
|
2152
|
-
const full = t.getFullText();
|
|
2153
|
-
if (full.includes(TOOL_TAG)) return true;
|
|
2154
|
-
}
|
|
2155
|
-
}
|
|
2156
|
-
return false;
|
|
2157
|
-
}
|
|
2158
|
-
function getDescription(jsDocs, fallbackName) {
|
|
2159
|
-
for (const doc of jsDocs) {
|
|
2160
|
-
const comment = doc.comment;
|
|
2161
|
-
if (typeof comment === "string") {
|
|
2162
|
-
const first = comment.split(/\n/)[0]?.trim() ?? "";
|
|
2163
|
-
if (first && !first.startsWith("@")) return first;
|
|
2164
|
-
}
|
|
2165
|
-
if (Array.isArray(comment)) {
|
|
2166
|
-
const first = comment[0];
|
|
2167
|
-
if (first && typeof first === "object" && "text" in first) {
|
|
2168
|
-
const t = first.text.trim();
|
|
2169
|
-
if (t && !t.startsWith("@")) return t;
|
|
2170
|
-
}
|
|
2171
|
-
}
|
|
2172
|
-
const full = doc.getFullText();
|
|
2173
|
-
const match = full.match(/\*\s*@tool\s+(.+?)(?=\n|$|\*\/)/s);
|
|
2174
|
-
if (match?.[1]) return match[1].trim();
|
|
2175
|
-
}
|
|
2176
|
-
return "";
|
|
2177
|
-
}
|
|
2178
|
-
function getEffect(host) {
|
|
2179
|
-
const tags = ts2.getJSDocTags(host);
|
|
2180
|
-
for (const tag of tags) {
|
|
2181
|
-
const name = tag.tagName?.getText() ?? "";
|
|
2182
|
-
if (name === "effect") {
|
|
2183
|
-
const comment = tag.comment;
|
|
2184
|
-
const v = (typeof comment === "string" ? comment : "").trim().toLowerCase();
|
|
2185
|
-
if (EFFECT_VALUES.includes(v)) return v;
|
|
2186
|
-
}
|
|
2187
|
-
}
|
|
2188
|
-
const all = ts2.getJSDocCommentsAndTags(host);
|
|
2189
|
-
for (const t of all) {
|
|
2190
|
-
if (ts2.isJSDoc(t)) {
|
|
2191
|
-
const full = t.getFullText();
|
|
2192
|
-
const match = full.match(/\*\s*@effect\s+(\w+)/);
|
|
2193
|
-
if (match && EFFECT_VALUES.includes(match[1])) return match[1];
|
|
2194
|
-
}
|
|
2195
|
-
}
|
|
2196
|
-
return "none";
|
|
2197
|
-
}
|
|
2198
|
-
function humanize(name) {
|
|
2199
|
-
return name.replace(/([A-Z])/g, " $1").replace(/^./, (s) => s.toUpperCase()).trim();
|
|
2200
|
-
}
|
|
2201
|
-
function scan(projectPath, options = {}) {
|
|
2202
|
-
const root = path.resolve(projectPath);
|
|
2203
|
-
const result = scanForTools({
|
|
2204
|
-
projectPath: root,
|
|
2205
|
-
include: options.include ?? ["**/*.ts"],
|
|
2206
|
-
tsconfigPath: options.tsconfigPath
|
|
2207
|
-
});
|
|
2208
|
-
const specs = result.specs.map((s) => mcpSpecToToolSpec(s, root));
|
|
2209
|
-
return Promise.resolve({
|
|
2210
|
-
specs,
|
|
2211
|
-
errors: result.errors,
|
|
2212
|
-
warnings: result.warnings
|
|
2213
|
-
});
|
|
2214
|
-
}
|
|
2215
|
-
var DEFAULT_EXTENSIONS = [".js", ".mjs"];
|
|
2216
|
-
async function resolveEntryPoint(dirPath, baseName, extensions = DEFAULT_EXTENSIONS) {
|
|
2217
|
-
if (extensions.some((ext) => baseName.endsWith(ext))) {
|
|
2218
|
-
const fullPath = join(dirPath, baseName);
|
|
2219
|
-
await stat(fullPath);
|
|
2220
|
-
return fullPath;
|
|
2221
|
-
}
|
|
2222
|
-
for (const ext of extensions) {
|
|
2223
|
-
const fullPath = join(dirPath, `${baseName}${ext}`);
|
|
2224
|
-
try {
|
|
2225
|
-
await stat(fullPath);
|
|
2226
|
-
return fullPath;
|
|
2227
|
-
} catch {
|
|
2228
|
-
}
|
|
2229
|
-
}
|
|
2230
|
-
throw new Error(
|
|
2231
|
-
`Could not find entry point in ${dirPath}. Tried: ${extensions.map((e) => baseName + e).join(", ")}`
|
|
2232
|
-
);
|
|
2233
|
-
}
|
|
2234
|
-
|
|
2235
|
-
// src/tools/skill/SkillManifest.ts
|
|
2236
|
-
var SkillManifestError = class extends Error {
|
|
2237
|
-
constructor(path7, field, message) {
|
|
2238
|
-
super(`SKILL.md error in ${path7}: ${message}`);
|
|
2239
|
-
this.path = path7;
|
|
2240
|
-
this.field = field;
|
|
2241
|
-
this.name = "SkillManifestError";
|
|
2242
|
-
}
|
|
2243
|
-
};
|
|
2244
|
-
var NAME_PATTERN = /^[a-z0-9-]+$/;
|
|
2245
|
-
var NAME_MAX_LENGTH = 64;
|
|
2246
|
-
var DESCRIPTION_MAX_LENGTH = 1024;
|
|
2247
|
-
var COMPATIBILITY_MAX_LENGTH = 500;
|
|
2248
|
-
var RESERVED_WORDS = ["anthropic", "claude"];
|
|
2249
|
-
var XML_TAG_PATTERN = /<\/?[a-zA-Z][^>]*>/;
|
|
2250
|
-
function validateFrontmatter(fm, filePath) {
|
|
2251
|
-
if (!fm.name || typeof fm.name !== "string") {
|
|
2252
|
-
throw new SkillManifestError(filePath, "name", "name is required");
|
|
2253
|
-
}
|
|
2254
|
-
if (fm.name.length > NAME_MAX_LENGTH) {
|
|
2255
|
-
throw new SkillManifestError(
|
|
2256
|
-
filePath,
|
|
2257
|
-
"name",
|
|
2258
|
-
`name must be at most ${NAME_MAX_LENGTH} characters (got ${fm.name.length})`
|
|
2259
|
-
);
|
|
2260
|
-
}
|
|
2261
|
-
if (!NAME_PATTERN.test(fm.name)) {
|
|
2262
|
-
throw new SkillManifestError(
|
|
2263
|
-
filePath,
|
|
2264
|
-
"name",
|
|
2265
|
-
"name must contain only lowercase letters, numbers, and hyphens"
|
|
2266
|
-
);
|
|
2267
|
-
}
|
|
2268
|
-
if (fm.name.startsWith("-") || fm.name.endsWith("-")) {
|
|
2269
|
-
throw new SkillManifestError(
|
|
2270
|
-
filePath,
|
|
2271
|
-
"name",
|
|
2272
|
-
"name must not start or end with a hyphen"
|
|
2273
|
-
);
|
|
2274
|
-
}
|
|
2275
|
-
if (fm.name.includes("--")) {
|
|
2276
|
-
throw new SkillManifestError(
|
|
2277
|
-
filePath,
|
|
2278
|
-
"name",
|
|
2279
|
-
"name must not contain consecutive hyphens"
|
|
2280
|
-
);
|
|
2281
|
-
}
|
|
2282
|
-
if (XML_TAG_PATTERN.test(fm.name)) {
|
|
2283
|
-
throw new SkillManifestError(filePath, "name", "name cannot contain XML tags");
|
|
2284
|
-
}
|
|
2285
|
-
for (const reserved of RESERVED_WORDS) {
|
|
2286
|
-
if (fm.name.includes(reserved)) {
|
|
2287
|
-
throw new SkillManifestError(
|
|
2288
|
-
filePath,
|
|
2289
|
-
"name",
|
|
2290
|
-
`name cannot contain reserved word "${reserved}"`
|
|
2291
|
-
);
|
|
2292
|
-
}
|
|
2293
|
-
}
|
|
2294
|
-
if (!fm.description || typeof fm.description !== "string") {
|
|
2295
|
-
throw new SkillManifestError(
|
|
2296
|
-
filePath,
|
|
2297
|
-
"description",
|
|
2298
|
-
"description is required and must be non-empty"
|
|
2299
|
-
);
|
|
2300
|
-
}
|
|
2301
|
-
if (fm.description.length > DESCRIPTION_MAX_LENGTH) {
|
|
2302
|
-
throw new SkillManifestError(
|
|
2303
|
-
filePath,
|
|
2304
|
-
"description",
|
|
2305
|
-
`description must be at most ${DESCRIPTION_MAX_LENGTH} characters (got ${fm.description.length})`
|
|
2306
|
-
);
|
|
2307
|
-
}
|
|
2308
|
-
if (XML_TAG_PATTERN.test(fm.description)) {
|
|
2309
|
-
throw new SkillManifestError(
|
|
2310
|
-
filePath,
|
|
2311
|
-
"description",
|
|
2312
|
-
"description cannot contain XML tags"
|
|
2313
|
-
);
|
|
2314
|
-
}
|
|
2315
|
-
if (fm.compatibility != null && typeof fm.compatibility === "string") {
|
|
2316
|
-
if (fm.compatibility.length > COMPATIBILITY_MAX_LENGTH) {
|
|
2317
|
-
throw new SkillManifestError(
|
|
2318
|
-
filePath,
|
|
2319
|
-
"compatibility",
|
|
2320
|
-
`compatibility must be at most ${COMPATIBILITY_MAX_LENGTH} characters (got ${fm.compatibility.length})`
|
|
2321
|
-
);
|
|
2322
|
-
}
|
|
2323
|
-
}
|
|
2324
|
-
}
|
|
2325
|
-
|
|
2326
|
-
// src/tools/skill/SkillMdParser.ts
|
|
2327
|
-
var CODE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
2328
|
-
".py",
|
|
2329
|
-
".js",
|
|
2330
|
-
".mjs",
|
|
2331
|
-
".ts",
|
|
2332
|
-
".sh",
|
|
2333
|
-
".bash",
|
|
2334
|
-
".rb",
|
|
2335
|
-
".go"
|
|
2336
|
-
]);
|
|
2337
|
-
var INSTRUCTION_EXTENSIONS = /* @__PURE__ */ new Set([".md", ".markdown", ".txt"]);
|
|
2338
|
-
var EXCLUDED_FILES = /* @__PURE__ */ new Set(["SKILL.md", "tool.json"]);
|
|
2339
|
-
function parseSkillMd(content, filePath) {
|
|
2340
|
-
const trimmed = content.trimStart();
|
|
2341
|
-
if (!trimmed.startsWith("---")) {
|
|
2342
|
-
throw new SkillManifestError(
|
|
2343
|
-
filePath,
|
|
2344
|
-
"frontmatter",
|
|
2345
|
-
"SKILL.md must start with YAML frontmatter (---)"
|
|
2346
|
-
);
|
|
2347
|
-
}
|
|
2348
|
-
const endIndex = trimmed.indexOf("\n---", 3);
|
|
2349
|
-
if (endIndex === -1) {
|
|
2350
|
-
throw new SkillManifestError(
|
|
2351
|
-
filePath,
|
|
2352
|
-
"frontmatter",
|
|
2353
|
-
"SKILL.md frontmatter is not closed (missing closing ---)"
|
|
2354
|
-
);
|
|
2355
|
-
}
|
|
2356
|
-
const yamlBlock = trimmed.slice(4, endIndex).trim();
|
|
2357
|
-
const body = trimmed.slice(endIndex + 4).trim();
|
|
2358
|
-
let raw;
|
|
2359
|
-
try {
|
|
2360
|
-
const parsed = yaml.load(yamlBlock);
|
|
2361
|
-
if (parsed == null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
2362
|
-
throw new SkillManifestError(
|
|
2363
|
-
filePath,
|
|
2364
|
-
"frontmatter",
|
|
2365
|
-
"YAML frontmatter must be an object (key: value)"
|
|
2366
|
-
);
|
|
2367
|
-
}
|
|
2368
|
-
raw = parsed;
|
|
2369
|
-
} catch (err) {
|
|
2370
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
2371
|
-
throw new SkillManifestError(
|
|
2372
|
-
filePath,
|
|
2373
|
-
"frontmatter",
|
|
2374
|
-
`Invalid YAML frontmatter: ${message}`
|
|
2375
|
-
);
|
|
2376
|
-
}
|
|
2377
|
-
const name = stringField(raw, "name", filePath);
|
|
2378
|
-
const description = stringField(raw, "description", filePath);
|
|
2379
|
-
if (!name || !description) {
|
|
2380
|
-
throw new SkillManifestError(
|
|
2381
|
-
filePath,
|
|
2382
|
-
"frontmatter",
|
|
2383
|
-
!name ? "name is required" : "description is required"
|
|
2384
|
-
);
|
|
2385
|
-
}
|
|
2386
|
-
const license = stringField(raw, "license");
|
|
2387
|
-
const compatibility = stringField(raw, "compatibility");
|
|
2388
|
-
const allowedTools = stringField(raw, "allowed-tools");
|
|
2389
|
-
const metadata = normalizeMetadata(raw.metadata);
|
|
2390
|
-
const frontmatter = {
|
|
2391
|
-
name,
|
|
2392
|
-
description,
|
|
2393
|
-
...license && { license },
|
|
2394
|
-
...compatibility && { compatibility },
|
|
2395
|
-
...allowedTools && { allowedTools },
|
|
2396
|
-
...metadata && Object.keys(metadata).length > 0 && { metadata }
|
|
2397
|
-
};
|
|
2398
|
-
validateFrontmatter(frontmatter, filePath);
|
|
2399
|
-
return { frontmatter, instructions: body };
|
|
2400
|
-
}
|
|
2401
|
-
function stringField(raw, key, filePath) {
|
|
2402
|
-
const v = raw[key];
|
|
2403
|
-
if (v == null) return "";
|
|
2404
|
-
if (typeof v === "string") return v;
|
|
2405
|
-
if (typeof v === "number" || typeof v === "boolean") return String(v);
|
|
2406
|
-
if (Array.isArray(v)) {
|
|
2407
|
-
return v.map((x) => typeof x === "string" ? x : String(x)).join("\n");
|
|
2408
|
-
}
|
|
2409
|
-
if (filePath) {
|
|
2410
|
-
throw new SkillManifestError(
|
|
2411
|
-
filePath,
|
|
2412
|
-
"frontmatter",
|
|
2413
|
-
`Frontmatter field "${key}" must be a string, number, boolean, or array`
|
|
2414
|
-
);
|
|
2415
|
-
}
|
|
2416
|
-
return String(v);
|
|
2417
|
-
}
|
|
2418
|
-
function normalizeMetadata(val) {
|
|
2419
|
-
if (val == null) return void 0;
|
|
2420
|
-
if (typeof val === "object" && !Array.isArray(val)) {
|
|
2421
|
-
const out = {};
|
|
2422
|
-
for (const [k, v] of Object.entries(val)) {
|
|
2423
|
-
if (typeof k === "string" && v !== void 0 && v !== null) {
|
|
2424
|
-
out[k] = typeof v === "object" ? JSON.stringify(v) : String(v);
|
|
2425
|
-
}
|
|
2426
|
-
}
|
|
2427
|
-
return Object.keys(out).length ? out : void 0;
|
|
2428
|
-
}
|
|
2429
|
-
if (typeof val === "string" || typeof val === "number" || typeof val === "boolean") {
|
|
2430
|
-
return { value: String(val) };
|
|
2431
|
-
}
|
|
2432
|
-
return void 0;
|
|
2433
|
-
}
|
|
2434
|
-
async function scanSkillResources(dirPath) {
|
|
2435
|
-
const resources = [];
|
|
2436
|
-
await scanDir(dirPath, dirPath, resources);
|
|
2437
|
-
return resources;
|
|
2438
|
-
}
|
|
2439
|
-
async function scanDir(basePath, currentPath, resources) {
|
|
2440
|
-
let entries;
|
|
2441
|
-
try {
|
|
2442
|
-
entries = await readdir(currentPath, { withFileTypes: true });
|
|
2443
|
-
} catch {
|
|
2444
|
-
return;
|
|
2445
|
-
}
|
|
2446
|
-
for (const entry of entries) {
|
|
2447
|
-
const fullPath = join(currentPath, entry.name);
|
|
2448
|
-
if (entry.isDirectory()) {
|
|
2449
|
-
if (entry.name.startsWith(".") || entry.name === "node_modules") {
|
|
2450
|
-
continue;
|
|
2451
|
-
}
|
|
2452
|
-
await scanDir(basePath, fullPath, resources);
|
|
2453
|
-
} else if (entry.isFile()) {
|
|
2454
|
-
if (EXCLUDED_FILES.has(entry.name)) {
|
|
2455
|
-
continue;
|
|
2456
|
-
}
|
|
2457
|
-
const ext = extname(entry.name).toLowerCase();
|
|
2458
|
-
const relPath = relative(basePath, fullPath);
|
|
2459
|
-
resources.push({
|
|
2460
|
-
relativePath: relPath,
|
|
2461
|
-
absolutePath: fullPath,
|
|
2462
|
-
extension: ext,
|
|
2463
|
-
type: inferResourceType(ext)
|
|
2464
|
-
});
|
|
2465
|
-
}
|
|
2466
|
-
}
|
|
2467
|
-
}
|
|
2468
|
-
function inferResourceType(ext) {
|
|
2469
|
-
if (CODE_EXTENSIONS.has(ext)) return "code";
|
|
2470
|
-
if (INSTRUCTION_EXTENSIONS.has(ext)) return "instructions";
|
|
2471
|
-
return "data";
|
|
2472
|
-
}
|
|
2473
|
-
async function loadSkillDefinition(dirPath) {
|
|
2474
|
-
const skillMdPath = join(dirPath, "SKILL.md");
|
|
2475
|
-
let content;
|
|
2476
|
-
try {
|
|
2477
|
-
content = await readFile(skillMdPath, "utf-8");
|
|
2478
|
-
} catch (err) {
|
|
2479
|
-
throw new SkillManifestError(
|
|
2480
|
-
skillMdPath,
|
|
2481
|
-
"file",
|
|
2482
|
-
`Cannot read SKILL.md: ${err.message}`
|
|
2483
|
-
);
|
|
2484
|
-
}
|
|
2485
|
-
const { frontmatter, instructions } = parseSkillMd(content, skillMdPath);
|
|
2486
|
-
const resources = await scanSkillResources(dirPath);
|
|
2487
|
-
return {
|
|
2488
|
-
frontmatter,
|
|
2489
|
-
instructions,
|
|
2490
|
-
resources,
|
|
2491
|
-
dirPath,
|
|
2492
|
-
skillMdPath
|
|
2493
|
-
};
|
|
2494
|
-
}
|
|
2495
|
-
|
|
2496
|
-
// src/tools/skill/scanSkill.ts
|
|
2497
|
-
var defaultInputSchema = { type: "object", properties: {}, additionalProperties: true };
|
|
2498
|
-
async function scanForSkill(projectPath) {
|
|
2499
|
-
const projectRoot = path.resolve(projectPath);
|
|
2500
|
-
const dirs = await findDirsContainingFile(projectRoot, "SKILL.md");
|
|
2501
|
-
const skills = [];
|
|
2502
|
-
const errors = [];
|
|
2503
|
-
for (const dirPath of dirs) {
|
|
2504
|
-
const relativePath = path.relative(projectRoot, dirPath) || path.basename(dirPath);
|
|
2505
|
-
try {
|
|
2506
|
-
const skillDef = await loadSkillDefinition(dirPath);
|
|
2507
|
-
const name = pathToToolName(relativePath, skillDef.frontmatter.name);
|
|
2508
|
-
skills.push({
|
|
2509
|
-
kind: SKILL_KIND,
|
|
2510
|
-
name,
|
|
2511
|
-
description: skillDef.frontmatter.description,
|
|
2512
|
-
inputSchema: defaultInputSchema,
|
|
2513
|
-
_meta: { hitl: { sideEffect: "none" } },
|
|
2514
|
-
sourcePath: relativePath.replace(/\\/g, "/")
|
|
2515
|
-
});
|
|
2516
|
-
} catch (err) {
|
|
2517
|
-
errors.push({ dir: relativePath, message: err instanceof Error ? err.message : String(err) });
|
|
2518
|
-
}
|
|
2519
|
-
}
|
|
2520
|
-
return { skills, errors };
|
|
2521
|
-
}
|
|
2522
|
-
async function scan2(projectPath, _options = {}) {
|
|
2523
|
-
const root = path.resolve(projectPath);
|
|
2524
|
-
const result = await scanForSkill(root);
|
|
2525
|
-
const specs = result.skills.map((s) => mcpSpecToToolSpec(s, root));
|
|
2526
|
-
return {
|
|
2527
|
-
specs,
|
|
2528
|
-
errors: result.errors.map((e) => ({ file: e.dir, message: e.message }))
|
|
2529
|
-
};
|
|
2530
|
-
}
|
|
2531
|
-
async function readWorkflowMeta(dirPath, workflowFileName = "workflow.json") {
|
|
2532
|
-
const workflowPath = join(dirPath, workflowFileName);
|
|
2533
|
-
let raw;
|
|
2534
|
-
try {
|
|
2535
|
-
raw = await readFile(workflowPath, "utf-8");
|
|
2536
|
-
} catch (err) {
|
|
2537
|
-
throw new DiscoveryError(
|
|
2538
|
-
dirPath,
|
|
2539
|
-
"load",
|
|
2540
|
-
`Failed to read workflow: ${workflowPath}`,
|
|
2541
|
-
err
|
|
2542
|
-
);
|
|
2543
|
-
}
|
|
2544
|
-
let workflowDef;
|
|
2545
|
-
try {
|
|
2546
|
-
workflowDef = JSON.parse(raw);
|
|
2547
|
-
} catch (err) {
|
|
2548
|
-
throw new DiscoveryError(
|
|
2549
|
-
dirPath,
|
|
2550
|
-
"load",
|
|
2551
|
-
`Invalid JSON in ${workflowPath}`,
|
|
2552
|
-
err
|
|
2553
|
-
);
|
|
2554
|
-
}
|
|
2555
|
-
if (!workflowDef.nodes || !Array.isArray(workflowDef.nodes)) {
|
|
2556
|
-
throw new DiscoveryError(
|
|
2557
|
-
dirPath,
|
|
2558
|
-
"validate",
|
|
2559
|
-
`workflow.json must have a "nodes" array`
|
|
2560
|
-
);
|
|
2561
|
-
}
|
|
2562
|
-
const meta = workflowDef.meta;
|
|
2563
|
-
const name = workflowDef.name || meta?.name || basename(dirPath);
|
|
2564
|
-
const description = workflowDef.description || meta?.description || `n8n workflow: ${name}`;
|
|
2565
|
-
let webhookUrl;
|
|
2566
|
-
const nodes = workflowDef.nodes;
|
|
2567
|
-
if (Array.isArray(nodes)) {
|
|
2568
|
-
const webhookNode = nodes.find(
|
|
2569
|
-
(n) => n.type === "n8n-nodes-base.webhook" || n.type?.includes("webhook")
|
|
2570
|
-
);
|
|
2571
|
-
if (webhookNode?.parameters && typeof webhookNode.parameters === "object") {
|
|
2572
|
-
const params = webhookNode.parameters;
|
|
2573
|
-
const pathVal = params.path ?? params.webhookPath;
|
|
2574
|
-
if (typeof pathVal === "string" && pathVal.startsWith("http")) {
|
|
2575
|
-
webhookUrl = pathVal;
|
|
2576
|
-
}
|
|
2577
|
-
}
|
|
2578
|
-
}
|
|
2579
|
-
return { name, description, webhookUrl, workflowDef };
|
|
2580
|
-
}
|
|
2581
|
-
async function loadN8nTool(dirPath, manifest) {
|
|
2582
|
-
const { workflowDef } = await readWorkflowMeta(
|
|
2583
|
-
dirPath,
|
|
2584
|
-
manifest.entryPoint ?? "workflow.json"
|
|
2585
|
-
);
|
|
2586
|
-
return { manifest, dirPath, workflowDef };
|
|
2587
|
-
}
|
|
2588
|
-
|
|
2589
|
-
// src/tools/n8n/scanN8n.ts
|
|
2590
|
-
var defaultInputSchema2 = { type: "object", properties: {}, additionalProperties: true };
|
|
2591
|
-
async function scanForN8n(projectPath) {
|
|
2592
|
-
const projectRoot = path.resolve(projectPath);
|
|
2593
|
-
const dirs = await findDirsContainingFile(projectRoot, "workflow.json");
|
|
2594
|
-
const n8n = [];
|
|
2595
|
-
const errors = [];
|
|
2596
|
-
for (const dirPath of dirs) {
|
|
2597
|
-
const relativePath = path.relative(projectRoot, dirPath) || path.basename(dirPath);
|
|
2598
|
-
try {
|
|
2599
|
-
const { name: wfName, description: wfDesc, webhookUrl } = await readWorkflowMeta(dirPath);
|
|
2600
|
-
const toolName = pathToToolName(relativePath, wfName);
|
|
2601
|
-
n8n.push({
|
|
2602
|
-
kind: N8N_KIND,
|
|
2603
|
-
name: toolName,
|
|
2604
|
-
description: wfDesc,
|
|
2605
|
-
inputSchema: defaultInputSchema2,
|
|
2606
|
-
_meta: { hitl: { sideEffect: "external_write" } },
|
|
2607
|
-
sourcePath: relativePath.replace(/\\/g, "/"),
|
|
2608
|
-
webhookUrl
|
|
2609
|
-
});
|
|
2610
|
-
} catch (err) {
|
|
2611
|
-
errors.push({ dir: relativePath, message: err instanceof Error ? err.message : String(err) });
|
|
2612
|
-
}
|
|
2613
|
-
}
|
|
2614
|
-
return { n8n, errors };
|
|
2615
|
-
}
|
|
2616
|
-
async function scan3(projectPath, _options = {}) {
|
|
2617
|
-
const root = path.resolve(projectPath);
|
|
2618
|
-
const result = await scanForN8n(root);
|
|
2619
|
-
const specs = result.n8n.map((s) => mcpSpecToToolSpec(s, root));
|
|
2620
|
-
return {
|
|
2621
|
-
specs,
|
|
2622
|
-
errors: result.errors.map((e) => ({ file: e.dir, message: e.message }))
|
|
2623
|
-
};
|
|
2624
|
-
}
|
|
2625
|
-
|
|
2626
|
-
// src/tools/mcp/types.ts
|
|
2627
|
-
var MCP_KIND = "mcp";
|
|
2628
|
-
|
|
2629
|
-
// src/tools/mcp/scanner.ts
|
|
2630
|
-
async function scan4(projectPath, options = {}) {
|
|
2631
|
-
const root = path.resolve(projectPath);
|
|
2632
|
-
const namespace = options.namespace ?? "dir";
|
|
2633
|
-
const errors = [];
|
|
2634
|
-
const scanner = new DirectoryScanner({
|
|
2635
|
-
roots: [root],
|
|
2636
|
-
namespace,
|
|
2637
|
-
extensions: options.extensions,
|
|
2638
|
-
onError: (dir, err) => {
|
|
2639
|
-
errors.push({ file: dir, message: err.message });
|
|
2640
|
-
options.onError?.(dir, err);
|
|
2641
|
-
}
|
|
2642
|
-
});
|
|
2643
|
-
let specs;
|
|
2644
|
-
try {
|
|
2645
|
-
specs = await scanner.scan();
|
|
2646
|
-
} catch (err) {
|
|
2647
|
-
errors.push({
|
|
2648
|
-
file: root,
|
|
2649
|
-
message: err instanceof Error ? err.message : String(err)
|
|
2650
|
-
});
|
|
2651
|
-
return { specs: [], errors };
|
|
2652
|
-
}
|
|
2653
|
-
const filtered = specs.filter((s) => s.kind === MCP_KIND);
|
|
2654
|
-
return { specs: filtered, errors };
|
|
2655
|
-
}
|
|
2656
|
-
|
|
2657
|
-
// src/tools/langchain/types.ts
|
|
2658
|
-
var LANGCHAIN_KIND = "langchain";
|
|
2659
|
-
var LANGCHAIN_DIR_NAME = "langchain";
|
|
2660
|
-
|
|
2661
|
-
// src/tools/langchain/scanner.ts
|
|
2662
|
-
async function scan5(projectPath, options = {}) {
|
|
2663
|
-
const root = path.resolve(projectPath);
|
|
2664
|
-
const namespace = options.namespace ?? "dir";
|
|
2665
|
-
const errors = [];
|
|
2666
|
-
const scanner = new DirectoryScanner({
|
|
2667
|
-
roots: [root],
|
|
2668
|
-
namespace,
|
|
2669
|
-
extensions: options.extensions,
|
|
2670
|
-
onError: (dir, err) => {
|
|
2671
|
-
errors.push({ file: dir, message: err.message });
|
|
2672
|
-
options.onError?.(dir, err);
|
|
2673
|
-
}
|
|
2674
|
-
});
|
|
2675
|
-
let specs;
|
|
2676
|
-
try {
|
|
2677
|
-
specs = await scanner.scan();
|
|
2678
|
-
} catch (err) {
|
|
2679
|
-
errors.push({
|
|
2680
|
-
file: root,
|
|
2681
|
-
message: err instanceof Error ? err.message : String(err)
|
|
2682
|
-
});
|
|
2683
|
-
return { specs: [], errors };
|
|
2684
|
-
}
|
|
2685
|
-
const filtered = specs.filter((s) => s.kind === LANGCHAIN_KIND);
|
|
2686
|
-
return { specs: filtered, errors };
|
|
2687
|
-
}
|
|
2688
|
-
async function loadLangChainTool(dirPath, manifest, extensions) {
|
|
2689
|
-
let entryFile;
|
|
2690
|
-
try {
|
|
2691
|
-
entryFile = await resolveEntryPoint(
|
|
2692
|
-
dirPath,
|
|
2693
|
-
manifest.entryPoint ?? "index",
|
|
2694
|
-
extensions
|
|
2695
|
-
);
|
|
2696
|
-
} catch (err) {
|
|
2697
|
-
throw new DiscoveryError(
|
|
2698
|
-
dirPath,
|
|
2699
|
-
"load",
|
|
2700
|
-
`Cannot find LangChain entry point`,
|
|
2701
|
-
err
|
|
2702
|
-
);
|
|
2703
|
-
}
|
|
2704
|
-
let mod;
|
|
2705
|
-
try {
|
|
2706
|
-
mod = await import(pathToFileURL(entryFile).href);
|
|
2707
|
-
} catch (err) {
|
|
2708
|
-
throw new DiscoveryError(
|
|
2709
|
-
dirPath,
|
|
2710
|
-
"load",
|
|
2711
|
-
`Failed to import ${entryFile}`,
|
|
2712
|
-
err
|
|
2713
|
-
);
|
|
2714
|
-
}
|
|
2715
|
-
const tool = mod.default ?? mod.tool ?? mod;
|
|
2716
|
-
if (!tool || typeof tool.invoke !== "function") {
|
|
2717
|
-
throw new DiscoveryError(
|
|
2718
|
-
dirPath,
|
|
2719
|
-
"validate",
|
|
2720
|
-
`Entry point must export an object with invoke() method (LangChainToolLike)`
|
|
2721
|
-
);
|
|
2722
|
-
}
|
|
2723
|
-
return { manifest, dirPath, impl: tool };
|
|
2724
|
-
}
|
|
2725
|
-
var DEFAULT_EXTENSIONS2 = [".js", ".mjs"];
|
|
2726
|
-
async function listSkillProgramFiles(dirPath, extensions = DEFAULT_EXTENSIONS2) {
|
|
2727
|
-
let entries;
|
|
2728
|
-
try {
|
|
2729
|
-
const dirEntries = await readdir(dirPath, { withFileTypes: true });
|
|
2730
|
-
entries = dirEntries.map((entry) => ({
|
|
2731
|
-
name: entry.name,
|
|
2732
|
-
isFile: entry.isFile()
|
|
2733
|
-
}));
|
|
2734
|
-
} catch {
|
|
2735
|
-
return [];
|
|
2736
|
-
}
|
|
2737
|
-
return entries.filter((e) => e.isFile).map((e) => e.name).filter((name) => {
|
|
2738
|
-
if (name.startsWith(".") || name.startsWith("_")) return false;
|
|
2739
|
-
if (name.includes(".test.") || name.includes(".spec.")) return false;
|
|
2740
|
-
return extensions.some((ext) => name.endsWith(ext));
|
|
2741
|
-
}).sort((a, b) => {
|
|
2742
|
-
if (a === "handler.js" || a === "index.js") return -1;
|
|
2743
|
-
if (b === "handler.js" || b === "index.js") return 1;
|
|
2744
|
-
return a.localeCompare(b);
|
|
2745
|
-
});
|
|
2746
|
-
}
|
|
2747
|
-
function isLangChainLikeTool(val) {
|
|
2748
|
-
return val != null && typeof val === "object" && "invoke" in val && typeof val.invoke === "function";
|
|
2749
|
-
}
|
|
2750
|
-
function isConstructable(val) {
|
|
2751
|
-
return typeof val === "function" && typeof val.prototype === "object";
|
|
2752
|
-
}
|
|
2753
|
-
async function loadOneSkillProgram(dirPath, manifest, entryFile, skillDef, programKey, extensions) {
|
|
2754
|
-
let impl;
|
|
2755
|
-
try {
|
|
2756
|
-
const fullPath = await resolveEntryPoint(dirPath, entryFile, extensions ?? [".js", ".mjs"]);
|
|
2757
|
-
const mod = await import(pathToFileURL(fullPath).href);
|
|
2758
|
-
const fn = mod.default ?? mod.handler ?? mod.Tool;
|
|
2759
|
-
if (isLangChainLikeTool(fn)) {
|
|
2760
|
-
impl = fn;
|
|
2761
|
-
} else if (isConstructable(fn)) {
|
|
2762
|
-
const instance = new fn();
|
|
2763
|
-
if (isLangChainLikeTool(instance)) impl = instance;
|
|
2764
|
-
} else if (typeof fn === "function") {
|
|
2765
|
-
impl = fn;
|
|
2766
|
-
}
|
|
2767
|
-
} catch {
|
|
2768
|
-
}
|
|
2769
|
-
return {
|
|
2770
|
-
manifest,
|
|
2771
|
-
dirPath,
|
|
2772
|
-
impl,
|
|
2773
|
-
skillDefinition: skillDef,
|
|
2774
|
-
programKey
|
|
2775
|
-
};
|
|
2776
|
-
}
|
|
2777
|
-
async function loadSkillTools(dirPath, manifest, extensions) {
|
|
2778
|
-
let skillDef;
|
|
2779
|
-
try {
|
|
2780
|
-
skillDef = await loadSkillDefinition(dirPath);
|
|
2781
|
-
} catch (err) {
|
|
2782
|
-
throw new DiscoveryError(
|
|
2783
|
-
dirPath,
|
|
2784
|
-
"load",
|
|
2785
|
-
`Failed to parse SKILL.md: ${err.message}`,
|
|
2786
|
-
err
|
|
2787
|
-
);
|
|
2788
|
-
}
|
|
2789
|
-
const programs = manifest.programs;
|
|
2790
|
-
if (programs && typeof programs === "object" && Object.keys(programs).length > 0) {
|
|
2791
|
-
const result = [];
|
|
2792
|
-
for (const [programKey, entryFile2] of Object.entries(programs)) {
|
|
2793
|
-
const loaded2 = await loadOneSkillProgram(
|
|
2794
|
-
dirPath,
|
|
2795
|
-
manifest,
|
|
2796
|
-
entryFile2,
|
|
2797
|
-
skillDef,
|
|
2798
|
-
programKey,
|
|
2799
|
-
extensions
|
|
2800
|
-
);
|
|
2801
|
-
result.push(loaded2);
|
|
2802
|
-
}
|
|
2803
|
-
return result;
|
|
2804
|
-
}
|
|
2805
|
-
const exts = extensions ?? DEFAULT_EXTENSIONS2;
|
|
2806
|
-
const files = await listSkillProgramFiles(dirPath, exts);
|
|
2807
|
-
if (files.length >= 2) {
|
|
2808
|
-
const result = [];
|
|
2809
|
-
for (let i = 0; i < files.length; i++) {
|
|
2810
|
-
const file = files[i];
|
|
2811
|
-
const programKey = i === 0 ? "default" : file.replace(/\.[^.]+$/, "");
|
|
2812
|
-
const loaded2 = await loadOneSkillProgram(
|
|
2813
|
-
dirPath,
|
|
2814
|
-
manifest,
|
|
2815
|
-
file,
|
|
2816
|
-
skillDef,
|
|
2817
|
-
programKey,
|
|
2818
|
-
extensions
|
|
2819
|
-
);
|
|
2820
|
-
result.push(loaded2);
|
|
2821
|
-
}
|
|
2822
|
-
return result;
|
|
2823
|
-
}
|
|
2824
|
-
const entryFile = manifest.entryPoint ?? files[0] ?? "handler";
|
|
2825
|
-
const loaded = await loadOneSkillProgram(
|
|
2826
|
-
dirPath,
|
|
2827
|
-
manifest,
|
|
2828
|
-
entryFile,
|
|
2829
|
-
skillDef,
|
|
2830
|
-
void 0,
|
|
2831
|
-
extensions
|
|
2832
|
-
);
|
|
2833
|
-
return [loaded];
|
|
2834
|
-
}
|
|
2835
|
-
async function listLangchainEntryFiles(dirPath, extensions) {
|
|
2836
|
-
let entries;
|
|
2837
|
-
try {
|
|
2838
|
-
const dirEntries = await readdir(dirPath, { withFileTypes: true });
|
|
2839
|
-
entries = dirEntries.map((entry) => ({
|
|
2840
|
-
name: entry.name,
|
|
2841
|
-
isFile: entry.isFile()
|
|
2842
|
-
}));
|
|
2843
|
-
} catch {
|
|
2844
|
-
return [];
|
|
2845
|
-
}
|
|
2846
|
-
return entries.filter((e) => e.isFile).map((e) => e.name).filter((name) => {
|
|
2847
|
-
if (name.startsWith(".") || name.startsWith("_")) return false;
|
|
2848
|
-
if (name.endsWith(".d.ts")) return false;
|
|
2849
|
-
if (name.includes(".test.") || name.includes(".spec.")) return false;
|
|
2850
|
-
return extensions.some((ext) => name.endsWith(ext));
|
|
2851
|
-
});
|
|
2852
|
-
}
|
|
2853
|
-
async function loadLangChainToolsFromDir(dirPath, dirName, manifest, strict, namespace, extensions, langchainDirName, toSpec, onError) {
|
|
2854
|
-
const entryFiles = await listLangchainEntryFiles(dirPath, extensions);
|
|
2855
|
-
if (entryFiles.length === 0) {
|
|
2856
|
-
if (strict) {
|
|
2857
|
-
throw new DiscoveryError(dirPath, "load", "No LangChain entry files found");
|
|
2858
|
-
}
|
|
2859
|
-
return [];
|
|
2860
|
-
}
|
|
2861
|
-
const specs = [];
|
|
2862
|
-
const useDirNameForSingle = dirName !== langchainDirName;
|
|
2863
|
-
for (const entryFile of entryFiles) {
|
|
2864
|
-
const fileManifest = { ...manifest, entryPoint: entryFile };
|
|
2865
|
-
try {
|
|
2866
|
-
const loaded = await loadLangChainTool(dirPath, fileManifest, extensions);
|
|
2867
|
-
const fileBase = basename(entryFile).replace(/\.[^.]+$/, "");
|
|
2868
|
-
const nameHint = entryFiles.length === 1 && useDirNameForSingle ? dirName : fileBase;
|
|
2869
|
-
specs.push(toSpec(loaded, nameHint, dirPath, namespace));
|
|
2870
|
-
} catch (error) {
|
|
2871
|
-
const err = error;
|
|
2872
|
-
if (err instanceof DiscoveryError && err.phase === "validate") {
|
|
2873
|
-
if (strict) throw err;
|
|
2874
|
-
continue;
|
|
2875
|
-
}
|
|
2876
|
-
onError?.(join(dirPath, entryFile), err);
|
|
2877
|
-
if (strict) throw err;
|
|
2878
|
-
}
|
|
2879
|
-
}
|
|
2880
|
-
return specs;
|
|
2881
|
-
}
|
|
2882
|
-
function isCursorFormat(obj) {
|
|
2883
|
-
return typeof obj === "object" && obj !== null && "mcpServers" in obj && typeof obj.mcpServers === "object" && obj.mcpServers !== null;
|
|
2884
|
-
}
|
|
2885
|
-
function extractMCPConfig(parsed, toolName) {
|
|
2886
|
-
if (isCursorFormat(parsed)) {
|
|
2887
|
-
const servers = parsed.mcpServers;
|
|
2888
|
-
const keys = Object.keys(servers);
|
|
2889
|
-
if (keys.length === 0) {
|
|
2890
|
-
return {};
|
|
2891
|
-
}
|
|
2892
|
-
const name = toolName && keys.includes(toolName) ? toolName : keys[0];
|
|
2893
|
-
return servers[name];
|
|
2894
|
-
}
|
|
2895
|
-
return parsed;
|
|
2896
|
-
}
|
|
2897
|
-
async function loadMCPTool(dirPath, manifest) {
|
|
2898
|
-
const mcpPath = join(dirPath, manifest.entryPoint ?? "mcp.json");
|
|
2899
|
-
let raw;
|
|
2900
|
-
try {
|
|
2901
|
-
raw = await readFile(mcpPath, "utf-8");
|
|
2902
|
-
} catch (err) {
|
|
2903
|
-
throw new DiscoveryError(
|
|
2904
|
-
dirPath,
|
|
2905
|
-
"load",
|
|
2906
|
-
`Failed to read MCP config: ${mcpPath}`,
|
|
2907
|
-
err
|
|
2908
|
-
);
|
|
2909
|
-
}
|
|
2910
|
-
let parsed;
|
|
2911
|
-
try {
|
|
2912
|
-
parsed = JSON.parse(raw);
|
|
2913
|
-
} catch (err) {
|
|
2914
|
-
throw new DiscoveryError(
|
|
2915
|
-
dirPath,
|
|
2916
|
-
"load",
|
|
2917
|
-
`Invalid JSON in ${mcpPath}`,
|
|
2918
|
-
err
|
|
2919
|
-
);
|
|
2920
|
-
}
|
|
2921
|
-
const baseName = manifest.name?.split("/").pop();
|
|
2922
|
-
const config = extractMCPConfig(parsed, baseName);
|
|
2923
|
-
if (!config.command && !config.url) {
|
|
2924
|
-
throw new DiscoveryError(
|
|
2925
|
-
dirPath,
|
|
2926
|
-
"validate",
|
|
2927
|
-
`mcp.json must have either "command" or "url" field`
|
|
2928
|
-
);
|
|
2929
|
-
}
|
|
2930
|
-
return { manifest, dirPath, mcpConfig: config };
|
|
2931
|
-
}
|
|
2932
|
-
|
|
2933
|
-
// src/tools/mcp/directoryApply.ts
|
|
2934
|
-
function applyLoadedToSpec(spec, loaded, _manifest, _defaultDirName, _namespace) {
|
|
2935
|
-
if (loaded.mcpConfig?.url) spec.endpoint = loaded.mcpConfig.url;
|
|
2936
|
-
spec.impl = loaded.mcpConfig;
|
|
2937
|
-
}
|
|
2938
|
-
var directoryMarker = {
|
|
2939
|
-
kind: "mcp",
|
|
2940
|
-
markerFile: "mcp.json",
|
|
2941
|
-
defaultEntryPoint: "mcp.json"
|
|
2942
|
-
};
|
|
2943
|
-
|
|
2944
|
-
// src/tools/langchain/directoryApply.ts
|
|
2945
|
-
function applyLoadedToSpec2(spec, loaded, manifest, _defaultDirName, namespace) {
|
|
2946
|
-
spec.impl = loaded.impl;
|
|
2947
|
-
if (!manifest.name && loaded.impl) {
|
|
2948
|
-
const toolName = loaded.impl.name;
|
|
2949
|
-
if (toolName) spec.name = `${namespace}/${toolName}`;
|
|
2950
|
-
}
|
|
2951
|
-
if (!manifest.description && loaded.impl) {
|
|
2952
|
-
const d = loaded.impl.description;
|
|
2953
|
-
if (d) spec.description = d;
|
|
2954
|
-
}
|
|
2955
|
-
if (!manifest.inputSchema && loaded.impl) {
|
|
2956
|
-
const schema = loaded.impl.schema;
|
|
2957
|
-
if (schema) spec.inputSchema = schema;
|
|
2958
|
-
}
|
|
2959
|
-
}
|
|
2960
|
-
|
|
2961
|
-
// src/tools/skill/directoryApply.ts
|
|
2962
|
-
function applyLoadedToSpec3(spec, loaded, manifest, defaultDirName, namespace) {
|
|
2963
|
-
const skillDef = loaded.skillDefinition;
|
|
2964
|
-
if (skillDef) {
|
|
2965
|
-
spec.name = manifest.name ?? skillDef.frontmatter.name;
|
|
2966
|
-
spec.description = skillDef.frontmatter.description;
|
|
2967
|
-
if (loaded.programKey === "default") {
|
|
2968
|
-
spec.name = `${namespace}/${defaultDirName}`;
|
|
2969
|
-
} else if (loaded.programKey) {
|
|
2970
|
-
spec.name = `${namespace}/${defaultDirName}/${loaded.programKey}`;
|
|
2971
|
-
}
|
|
2972
|
-
const impl = loaded.impl;
|
|
2973
|
-
if (impl && typeof impl === "object" && typeof impl.invoke === "function") {
|
|
2974
|
-
if (impl.description != null && impl.description !== "")
|
|
2975
|
-
spec.description = impl.description;
|
|
2976
|
-
if (impl.schema != null && typeof impl.schema === "object")
|
|
2977
|
-
spec.inputSchema = impl.schema;
|
|
2978
|
-
}
|
|
2979
|
-
spec.impl = { ...skillDef, handler: loaded.impl };
|
|
2980
|
-
} else {
|
|
2981
|
-
spec.impl = loaded.impl;
|
|
2982
|
-
}
|
|
2983
|
-
}
|
|
2984
|
-
var directoryMarker2 = {
|
|
2985
|
-
kind: "skill",
|
|
2986
|
-
markerFile: "SKILL.md",
|
|
2987
|
-
defaultEntryPoint: "handler"
|
|
2988
|
-
};
|
|
2989
|
-
|
|
2990
|
-
// src/tools/n8n/directoryApply.ts
|
|
2991
|
-
function applyLoadedToSpec4(spec, loaded, manifest, _defaultDirName, _namespace) {
|
|
2992
|
-
const workflow = loaded.workflowDef;
|
|
2993
|
-
if (workflow?.id) spec.resourceId = String(workflow.id);
|
|
2994
|
-
if (!manifest.description && workflow) {
|
|
2995
|
-
const d = workflow.description ?? workflow.meta?.description ?? (typeof workflow.name === "string" ? workflow.name : void 0);
|
|
2996
|
-
if (d) spec.description = d;
|
|
2997
|
-
}
|
|
2998
|
-
spec.impl = loaded.workflowDef;
|
|
2999
|
-
}
|
|
3000
|
-
var directoryMarker3 = {
|
|
3001
|
-
kind: "n8n",
|
|
3002
|
-
markerFile: "workflow.json",
|
|
3003
|
-
defaultEntryPoint: "workflow.json"
|
|
3004
|
-
};
|
|
3005
|
-
|
|
3006
|
-
// src/tools/discoveryFactory.ts
|
|
3007
|
-
var DiscoveryError = class extends Error {
|
|
3008
|
-
toolDir;
|
|
3009
|
-
phase;
|
|
3010
|
-
cause;
|
|
3011
|
-
constructor(toolDir, phase, message, cause) {
|
|
3012
|
-
super(`[${phase}] ${toolDir}: ${message}`);
|
|
3013
|
-
this.name = "DiscoveryError";
|
|
3014
|
-
this.toolDir = toolDir;
|
|
3015
|
-
this.phase = phase;
|
|
3016
|
-
this.cause = cause;
|
|
3017
|
-
}
|
|
3018
|
-
};
|
|
3019
|
-
var DIRECTORY_KINDS = ["mcp", "langchain", "skill", "n8n"];
|
|
3020
|
-
var DIRECTORY_DISCOVERABLE_KINDS = DIRECTORY_KINDS;
|
|
3021
|
-
var DIRECTORY_KIND_MARKERS = [
|
|
3022
|
-
directoryMarker2,
|
|
3023
|
-
directoryMarker3,
|
|
3024
|
-
directoryMarker
|
|
3025
|
-
];
|
|
3026
|
-
var DIRECTORY_LOADERS = {
|
|
3027
|
-
mcp: async (dirPath, manifest) => [await loadMCPTool(dirPath, manifest)],
|
|
3028
|
-
langchain: async (dirPath, manifest, ext) => [await loadLangChainTool(dirPath, manifest, ext)],
|
|
3029
|
-
skill: (dirPath, manifest, ext) => loadSkillTools(dirPath, manifest, ext),
|
|
3030
|
-
n8n: async (dirPath, manifest) => [await loadN8nTool(dirPath, manifest)]
|
|
3031
|
-
};
|
|
3032
|
-
function getDirectoryLoader(kind) {
|
|
3033
|
-
const loader = DIRECTORY_LOADERS[kind];
|
|
3034
|
-
if (!loader) {
|
|
3035
|
-
throw new DiscoveryError("", "manifest", `Unknown directory tool kind: "${kind}"`);
|
|
3036
|
-
}
|
|
3037
|
-
return loader;
|
|
3038
|
-
}
|
|
3039
|
-
function applyDirectoryLoadedToSpec(spec, loaded, manifest, defaultDirName, namespace) {
|
|
3040
|
-
switch (manifest.kind) {
|
|
3041
|
-
case "mcp":
|
|
3042
|
-
return applyLoadedToSpec(spec, loaded);
|
|
3043
|
-
case "langchain":
|
|
3044
|
-
return applyLoadedToSpec2(spec, loaded, manifest, defaultDirName, namespace);
|
|
3045
|
-
case "skill":
|
|
3046
|
-
return applyLoadedToSpec3(spec, loaded, manifest, defaultDirName, namespace);
|
|
3047
|
-
case "n8n":
|
|
3048
|
-
return applyLoadedToSpec4(spec, loaded, manifest);
|
|
3049
|
-
}
|
|
3050
|
-
}
|
|
3051
|
-
async function discoverTools(type, projectPath, options = {}) {
|
|
3052
|
-
const root = path.resolve(projectPath);
|
|
3053
|
-
switch (type) {
|
|
3054
|
-
case "function":
|
|
3055
|
-
return scan(root, options);
|
|
3056
|
-
case "skill":
|
|
3057
|
-
return scan2(root, options);
|
|
3058
|
-
case "n8n":
|
|
3059
|
-
return scan3(root, options);
|
|
3060
|
-
case "mcp":
|
|
3061
|
-
return scan4(root, options);
|
|
3062
|
-
case "langchain":
|
|
3063
|
-
return scan5(root, options);
|
|
3064
|
-
default: {
|
|
3065
|
-
const _ = type;
|
|
3066
|
-
return _;
|
|
3067
|
-
}
|
|
3068
|
-
}
|
|
3069
|
-
}
|
|
3070
|
-
var DEFAULT_EXTENSIONS3 = [".js", ".mjs"];
|
|
3071
|
-
var DirectoryScanner = class {
|
|
3072
|
-
roots;
|
|
3073
|
-
extensions;
|
|
3074
|
-
onError;
|
|
3075
|
-
constructor(options) {
|
|
3076
|
-
const defaultNamespace = options.namespace ?? "dir";
|
|
3077
|
-
this.roots = options.roots.map((root) => {
|
|
3078
|
-
if (typeof root === "string") {
|
|
3079
|
-
return { path: root, namespace: defaultNamespace };
|
|
3080
|
-
}
|
|
3081
|
-
return {
|
|
3082
|
-
path: root.path,
|
|
3083
|
-
namespace: root.namespace ?? defaultNamespace
|
|
3084
|
-
};
|
|
3085
|
-
});
|
|
3086
|
-
this.extensions = options.extensions ?? DEFAULT_EXTENSIONS3;
|
|
3087
|
-
this.onError = options.onError;
|
|
3088
|
-
}
|
|
3089
|
-
async scan() {
|
|
3090
|
-
const specs = [];
|
|
3091
|
-
for (const root of this.roots) {
|
|
3092
|
-
const rootSpecs = await this.scanRoot(root.path, root.namespace);
|
|
3093
|
-
specs.push(...rootSpecs);
|
|
3094
|
-
}
|
|
3095
|
-
return specs;
|
|
3096
|
-
}
|
|
3097
|
-
async scanRoot(rootPath, namespace) {
|
|
3098
|
-
return this.scanRecursive(rootPath, namespace);
|
|
3099
|
-
}
|
|
3100
|
-
async scanRecursive(dirPath, namespace) {
|
|
3101
|
-
const specs = [];
|
|
3102
|
-
let dirEntries;
|
|
3103
|
-
try {
|
|
3104
|
-
const entries = await readdir(dirPath, { withFileTypes: true });
|
|
3105
|
-
dirEntries = entries.map((entry) => ({
|
|
3106
|
-
name: entry.name,
|
|
3107
|
-
isDirectory: entry.isDirectory()
|
|
3108
|
-
}));
|
|
3109
|
-
} catch (error) {
|
|
3110
|
-
this.onError?.(dirPath, error);
|
|
3111
|
-
return specs;
|
|
3112
|
-
}
|
|
3113
|
-
const dirName = basename(dirPath);
|
|
3114
|
-
try {
|
|
3115
|
-
const loadedSpecs = await this.loadToolDir(dirPath, dirName, namespace);
|
|
3116
|
-
if (loadedSpecs.length > 0) specs.push(...loadedSpecs);
|
|
3117
|
-
} catch (error) {
|
|
3118
|
-
this.onError?.(dirPath, error);
|
|
3119
|
-
}
|
|
3120
|
-
for (const entry of dirEntries) {
|
|
3121
|
-
if (!entry.isDirectory) continue;
|
|
3122
|
-
const childPath = join(dirPath, entry.name);
|
|
3123
|
-
try {
|
|
3124
|
-
const childSpecs = await this.scanRecursive(childPath, namespace);
|
|
3125
|
-
specs.push(...childSpecs);
|
|
3126
|
-
} catch (error) {
|
|
3127
|
-
this.onError?.(childPath, error);
|
|
3128
|
-
}
|
|
3129
|
-
}
|
|
3130
|
-
return specs;
|
|
3131
|
-
}
|
|
3132
|
-
async loadToolDir(dirPath, dirName, namespace) {
|
|
3133
|
-
const manifestPath = join(dirPath, "tool.json");
|
|
3134
|
-
let manifestRaw;
|
|
3135
|
-
try {
|
|
3136
|
-
manifestRaw = await readFile(manifestPath, "utf-8");
|
|
3137
|
-
} catch {
|
|
3138
|
-
const inferred = await this.inferManifest(dirPath, dirName);
|
|
3139
|
-
if (!inferred) return [];
|
|
3140
|
-
if (inferred.kind === "langchain") {
|
|
3141
|
-
if (inferred.entryPoint) {
|
|
3142
|
-
const loaded3 = await loadLangChainTool(dirPath, inferred, this.extensions);
|
|
3143
|
-
return [this.toToolSpec(loaded3, dirName, dirPath, namespace)];
|
|
3144
|
-
}
|
|
3145
|
-
return loadLangChainToolsFromDir(
|
|
3146
|
-
dirPath,
|
|
3147
|
-
dirName,
|
|
3148
|
-
inferred,
|
|
3149
|
-
false,
|
|
3150
|
-
namespace,
|
|
3151
|
-
this.extensions,
|
|
3152
|
-
LANGCHAIN_DIR_NAME,
|
|
3153
|
-
(loaded3, nameHint, dp, ns) => this.toToolSpec(loaded3, nameHint, dp, ns),
|
|
3154
|
-
this.onError
|
|
3155
|
-
);
|
|
3156
|
-
}
|
|
3157
|
-
if (inferred.kind === "skill") {
|
|
3158
|
-
const loadedList = await loadSkillTools(dirPath, inferred, this.extensions);
|
|
3159
|
-
return loadedList.map(
|
|
3160
|
-
(loaded3) => this.toToolSpec(loaded3, dirName, dirPath, namespace)
|
|
3161
|
-
);
|
|
3162
|
-
}
|
|
3163
|
-
const loaded2 = await this.loadByKind(dirPath, inferred);
|
|
3164
|
-
return [this.toToolSpec(loaded2, dirName, dirPath, namespace)];
|
|
3165
|
-
}
|
|
3166
|
-
let manifest;
|
|
3167
|
-
try {
|
|
3168
|
-
manifest = JSON.parse(manifestRaw);
|
|
3169
|
-
} catch (err) {
|
|
3170
|
-
throw new DiscoveryError(dirPath, "manifest", "Invalid JSON in tool.json", err);
|
|
3171
|
-
}
|
|
3172
|
-
if (!manifest.kind) {
|
|
3173
|
-
throw new DiscoveryError(dirPath, "manifest", `tool.json must have a "kind" field`);
|
|
3174
|
-
}
|
|
3175
|
-
if (manifest.enabled === false) return [];
|
|
3176
|
-
if (manifest.kind === "langchain") {
|
|
3177
|
-
if (manifest.entryPoint) {
|
|
3178
|
-
const loaded2 = await loadLangChainTool(dirPath, manifest, this.extensions);
|
|
3179
|
-
return [this.toToolSpec(loaded2, dirName, dirPath, namespace)];
|
|
3180
|
-
}
|
|
3181
|
-
return loadLangChainToolsFromDir(
|
|
3182
|
-
dirPath,
|
|
3183
|
-
dirName,
|
|
3184
|
-
manifest,
|
|
3185
|
-
true,
|
|
3186
|
-
namespace,
|
|
3187
|
-
this.extensions,
|
|
3188
|
-
LANGCHAIN_DIR_NAME,
|
|
3189
|
-
(loaded2, nameHint, dp, ns) => this.toToolSpec(loaded2, nameHint, dp, ns),
|
|
3190
|
-
this.onError
|
|
3191
|
-
);
|
|
3192
|
-
}
|
|
3193
|
-
if (manifest.kind === "skill") {
|
|
3194
|
-
const loadedList = await loadSkillTools(dirPath, manifest, this.extensions);
|
|
3195
|
-
return loadedList.map(
|
|
3196
|
-
(loaded2) => this.toToolSpec(loaded2, dirName, dirPath, namespace)
|
|
3197
|
-
);
|
|
3198
|
-
}
|
|
3199
|
-
const loaded = await this.loadByKind(dirPath, manifest);
|
|
3200
|
-
return [this.toToolSpec(loaded, dirName, dirPath, namespace)];
|
|
3201
|
-
}
|
|
3202
|
-
async inferManifest(dirPath, dirName) {
|
|
3203
|
-
const kinds = [];
|
|
3204
|
-
for (const m of DIRECTORY_KIND_MARKERS) {
|
|
3205
|
-
if (await this.fileExists(join(dirPath, m.markerFile))) kinds.push(m.kind);
|
|
3206
|
-
}
|
|
3207
|
-
const isLangchainDir = dirName === LANGCHAIN_DIR_NAME;
|
|
3208
|
-
const hasLangchain = isLangchainDir ? (await listLangchainEntryFiles(dirPath, this.extensions)).length > 0 : dirName !== SKILL_DIR_NAME && await this.hasEntryPoint(dirPath, "index");
|
|
3209
|
-
if (hasLangchain) kinds.push("langchain");
|
|
3210
|
-
if (kinds.length === 0) return null;
|
|
3211
|
-
if (kinds.length > 1) {
|
|
3212
|
-
throw new DiscoveryError(
|
|
3213
|
-
dirPath,
|
|
3214
|
-
"manifest",
|
|
3215
|
-
`Ambiguous tool kind (found ${kinds.join(", ")}). Add tool.json to disambiguate.`
|
|
3216
|
-
);
|
|
3217
|
-
}
|
|
3218
|
-
const kind = kinds[0];
|
|
3219
|
-
const manifest = { kind };
|
|
3220
|
-
const marker = DIRECTORY_KIND_MARKERS.find((m) => m.kind === kind);
|
|
3221
|
-
if (marker) {
|
|
3222
|
-
manifest.entryPoint = marker.defaultEntryPoint;
|
|
3223
|
-
}
|
|
3224
|
-
if (kind === "langchain" && !isLangchainDir) manifest.entryPoint = "index";
|
|
3225
|
-
return manifest;
|
|
3226
|
-
}
|
|
3227
|
-
async fileExists(path7) {
|
|
3228
|
-
try {
|
|
3229
|
-
await access(path7);
|
|
3230
|
-
return true;
|
|
3231
|
-
} catch {
|
|
3232
|
-
return false;
|
|
3233
|
-
}
|
|
3234
|
-
}
|
|
3235
|
-
async hasEntryPoint(dirPath, baseName) {
|
|
3236
|
-
try {
|
|
3237
|
-
await resolveEntryPoint(dirPath, baseName, this.extensions);
|
|
3238
|
-
return true;
|
|
3239
|
-
} catch {
|
|
3240
|
-
return false;
|
|
3241
|
-
}
|
|
3242
|
-
}
|
|
3243
|
-
async loadByKind(dirPath, manifest) {
|
|
3244
|
-
const kind = manifest.kind;
|
|
3245
|
-
const loader = getDirectoryLoader(kind);
|
|
3246
|
-
const result = await loader(dirPath, manifest, this.extensions);
|
|
3247
|
-
const list = Array.isArray(result) ? result : [result];
|
|
3248
|
-
if (list.length === 0) {
|
|
3249
|
-
throw new DiscoveryError(dirPath, "load", "No tools loaded", new Error("empty"));
|
|
3250
|
-
}
|
|
3251
|
-
return list[0];
|
|
3252
|
-
}
|
|
3253
|
-
toToolSpec(loaded, dirName, dirPath, namespace) {
|
|
3254
|
-
const { manifest } = loaded;
|
|
3255
|
-
const kindDirNames = new Set(DIRECTORY_DISCOVERABLE_KINDS);
|
|
3256
|
-
const parentName = basename(join(dirPath, ".."));
|
|
3257
|
-
const isKindDir = kindDirNames.has(dirName);
|
|
3258
|
-
const defaultDirName = isKindDir ? parentName : dirName;
|
|
3259
|
-
const inferredName = isKindDir ? `${namespace}/${defaultDirName}-${dirName}` : `${namespace}/${defaultDirName}`;
|
|
3260
|
-
const name = manifest.name ?? inferredName;
|
|
3261
|
-
const spec = this.buildBaseSpec(manifest, name, dirName);
|
|
3262
|
-
applyDirectoryLoadedToSpec(spec, loaded, manifest, defaultDirName, namespace);
|
|
3263
|
-
return spec;
|
|
3264
|
-
}
|
|
3265
|
-
buildBaseSpec(manifest, name, dirName) {
|
|
3266
|
-
return {
|
|
3267
|
-
name,
|
|
3268
|
-
version: manifest.version ?? "1.0.0",
|
|
3269
|
-
kind: manifest.kind,
|
|
3270
|
-
description: manifest.description ?? `${manifest.kind} tool: ${dirName}`,
|
|
3271
|
-
tags: manifest.tags,
|
|
3272
|
-
inputSchema: manifest.inputSchema ?? { type: "object", additionalProperties: true },
|
|
3273
|
-
outputSchema: manifest.outputSchema ?? { type: "object", additionalProperties: true },
|
|
3274
|
-
capabilities: manifest.capabilities ?? [],
|
|
3275
|
-
costHints: manifest.costHints
|
|
3276
|
-
};
|
|
3277
|
-
}
|
|
3278
|
-
};
|
|
3279
1720
|
|
|
3280
1721
|
// src/api/runtimeFromConfig.ts
|
|
3281
1722
|
var requireFromPackage = createRequire(import.meta.url);
|
|
@@ -3472,11 +1913,11 @@ function parseNpmDescriptor(entry) {
|
|
|
3472
1913
|
}
|
|
3473
1914
|
function loadExtensionFromFileDescriptorSync(descriptor, configFilePath, stepLog) {
|
|
3474
1915
|
const entryStr = descriptor.trim();
|
|
3475
|
-
const
|
|
3476
|
-
if (!
|
|
1916
|
+
const path = parseToolPath(entryStr);
|
|
1917
|
+
if (!path || path.protocol !== "file") return null;
|
|
3477
1918
|
const localPath = isAbsolute(configFilePath) ? configFilePath : resolve(process.cwd(), configFilePath);
|
|
3478
1919
|
const configDir = dirname(localPath);
|
|
3479
|
-
const pathPart = `${
|
|
1920
|
+
const pathPart = `${path.scope}/${path.packageWithVersion}`;
|
|
3480
1921
|
const resolvedPath = resolve(configDir, pathPart);
|
|
3481
1922
|
if (!existsSync(resolvedPath) || !statSync(resolvedPath).isDirectory()) return null;
|
|
3482
1923
|
try {
|
|
@@ -3943,18 +2384,18 @@ var BodyParseError = class extends Error {
|
|
|
3943
2384
|
}
|
|
3944
2385
|
};
|
|
3945
2386
|
function parseBody(req) {
|
|
3946
|
-
return new Promise((
|
|
2387
|
+
return new Promise((resolve3, reject) => {
|
|
3947
2388
|
const chunks = [];
|
|
3948
2389
|
req.on("data", (chunk) => chunks.push(chunk));
|
|
3949
2390
|
req.on("end", () => {
|
|
3950
2391
|
const raw = Buffer.concat(chunks).toString("utf-8");
|
|
3951
2392
|
if (!raw.trim()) {
|
|
3952
|
-
|
|
2393
|
+
resolve3({});
|
|
3953
2394
|
return;
|
|
3954
2395
|
}
|
|
3955
2396
|
const parsed = safeParseToolArgs(raw);
|
|
3956
2397
|
if (parsed.ok) {
|
|
3957
|
-
|
|
2398
|
+
resolve3(parsed.value);
|
|
3958
2399
|
return;
|
|
3959
2400
|
}
|
|
3960
2401
|
reject(new BodyParseError("Invalid JSON body", parsed.hint));
|
|
@@ -4002,8 +2443,8 @@ function createOpenAPIHttpServer(runtime, options = {}) {
|
|
|
4002
2443
|
const ctxFactory = options.execContextFactory ?? (() => ({ ...DEFAULT_CTX }));
|
|
4003
2444
|
const server = createServer(async (req, res) => {
|
|
4004
2445
|
const url = req.url ?? "/";
|
|
4005
|
-
const
|
|
4006
|
-
const norm = basePath ?
|
|
2446
|
+
const path = url.split("?")[0] ?? "/";
|
|
2447
|
+
const norm = basePath ? path === basePath ? "" : path.replace(basePath, "") || "/" : path;
|
|
4007
2448
|
try {
|
|
4008
2449
|
if (req.method === "GET" && (norm === "/" || norm === "/swagger")) {
|
|
4009
2450
|
const specPath = basePath ? `${basePath}/openapi.json` : "/openapi.json";
|
|
@@ -4112,13 +2553,13 @@ function createOpenAPIHttpServer(runtime, options = {}) {
|
|
|
4112
2553
|
return server;
|
|
4113
2554
|
}
|
|
4114
2555
|
function listenOpenAPIHttpServer(server, options = {}) {
|
|
4115
|
-
return new Promise((
|
|
2556
|
+
return new Promise((resolve3, reject) => {
|
|
4116
2557
|
const port = options.port ?? 0;
|
|
4117
2558
|
const host = options.host ?? "localhost";
|
|
4118
2559
|
server.listen(port, host, () => {
|
|
4119
2560
|
const addr = server.address();
|
|
4120
2561
|
const actualPort = typeof addr === "object" && addr?.port != null ? addr.port : port;
|
|
4121
|
-
|
|
2562
|
+
resolve3({ port: actualPort, host });
|
|
4122
2563
|
});
|
|
4123
2564
|
server.on("error", reject);
|
|
4124
2565
|
});
|
|
@@ -4240,24 +2681,24 @@ function createMCPStreamableHttpHandler(runtimeOrConfig, options = {}) {
|
|
|
4240
2681
|
})();
|
|
4241
2682
|
}
|
|
4242
2683
|
async function createMCPServerStreamableHttp(runtimeOrConfig, options = {}) {
|
|
4243
|
-
const
|
|
2684
|
+
const path = options.path ?? "/mcp";
|
|
4244
2685
|
const host = options.host ?? "127.0.0.1";
|
|
4245
2686
|
const port = options.port ?? 3e3;
|
|
4246
2687
|
const { createMcpExpressApp } = await import('@modelcontextprotocol/sdk/server/express.js');
|
|
4247
2688
|
const handler = "invoke" in runtimeOrConfig && typeof runtimeOrConfig.invoke === "function" ? createMCPStreamableHttpHandler(runtimeOrConfig, options) : await createMCPStreamableHttpHandler(runtimeOrConfig, options);
|
|
4248
2689
|
const app = createMcpExpressApp({ host });
|
|
4249
|
-
app.post(
|
|
2690
|
+
app.post(path, handler);
|
|
4250
2691
|
return {
|
|
4251
2692
|
app,
|
|
4252
|
-
path
|
|
2693
|
+
path,
|
|
4253
2694
|
async listen(listenPort, listenHost) {
|
|
4254
2695
|
const p = listenPort ?? port;
|
|
4255
2696
|
const h = listenHost ?? host;
|
|
4256
|
-
return new Promise((
|
|
2697
|
+
return new Promise((resolve3, reject) => {
|
|
4257
2698
|
const server = app.listen(p, h, () => {
|
|
4258
2699
|
const addr = server.address();
|
|
4259
2700
|
const actualPort = typeof addr === "object" && addr !== null && "port" in addr ? addr.port : p;
|
|
4260
|
-
|
|
2701
|
+
resolve3({ url: `http://${h}:${actualPort}${path}`, port: actualPort });
|
|
4261
2702
|
});
|
|
4262
2703
|
});
|
|
4263
2704
|
}
|
|
@@ -4269,6 +2710,6 @@ async function runMCPServerOverStdio(runtime, options = {}) {
|
|
|
4269
2710
|
return result;
|
|
4270
2711
|
}
|
|
4271
2712
|
|
|
4272
|
-
export {
|
|
4273
|
-
//# sourceMappingURL=chunk-
|
|
4274
|
-
//# sourceMappingURL=chunk-
|
|
2713
|
+
export { PTCRuntime, createHttpService, createMCPServer, createMCPServerStreamableHttp, createMCPStreamableHttpHandler, createRuntimeFromConfig, createRuntimeFromConfigSync, expandToolDescriptorsToRegistryNames, fileDescriptorToPackagePrefix, findAndLoadToolConfig, getDisplayScope, isBarePackageDescriptor, loadToolConfig, npmDescriptorToPackagePrefixWithVersion, resolveSandboxedPath, resolveToolDescriptor, runMCPServerOverStdio };
|
|
2714
|
+
//# sourceMappingURL=chunk-NVT4X4CB.js.map
|
|
2715
|
+
//# sourceMappingURL=chunk-NVT4X4CB.js.map
|