ai4l-library-dev 0.0.0-stage → 0.1.1
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/LICENSE +9 -0
- package/README.md +10 -2
- package/dist/index.js +334 -0
- package/package.json +20 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Forever Healthy Foundation
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including, without limitation, the rights to use, copy, modify, merge, publish, distribute, sub-license, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
6
|
+
|
|
7
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
-
#
|
|
1
|
+
# ai4l-library-dev
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**Internal development build. Not for public use.**
|
|
4
|
+
|
|
5
|
+
This package is used for internal testing of the ai4l-library MCP server. It may
|
|
6
|
+
change or break at any time.
|
|
7
|
+
|
|
8
|
+
The public release lives at:
|
|
9
|
+
|
|
10
|
+
- npm: [`ai4l-library`](https://www.npmjs.com/package/ai4l-library)
|
|
11
|
+
- GitHub: [forever-healthy/AI4L-library](https://github.com/forever-healthy/AI4L-library)
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6
|
+
|
|
7
|
+
// src/tools.ts
|
|
8
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
9
|
+
import { z } from "zod";
|
|
10
|
+
|
|
11
|
+
// src/entry.ts
|
|
12
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
13
|
+
import { basename } from "path";
|
|
14
|
+
function emptyStatus(requested, type, id, libraryDir) {
|
|
15
|
+
return {
|
|
16
|
+
requested,
|
|
17
|
+
type,
|
|
18
|
+
id,
|
|
19
|
+
entry: null,
|
|
20
|
+
entry_dir: null,
|
|
21
|
+
library_dir: libraryDir,
|
|
22
|
+
pmid: null,
|
|
23
|
+
pmcid: null,
|
|
24
|
+
doi: null,
|
|
25
|
+
title: null,
|
|
26
|
+
first_author: null,
|
|
27
|
+
year: null,
|
|
28
|
+
publication_types: [],
|
|
29
|
+
retracted: false,
|
|
30
|
+
level: null,
|
|
31
|
+
version: null,
|
|
32
|
+
retrieved_by: null,
|
|
33
|
+
retrieved_date: null,
|
|
34
|
+
attempts: [],
|
|
35
|
+
files: []
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
function renderAbstractMd(r) {
|
|
39
|
+
const line = [
|
|
40
|
+
r.authors.join(", ") || null,
|
|
41
|
+
r.journal,
|
|
42
|
+
r.year,
|
|
43
|
+
`PMID ${r.pmid}`,
|
|
44
|
+
r.doi ? `DOI ${r.doi}` : null,
|
|
45
|
+
r.pmcid ? `PMCID ${r.pmcid}` : null
|
|
46
|
+
].filter(Boolean).join(" \xB7 ");
|
|
47
|
+
const body = r.abstract.length ? r.abstract.map((a) => a.label ? `**${a.label}.** ${a.text}` : a.text).join("\n\n") : "_No abstract available._";
|
|
48
|
+
return `# ${r.title}
|
|
49
|
+
|
|
50
|
+
${line}
|
|
51
|
+
|
|
52
|
+
## Abstract
|
|
53
|
+
|
|
54
|
+
${body}
|
|
55
|
+
`;
|
|
56
|
+
}
|
|
57
|
+
function renderMeta(r, files, retrievedDate, converter) {
|
|
58
|
+
const lines = [
|
|
59
|
+
["pmid", r.pmid],
|
|
60
|
+
["pmcid", r.pmcid ?? ""],
|
|
61
|
+
["doi", r.doi ?? ""],
|
|
62
|
+
["title", JSON.stringify(r.title)],
|
|
63
|
+
["first_author", r.firstAuthor ?? ""],
|
|
64
|
+
["year", r.year ?? ""],
|
|
65
|
+
["publication_types", r.publicationTypes.join(", ")],
|
|
66
|
+
["retracted", r.publicationTypes.includes("Retracted Publication") ? "yes" : "no"],
|
|
67
|
+
["level", "abstract"],
|
|
68
|
+
["version", ""],
|
|
69
|
+
["files", files.join(", ")],
|
|
70
|
+
["retrieved_by", "pubmed"],
|
|
71
|
+
["retrieved_date", retrievedDate],
|
|
72
|
+
["converted_by", converter],
|
|
73
|
+
["attempts", ""]
|
|
74
|
+
];
|
|
75
|
+
return "---\n" + lines.map(([k, v]) => v === "" ? `${k}:` : `${k}: ${v}`).join("\n") + "\n---\n";
|
|
76
|
+
}
|
|
77
|
+
function entryExists(p) {
|
|
78
|
+
return existsSync(p.meta);
|
|
79
|
+
}
|
|
80
|
+
function writeEntry(p, r, xml, converter) {
|
|
81
|
+
mkdirSync(p.entryDir, { recursive: true });
|
|
82
|
+
const files = [basename(p.xml), basename(p.md)];
|
|
83
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
84
|
+
writeFileSync(p.xml, xml);
|
|
85
|
+
writeFileSync(p.md, renderAbstractMd(r));
|
|
86
|
+
writeFileSync(p.meta, renderMeta(r, files, today, converter));
|
|
87
|
+
}
|
|
88
|
+
function parseFrontmatter(md) {
|
|
89
|
+
const m = /^---\n([\s\S]*?)\n---/.exec(md);
|
|
90
|
+
const out = {};
|
|
91
|
+
for (const line of (m?.[1] ?? "").split("\n")) {
|
|
92
|
+
const i = line.indexOf(":");
|
|
93
|
+
if (i > 0) out[line.slice(0, i).trim()] = line.slice(i + 1).trim();
|
|
94
|
+
}
|
|
95
|
+
return out;
|
|
96
|
+
}
|
|
97
|
+
var list = (v) => v ? v.split(",").map((s) => s.trim()).filter(Boolean) : [];
|
|
98
|
+
var opt = (v) => v ? v : null;
|
|
99
|
+
function readStatus(p, base) {
|
|
100
|
+
const fm = parseFrontmatter(readFileSync(p.meta, "utf8"));
|
|
101
|
+
let title = opt(fm.title);
|
|
102
|
+
if (title?.startsWith('"')) {
|
|
103
|
+
try {
|
|
104
|
+
title = JSON.parse(title);
|
|
105
|
+
} catch {
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
...base,
|
|
110
|
+
entry: p.entry,
|
|
111
|
+
entry_dir: p.entryDir,
|
|
112
|
+
pmid: opt(fm.pmid),
|
|
113
|
+
pmcid: opt(fm.pmcid),
|
|
114
|
+
doi: opt(fm.doi),
|
|
115
|
+
title,
|
|
116
|
+
first_author: opt(fm.first_author),
|
|
117
|
+
year: opt(fm.year),
|
|
118
|
+
publication_types: list(fm.publication_types),
|
|
119
|
+
retracted: fm.retracted === "yes",
|
|
120
|
+
level: opt(fm.level) ?? null,
|
|
121
|
+
version: opt(fm.version),
|
|
122
|
+
retrieved_by: opt(fm.retrieved_by),
|
|
123
|
+
retrieved_date: opt(fm.retrieved_date),
|
|
124
|
+
attempts: list(fm.attempts),
|
|
125
|
+
files: list(fm.files)
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// src/ids.ts
|
|
130
|
+
var SUPPORTED = /* @__PURE__ */ new Set(["pmid"]);
|
|
131
|
+
var KNOWN = /* @__PURE__ */ new Set(["pmid", "pmc", "doi", "nct"]);
|
|
132
|
+
function parseId(raw) {
|
|
133
|
+
const requested = String(raw ?? "").trim();
|
|
134
|
+
const bare = /^\d+$/.test(requested);
|
|
135
|
+
const m = bare ? null : /^([A-Za-z][A-Za-z0-9_-]*):(.+)$/.exec(requested);
|
|
136
|
+
if (!bare && !m) return { requested, type: null, id: null, error: "invalid id" };
|
|
137
|
+
const type = bare ? "pmid" : m[1].toLowerCase();
|
|
138
|
+
const value = bare ? requested : m[2].trim();
|
|
139
|
+
if (!KNOWN.has(type)) return { requested, type, id: value, error: "invalid id" };
|
|
140
|
+
if (!SUPPORTED.has(type)) return { requested, type, id: value, error: "type not supported yet" };
|
|
141
|
+
if (!/^[1-9]\d*$/.test(value)) return { requested, type, id: value, error: "invalid id" };
|
|
142
|
+
return { requested, type, id: value };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// src/layout.ts
|
|
146
|
+
import { existsSync as existsSync2 } from "fs";
|
|
147
|
+
import { join, resolve } from "path";
|
|
148
|
+
var LIBRARY_DIR = (() => {
|
|
149
|
+
const env = process.env.LIBRARY_DIR;
|
|
150
|
+
if (env && env.trim()) return resolve(env.trim());
|
|
151
|
+
if (existsSync2("/library")) return "/library";
|
|
152
|
+
return resolve("library");
|
|
153
|
+
})();
|
|
154
|
+
function entryDirRel(type, num) {
|
|
155
|
+
const l1 = num.length > 6 ? num.slice(0, -6) : "0";
|
|
156
|
+
const l2 = num.length > 4 ? num.slice(0, -4) : "0";
|
|
157
|
+
return `${type}/${l1}/${l2}/${num}`;
|
|
158
|
+
}
|
|
159
|
+
function entryPaths(libraryDir, type, num) {
|
|
160
|
+
const entry = `${type}-${num}`;
|
|
161
|
+
const entryDir = join(libraryDir, entryDirRel(type, num));
|
|
162
|
+
return {
|
|
163
|
+
entry,
|
|
164
|
+
entryDir,
|
|
165
|
+
xml: join(entryDir, `${entry}-abstract.xml`),
|
|
166
|
+
md: join(entryDir, `${entry}-abstract.md`),
|
|
167
|
+
meta: join(entryDir, `${entry}-meta.md`)
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// src/pubmed.ts
|
|
172
|
+
import { XMLParser } from "fast-xml-parser";
|
|
173
|
+
var EFETCH = "https://eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi";
|
|
174
|
+
var TIMEOUT_MS = 3e4;
|
|
175
|
+
function efetchUrl(pmid) {
|
|
176
|
+
const u = new URL(EFETCH);
|
|
177
|
+
u.searchParams.set("db", "pubmed");
|
|
178
|
+
u.searchParams.set("id", pmid);
|
|
179
|
+
u.searchParams.set("retmode", "xml");
|
|
180
|
+
if (process.env.NCBI_API_KEY) u.searchParams.set("api_key", process.env.NCBI_API_KEY);
|
|
181
|
+
if (process.env.CONTACT_EMAIL) {
|
|
182
|
+
u.searchParams.set("tool", "ai4l-library");
|
|
183
|
+
u.searchParams.set("email", process.env.CONTACT_EMAIL);
|
|
184
|
+
}
|
|
185
|
+
return u.toString();
|
|
186
|
+
}
|
|
187
|
+
async function fetchPubmedXml(pmid) {
|
|
188
|
+
const url = efetchUrl(pmid);
|
|
189
|
+
let last;
|
|
190
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
191
|
+
try {
|
|
192
|
+
const res = await fetch(url, { signal: AbortSignal.timeout(TIMEOUT_MS) });
|
|
193
|
+
if (res.ok) return await res.text();
|
|
194
|
+
last = new Error(`HTTP ${res.status}`);
|
|
195
|
+
if (res.status !== 429 && res.status < 500) break;
|
|
196
|
+
} catch (e) {
|
|
197
|
+
last = e;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
const msg = last instanceof Error ? last.message : String(last);
|
|
201
|
+
throw new Error(`pubmed fetch failed: ${msg}`);
|
|
202
|
+
}
|
|
203
|
+
var parser = new XMLParser({
|
|
204
|
+
ignoreAttributes: false,
|
|
205
|
+
attributeNamePrefix: "@_",
|
|
206
|
+
parseTagValue: false,
|
|
207
|
+
processEntities: true,
|
|
208
|
+
stopNodes: ["*.ArticleTitle", "*.AbstractText"],
|
|
209
|
+
isArray: (name) => ["PubmedArticle", "AbstractText", "Author", "ArticleId", "PublicationType"].includes(name)
|
|
210
|
+
});
|
|
211
|
+
function text(n) {
|
|
212
|
+
if (n == null) return "";
|
|
213
|
+
if (typeof n === "object") return text(n["#text"]);
|
|
214
|
+
return String(n);
|
|
215
|
+
}
|
|
216
|
+
function inlineText(raw) {
|
|
217
|
+
return decodeEntities(raw.replace(/<[^>]+>/g, "")).replace(/\s+/g, " ").trim();
|
|
218
|
+
}
|
|
219
|
+
function decodeEntities(s) {
|
|
220
|
+
return s.replace(/&#x([0-9a-f]+);/gi, (_, h) => String.fromCodePoint(parseInt(h, 16))).replace(/&#(\d+);/g, (_, d) => String.fromCodePoint(parseInt(d, 10))).replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'").replace(/&/g, "&");
|
|
221
|
+
}
|
|
222
|
+
function parsePubmedXml(xml) {
|
|
223
|
+
const doc = parser.parse(xml);
|
|
224
|
+
const article = doc?.PubmedArticleSet?.PubmedArticle?.[0];
|
|
225
|
+
if (!article) return null;
|
|
226
|
+
const cit = article.MedlineCitation ?? {};
|
|
227
|
+
const art = cit.Article ?? {};
|
|
228
|
+
const ids = article.PubmedData?.ArticleIdList?.ArticleId ?? [];
|
|
229
|
+
const idOf = (t) => {
|
|
230
|
+
const hit = ids.find((i) => i["@_IdType"] === t);
|
|
231
|
+
return hit ? text(hit).trim() || null : null;
|
|
232
|
+
};
|
|
233
|
+
const authorList = art.AuthorList?.Author ?? [];
|
|
234
|
+
const authors = authorList.map((a) => {
|
|
235
|
+
if (a.CollectiveName) return text(a.CollectiveName).trim();
|
|
236
|
+
const last = text(a.LastName).trim();
|
|
237
|
+
const ini = text(a.Initials).trim();
|
|
238
|
+
return [last, ini].filter(Boolean).join(" ");
|
|
239
|
+
}).filter(Boolean);
|
|
240
|
+
const first = authorList[0];
|
|
241
|
+
const firstAuthor = first ? text(first.CollectiveName ?? first.LastName).trim() || null : null;
|
|
242
|
+
const pub = art.Journal?.JournalIssue?.PubDate ?? {};
|
|
243
|
+
let year = text(pub.Year).trim() || null;
|
|
244
|
+
if (!year) year = /\d{4}/.exec(text(pub.MedlineDate))?.[0] ?? null;
|
|
245
|
+
const abstract = (art.Abstract?.AbstractText ?? []).map((a) => ({
|
|
246
|
+
label: typeof a === "object" ? a["@_Label"] ?? null : null,
|
|
247
|
+
text: inlineText(text(a))
|
|
248
|
+
})).filter((a) => a.text);
|
|
249
|
+
return {
|
|
250
|
+
pmid: text(cit.PMID).trim() || idOf("pubmed") || "",
|
|
251
|
+
pmcid: idOf("pmc"),
|
|
252
|
+
doi: idOf("doi"),
|
|
253
|
+
title: inlineText(text(art.ArticleTitle)),
|
|
254
|
+
authors,
|
|
255
|
+
firstAuthor,
|
|
256
|
+
journal: text(art.Journal?.Title).trim() || null,
|
|
257
|
+
year,
|
|
258
|
+
publicationTypes: (art.PublicationTypeList?.PublicationType ?? []).map((p) => text(p).trim()).filter(Boolean),
|
|
259
|
+
abstract
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// src/tools.ts
|
|
264
|
+
var pkg = (() => {
|
|
265
|
+
try {
|
|
266
|
+
return JSON.parse(readFileSync2(new URL("../package.json", import.meta.url), "utf8"));
|
|
267
|
+
} catch {
|
|
268
|
+
return { name: "ai4l-library", version: "0.0.0" };
|
|
269
|
+
}
|
|
270
|
+
})();
|
|
271
|
+
var INSTRUCTIONS = `ai4l-library builds a local library of the scientific papers cited by evidence reviews. Given a paper ID ("type:value", e.g. pmid:30955454; bare digits count as pmid) it fetches the paper's record and stores it as files under the library directory. Nothing flows through your context: the server writes to disk itself and returns only a small JSON status per call. One ID per call \u2014 no batches.
|
|
272
|
+
|
|
273
|
+
Tools:
|
|
274
|
+
- library_add(id, url?) \u2014 build the entry for one ID and return its status. Idempotent: an existing entry is returned without any network call.
|
|
275
|
+
- library_status(id) \u2014 read-only: the entry's status, or level "missing".
|
|
276
|
+
- get_version() \u2014 package name, version and the resolved library directory.
|
|
277
|
+
|
|
278
|
+
Status JSON fields: requested, type, id, entry, entry_dir, library_dir, pmid, pmcid, doi, title, first_author, year, publication_types, retracted, level, version, retrieved_by, retrieved_date, attempts, files, and error when the call could not produce an entry ("invalid id", "type not supported yet", "pmid not found", or a fetch failure).
|
|
279
|
+
|
|
280
|
+
v0.1: pmid: IDs only (other types return error "type not supported yet"); the pubmed tier only, so every entry is level "abstract" (efetch XML, abstract Markdown, meta); url is accepted but ignored.`;
|
|
281
|
+
var idSchema = z.string().describe('Paper ID as "type:value", e.g. "pmid:30955454"; bare digits count as pmid.');
|
|
282
|
+
var json = (s) => ({ content: [{ type: "text", text: JSON.stringify(s, null, 2) }] });
|
|
283
|
+
function registerTools(server2) {
|
|
284
|
+
server2.tool(
|
|
285
|
+
"library_add",
|
|
286
|
+
"Build the library entry for one paper ID and return its status JSON. v0.1: pmid: only, pubmed tier only (level abstract). Idempotent \u2014 an existing entry is returned without refetching.",
|
|
287
|
+
{ id: idSchema, url: z.string().optional().describe("A download location obtained elsewhere (ignored in v0.1).") },
|
|
288
|
+
async ({ id }) => {
|
|
289
|
+
const parsed = parseId(id);
|
|
290
|
+
const status = emptyStatus(parsed.requested, parsed.type, parsed.id, LIBRARY_DIR);
|
|
291
|
+
if (parsed.error) return json({ ...status, error: parsed.error });
|
|
292
|
+
const paths = entryPaths(LIBRARY_DIR, parsed.type, parsed.id);
|
|
293
|
+
if (entryExists(paths)) return json(readStatus(paths, status));
|
|
294
|
+
let xml;
|
|
295
|
+
try {
|
|
296
|
+
xml = await fetchPubmedXml(parsed.id);
|
|
297
|
+
} catch (e) {
|
|
298
|
+
return json({ ...status, error: e instanceof Error ? e.message : String(e) });
|
|
299
|
+
}
|
|
300
|
+
const record = parsePubmedXml(xml);
|
|
301
|
+
if (!record) return json({ ...status, error: "pmid not found" });
|
|
302
|
+
writeEntry(paths, record, xml, `${pkg.name} ${pkg.version}`);
|
|
303
|
+
return json(readStatus(paths, status));
|
|
304
|
+
}
|
|
305
|
+
);
|
|
306
|
+
server2.tool(
|
|
307
|
+
"library_status",
|
|
308
|
+
'Read-only status of the library entry for one paper ID: the same status JSON as library_add, or level "missing" when there is no entry. Never fetches.',
|
|
309
|
+
{ id: idSchema },
|
|
310
|
+
async ({ id }) => {
|
|
311
|
+
const parsed = parseId(id);
|
|
312
|
+
const status = emptyStatus(parsed.requested, parsed.type, parsed.id, LIBRARY_DIR);
|
|
313
|
+
if (parsed.error) return json({ ...status, error: parsed.error });
|
|
314
|
+
const paths = entryPaths(LIBRARY_DIR, parsed.type, parsed.id);
|
|
315
|
+
if (entryExists(paths)) return json(readStatus(paths, status));
|
|
316
|
+
return json({ ...status, entry: paths.entry, entry_dir: paths.entryDir, level: "missing" });
|
|
317
|
+
}
|
|
318
|
+
);
|
|
319
|
+
server2.tool(
|
|
320
|
+
"get_version",
|
|
321
|
+
"Get the running ai4l-library MCP server's package name and version, and the resolved library directory. Useful to confirm which build is loaded.",
|
|
322
|
+
{},
|
|
323
|
+
async () => ({ content: [{ type: "text", text: `${pkg.name} v${pkg.version} \xB7 library_dir: ${LIBRARY_DIR}` }] })
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// src/index.ts
|
|
328
|
+
var server = new McpServer(
|
|
329
|
+
{ name: pkg.name, version: pkg.version },
|
|
330
|
+
{ instructions: INSTRUCTIONS }
|
|
331
|
+
);
|
|
332
|
+
registerTools(server);
|
|
333
|
+
var transport = new StdioServerTransport();
|
|
334
|
+
await server.connect(transport);
|
package/package.json
CHANGED
|
@@ -1,6 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai4l-library-dev",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Internal development build of ai4l-library. Not for public use.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"ai4l-library-dev": "dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist"
|
|
11
|
+
],
|
|
12
|
+
"engines": {
|
|
13
|
+
"node": ">=18"
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
17
|
+
"fast-xml-parser": "^5.11.2",
|
|
18
|
+
"zod": "^3.0.0"
|
|
19
|
+
},
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"private": false
|
|
22
|
+
}
|