@instavar/mcp-server 0.1.0
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 +63 -0
- package/dist/index.js +341 -0
- package/package.json +40 -0
package/README.md
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# @instavar/mcp-server
|
|
2
|
+
|
|
3
|
+
Drive the full [Instavar Studio](https://instavar.com) video workflow —
|
|
4
|
+
**create → render → approve → publish → metrics** — from Claude Code or Codex,
|
|
5
|
+
without leaving your terminal.
|
|
6
|
+
|
|
7
|
+
The server is a thin HTTP client: it authenticates with a per-user API key and
|
|
8
|
+
talks to the hosted Instavar API. It holds **no database connection** and stores
|
|
9
|
+
no data of its own.
|
|
10
|
+
|
|
11
|
+
## Setup
|
|
12
|
+
|
|
13
|
+
1. Create an API key at **https://instavar.com/studio/settings** (shown once —
|
|
14
|
+
copy it).
|
|
15
|
+
2. Add the server to your MCP client config:
|
|
16
|
+
|
|
17
|
+
```json
|
|
18
|
+
{
|
|
19
|
+
"mcpServers": {
|
|
20
|
+
"instavar": {
|
|
21
|
+
"command": "npx",
|
|
22
|
+
"args": ["-y", "@instavar/mcp-server"],
|
|
23
|
+
"env": { "INSTAVAR_API_KEY": "ik_live_..." }
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
3. Restart the client. The `instavar` tools appear.
|
|
30
|
+
|
|
31
|
+
### Environment
|
|
32
|
+
|
|
33
|
+
| Var | Required | Default | Purpose |
|
|
34
|
+
| ------------------- | -------- | ---------------------- | ---------------------------------------------------------------- |
|
|
35
|
+
| `INSTAVAR_API_KEY` | yes | — | Your key from `/studio/settings`. Scoped to your org; revocable. |
|
|
36
|
+
| `INSTAVAR_BASE_URL` | no | `https://instavar.com` | Override the API host. |
|
|
37
|
+
|
|
38
|
+
## Tools
|
|
39
|
+
|
|
40
|
+
| Tool | What it does |
|
|
41
|
+
| -------------------- | --------------------------------------------------------------- |
|
|
42
|
+
| `list_jobs` | List recent jobs (newest first). |
|
|
43
|
+
| `get_job_status` | Runs, artifacts, verifications, current video + thumbnail URLs. |
|
|
44
|
+
| `get_video_state` | Materialized composition state for a job. |
|
|
45
|
+
| `get_job_metrics` | Platform engagement snapshots for a published job. |
|
|
46
|
+
| `get_cost_summary` | Infra cost summary (Lambda / RunPod / R2 / WaveSpeed / PoYo). |
|
|
47
|
+
| `create_video_brief` | Create a job from a structured brief and start rendering. |
|
|
48
|
+
| `edit_video_brief` | Patch a brief and re-render. |
|
|
49
|
+
| `approve_job` | Approve a rendered job so it can be published. |
|
|
50
|
+
| `publish_job` | Publish an approved job to its connected social destination. |
|
|
51
|
+
|
|
52
|
+
Writes require a key with the `write` scope; `publish_job` requires `publish`.
|
|
53
|
+
|
|
54
|
+
## CLI
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
npx @instavar/mcp-server --version
|
|
58
|
+
npx @instavar/mcp-server --help
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## License
|
|
62
|
+
|
|
63
|
+
MIT
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,341 @@
|
|
|
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 { z as z2 } from "zod";
|
|
9
|
+
|
|
10
|
+
// src/http.ts
|
|
11
|
+
var API_KEY_HEADER = "x-api-key";
|
|
12
|
+
function getApiKey() {
|
|
13
|
+
const key = process.env.INSTAVAR_API_KEY?.trim();
|
|
14
|
+
if (!key) {
|
|
15
|
+
throw new Error(
|
|
16
|
+
"INSTAVAR_API_KEY is not set. Create a key at https://instavar.com/studio/settings and add it to the MCP server env block (env.INSTAVAR_API_KEY)."
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
return key;
|
|
20
|
+
}
|
|
21
|
+
function baseUrl() {
|
|
22
|
+
return (process.env.INSTAVAR_BASE_URL || "https://instavar.com").replace(
|
|
23
|
+
/\/+$/,
|
|
24
|
+
""
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
function explain(status, body) {
|
|
28
|
+
if (status === 401) {
|
|
29
|
+
return "API key invalid or revoked (401). Check INSTAVAR_API_KEY or mint a new key at /studio/settings.";
|
|
30
|
+
}
|
|
31
|
+
if (status === 403) {
|
|
32
|
+
const reason = body && typeof body === "object" && "reason" in body ? String(body.reason) : "insufficient scope";
|
|
33
|
+
return `Forbidden (403): ${reason}. The key lacks the required scope for this action.`;
|
|
34
|
+
}
|
|
35
|
+
return "";
|
|
36
|
+
}
|
|
37
|
+
async function parse(res) {
|
|
38
|
+
const text = await res.text();
|
|
39
|
+
try {
|
|
40
|
+
return JSON.parse(text);
|
|
41
|
+
} catch {
|
|
42
|
+
return text;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
async function apiGet(path) {
|
|
46
|
+
const res = await fetch(`${baseUrl()}${path}`, {
|
|
47
|
+
headers: { [API_KEY_HEADER]: getApiKey() }
|
|
48
|
+
});
|
|
49
|
+
const body = await parse(res);
|
|
50
|
+
const hint = explain(res.status, body);
|
|
51
|
+
return { ok: res.ok, status: res.status, body: hint ? { error: hint, body } : body };
|
|
52
|
+
}
|
|
53
|
+
async function apiPost(path, payload) {
|
|
54
|
+
const clean = Object.fromEntries(
|
|
55
|
+
Object.entries(payload).filter(([, v]) => v !== void 0)
|
|
56
|
+
);
|
|
57
|
+
const res = await fetch(`${baseUrl()}${path}`, {
|
|
58
|
+
method: "POST",
|
|
59
|
+
headers: {
|
|
60
|
+
[API_KEY_HEADER]: getApiKey(),
|
|
61
|
+
"content-type": "application/json"
|
|
62
|
+
},
|
|
63
|
+
body: JSON.stringify(clean)
|
|
64
|
+
});
|
|
65
|
+
const body = await parse(res);
|
|
66
|
+
const hint = explain(res.status, body);
|
|
67
|
+
return { ok: res.ok, status: res.status, body: hint ? { error: hint, body } : body };
|
|
68
|
+
}
|
|
69
|
+
function toToolResult(result) {
|
|
70
|
+
return {
|
|
71
|
+
content: [
|
|
72
|
+
{
|
|
73
|
+
type: "text",
|
|
74
|
+
text: JSON.stringify(result.body, null, 2)
|
|
75
|
+
}
|
|
76
|
+
],
|
|
77
|
+
isError: !result.ok
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
async function runTool(fn) {
|
|
81
|
+
try {
|
|
82
|
+
return toToolResult(await fn());
|
|
83
|
+
} catch (error) {
|
|
84
|
+
return {
|
|
85
|
+
content: [
|
|
86
|
+
{
|
|
87
|
+
type: "text",
|
|
88
|
+
text: error instanceof Error ? error.message : String(error)
|
|
89
|
+
}
|
|
90
|
+
],
|
|
91
|
+
isError: true
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// src/brief-schema.ts
|
|
97
|
+
import { z } from "zod";
|
|
98
|
+
var PUBLISH_TARGETS = [
|
|
99
|
+
"linkedin",
|
|
100
|
+
"x",
|
|
101
|
+
"threads",
|
|
102
|
+
"instagram",
|
|
103
|
+
"tiktok",
|
|
104
|
+
"youtube",
|
|
105
|
+
"facebook",
|
|
106
|
+
"rednote",
|
|
107
|
+
"lemon8"
|
|
108
|
+
];
|
|
109
|
+
var BRIEF_OBJECTIVES = [
|
|
110
|
+
"build_log",
|
|
111
|
+
"product_update",
|
|
112
|
+
"launch_update",
|
|
113
|
+
"faq",
|
|
114
|
+
"explainer",
|
|
115
|
+
"customer_story",
|
|
116
|
+
"other"
|
|
117
|
+
];
|
|
118
|
+
var ASPECT_RATIOS = ["9:16", "4:5", "1:1", "16:9"];
|
|
119
|
+
var briefStepSchema = z.object({
|
|
120
|
+
label: z.string(),
|
|
121
|
+
latex: z.string().optional(),
|
|
122
|
+
explanation: z.string().optional()
|
|
123
|
+
});
|
|
124
|
+
var briefSceneSchema = z.object({
|
|
125
|
+
type: z.enum([
|
|
126
|
+
"hero",
|
|
127
|
+
"points",
|
|
128
|
+
"equation",
|
|
129
|
+
"metric-bars",
|
|
130
|
+
"metric-compare",
|
|
131
|
+
"timeline",
|
|
132
|
+
"limit-cards",
|
|
133
|
+
"slide",
|
|
134
|
+
"caption-overlay",
|
|
135
|
+
"cta",
|
|
136
|
+
"progress-ring",
|
|
137
|
+
"metric-counter",
|
|
138
|
+
"callout",
|
|
139
|
+
"step-pill",
|
|
140
|
+
"cta-pulse",
|
|
141
|
+
"video-window",
|
|
142
|
+
"custom"
|
|
143
|
+
]),
|
|
144
|
+
durationInFrames: z.number().optional(),
|
|
145
|
+
title: z.string().optional(),
|
|
146
|
+
subtitle: z.string().optional(),
|
|
147
|
+
text: z.string().optional(),
|
|
148
|
+
points: z.array(z.string()).optional(),
|
|
149
|
+
latex: z.string().optional(),
|
|
150
|
+
value: z.union([z.string(), z.number()]).optional(),
|
|
151
|
+
max: z.number().optional(),
|
|
152
|
+
label: z.string().optional(),
|
|
153
|
+
unit: z.string().optional(),
|
|
154
|
+
prefix: z.string().optional(),
|
|
155
|
+
suffix: z.string().optional(),
|
|
156
|
+
decimals: z.number().optional(),
|
|
157
|
+
color: z.string().optional(),
|
|
158
|
+
tone: z.string().optional(),
|
|
159
|
+
iconKey: z.string().optional(),
|
|
160
|
+
stepNumber: z.number().optional(),
|
|
161
|
+
totalSteps: z.number().optional(),
|
|
162
|
+
items: z.array(z.object({ label: z.string(), value: z.number() })).optional(),
|
|
163
|
+
src: z.string().optional(),
|
|
164
|
+
caption: z.string().optional()
|
|
165
|
+
});
|
|
166
|
+
var createBriefShape = {
|
|
167
|
+
title: z.string().min(1).max(120).describe("Short title (max 120 chars)"),
|
|
168
|
+
script: z.string().min(1).describe("Full narration script (sent to TTS)"),
|
|
169
|
+
caption: z.string().min(1).describe("Social caption with hooks, hashtags, CTA"),
|
|
170
|
+
publishTarget: z.enum(PUBLISH_TARGETS).describe("Target social platform"),
|
|
171
|
+
objective: z.enum(BRIEF_OBJECTIVES).describe("Content objective (drives composition routing)"),
|
|
172
|
+
lessonTitle: z.string().optional().describe("Display title rendered inside the video"),
|
|
173
|
+
steps: z.array(briefStepSchema).optional().describe("Structured steps for educational content (LaTeX)"),
|
|
174
|
+
scenes: z.array(briefSceneSchema).optional().describe("Scene graph for the UniversalVideo composition"),
|
|
175
|
+
aspectRatio: z.enum(ASPECT_RATIOS).default("9:16").describe("Aspect ratio (default 9:16)"),
|
|
176
|
+
compositionId: z.string().optional().describe("Remotion composition ID override (advanced)")
|
|
177
|
+
};
|
|
178
|
+
var editBriefShape = {
|
|
179
|
+
jobId: z.string().uuid().describe("The job to edit"),
|
|
180
|
+
title: z.string().optional().describe("Updated title"),
|
|
181
|
+
script: z.string().optional().describe("Updated narration script"),
|
|
182
|
+
caption: z.string().optional().describe("Updated social caption"),
|
|
183
|
+
lessonTitle: z.string().optional().describe("Updated lesson title"),
|
|
184
|
+
steps: z.array(briefStepSchema).optional().describe("Updated steps (replaces the array)"),
|
|
185
|
+
scenes: z.array(briefSceneSchema).optional().describe("Updated scenes (replaces the array)"),
|
|
186
|
+
aspectRatio: z.enum(ASPECT_RATIOS).optional().describe("Updated aspect ratio")
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
// src/tools.ts
|
|
190
|
+
var uuid = (desc) => z2.string().uuid().describe(desc);
|
|
191
|
+
function registerTools(server) {
|
|
192
|
+
server.tool(
|
|
193
|
+
"list_jobs",
|
|
194
|
+
"List recent video jobs for the current organization (newest first).",
|
|
195
|
+
{
|
|
196
|
+
limit: z2.number().int().min(1).max(50).default(10).describe("Number of jobs to return (default 10, max 50)"),
|
|
197
|
+
status: z2.enum([
|
|
198
|
+
"queued",
|
|
199
|
+
"rendering",
|
|
200
|
+
"draft_ready",
|
|
201
|
+
"approved",
|
|
202
|
+
"publish_requested",
|
|
203
|
+
"publishing",
|
|
204
|
+
"published",
|
|
205
|
+
"failed"
|
|
206
|
+
]).optional().describe("Filter by job status")
|
|
207
|
+
},
|
|
208
|
+
async ({ limit, status }) => {
|
|
209
|
+
const q = new URLSearchParams({ limit: String(limit) });
|
|
210
|
+
if (status) q.set("status", status);
|
|
211
|
+
return runTool(() => apiGet(`/api/v1/jobs?${q.toString()}`));
|
|
212
|
+
}
|
|
213
|
+
);
|
|
214
|
+
server.tool(
|
|
215
|
+
"get_job_status",
|
|
216
|
+
"Get detailed status for a job: runs, artifacts, verifications, plus the current video and thumbnail URLs.",
|
|
217
|
+
{ jobId: uuid("The job ID to check") },
|
|
218
|
+
async ({ jobId }) => runTool(() => apiGet(`/api/v1/jobs/${encodeURIComponent(jobId)}`))
|
|
219
|
+
);
|
|
220
|
+
server.tool(
|
|
221
|
+
"get_video_state",
|
|
222
|
+
"Get the materialized video state for a job (composition decisions: scenes, duration, aspect ratio, etc.).",
|
|
223
|
+
{ jobId: uuid("The job ID") },
|
|
224
|
+
async ({ jobId }) => runTool(
|
|
225
|
+
() => apiGet(`/api/v1/jobs/${encodeURIComponent(jobId)}/video-state`)
|
|
226
|
+
)
|
|
227
|
+
);
|
|
228
|
+
server.tool(
|
|
229
|
+
"get_job_metrics",
|
|
230
|
+
"Get the latest platform engagement metric snapshots for a published job (newest first). Read-only.",
|
|
231
|
+
{
|
|
232
|
+
jobId: uuid("The job ID"),
|
|
233
|
+
limit: z2.number().int().min(1).max(20).default(10).describe("Max metric snapshots (default 10, max 20)")
|
|
234
|
+
},
|
|
235
|
+
async ({ jobId, limit }) => runTool(
|
|
236
|
+
() => apiGet(
|
|
237
|
+
`/api/v1/jobs/${encodeURIComponent(jobId)}/metrics?limit=${limit}`
|
|
238
|
+
)
|
|
239
|
+
)
|
|
240
|
+
);
|
|
241
|
+
server.tool(
|
|
242
|
+
"get_cost_summary",
|
|
243
|
+
"Query production infrastructure costs (Lambda, RunPod, R2, WaveSpeed, PoYo), optionally with reconciliation drift.",
|
|
244
|
+
{
|
|
245
|
+
since: z2.string().optional().describe("ISO date \u2014 only include events after this date"),
|
|
246
|
+
provider: z2.enum(["runpod", "lambda", "r2", "wavespeed", "poyo", "all"]).default("all").describe("Filter by provider"),
|
|
247
|
+
groupBy: z2.enum(["provider", "job", "day"]).default("provider").describe("How to aggregate the summary"),
|
|
248
|
+
includeReconciliation: z2.boolean().default(false).describe("Include account-wide reconciliation events")
|
|
249
|
+
},
|
|
250
|
+
async ({ since, provider, groupBy, includeReconciliation }) => {
|
|
251
|
+
const q = new URLSearchParams({ provider, groupBy });
|
|
252
|
+
if (since) q.set("since", since);
|
|
253
|
+
if (includeReconciliation) q.set("includeReconciliation", "true");
|
|
254
|
+
return runTool(() => apiGet(`/api/v1/cost-summary?${q.toString()}`));
|
|
255
|
+
}
|
|
256
|
+
);
|
|
257
|
+
server.tool(
|
|
258
|
+
"create_video_brief",
|
|
259
|
+
"Create a new video job from a structured brief and trigger the render pipeline. Returns the job and run IDs.",
|
|
260
|
+
createBriefShape,
|
|
261
|
+
async (input) => runTool(() => apiPost(`/api/v1/jobs`, input))
|
|
262
|
+
);
|
|
263
|
+
server.tool(
|
|
264
|
+
"edit_video_brief",
|
|
265
|
+
"Edit an existing job's brief (only provided fields change) and trigger a re-render. Returns the new run ID.",
|
|
266
|
+
editBriefShape,
|
|
267
|
+
async (input) => {
|
|
268
|
+
const { jobId, ...patch } = input;
|
|
269
|
+
return runTool(
|
|
270
|
+
() => apiPost(`/api/v1/jobs/${encodeURIComponent(jobId)}/brief`, patch)
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
);
|
|
274
|
+
server.tool(
|
|
275
|
+
"approve_job",
|
|
276
|
+
"Approve a rendered job so it becomes publishable (draft_ready/awaiting_review/needs_changes -> approved) and revoke active review links.",
|
|
277
|
+
{ jobId: uuid("The job ID to approve") },
|
|
278
|
+
async ({ jobId }) => runTool(
|
|
279
|
+
() => apiPost(`/api/v1/jobs/${encodeURIComponent(jobId)}/approve`, {})
|
|
280
|
+
)
|
|
281
|
+
);
|
|
282
|
+
server.tool(
|
|
283
|
+
"publish_job",
|
|
284
|
+
"Publish an APPROVED job to its connected social destination via the gated publish API. All production gates run (paid plan, ownership, abuse, QA, disclosure, destination, rights, moderation). For YouTube pass youtubePrivacyStatus:'private' for safe runs.",
|
|
285
|
+
{
|
|
286
|
+
jobId: uuid("The approved job to publish"),
|
|
287
|
+
youtubePrivacyStatus: z2.enum(["private", "unlisted", "public"]).optional().describe("YouTube only. Use 'private' for safe dogfood runs."),
|
|
288
|
+
tiktokPostMode: z2.enum(["direct_publish", "draft_upload"]).optional().describe("TikTok only. Use 'draft_upload' for safe dogfood runs."),
|
|
289
|
+
connectedAccountDestinationId: z2.string().uuid().optional().describe(
|
|
290
|
+
"Specific destination when the org has multiple accounts for the provider."
|
|
291
|
+
),
|
|
292
|
+
confirmSyntheticDisclosure: z2.boolean().optional().describe("Set true when the job requires an AI synthetic-media disclosure."),
|
|
293
|
+
confirmQaOverride: z2.boolean().optional().describe("Set true to override a non-passing QA gate.")
|
|
294
|
+
},
|
|
295
|
+
async ({ jobId, ...rest }) => (
|
|
296
|
+
// NOTE: publish is NOT under /api/v1 — it is the gated /api/jobs route.
|
|
297
|
+
runTool(() => apiPost(`/api/jobs/${encodeURIComponent(jobId)}/publish`, rest))
|
|
298
|
+
)
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// src/index.ts
|
|
303
|
+
var VERSION = "0.1.0";
|
|
304
|
+
var HELP = [
|
|
305
|
+
"instavar-mcp \u2014 Instavar Studio MCP server",
|
|
306
|
+
"",
|
|
307
|
+
"Runs over stdio (launched by Claude Code / Codex). Configure via env:",
|
|
308
|
+
" INSTAVAR_API_KEY (required) your key from https://instavar.com/studio/settings",
|
|
309
|
+
" INSTAVAR_BASE_URL (optional) defaults to https://instavar.com",
|
|
310
|
+
"",
|
|
311
|
+
"Flags: --version, --help",
|
|
312
|
+
""
|
|
313
|
+
].join("\n");
|
|
314
|
+
async function main() {
|
|
315
|
+
const argv = process.argv.slice(2);
|
|
316
|
+
if (argv.includes("--version") || argv.includes("-v")) {
|
|
317
|
+
process.stdout.write(`${VERSION}
|
|
318
|
+
`);
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
if (argv.includes("--help") || argv.includes("-h")) {
|
|
322
|
+
process.stdout.write(HELP);
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
if (!process.env.INSTAVAR_API_KEY?.trim()) {
|
|
326
|
+
process.stderr.write(
|
|
327
|
+
"instavar-mcp: INSTAVAR_API_KEY is not set. Create a key at https://instavar.com/studio/settings and add it to the MCP server env block (env.INSTAVAR_API_KEY).\n"
|
|
328
|
+
);
|
|
329
|
+
process.exit(1);
|
|
330
|
+
}
|
|
331
|
+
const server = new McpServer({ name: "instavar", version: VERSION });
|
|
332
|
+
registerTools(server);
|
|
333
|
+
await server.connect(new StdioServerTransport());
|
|
334
|
+
}
|
|
335
|
+
main().catch((error) => {
|
|
336
|
+
process.stderr.write(
|
|
337
|
+
`instavar-mcp: fatal: ${error instanceof Error ? error.message : String(error)}
|
|
338
|
+
`
|
|
339
|
+
);
|
|
340
|
+
process.exit(1);
|
|
341
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@instavar/mcp-server",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Instavar Studio MCP server — drive the full video create→render→approve→publish→metrics workflow from Claude Code or Codex with an API key.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"homepage": "https://instavar.com",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/cheeweijie/instavar-nextjs.git",
|
|
11
|
+
"directory": "packages/mcp-server"
|
|
12
|
+
},
|
|
13
|
+
"keywords": ["mcp", "model-context-protocol", "instavar", "video", "claude", "codex"],
|
|
14
|
+
"bin": {
|
|
15
|
+
"instavar-mcp": "dist/index.js"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist",
|
|
19
|
+
"README.md"
|
|
20
|
+
],
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": ">=18"
|
|
23
|
+
},
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "tsup",
|
|
26
|
+
"typecheck": "tsc --noEmit",
|
|
27
|
+
"test": "vitest run",
|
|
28
|
+
"prepublishOnly": "npm run build"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
32
|
+
"zod": "^3.25.64"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/node": "^20.19.9",
|
|
36
|
+
"tsup": "^8.3.0",
|
|
37
|
+
"typescript": "5.8.3",
|
|
38
|
+
"vitest": "^3.2.4"
|
|
39
|
+
}
|
|
40
|
+
}
|