@yawlabs/tailscale-mcp 0.21.0 → 0.21.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 +9 -9
- package/bin/tailscale-mcp.mjs +2 -2
- package/dist/index.js +82 -18
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# @yawlabs/tailscale-mcp
|
|
2
2
|
|
|
3
|
+
[](https://yaw.sh/mcp/install?name=Tailscale&command=npx&args=-y%2C%40yawlabs%2Ftailscale-mcp&description=Manage%20your%20Tailscale%20tailnet%20-%20devices%2C%20ACLs%2C%20DNS%2C%20keys&source=https%3A%2F%2Fgithub.com%2FYawLabs%2Ftailscale-mcp)
|
|
4
|
+
|
|
5
|
+
One click adds this to your local Yaw MCP config so it's available in every Yaw Terminal session. Or install manually below.
|
|
6
|
+
|
|
3
7
|
[](https://www.npmjs.com/package/@yawlabs/tailscale-mcp)
|
|
4
8
|
[](https://opensource.org/licenses/MIT)
|
|
5
9
|
[](https://github.com/YawLabs/tailscale-mcp/stargazers)
|
|
@@ -9,10 +13,6 @@
|
|
|
9
13
|
|
|
10
14
|
Built and maintained by [Yaw Labs](https://yaw.sh).
|
|
11
15
|
|
|
12
|
-
[](https://yaw.sh/mcp/install?name=Tailscale&command=npx&args=-y%2C%40yawlabs%2Ftailscale-mcp&description=Manage%20your%20Tailscale%20tailnet%20-%20devices%2C%20ACLs%2C%20DNS%2C%20keys&source=https%3A%2F%2Fgithub.com%2FYawLabs%2Ftailscale-mcp)
|
|
13
|
-
|
|
14
|
-
One click adds this to your local Yaw MCP config so it's available in every Yaw Terminal session. Or install manually below.
|
|
15
|
-
|
|
16
16
|
## What's the point if the API already exists?
|
|
17
17
|
|
|
18
18
|
You could `curl` the Tailscale API. The point isn't replacing `curl` — it's letting an agent compose multi-endpoint workflows in one turn without writing a script:
|
|
@@ -730,13 +730,13 @@ This shows a read-only banner in the Tailscale Admin Console pointing to your re
|
|
|
730
730
|
|
|
731
731
|
## Running on oam.js (optional)
|
|
732
732
|
|
|
733
|
-
[oam.js](https://oamjs.org) runs this server unmodified, and the `tailscale-mcp` command only ever uses the **latest oam release, currently 0.
|
|
733
|
+
[oam.js](https://oamjs.org) runs this server unmodified, and the `tailscale-mcp` command only ever uses the **latest oam release, currently 0.18.0**. Verified against the published oam 0.18.0: full MCP handshake, all 97 admin-API tools plus `tailscale_tool_groups`, all 4 resources, identical error responses, and a clean stdout protocol stream — from the shipped bundle *and* straight from the TypeScript source with no build step.
|
|
734
734
|
|
|
735
|
-
**oam 0.
|
|
735
|
+
**oam 0.18.0 is the minimum.** A floor matters here: releases before 0.9.0 ran `child_process.execFile` arguments through a shell, re-splitting them on whitespace and executing shell metacharacters inside an argument, and this server shells out to the `tailscale` binary across its local-CLI tools, so that was a reachable bug rather than a theoretical one.
|
|
736
736
|
|
|
737
737
|
How the `tailscale-mcp` command (`bin/tailscale-mcp.mjs`) picks a runtime:
|
|
738
738
|
|
|
739
|
-
- **`TAILSCALE_MCP_RUNTIME=auto`** (the default) — if a client already launched it with `oam run` on oam 0.
|
|
739
|
+
- **`TAILSCALE_MCP_RUNTIME=auto`** (the default) — if a client already launched it with `oam run` on oam 0.18.0 or newer, the server runs in that process. Otherwise it uses `OAM_BIN` when that is 0.18.0 or newer, else asks every oam binary it can find — `%LOCALAPPDATA%\oam\bin` then `~/.oam/bin` on Windows, `~/.oam/bin` elsewhere, then `PATH` — for its version and uses the newest at or above the floor (on a tie the installed copy wins). With none, it runs on Node. An oam host older than 0.18.0 never serves the server itself: it hands off to the newest usable oam, or to Node on `PATH`, or exits with an error when there is neither. Whenever it looks for an oam, stderr names an `OAM_BIN` that was passed over and why; the oam binaries it found and passed over are named, each with its reason, only when no usable oam turns up.
|
|
740
740
|
- **`TAILSCALE_MCP_RUNTIME=oam`** — the same, but exit with an error instead of falling back to Node.
|
|
741
741
|
- **`TAILSCALE_MCP_RUNTIME=node`** — always Node: in-process under `npx`, handed off to Node on `PATH` when a client launches the command with `oam run`.
|
|
742
742
|
|
|
@@ -761,7 +761,7 @@ The sandbox is applied by the `tailscale-mcp` command, which spawns a fresh oam
|
|
|
761
761
|
}
|
|
762
762
|
```
|
|
763
763
|
|
|
764
|
-
**Measure startup on your own hardware.** An MCP client cold-starts this server once per session, so startup is the cost that actually gets paid, and on the machine this was measured on node won it — 437ms vs 1554ms for `oam run` over 10 warmed runs (an earlier 5-run round showed 326ms vs 427ms; the box was busy, so treat the magnitude as noisy and the direction as the finding). Those runs used an oam that predates the 0.
|
|
764
|
+
**Measure startup on your own hardware.** An MCP client cold-starts this server once per session, so startup is the cost that actually gets paid, and on the machine this was measured on node won it — 437ms vs 1554ms for `oam run` over 10 warmed runs (an earlier 5-run round showed 326ms vs 427ms; the box was busy, so treat the magnitude as noisy and the direction as the finding). Those runs used an oam that predates the 0.18.0 floor and have not been repeated since, so do not read them as a current ranking.
|
|
765
765
|
|
|
766
766
|
The published `tailscale-mcp` command prefers the newest usable oam it finds (see above). Without oam that costs almost nothing: discovery is file-existence checks only, never a subprocess, and the fallback runs the server inside the Node process npm already started. With oam installed, though, the command boots Node, runs `--version` on every oam binary it found to pick the newest, and only then boots oam, so it is always slower than pointing your client at a runtime directly — the config above for oam, `node /path/to/tailscale-mcp/dist/index.js` for Node. `TAILSCALE_MCP_RUNTIME=node` skips oam entirely.
|
|
767
767
|
|
|
@@ -796,4 +796,4 @@ Found a vulnerability? See [SECURITY.md](SECURITY.md) — please use GitHub's pr
|
|
|
796
796
|
|
|
797
797
|
MIT
|
|
798
798
|
|
|
799
|
-
[](https://x.com/YawLabs)
|
package/bin/tailscale-mcp.mjs
CHANGED
|
@@ -91,7 +91,7 @@
|
|
|
91
91
|
* shipped bundle; keep it in step.
|
|
92
92
|
*
|
|
93
93
|
* MINIMUM OAM VERSION
|
|
94
|
-
* The latest oam release, 0.
|
|
94
|
+
* The latest oam release, 0.18.0 -- bump OAM_MIN when oam ships a newer one.
|
|
95
95
|
* Only the current oam is used and verified; an older one is passed over for a
|
|
96
96
|
* newer oam, or for Node. The floor is not cosmetic: before 0.9.0
|
|
97
97
|
* `child_process.execFile` ran its arguments through a SHELL, `exec` accepted
|
|
@@ -124,7 +124,7 @@ import { delimiter, join } from "node:path";
|
|
|
124
124
|
import { fileURLToPath } from "node:url";
|
|
125
125
|
|
|
126
126
|
/** The latest oam release, and the oldest one used. See MINIMUM OAM VERSION above. */
|
|
127
|
-
const OAM_MIN = [0,
|
|
127
|
+
const OAM_MIN = [0, 18, 0];
|
|
128
128
|
|
|
129
129
|
/**
|
|
130
130
|
* The oldest Node this package supports, matching package.json `engines.node`.
|
package/dist/index.js
CHANGED
|
@@ -4148,13 +4148,14 @@ var require_fast_uri = __commonJS({
|
|
|
4148
4148
|
if (!malformedIPLiteral) {
|
|
4149
4149
|
malformedHost = canonicalizeHost(parsed, options, schemeHandler, isIP2);
|
|
4150
4150
|
}
|
|
4151
|
-
if (
|
|
4152
|
-
|
|
4153
|
-
|
|
4154
|
-
|
|
4155
|
-
parsed.host = reescapeHostDelimiters(host, isIP2);
|
|
4156
|
-
}
|
|
4151
|
+
if (uri.indexOf("%") !== -1 && parsed.host !== void 0 && !malformedIPLiteral) {
|
|
4152
|
+
let host = isIP2 ? parsed.host : normalizePercentEncoding(parsed.host, true);
|
|
4153
|
+
if (!isIP2) {
|
|
4154
|
+
host = normalizePercentEncoding(host.toLowerCase());
|
|
4157
4155
|
}
|
|
4156
|
+
parsed.host = reescapeHostDelimiters(host, isIP2);
|
|
4157
|
+
}
|
|
4158
|
+
if (!schemeHandler || schemeHandler && !schemeHandler.skipNormalize) {
|
|
4158
4159
|
if (parsed.path) {
|
|
4159
4160
|
parsed.path = normalizePathEncoding(parsed.path);
|
|
4160
4161
|
}
|
|
@@ -28700,6 +28701,9 @@ var Protocol = class {
|
|
|
28700
28701
|
this.setRequestHandler(GetTaskPayloadRequestSchema, async (request, extra) => {
|
|
28701
28702
|
const handleTaskResult = async () => {
|
|
28702
28703
|
const taskId = request.params.taskId;
|
|
28704
|
+
if (!await this._taskStore.getTask(taskId, extra.sessionId)) {
|
|
28705
|
+
throw new McpError(ErrorCode.InvalidParams, `Task not found: ${taskId}`);
|
|
28706
|
+
}
|
|
28703
28707
|
if (this._taskMessageQueue) {
|
|
28704
28708
|
let queuedMessage;
|
|
28705
28709
|
while (queuedMessage = await this._taskMessageQueue.dequeue(taskId, extra.sessionId)) {
|
|
@@ -28730,12 +28734,12 @@ var Protocol = class {
|
|
|
28730
28734
|
throw new McpError(ErrorCode.InvalidParams, `Task not found: ${taskId}`);
|
|
28731
28735
|
}
|
|
28732
28736
|
if (!isTerminal(task.status)) {
|
|
28733
|
-
await this._waitForTaskUpdate(taskId, extra.signal);
|
|
28737
|
+
await this._waitForTaskUpdate(taskId, extra.signal, extra.sessionId);
|
|
28734
28738
|
return await handleTaskResult();
|
|
28735
28739
|
}
|
|
28736
28740
|
if (isTerminal(task.status)) {
|
|
28737
28741
|
const result = await this._taskStore.getTaskResult(taskId, extra.sessionId);
|
|
28738
|
-
this._clearTaskQueue(taskId);
|
|
28742
|
+
this._clearTaskQueue(taskId, extra.sessionId);
|
|
28739
28743
|
return {
|
|
28740
28744
|
...result,
|
|
28741
28745
|
_meta: {
|
|
@@ -28772,7 +28776,7 @@ var Protocol = class {
|
|
|
28772
28776
|
throw new McpError(ErrorCode.InvalidParams, `Cannot cancel task in terminal status: ${task.status}`);
|
|
28773
28777
|
}
|
|
28774
28778
|
await this._taskStore.updateTaskStatus(request.params.taskId, "cancelled", "Client cancelled task execution.", extra.sessionId);
|
|
28775
|
-
this._clearTaskQueue(request.params.taskId);
|
|
28779
|
+
this._clearTaskQueue(request.params.taskId, extra.sessionId);
|
|
28776
28780
|
const cancelledTask = await this._taskStore.getTask(request.params.taskId, extra.sessionId);
|
|
28777
28781
|
if (!cancelledTask) {
|
|
28778
28782
|
throw new McpError(ErrorCode.InvalidParams, `Task not found after cancellation: ${request.params.taskId}`);
|
|
@@ -28900,6 +28904,19 @@ var Protocol = class {
|
|
|
28900
28904
|
const handler = this._requestHandlers.get(request.method) ?? this.fallbackRequestHandler;
|
|
28901
28905
|
const capturedTransport = this._transport;
|
|
28902
28906
|
const relatedTaskId = request.params?._meta?.[RELATED_TASK_META_KEY]?.taskId;
|
|
28907
|
+
const sessionId = capturedTransport?.sessionId;
|
|
28908
|
+
const store = this._taskStore;
|
|
28909
|
+
let relatedTaskFound = true;
|
|
28910
|
+
let relatedTaskLookup;
|
|
28911
|
+
if (relatedTaskId && store && this._taskMessageQueue && sessionId !== void 0) {
|
|
28912
|
+
relatedTaskFound = false;
|
|
28913
|
+
relatedTaskLookup = (async () => {
|
|
28914
|
+
if (!await store.getTask(relatedTaskId, sessionId)) {
|
|
28915
|
+
throw new McpError(ErrorCode.InvalidParams, `Task not found: ${relatedTaskId}`);
|
|
28916
|
+
}
|
|
28917
|
+
relatedTaskFound = true;
|
|
28918
|
+
})();
|
|
28919
|
+
}
|
|
28903
28920
|
if (handler === void 0) {
|
|
28904
28921
|
const errorResponse = {
|
|
28905
28922
|
jsonrpc: "2.0",
|
|
@@ -28909,7 +28926,10 @@ var Protocol = class {
|
|
|
28909
28926
|
message: "Method not found"
|
|
28910
28927
|
}
|
|
28911
28928
|
};
|
|
28912
|
-
if (relatedTaskId &&
|
|
28929
|
+
if (relatedTaskId && relatedTaskLookup) {
|
|
28930
|
+
const queuedError = { type: "error", message: errorResponse, timestamp: Date.now() };
|
|
28931
|
+
relatedTaskLookup.then(() => this._enqueueTaskMessage(relatedTaskId, queuedError, sessionId), () => capturedTransport?.send(errorResponse)).catch((error51) => this._onerror(new Error(`Failed to send an error response: ${error51}`)));
|
|
28932
|
+
} else if (relatedTaskId && this._taskMessageQueue) {
|
|
28913
28933
|
this._enqueueTaskMessage(relatedTaskId, {
|
|
28914
28934
|
type: "error",
|
|
28915
28935
|
message: errorResponse,
|
|
@@ -28960,7 +28980,10 @@ var Protocol = class {
|
|
|
28960
28980
|
closeSSEStream: extra?.closeSSEStream,
|
|
28961
28981
|
closeStandaloneSSEStream: extra?.closeStandaloneSSEStream
|
|
28962
28982
|
};
|
|
28963
|
-
Promise.resolve().then(() => {
|
|
28983
|
+
(relatedTaskLookup ?? Promise.resolve()).then(() => {
|
|
28984
|
+
if (relatedTaskLookup && abortController.signal.aborted) {
|
|
28985
|
+
throw new McpError(ErrorCode.ConnectionClosed, "Request was cancelled");
|
|
28986
|
+
}
|
|
28964
28987
|
if (taskCreationParams) {
|
|
28965
28988
|
this.assertTaskHandlerCapability(request.method);
|
|
28966
28989
|
}
|
|
@@ -28995,7 +29018,7 @@ var Protocol = class {
|
|
|
28995
29018
|
...error51["data"] !== void 0 && { data: error51["data"] }
|
|
28996
29019
|
}
|
|
28997
29020
|
};
|
|
28998
|
-
if (relatedTaskId && this._taskMessageQueue) {
|
|
29021
|
+
if (relatedTaskId && this._taskMessageQueue && relatedTaskFound) {
|
|
28999
29022
|
await this._enqueueTaskMessage(relatedTaskId, {
|
|
29000
29023
|
type: "error",
|
|
29001
29024
|
message: errorResponse,
|
|
@@ -29475,7 +29498,7 @@ var Protocol = class {
|
|
|
29475
29498
|
throw new Error("Cannot enqueue task message: taskStore and taskMessageQueue are not configured");
|
|
29476
29499
|
}
|
|
29477
29500
|
const maxQueueSize = this._options?.maxTaskQueueSize;
|
|
29478
|
-
await this._taskMessageQueue.enqueue(taskId, message, sessionId, maxQueueSize);
|
|
29501
|
+
await this._taskMessageQueue.enqueue(taskId, message, sessionId ?? this._transport?.sessionId, maxQueueSize);
|
|
29479
29502
|
}
|
|
29480
29503
|
/**
|
|
29481
29504
|
* Clears the message queue for a task and rejects any pending request resolvers.
|
|
@@ -29504,12 +29527,13 @@ var Protocol = class {
|
|
|
29504
29527
|
* Uses polling to check for updates at the task's configured poll interval.
|
|
29505
29528
|
* @param taskId The task ID to wait for
|
|
29506
29529
|
* @param signal Abort signal to cancel the wait
|
|
29530
|
+
* @param sessionId Session of the request that waits, passed to the task store
|
|
29507
29531
|
* @returns Promise that resolves when an update occurs or rejects if aborted
|
|
29508
29532
|
*/
|
|
29509
|
-
async _waitForTaskUpdate(taskId, signal) {
|
|
29533
|
+
async _waitForTaskUpdate(taskId, signal, sessionId) {
|
|
29510
29534
|
let interval = this._options?.defaultTaskPollInterval ?? 1e3;
|
|
29511
29535
|
try {
|
|
29512
|
-
const task = await this._taskStore?.getTask(taskId);
|
|
29536
|
+
const task = await this._taskStore?.getTask(taskId, sessionId);
|
|
29513
29537
|
if (task?.pollInterval) {
|
|
29514
29538
|
interval = task.pollInterval;
|
|
29515
29539
|
}
|
|
@@ -30388,6 +30412,42 @@ var ExperimentalMcpServerTasks = class {
|
|
|
30388
30412
|
};
|
|
30389
30413
|
|
|
30390
30414
|
// node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js
|
|
30415
|
+
function toolInputElementCount(value, max) {
|
|
30416
|
+
let count = 0;
|
|
30417
|
+
const stack = [value];
|
|
30418
|
+
while (stack.length > 0) {
|
|
30419
|
+
const node = stack.pop();
|
|
30420
|
+
if (node === null || typeof node !== "object")
|
|
30421
|
+
continue;
|
|
30422
|
+
if (Array.isArray(node)) {
|
|
30423
|
+
for (const child of node) {
|
|
30424
|
+
if (++count > max)
|
|
30425
|
+
return count;
|
|
30426
|
+
if (child !== null && typeof child === "object")
|
|
30427
|
+
stack.push(child);
|
|
30428
|
+
}
|
|
30429
|
+
} else {
|
|
30430
|
+
for (const key in node) {
|
|
30431
|
+
if (!Object.prototype.hasOwnProperty.call(node, key))
|
|
30432
|
+
continue;
|
|
30433
|
+
if (++count > max)
|
|
30434
|
+
return count;
|
|
30435
|
+
const child = node[key];
|
|
30436
|
+
if (child !== null && typeof child === "object")
|
|
30437
|
+
stack.push(child);
|
|
30438
|
+
}
|
|
30439
|
+
}
|
|
30440
|
+
}
|
|
30441
|
+
return count;
|
|
30442
|
+
}
|
|
30443
|
+
function resolveMaxToolInputElements(value) {
|
|
30444
|
+
if (value === void 0 || value === Infinity)
|
|
30445
|
+
return void 0;
|
|
30446
|
+
if (typeof value !== "number" || Number.isNaN(value) || value < 1) {
|
|
30447
|
+
throw new RangeError(`maxToolInputElements must be a number of at least 1, or Infinity, got ${String(value)}`);
|
|
30448
|
+
}
|
|
30449
|
+
return value;
|
|
30450
|
+
}
|
|
30391
30451
|
var McpServer = class {
|
|
30392
30452
|
constructor(serverInfo, options) {
|
|
30393
30453
|
this._registeredResources = {};
|
|
@@ -30399,6 +30459,7 @@ var McpServer = class {
|
|
|
30399
30459
|
this._resourceHandlersInitialized = false;
|
|
30400
30460
|
this._promptHandlersInitialized = false;
|
|
30401
30461
|
this.server = new Server(serverInfo, options);
|
|
30462
|
+
this._maxToolInputElements = resolveMaxToolInputElements(options?.maxToolInputElements);
|
|
30402
30463
|
}
|
|
30403
30464
|
/**
|
|
30404
30465
|
* Access experimental features.
|
|
@@ -30529,12 +30590,15 @@ var McpServer = class {
|
|
|
30529
30590
|
* Validates tool input arguments against the tool's input schema.
|
|
30530
30591
|
*/
|
|
30531
30592
|
async validateToolInput(tool, args, toolName) {
|
|
30593
|
+
if (this._maxToolInputElements !== void 0 && toolInputElementCount(args, this._maxToolInputElements) > this._maxToolInputElements) {
|
|
30594
|
+
throw new McpError(ErrorCode.InvalidParams, `Invalid arguments for tool ${toolName}: arguments contain more than the maximum of ${this._maxToolInputElements} elements`);
|
|
30595
|
+
}
|
|
30532
30596
|
if (!tool.inputSchema) {
|
|
30533
30597
|
return void 0;
|
|
30534
30598
|
}
|
|
30535
30599
|
const inputObj = normalizeObjectSchema(tool.inputSchema);
|
|
30536
30600
|
const schemaToParse = inputObj ?? tool.inputSchema;
|
|
30537
|
-
const parseResult = await safeParseAsync2(schemaToParse, args);
|
|
30601
|
+
const parseResult = await safeParseAsync2(schemaToParse, args ?? {});
|
|
30538
30602
|
if (!parseResult.success) {
|
|
30539
30603
|
const error51 = "error" in parseResult ? parseResult.error : "Unknown error";
|
|
30540
30604
|
const errorMessage = getParseErrorMessage(error51);
|
|
@@ -30772,7 +30836,7 @@ var McpServer = class {
|
|
|
30772
30836
|
}
|
|
30773
30837
|
if (prompt.argsSchema) {
|
|
30774
30838
|
const argsObj = normalizeObjectSchema(prompt.argsSchema);
|
|
30775
|
-
const parseResult = await safeParseAsync2(argsObj, request.params.arguments);
|
|
30839
|
+
const parseResult = await safeParseAsync2(argsObj, request.params.arguments ?? {});
|
|
30776
30840
|
if (!parseResult.success) {
|
|
30777
30841
|
const error51 = "error" in parseResult ? parseResult.error : "Unknown error";
|
|
30778
30842
|
const errorMessage = getParseErrorMessage(error51);
|
|
@@ -35225,7 +35289,7 @@ Install a newer Node (https://nodejs.org/en/download), or point your MCP client'
|
|
|
35225
35289
|
);
|
|
35226
35290
|
process.exit(1);
|
|
35227
35291
|
}
|
|
35228
|
-
var version2 = true ? "0.21.
|
|
35292
|
+
var version2 = true ? "0.21.2" : resolveVersionFallback();
|
|
35229
35293
|
var subcommand = process.argv[2];
|
|
35230
35294
|
var USAGE = `Usage: tailscale-mcp [command]
|
|
35231
35295
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yawlabs/tailscale-mcp",
|
|
3
|
-
"version": "0.21.
|
|
3
|
+
"version": "0.21.2",
|
|
4
4
|
"mcpName": "io.github.YawLabs/tailscale-mcp",
|
|
5
5
|
"description": "Tailscale MCP server: admin-API tools for devices, ACLs, DNS, auth keys, users, and audit logs, plus a CLI to validate and deploy ACLs.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -64,13 +64,13 @@
|
|
|
64
64
|
"overrides": {
|
|
65
65
|
"hono": "^4.13.5",
|
|
66
66
|
"@hono/node-server": "^1.19.15",
|
|
67
|
-
"fast-uri": "^3.1.
|
|
68
|
-
"ip-address": "^10.3
|
|
67
|
+
"fast-uri": "^3.1.8",
|
|
68
|
+
"ip-address": "^10.7.3",
|
|
69
69
|
"qs": "^6.16.0"
|
|
70
70
|
},
|
|
71
71
|
"devDependencies": {
|
|
72
72
|
"@biomejs/biome": "^2.4.12",
|
|
73
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
73
|
+
"@modelcontextprotocol/sdk": "^1.32.1",
|
|
74
74
|
"@types/node": "^26.0.0",
|
|
75
75
|
"esbuild": "^0.28.1",
|
|
76
76
|
"postject": "^1.0.0-alpha.6",
|