ultralytics-mcp 0.1.1 → 0.1.3
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/README.md +4 -2
- package/dist/client.js +19 -0
- package/dist/tools/datasets.js +200 -0
- package/dist/tools/index.js +63 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -8,18 +8,20 @@ Current milestone: read, monitor, predict, export, and initial project and
|
|
|
8
8
|
dataset lifecycle tools are available. Additional resource-management tools
|
|
9
9
|
land incrementally from here.
|
|
10
10
|
|
|
11
|
-
## Tools (
|
|
11
|
+
## Tools (23)
|
|
12
12
|
|
|
13
13
|
| Tool | Description |
|
|
14
14
|
| --- | --- |
|
|
15
15
|
| `projects_list` / `projects_get` | Browse projects |
|
|
16
16
|
| `projects_create` / `projects_delete` | Create / soft-delete projects |
|
|
17
|
-
| `datasets_list` / `datasets_get` / `datasets_create` / `datasets_delete` | Browse / create / soft-delete datasets |
|
|
17
|
+
| `datasets_list` / `datasets_get` / `datasets_create` / `datasets_delete` / `dataset_images_list` / `dataset_ingest` / `dataset_upload_file` | Browse / create / soft-delete datasets, inspect images, start remote ingest jobs, and upload archive files |
|
|
18
18
|
| `models_list` / `models_get` | Browse trained models and metrics |
|
|
19
19
|
| `training_monitor` | Status, progress, and latest metrics |
|
|
20
20
|
| `model_predict` | Run inference on an image URL or base64 source |
|
|
21
21
|
| `model_download` | Download a model weight file to a local path |
|
|
22
22
|
| `gpu_availability` | Cloud GPU stock status |
|
|
23
|
+
| `dataset_export` | Get export link for latest or frozen dataset version |
|
|
24
|
+
| `dataset_version_create` | Create a frozen dataset version snapshot |
|
|
23
25
|
| `exports_list` / `export_status` | List / check export jobs |
|
|
24
26
|
| `export_create` | Create an export job — **requires `confirm_cost: true`** |
|
|
25
27
|
| `training_start` | Start cloud training — **requires `confirm_cost: true`** |
|
package/dist/client.js
CHANGED
|
@@ -20,6 +20,7 @@ export class UltralyticsClient {
|
|
|
20
20
|
maxRetries;
|
|
21
21
|
fetchImpl;
|
|
22
22
|
downloadFetchImpl;
|
|
23
|
+
uploadFetchImpl;
|
|
23
24
|
constructor(options = {}) {
|
|
24
25
|
this.baseUrl = (options.baseUrl ?? getApiBase()).replace(/\/+$/, "");
|
|
25
26
|
this.apiKey = options.apiKey ?? getApiKey();
|
|
@@ -27,6 +28,7 @@ export class UltralyticsClient {
|
|
|
27
28
|
this.maxRetries = options.maxRetries ?? 3;
|
|
28
29
|
this.fetchImpl = options.fetchImpl ?? fetch;
|
|
29
30
|
this.downloadFetchImpl = options.downloadFetchImpl ?? fetch;
|
|
31
|
+
this.uploadFetchImpl = options.uploadFetchImpl ?? fetch;
|
|
30
32
|
}
|
|
31
33
|
// -- public verbs --------------------------------------------------------
|
|
32
34
|
/** GET requests are idempotent and retry 429 responses. */
|
|
@@ -87,6 +89,23 @@ export class UltralyticsClient {
|
|
|
87
89
|
throw new Error("unreachable");
|
|
88
90
|
}
|
|
89
91
|
}
|
|
92
|
+
/** Upload bytes to a signed URL WITHOUT forwarding API credentials. */
|
|
93
|
+
async uploadBytes(url, content, contentType) {
|
|
94
|
+
const bytes = new Uint8Array(content.byteLength);
|
|
95
|
+
bytes.set(content);
|
|
96
|
+
const response = await this.fetchWithTimeout(this.uploadFetchImpl, url, {
|
|
97
|
+
method: "PUT",
|
|
98
|
+
headers: {
|
|
99
|
+
Accept: "*/*",
|
|
100
|
+
"Content-Type": contentType,
|
|
101
|
+
},
|
|
102
|
+
body: bytes,
|
|
103
|
+
});
|
|
104
|
+
if (response.ok) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
await this.handle(response, url);
|
|
108
|
+
}
|
|
90
109
|
// -- internals -----------------------------------------------------------
|
|
91
110
|
buildUrl(path, params) {
|
|
92
111
|
const suffix = path.startsWith("/") ? path : `/${path}`;
|
package/dist/tools/datasets.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
/** Read-only dataset tools. */
|
|
2
|
+
import { readFile, stat } from "node:fs/promises";
|
|
3
|
+
import { basename } from "node:path";
|
|
2
4
|
import { resolveDataset } from "../resolve.js";
|
|
3
5
|
import { asRecord, listField, pyCount, pyField } from "./shared.js";
|
|
4
6
|
const DATASET_TASKS = new Set([
|
|
@@ -9,10 +11,51 @@ const DATASET_TASKS = new Set([
|
|
|
9
11
|
"pose",
|
|
10
12
|
"obb",
|
|
11
13
|
]);
|
|
14
|
+
const TARGET_SPLITS = new Set(["train", "val", "test"]);
|
|
15
|
+
const MAX_UPLOAD_BYTES = 10 * 1024 * 1024 * 1024;
|
|
16
|
+
const UPLOAD_TYPES = [
|
|
17
|
+
[".tar.gz", "application/gzip"],
|
|
18
|
+
[".zip", "application/zip"],
|
|
19
|
+
[".tar", "application/x-tar"],
|
|
20
|
+
[".tgz", "application/gzip"],
|
|
21
|
+
[".ndjson", "application/x-ndjson"],
|
|
22
|
+
];
|
|
12
23
|
function resourceId(item, fallback) {
|
|
13
24
|
const value = item._id ?? item.id ?? item.projectId ?? item.datasetId;
|
|
14
25
|
return String(value ?? fallback ?? "None");
|
|
15
26
|
}
|
|
27
|
+
function validateTargetSplit(targetSplit) {
|
|
28
|
+
if (targetSplit !== undefined && !TARGET_SPLITS.has(targetSplit)) {
|
|
29
|
+
const allowed = Array.from(TARGET_SPLITS).sort().join(", ");
|
|
30
|
+
throw new Error(`Unsupported targetSplit '${targetSplit}'. Expected one of: ${allowed}.`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
async function datasetUploadFileMeta(filePath) {
|
|
34
|
+
if (!filePath.trim()) {
|
|
35
|
+
throw new Error("`filePath` is required.");
|
|
36
|
+
}
|
|
37
|
+
const info = await stat(filePath).catch(() => null);
|
|
38
|
+
if (info === null) {
|
|
39
|
+
throw new Error(`Upload file does not exist: ${filePath}`);
|
|
40
|
+
}
|
|
41
|
+
if (!info.isFile()) {
|
|
42
|
+
throw new Error(`Upload path is not a file: ${filePath}`);
|
|
43
|
+
}
|
|
44
|
+
if (info.size >= MAX_UPLOAD_BYTES) {
|
|
45
|
+
throw new Error("Upload file must be smaller than 10 GB.");
|
|
46
|
+
}
|
|
47
|
+
const filename = basename(filePath);
|
|
48
|
+
const lower = filename.toLowerCase();
|
|
49
|
+
const matched = UPLOAD_TYPES.find(([suffix]) => lower.endsWith(suffix));
|
|
50
|
+
if (!matched) {
|
|
51
|
+
throw new Error("Unsupported dataset upload file type. Expected one of: .zip, .tar, .tar.gz, .tgz, .ndjson.");
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
filename,
|
|
55
|
+
contentType: matched[1],
|
|
56
|
+
totalBytes: info.size,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
16
59
|
/** List datasets in the workspace, optionally filtered by username. */
|
|
17
60
|
export async function datasetsList(client, username) {
|
|
18
61
|
const data = await client.get("/datasets", username ? { username } : undefined);
|
|
@@ -74,6 +117,72 @@ export async function datasetsCreate(client, options) {
|
|
|
74
117
|
data: item,
|
|
75
118
|
};
|
|
76
119
|
}
|
|
120
|
+
/** List images in a dataset with optional filtering. */
|
|
121
|
+
export async function datasetImagesList(client, options) {
|
|
122
|
+
if (options.split !== undefined && !TARGET_SPLITS.has(options.split)) {
|
|
123
|
+
const allowed = Array.from(TARGET_SPLITS).sort().join(", ");
|
|
124
|
+
throw new Error(`Unsupported split '${options.split}'. Expected one of: ${allowed}.`);
|
|
125
|
+
}
|
|
126
|
+
if (options.limit !== undefined) {
|
|
127
|
+
if (options.limit <= 0) {
|
|
128
|
+
throw new Error("`limit` must be greater than 0.");
|
|
129
|
+
}
|
|
130
|
+
if (options.limit > 5000) {
|
|
131
|
+
throw new Error("`limit` must be at most 5000.");
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if (options.offset !== undefined && options.offset < 0) {
|
|
135
|
+
throw new Error("`offset` must be greater than or equal to 0.");
|
|
136
|
+
}
|
|
137
|
+
const datasetId = await resolveDataset(client, options.dataset);
|
|
138
|
+
const params = {};
|
|
139
|
+
if (options.split !== undefined) {
|
|
140
|
+
params.split = options.split;
|
|
141
|
+
}
|
|
142
|
+
if (options.search !== undefined) {
|
|
143
|
+
params.search = options.search;
|
|
144
|
+
}
|
|
145
|
+
if (options.hasLabel !== undefined) {
|
|
146
|
+
params.hasLabel = options.hasLabel;
|
|
147
|
+
}
|
|
148
|
+
if (options.classIds && options.classIds.length > 0) {
|
|
149
|
+
params.classIds = options.classIds.join(",");
|
|
150
|
+
}
|
|
151
|
+
if (options.limit !== undefined) {
|
|
152
|
+
params.limit = options.limit;
|
|
153
|
+
}
|
|
154
|
+
if (options.offset !== undefined) {
|
|
155
|
+
params.offset = options.offset;
|
|
156
|
+
}
|
|
157
|
+
if (options.includeImageUrls !== undefined) {
|
|
158
|
+
params.includeImageUrls = options.includeImageUrls;
|
|
159
|
+
}
|
|
160
|
+
const data = await client.get(`/datasets/${datasetId}/images`, Object.keys(params).length > 0 ? params : undefined);
|
|
161
|
+
const record = asRecord(data);
|
|
162
|
+
const images = listField(data, "images").map((image) => ({
|
|
163
|
+
id: image._id ?? image.id ?? null,
|
|
164
|
+
name: image.name ?? null,
|
|
165
|
+
ext: image.ext ?? null,
|
|
166
|
+
split: image.split ?? null,
|
|
167
|
+
width: image.width ?? null,
|
|
168
|
+
height: image.height ?? null,
|
|
169
|
+
labelCount: image.labelCount ?? null,
|
|
170
|
+
bytes: image.bytes ?? null,
|
|
171
|
+
...(image.imageUrl !== undefined ? { imageUrl: image.imageUrl } : {}),
|
|
172
|
+
...(image.thumbnailUrl !== undefined
|
|
173
|
+
? { thumbnailUrl: image.thumbnailUrl }
|
|
174
|
+
: {}),
|
|
175
|
+
}));
|
|
176
|
+
return {
|
|
177
|
+
summary: `${images.length} image(s) (total ${String(record.total ?? null)})`,
|
|
178
|
+
data: {
|
|
179
|
+
total: record.total ?? null,
|
|
180
|
+
hasMore: record.hasMore ?? null,
|
|
181
|
+
nextCursor: record.nextCursor ?? null,
|
|
182
|
+
images,
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
}
|
|
77
186
|
/** Soft-delete a dataset by id, slug, username/slug, or dataset ul:// URI. */
|
|
78
187
|
export async function datasetsDelete(client, dataset) {
|
|
79
188
|
const datasetId = await resolveDataset(client, dataset);
|
|
@@ -83,3 +192,94 @@ export async function datasetsDelete(client, dataset) {
|
|
|
83
192
|
data: { id: datasetId, response: data },
|
|
84
193
|
};
|
|
85
194
|
}
|
|
195
|
+
/** Start a remote URL ingest job for an existing dataset. */
|
|
196
|
+
export async function datasetsIngest(client, options) {
|
|
197
|
+
if (!options.sourceUrl.trim()) {
|
|
198
|
+
throw new Error("`sourceUrl` is required.");
|
|
199
|
+
}
|
|
200
|
+
validateTargetSplit(options.targetSplit);
|
|
201
|
+
const datasetId = await resolveDataset(client, options.dataset);
|
|
202
|
+
const payload = {
|
|
203
|
+
datasetId,
|
|
204
|
+
sourceUrl: options.sourceUrl,
|
|
205
|
+
};
|
|
206
|
+
if (options.targetSplit !== undefined) {
|
|
207
|
+
payload.targetSplit = options.targetSplit;
|
|
208
|
+
}
|
|
209
|
+
const data = await client.postJson("/datasets/ingest", payload);
|
|
210
|
+
const item = asRecord(data);
|
|
211
|
+
const jobId = item.jobId ?? item.id ?? "None";
|
|
212
|
+
return {
|
|
213
|
+
summary: `Started dataset ingest job ${String(jobId)} for dataset ${datasetId}.`,
|
|
214
|
+
data: item,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
/** Upload a local dataset archive file, then start ingest for that upload. */
|
|
218
|
+
export async function datasetUploadFile(client, options) {
|
|
219
|
+
validateTargetSplit(options.targetSplit);
|
|
220
|
+
const meta = await datasetUploadFileMeta(options.filePath);
|
|
221
|
+
const datasetId = await resolveDataset(client, options.dataset);
|
|
222
|
+
const content = await readFile(options.filePath);
|
|
223
|
+
const signed = asRecord(await client.postJson("/upload/signed-url", {
|
|
224
|
+
assetType: "datasets",
|
|
225
|
+
assetId: datasetId,
|
|
226
|
+
filename: meta.filename,
|
|
227
|
+
contentType: meta.contentType,
|
|
228
|
+
totalBytes: meta.totalBytes,
|
|
229
|
+
}));
|
|
230
|
+
const uploadUrl = String(signed.url ?? "");
|
|
231
|
+
const sessionId = String(signed.sessionId ?? "");
|
|
232
|
+
await client.uploadBytes(uploadUrl, content, meta.contentType);
|
|
233
|
+
await client.postJson("/upload/complete", { sessionId });
|
|
234
|
+
const ingestPayload = { datasetId, sessionId };
|
|
235
|
+
if (options.targetSplit !== undefined) {
|
|
236
|
+
ingestPayload.targetSplit = options.targetSplit;
|
|
237
|
+
}
|
|
238
|
+
const ingest = asRecord(await client.postJson("/datasets/ingest", ingestPayload));
|
|
239
|
+
const jobId = ingest.jobId ?? ingest.id ?? "None";
|
|
240
|
+
return {
|
|
241
|
+
summary: `Uploaded ${meta.filename} (${meta.totalBytes} bytes) and started dataset ingest job ${String(jobId)}.`,
|
|
242
|
+
data: {
|
|
243
|
+
datasetId,
|
|
244
|
+
filename: meta.filename,
|
|
245
|
+
bytes: meta.totalBytes,
|
|
246
|
+
sessionId,
|
|
247
|
+
ingest,
|
|
248
|
+
},
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
/** Get dataset export link for latest or one frozen version. */
|
|
252
|
+
export async function datasetExport(client, options) {
|
|
253
|
+
if (options.version !== undefined && options.version <= 0) {
|
|
254
|
+
throw new Error("`version` must be greater than 0.");
|
|
255
|
+
}
|
|
256
|
+
const datasetId = await resolveDataset(client, options.dataset);
|
|
257
|
+
const data = asRecord(await client.get(`/datasets/${datasetId}/export`, options.version !== undefined ? { v: options.version } : undefined));
|
|
258
|
+
const cached = typeof data.cached === "boolean"
|
|
259
|
+
? String(data.cached)
|
|
260
|
+
: String(data.cached ?? null);
|
|
261
|
+
return {
|
|
262
|
+
summary: `Export link for ${options.dataset} ` +
|
|
263
|
+
`(version ${String(options.version ?? "latest")}, cached=${cached})`,
|
|
264
|
+
data: {
|
|
265
|
+
downloadUrl: data.downloadUrl ?? null,
|
|
266
|
+
cached: data.cached ?? null,
|
|
267
|
+
},
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
/** Create frozen dataset export version. */
|
|
271
|
+
export async function datasetVersionCreate(client, options) {
|
|
272
|
+
const datasetId = await resolveDataset(client, options.dataset);
|
|
273
|
+
const payload = {};
|
|
274
|
+
if (options.description !== undefined) {
|
|
275
|
+
payload.description = options.description;
|
|
276
|
+
}
|
|
277
|
+
const data = asRecord(await client.postJson(`/datasets/${datasetId}/export`, payload));
|
|
278
|
+
return {
|
|
279
|
+
summary: `Created dataset version ${String(data.version ?? null)}`,
|
|
280
|
+
data: {
|
|
281
|
+
version: data.version ?? null,
|
|
282
|
+
downloadUrl: data.downloadUrl ?? null,
|
|
283
|
+
},
|
|
284
|
+
};
|
|
285
|
+
}
|
package/dist/tools/index.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { z } from "zod";
|
|
9
9
|
import { toMcpTextResult } from "../tool-result.js";
|
|
10
|
-
import { datasetsCreate, datasetsDelete, datasetsGet, datasetsList, } from "./datasets.js";
|
|
10
|
+
import { datasetExport, datasetImagesList, datasetsCreate, datasetsDelete, datasetsGet, datasetsIngest, datasetsList, datasetUploadFile, datasetVersionCreate, } from "./datasets.js";
|
|
11
11
|
import { modelDownload } from "./downloads.js";
|
|
12
12
|
import { exportCreate, exportStatus, exportsList } from "./exports.js";
|
|
13
13
|
import { gpuAvailability } from "./gpu.js";
|
|
@@ -15,7 +15,7 @@ import { modelsGet, modelsList } from "./models.js";
|
|
|
15
15
|
import { modelPredict } from "./predict.js";
|
|
16
16
|
import { projectsCreate, projectsDelete, projectsGet, projectsList, } from "./projects.js";
|
|
17
17
|
import { trainingMonitor, trainingStart } from "./training.js";
|
|
18
|
-
export { datasetsCreate, datasetsDelete, datasetsGet, datasetsList, } from "./datasets.js";
|
|
18
|
+
export { datasetExport, datasetImagesList, datasetsCreate, datasetsDelete, datasetsGet, datasetsIngest, datasetsList, datasetUploadFile, datasetVersionCreate, } from "./datasets.js";
|
|
19
19
|
export { modelDownload } from "./downloads.js";
|
|
20
20
|
export { exportCreate, exportStatus, exportsList } from "./exports.js";
|
|
21
21
|
export { gpuAvailability } from "./gpu.js";
|
|
@@ -32,7 +32,12 @@ export const READ_TOOL_NAMES = [
|
|
|
32
32
|
"datasets_list",
|
|
33
33
|
"datasets_get",
|
|
34
34
|
"datasets_create",
|
|
35
|
+
"dataset_images_list",
|
|
36
|
+
"dataset_export",
|
|
37
|
+
"dataset_version_create",
|
|
35
38
|
"datasets_delete",
|
|
39
|
+
"dataset_ingest",
|
|
40
|
+
"dataset_upload_file",
|
|
36
41
|
"models_list",
|
|
37
42
|
"models_get",
|
|
38
43
|
"gpu_availability",
|
|
@@ -85,10 +90,66 @@ export function registerReadTools(server, getClient) {
|
|
|
85
90
|
visibility,
|
|
86
91
|
classNames,
|
|
87
92
|
})));
|
|
93
|
+
server.registerTool("dataset_images_list", {
|
|
94
|
+
description: "List images in a dataset with optional filtering.",
|
|
95
|
+
inputSchema: {
|
|
96
|
+
dataset: z.string(),
|
|
97
|
+
split: z.string().optional(),
|
|
98
|
+
search: z.string().optional(),
|
|
99
|
+
hasLabel: z.boolean().optional(),
|
|
100
|
+
classIds: z.array(z.string()).optional(),
|
|
101
|
+
limit: z.number().optional(),
|
|
102
|
+
offset: z.number().optional(),
|
|
103
|
+
includeImageUrls: z.boolean().optional(),
|
|
104
|
+
},
|
|
105
|
+
}, async ({ dataset, split, search, hasLabel, classIds, limit, offset, includeImageUrls, }) => toMcpTextResult(await datasetImagesList(getClient(), {
|
|
106
|
+
dataset,
|
|
107
|
+
split,
|
|
108
|
+
search,
|
|
109
|
+
hasLabel,
|
|
110
|
+
classIds,
|
|
111
|
+
limit,
|
|
112
|
+
offset,
|
|
113
|
+
includeImageUrls,
|
|
114
|
+
})));
|
|
115
|
+
server.registerTool("dataset_export", {
|
|
116
|
+
description: "Get export link for latest or one frozen dataset version.",
|
|
117
|
+
inputSchema: {
|
|
118
|
+
dataset: z.string(),
|
|
119
|
+
version: z.number().optional(),
|
|
120
|
+
},
|
|
121
|
+
}, async ({ dataset, version }) => toMcpTextResult(await datasetExport(getClient(), { dataset, version })));
|
|
122
|
+
server.registerTool("dataset_version_create", {
|
|
123
|
+
description: "Create a frozen dataset version snapshot.",
|
|
124
|
+
inputSchema: {
|
|
125
|
+
dataset: z.string(),
|
|
126
|
+
description: z.string().optional(),
|
|
127
|
+
},
|
|
128
|
+
}, async ({ dataset, description }) => toMcpTextResult(await datasetVersionCreate(getClient(), { dataset, description })));
|
|
88
129
|
server.registerTool("datasets_delete", {
|
|
89
130
|
description: "Soft-delete a dataset by id, slug, username/slug, or dataset ul:// URI.",
|
|
90
131
|
inputSchema: { dataset: z.string() },
|
|
91
132
|
}, async ({ dataset }) => toMcpTextResult(await datasetsDelete(getClient(), dataset)));
|
|
133
|
+
server.registerTool("dataset_ingest", {
|
|
134
|
+
description: "Start a remote URL ingest job for an existing dataset.",
|
|
135
|
+
inputSchema: {
|
|
136
|
+
dataset: z.string(),
|
|
137
|
+
sourceUrl: z.string(),
|
|
138
|
+
targetSplit: z.string().optional(),
|
|
139
|
+
},
|
|
140
|
+
}, async ({ dataset, sourceUrl, targetSplit }) => toMcpTextResult(await datasetsIngest(getClient(), { dataset, sourceUrl, targetSplit })));
|
|
141
|
+
server.registerTool("dataset_upload_file", {
|
|
142
|
+
description: "Upload a local dataset archive file and start ingest for an existing dataset.",
|
|
143
|
+
inputSchema: {
|
|
144
|
+
dataset: z.string(),
|
|
145
|
+
file_path: z.string(),
|
|
146
|
+
targetSplit: z.string().optional(),
|
|
147
|
+
},
|
|
148
|
+
}, async ({ dataset, file_path, targetSplit }) => toMcpTextResult(await datasetUploadFile(getClient(), {
|
|
149
|
+
dataset,
|
|
150
|
+
filePath: file_path,
|
|
151
|
+
targetSplit,
|
|
152
|
+
})));
|
|
92
153
|
server.registerTool("models_list", {
|
|
93
154
|
description: "List models in a project by project id, slug, username/slug, or project ul:// URI.",
|
|
94
155
|
inputSchema: { project: z.string() },
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ultralytics-mcp",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.1.3",
|
|
4
|
+
"description": "MCP for Ultralytics Platform workflows, datasets, training, prediction, and model operations.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"ultralytics-mcp": "./dist/cli.js"
|