@easynet/agent-tool 1.0.54 → 1.0.55
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/mcp-build/generator.d.ts.map +1 -1
- package/dist/api/main.cjs +13 -13
- package/dist/api/main.js +2 -2
- package/dist/api/runtimeFromConfig.d.ts.map +1 -1
- package/dist/{chunk-WFPHIYB6.cjs → chunk-D2T3NTFN.cjs} +1726 -51
- package/dist/chunk-D2T3NTFN.cjs.map +1 -0
- package/dist/chunk-TFT4RYFZ.cjs +353 -0
- package/dist/chunk-TFT4RYFZ.cjs.map +1 -0
- package/dist/chunk-W4AGLV2U.js +325 -0
- package/dist/chunk-W4AGLV2U.js.map +1 -0
- package/dist/{chunk-VXEQDRMQ.cjs → chunk-WAGGOFT3.cjs} +15 -15
- package/dist/{chunk-VXEQDRMQ.cjs.map → chunk-WAGGOFT3.cjs.map} +1 -1
- package/dist/{chunk-CRYLF2CC.js → chunk-WC54NBDE.js} +3 -3
- package/dist/{chunk-CRYLF2CC.js.map → chunk-WC54NBDE.js.map} +1 -1
- package/dist/{chunk-6VU6PRO3.js → chunk-XT2CXR6A.js} +1703 -52
- package/dist/chunk-XT2CXR6A.js.map +1 -0
- package/dist/index.cjs +30 -30
- package/dist/index.js +4 -4
- package/dist/utils/cli/index.cjs +16 -16
- package/dist/utils/cli/index.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-6VU6PRO3.js.map +0 -1
- package/dist/chunk-K45PQLZI.cjs +0 -1868
- package/dist/chunk-K45PQLZI.cjs.map +0 -1
- package/dist/chunk-WFPHIYB6.cjs.map +0 -1
- package/dist/chunk-Z5RKNQQB.js +0 -1835
- package/dist/chunk-Z5RKNQQB.js.map +0 -1
|
@@ -13,17 +13,41 @@ var eventemitter3 = require('eventemitter3');
|
|
|
13
13
|
var uuid = require('uuid');
|
|
14
14
|
var pTimeout = require('p-timeout');
|
|
15
15
|
var promises = require('fs/promises');
|
|
16
|
+
var ts2 = require('typescript');
|
|
16
17
|
var module$1 = require('module');
|
|
17
18
|
var url = require('url');
|
|
19
|
+
var yaml = require('js-yaml');
|
|
18
20
|
var npm = require('@easynet/agent-common/npm');
|
|
19
21
|
var http = require('http');
|
|
20
22
|
|
|
21
23
|
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
22
24
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
23
25
|
|
|
26
|
+
function _interopNamespace(e) {
|
|
27
|
+
if (e && e.__esModule) return e;
|
|
28
|
+
var n = Object.create(null);
|
|
29
|
+
if (e) {
|
|
30
|
+
Object.keys(e).forEach(function (k) {
|
|
31
|
+
if (k !== 'default') {
|
|
32
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
33
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
34
|
+
enumerable: true,
|
|
35
|
+
get: function () { return e[k]; }
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
n.default = e;
|
|
41
|
+
return Object.freeze(n);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
var fs__namespace = /*#__PURE__*/_interopNamespace(fs);
|
|
45
|
+
var path__namespace = /*#__PURE__*/_interopNamespace(path);
|
|
24
46
|
var Ajv__default = /*#__PURE__*/_interopDefault(Ajv);
|
|
25
47
|
var addFormats__default = /*#__PURE__*/_interopDefault(addFormats);
|
|
26
48
|
var pTimeout__default = /*#__PURE__*/_interopDefault(pTimeout);
|
|
49
|
+
var ts2__namespace = /*#__PURE__*/_interopNamespace(ts2);
|
|
50
|
+
var yaml__default = /*#__PURE__*/_interopDefault(yaml);
|
|
27
51
|
|
|
28
52
|
function loadToolConfig(toolYamlPath) {
|
|
29
53
|
const abs = path.resolve(toolYamlPath);
|
|
@@ -202,13 +226,13 @@ function expandToolDescriptorsToRegistryNames(descriptors, registryNames) {
|
|
|
202
226
|
out.push(s);
|
|
203
227
|
continue;
|
|
204
228
|
}
|
|
205
|
-
const
|
|
206
|
-
if (
|
|
207
|
-
const packagePrefix =
|
|
229
|
+
const path7 = parseToolPath(s);
|
|
230
|
+
if (path7) {
|
|
231
|
+
const packagePrefix = path7.protocol === "npm" ? npmDescriptorToPackagePrefix(s) : path7.protocol === "file" ? fileDescriptorToPackagePrefix(s) : "";
|
|
208
232
|
const prefixWithDot = packagePrefix ? packagePrefix + "." : "";
|
|
209
233
|
if (prefixWithDot) {
|
|
210
|
-
if (
|
|
211
|
-
const suffix = "." +
|
|
234
|
+
if (path7.toolName) {
|
|
235
|
+
const suffix = "." + path7.toolName;
|
|
212
236
|
for (const r of registryNames) {
|
|
213
237
|
if (r.startsWith(prefixWithDot) && r.endsWith(suffix) && !seen.has(r)) {
|
|
214
238
|
seen.add(r);
|
|
@@ -237,9 +261,9 @@ function resolveToolDescriptor(descriptor) {
|
|
|
237
261
|
return s;
|
|
238
262
|
}
|
|
239
263
|
function fileDescriptorToPackagePrefix(descriptor) {
|
|
240
|
-
const
|
|
241
|
-
if (!
|
|
242
|
-
const pathPart = `${
|
|
264
|
+
const path7 = parseToolPath(descriptor.trim());
|
|
265
|
+
if (!path7 || path7.protocol !== "file") return "";
|
|
266
|
+
const pathPart = `${path7.scope}/${path7.packageWithVersion}`;
|
|
243
267
|
const normalized = chunkQEJF3KDV_cjs.normalizeToolName(pathPart);
|
|
244
268
|
if (!normalized) return "";
|
|
245
269
|
return "file." + normalized;
|
|
@@ -539,17 +563,17 @@ var PolicyEngine = class {
|
|
|
539
563
|
*/
|
|
540
564
|
extractStringValues(args, keyPatterns) {
|
|
541
565
|
const results = [];
|
|
542
|
-
const
|
|
566
|
+
const walk2 = (obj) => {
|
|
543
567
|
for (const [key, val] of Object.entries(obj)) {
|
|
544
568
|
const lowerKey = key.toLowerCase();
|
|
545
569
|
if (typeof val === "string" && keyPatterns.some((p) => lowerKey.includes(p))) {
|
|
546
570
|
results.push(val);
|
|
547
571
|
} else if (val && typeof val === "object" && !Array.isArray(val)) {
|
|
548
|
-
|
|
572
|
+
walk2(val);
|
|
549
573
|
}
|
|
550
574
|
}
|
|
551
575
|
};
|
|
552
|
-
|
|
576
|
+
walk2(args);
|
|
553
577
|
return results;
|
|
554
578
|
}
|
|
555
579
|
};
|
|
@@ -734,30 +758,30 @@ function summarizeValue(value, maxLen) {
|
|
|
734
758
|
}
|
|
735
759
|
function extractUrls(obj) {
|
|
736
760
|
const urls = [];
|
|
737
|
-
const
|
|
761
|
+
const walk2 = (val) => {
|
|
738
762
|
if (typeof val === "string" && /^https?:\/\//i.test(val)) {
|
|
739
763
|
urls.push(val);
|
|
740
764
|
} else if (val && typeof val === "object") {
|
|
741
765
|
for (const v of Object.values(val)) {
|
|
742
|
-
|
|
766
|
+
walk2(v);
|
|
743
767
|
}
|
|
744
768
|
}
|
|
745
769
|
};
|
|
746
|
-
|
|
770
|
+
walk2(obj);
|
|
747
771
|
return urls.slice(0, 10);
|
|
748
772
|
}
|
|
749
773
|
function extractFilePaths(obj) {
|
|
750
774
|
const paths = [];
|
|
751
|
-
const
|
|
775
|
+
const walk2 = (val) => {
|
|
752
776
|
if (typeof val === "string" && (val.startsWith("/") || val.startsWith("./")) && val.includes(".")) {
|
|
753
777
|
paths.push(val);
|
|
754
778
|
} else if (val && typeof val === "object") {
|
|
755
779
|
for (const v of Object.values(val)) {
|
|
756
|
-
|
|
780
|
+
walk2(v);
|
|
757
781
|
}
|
|
758
782
|
}
|
|
759
783
|
};
|
|
760
|
-
|
|
784
|
+
walk2(obj);
|
|
761
785
|
return paths.slice(0, 10);
|
|
762
786
|
}
|
|
763
787
|
var EventLog = class {
|
|
@@ -1760,14 +1784,1530 @@ async function resolveSandboxedPath2(inputPath, sandboxRoot) {
|
|
|
1760
1784
|
}
|
|
1761
1785
|
return real;
|
|
1762
1786
|
}
|
|
1763
|
-
function isWithinRoot(
|
|
1764
|
-
const normalizedPath = path.normalize(
|
|
1787
|
+
function isWithinRoot(path7, root) {
|
|
1788
|
+
const normalizedPath = path.normalize(path7);
|
|
1765
1789
|
const normalizedRoot = path.normalize(root);
|
|
1766
1790
|
return normalizedPath === normalizedRoot || normalizedPath.startsWith(normalizedRoot + "/");
|
|
1767
1791
|
}
|
|
1792
|
+
async function findDirsContainingFile(rootPath, fileName) {
|
|
1793
|
+
const found = [];
|
|
1794
|
+
await collectDirsWithFile(rootPath, fileName, found);
|
|
1795
|
+
return found;
|
|
1796
|
+
}
|
|
1797
|
+
async function collectDirsWithFile(dir, fileName, acc) {
|
|
1798
|
+
let entries;
|
|
1799
|
+
try {
|
|
1800
|
+
const e = await promises.readdir(dir, { withFileTypes: true });
|
|
1801
|
+
entries = e.map((x) => ({
|
|
1802
|
+
name: x.name,
|
|
1803
|
+
isDirectory: x.isDirectory(),
|
|
1804
|
+
isFile: x.isFile()
|
|
1805
|
+
}));
|
|
1806
|
+
} catch {
|
|
1807
|
+
return;
|
|
1808
|
+
}
|
|
1809
|
+
if (entries.some((x) => x.isFile && x.name === fileName)) acc.push(dir);
|
|
1810
|
+
for (const entry of entries) {
|
|
1811
|
+
if (!entry.isDirectory || entry.name === "node_modules" || entry.name.startsWith(".")) continue;
|
|
1812
|
+
await collectDirsWithFile(path.join(dir, entry.name), fileName, acc);
|
|
1813
|
+
}
|
|
1814
|
+
}
|
|
1815
|
+
function pathToToolName(sourcePath, programName) {
|
|
1816
|
+
const normalized = sourcePath.replace(/\\/g, "/").replace(/\.(ts|tsx|js|mjs|json)$/i, "");
|
|
1817
|
+
const segments = normalized.split("/").filter(Boolean);
|
|
1818
|
+
return segments.length === 0 ? programName : `${segments.join(".")}.${programName}`;
|
|
1819
|
+
}
|
|
1820
|
+
function buildOutputSchemaFromReturnType(node, typeChecker, onWarn) {
|
|
1821
|
+
const sig = typeChecker.getSignatureFromDeclaration(node);
|
|
1822
|
+
if (!sig) {
|
|
1823
|
+
onWarn?.("Could not get signature for return type, using object");
|
|
1824
|
+
return { type: "object", additionalProperties: true };
|
|
1825
|
+
}
|
|
1826
|
+
let returnType = typeChecker.getReturnTypeOfSignature(sig);
|
|
1827
|
+
if (returnType.getSymbol?.()?.getName() === "Promise") {
|
|
1828
|
+
const typeArgs = returnType.typeArguments;
|
|
1829
|
+
if (typeArgs?.[0]) returnType = typeArgs[0];
|
|
1830
|
+
}
|
|
1831
|
+
const schema = typeToJsonSchema(returnType, typeChecker, onWarn);
|
|
1832
|
+
const hasProps = typeof schema === "object" && schema.type === "object" && Object.keys(schema.properties ?? {}).length > 0;
|
|
1833
|
+
return hasProps ? schema : { type: "object", additionalProperties: true };
|
|
1834
|
+
}
|
|
1835
|
+
function buildInputSchemaFromParams(node, typeChecker, onWarn) {
|
|
1836
|
+
const properties = {};
|
|
1837
|
+
const required = [];
|
|
1838
|
+
if (!node.parameters.length) {
|
|
1839
|
+
return { schema: { type: "object", properties: {} }, required: [] };
|
|
1840
|
+
}
|
|
1841
|
+
for (const param of node.parameters) {
|
|
1842
|
+
const name = param.name.getText();
|
|
1843
|
+
if (name.startsWith("_") && name.length <= 2) continue;
|
|
1844
|
+
const sym = param.symbol;
|
|
1845
|
+
const paramType = sym ? typeChecker.getTypeOfSymbolAtLocation(sym, param) : typeChecker.getTypeAtLocation(param);
|
|
1846
|
+
const isOptional = !!param.questionToken || param.initializer !== void 0;
|
|
1847
|
+
const propSchema = typeToJsonSchema(paramType, typeChecker, onWarn);
|
|
1848
|
+
properties[name] = propSchema;
|
|
1849
|
+
if (!isOptional) required.push(name);
|
|
1850
|
+
}
|
|
1851
|
+
const paramNames = Object.keys(properties);
|
|
1852
|
+
if (paramNames.length === 1) {
|
|
1853
|
+
const soleName = paramNames[0];
|
|
1854
|
+
const inner = properties[soleName];
|
|
1855
|
+
if (inner && typeof inner === "object" && inner.type === "object" && inner.properties && typeof inner.properties === "object") {
|
|
1856
|
+
return {
|
|
1857
|
+
schema: {
|
|
1858
|
+
type: "object",
|
|
1859
|
+
properties: inner.properties,
|
|
1860
|
+
...Array.isArray(inner.required) && inner.required.length > 0 ? { required: inner.required } : {},
|
|
1861
|
+
...inner.additionalProperties !== void 0 ? { additionalProperties: inner.additionalProperties } : {}
|
|
1862
|
+
},
|
|
1863
|
+
required: inner.required ?? []
|
|
1864
|
+
};
|
|
1865
|
+
}
|
|
1866
|
+
}
|
|
1867
|
+
return {
|
|
1868
|
+
schema: {
|
|
1869
|
+
type: "object",
|
|
1870
|
+
properties,
|
|
1871
|
+
...required.length > 0 ? { required } : {}
|
|
1872
|
+
},
|
|
1873
|
+
required
|
|
1874
|
+
};
|
|
1875
|
+
}
|
|
1876
|
+
function typeToJsonSchema(type, typeChecker, onWarn) {
|
|
1877
|
+
const flags = type.flags;
|
|
1878
|
+
if (flags & ts2__namespace.TypeFlags.String) return { type: "string" };
|
|
1879
|
+
if (flags & ts2__namespace.TypeFlags.Number) return { type: "number" };
|
|
1880
|
+
if (flags & ts2__namespace.TypeFlags.Boolean) return { type: "boolean" };
|
|
1881
|
+
if (flags & ts2__namespace.TypeFlags.BooleanLiteral) return { type: "boolean" };
|
|
1882
|
+
if (flags & ts2__namespace.TypeFlags.Null) return { type: "null" };
|
|
1883
|
+
if (flags & ts2__namespace.TypeFlags.Undefined || flags & ts2__namespace.TypeFlags.Void) return {};
|
|
1884
|
+
if (flags & ts2__namespace.TypeFlags.Any || flags & ts2__namespace.TypeFlags.Unknown) {
|
|
1885
|
+
onWarn?.(`Unsupported type: any/unknown, using empty schema`);
|
|
1886
|
+
return {};
|
|
1887
|
+
}
|
|
1888
|
+
if (type.isUnion?.()) {
|
|
1889
|
+
const union = type;
|
|
1890
|
+
const types = union.types;
|
|
1891
|
+
const withoutUndef = types.filter(
|
|
1892
|
+
(t) => !(t.flags & ts2__namespace.TypeFlags.Undefined) && !(t.flags & ts2__namespace.TypeFlags.Void)
|
|
1893
|
+
);
|
|
1894
|
+
if (withoutUndef.length === 1) return typeToJsonSchema(withoutUndef[0], typeChecker, onWarn);
|
|
1895
|
+
if (withoutUndef.length === 0) return {};
|
|
1896
|
+
const stringEnumValues = [];
|
|
1897
|
+
let allStringLiterals = true;
|
|
1898
|
+
for (const t of withoutUndef) {
|
|
1899
|
+
if (t.flags & ts2__namespace.TypeFlags.StringLiteral) {
|
|
1900
|
+
const lit = t;
|
|
1901
|
+
if (typeof lit.value === "string") stringEnumValues.push(lit.value);
|
|
1902
|
+
} else {
|
|
1903
|
+
allStringLiterals = false;
|
|
1904
|
+
break;
|
|
1905
|
+
}
|
|
1906
|
+
}
|
|
1907
|
+
if (allStringLiterals && stringEnumValues.length > 0) {
|
|
1908
|
+
return { type: "string", enum: [...new Set(stringEnumValues)] };
|
|
1909
|
+
}
|
|
1910
|
+
let allBooleanLiterals = true;
|
|
1911
|
+
for (const t of withoutUndef) {
|
|
1912
|
+
if (!(t.flags & ts2__namespace.TypeFlags.BooleanLiteral)) {
|
|
1913
|
+
allBooleanLiterals = false;
|
|
1914
|
+
break;
|
|
1915
|
+
}
|
|
1916
|
+
}
|
|
1917
|
+
if (allBooleanLiterals) return { type: "boolean" };
|
|
1918
|
+
}
|
|
1919
|
+
if (flags & ts2__namespace.TypeFlags.StringLiteral) {
|
|
1920
|
+
const lit = type;
|
|
1921
|
+
if (typeof lit.value === "string") {
|
|
1922
|
+
return { type: "string", enum: [lit.value] };
|
|
1923
|
+
}
|
|
1924
|
+
return { type: "string" };
|
|
1925
|
+
}
|
|
1926
|
+
if (typeChecker.isArrayType(type)) {
|
|
1927
|
+
const typeRef = type;
|
|
1928
|
+
const typeArgs = typeRef.typeArguments;
|
|
1929
|
+
const itemType = typeArgs?.[0];
|
|
1930
|
+
const items = itemType ? typeToJsonSchema(itemType, typeChecker, onWarn) : {};
|
|
1931
|
+
return { type: "array", items: Object.keys(items).length ? items : {} };
|
|
1932
|
+
}
|
|
1933
|
+
const str = typeChecker.typeToString(type);
|
|
1934
|
+
if (str === "string") return { type: "string" };
|
|
1935
|
+
if (str === "number") return { type: "number" };
|
|
1936
|
+
if (str === "boolean") return { type: "boolean" };
|
|
1937
|
+
if (str.endsWith("[]")) {
|
|
1938
|
+
const inner = str.slice(0, -2).trim();
|
|
1939
|
+
const itemType = inner === "string" ? { type: "string" } : inner === "number" ? { type: "number" } : {};
|
|
1940
|
+
return { type: "array", items: itemType };
|
|
1941
|
+
}
|
|
1942
|
+
if (type.getProperties && type.getProperties().length >= 0) {
|
|
1943
|
+
const props = type.getProperties();
|
|
1944
|
+
const properties = {};
|
|
1945
|
+
const required = [];
|
|
1946
|
+
for (const p of props) {
|
|
1947
|
+
const decl = p.valueDeclaration;
|
|
1948
|
+
const propType = decl ? typeChecker.getTypeAtLocation(decl) : typeChecker.getTypeOfSymbolAtLocation(p, p.valueDeclaration);
|
|
1949
|
+
const optional = decl && ts2__namespace.isPropertySignature(decl) ? !!decl.questionToken : false;
|
|
1950
|
+
properties[p.name] = typeToJsonSchema(propType, typeChecker, onWarn);
|
|
1951
|
+
if (!optional) required.push(p.name);
|
|
1952
|
+
}
|
|
1953
|
+
return { type: "object", properties, ...required.length ? { required } : {} };
|
|
1954
|
+
}
|
|
1955
|
+
onWarn?.(`Unsupported type: ${str}, using object`);
|
|
1956
|
+
return { type: "object" };
|
|
1957
|
+
}
|
|
1958
|
+
|
|
1959
|
+
// src/tools/function/types.ts
|
|
1960
|
+
var FUNCTION_KIND = "function";
|
|
1961
|
+
|
|
1962
|
+
// src/tools/skill/types.ts
|
|
1963
|
+
var SKILL_KIND = "skill";
|
|
1964
|
+
var SKILL_DIR_NAME = "skill";
|
|
1965
|
+
|
|
1966
|
+
// src/tools/n8n/types.ts
|
|
1967
|
+
var N8N_KIND = "n8n";
|
|
1968
|
+
|
|
1969
|
+
// src/tools/mcp/mcpSpecToToolSpec.ts
|
|
1970
|
+
var DEFAULT_OUTPUT = { type: "object", additionalProperties: true };
|
|
1971
|
+
function mcpSpecToToolSpec(spec, projectPath) {
|
|
1972
|
+
const base = {
|
|
1973
|
+
name: spec.name,
|
|
1974
|
+
version: "1.0.0",
|
|
1975
|
+
kind: spec.kind,
|
|
1976
|
+
description: spec.description,
|
|
1977
|
+
inputSchema: spec.inputSchema ?? DEFAULT_OUTPUT,
|
|
1978
|
+
outputSchema: "outputSchema" in spec && spec.outputSchema ? spec.outputSchema : chunkQEJF3KDV_cjs.DEFAULT_OUTPUT_SCHEMA,
|
|
1979
|
+
capabilities: [],
|
|
1980
|
+
_meta: spec._meta,
|
|
1981
|
+
...spec.kind === N8N_KIND && "webhookUrl" in spec && spec.webhookUrl ? { endpoint: spec.webhookUrl } : {}
|
|
1982
|
+
};
|
|
1983
|
+
if (spec.kind === FUNCTION_KIND && "sourcePath" in spec && "exportName" in spec) {
|
|
1984
|
+
base._meta = {
|
|
1985
|
+
...base._meta,
|
|
1986
|
+
sourcePath: spec.sourcePath,
|
|
1987
|
+
exportName: spec.exportName,
|
|
1988
|
+
...projectPath && { projectPath }
|
|
1989
|
+
};
|
|
1990
|
+
}
|
|
1991
|
+
if (spec.kind === SKILL_KIND && "sourcePath" in spec && projectPath) {
|
|
1992
|
+
base._meta = { ...base._meta, sourcePath: spec.sourcePath, projectPath };
|
|
1993
|
+
}
|
|
1994
|
+
if (spec.kind === N8N_KIND && "sourcePath" in spec && projectPath) {
|
|
1995
|
+
base._meta = { ...base._meta, sourcePath: spec.sourcePath, projectPath };
|
|
1996
|
+
}
|
|
1997
|
+
return base;
|
|
1998
|
+
}
|
|
1999
|
+
|
|
2000
|
+
// src/tools/function/scanner.ts
|
|
2001
|
+
var TOOL_TAG = "@tool";
|
|
2002
|
+
var EFFECT_VALUES = ["none", "local_write", "external_write", "destructive"];
|
|
2003
|
+
function scanForTools(options) {
|
|
2004
|
+
const projectPath = path__namespace.resolve(options.projectPath);
|
|
2005
|
+
const tsconfigPath = options.tsconfigPath ?? path__namespace.join(projectPath, "tsconfig.json");
|
|
2006
|
+
const include = options.include ?? ["**/*.ts"];
|
|
2007
|
+
const errors = [];
|
|
2008
|
+
const warnings = [];
|
|
2009
|
+
let config;
|
|
2010
|
+
let configPathResolved = path__namespace.resolve(projectPath, tsconfigPath);
|
|
2011
|
+
if (!fs__namespace.existsSync(configPathResolved)) {
|
|
2012
|
+
configPathResolved = path__namespace.join(projectPath, "tsconfig.json");
|
|
2013
|
+
}
|
|
2014
|
+
if (fs__namespace.existsSync(configPathResolved)) {
|
|
2015
|
+
const configFile = ts2__namespace.readConfigFile(configPathResolved, ts2__namespace.sys.readFile);
|
|
2016
|
+
if (configFile.error) {
|
|
2017
|
+
errors.push({ file: configPathResolved, message: String(configFile.error.messageText) });
|
|
2018
|
+
return { specs: [], errors, warnings };
|
|
2019
|
+
}
|
|
2020
|
+
const parsed = ts2__namespace.parseJsonConfigFileContent(
|
|
2021
|
+
configFile.config,
|
|
2022
|
+
ts2__namespace.sys,
|
|
2023
|
+
path__namespace.dirname(configPathResolved)
|
|
2024
|
+
);
|
|
2025
|
+
if (parsed.errors.length) {
|
|
2026
|
+
for (const e of parsed.errors) {
|
|
2027
|
+
errors.push({ file: e.file?.fileName ?? "tsconfig", message: String(e.messageText) });
|
|
2028
|
+
}
|
|
2029
|
+
return { specs: [], errors, warnings };
|
|
2030
|
+
}
|
|
2031
|
+
config = parsed;
|
|
2032
|
+
} else {
|
|
2033
|
+
config = {
|
|
2034
|
+
options: {
|
|
2035
|
+
target: ts2__namespace.ScriptTarget.ES2022,
|
|
2036
|
+
module: ts2__namespace.ModuleKind.ESNext,
|
|
2037
|
+
moduleResolution: ts2__namespace.ModuleResolutionKind.NodeNext,
|
|
2038
|
+
strict: true,
|
|
2039
|
+
skipLibCheck: true,
|
|
2040
|
+
noEmit: true
|
|
2041
|
+
},
|
|
2042
|
+
fileNames: resolveGlob(projectPath, include),
|
|
2043
|
+
errors: []
|
|
2044
|
+
};
|
|
2045
|
+
}
|
|
2046
|
+
const program = ts2__namespace.createProgram(config.fileNames, config.options);
|
|
2047
|
+
const typeChecker = program.getTypeChecker();
|
|
2048
|
+
const specs = [];
|
|
2049
|
+
for (const sourceFile of program.getSourceFiles()) {
|
|
2050
|
+
const fileName = sourceFile.fileName;
|
|
2051
|
+
if (fileName.includes("node_modules") || fileName.endsWith(".d.ts")) continue;
|
|
2052
|
+
if (!config.fileNames.some((f) => path__namespace.resolve(f) === path__namespace.resolve(fileName))) continue;
|
|
2053
|
+
ts2__namespace.forEachChild(sourceFile, (node) => {
|
|
2054
|
+
const decl = getExportedFunctionDeclaration(node);
|
|
2055
|
+
if (!decl) return;
|
|
2056
|
+
const func = decl.func;
|
|
2057
|
+
const name = decl.name;
|
|
2058
|
+
if (!name) return;
|
|
2059
|
+
const host = getJSDocHost(func);
|
|
2060
|
+
if (!hasToolTag(host)) return;
|
|
2061
|
+
const jsDoc = getJSDocComments(host);
|
|
2062
|
+
const description = getDescription(jsDoc);
|
|
2063
|
+
if (!description) {
|
|
2064
|
+
warnings.push({ file: fileName, message: `Tool ${name}: missing description, using humanized name` });
|
|
2065
|
+
}
|
|
2066
|
+
const sideEffect = getEffect(host);
|
|
2067
|
+
const onWarn = (msg) => warnings.push({ file: fileName, message: `${name}: ${msg}` });
|
|
2068
|
+
const { schema } = buildInputSchemaFromParams(func, typeChecker, onWarn);
|
|
2069
|
+
const inputSchema = Object.keys(schema.properties ?? {}).length > 0 ? schema : { type: "object", properties: {} };
|
|
2070
|
+
const outputSchema = buildOutputSchemaFromReturnType(func, typeChecker, onWarn);
|
|
2071
|
+
const sourcePath = path__namespace.relative(projectPath, fileName) || path__namespace.basename(fileName);
|
|
2072
|
+
const toolName = pathToToolName(sourcePath, name);
|
|
2073
|
+
specs.push({
|
|
2074
|
+
kind: FUNCTION_KIND,
|
|
2075
|
+
name: toolName,
|
|
2076
|
+
description: description || humanize(name),
|
|
2077
|
+
inputSchema,
|
|
2078
|
+
outputSchema,
|
|
2079
|
+
_meta: { hitl: { sideEffect } },
|
|
2080
|
+
sourcePath,
|
|
2081
|
+
exportName: name
|
|
2082
|
+
});
|
|
2083
|
+
});
|
|
2084
|
+
}
|
|
2085
|
+
return { specs, errors, warnings };
|
|
2086
|
+
}
|
|
2087
|
+
function resolveGlob(projectPath, patterns) {
|
|
2088
|
+
const result = [];
|
|
2089
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2090
|
+
const add = (f) => {
|
|
2091
|
+
const abs = path__namespace.resolve(f);
|
|
2092
|
+
if (f.endsWith(".ts") && !f.endsWith(".d.ts") && !seen.has(abs)) {
|
|
2093
|
+
seen.add(abs);
|
|
2094
|
+
result.push(abs);
|
|
2095
|
+
}
|
|
2096
|
+
};
|
|
2097
|
+
for (const p of patterns) {
|
|
2098
|
+
const full = path__namespace.join(projectPath, p);
|
|
2099
|
+
if (full.includes("*")) {
|
|
2100
|
+
const baseDir = full.replace(/\*\*\/.*$/, "").replace(/\*.*$/, "").replace(/\/?$/, "") || ".";
|
|
2101
|
+
const dir = path__namespace.resolve(projectPath, baseDir);
|
|
2102
|
+
if (fs__namespace.existsSync(dir)) walk(dir, add);
|
|
2103
|
+
} else {
|
|
2104
|
+
const resolved = path__namespace.resolve(projectPath, full);
|
|
2105
|
+
if (fs__namespace.existsSync(resolved)) {
|
|
2106
|
+
if (fs__namespace.statSync(resolved).isFile()) add(resolved);
|
|
2107
|
+
else walk(resolved, add);
|
|
2108
|
+
}
|
|
2109
|
+
}
|
|
2110
|
+
}
|
|
2111
|
+
if (result.length > 0) return result;
|
|
2112
|
+
const srcDir = path__namespace.join(projectPath, "src");
|
|
2113
|
+
if (fs__namespace.existsSync(srcDir)) return walkCollect(srcDir);
|
|
2114
|
+
return [];
|
|
2115
|
+
}
|
|
2116
|
+
function walkCollect(dir) {
|
|
2117
|
+
const out = [];
|
|
2118
|
+
walk(dir, (fullPath) => {
|
|
2119
|
+
if (fullPath.endsWith(".ts") && !fullPath.endsWith(".d.ts")) out.push(path__namespace.resolve(fullPath));
|
|
2120
|
+
});
|
|
2121
|
+
return out;
|
|
2122
|
+
}
|
|
2123
|
+
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", "generated", "dist"]);
|
|
2124
|
+
function walk(dir, visit) {
|
|
2125
|
+
try {
|
|
2126
|
+
const entries = fs__namespace.readdirSync(dir, { withFileTypes: true });
|
|
2127
|
+
for (const e of entries) {
|
|
2128
|
+
const full = path__namespace.join(dir, e.name);
|
|
2129
|
+
if (e.isDirectory() && !SKIP_DIRS.has(e.name)) walk(full, visit);
|
|
2130
|
+
else if (e.isFile()) visit(full);
|
|
2131
|
+
}
|
|
2132
|
+
} catch {
|
|
2133
|
+
}
|
|
2134
|
+
}
|
|
2135
|
+
function getExportedFunctionDeclaration(node, _sourceFile) {
|
|
2136
|
+
if (ts2__namespace.isFunctionDeclaration(node) && node.name) {
|
|
2137
|
+
const exported = (ts2__namespace.getModifiers(node) ?? []).some((m) => m.kind === ts2__namespace.SyntaxKind.ExportKeyword);
|
|
2138
|
+
if (exported) return { func: node, name: node.name.getText() };
|
|
2139
|
+
return null;
|
|
2140
|
+
}
|
|
2141
|
+
if (ts2__namespace.isVariableStatement(node)) {
|
|
2142
|
+
const exported = (ts2__namespace.getModifiers(node) ?? []).some((m) => m.kind === ts2__namespace.SyntaxKind.ExportKeyword);
|
|
2143
|
+
if (!exported) return null;
|
|
2144
|
+
for (const decl of node.declarationList.declarations) {
|
|
2145
|
+
let init = decl.initializer;
|
|
2146
|
+
while (init && (ts2__namespace.isParenthesizedExpression(init) || ts2__namespace.isAsExpression(init)))
|
|
2147
|
+
init = init.expression;
|
|
2148
|
+
if (init && ts2__namespace.isArrowFunction(init)) {
|
|
2149
|
+
const name = decl.name.getText();
|
|
2150
|
+
return { func: init, name };
|
|
2151
|
+
}
|
|
2152
|
+
if (init && ts2__namespace.isFunctionExpression(init)) {
|
|
2153
|
+
const name = decl.name.getText();
|
|
2154
|
+
return { func: init, name };
|
|
2155
|
+
}
|
|
2156
|
+
}
|
|
2157
|
+
}
|
|
2158
|
+
return null;
|
|
2159
|
+
}
|
|
2160
|
+
function getJSDocHost(node) {
|
|
2161
|
+
const parent = node.parent;
|
|
2162
|
+
if (ts2__namespace.isVariableDeclaration(parent)) {
|
|
2163
|
+
const gp = parent.parent;
|
|
2164
|
+
if (ts2__namespace.isVariableDeclarationList(gp) && gp.parent && ts2__namespace.isVariableStatement(gp.parent)) return gp.parent;
|
|
2165
|
+
}
|
|
2166
|
+
return node;
|
|
2167
|
+
}
|
|
2168
|
+
function getJSDocComments(host) {
|
|
2169
|
+
const all = ts2__namespace.getJSDocCommentsAndTags(host);
|
|
2170
|
+
return all.filter((t) => ts2__namespace.isJSDoc(t));
|
|
2171
|
+
}
|
|
2172
|
+
function hasToolTag(host) {
|
|
2173
|
+
const tags = ts2__namespace.getJSDocTags(host);
|
|
2174
|
+
for (const tag of tags) {
|
|
2175
|
+
const name = tag.tagName?.getText() ?? "";
|
|
2176
|
+
if (name === "tool") return true;
|
|
2177
|
+
}
|
|
2178
|
+
const all = ts2__namespace.getJSDocCommentsAndTags(host);
|
|
2179
|
+
for (const t of all) {
|
|
2180
|
+
if (ts2__namespace.isJSDoc(t)) {
|
|
2181
|
+
const full = t.getFullText();
|
|
2182
|
+
if (full.includes(TOOL_TAG)) return true;
|
|
2183
|
+
}
|
|
2184
|
+
}
|
|
2185
|
+
return false;
|
|
2186
|
+
}
|
|
2187
|
+
function getDescription(jsDocs, fallbackName) {
|
|
2188
|
+
for (const doc of jsDocs) {
|
|
2189
|
+
const comment = doc.comment;
|
|
2190
|
+
if (typeof comment === "string") {
|
|
2191
|
+
const first = comment.split(/\n/)[0]?.trim() ?? "";
|
|
2192
|
+
if (first && !first.startsWith("@")) return first;
|
|
2193
|
+
}
|
|
2194
|
+
if (Array.isArray(comment)) {
|
|
2195
|
+
const first = comment[0];
|
|
2196
|
+
if (first && typeof first === "object" && "text" in first) {
|
|
2197
|
+
const t = first.text.trim();
|
|
2198
|
+
if (t && !t.startsWith("@")) return t;
|
|
2199
|
+
}
|
|
2200
|
+
}
|
|
2201
|
+
const full = doc.getFullText();
|
|
2202
|
+
const match = full.match(/\*\s*@tool\s+(.+?)(?=\n|$|\*\/)/s);
|
|
2203
|
+
if (match?.[1]) return match[1].trim();
|
|
2204
|
+
}
|
|
2205
|
+
return "";
|
|
2206
|
+
}
|
|
2207
|
+
function getEffect(host) {
|
|
2208
|
+
const tags = ts2__namespace.getJSDocTags(host);
|
|
2209
|
+
for (const tag of tags) {
|
|
2210
|
+
const name = tag.tagName?.getText() ?? "";
|
|
2211
|
+
if (name === "effect") {
|
|
2212
|
+
const comment = tag.comment;
|
|
2213
|
+
const v = (typeof comment === "string" ? comment : "").trim().toLowerCase();
|
|
2214
|
+
if (EFFECT_VALUES.includes(v)) return v;
|
|
2215
|
+
}
|
|
2216
|
+
}
|
|
2217
|
+
const all = ts2__namespace.getJSDocCommentsAndTags(host);
|
|
2218
|
+
for (const t of all) {
|
|
2219
|
+
if (ts2__namespace.isJSDoc(t)) {
|
|
2220
|
+
const full = t.getFullText();
|
|
2221
|
+
const match = full.match(/\*\s*@effect\s+(\w+)/);
|
|
2222
|
+
if (match && EFFECT_VALUES.includes(match[1])) return match[1];
|
|
2223
|
+
}
|
|
2224
|
+
}
|
|
2225
|
+
return "none";
|
|
2226
|
+
}
|
|
2227
|
+
function humanize(name) {
|
|
2228
|
+
return name.replace(/([A-Z])/g, " $1").replace(/^./, (s) => s.toUpperCase()).trim();
|
|
2229
|
+
}
|
|
2230
|
+
function scan(projectPath, options = {}) {
|
|
2231
|
+
const root = path__namespace.resolve(projectPath);
|
|
2232
|
+
const result = scanForTools({
|
|
2233
|
+
projectPath: root,
|
|
2234
|
+
include: options.include ?? ["**/*.ts"],
|
|
2235
|
+
tsconfigPath: options.tsconfigPath
|
|
2236
|
+
});
|
|
2237
|
+
const specs = result.specs.map((s) => mcpSpecToToolSpec(s, root));
|
|
2238
|
+
return Promise.resolve({
|
|
2239
|
+
specs,
|
|
2240
|
+
errors: result.errors,
|
|
2241
|
+
warnings: result.warnings
|
|
2242
|
+
});
|
|
2243
|
+
}
|
|
2244
|
+
var DEFAULT_EXTENSIONS = [".js", ".mjs"];
|
|
2245
|
+
async function resolveEntryPoint(dirPath, baseName, extensions = DEFAULT_EXTENSIONS) {
|
|
2246
|
+
if (extensions.some((ext) => baseName.endsWith(ext))) {
|
|
2247
|
+
const fullPath = path.join(dirPath, baseName);
|
|
2248
|
+
await promises.stat(fullPath);
|
|
2249
|
+
return fullPath;
|
|
2250
|
+
}
|
|
2251
|
+
for (const ext of extensions) {
|
|
2252
|
+
const fullPath = path.join(dirPath, `${baseName}${ext}`);
|
|
2253
|
+
try {
|
|
2254
|
+
await promises.stat(fullPath);
|
|
2255
|
+
return fullPath;
|
|
2256
|
+
} catch {
|
|
2257
|
+
}
|
|
2258
|
+
}
|
|
2259
|
+
throw new Error(
|
|
2260
|
+
`Could not find entry point in ${dirPath}. Tried: ${extensions.map((e) => baseName + e).join(", ")}`
|
|
2261
|
+
);
|
|
2262
|
+
}
|
|
2263
|
+
|
|
2264
|
+
// src/tools/skill/SkillManifest.ts
|
|
2265
|
+
var SkillManifestError = class extends Error {
|
|
2266
|
+
constructor(path7, field, message) {
|
|
2267
|
+
super(`SKILL.md error in ${path7}: ${message}`);
|
|
2268
|
+
this.path = path7;
|
|
2269
|
+
this.field = field;
|
|
2270
|
+
this.name = "SkillManifestError";
|
|
2271
|
+
}
|
|
2272
|
+
};
|
|
2273
|
+
var NAME_PATTERN = /^[a-z0-9-]+$/;
|
|
2274
|
+
var NAME_MAX_LENGTH = 64;
|
|
2275
|
+
var DESCRIPTION_MAX_LENGTH = 1024;
|
|
2276
|
+
var COMPATIBILITY_MAX_LENGTH = 500;
|
|
2277
|
+
var RESERVED_WORDS = ["anthropic", "claude"];
|
|
2278
|
+
var XML_TAG_PATTERN = /<\/?[a-zA-Z][^>]*>/;
|
|
2279
|
+
function validateFrontmatter(fm, filePath) {
|
|
2280
|
+
if (!fm.name || typeof fm.name !== "string") {
|
|
2281
|
+
throw new SkillManifestError(filePath, "name", "name is required");
|
|
2282
|
+
}
|
|
2283
|
+
if (fm.name.length > NAME_MAX_LENGTH) {
|
|
2284
|
+
throw new SkillManifestError(
|
|
2285
|
+
filePath,
|
|
2286
|
+
"name",
|
|
2287
|
+
`name must be at most ${NAME_MAX_LENGTH} characters (got ${fm.name.length})`
|
|
2288
|
+
);
|
|
2289
|
+
}
|
|
2290
|
+
if (!NAME_PATTERN.test(fm.name)) {
|
|
2291
|
+
throw new SkillManifestError(
|
|
2292
|
+
filePath,
|
|
2293
|
+
"name",
|
|
2294
|
+
"name must contain only lowercase letters, numbers, and hyphens"
|
|
2295
|
+
);
|
|
2296
|
+
}
|
|
2297
|
+
if (fm.name.startsWith("-") || fm.name.endsWith("-")) {
|
|
2298
|
+
throw new SkillManifestError(
|
|
2299
|
+
filePath,
|
|
2300
|
+
"name",
|
|
2301
|
+
"name must not start or end with a hyphen"
|
|
2302
|
+
);
|
|
2303
|
+
}
|
|
2304
|
+
if (fm.name.includes("--")) {
|
|
2305
|
+
throw new SkillManifestError(
|
|
2306
|
+
filePath,
|
|
2307
|
+
"name",
|
|
2308
|
+
"name must not contain consecutive hyphens"
|
|
2309
|
+
);
|
|
2310
|
+
}
|
|
2311
|
+
if (XML_TAG_PATTERN.test(fm.name)) {
|
|
2312
|
+
throw new SkillManifestError(filePath, "name", "name cannot contain XML tags");
|
|
2313
|
+
}
|
|
2314
|
+
for (const reserved of RESERVED_WORDS) {
|
|
2315
|
+
if (fm.name.includes(reserved)) {
|
|
2316
|
+
throw new SkillManifestError(
|
|
2317
|
+
filePath,
|
|
2318
|
+
"name",
|
|
2319
|
+
`name cannot contain reserved word "${reserved}"`
|
|
2320
|
+
);
|
|
2321
|
+
}
|
|
2322
|
+
}
|
|
2323
|
+
if (!fm.description || typeof fm.description !== "string") {
|
|
2324
|
+
throw new SkillManifestError(
|
|
2325
|
+
filePath,
|
|
2326
|
+
"description",
|
|
2327
|
+
"description is required and must be non-empty"
|
|
2328
|
+
);
|
|
2329
|
+
}
|
|
2330
|
+
if (fm.description.length > DESCRIPTION_MAX_LENGTH) {
|
|
2331
|
+
throw new SkillManifestError(
|
|
2332
|
+
filePath,
|
|
2333
|
+
"description",
|
|
2334
|
+
`description must be at most ${DESCRIPTION_MAX_LENGTH} characters (got ${fm.description.length})`
|
|
2335
|
+
);
|
|
2336
|
+
}
|
|
2337
|
+
if (XML_TAG_PATTERN.test(fm.description)) {
|
|
2338
|
+
throw new SkillManifestError(
|
|
2339
|
+
filePath,
|
|
2340
|
+
"description",
|
|
2341
|
+
"description cannot contain XML tags"
|
|
2342
|
+
);
|
|
2343
|
+
}
|
|
2344
|
+
if (fm.compatibility != null && typeof fm.compatibility === "string") {
|
|
2345
|
+
if (fm.compatibility.length > COMPATIBILITY_MAX_LENGTH) {
|
|
2346
|
+
throw new SkillManifestError(
|
|
2347
|
+
filePath,
|
|
2348
|
+
"compatibility",
|
|
2349
|
+
`compatibility must be at most ${COMPATIBILITY_MAX_LENGTH} characters (got ${fm.compatibility.length})`
|
|
2350
|
+
);
|
|
2351
|
+
}
|
|
2352
|
+
}
|
|
2353
|
+
}
|
|
2354
|
+
|
|
2355
|
+
// src/tools/skill/SkillMdParser.ts
|
|
2356
|
+
var CODE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
2357
|
+
".py",
|
|
2358
|
+
".js",
|
|
2359
|
+
".mjs",
|
|
2360
|
+
".ts",
|
|
2361
|
+
".sh",
|
|
2362
|
+
".bash",
|
|
2363
|
+
".rb",
|
|
2364
|
+
".go"
|
|
2365
|
+
]);
|
|
2366
|
+
var INSTRUCTION_EXTENSIONS = /* @__PURE__ */ new Set([".md", ".markdown", ".txt"]);
|
|
2367
|
+
var EXCLUDED_FILES = /* @__PURE__ */ new Set(["SKILL.md", "tool.json"]);
|
|
2368
|
+
function parseSkillMd(content, filePath) {
|
|
2369
|
+
const trimmed = content.trimStart();
|
|
2370
|
+
if (!trimmed.startsWith("---")) {
|
|
2371
|
+
throw new SkillManifestError(
|
|
2372
|
+
filePath,
|
|
2373
|
+
"frontmatter",
|
|
2374
|
+
"SKILL.md must start with YAML frontmatter (---)"
|
|
2375
|
+
);
|
|
2376
|
+
}
|
|
2377
|
+
const endIndex = trimmed.indexOf("\n---", 3);
|
|
2378
|
+
if (endIndex === -1) {
|
|
2379
|
+
throw new SkillManifestError(
|
|
2380
|
+
filePath,
|
|
2381
|
+
"frontmatter",
|
|
2382
|
+
"SKILL.md frontmatter is not closed (missing closing ---)"
|
|
2383
|
+
);
|
|
2384
|
+
}
|
|
2385
|
+
const yamlBlock = trimmed.slice(4, endIndex).trim();
|
|
2386
|
+
const body = trimmed.slice(endIndex + 4).trim();
|
|
2387
|
+
let raw;
|
|
2388
|
+
try {
|
|
2389
|
+
const parsed = yaml__default.default.load(yamlBlock);
|
|
2390
|
+
if (parsed == null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
2391
|
+
throw new SkillManifestError(
|
|
2392
|
+
filePath,
|
|
2393
|
+
"frontmatter",
|
|
2394
|
+
"YAML frontmatter must be an object (key: value)"
|
|
2395
|
+
);
|
|
2396
|
+
}
|
|
2397
|
+
raw = parsed;
|
|
2398
|
+
} catch (err) {
|
|
2399
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2400
|
+
throw new SkillManifestError(
|
|
2401
|
+
filePath,
|
|
2402
|
+
"frontmatter",
|
|
2403
|
+
`Invalid YAML frontmatter: ${message}`
|
|
2404
|
+
);
|
|
2405
|
+
}
|
|
2406
|
+
const name = stringField(raw, "name", filePath);
|
|
2407
|
+
const description = stringField(raw, "description", filePath);
|
|
2408
|
+
if (!name || !description) {
|
|
2409
|
+
throw new SkillManifestError(
|
|
2410
|
+
filePath,
|
|
2411
|
+
"frontmatter",
|
|
2412
|
+
!name ? "name is required" : "description is required"
|
|
2413
|
+
);
|
|
2414
|
+
}
|
|
2415
|
+
const license = stringField(raw, "license");
|
|
2416
|
+
const compatibility = stringField(raw, "compatibility");
|
|
2417
|
+
const allowedTools = stringField(raw, "allowed-tools");
|
|
2418
|
+
const metadata = normalizeMetadata(raw.metadata);
|
|
2419
|
+
const frontmatter = {
|
|
2420
|
+
name,
|
|
2421
|
+
description,
|
|
2422
|
+
...license && { license },
|
|
2423
|
+
...compatibility && { compatibility },
|
|
2424
|
+
...allowedTools && { allowedTools },
|
|
2425
|
+
...metadata && Object.keys(metadata).length > 0 && { metadata }
|
|
2426
|
+
};
|
|
2427
|
+
validateFrontmatter(frontmatter, filePath);
|
|
2428
|
+
return { frontmatter, instructions: body };
|
|
2429
|
+
}
|
|
2430
|
+
function stringField(raw, key, filePath) {
|
|
2431
|
+
const v = raw[key];
|
|
2432
|
+
if (v == null) return "";
|
|
2433
|
+
if (typeof v === "string") return v;
|
|
2434
|
+
if (typeof v === "number" || typeof v === "boolean") return String(v);
|
|
2435
|
+
if (Array.isArray(v)) {
|
|
2436
|
+
return v.map((x) => typeof x === "string" ? x : String(x)).join("\n");
|
|
2437
|
+
}
|
|
2438
|
+
if (filePath) {
|
|
2439
|
+
throw new SkillManifestError(
|
|
2440
|
+
filePath,
|
|
2441
|
+
"frontmatter",
|
|
2442
|
+
`Frontmatter field "${key}" must be a string, number, boolean, or array`
|
|
2443
|
+
);
|
|
2444
|
+
}
|
|
2445
|
+
return String(v);
|
|
2446
|
+
}
|
|
2447
|
+
function normalizeMetadata(val) {
|
|
2448
|
+
if (val == null) return void 0;
|
|
2449
|
+
if (typeof val === "object" && !Array.isArray(val)) {
|
|
2450
|
+
const out = {};
|
|
2451
|
+
for (const [k, v] of Object.entries(val)) {
|
|
2452
|
+
if (typeof k === "string" && v !== void 0 && v !== null) {
|
|
2453
|
+
out[k] = typeof v === "object" ? JSON.stringify(v) : String(v);
|
|
2454
|
+
}
|
|
2455
|
+
}
|
|
2456
|
+
return Object.keys(out).length ? out : void 0;
|
|
2457
|
+
}
|
|
2458
|
+
if (typeof val === "string" || typeof val === "number" || typeof val === "boolean") {
|
|
2459
|
+
return { value: String(val) };
|
|
2460
|
+
}
|
|
2461
|
+
return void 0;
|
|
2462
|
+
}
|
|
2463
|
+
async function scanSkillResources(dirPath) {
|
|
2464
|
+
const resources = [];
|
|
2465
|
+
await scanDir(dirPath, dirPath, resources);
|
|
2466
|
+
return resources;
|
|
2467
|
+
}
|
|
2468
|
+
async function scanDir(basePath, currentPath, resources) {
|
|
2469
|
+
let entries;
|
|
2470
|
+
try {
|
|
2471
|
+
entries = await promises.readdir(currentPath, { withFileTypes: true });
|
|
2472
|
+
} catch {
|
|
2473
|
+
return;
|
|
2474
|
+
}
|
|
2475
|
+
for (const entry of entries) {
|
|
2476
|
+
const fullPath = path.join(currentPath, entry.name);
|
|
2477
|
+
if (entry.isDirectory()) {
|
|
2478
|
+
if (entry.name.startsWith(".") || entry.name === "node_modules") {
|
|
2479
|
+
continue;
|
|
2480
|
+
}
|
|
2481
|
+
await scanDir(basePath, fullPath, resources);
|
|
2482
|
+
} else if (entry.isFile()) {
|
|
2483
|
+
if (EXCLUDED_FILES.has(entry.name)) {
|
|
2484
|
+
continue;
|
|
2485
|
+
}
|
|
2486
|
+
const ext = path.extname(entry.name).toLowerCase();
|
|
2487
|
+
const relPath = path.relative(basePath, fullPath);
|
|
2488
|
+
resources.push({
|
|
2489
|
+
relativePath: relPath,
|
|
2490
|
+
absolutePath: fullPath,
|
|
2491
|
+
extension: ext,
|
|
2492
|
+
type: inferResourceType(ext)
|
|
2493
|
+
});
|
|
2494
|
+
}
|
|
2495
|
+
}
|
|
2496
|
+
}
|
|
2497
|
+
function inferResourceType(ext) {
|
|
2498
|
+
if (CODE_EXTENSIONS.has(ext)) return "code";
|
|
2499
|
+
if (INSTRUCTION_EXTENSIONS.has(ext)) return "instructions";
|
|
2500
|
+
return "data";
|
|
2501
|
+
}
|
|
2502
|
+
async function loadSkillDefinition(dirPath) {
|
|
2503
|
+
const skillMdPath = path.join(dirPath, "SKILL.md");
|
|
2504
|
+
let content;
|
|
2505
|
+
try {
|
|
2506
|
+
content = await promises.readFile(skillMdPath, "utf-8");
|
|
2507
|
+
} catch (err) {
|
|
2508
|
+
throw new SkillManifestError(
|
|
2509
|
+
skillMdPath,
|
|
2510
|
+
"file",
|
|
2511
|
+
`Cannot read SKILL.md: ${err.message}`
|
|
2512
|
+
);
|
|
2513
|
+
}
|
|
2514
|
+
const { frontmatter, instructions } = parseSkillMd(content, skillMdPath);
|
|
2515
|
+
const resources = await scanSkillResources(dirPath);
|
|
2516
|
+
return {
|
|
2517
|
+
frontmatter,
|
|
2518
|
+
instructions,
|
|
2519
|
+
resources,
|
|
2520
|
+
dirPath,
|
|
2521
|
+
skillMdPath
|
|
2522
|
+
};
|
|
2523
|
+
}
|
|
2524
|
+
|
|
2525
|
+
// src/tools/skill/scanSkill.ts
|
|
2526
|
+
var defaultInputSchema = { type: "object", properties: {}, additionalProperties: true };
|
|
2527
|
+
async function scanForSkill(projectPath) {
|
|
2528
|
+
const projectRoot = path__namespace.resolve(projectPath);
|
|
2529
|
+
const dirs = await findDirsContainingFile(projectRoot, "SKILL.md");
|
|
2530
|
+
const skills = [];
|
|
2531
|
+
const errors = [];
|
|
2532
|
+
for (const dirPath of dirs) {
|
|
2533
|
+
const relativePath = path__namespace.relative(projectRoot, dirPath) || path__namespace.basename(dirPath);
|
|
2534
|
+
try {
|
|
2535
|
+
const skillDef = await loadSkillDefinition(dirPath);
|
|
2536
|
+
const name = pathToToolName(relativePath, skillDef.frontmatter.name);
|
|
2537
|
+
skills.push({
|
|
2538
|
+
kind: SKILL_KIND,
|
|
2539
|
+
name,
|
|
2540
|
+
description: skillDef.frontmatter.description,
|
|
2541
|
+
inputSchema: defaultInputSchema,
|
|
2542
|
+
_meta: { hitl: { sideEffect: "none" } },
|
|
2543
|
+
sourcePath: relativePath.replace(/\\/g, "/")
|
|
2544
|
+
});
|
|
2545
|
+
} catch (err) {
|
|
2546
|
+
errors.push({ dir: relativePath, message: err instanceof Error ? err.message : String(err) });
|
|
2547
|
+
}
|
|
2548
|
+
}
|
|
2549
|
+
return { skills, errors };
|
|
2550
|
+
}
|
|
2551
|
+
async function scan2(projectPath, _options = {}) {
|
|
2552
|
+
const root = path__namespace.resolve(projectPath);
|
|
2553
|
+
const result = await scanForSkill(root);
|
|
2554
|
+
const specs = result.skills.map((s) => mcpSpecToToolSpec(s, root));
|
|
2555
|
+
return {
|
|
2556
|
+
specs,
|
|
2557
|
+
errors: result.errors.map((e) => ({ file: e.dir, message: e.message }))
|
|
2558
|
+
};
|
|
2559
|
+
}
|
|
2560
|
+
async function readWorkflowMeta(dirPath, workflowFileName = "workflow.json") {
|
|
2561
|
+
const workflowPath = path.join(dirPath, workflowFileName);
|
|
2562
|
+
let raw;
|
|
2563
|
+
try {
|
|
2564
|
+
raw = await promises.readFile(workflowPath, "utf-8");
|
|
2565
|
+
} catch (err) {
|
|
2566
|
+
throw new DiscoveryError(
|
|
2567
|
+
dirPath,
|
|
2568
|
+
"load",
|
|
2569
|
+
`Failed to read workflow: ${workflowPath}`,
|
|
2570
|
+
err
|
|
2571
|
+
);
|
|
2572
|
+
}
|
|
2573
|
+
let workflowDef;
|
|
2574
|
+
try {
|
|
2575
|
+
workflowDef = JSON.parse(raw);
|
|
2576
|
+
} catch (err) {
|
|
2577
|
+
throw new DiscoveryError(
|
|
2578
|
+
dirPath,
|
|
2579
|
+
"load",
|
|
2580
|
+
`Invalid JSON in ${workflowPath}`,
|
|
2581
|
+
err
|
|
2582
|
+
);
|
|
2583
|
+
}
|
|
2584
|
+
if (!workflowDef.nodes || !Array.isArray(workflowDef.nodes)) {
|
|
2585
|
+
throw new DiscoveryError(
|
|
2586
|
+
dirPath,
|
|
2587
|
+
"validate",
|
|
2588
|
+
`workflow.json must have a "nodes" array`
|
|
2589
|
+
);
|
|
2590
|
+
}
|
|
2591
|
+
const meta = workflowDef.meta;
|
|
2592
|
+
const name = workflowDef.name || meta?.name || path.basename(dirPath);
|
|
2593
|
+
const description = workflowDef.description || meta?.description || `n8n workflow: ${name}`;
|
|
2594
|
+
let webhookUrl;
|
|
2595
|
+
const nodes = workflowDef.nodes;
|
|
2596
|
+
if (Array.isArray(nodes)) {
|
|
2597
|
+
const webhookNode = nodes.find(
|
|
2598
|
+
(n) => n.type === "n8n-nodes-base.webhook" || n.type?.includes("webhook")
|
|
2599
|
+
);
|
|
2600
|
+
if (webhookNode?.parameters && typeof webhookNode.parameters === "object") {
|
|
2601
|
+
const params = webhookNode.parameters;
|
|
2602
|
+
const pathVal = params.path ?? params.webhookPath;
|
|
2603
|
+
if (typeof pathVal === "string" && pathVal.startsWith("http")) {
|
|
2604
|
+
webhookUrl = pathVal;
|
|
2605
|
+
}
|
|
2606
|
+
}
|
|
2607
|
+
}
|
|
2608
|
+
return { name, description, webhookUrl, workflowDef };
|
|
2609
|
+
}
|
|
2610
|
+
async function loadN8nTool(dirPath, manifest) {
|
|
2611
|
+
const { workflowDef } = await readWorkflowMeta(
|
|
2612
|
+
dirPath,
|
|
2613
|
+
manifest.entryPoint ?? "workflow.json"
|
|
2614
|
+
);
|
|
2615
|
+
return { manifest, dirPath, workflowDef };
|
|
2616
|
+
}
|
|
2617
|
+
|
|
2618
|
+
// src/tools/n8n/scanN8n.ts
|
|
2619
|
+
var defaultInputSchema2 = { type: "object", properties: {}, additionalProperties: true };
|
|
2620
|
+
async function scanForN8n(projectPath) {
|
|
2621
|
+
const projectRoot = path__namespace.resolve(projectPath);
|
|
2622
|
+
const dirs = await findDirsContainingFile(projectRoot, "workflow.json");
|
|
2623
|
+
const n8n = [];
|
|
2624
|
+
const errors = [];
|
|
2625
|
+
for (const dirPath of dirs) {
|
|
2626
|
+
const relativePath = path__namespace.relative(projectRoot, dirPath) || path__namespace.basename(dirPath);
|
|
2627
|
+
try {
|
|
2628
|
+
const { name: wfName, description: wfDesc, webhookUrl } = await readWorkflowMeta(dirPath);
|
|
2629
|
+
const toolName = pathToToolName(relativePath, wfName);
|
|
2630
|
+
n8n.push({
|
|
2631
|
+
kind: N8N_KIND,
|
|
2632
|
+
name: toolName,
|
|
2633
|
+
description: wfDesc,
|
|
2634
|
+
inputSchema: defaultInputSchema2,
|
|
2635
|
+
_meta: { hitl: { sideEffect: "external_write" } },
|
|
2636
|
+
sourcePath: relativePath.replace(/\\/g, "/"),
|
|
2637
|
+
webhookUrl
|
|
2638
|
+
});
|
|
2639
|
+
} catch (err) {
|
|
2640
|
+
errors.push({ dir: relativePath, message: err instanceof Error ? err.message : String(err) });
|
|
2641
|
+
}
|
|
2642
|
+
}
|
|
2643
|
+
return { n8n, errors };
|
|
2644
|
+
}
|
|
2645
|
+
async function scan3(projectPath, _options = {}) {
|
|
2646
|
+
const root = path__namespace.resolve(projectPath);
|
|
2647
|
+
const result = await scanForN8n(root);
|
|
2648
|
+
const specs = result.n8n.map((s) => mcpSpecToToolSpec(s, root));
|
|
2649
|
+
return {
|
|
2650
|
+
specs,
|
|
2651
|
+
errors: result.errors.map((e) => ({ file: e.dir, message: e.message }))
|
|
2652
|
+
};
|
|
2653
|
+
}
|
|
2654
|
+
|
|
2655
|
+
// src/tools/mcp/types.ts
|
|
2656
|
+
var MCP_KIND = "mcp";
|
|
2657
|
+
|
|
2658
|
+
// src/tools/mcp/scanner.ts
|
|
2659
|
+
async function scan4(projectPath, options = {}) {
|
|
2660
|
+
const root = path__namespace.resolve(projectPath);
|
|
2661
|
+
const namespace = options.namespace ?? "dir";
|
|
2662
|
+
const errors = [];
|
|
2663
|
+
const scanner = new DirectoryScanner({
|
|
2664
|
+
roots: [root],
|
|
2665
|
+
namespace,
|
|
2666
|
+
extensions: options.extensions,
|
|
2667
|
+
onError: (dir, err) => {
|
|
2668
|
+
errors.push({ file: dir, message: err.message });
|
|
2669
|
+
options.onError?.(dir, err);
|
|
2670
|
+
}
|
|
2671
|
+
});
|
|
2672
|
+
let specs;
|
|
2673
|
+
try {
|
|
2674
|
+
specs = await scanner.scan();
|
|
2675
|
+
} catch (err) {
|
|
2676
|
+
errors.push({
|
|
2677
|
+
file: root,
|
|
2678
|
+
message: err instanceof Error ? err.message : String(err)
|
|
2679
|
+
});
|
|
2680
|
+
return { specs: [], errors };
|
|
2681
|
+
}
|
|
2682
|
+
const filtered = specs.filter((s) => s.kind === MCP_KIND);
|
|
2683
|
+
return { specs: filtered, errors };
|
|
2684
|
+
}
|
|
2685
|
+
|
|
2686
|
+
// src/tools/langchain/types.ts
|
|
2687
|
+
var LANGCHAIN_KIND = "langchain";
|
|
2688
|
+
var LANGCHAIN_DIR_NAME = "langchain";
|
|
2689
|
+
|
|
2690
|
+
// src/tools/langchain/scanner.ts
|
|
2691
|
+
async function scan5(projectPath, options = {}) {
|
|
2692
|
+
const root = path__namespace.resolve(projectPath);
|
|
2693
|
+
const namespace = options.namespace ?? "dir";
|
|
2694
|
+
const errors = [];
|
|
2695
|
+
const scanner = new DirectoryScanner({
|
|
2696
|
+
roots: [root],
|
|
2697
|
+
namespace,
|
|
2698
|
+
extensions: options.extensions,
|
|
2699
|
+
onError: (dir, err) => {
|
|
2700
|
+
errors.push({ file: dir, message: err.message });
|
|
2701
|
+
options.onError?.(dir, err);
|
|
2702
|
+
}
|
|
2703
|
+
});
|
|
2704
|
+
let specs;
|
|
2705
|
+
try {
|
|
2706
|
+
specs = await scanner.scan();
|
|
2707
|
+
} catch (err) {
|
|
2708
|
+
errors.push({
|
|
2709
|
+
file: root,
|
|
2710
|
+
message: err instanceof Error ? err.message : String(err)
|
|
2711
|
+
});
|
|
2712
|
+
return { specs: [], errors };
|
|
2713
|
+
}
|
|
2714
|
+
const filtered = specs.filter((s) => s.kind === LANGCHAIN_KIND);
|
|
2715
|
+
return { specs: filtered, errors };
|
|
2716
|
+
}
|
|
2717
|
+
async function loadLangChainTool(dirPath, manifest, extensions) {
|
|
2718
|
+
let entryFile;
|
|
2719
|
+
try {
|
|
2720
|
+
entryFile = await resolveEntryPoint(
|
|
2721
|
+
dirPath,
|
|
2722
|
+
manifest.entryPoint ?? "index",
|
|
2723
|
+
extensions
|
|
2724
|
+
);
|
|
2725
|
+
} catch (err) {
|
|
2726
|
+
throw new DiscoveryError(
|
|
2727
|
+
dirPath,
|
|
2728
|
+
"load",
|
|
2729
|
+
`Cannot find LangChain entry point`,
|
|
2730
|
+
err
|
|
2731
|
+
);
|
|
2732
|
+
}
|
|
2733
|
+
let mod;
|
|
2734
|
+
try {
|
|
2735
|
+
mod = await import(url.pathToFileURL(entryFile).href);
|
|
2736
|
+
} catch (err) {
|
|
2737
|
+
throw new DiscoveryError(
|
|
2738
|
+
dirPath,
|
|
2739
|
+
"load",
|
|
2740
|
+
`Failed to import ${entryFile}`,
|
|
2741
|
+
err
|
|
2742
|
+
);
|
|
2743
|
+
}
|
|
2744
|
+
const tool = mod.default ?? mod.tool ?? mod;
|
|
2745
|
+
if (!tool || typeof tool.invoke !== "function") {
|
|
2746
|
+
throw new DiscoveryError(
|
|
2747
|
+
dirPath,
|
|
2748
|
+
"validate",
|
|
2749
|
+
`Entry point must export an object with invoke() method (LangChainToolLike)`
|
|
2750
|
+
);
|
|
2751
|
+
}
|
|
2752
|
+
return { manifest, dirPath, impl: tool };
|
|
2753
|
+
}
|
|
2754
|
+
var DEFAULT_EXTENSIONS2 = [".js", ".mjs"];
|
|
2755
|
+
async function listSkillProgramFiles(dirPath, extensions = DEFAULT_EXTENSIONS2) {
|
|
2756
|
+
let entries;
|
|
2757
|
+
try {
|
|
2758
|
+
const dirEntries = await promises.readdir(dirPath, { withFileTypes: true });
|
|
2759
|
+
entries = dirEntries.map((entry) => ({
|
|
2760
|
+
name: entry.name,
|
|
2761
|
+
isFile: entry.isFile()
|
|
2762
|
+
}));
|
|
2763
|
+
} catch {
|
|
2764
|
+
return [];
|
|
2765
|
+
}
|
|
2766
|
+
return entries.filter((e) => e.isFile).map((e) => e.name).filter((name) => {
|
|
2767
|
+
if (name.startsWith(".") || name.startsWith("_")) return false;
|
|
2768
|
+
if (name.includes(".test.") || name.includes(".spec.")) return false;
|
|
2769
|
+
return extensions.some((ext) => name.endsWith(ext));
|
|
2770
|
+
}).sort((a, b) => {
|
|
2771
|
+
if (a === "handler.js" || a === "index.js") return -1;
|
|
2772
|
+
if (b === "handler.js" || b === "index.js") return 1;
|
|
2773
|
+
return a.localeCompare(b);
|
|
2774
|
+
});
|
|
2775
|
+
}
|
|
2776
|
+
function isLangChainLikeTool(val) {
|
|
2777
|
+
return val != null && typeof val === "object" && "invoke" in val && typeof val.invoke === "function";
|
|
2778
|
+
}
|
|
2779
|
+
function isConstructable(val) {
|
|
2780
|
+
return typeof val === "function" && typeof val.prototype === "object";
|
|
2781
|
+
}
|
|
2782
|
+
async function loadOneSkillProgram(dirPath, manifest, entryFile, skillDef, programKey, extensions) {
|
|
2783
|
+
let impl;
|
|
2784
|
+
try {
|
|
2785
|
+
const fullPath = await resolveEntryPoint(dirPath, entryFile, extensions ?? [".js", ".mjs"]);
|
|
2786
|
+
const mod = await import(url.pathToFileURL(fullPath).href);
|
|
2787
|
+
const fn = mod.default ?? mod.handler ?? mod.Tool;
|
|
2788
|
+
if (isLangChainLikeTool(fn)) {
|
|
2789
|
+
impl = fn;
|
|
2790
|
+
} else if (isConstructable(fn)) {
|
|
2791
|
+
const instance = new fn();
|
|
2792
|
+
if (isLangChainLikeTool(instance)) impl = instance;
|
|
2793
|
+
} else if (typeof fn === "function") {
|
|
2794
|
+
impl = fn;
|
|
2795
|
+
}
|
|
2796
|
+
} catch {
|
|
2797
|
+
}
|
|
2798
|
+
return {
|
|
2799
|
+
manifest,
|
|
2800
|
+
dirPath,
|
|
2801
|
+
impl,
|
|
2802
|
+
skillDefinition: skillDef,
|
|
2803
|
+
programKey
|
|
2804
|
+
};
|
|
2805
|
+
}
|
|
2806
|
+
async function loadSkillTools(dirPath, manifest, extensions) {
|
|
2807
|
+
let skillDef;
|
|
2808
|
+
try {
|
|
2809
|
+
skillDef = await loadSkillDefinition(dirPath);
|
|
2810
|
+
} catch (err) {
|
|
2811
|
+
throw new DiscoveryError(
|
|
2812
|
+
dirPath,
|
|
2813
|
+
"load",
|
|
2814
|
+
`Failed to parse SKILL.md: ${err.message}`,
|
|
2815
|
+
err
|
|
2816
|
+
);
|
|
2817
|
+
}
|
|
2818
|
+
const programs = manifest.programs;
|
|
2819
|
+
if (programs && typeof programs === "object" && Object.keys(programs).length > 0) {
|
|
2820
|
+
const result = [];
|
|
2821
|
+
for (const [programKey, entryFile2] of Object.entries(programs)) {
|
|
2822
|
+
const loaded2 = await loadOneSkillProgram(
|
|
2823
|
+
dirPath,
|
|
2824
|
+
manifest,
|
|
2825
|
+
entryFile2,
|
|
2826
|
+
skillDef,
|
|
2827
|
+
programKey,
|
|
2828
|
+
extensions
|
|
2829
|
+
);
|
|
2830
|
+
result.push(loaded2);
|
|
2831
|
+
}
|
|
2832
|
+
return result;
|
|
2833
|
+
}
|
|
2834
|
+
const exts = extensions ?? DEFAULT_EXTENSIONS2;
|
|
2835
|
+
const files = await listSkillProgramFiles(dirPath, exts);
|
|
2836
|
+
if (files.length >= 2) {
|
|
2837
|
+
const result = [];
|
|
2838
|
+
for (let i = 0; i < files.length; i++) {
|
|
2839
|
+
const file = files[i];
|
|
2840
|
+
const programKey = i === 0 ? "default" : file.replace(/\.[^.]+$/, "");
|
|
2841
|
+
const loaded2 = await loadOneSkillProgram(
|
|
2842
|
+
dirPath,
|
|
2843
|
+
manifest,
|
|
2844
|
+
file,
|
|
2845
|
+
skillDef,
|
|
2846
|
+
programKey,
|
|
2847
|
+
extensions
|
|
2848
|
+
);
|
|
2849
|
+
result.push(loaded2);
|
|
2850
|
+
}
|
|
2851
|
+
return result;
|
|
2852
|
+
}
|
|
2853
|
+
const entryFile = manifest.entryPoint ?? files[0] ?? "handler";
|
|
2854
|
+
const loaded = await loadOneSkillProgram(
|
|
2855
|
+
dirPath,
|
|
2856
|
+
manifest,
|
|
2857
|
+
entryFile,
|
|
2858
|
+
skillDef,
|
|
2859
|
+
void 0,
|
|
2860
|
+
extensions
|
|
2861
|
+
);
|
|
2862
|
+
return [loaded];
|
|
2863
|
+
}
|
|
2864
|
+
async function listLangchainEntryFiles(dirPath, extensions) {
|
|
2865
|
+
let entries;
|
|
2866
|
+
try {
|
|
2867
|
+
const dirEntries = await promises.readdir(dirPath, { withFileTypes: true });
|
|
2868
|
+
entries = dirEntries.map((entry) => ({
|
|
2869
|
+
name: entry.name,
|
|
2870
|
+
isFile: entry.isFile()
|
|
2871
|
+
}));
|
|
2872
|
+
} catch {
|
|
2873
|
+
return [];
|
|
2874
|
+
}
|
|
2875
|
+
return entries.filter((e) => e.isFile).map((e) => e.name).filter((name) => {
|
|
2876
|
+
if (name.startsWith(".") || name.startsWith("_")) return false;
|
|
2877
|
+
if (name.endsWith(".d.ts")) return false;
|
|
2878
|
+
if (name.includes(".test.") || name.includes(".spec.")) return false;
|
|
2879
|
+
return extensions.some((ext) => name.endsWith(ext));
|
|
2880
|
+
});
|
|
2881
|
+
}
|
|
2882
|
+
async function loadLangChainToolsFromDir(dirPath, dirName, manifest, strict, namespace, extensions, langchainDirName, toSpec, onError) {
|
|
2883
|
+
const entryFiles = await listLangchainEntryFiles(dirPath, extensions);
|
|
2884
|
+
if (entryFiles.length === 0) {
|
|
2885
|
+
if (strict) {
|
|
2886
|
+
throw new DiscoveryError(dirPath, "load", "No LangChain entry files found");
|
|
2887
|
+
}
|
|
2888
|
+
return [];
|
|
2889
|
+
}
|
|
2890
|
+
const specs = [];
|
|
2891
|
+
const useDirNameForSingle = dirName !== langchainDirName;
|
|
2892
|
+
for (const entryFile of entryFiles) {
|
|
2893
|
+
const fileManifest = { ...manifest, entryPoint: entryFile };
|
|
2894
|
+
try {
|
|
2895
|
+
const loaded = await loadLangChainTool(dirPath, fileManifest, extensions);
|
|
2896
|
+
const fileBase = path.basename(entryFile).replace(/\.[^.]+$/, "");
|
|
2897
|
+
const nameHint = entryFiles.length === 1 && useDirNameForSingle ? dirName : fileBase;
|
|
2898
|
+
specs.push(toSpec(loaded, nameHint, dirPath, namespace));
|
|
2899
|
+
} catch (error) {
|
|
2900
|
+
const err = error;
|
|
2901
|
+
if (err instanceof DiscoveryError && err.phase === "validate") {
|
|
2902
|
+
if (strict) throw err;
|
|
2903
|
+
continue;
|
|
2904
|
+
}
|
|
2905
|
+
onError?.(path.join(dirPath, entryFile), err);
|
|
2906
|
+
if (strict) throw err;
|
|
2907
|
+
}
|
|
2908
|
+
}
|
|
2909
|
+
return specs;
|
|
2910
|
+
}
|
|
2911
|
+
function isCursorFormat(obj) {
|
|
2912
|
+
return typeof obj === "object" && obj !== null && "mcpServers" in obj && typeof obj.mcpServers === "object" && obj.mcpServers !== null;
|
|
2913
|
+
}
|
|
2914
|
+
function extractMCPConfig(parsed, toolName) {
|
|
2915
|
+
if (isCursorFormat(parsed)) {
|
|
2916
|
+
const servers = parsed.mcpServers;
|
|
2917
|
+
const keys = Object.keys(servers);
|
|
2918
|
+
if (keys.length === 0) {
|
|
2919
|
+
return {};
|
|
2920
|
+
}
|
|
2921
|
+
const name = toolName && keys.includes(toolName) ? toolName : keys[0];
|
|
2922
|
+
return servers[name];
|
|
2923
|
+
}
|
|
2924
|
+
return parsed;
|
|
2925
|
+
}
|
|
2926
|
+
async function loadMCPTool(dirPath, manifest) {
|
|
2927
|
+
const mcpPath = path.join(dirPath, manifest.entryPoint ?? "mcp.json");
|
|
2928
|
+
let raw;
|
|
2929
|
+
try {
|
|
2930
|
+
raw = await promises.readFile(mcpPath, "utf-8");
|
|
2931
|
+
} catch (err) {
|
|
2932
|
+
throw new DiscoveryError(
|
|
2933
|
+
dirPath,
|
|
2934
|
+
"load",
|
|
2935
|
+
`Failed to read MCP config: ${mcpPath}`,
|
|
2936
|
+
err
|
|
2937
|
+
);
|
|
2938
|
+
}
|
|
2939
|
+
let parsed;
|
|
2940
|
+
try {
|
|
2941
|
+
parsed = JSON.parse(raw);
|
|
2942
|
+
} catch (err) {
|
|
2943
|
+
throw new DiscoveryError(
|
|
2944
|
+
dirPath,
|
|
2945
|
+
"load",
|
|
2946
|
+
`Invalid JSON in ${mcpPath}`,
|
|
2947
|
+
err
|
|
2948
|
+
);
|
|
2949
|
+
}
|
|
2950
|
+
const baseName = manifest.name?.split("/").pop();
|
|
2951
|
+
const config = extractMCPConfig(parsed, baseName);
|
|
2952
|
+
if (!config.command && !config.url) {
|
|
2953
|
+
throw new DiscoveryError(
|
|
2954
|
+
dirPath,
|
|
2955
|
+
"validate",
|
|
2956
|
+
`mcp.json must have either "command" or "url" field`
|
|
2957
|
+
);
|
|
2958
|
+
}
|
|
2959
|
+
return { manifest, dirPath, mcpConfig: config };
|
|
2960
|
+
}
|
|
2961
|
+
|
|
2962
|
+
// src/tools/mcp/directoryApply.ts
|
|
2963
|
+
function applyLoadedToSpec(spec, loaded, _manifest, _defaultDirName, _namespace) {
|
|
2964
|
+
if (loaded.mcpConfig?.url) spec.endpoint = loaded.mcpConfig.url;
|
|
2965
|
+
spec.impl = loaded.mcpConfig;
|
|
2966
|
+
}
|
|
2967
|
+
var directoryMarker = {
|
|
2968
|
+
kind: "mcp",
|
|
2969
|
+
markerFile: "mcp.json",
|
|
2970
|
+
defaultEntryPoint: "mcp.json"
|
|
2971
|
+
};
|
|
2972
|
+
|
|
2973
|
+
// src/tools/langchain/directoryApply.ts
|
|
2974
|
+
function applyLoadedToSpec2(spec, loaded, manifest, _defaultDirName, namespace) {
|
|
2975
|
+
spec.impl = loaded.impl;
|
|
2976
|
+
if (!manifest.name && loaded.impl) {
|
|
2977
|
+
const toolName = loaded.impl.name;
|
|
2978
|
+
if (toolName) spec.name = `${namespace}/${toolName}`;
|
|
2979
|
+
}
|
|
2980
|
+
if (!manifest.description && loaded.impl) {
|
|
2981
|
+
const d = loaded.impl.description;
|
|
2982
|
+
if (d) spec.description = d;
|
|
2983
|
+
}
|
|
2984
|
+
if (!manifest.inputSchema && loaded.impl) {
|
|
2985
|
+
const schema = loaded.impl.schema;
|
|
2986
|
+
if (schema) spec.inputSchema = schema;
|
|
2987
|
+
}
|
|
2988
|
+
}
|
|
2989
|
+
|
|
2990
|
+
// src/tools/skill/directoryApply.ts
|
|
2991
|
+
function applyLoadedToSpec3(spec, loaded, manifest, defaultDirName, namespace) {
|
|
2992
|
+
const skillDef = loaded.skillDefinition;
|
|
2993
|
+
if (skillDef) {
|
|
2994
|
+
spec.name = manifest.name ?? skillDef.frontmatter.name;
|
|
2995
|
+
spec.description = skillDef.frontmatter.description;
|
|
2996
|
+
if (loaded.programKey === "default") {
|
|
2997
|
+
spec.name = `${namespace}/${defaultDirName}`;
|
|
2998
|
+
} else if (loaded.programKey) {
|
|
2999
|
+
spec.name = `${namespace}/${defaultDirName}/${loaded.programKey}`;
|
|
3000
|
+
}
|
|
3001
|
+
const impl = loaded.impl;
|
|
3002
|
+
if (impl && typeof impl === "object" && typeof impl.invoke === "function") {
|
|
3003
|
+
if (impl.description != null && impl.description !== "")
|
|
3004
|
+
spec.description = impl.description;
|
|
3005
|
+
if (impl.schema != null && typeof impl.schema === "object")
|
|
3006
|
+
spec.inputSchema = impl.schema;
|
|
3007
|
+
}
|
|
3008
|
+
spec.impl = { ...skillDef, handler: loaded.impl };
|
|
3009
|
+
} else {
|
|
3010
|
+
spec.impl = loaded.impl;
|
|
3011
|
+
}
|
|
3012
|
+
}
|
|
3013
|
+
var directoryMarker2 = {
|
|
3014
|
+
kind: "skill",
|
|
3015
|
+
markerFile: "SKILL.md",
|
|
3016
|
+
defaultEntryPoint: "handler"
|
|
3017
|
+
};
|
|
3018
|
+
|
|
3019
|
+
// src/tools/n8n/directoryApply.ts
|
|
3020
|
+
function applyLoadedToSpec4(spec, loaded, manifest, _defaultDirName, _namespace) {
|
|
3021
|
+
const workflow = loaded.workflowDef;
|
|
3022
|
+
if (workflow?.id) spec.resourceId = String(workflow.id);
|
|
3023
|
+
if (!manifest.description && workflow) {
|
|
3024
|
+
const d = workflow.description ?? workflow.meta?.description ?? (typeof workflow.name === "string" ? workflow.name : void 0);
|
|
3025
|
+
if (d) spec.description = d;
|
|
3026
|
+
}
|
|
3027
|
+
spec.impl = loaded.workflowDef;
|
|
3028
|
+
}
|
|
3029
|
+
var directoryMarker3 = {
|
|
3030
|
+
kind: "n8n",
|
|
3031
|
+
markerFile: "workflow.json",
|
|
3032
|
+
defaultEntryPoint: "workflow.json"
|
|
3033
|
+
};
|
|
3034
|
+
|
|
3035
|
+
// src/tools/discoveryFactory.ts
|
|
3036
|
+
var DiscoveryError = class extends Error {
|
|
3037
|
+
toolDir;
|
|
3038
|
+
phase;
|
|
3039
|
+
cause;
|
|
3040
|
+
constructor(toolDir, phase, message, cause) {
|
|
3041
|
+
super(`[${phase}] ${toolDir}: ${message}`);
|
|
3042
|
+
this.name = "DiscoveryError";
|
|
3043
|
+
this.toolDir = toolDir;
|
|
3044
|
+
this.phase = phase;
|
|
3045
|
+
this.cause = cause;
|
|
3046
|
+
}
|
|
3047
|
+
};
|
|
3048
|
+
var DIRECTORY_KINDS = ["mcp", "langchain", "skill", "n8n"];
|
|
3049
|
+
var DIRECTORY_DISCOVERABLE_KINDS = DIRECTORY_KINDS;
|
|
3050
|
+
var DIRECTORY_KIND_MARKERS = [
|
|
3051
|
+
directoryMarker2,
|
|
3052
|
+
directoryMarker3,
|
|
3053
|
+
directoryMarker
|
|
3054
|
+
];
|
|
3055
|
+
var DIRECTORY_LOADERS = {
|
|
3056
|
+
mcp: async (dirPath, manifest) => [await loadMCPTool(dirPath, manifest)],
|
|
3057
|
+
langchain: async (dirPath, manifest, ext) => [await loadLangChainTool(dirPath, manifest, ext)],
|
|
3058
|
+
skill: (dirPath, manifest, ext) => loadSkillTools(dirPath, manifest, ext),
|
|
3059
|
+
n8n: async (dirPath, manifest) => [await loadN8nTool(dirPath, manifest)]
|
|
3060
|
+
};
|
|
3061
|
+
function getDirectoryLoader(kind) {
|
|
3062
|
+
const loader = DIRECTORY_LOADERS[kind];
|
|
3063
|
+
if (!loader) {
|
|
3064
|
+
throw new DiscoveryError("", "manifest", `Unknown directory tool kind: "${kind}"`);
|
|
3065
|
+
}
|
|
3066
|
+
return loader;
|
|
3067
|
+
}
|
|
3068
|
+
function applyDirectoryLoadedToSpec(spec, loaded, manifest, defaultDirName, namespace) {
|
|
3069
|
+
switch (manifest.kind) {
|
|
3070
|
+
case "mcp":
|
|
3071
|
+
return applyLoadedToSpec(spec, loaded);
|
|
3072
|
+
case "langchain":
|
|
3073
|
+
return applyLoadedToSpec2(spec, loaded, manifest, defaultDirName, namespace);
|
|
3074
|
+
case "skill":
|
|
3075
|
+
return applyLoadedToSpec3(spec, loaded, manifest, defaultDirName, namespace);
|
|
3076
|
+
case "n8n":
|
|
3077
|
+
return applyLoadedToSpec4(spec, loaded, manifest);
|
|
3078
|
+
}
|
|
3079
|
+
}
|
|
3080
|
+
async function discoverTools(type, projectPath, options = {}) {
|
|
3081
|
+
const root = path__namespace.resolve(projectPath);
|
|
3082
|
+
switch (type) {
|
|
3083
|
+
case "function":
|
|
3084
|
+
return scan(root, options);
|
|
3085
|
+
case "skill":
|
|
3086
|
+
return scan2(root, options);
|
|
3087
|
+
case "n8n":
|
|
3088
|
+
return scan3(root, options);
|
|
3089
|
+
case "mcp":
|
|
3090
|
+
return scan4(root, options);
|
|
3091
|
+
case "langchain":
|
|
3092
|
+
return scan5(root, options);
|
|
3093
|
+
default: {
|
|
3094
|
+
const _ = type;
|
|
3095
|
+
return _;
|
|
3096
|
+
}
|
|
3097
|
+
}
|
|
3098
|
+
}
|
|
3099
|
+
var DEFAULT_EXTENSIONS3 = [".js", ".mjs"];
|
|
3100
|
+
var DirectoryScanner = class {
|
|
3101
|
+
roots;
|
|
3102
|
+
extensions;
|
|
3103
|
+
onError;
|
|
3104
|
+
constructor(options) {
|
|
3105
|
+
const defaultNamespace = options.namespace ?? "dir";
|
|
3106
|
+
this.roots = options.roots.map((root) => {
|
|
3107
|
+
if (typeof root === "string") {
|
|
3108
|
+
return { path: root, namespace: defaultNamespace };
|
|
3109
|
+
}
|
|
3110
|
+
return {
|
|
3111
|
+
path: root.path,
|
|
3112
|
+
namespace: root.namespace ?? defaultNamespace
|
|
3113
|
+
};
|
|
3114
|
+
});
|
|
3115
|
+
this.extensions = options.extensions ?? DEFAULT_EXTENSIONS3;
|
|
3116
|
+
this.onError = options.onError;
|
|
3117
|
+
}
|
|
3118
|
+
async scan() {
|
|
3119
|
+
const specs = [];
|
|
3120
|
+
for (const root of this.roots) {
|
|
3121
|
+
const rootSpecs = await this.scanRoot(root.path, root.namespace);
|
|
3122
|
+
specs.push(...rootSpecs);
|
|
3123
|
+
}
|
|
3124
|
+
return specs;
|
|
3125
|
+
}
|
|
3126
|
+
async scanRoot(rootPath, namespace) {
|
|
3127
|
+
return this.scanRecursive(rootPath, namespace);
|
|
3128
|
+
}
|
|
3129
|
+
async scanRecursive(dirPath, namespace) {
|
|
3130
|
+
const specs = [];
|
|
3131
|
+
let dirEntries;
|
|
3132
|
+
try {
|
|
3133
|
+
const entries = await promises.readdir(dirPath, { withFileTypes: true });
|
|
3134
|
+
dirEntries = entries.map((entry) => ({
|
|
3135
|
+
name: entry.name,
|
|
3136
|
+
isDirectory: entry.isDirectory()
|
|
3137
|
+
}));
|
|
3138
|
+
} catch (error) {
|
|
3139
|
+
this.onError?.(dirPath, error);
|
|
3140
|
+
return specs;
|
|
3141
|
+
}
|
|
3142
|
+
const dirName = path.basename(dirPath);
|
|
3143
|
+
try {
|
|
3144
|
+
const loadedSpecs = await this.loadToolDir(dirPath, dirName, namespace);
|
|
3145
|
+
if (loadedSpecs.length > 0) specs.push(...loadedSpecs);
|
|
3146
|
+
} catch (error) {
|
|
3147
|
+
this.onError?.(dirPath, error);
|
|
3148
|
+
}
|
|
3149
|
+
for (const entry of dirEntries) {
|
|
3150
|
+
if (!entry.isDirectory) continue;
|
|
3151
|
+
const childPath = path.join(dirPath, entry.name);
|
|
3152
|
+
try {
|
|
3153
|
+
const childSpecs = await this.scanRecursive(childPath, namespace);
|
|
3154
|
+
specs.push(...childSpecs);
|
|
3155
|
+
} catch (error) {
|
|
3156
|
+
this.onError?.(childPath, error);
|
|
3157
|
+
}
|
|
3158
|
+
}
|
|
3159
|
+
return specs;
|
|
3160
|
+
}
|
|
3161
|
+
async loadToolDir(dirPath, dirName, namespace) {
|
|
3162
|
+
const manifestPath = path.join(dirPath, "tool.json");
|
|
3163
|
+
let manifestRaw;
|
|
3164
|
+
try {
|
|
3165
|
+
manifestRaw = await promises.readFile(manifestPath, "utf-8");
|
|
3166
|
+
} catch {
|
|
3167
|
+
const inferred = await this.inferManifest(dirPath, dirName);
|
|
3168
|
+
if (!inferred) return [];
|
|
3169
|
+
if (inferred.kind === "langchain") {
|
|
3170
|
+
if (inferred.entryPoint) {
|
|
3171
|
+
const loaded3 = await loadLangChainTool(dirPath, inferred, this.extensions);
|
|
3172
|
+
return [this.toToolSpec(loaded3, dirName, dirPath, namespace)];
|
|
3173
|
+
}
|
|
3174
|
+
return loadLangChainToolsFromDir(
|
|
3175
|
+
dirPath,
|
|
3176
|
+
dirName,
|
|
3177
|
+
inferred,
|
|
3178
|
+
false,
|
|
3179
|
+
namespace,
|
|
3180
|
+
this.extensions,
|
|
3181
|
+
LANGCHAIN_DIR_NAME,
|
|
3182
|
+
(loaded3, nameHint, dp, ns) => this.toToolSpec(loaded3, nameHint, dp, ns),
|
|
3183
|
+
this.onError
|
|
3184
|
+
);
|
|
3185
|
+
}
|
|
3186
|
+
if (inferred.kind === "skill") {
|
|
3187
|
+
const loadedList = await loadSkillTools(dirPath, inferred, this.extensions);
|
|
3188
|
+
return loadedList.map(
|
|
3189
|
+
(loaded3) => this.toToolSpec(loaded3, dirName, dirPath, namespace)
|
|
3190
|
+
);
|
|
3191
|
+
}
|
|
3192
|
+
const loaded2 = await this.loadByKind(dirPath, inferred);
|
|
3193
|
+
return [this.toToolSpec(loaded2, dirName, dirPath, namespace)];
|
|
3194
|
+
}
|
|
3195
|
+
let manifest;
|
|
3196
|
+
try {
|
|
3197
|
+
manifest = JSON.parse(manifestRaw);
|
|
3198
|
+
} catch (err) {
|
|
3199
|
+
throw new DiscoveryError(dirPath, "manifest", "Invalid JSON in tool.json", err);
|
|
3200
|
+
}
|
|
3201
|
+
if (!manifest.kind) {
|
|
3202
|
+
throw new DiscoveryError(dirPath, "manifest", `tool.json must have a "kind" field`);
|
|
3203
|
+
}
|
|
3204
|
+
if (manifest.enabled === false) return [];
|
|
3205
|
+
if (manifest.kind === "langchain") {
|
|
3206
|
+
if (manifest.entryPoint) {
|
|
3207
|
+
const loaded2 = await loadLangChainTool(dirPath, manifest, this.extensions);
|
|
3208
|
+
return [this.toToolSpec(loaded2, dirName, dirPath, namespace)];
|
|
3209
|
+
}
|
|
3210
|
+
return loadLangChainToolsFromDir(
|
|
3211
|
+
dirPath,
|
|
3212
|
+
dirName,
|
|
3213
|
+
manifest,
|
|
3214
|
+
true,
|
|
3215
|
+
namespace,
|
|
3216
|
+
this.extensions,
|
|
3217
|
+
LANGCHAIN_DIR_NAME,
|
|
3218
|
+
(loaded2, nameHint, dp, ns) => this.toToolSpec(loaded2, nameHint, dp, ns),
|
|
3219
|
+
this.onError
|
|
3220
|
+
);
|
|
3221
|
+
}
|
|
3222
|
+
if (manifest.kind === "skill") {
|
|
3223
|
+
const loadedList = await loadSkillTools(dirPath, manifest, this.extensions);
|
|
3224
|
+
return loadedList.map(
|
|
3225
|
+
(loaded2) => this.toToolSpec(loaded2, dirName, dirPath, namespace)
|
|
3226
|
+
);
|
|
3227
|
+
}
|
|
3228
|
+
const loaded = await this.loadByKind(dirPath, manifest);
|
|
3229
|
+
return [this.toToolSpec(loaded, dirName, dirPath, namespace)];
|
|
3230
|
+
}
|
|
3231
|
+
async inferManifest(dirPath, dirName) {
|
|
3232
|
+
const kinds = [];
|
|
3233
|
+
for (const m of DIRECTORY_KIND_MARKERS) {
|
|
3234
|
+
if (await this.fileExists(path.join(dirPath, m.markerFile))) kinds.push(m.kind);
|
|
3235
|
+
}
|
|
3236
|
+
const isLangchainDir = dirName === LANGCHAIN_DIR_NAME;
|
|
3237
|
+
const hasLangchain = isLangchainDir ? (await listLangchainEntryFiles(dirPath, this.extensions)).length > 0 : dirName !== SKILL_DIR_NAME && await this.hasEntryPoint(dirPath, "index");
|
|
3238
|
+
if (hasLangchain) kinds.push("langchain");
|
|
3239
|
+
if (kinds.length === 0) return null;
|
|
3240
|
+
if (kinds.length > 1) {
|
|
3241
|
+
throw new DiscoveryError(
|
|
3242
|
+
dirPath,
|
|
3243
|
+
"manifest",
|
|
3244
|
+
`Ambiguous tool kind (found ${kinds.join(", ")}). Add tool.json to disambiguate.`
|
|
3245
|
+
);
|
|
3246
|
+
}
|
|
3247
|
+
const kind = kinds[0];
|
|
3248
|
+
const manifest = { kind };
|
|
3249
|
+
const marker = DIRECTORY_KIND_MARKERS.find((m) => m.kind === kind);
|
|
3250
|
+
if (marker) {
|
|
3251
|
+
manifest.entryPoint = marker.defaultEntryPoint;
|
|
3252
|
+
}
|
|
3253
|
+
if (kind === "langchain" && !isLangchainDir) manifest.entryPoint = "index";
|
|
3254
|
+
return manifest;
|
|
3255
|
+
}
|
|
3256
|
+
async fileExists(path7) {
|
|
3257
|
+
try {
|
|
3258
|
+
await promises.access(path7);
|
|
3259
|
+
return true;
|
|
3260
|
+
} catch {
|
|
3261
|
+
return false;
|
|
3262
|
+
}
|
|
3263
|
+
}
|
|
3264
|
+
async hasEntryPoint(dirPath, baseName) {
|
|
3265
|
+
try {
|
|
3266
|
+
await resolveEntryPoint(dirPath, baseName, this.extensions);
|
|
3267
|
+
return true;
|
|
3268
|
+
} catch {
|
|
3269
|
+
return false;
|
|
3270
|
+
}
|
|
3271
|
+
}
|
|
3272
|
+
async loadByKind(dirPath, manifest) {
|
|
3273
|
+
const kind = manifest.kind;
|
|
3274
|
+
const loader = getDirectoryLoader(kind);
|
|
3275
|
+
const result = await loader(dirPath, manifest, this.extensions);
|
|
3276
|
+
const list = Array.isArray(result) ? result : [result];
|
|
3277
|
+
if (list.length === 0) {
|
|
3278
|
+
throw new DiscoveryError(dirPath, "load", "No tools loaded", new Error("empty"));
|
|
3279
|
+
}
|
|
3280
|
+
return list[0];
|
|
3281
|
+
}
|
|
3282
|
+
toToolSpec(loaded, dirName, dirPath, namespace) {
|
|
3283
|
+
const { manifest } = loaded;
|
|
3284
|
+
const kindDirNames = new Set(DIRECTORY_DISCOVERABLE_KINDS);
|
|
3285
|
+
const parentName = path.basename(path.join(dirPath, ".."));
|
|
3286
|
+
const isKindDir = kindDirNames.has(dirName);
|
|
3287
|
+
const defaultDirName = isKindDir ? parentName : dirName;
|
|
3288
|
+
const inferredName = isKindDir ? `${namespace}/${defaultDirName}-${dirName}` : `${namespace}/${defaultDirName}`;
|
|
3289
|
+
const name = manifest.name ?? inferredName;
|
|
3290
|
+
const spec = this.buildBaseSpec(manifest, name, dirName);
|
|
3291
|
+
applyDirectoryLoadedToSpec(spec, loaded, manifest, defaultDirName, namespace);
|
|
3292
|
+
return spec;
|
|
3293
|
+
}
|
|
3294
|
+
buildBaseSpec(manifest, name, dirName) {
|
|
3295
|
+
return {
|
|
3296
|
+
name,
|
|
3297
|
+
version: manifest.version ?? "1.0.0",
|
|
3298
|
+
kind: manifest.kind,
|
|
3299
|
+
description: manifest.description ?? `${manifest.kind} tool: ${dirName}`,
|
|
3300
|
+
tags: manifest.tags,
|
|
3301
|
+
inputSchema: manifest.inputSchema ?? { type: "object", additionalProperties: true },
|
|
3302
|
+
outputSchema: manifest.outputSchema ?? { type: "object", additionalProperties: true },
|
|
3303
|
+
capabilities: manifest.capabilities ?? [],
|
|
3304
|
+
costHints: manifest.costHints
|
|
3305
|
+
};
|
|
3306
|
+
}
|
|
3307
|
+
};
|
|
1768
3308
|
|
|
1769
3309
|
// src/api/runtimeFromConfig.ts
|
|
1770
|
-
var requireFromPackage = module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('chunk-
|
|
3310
|
+
var requireFromPackage = module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('chunk-D2T3NTFN.cjs', document.baseURI).href)));
|
|
1771
3311
|
function getProjectRequire() {
|
|
1772
3312
|
const cwd = process.cwd();
|
|
1773
3313
|
if (fs.existsSync(path.join(cwd, "package.json"))) return module$1.createRequire(path.join(cwd, "package.json"));
|
|
@@ -1801,6 +3341,98 @@ function getInstalledPackageVersion(packageName) {
|
|
|
1801
3341
|
}
|
|
1802
3342
|
return null;
|
|
1803
3343
|
}
|
|
3344
|
+
function hasInvokeFunction(value) {
|
|
3345
|
+
return value != null && typeof value === "object" && "invoke" in value && typeof value.invoke === "function";
|
|
3346
|
+
}
|
|
3347
|
+
function normalizeToolOutput(output) {
|
|
3348
|
+
if (output != null && typeof output === "object" && "result" in output) {
|
|
3349
|
+
const o = output;
|
|
3350
|
+
const evidence = Array.isArray(o.evidence) ? o.evidence : void 0;
|
|
3351
|
+
return evidence ? { result: o.result, raw: { evidence } } : { result: o.result };
|
|
3352
|
+
}
|
|
3353
|
+
return { result: output };
|
|
3354
|
+
}
|
|
3355
|
+
function createLocalDirectoryAdapter(kind) {
|
|
3356
|
+
return {
|
|
3357
|
+
kind,
|
|
3358
|
+
async invoke(spec, args, ctx) {
|
|
3359
|
+
if (spec.kind === "skill") {
|
|
3360
|
+
const impl = spec.impl;
|
|
3361
|
+
if (hasInvokeFunction(impl)) {
|
|
3362
|
+
const out2 = await impl.invoke(args);
|
|
3363
|
+
return normalizeToolOutput(out2);
|
|
3364
|
+
}
|
|
3365
|
+
const handler = typeof impl?.handler === "function" ? impl.handler : typeof spec.impl === "function" ? spec.impl : void 0;
|
|
3366
|
+
if (typeof handler !== "function") {
|
|
3367
|
+
throw new Error(`Local skill ${spec.name} has no executable handler`);
|
|
3368
|
+
}
|
|
3369
|
+
const out = await handler(args, {
|
|
3370
|
+
requestId: ctx.requestId,
|
|
3371
|
+
taskId: ctx.taskId,
|
|
3372
|
+
skill: {
|
|
3373
|
+
name: impl?.frontmatter?.name ?? spec.name,
|
|
3374
|
+
description: impl?.frontmatter?.description ?? spec.description ?? "",
|
|
3375
|
+
instructions: impl?.instructions ?? "",
|
|
3376
|
+
resources: [],
|
|
3377
|
+
readResource: async () => "",
|
|
3378
|
+
getResourcesByType: () => [],
|
|
3379
|
+
dirPath: impl?.dirPath ?? ""
|
|
3380
|
+
}
|
|
3381
|
+
});
|
|
3382
|
+
return normalizeToolOutput(out);
|
|
3383
|
+
}
|
|
3384
|
+
if (spec.kind === "langchain") {
|
|
3385
|
+
if (!hasInvokeFunction(spec.impl)) {
|
|
3386
|
+
throw new Error(`Local langchain tool ${spec.name} missing invoke()`);
|
|
3387
|
+
}
|
|
3388
|
+
const out = await spec.impl.invoke(args);
|
|
3389
|
+
return normalizeToolOutput(out);
|
|
3390
|
+
}
|
|
3391
|
+
if (spec.kind === "n8n") {
|
|
3392
|
+
if (!spec.endpoint) {
|
|
3393
|
+
throw new Error(`Local n8n tool ${spec.name} missing endpoint/webhook URL`);
|
|
3394
|
+
}
|
|
3395
|
+
const res = await fetch(spec.endpoint, {
|
|
3396
|
+
method: "POST",
|
|
3397
|
+
headers: { "content-type": "application/json" },
|
|
3398
|
+
body: JSON.stringify(args ?? {})
|
|
3399
|
+
});
|
|
3400
|
+
if (!res.ok) {
|
|
3401
|
+
throw new Error(`n8n request failed (${res.status})`);
|
|
3402
|
+
}
|
|
3403
|
+
const text = await res.text();
|
|
3404
|
+
if (!text.trim()) return { result: {} };
|
|
3405
|
+
try {
|
|
3406
|
+
return { result: JSON.parse(text) };
|
|
3407
|
+
} catch {
|
|
3408
|
+
return { result: text };
|
|
3409
|
+
}
|
|
3410
|
+
}
|
|
3411
|
+
if (spec.kind === "function") {
|
|
3412
|
+
const meta = spec._meta;
|
|
3413
|
+
if (!meta?.sourcePath || !meta?.exportName || !meta?.projectPath) {
|
|
3414
|
+
throw new Error(`Local function ${spec.name} missing sourcePath/exportName/projectPath`);
|
|
3415
|
+
}
|
|
3416
|
+
const source = path.resolve(meta.projectPath, meta.sourcePath);
|
|
3417
|
+
const mod = await import(url.pathToFileURL(source).href);
|
|
3418
|
+
const fn = mod[meta.exportName];
|
|
3419
|
+
if (typeof fn !== "function") {
|
|
3420
|
+
throw new Error(`Local function ${spec.name} export "${meta.exportName}" is not a function`);
|
|
3421
|
+
}
|
|
3422
|
+
const out = await fn(args);
|
|
3423
|
+
return normalizeToolOutput(out);
|
|
3424
|
+
}
|
|
3425
|
+
throw new Error(`Local directory tool kind not supported for invoke: ${spec.kind}`);
|
|
3426
|
+
}
|
|
3427
|
+
};
|
|
3428
|
+
}
|
|
3429
|
+
function resolveFileDescriptorPath(descriptor, configFilePath) {
|
|
3430
|
+
const parsed = parseToolPath(descriptor.trim());
|
|
3431
|
+
if (!parsed || parsed.protocol !== "file") return null;
|
|
3432
|
+
const localPath = path.isAbsolute(configFilePath) ? configFilePath : path.resolve(process.cwd(), configFilePath);
|
|
3433
|
+
const configDir = path.dirname(localPath);
|
|
3434
|
+
return path.resolve(configDir, `${parsed.scope}/${parsed.packageWithVersion}`);
|
|
3435
|
+
}
|
|
1804
3436
|
function getRegisterFn(mod) {
|
|
1805
3437
|
return mod?.register ?? mod?.registerCoreTools;
|
|
1806
3438
|
}
|
|
@@ -1869,11 +3501,11 @@ function parseNpmDescriptor(entry) {
|
|
|
1869
3501
|
}
|
|
1870
3502
|
function loadExtensionFromFileDescriptorSync(descriptor, configFilePath, stepLog) {
|
|
1871
3503
|
const entryStr = descriptor.trim();
|
|
1872
|
-
const
|
|
1873
|
-
if (!
|
|
3504
|
+
const path7 = parseToolPath(entryStr);
|
|
3505
|
+
if (!path7 || path7.protocol !== "file") return null;
|
|
1874
3506
|
const localPath = path.isAbsolute(configFilePath) ? configFilePath : path.resolve(process.cwd(), configFilePath);
|
|
1875
3507
|
const configDir = path.dirname(localPath);
|
|
1876
|
-
const pathPart = `${
|
|
3508
|
+
const pathPart = `${path7.scope}/${path7.packageWithVersion}`;
|
|
1877
3509
|
const resolvedPath = path.resolve(configDir, pathPart);
|
|
1878
3510
|
if (!fs.existsSync(resolvedPath) || !fs.statSync(resolvedPath).isDirectory()) return null;
|
|
1879
3511
|
try {
|
|
@@ -2016,6 +3648,29 @@ async function loadExtensionForDescriptorAsync(descriptor, configFilePath, stepL
|
|
|
2016
3648
|
}
|
|
2017
3649
|
return null;
|
|
2018
3650
|
}
|
|
3651
|
+
async function loadLocalDirectoryForFileDescriptor(descriptor, configFilePath, stepLog) {
|
|
3652
|
+
const resolvedPath = resolveFileDescriptorPath(descriptor, configFilePath);
|
|
3653
|
+
if (!resolvedPath || !fs.existsSync(resolvedPath) || !fs.statSync(resolvedPath).isDirectory()) return null;
|
|
3654
|
+
if (fs.existsSync(path.join(resolvedPath, "package.json"))) return null;
|
|
3655
|
+
const scanErrors = [];
|
|
3656
|
+
const scanner = new DirectoryScanner({
|
|
3657
|
+
roots: [{ path: resolvedPath, namespace: "local" }],
|
|
3658
|
+
onError: (toolDir, error) => scanErrors.push(`${toolDir}: ${error.message}`)
|
|
3659
|
+
});
|
|
3660
|
+
const specs = await scanner.scan();
|
|
3661
|
+
if (scanErrors.length > 0 && stepLog) {
|
|
3662
|
+
stepLog(`Local directory scan (${descriptor}) reported ${scanErrors.length} error(s)`);
|
|
3663
|
+
}
|
|
3664
|
+
if (specs.length === 0) return null;
|
|
3665
|
+
if (stepLog) {
|
|
3666
|
+
stepLog(`Loaded ${specs.length} local tool(s) from ${descriptor}`);
|
|
3667
|
+
}
|
|
3668
|
+
return {
|
|
3669
|
+
specs,
|
|
3670
|
+
descriptor: descriptor.trim(),
|
|
3671
|
+
resolvedVersion: "local"
|
|
3672
|
+
};
|
|
3673
|
+
}
|
|
2019
3674
|
async function loadAllExtensionsFromToolYamlAsync(configFilePath, stepLog) {
|
|
2020
3675
|
const localPath = path.isAbsolute(configFilePath) ? configFilePath : path.resolve(process.cwd(), configFilePath);
|
|
2021
3676
|
if (!fs.existsSync(localPath)) return [];
|
|
@@ -2031,7 +3686,12 @@ async function loadAllExtensionsFromToolYamlAsync(configFilePath, stepLog) {
|
|
|
2031
3686
|
if (result) loaded.push(result);
|
|
2032
3687
|
} else if (entryStr.startsWith("file:")) {
|
|
2033
3688
|
const result = loadExtensionFromFileDescriptorSync(entryStr, configFilePath, stepLog);
|
|
2034
|
-
if (result)
|
|
3689
|
+
if (result) {
|
|
3690
|
+
loaded.push(result);
|
|
3691
|
+
} else {
|
|
3692
|
+
const local = await loadLocalDirectoryForFileDescriptor(entryStr, configFilePath, stepLog);
|
|
3693
|
+
if (local) loaded.push(local);
|
|
3694
|
+
}
|
|
2035
3695
|
}
|
|
2036
3696
|
}
|
|
2037
3697
|
return loaded;
|
|
@@ -2092,10 +3752,28 @@ async function createRuntimeFromConfig(options = {}) {
|
|
|
2092
3752
|
const before = new Set(registry.snapshot().map((s) => s.name));
|
|
2093
3753
|
const prefix = ext.descriptor.startsWith("file:") ? fileDescriptorToRegistryPrefix(ext.descriptor) : npmDescriptorToRegistryPrefix(ext.descriptor, ext.resolvedVersion);
|
|
2094
3754
|
const reg = createPrefixingRegistry(registry, prefix ?? "");
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
3755
|
+
if ("register" in ext) {
|
|
3756
|
+
const adapter = ext.register(reg, options.coreTools);
|
|
3757
|
+
runtime.registerAdapter(adapter);
|
|
3758
|
+
const registeredNow = registry.snapshot().map((s) => s.name).filter((name) => !before.has(name));
|
|
3759
|
+
runtime.registerAdapterForTools(registeredNow, adapter);
|
|
3760
|
+
} else {
|
|
3761
|
+
reg.bulkRegister(ext.specs);
|
|
3762
|
+
const registeredNow = registry.snapshot().map((s) => s.name).filter((name) => !before.has(name));
|
|
3763
|
+
const byKind = /* @__PURE__ */ new Map();
|
|
3764
|
+
for (const toolName of registeredNow) {
|
|
3765
|
+
const spec = registry.get(toolName);
|
|
3766
|
+
if (!spec) continue;
|
|
3767
|
+
const list = byKind.get(spec.kind) ?? [];
|
|
3768
|
+
list.push(toolName);
|
|
3769
|
+
byKind.set(spec.kind, list);
|
|
3770
|
+
}
|
|
3771
|
+
for (const [kind, names] of byKind.entries()) {
|
|
3772
|
+
const adapter = createLocalDirectoryAdapter(kind);
|
|
3773
|
+
runtime.registerAdapter(adapter);
|
|
3774
|
+
runtime.registerAdapterForTools(names, adapter);
|
|
3775
|
+
}
|
|
3776
|
+
}
|
|
2099
3777
|
}
|
|
2100
3778
|
return { runtime, registry };
|
|
2101
3779
|
}
|
|
@@ -2263,17 +3941,17 @@ var DEFAULT_CTX = {
|
|
|
2263
3941
|
]
|
|
2264
3942
|
};
|
|
2265
3943
|
function parseBody(req) {
|
|
2266
|
-
return new Promise((
|
|
3944
|
+
return new Promise((resolve10, reject) => {
|
|
2267
3945
|
const chunks = [];
|
|
2268
3946
|
req.on("data", (chunk) => chunks.push(chunk));
|
|
2269
3947
|
req.on("end", () => {
|
|
2270
3948
|
const raw = Buffer.concat(chunks).toString("utf-8");
|
|
2271
3949
|
if (!raw.trim()) {
|
|
2272
|
-
|
|
3950
|
+
resolve10({});
|
|
2273
3951
|
return;
|
|
2274
3952
|
}
|
|
2275
3953
|
try {
|
|
2276
|
-
|
|
3954
|
+
resolve10(JSON.parse(raw));
|
|
2277
3955
|
} catch {
|
|
2278
3956
|
reject(new Error("Invalid JSON body"));
|
|
2279
3957
|
}
|
|
@@ -2321,8 +3999,8 @@ function createOpenAPIHttpServer(runtime, options = {}) {
|
|
|
2321
3999
|
const ctxFactory = options.execContextFactory ?? (() => ({ ...DEFAULT_CTX }));
|
|
2322
4000
|
const server = http.createServer(async (req, res) => {
|
|
2323
4001
|
const url = req.url ?? "/";
|
|
2324
|
-
const
|
|
2325
|
-
const norm = basePath ?
|
|
4002
|
+
const path7 = url.split("?")[0] ?? "/";
|
|
4003
|
+
const norm = basePath ? path7 === basePath ? "" : path7.replace(basePath, "") || "/" : path7;
|
|
2326
4004
|
try {
|
|
2327
4005
|
if (req.method === "GET" && (norm === "/" || norm === "/swagger")) {
|
|
2328
4006
|
const specPath = basePath ? `${basePath}/openapi.json` : "/openapi.json";
|
|
@@ -2412,13 +4090,13 @@ function createOpenAPIHttpServer(runtime, options = {}) {
|
|
|
2412
4090
|
return server;
|
|
2413
4091
|
}
|
|
2414
4092
|
function listenOpenAPIHttpServer(server, options = {}) {
|
|
2415
|
-
return new Promise((
|
|
4093
|
+
return new Promise((resolve10, reject) => {
|
|
2416
4094
|
const port = options.port ?? 0;
|
|
2417
4095
|
const host = options.host ?? "localhost";
|
|
2418
4096
|
server.listen(port, host, () => {
|
|
2419
4097
|
const addr = server.address();
|
|
2420
4098
|
const actualPort = typeof addr === "object" && addr?.port != null ? addr.port : port;
|
|
2421
|
-
|
|
4099
|
+
resolve10({ port: actualPort, host });
|
|
2422
4100
|
});
|
|
2423
4101
|
server.on("error", reject);
|
|
2424
4102
|
});
|
|
@@ -2438,9 +4116,6 @@ async function createHttpService(runtimeOrConfig, options = {}) {
|
|
|
2438
4116
|
};
|
|
2439
4117
|
}
|
|
2440
4118
|
|
|
2441
|
-
// src/tools/mcp/types.ts
|
|
2442
|
-
var MCP_KIND = "mcp";
|
|
2443
|
-
|
|
2444
4119
|
// src/api/expose/mcpServer.ts
|
|
2445
4120
|
var DEFAULT_CTX2 = {
|
|
2446
4121
|
requestId: `mcp-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`,
|
|
@@ -2543,24 +4218,24 @@ function createMCPStreamableHttpHandler(runtimeOrConfig, options = {}) {
|
|
|
2543
4218
|
})();
|
|
2544
4219
|
}
|
|
2545
4220
|
async function createMCPServerStreamableHttp(runtimeOrConfig, options = {}) {
|
|
2546
|
-
const
|
|
4221
|
+
const path7 = options.path ?? "/mcp";
|
|
2547
4222
|
const host = options.host ?? "127.0.0.1";
|
|
2548
4223
|
const port = options.port ?? 3e3;
|
|
2549
4224
|
const { createMcpExpressApp } = await import('@modelcontextprotocol/sdk/server/express.js');
|
|
2550
4225
|
const handler = "invoke" in runtimeOrConfig && typeof runtimeOrConfig.invoke === "function" ? createMCPStreamableHttpHandler(runtimeOrConfig, options) : await createMCPStreamableHttpHandler(runtimeOrConfig, options);
|
|
2551
4226
|
const app = createMcpExpressApp({ host });
|
|
2552
|
-
app.post(
|
|
4227
|
+
app.post(path7, handler);
|
|
2553
4228
|
return {
|
|
2554
4229
|
app,
|
|
2555
|
-
path,
|
|
4230
|
+
path: path7,
|
|
2556
4231
|
async listen(listenPort, listenHost) {
|
|
2557
4232
|
const p = listenPort ?? port;
|
|
2558
4233
|
const h = listenHost ?? host;
|
|
2559
|
-
return new Promise((
|
|
4234
|
+
return new Promise((resolve10, reject) => {
|
|
2560
4235
|
const server = app.listen(p, h, () => {
|
|
2561
4236
|
const addr = server.address();
|
|
2562
4237
|
const actualPort = typeof addr === "object" && addr !== null && "port" in addr ? addr.port : p;
|
|
2563
|
-
|
|
4238
|
+
resolve10({ url: `http://${h}:${actualPort}${path7}`, port: actualPort });
|
|
2564
4239
|
});
|
|
2565
4240
|
});
|
|
2566
4241
|
}
|
|
@@ -2572,20 +4247,18 @@ async function runMCPServerOverStdio(runtime, options = {}) {
|
|
|
2572
4247
|
return result;
|
|
2573
4248
|
}
|
|
2574
4249
|
|
|
2575
|
-
|
|
2576
|
-
var LANGCHAIN_KIND = "langchain";
|
|
2577
|
-
var LANGCHAIN_DIR_NAME = "langchain";
|
|
2578
|
-
|
|
2579
|
-
exports.LANGCHAIN_DIR_NAME = LANGCHAIN_DIR_NAME;
|
|
4250
|
+
exports.FUNCTION_KIND = FUNCTION_KIND;
|
|
2580
4251
|
exports.LANGCHAIN_KIND = LANGCHAIN_KIND;
|
|
2581
|
-
exports.
|
|
4252
|
+
exports.N8N_KIND = N8N_KIND;
|
|
2582
4253
|
exports.PTCRuntime = PTCRuntime;
|
|
4254
|
+
exports.SKILL_KIND = SKILL_KIND;
|
|
2583
4255
|
exports.createHttpService = createHttpService;
|
|
2584
4256
|
exports.createMCPServer = createMCPServer;
|
|
2585
4257
|
exports.createMCPServerStreamableHttp = createMCPServerStreamableHttp;
|
|
2586
4258
|
exports.createMCPStreamableHttpHandler = createMCPStreamableHttpHandler;
|
|
2587
4259
|
exports.createRuntimeFromConfig = createRuntimeFromConfig;
|
|
2588
4260
|
exports.createRuntimeFromConfigSync = createRuntimeFromConfigSync;
|
|
4261
|
+
exports.discoverTools = discoverTools;
|
|
2589
4262
|
exports.expandToolDescriptorsToRegistryNames = expandToolDescriptorsToRegistryNames;
|
|
2590
4263
|
exports.fileDescriptorToPackagePrefix = fileDescriptorToPackagePrefix;
|
|
2591
4264
|
exports.findAndLoadToolConfig = findAndLoadToolConfig;
|
|
@@ -2597,6 +4270,8 @@ exports.resolveSandboxedPath = resolveSandboxedPath;
|
|
|
2597
4270
|
exports.resolveSandboxedPath2 = resolveSandboxedPath2;
|
|
2598
4271
|
exports.resolveToolDescriptor = resolveToolDescriptor;
|
|
2599
4272
|
exports.runMCPServerOverStdio = runMCPServerOverStdio;
|
|
4273
|
+
exports.scan = scan;
|
|
4274
|
+
exports.scanForTools = scanForTools;
|
|
2600
4275
|
exports.setSandboxValidationEnabled = setSandboxValidationEnabled;
|
|
2601
|
-
//# sourceMappingURL=chunk-
|
|
2602
|
-
//# sourceMappingURL=chunk-
|
|
4276
|
+
//# sourceMappingURL=chunk-D2T3NTFN.cjs.map
|
|
4277
|
+
//# sourceMappingURL=chunk-D2T3NTFN.cjs.map
|