ultralytics-mcp 0.1.0 → 0.1.2
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 +22 -3
- package/dist/client.js +23 -0
- package/dist/tools/datasets.js +154 -0
- package/dist/tools/index.js +64 -4
- package/dist/tools/projects.js +32 -0
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -1,9 +1,28 @@
|
|
|
1
1
|
# Ultralytics Platform MCP
|
|
2
2
|
|
|
3
|
-
MCP server for the Ultralytics Platform
|
|
3
|
+
MCP server for the [Ultralytics Platform](https://platform.ultralytics.com).
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
> Independent community project. Not affiliated with or endorsed by Ultralytics.
|
|
6
|
+
|
|
7
|
+
Current milestone: read, monitor, predict, export, and initial project and
|
|
8
|
+
dataset lifecycle tools are available. Additional resource-management tools
|
|
9
|
+
land incrementally from here.
|
|
10
|
+
|
|
11
|
+
## Tools (20)
|
|
12
|
+
|
|
13
|
+
| Tool | Description |
|
|
14
|
+
| --- | --- |
|
|
15
|
+
| `projects_list` / `projects_get` | Browse projects |
|
|
16
|
+
| `projects_create` / `projects_delete` | Create / soft-delete projects |
|
|
17
|
+
| `datasets_list` / `datasets_get` / `datasets_create` / `datasets_delete` / `dataset_ingest` / `dataset_upload_file` | Browse / create / soft-delete datasets, start remote ingest jobs, and upload archive files |
|
|
18
|
+
| `models_list` / `models_get` | Browse trained models and metrics |
|
|
19
|
+
| `training_monitor` | Status, progress, and latest metrics |
|
|
20
|
+
| `model_predict` | Run inference on an image URL or base64 source |
|
|
21
|
+
| `model_download` | Download a model weight file to a local path |
|
|
22
|
+
| `gpu_availability` | Cloud GPU stock status |
|
|
23
|
+
| `exports_list` / `export_status` | List / check export jobs |
|
|
24
|
+
| `export_create` | Create an export job — **requires `confirm_cost: true`** |
|
|
25
|
+
| `training_start` | Start cloud training — **requires `confirm_cost: true`** |
|
|
7
26
|
|
|
8
27
|
## Development
|
|
9
28
|
|
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. */
|
|
@@ -63,6 +65,10 @@ export class UltralyticsClient {
|
|
|
63
65
|
retryOn429: options.retryOn429 ?? false,
|
|
64
66
|
});
|
|
65
67
|
}
|
|
68
|
+
/** DELETE requests are state-changing and do not retry 429 responses. */
|
|
69
|
+
async delete(path) {
|
|
70
|
+
return this.request("DELETE", path, { retryOn429: false });
|
|
71
|
+
}
|
|
66
72
|
/** Download bytes from a signed URL WITHOUT forwarding API credentials. */
|
|
67
73
|
async downloadBytes(url) {
|
|
68
74
|
let attempt = 0;
|
|
@@ -83,6 +89,23 @@ export class UltralyticsClient {
|
|
|
83
89
|
throw new Error("unreachable");
|
|
84
90
|
}
|
|
85
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
|
+
}
|
|
86
109
|
// -- internals -----------------------------------------------------------
|
|
87
110
|
buildUrl(path, params) {
|
|
88
111
|
const suffix = path.startsWith("/") ? path : `/${path}`;
|
package/dist/tools/datasets.js
CHANGED
|
@@ -1,6 +1,61 @@
|
|
|
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";
|
|
6
|
+
const DATASET_TASKS = new Set([
|
|
7
|
+
"detect",
|
|
8
|
+
"segment",
|
|
9
|
+
"semantic",
|
|
10
|
+
"classify",
|
|
11
|
+
"pose",
|
|
12
|
+
"obb",
|
|
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
|
+
];
|
|
23
|
+
function resourceId(item, fallback) {
|
|
24
|
+
const value = item._id ?? item.id ?? item.projectId ?? item.datasetId;
|
|
25
|
+
return String(value ?? fallback ?? "None");
|
|
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
|
+
}
|
|
4
59
|
/** List datasets in the workspace, optionally filtered by username. */
|
|
5
60
|
export async function datasetsList(client, username) {
|
|
6
61
|
const data = await client.get("/datasets", username ? { username } : undefined);
|
|
@@ -28,3 +83,102 @@ export async function datasetsGet(client, dataset) {
|
|
|
28
83
|
data: item,
|
|
29
84
|
};
|
|
30
85
|
}
|
|
86
|
+
/** Create a dataset. */
|
|
87
|
+
export async function datasetsCreate(client, options) {
|
|
88
|
+
if (!DATASET_TASKS.has(options.task)) {
|
|
89
|
+
const allowed = Array.from(DATASET_TASKS).sort().join(", ");
|
|
90
|
+
throw new Error(`Unsupported dataset task '${options.task}'. Expected one of: ${allowed}.`);
|
|
91
|
+
}
|
|
92
|
+
if (!options.slug.trim()) {
|
|
93
|
+
throw new Error("`slug` is required.");
|
|
94
|
+
}
|
|
95
|
+
const payload = {
|
|
96
|
+
name: options.name,
|
|
97
|
+
task: options.task,
|
|
98
|
+
slug: options.slug,
|
|
99
|
+
};
|
|
100
|
+
if (options.description !== undefined) {
|
|
101
|
+
payload.description = options.description;
|
|
102
|
+
}
|
|
103
|
+
if (options.visibility !== undefined) {
|
|
104
|
+
payload.visibility = options.visibility;
|
|
105
|
+
}
|
|
106
|
+
if (options.classNames !== undefined) {
|
|
107
|
+
payload.classNames = options.classNames;
|
|
108
|
+
}
|
|
109
|
+
const data = await client.postJson("/datasets", payload);
|
|
110
|
+
const record = asRecord(data);
|
|
111
|
+
const item = asRecord("dataset" in record ? record.dataset : data);
|
|
112
|
+
const id = resourceId(item);
|
|
113
|
+
const slug = item.slug ?? options.slug;
|
|
114
|
+
const task = item.task ?? options.task;
|
|
115
|
+
return {
|
|
116
|
+
summary: `Created dataset ${id} slug=${String(slug)} task=${String(task)}.`,
|
|
117
|
+
data: item,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
/** Soft-delete a dataset by id, slug, username/slug, or dataset ul:// URI. */
|
|
121
|
+
export async function datasetsDelete(client, dataset) {
|
|
122
|
+
const datasetId = await resolveDataset(client, dataset);
|
|
123
|
+
const data = await client.delete(`/datasets/${datasetId}`);
|
|
124
|
+
return {
|
|
125
|
+
summary: `Deleted dataset ${datasetId} (soft delete).`,
|
|
126
|
+
data: { id: datasetId, response: data },
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
/** Start a remote URL ingest job for an existing dataset. */
|
|
130
|
+
export async function datasetsIngest(client, options) {
|
|
131
|
+
if (!options.sourceUrl.trim()) {
|
|
132
|
+
throw new Error("`sourceUrl` is required.");
|
|
133
|
+
}
|
|
134
|
+
validateTargetSplit(options.targetSplit);
|
|
135
|
+
const datasetId = await resolveDataset(client, options.dataset);
|
|
136
|
+
const payload = {
|
|
137
|
+
datasetId,
|
|
138
|
+
sourceUrl: options.sourceUrl,
|
|
139
|
+
};
|
|
140
|
+
if (options.targetSplit !== undefined) {
|
|
141
|
+
payload.targetSplit = options.targetSplit;
|
|
142
|
+
}
|
|
143
|
+
const data = await client.postJson("/datasets/ingest", payload);
|
|
144
|
+
const item = asRecord(data);
|
|
145
|
+
const jobId = item.jobId ?? item.id ?? "None";
|
|
146
|
+
return {
|
|
147
|
+
summary: `Started dataset ingest job ${String(jobId)} for dataset ${datasetId}.`,
|
|
148
|
+
data: item,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
/** Upload a local dataset archive file, then start ingest for that upload. */
|
|
152
|
+
export async function datasetUploadFile(client, options) {
|
|
153
|
+
validateTargetSplit(options.targetSplit);
|
|
154
|
+
const meta = await datasetUploadFileMeta(options.filePath);
|
|
155
|
+
const datasetId = await resolveDataset(client, options.dataset);
|
|
156
|
+
const content = await readFile(options.filePath);
|
|
157
|
+
const signed = asRecord(await client.postJson("/upload/signed-url", {
|
|
158
|
+
assetType: "datasets",
|
|
159
|
+
assetId: datasetId,
|
|
160
|
+
filename: meta.filename,
|
|
161
|
+
contentType: meta.contentType,
|
|
162
|
+
totalBytes: meta.totalBytes,
|
|
163
|
+
}));
|
|
164
|
+
const uploadUrl = String(signed.url ?? "");
|
|
165
|
+
const sessionId = String(signed.sessionId ?? "");
|
|
166
|
+
await client.uploadBytes(uploadUrl, content, meta.contentType);
|
|
167
|
+
await client.postJson("/upload/complete", { sessionId });
|
|
168
|
+
const ingestPayload = { datasetId, sessionId };
|
|
169
|
+
if (options.targetSplit !== undefined) {
|
|
170
|
+
ingestPayload.targetSplit = options.targetSplit;
|
|
171
|
+
}
|
|
172
|
+
const ingest = asRecord(await client.postJson("/datasets/ingest", ingestPayload));
|
|
173
|
+
const jobId = ingest.jobId ?? ingest.id ?? "None";
|
|
174
|
+
return {
|
|
175
|
+
summary: `Uploaded ${meta.filename} (${meta.totalBytes} bytes) and started dataset ingest job ${String(jobId)}.`,
|
|
176
|
+
data: {
|
|
177
|
+
datasetId,
|
|
178
|
+
filename: meta.filename,
|
|
179
|
+
bytes: meta.totalBytes,
|
|
180
|
+
sessionId,
|
|
181
|
+
ingest,
|
|
182
|
+
},
|
|
183
|
+
};
|
|
184
|
+
}
|
package/dist/tools/index.js
CHANGED
|
@@ -7,28 +7,34 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { z } from "zod";
|
|
9
9
|
import { toMcpTextResult } from "../tool-result.js";
|
|
10
|
-
import { datasetsGet, datasetsList } from "./datasets.js";
|
|
10
|
+
import { datasetsCreate, datasetsDelete, datasetsGet, datasetsIngest, datasetsList, datasetUploadFile, } 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";
|
|
14
14
|
import { modelsGet, modelsList } from "./models.js";
|
|
15
15
|
import { modelPredict } from "./predict.js";
|
|
16
|
-
import { projectsGet, projectsList } from "./projects.js";
|
|
16
|
+
import { projectsCreate, projectsDelete, projectsGet, projectsList, } from "./projects.js";
|
|
17
17
|
import { trainingMonitor, trainingStart } from "./training.js";
|
|
18
|
-
export { datasetsGet, datasetsList } from "./datasets.js";
|
|
18
|
+
export { datasetsCreate, datasetsDelete, datasetsGet, datasetsIngest, datasetsList, datasetUploadFile, } 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";
|
|
22
22
|
export { modelsGet, modelsList } from "./models.js";
|
|
23
23
|
export { modelPredict } from "./predict.js";
|
|
24
|
-
export { projectsGet, projectsList } from "./projects.js";
|
|
24
|
+
export { projectsCreate, projectsDelete, projectsGet, projectsList, } from "./projects.js";
|
|
25
25
|
export { trainingMonitor, trainingStart } from "./training.js";
|
|
26
26
|
/** Names of the read-only tools registered by `registerReadTools`. */
|
|
27
27
|
export const READ_TOOL_NAMES = [
|
|
28
28
|
"projects_list",
|
|
29
29
|
"projects_get",
|
|
30
|
+
"projects_create",
|
|
31
|
+
"projects_delete",
|
|
30
32
|
"datasets_list",
|
|
31
33
|
"datasets_get",
|
|
34
|
+
"datasets_create",
|
|
35
|
+
"datasets_delete",
|
|
36
|
+
"dataset_ingest",
|
|
37
|
+
"dataset_upload_file",
|
|
32
38
|
"models_list",
|
|
33
39
|
"models_get",
|
|
34
40
|
"gpu_availability",
|
|
@@ -43,6 +49,18 @@ export function registerReadTools(server, getClient) {
|
|
|
43
49
|
description: "Get details for one project by id, slug, username/slug, or project ul:// URI.",
|
|
44
50
|
inputSchema: { project: z.string() },
|
|
45
51
|
}, async ({ project }) => toMcpTextResult(await projectsGet(getClient(), project)));
|
|
52
|
+
server.registerTool("projects_create", {
|
|
53
|
+
description: "Create a project in your Ultralytics workspace.",
|
|
54
|
+
inputSchema: {
|
|
55
|
+
name: z.string(),
|
|
56
|
+
slug: z.string().optional(),
|
|
57
|
+
description: z.string().optional(),
|
|
58
|
+
},
|
|
59
|
+
}, async ({ name, slug, description }) => toMcpTextResult(await projectsCreate(getClient(), { name, slug, description })));
|
|
60
|
+
server.registerTool("projects_delete", {
|
|
61
|
+
description: "Soft-delete a project by id, slug, username/slug, or project ul:// URI.",
|
|
62
|
+
inputSchema: { project: z.string() },
|
|
63
|
+
}, async ({ project }) => toMcpTextResult(await projectsDelete(getClient(), project)));
|
|
46
64
|
server.registerTool("datasets_list", {
|
|
47
65
|
description: "List datasets in your Ultralytics workspace.",
|
|
48
66
|
inputSchema: { username: z.string().optional() },
|
|
@@ -51,6 +69,48 @@ export function registerReadTools(server, getClient) {
|
|
|
51
69
|
description: "Get details for one dataset by id, slug, username/slug, or dataset ul:// URI.",
|
|
52
70
|
inputSchema: { dataset: z.string() },
|
|
53
71
|
}, async ({ dataset }) => toMcpTextResult(await datasetsGet(getClient(), dataset)));
|
|
72
|
+
server.registerTool("datasets_create", {
|
|
73
|
+
description: "Create a dataset in your Ultralytics workspace.",
|
|
74
|
+
inputSchema: {
|
|
75
|
+
name: z.string(),
|
|
76
|
+
task: z.string(),
|
|
77
|
+
slug: z.string(),
|
|
78
|
+
description: z.string().optional(),
|
|
79
|
+
visibility: z.string().optional(),
|
|
80
|
+
classNames: z.array(z.string()).optional(),
|
|
81
|
+
},
|
|
82
|
+
}, async ({ name, task, slug, description, visibility, classNames }) => toMcpTextResult(await datasetsCreate(getClient(), {
|
|
83
|
+
name,
|
|
84
|
+
task,
|
|
85
|
+
slug,
|
|
86
|
+
description,
|
|
87
|
+
visibility,
|
|
88
|
+
classNames,
|
|
89
|
+
})));
|
|
90
|
+
server.registerTool("datasets_delete", {
|
|
91
|
+
description: "Soft-delete a dataset by id, slug, username/slug, or dataset ul:// URI.",
|
|
92
|
+
inputSchema: { dataset: z.string() },
|
|
93
|
+
}, async ({ dataset }) => toMcpTextResult(await datasetsDelete(getClient(), dataset)));
|
|
94
|
+
server.registerTool("dataset_ingest", {
|
|
95
|
+
description: "Start a remote URL ingest job for an existing dataset.",
|
|
96
|
+
inputSchema: {
|
|
97
|
+
dataset: z.string(),
|
|
98
|
+
sourceUrl: z.string(),
|
|
99
|
+
targetSplit: z.string().optional(),
|
|
100
|
+
},
|
|
101
|
+
}, async ({ dataset, sourceUrl, targetSplit }) => toMcpTextResult(await datasetsIngest(getClient(), { dataset, sourceUrl, targetSplit })));
|
|
102
|
+
server.registerTool("dataset_upload_file", {
|
|
103
|
+
description: "Upload a local dataset archive file and start ingest for an existing dataset.",
|
|
104
|
+
inputSchema: {
|
|
105
|
+
dataset: z.string(),
|
|
106
|
+
file_path: z.string(),
|
|
107
|
+
targetSplit: z.string().optional(),
|
|
108
|
+
},
|
|
109
|
+
}, async ({ dataset, file_path, targetSplit }) => toMcpTextResult(await datasetUploadFile(getClient(), {
|
|
110
|
+
dataset,
|
|
111
|
+
filePath: file_path,
|
|
112
|
+
targetSplit,
|
|
113
|
+
})));
|
|
54
114
|
server.registerTool("models_list", {
|
|
55
115
|
description: "List models in a project by project id, slug, username/slug, or project ul:// URI.",
|
|
56
116
|
inputSchema: { project: z.string() },
|
package/dist/tools/projects.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
/** Read-only project tools. */
|
|
2
2
|
import { resolveProject } from "../resolve.js";
|
|
3
3
|
import { asRecord, listField, pyCount, pyField } from "./shared.js";
|
|
4
|
+
function resourceId(item, fallback) {
|
|
5
|
+
const value = item._id ?? item.id ?? item.projectId ?? item.datasetId;
|
|
6
|
+
return String(value ?? fallback ?? "None");
|
|
7
|
+
}
|
|
4
8
|
/** List projects in the workspace, optionally filtered by username. */
|
|
5
9
|
export async function projectsList(client, username) {
|
|
6
10
|
const data = await client.get("/projects", username ? { username } : undefined);
|
|
@@ -27,3 +31,31 @@ export async function projectsGet(client, project) {
|
|
|
27
31
|
data: item,
|
|
28
32
|
};
|
|
29
33
|
}
|
|
34
|
+
/** Create a project. */
|
|
35
|
+
export async function projectsCreate(client, options) {
|
|
36
|
+
const payload = { name: options.name };
|
|
37
|
+
if (options.slug !== undefined) {
|
|
38
|
+
payload.slug = options.slug;
|
|
39
|
+
}
|
|
40
|
+
if (options.description !== undefined) {
|
|
41
|
+
payload.description = options.description;
|
|
42
|
+
}
|
|
43
|
+
const data = await client.postJson("/projects", payload);
|
|
44
|
+
const record = asRecord(data);
|
|
45
|
+
const item = asRecord("project" in record ? record.project : data);
|
|
46
|
+
const id = resourceId(item);
|
|
47
|
+
const slug = item.slug ?? options.slug ?? "None";
|
|
48
|
+
return {
|
|
49
|
+
summary: `Created project ${id} slug=${String(slug)}.`,
|
|
50
|
+
data: item,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/** Soft-delete a project by id, slug, username/slug, or project ul:// URI. */
|
|
54
|
+
export async function projectsDelete(client, project) {
|
|
55
|
+
const projectId = await resolveProject(client, project);
|
|
56
|
+
const data = await client.delete(`/projects/${projectId}`);
|
|
57
|
+
return {
|
|
58
|
+
summary: `Deleted project ${projectId} (soft delete).`,
|
|
59
|
+
data: { id: projectId, response: data },
|
|
60
|
+
};
|
|
61
|
+
}
|
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.2",
|
|
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"
|
|
@@ -20,7 +20,8 @@
|
|
|
20
20
|
"url": "https://github.com/amanharshx/ultralytics-mcp/issues"
|
|
21
21
|
},
|
|
22
22
|
"publishConfig": {
|
|
23
|
-
"access": "public"
|
|
23
|
+
"access": "public",
|
|
24
|
+
"provenance": true
|
|
24
25
|
},
|
|
25
26
|
"engines": {
|
|
26
27
|
"node": ">=20"
|