@myna-sh/mcp 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/dist/main.js ADDED
@@ -0,0 +1,1118 @@
1
+ // src/main.ts
2
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
+
4
+ // src/server.ts
5
+ import { McpServer as McpServer2 } from "@modelcontextprotocol/sdk/server/mcp.js";
6
+
7
+ // src/config.ts
8
+ import { readFileSync } from "fs";
9
+ import { homedir } from "os";
10
+ import { join } from "path";
11
+
12
+ // ../sdk/dist/chunk-VAS24QPJ.js
13
+ var MynaApiError = class _MynaApiError extends Error {
14
+ /** HTTP status code. */
15
+ status;
16
+ /** Stable machine-readable error code (e.g. `VALIDATION_FAILED`). */
17
+ code;
18
+ /** RFC 9457 `type` URI. */
19
+ type;
20
+ /** Human-facing title. */
21
+ title;
22
+ /** Longer human-facing detail, when provided. */
23
+ detail;
24
+ /** Server request id for support/correlation. */
25
+ requestId;
26
+ /** Field-level validation errors, when present. */
27
+ fields;
28
+ /** The full parsed problem document (or a synthesized one). */
29
+ problem;
30
+ constructor(problem) {
31
+ super(problem.detail ?? problem.title ?? `Request failed (${problem.status})`);
32
+ this.name = "MynaApiError";
33
+ this.status = problem.status;
34
+ this.code = problem.code;
35
+ this.type = problem.type;
36
+ this.title = problem.title;
37
+ this.detail = problem.detail;
38
+ this.requestId = problem.requestId;
39
+ this.fields = problem.fields;
40
+ this.problem = problem;
41
+ Object.setPrototypeOf(this, _MynaApiError.prototype);
42
+ }
43
+ static is(value) {
44
+ return value instanceof _MynaApiError;
45
+ }
46
+ };
47
+ function isMynaApiError(value) {
48
+ return value instanceof MynaApiError;
49
+ }
50
+ function problemFromResponse(status, body, fallbackTitle) {
51
+ if (body && typeof body === "object" && "code" in body && "status" in body) {
52
+ return new MynaApiError(body);
53
+ }
54
+ const detail = body && typeof body === "object" && "error" in body && typeof body.error === "string" ? body.error : typeof body === "string" && body ? body : void 0;
55
+ return new MynaApiError({
56
+ type: "about:blank",
57
+ title: fallbackTitle,
58
+ status,
59
+ code: status >= 500 ? "INTERNAL" : "REQUEST_FAILED",
60
+ detail
61
+ });
62
+ }
63
+ var RETRYABLE_STATUS = /* @__PURE__ */ new Set([408, 425, 429, 500, 502, 503, 504]);
64
+ var SAFE_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "OPTIONS"]);
65
+ function resolveFetch(custom) {
66
+ if (custom) return custom;
67
+ if (typeof globalThis.fetch === "function") return globalThis.fetch.bind(globalThis);
68
+ throw new Error("No fetch implementation available. Pass `fetch` to the Myna client.");
69
+ }
70
+ function buildQuery(query2) {
71
+ if (!query2) return "";
72
+ const params = new URLSearchParams();
73
+ const append = (key, value) => {
74
+ if (value === void 0 || value === null) return;
75
+ if (Array.isArray(value)) {
76
+ params.set(key, value.join(","));
77
+ return;
78
+ }
79
+ if (typeof value === "object") {
80
+ for (const [op, v] of Object.entries(value)) {
81
+ append(`${key}[${op}]`, v);
82
+ }
83
+ return;
84
+ }
85
+ params.set(key, String(value));
86
+ };
87
+ for (const [key, value] of Object.entries(query2)) append(key, value);
88
+ const qs = params.toString();
89
+ return qs ? `?${qs}` : "";
90
+ }
91
+ function sleep(ms, signal) {
92
+ return new Promise((resolve, reject) => {
93
+ if (signal?.aborted) return reject(signal.reason ?? new Error("Aborted"));
94
+ const timer = setTimeout(resolve, ms);
95
+ signal?.addEventListener(
96
+ "abort",
97
+ () => {
98
+ clearTimeout(timer);
99
+ reject(signal.reason ?? new Error("Aborted"));
100
+ },
101
+ { once: true }
102
+ );
103
+ });
104
+ }
105
+ var HttpClient = class {
106
+ baseUrl;
107
+ token;
108
+ fetchImpl;
109
+ defaultHeaders;
110
+ retry;
111
+ constructor(options) {
112
+ this.baseUrl = options.baseUrl.replace(/\/+$/, "");
113
+ this.token = options.token;
114
+ this.fetchImpl = resolveFetch(options.fetch);
115
+ this.defaultHeaders = options.headers ?? {};
116
+ this.retry = {
117
+ maxRetries: options.retry?.maxRetries ?? 3,
118
+ baseDelayMs: options.retry?.baseDelayMs ?? 200,
119
+ maxDelayMs: options.retry?.maxDelayMs ?? 5e3
120
+ };
121
+ }
122
+ /** Perform a request and unwrap the `{ data }` envelope. */
123
+ async request(method, path, options = {}) {
124
+ const raw = await this.requestRaw(method, path, options);
125
+ if (raw.status === 204 || raw.status === 304) return void 0;
126
+ const parsed = await raw.json().catch(() => void 0);
127
+ return parsed && "data" in parsed ? parsed.data : parsed;
128
+ }
129
+ /** Perform a request and return the full parsed body (envelope included). */
130
+ async requestEnvelope(method, path, options = {}) {
131
+ const raw = await this.requestRaw(method, path, options);
132
+ return await raw.json().catch(() => ({}));
133
+ }
134
+ /** Low-level request with retries; throws `MynaApiError` on non-2xx. */
135
+ async requestRaw(method, path, options = {}) {
136
+ const url = `${this.baseUrl}${path}${buildQuery(options.query)}`;
137
+ const headers = {
138
+ accept: "application/json",
139
+ ...this.defaultHeaders,
140
+ ...options.headers
141
+ };
142
+ const token = options.token ?? this.token;
143
+ if (token) headers.authorization = `Bearer ${token}`;
144
+ let payload;
145
+ if (options.body !== void 0) {
146
+ if (options.body instanceof Uint8Array || typeof options.body === "string") {
147
+ payload = options.body;
148
+ } else {
149
+ headers["content-type"] = "application/json";
150
+ payload = JSON.stringify(options.body);
151
+ }
152
+ }
153
+ if (options.idempotencyKey) headers["idempotency-key"] = options.idempotencyKey;
154
+ const idempotent = SAFE_METHODS.has(method.toUpperCase()) || Boolean(options.idempotencyKey);
155
+ const init = { method, headers };
156
+ if (payload !== void 0) init.body = payload;
157
+ if (options.signal) init.signal = options.signal;
158
+ let attempt = 0;
159
+ for (; ; ) {
160
+ let response;
161
+ try {
162
+ response = await this.fetchImpl(url, init);
163
+ } catch (error) {
164
+ if (idempotent && attempt < this.retry.maxRetries && !isAbort(error)) {
165
+ await sleep(this.backoff(attempt), options.signal);
166
+ attempt++;
167
+ continue;
168
+ }
169
+ throw error;
170
+ }
171
+ if (response.ok) return response;
172
+ if (idempotent && RETRYABLE_STATUS.has(response.status) && attempt < this.retry.maxRetries) {
173
+ const retryAfter = retryAfterMs(response);
174
+ await sleep(retryAfter ?? this.backoff(attempt), options.signal);
175
+ attempt++;
176
+ continue;
177
+ }
178
+ const body = await response.json().catch(() => void 0);
179
+ throw problemFromResponse(response.status, body, response.statusText || "Request failed");
180
+ }
181
+ }
182
+ backoff(attempt) {
183
+ const base = Math.min(this.retry.baseDelayMs * 2 ** attempt, this.retry.maxDelayMs);
184
+ return Math.round(base * (0.5 + Math.random() * 0.5));
185
+ }
186
+ };
187
+ function retryAfterMs(response) {
188
+ const header = response.headers.get("retry-after");
189
+ if (!header) return void 0;
190
+ const seconds = Number(header);
191
+ return Number.isFinite(seconds) ? seconds * 1e3 : void 0;
192
+ }
193
+ function isAbort(error) {
194
+ return error instanceof Error && (error.name === "AbortError" || error.name === "TimeoutError");
195
+ }
196
+
197
+ // ../sdk/dist/management.js
198
+ import { createHash, randomUUID } from "crypto";
199
+ import { readFile } from "fs/promises";
200
+ import { basename } from "path";
201
+ var DEFAULT_API_URL = "https://api.myna.sh";
202
+ var ManagementClient = class {
203
+ http;
204
+ newIdempotencyKey;
205
+ fetchImpl;
206
+ constructor(options) {
207
+ if (!options.token) throw new Error("createManagementClient: `token` is required.");
208
+ this.newIdempotencyKey = options.idempotencyKey ?? (() => randomUUID());
209
+ this.fetchImpl = options.fetch ?? ((input, init) => fetch(input, init));
210
+ this.http = new HttpClient({
211
+ baseUrl: `${(options.apiUrl ?? DEFAULT_API_URL).replace(/\/+$/, "")}/v1`,
212
+ token: options.token,
213
+ fetch: options.fetch,
214
+ retry: options.retry
215
+ });
216
+ }
217
+ // Convenience: idempotency key for a mutation.
218
+ idem() {
219
+ return this.newIdempotencyKey();
220
+ }
221
+ get(path, query2, signal) {
222
+ return this.http.request("GET", path, { query: query2, signal });
223
+ }
224
+ async page(path, opts = {}) {
225
+ const body = await this.http.requestEnvelope(
226
+ "GET",
227
+ path,
228
+ { query: { limit: opts.limit, cursor: opts.cursor }, signal: opts.signal }
229
+ );
230
+ return { data: body.data, nextCursor: body.pagination?.nextCursor ?? null };
231
+ }
232
+ mutate(method, path, body, query2) {
233
+ return this.http.request(method, path, { body, query: query2, idempotencyKey: this.idem() });
234
+ }
235
+ // --- Organizations --------------------------------------------------------
236
+ organizations = {
237
+ list: (signal) => this.get("/organizations", void 0, signal),
238
+ create: (body) => this.mutate("POST", "/organizations", body),
239
+ get: (organization, signal) => this.get(`/organizations/${enc(organization)}`, void 0, signal),
240
+ update: (organization, body) => this.mutate("PATCH", `/organizations/${enc(organization)}`, body),
241
+ delete: (organization) => this.mutate("DELETE", `/organizations/${enc(organization)}`),
242
+ export: (organization) => this.mutate("POST", `/organizations/${enc(organization)}/export`),
243
+ usage: (organization, signal) => this.get(`/organizations/${enc(organization)}/usage`, void 0, signal)
244
+ };
245
+ // --- Members & invitations ------------------------------------------------
246
+ members = {
247
+ list: (organization, signal) => this.get(`/organizations/${enc(organization)}/members`, void 0, signal),
248
+ update: (organization, user, body) => this.mutate("PATCH", `/organizations/${enc(organization)}/members/${enc(user)}`, body),
249
+ remove: (organization, user) => this.mutate("DELETE", `/organizations/${enc(organization)}/members/${enc(user)}`)
250
+ };
251
+ invitations = {
252
+ list: (organization, signal) => this.get(`/organizations/${enc(organization)}/invitations`, void 0, signal),
253
+ create: (organization, body) => this.mutate("POST", `/organizations/${enc(organization)}/invitations`, body),
254
+ revoke: (organization, invitation) => this.mutate("DELETE", `/organizations/${enc(organization)}/invitations/${enc(invitation)}`),
255
+ accept: (token) => this.mutate("POST", `/invitations/${enc(token)}/accept`)
256
+ };
257
+ // --- Projects -------------------------------------------------------------
258
+ projects = {
259
+ list: (organization, signal) => this.get(`/organizations/${enc(organization)}/projects`, void 0, signal),
260
+ create: (organization, body) => this.mutate("POST", `/organizations/${enc(organization)}/projects`, body),
261
+ get: (project, signal) => this.get(`/projects/${enc(project)}`, void 0, signal),
262
+ update: (project, body) => this.mutate("PATCH", `/projects/${enc(project)}`, body),
263
+ archive: (project) => this.mutate("POST", `/projects/${enc(project)}/archive`)
264
+ };
265
+ // --- Schema ---------------------------------------------------------------
266
+ schema = {
267
+ collections: (project, signal) => this.get(`/projects/${enc(project)}/collections`, void 0, signal),
268
+ collection: (project, key, signal) => this.get(`/projects/${enc(project)}/collections/${enc(key)}`, void 0, signal),
269
+ versions: (project, key, signal) => this.get(`/projects/${enc(project)}/collections/${enc(key)}/versions`, void 0, signal),
270
+ diff: (project, collections) => this.mutate("POST", `/projects/${enc(project)}/schema/diff`, { collections }),
271
+ push: (project, collections, opts = {}) => this.mutate("POST", `/projects/${enc(project)}/schema/push`, {
272
+ collections,
273
+ allowDestructive: opts.allowDestructive ?? false,
274
+ changeSummary: opts.changeSummary
275
+ })
276
+ };
277
+ // --- Entries & revisions --------------------------------------------------
278
+ entries = {
279
+ list: (project, opts = {}) => this.page(
280
+ `/projects/${enc(project)}/entries` + query({ collection: opts.collection, status: opts.status }),
281
+ opts
282
+ ),
283
+ create: (project, body) => this.mutate("POST", `/projects/${enc(project)}/entries`, body),
284
+ get: (project, entry, signal) => this.get(`/projects/${enc(project)}/entries/${enc(entry)}`, void 0, signal),
285
+ update: (project, entry, body) => this.mutate("PATCH", `/projects/${enc(project)}/entries/${enc(entry)}`, body),
286
+ delete: (project, entry, changeSetId) => this.mutate("DELETE", `/projects/${enc(project)}/entries/${enc(entry)}`, void 0, { changeSetId }),
287
+ unpublish: (project, entry, changeSetId) => this.mutate("POST", `/projects/${enc(project)}/entries/${enc(entry)}/unpublish`, void 0, { changeSetId }),
288
+ restore: (project, entry) => this.mutate("POST", `/projects/${enc(project)}/entries/${enc(entry)}/restore`),
289
+ revisions: (project, entry, signal) => this.get(`/projects/${enc(project)}/entries/${enc(entry)}/revisions`, void 0, signal),
290
+ revision: (project, entry, revision, signal) => this.get(`/projects/${enc(project)}/entries/${enc(entry)}/revisions/${enc(revision)}`, void 0, signal),
291
+ restoreRevision: (project, entry, revision) => this.mutate(
292
+ "POST",
293
+ `/projects/${enc(project)}/entries/${enc(entry)}/revisions/${enc(revision)}/restore`
294
+ )
295
+ };
296
+ // --- Change sets ----------------------------------------------------------
297
+ changeSets = {
298
+ list: (project, opts = {}) => this.page(`/projects/${enc(project)}/change-sets` + query({ status: opts.status }), opts),
299
+ create: (project, body) => this.mutate("POST", `/projects/${enc(project)}/change-sets`, body),
300
+ get: (project, changeSet, signal) => this.get(`/projects/${enc(project)}/change-sets/${enc(changeSet)}`, void 0, signal),
301
+ update: (project, changeSet, body) => this.mutate("PATCH", `/projects/${enc(project)}/change-sets/${enc(changeSet)}`, body),
302
+ validate: (project, changeSet) => this.mutate("POST", `/projects/${enc(project)}/change-sets/${enc(changeSet)}/validate`),
303
+ publish: (project, changeSet) => this.mutate("POST", `/projects/${enc(project)}/change-sets/${enc(changeSet)}/publish`, { confirm: true }),
304
+ close: (project, changeSet) => this.mutate("POST", `/projects/${enc(project)}/change-sets/${enc(changeSet)}/close`)
305
+ };
306
+ // --- Previews -------------------------------------------------------------
307
+ previews = {
308
+ create: (project, body) => this.mutate("POST", `/projects/${enc(project)}/previews`, body),
309
+ list: (project, signal) => this.get(`/projects/${enc(project)}/previews`, void 0, signal),
310
+ revoke: (project, preview) => this.mutate("DELETE", `/projects/${enc(project)}/previews/${enc(preview)}`)
311
+ };
312
+ // --- Assets ---------------------------------------------------------------
313
+ assets = {
314
+ createUpload: (project, body) => this.mutate("POST", `/projects/${enc(project)}/assets/uploads`, body),
315
+ completeUpload: (project, upload) => this.mutate("POST", `/projects/${enc(project)}/assets/uploads/${enc(upload)}/complete`).then(
316
+ (r) => r.asset
317
+ ),
318
+ list: (project, opts = {}) => this.page(`/projects/${enc(project)}/assets`, opts),
319
+ get: (project, asset, signal) => this.get(`/projects/${enc(project)}/assets/${enc(asset)}`, void 0, signal),
320
+ usage: (project, asset, signal) => this.get(`/projects/${enc(project)}/assets/${enc(asset)}`, { usage: "true" }, signal),
321
+ update: (project, asset, body) => this.mutate("PATCH", `/projects/${enc(project)}/assets/${enc(asset)}`, body),
322
+ delete: (project, asset) => this.mutate("DELETE", `/projects/${enc(project)}/assets/${enc(asset)}`),
323
+ /** Full presigned upload flow: create → PUT bytes → complete. */
324
+ upload: (project, input, meta = {}) => this.uploadAsset(project, input, meta)
325
+ };
326
+ // --- API keys -------------------------------------------------------------
327
+ apiKeys = {
328
+ listForOrganization: (organization, signal) => this.get(`/organizations/${enc(organization)}/api-keys`, void 0, signal),
329
+ createForOrganization: (organization, body) => this.mutate("POST", `/organizations/${enc(organization)}/api-keys`, body),
330
+ revokeForOrganization: (organization, key) => this.mutate("DELETE", `/organizations/${enc(organization)}/api-keys/${enc(key)}`),
331
+ listForProject: (project, signal) => this.get(`/projects/${enc(project)}/api-keys`, void 0, signal),
332
+ createForProject: (project, body) => this.mutate("POST", `/projects/${enc(project)}/api-keys`, body),
333
+ revokeForProject: (project, key) => this.mutate("DELETE", `/projects/${enc(project)}/api-keys/${enc(key)}`)
334
+ };
335
+ // --- Webhooks -------------------------------------------------------------
336
+ webhooks = {
337
+ list: (project, signal) => this.get(`/projects/${enc(project)}/webhooks`, void 0, signal),
338
+ create: (project, body) => this.mutate("POST", `/projects/${enc(project)}/webhooks`, body),
339
+ update: (project, webhook, body) => this.mutate("PATCH", `/projects/${enc(project)}/webhooks/${enc(webhook)}`, body),
340
+ delete: (project, webhook) => this.mutate("DELETE", `/projects/${enc(project)}/webhooks/${enc(webhook)}`),
341
+ deliveries: (project, webhook, signal) => this.get(`/projects/${enc(project)}/webhooks/${enc(webhook)}/deliveries`, void 0, signal),
342
+ retry: (project, webhook, delivery) => this.mutate("POST", `/projects/${enc(project)}/webhooks/${enc(webhook)}/deliveries/${enc(delivery)}/retry`)
343
+ };
344
+ // --- Activity -------------------------------------------------------------
345
+ activity(project, opts = {}) {
346
+ return this.page(
347
+ `/projects/${enc(project)}/activity` + query({
348
+ actorType: opts.actorType,
349
+ action: opts.action,
350
+ targetType: opts.targetType,
351
+ from: opts.from,
352
+ to: opts.to
353
+ }),
354
+ opts
355
+ );
356
+ }
357
+ // --- Billing --------------------------------------------------------------
358
+ billing = {
359
+ status: (organization, signal) => this.get(`/organizations/${enc(organization)}/billing`, void 0, signal),
360
+ checkout: (organization, body) => this.mutate("POST", `/organizations/${enc(organization)}/billing/checkout`, body),
361
+ portal: (organization) => this.mutate("POST", `/organizations/${enc(organization)}/billing/portal`)
362
+ };
363
+ // --- Upload helper --------------------------------------------------------
364
+ async uploadAsset(project, input, meta) {
365
+ const { bytes, filename } = await readInput(input, meta.filename);
366
+ const contentType = meta.contentType ?? guessContentType(filename);
367
+ const checksum = createHash("md5").update(bytes).digest("hex");
368
+ const upload = await this.assets.createUpload(project, {
369
+ filename,
370
+ contentType,
371
+ byteSize: meta.byteSize ?? bytes.byteLength,
372
+ checksum
373
+ });
374
+ const put = await this.fetchImpl(upload.url, {
375
+ method: upload.method,
376
+ headers: upload.headers,
377
+ body: bytes
378
+ });
379
+ if (!put.ok) {
380
+ throw new Error(`Asset upload PUT failed with status ${put.status}.`);
381
+ }
382
+ return this.assets.completeUpload(project, upload.uploadId);
383
+ }
384
+ };
385
+ async function readInput(input, filenameOverride) {
386
+ if (typeof input === "string") {
387
+ const bytes2 = await readFile(input);
388
+ return { bytes: new Uint8Array(bytes2), filename: filenameOverride ?? basename(input) };
389
+ }
390
+ const bytes = input instanceof Uint8Array ? input : new Uint8Array(input);
391
+ return { bytes, filename: filenameOverride ?? "upload.bin" };
392
+ }
393
+ var CONTENT_TYPES = {
394
+ png: "image/png",
395
+ jpg: "image/jpeg",
396
+ jpeg: "image/jpeg",
397
+ gif: "image/gif",
398
+ webp: "image/webp",
399
+ avif: "image/avif",
400
+ pdf: "application/pdf",
401
+ json: "application/json",
402
+ txt: "text/plain",
403
+ md: "text/markdown",
404
+ csv: "text/csv",
405
+ mp4: "video/mp4",
406
+ webm: "video/webm",
407
+ mp3: "audio/mpeg",
408
+ wav: "audio/wav"
409
+ };
410
+ function guessContentType(filename) {
411
+ const ext = filename.split(".").pop()?.toLowerCase() ?? "";
412
+ return CONTENT_TYPES[ext] ?? "application/octet-stream";
413
+ }
414
+ function enc(segment) {
415
+ return encodeURIComponent(segment);
416
+ }
417
+ function query(params) {
418
+ const entries = Object.entries(params).filter(([, v]) => v !== void 0 && v !== "");
419
+ if (entries.length === 0) return "";
420
+ return "?" + entries.map(([k, v]) => `${k}=${encodeURIComponent(v)}`).join("&");
421
+ }
422
+ function createManagementClient(options) {
423
+ return new ManagementClient(options);
424
+ }
425
+
426
+ // src/config.ts
427
+ var DEFAULT_API_URL2 = "https://api.myna.sh";
428
+ var ProjectRegistry = class {
429
+ constructor(env = process.env) {
430
+ this.env = env;
431
+ this.file = loadConfigFile(env);
432
+ }
433
+ env;
434
+ file;
435
+ clients = /* @__PURE__ */ new Map();
436
+ get defaultApiUrl() {
437
+ return this.env.MYNA_API_URL ?? this.file.apiUrl ?? DEFAULT_API_URL2;
438
+ }
439
+ get defaultToken() {
440
+ return this.env.MYNA_TOKEN ?? this.file.token;
441
+ }
442
+ /** The named-project key used when a call omits `project`. */
443
+ defaultProjectName() {
444
+ return this.file.default ?? (this.env.MYNA_PROJECT ? "__env" : void 0);
445
+ }
446
+ /** All configured named projects (env default included). */
447
+ list() {
448
+ const out = [];
449
+ if (this.env.MYNA_PROJECT) {
450
+ out.push({ name: "__env", project: this.env.MYNA_PROJECT, organization: this.env.MYNA_ORGANIZATION });
451
+ }
452
+ for (const [name, cfg] of Object.entries(this.file.projects ?? {})) {
453
+ out.push({ name, project: cfg.project, organization: cfg.organization });
454
+ }
455
+ return out;
456
+ }
457
+ /** Resolve a project reference: an explicit id/slug, a named config, or the default. */
458
+ resolve(ref) {
459
+ if (ref && this.file.projects?.[ref]) {
460
+ const cfg = this.file.projects[ref];
461
+ return {
462
+ project: cfg.project,
463
+ organization: cfg.organization,
464
+ apiUrl: cfg.apiUrl ?? this.defaultApiUrl,
465
+ token: cfg.token ?? this.defaultToken
466
+ };
467
+ }
468
+ if ((!ref || ref === "__env") && this.env.MYNA_PROJECT) {
469
+ return {
470
+ project: ref && ref !== "__env" ? ref : this.env.MYNA_PROJECT,
471
+ organization: this.env.MYNA_ORGANIZATION,
472
+ apiUrl: this.defaultApiUrl,
473
+ token: this.defaultToken
474
+ };
475
+ }
476
+ if (!ref && this.file.default && this.file.projects?.[this.file.default]) {
477
+ return this.resolve(this.file.default);
478
+ }
479
+ if (!ref) {
480
+ throw new Error("No project specified and no default is configured (set MYNA_PROJECT or configure a default).");
481
+ }
482
+ return { project: ref, organization: this.env.MYNA_ORGANIZATION, apiUrl: this.defaultApiUrl, token: this.defaultToken };
483
+ }
484
+ /** A management client bound to the resolved project's API + token. */
485
+ clientFor(ref) {
486
+ const resolved = this.resolve(ref);
487
+ if (!resolved.token) {
488
+ throw new Error("No credential available. Set MYNA_TOKEN or configure a token for the project.");
489
+ }
490
+ const cacheKey = `${resolved.apiUrl}::${resolved.token}`;
491
+ let client = this.clients.get(cacheKey);
492
+ if (!client) {
493
+ client = createManagementClient({ token: resolved.token, apiUrl: resolved.apiUrl });
494
+ this.clients.set(cacheKey, client);
495
+ }
496
+ return { client, project: resolved.project, organization: resolved.organization };
497
+ }
498
+ /** Resolve an organization for org-scoped calls. */
499
+ organizationFor(ref, organizationOverride) {
500
+ const resolved = this.resolve(ref);
501
+ const org = organizationOverride ?? resolved.organization;
502
+ if (!org) throw new Error("No organization specified. Provide `organization` or configure one for the project.");
503
+ return org;
504
+ }
505
+ };
506
+ function loadConfigFile(env) {
507
+ const explicit = env.MYNA_MCP_CONFIG;
508
+ const candidates = [explicit, join(env.XDG_CONFIG_HOME ?? join(homedir(), ".config"), "myna", "mcp.json")].filter(
509
+ (v) => Boolean(v)
510
+ );
511
+ for (const path of candidates) {
512
+ try {
513
+ return JSON.parse(readFileSync(path, "utf8"));
514
+ } catch {
515
+ }
516
+ }
517
+ return {};
518
+ }
519
+
520
+ // src/tools.ts
521
+ import { z } from "zod";
522
+ function ok(text, structured) {
523
+ return {
524
+ content: [{ type: "text", text }],
525
+ structuredContent: structured
526
+ };
527
+ }
528
+ function fail(error) {
529
+ const message = isMynaApiError(error) ? `${error.code}: ${error.detail ?? error.title}` : error instanceof Error ? error.message : String(error);
530
+ return { content: [{ type: "text", text: `Error: ${message}` }], isError: true };
531
+ }
532
+ async function resolveEntryId(client, project, ref) {
533
+ if (ref.startsWith("ent_") && !ref.includes("/")) return ref;
534
+ const [collection, slug] = ref.includes("/") ? ref.split("/", 2) : [void 0, ref];
535
+ if (!collection) throw new Error(`Provide the entry as "<collection>/<slug>" or an ent_ id (got "${ref}").`);
536
+ let cursor;
537
+ for (let i = 0; i < 50; i++) {
538
+ const page = await client.entries.list(project, { collection, limit: 100, cursor });
539
+ const match = page.data.find((e) => e.slug === slug || e.id === slug);
540
+ if (match) return match.id;
541
+ if (!page.nextCursor) break;
542
+ cursor = page.nextCursor;
543
+ }
544
+ throw new Error(`Entry not found: ${ref}`);
545
+ }
546
+ var projectArg = { project: z.string().optional().describe("Project id, slug, or configured name. Omit to use the default.") };
547
+ function registerTools(server, registry) {
548
+ server.registerTool(
549
+ "myna_list_projects",
550
+ {
551
+ title: "List projects",
552
+ description: "List projects in an organization.",
553
+ inputSchema: {
554
+ organization: z.string().optional().describe("Organization id or slug. Omit to use the default."),
555
+ ...projectArg
556
+ },
557
+ annotations: { readOnlyHint: true, openWorldHint: true }
558
+ },
559
+ async (args) => {
560
+ try {
561
+ const { client } = registry.clientFor(args.project);
562
+ const org = registry.organizationFor(args.project, args.organization);
563
+ const projects = await client.projects.list(org);
564
+ return ok(`${projects.length} project(s) in ${org}.`, { projects });
565
+ } catch (error) {
566
+ return fail(error);
567
+ }
568
+ }
569
+ );
570
+ server.registerTool(
571
+ "myna_get_project",
572
+ {
573
+ title: "Get project",
574
+ description: "Fetch a single project's details.",
575
+ inputSchema: { ...projectArg },
576
+ annotations: { readOnlyHint: true, openWorldHint: true }
577
+ },
578
+ async (args) => {
579
+ try {
580
+ const { client, project } = registry.clientFor(args.project);
581
+ const result = await client.projects.get(project);
582
+ return ok(`Project ${result.name} (${result.slug}).`, { project: result });
583
+ } catch (error) {
584
+ return fail(error);
585
+ }
586
+ }
587
+ );
588
+ server.registerTool(
589
+ "myna_list_collections",
590
+ {
591
+ title: "List collections",
592
+ description: "List the deployed collections in a project.",
593
+ inputSchema: { ...projectArg },
594
+ annotations: { readOnlyHint: true, openWorldHint: true }
595
+ },
596
+ async (args) => {
597
+ try {
598
+ const { client, project } = registry.clientFor(args.project);
599
+ const collections = await client.schema.collections(project);
600
+ return ok(`${collections.length} collection(s).`, { collections });
601
+ } catch (error) {
602
+ return fail(error);
603
+ }
604
+ }
605
+ );
606
+ server.registerTool(
607
+ "myna_get_collection_schema",
608
+ {
609
+ title: "Get collection schema",
610
+ description: "Fetch the latest canonical schema for a collection.",
611
+ inputSchema: { ...projectArg, collection: z.string().describe("Collection key.") },
612
+ annotations: { readOnlyHint: true, openWorldHint: true }
613
+ },
614
+ async (args) => {
615
+ try {
616
+ const { client, project } = registry.clientFor(args.project);
617
+ const versions = await client.schema.versions(project, args.collection);
618
+ const latest = versions[0];
619
+ if (!latest) throw new Error(`Collection ${args.collection} has no versions.`);
620
+ return ok(`${args.collection} schema v${latest.version}.`, { version: latest.version, schema: latest.schemaJson });
621
+ } catch (error) {
622
+ return fail(error);
623
+ }
624
+ }
625
+ );
626
+ server.registerTool(
627
+ "myna_list_entries",
628
+ {
629
+ title: "List entries",
630
+ description: "List entries in a collection.",
631
+ inputSchema: {
632
+ ...projectArg,
633
+ collection: z.string().describe("Collection key."),
634
+ status: z.enum(["draft", "published", "changed", "unpublished", "deleted"]).optional(),
635
+ limit: z.number().int().min(1).max(100).optional(),
636
+ cursor: z.string().optional()
637
+ },
638
+ annotations: { readOnlyHint: true, openWorldHint: true }
639
+ },
640
+ async (args) => {
641
+ try {
642
+ const { client, project } = registry.clientFor(args.project);
643
+ const page = await client.entries.list(project, {
644
+ collection: args.collection,
645
+ status: args.status,
646
+ limit: args.limit ?? 25,
647
+ cursor: args.cursor
648
+ });
649
+ return ok(`${page.data.length} entr(y/ies).`, { entries: page.data, nextCursor: page.nextCursor });
650
+ } catch (error) {
651
+ return fail(error);
652
+ }
653
+ }
654
+ );
655
+ server.registerTool(
656
+ "myna_get_entry",
657
+ {
658
+ title: "Get entry",
659
+ description: 'Fetch one entry by "<collection>/<slug>" or entry id.',
660
+ inputSchema: { ...projectArg, entry: z.string().describe("collection/slug or ent_ id.") },
661
+ annotations: { readOnlyHint: true, openWorldHint: true }
662
+ },
663
+ async (args) => {
664
+ try {
665
+ const { client, project } = registry.clientFor(args.project);
666
+ const id = await resolveEntryId(client, project, args.entry);
667
+ const entry = await client.entries.get(project, id);
668
+ return ok(`Entry ${entry.id} (${entry.status}).`, { entry });
669
+ } catch (error) {
670
+ return fail(error);
671
+ }
672
+ }
673
+ );
674
+ server.registerTool(
675
+ "myna_create_change_set",
676
+ {
677
+ title: "Create change set",
678
+ description: "Open a new change set to group entry and asset operations.",
679
+ inputSchema: {
680
+ ...projectArg,
681
+ title: z.string().min(1).max(200),
682
+ description: z.string().max(2e3).optional()
683
+ },
684
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }
685
+ },
686
+ async (args) => {
687
+ try {
688
+ const { client, project } = registry.clientFor(args.project);
689
+ const cs = await client.changeSets.create(project, { title: args.title, description: args.description });
690
+ return ok(`Created change set ${cs.id}.`, { changeSet: cs });
691
+ } catch (error) {
692
+ return fail(error);
693
+ }
694
+ }
695
+ );
696
+ server.registerTool(
697
+ "myna_get_change_set",
698
+ {
699
+ title: "Get change set",
700
+ description: "Fetch a change set and its items.",
701
+ inputSchema: { ...projectArg, changeSet: z.string().describe("Change set id (chs_).") },
702
+ annotations: { readOnlyHint: true, openWorldHint: true }
703
+ },
704
+ async (args) => {
705
+ try {
706
+ const { client, project } = registry.clientFor(args.project);
707
+ const cs = await client.changeSets.get(project, args.changeSet);
708
+ return ok(`Change set ${cs.id} (${cs.status}), ${cs.items.length} item(s).`, { changeSet: cs });
709
+ } catch (error) {
710
+ return fail(error);
711
+ }
712
+ }
713
+ );
714
+ server.registerTool(
715
+ "myna_create_entry",
716
+ {
717
+ title: "Create entry",
718
+ description: "Create a draft entry. Does not publish.",
719
+ inputSchema: {
720
+ ...projectArg,
721
+ collection: z.string(),
722
+ data: z.record(z.string(), z.unknown()).describe("Field values."),
723
+ slug: z.string().optional(),
724
+ changeSet: z.string().optional().describe("Attach to an existing change set (chs_)."),
725
+ changeSummary: z.string().max(500).optional()
726
+ },
727
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }
728
+ },
729
+ async (args) => {
730
+ try {
731
+ const { client, project } = registry.clientFor(args.project);
732
+ const entry = await client.entries.create(project, {
733
+ collection: args.collection,
734
+ data: args.data,
735
+ slug: args.slug,
736
+ changeSetId: args.changeSet,
737
+ changeSummary: args.changeSummary
738
+ });
739
+ return ok(`Created entry ${entry.id} (${entry.status}).`, { entry });
740
+ } catch (error) {
741
+ return fail(error);
742
+ }
743
+ }
744
+ );
745
+ server.registerTool(
746
+ "myna_update_entry",
747
+ {
748
+ title: "Update entry",
749
+ description: "Update a draft entry. Does not publish.",
750
+ inputSchema: {
751
+ ...projectArg,
752
+ entry: z.string().describe("collection/slug or ent_ id."),
753
+ data: z.record(z.string(), z.unknown()).optional(),
754
+ slug: z.string().optional(),
755
+ expectedRevisionId: z.string().optional().describe("Optimistic concurrency guard."),
756
+ changeSet: z.string().optional(),
757
+ changeSummary: z.string().max(500).optional()
758
+ },
759
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }
760
+ },
761
+ async (args) => {
762
+ try {
763
+ const { client, project } = registry.clientFor(args.project);
764
+ const id = await resolveEntryId(client, project, args.entry);
765
+ const entry = await client.entries.update(project, id, {
766
+ data: args.data,
767
+ slug: args.slug,
768
+ expectedRevisionId: args.expectedRevisionId,
769
+ changeSetId: args.changeSet,
770
+ changeSummary: args.changeSummary
771
+ });
772
+ return ok(`Updated entry ${entry.id} (${entry.status}).`, { entry });
773
+ } catch (error) {
774
+ return fail(error);
775
+ }
776
+ }
777
+ );
778
+ server.registerTool(
779
+ "myna_delete_entry",
780
+ {
781
+ title: "Delete entry",
782
+ description: "Stage an entry deletion on a change set. Requires confirm=true. Takes effect only on publish.",
783
+ inputSchema: {
784
+ ...projectArg,
785
+ entry: z.string(),
786
+ changeSet: z.string().optional(),
787
+ confirm: z.literal(true).describe("Must be true to proceed.")
788
+ },
789
+ annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true }
790
+ },
791
+ async (args) => {
792
+ try {
793
+ const { client, project } = registry.clientFor(args.project);
794
+ const id = await resolveEntryId(client, project, args.entry);
795
+ const result = await client.entries.delete(project, id, args.changeSet);
796
+ return ok(`Staged delete of ${id} on ${result.changeSetId}.`, result);
797
+ } catch (error) {
798
+ return fail(error);
799
+ }
800
+ }
801
+ );
802
+ server.registerTool(
803
+ "myna_upload_asset",
804
+ {
805
+ title: "Upload asset",
806
+ description: "Upload an asset from base64 bytes or a local file path via the presigned flow.",
807
+ inputSchema: {
808
+ ...projectArg,
809
+ filename: z.string().describe("Display filename, e.g. cover.png."),
810
+ base64: z.string().optional().describe("Base64-encoded file bytes."),
811
+ path: z.string().optional().describe("Local filesystem path (stdio hosts only)."),
812
+ contentType: z.string().optional()
813
+ },
814
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }
815
+ },
816
+ async (args) => {
817
+ try {
818
+ const { client, project } = registry.clientFor(args.project);
819
+ if (!args.base64 && !args.path) throw new Error("Provide either `base64` or `path`.");
820
+ const input = args.base64 ? new Uint8Array(Buffer.from(args.base64, "base64")) : args.path;
821
+ const asset = await client.assets.upload(project, input, {
822
+ filename: args.filename,
823
+ contentType: args.contentType
824
+ });
825
+ return ok(`Uploaded ${asset.displayFilename} (${asset.id}).`, { asset });
826
+ } catch (error) {
827
+ return fail(error);
828
+ }
829
+ }
830
+ );
831
+ server.registerTool(
832
+ "myna_validate_change_set",
833
+ {
834
+ title: "Validate change set",
835
+ description: "Validate every changed resource in a change set against the deployed schemas.",
836
+ inputSchema: { ...projectArg, changeSet: z.string() },
837
+ annotations: { readOnlyHint: true, openWorldHint: true }
838
+ },
839
+ async (args) => {
840
+ try {
841
+ const { client, project } = registry.clientFor(args.project);
842
+ const result = await client.changeSets.validate(project, args.changeSet);
843
+ return ok(result.valid ? "Change set is valid." : `Invalid: ${result.errors.length} error(s).`, result);
844
+ } catch (error) {
845
+ return fail(error);
846
+ }
847
+ }
848
+ );
849
+ server.registerTool(
850
+ "myna_create_preview",
851
+ {
852
+ title: "Create preview",
853
+ description: "Create one preview URL for a change set (or entry). Never publishes.",
854
+ inputSchema: {
855
+ ...projectArg,
856
+ changeSet: z.string().optional().describe("Change set id (chs_)."),
857
+ entry: z.string().optional().describe("Entry id (ent_)."),
858
+ expiresInSeconds: z.number().int().min(60).max(604800).optional()
859
+ },
860
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }
861
+ },
862
+ async (args) => {
863
+ try {
864
+ if (Boolean(args.changeSet) === Boolean(args.entry)) {
865
+ throw new Error("Provide exactly one of `changeSet` or `entry`.");
866
+ }
867
+ const { client, project } = registry.clientFor(args.project);
868
+ const preview = await client.previews.create(project, {
869
+ changeSetId: args.changeSet,
870
+ entryId: args.entry,
871
+ expiresInSeconds: args.expiresInSeconds
872
+ });
873
+ return ok(`Preview: ${preview.url}`, { preview });
874
+ } catch (error) {
875
+ return fail(error);
876
+ }
877
+ }
878
+ );
879
+ server.registerTool(
880
+ "myna_publish_change_set",
881
+ {
882
+ title: "Publish change set",
883
+ description: "Atomically publish a change set. Requires confirm=true and content:publish scope.",
884
+ inputSchema: { ...projectArg, changeSet: z.string(), confirm: z.literal(true).describe("Must be true.") },
885
+ annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true }
886
+ },
887
+ async (args) => {
888
+ try {
889
+ const { client, project } = registry.clientFor(args.project);
890
+ const result = await client.changeSets.publish(project, args.changeSet);
891
+ return ok(`Published ${result.publishedEntryIds.length} entr(y/ies).`, result);
892
+ } catch (error) {
893
+ return fail(error);
894
+ }
895
+ }
896
+ );
897
+ server.registerTool(
898
+ "myna_get_entry_revisions",
899
+ {
900
+ title: "Get entry revisions",
901
+ description: "List the immutable revisions of an entry.",
902
+ inputSchema: { ...projectArg, entry: z.string() },
903
+ annotations: { readOnlyHint: true, openWorldHint: true }
904
+ },
905
+ async (args) => {
906
+ try {
907
+ const { client, project } = registry.clientFor(args.project);
908
+ const id = await resolveEntryId(client, project, args.entry);
909
+ const revisions = await client.entries.revisions(project, id);
910
+ return ok(`${revisions.length} revision(s).`, { revisions });
911
+ } catch (error) {
912
+ return fail(error);
913
+ }
914
+ }
915
+ );
916
+ server.registerTool(
917
+ "myna_restore_revision",
918
+ {
919
+ title: "Restore revision",
920
+ description: "Restore a historical revision into a new draft revision. Requires confirm=true.",
921
+ inputSchema: {
922
+ ...projectArg,
923
+ entry: z.string(),
924
+ revision: z.string().describe("Revision id (rev_)."),
925
+ confirm: z.literal(true).describe("Must be true.")
926
+ },
927
+ annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true }
928
+ },
929
+ async (args) => {
930
+ try {
931
+ const { client, project } = registry.clientFor(args.project);
932
+ const id = await resolveEntryId(client, project, args.entry);
933
+ const result = await client.entries.restoreRevision(project, id, args.revision);
934
+ return ok(`Restored into ${result.revision.id}.`, result);
935
+ } catch (error) {
936
+ return fail(error);
937
+ }
938
+ }
939
+ );
940
+ }
941
+
942
+ // src/resources.ts
943
+ import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
944
+ function registerResources(server, registry) {
945
+ const json = (uri, value) => ({
946
+ contents: [{ uri, mimeType: "application/json", text: JSON.stringify(value, null, 2) }]
947
+ });
948
+ server.registerResource(
949
+ "projects",
950
+ "myna://projects",
951
+ { title: "Projects", description: "Projects in the default organization.", mimeType: "application/json" },
952
+ async (uri) => {
953
+ const { client } = registry.clientFor();
954
+ const org = registry.organizationFor();
955
+ const projects = await client.projects.list(org);
956
+ return json(uri.href, { organization: org, projects });
957
+ }
958
+ );
959
+ server.registerResource(
960
+ "collections",
961
+ new ResourceTemplate("myna://{project}/collections", { list: void 0 }),
962
+ { title: "Collections", description: "Deployed collections for a project.", mimeType: "application/json" },
963
+ async (uri, variables) => {
964
+ const project = String(variables.project);
965
+ const { client, project: ref } = registry.clientFor(project);
966
+ const collections = await client.schema.collections(ref);
967
+ return json(uri.href, { project: ref, collections });
968
+ }
969
+ );
970
+ server.registerResource(
971
+ "collection-schema",
972
+ new ResourceTemplate("myna://{project}/collection/{key}", { list: void 0 }),
973
+ { title: "Collection schema", description: "Latest canonical schema for a collection.", mimeType: "application/json" },
974
+ async (uri, variables) => {
975
+ const { client, project } = registry.clientFor(String(variables.project));
976
+ const versions = await client.schema.versions(project, String(variables.key));
977
+ const latest = versions[0];
978
+ return json(uri.href, { collection: String(variables.key), version: latest?.version ?? null, schema: latest?.schemaJson ?? null });
979
+ }
980
+ );
981
+ server.registerResource(
982
+ "entry",
983
+ new ResourceTemplate("myna://{project}/entry/{entry}", { list: void 0 }),
984
+ { title: "Entry", description: "An entry's current representation.", mimeType: "application/json" },
985
+ async (uri, variables) => {
986
+ const { client, project } = registry.clientFor(String(variables.project));
987
+ const entry = await client.entries.get(project, String(variables.entry));
988
+ return json(uri.href, { entry });
989
+ }
990
+ );
991
+ server.registerResource(
992
+ "entry-revisions",
993
+ new ResourceTemplate("myna://{project}/entry/{entry}/revisions", { list: void 0 }),
994
+ { title: "Entry revisions", description: "Immutable revisions for an entry.", mimeType: "application/json" },
995
+ async (uri, variables) => {
996
+ const { client, project } = registry.clientFor(String(variables.project));
997
+ const revisions = await client.entries.revisions(project, String(variables.entry));
998
+ return json(uri.href, { entry: String(variables.entry), revisions });
999
+ }
1000
+ );
1001
+ server.registerResource(
1002
+ "change-set-validation",
1003
+ new ResourceTemplate("myna://{project}/change-set/{changeSet}/validation", { list: void 0 }),
1004
+ { title: "Change-set validation", description: "Validation result for a change set.", mimeType: "application/json" },
1005
+ async (uri, variables) => {
1006
+ const { client, project } = registry.clientFor(String(variables.project));
1007
+ const result = await client.changeSets.validate(project, String(variables.changeSet));
1008
+ return json(uri.href, result);
1009
+ }
1010
+ );
1011
+ }
1012
+
1013
+ // src/server.ts
1014
+ var SERVER_NAME = "myna";
1015
+ var SERVER_VERSION = "0.1.0";
1016
+ function createServer() {
1017
+ return createServerForRegistry(new ProjectRegistry());
1018
+ }
1019
+ function createServerForRegistry(registry) {
1020
+ const server = new McpServer2(
1021
+ { name: SERVER_NAME, version: SERVER_VERSION },
1022
+ {
1023
+ capabilities: { tools: {}, resources: {} },
1024
+ instructions: "Myna MCP server: manage content, schemas, assets, change sets, and previews. The primary flow is: create a change set, apply entry/asset operations, validate, then create one preview URL. Publishing is a separate, confirmation-gated action."
1025
+ }
1026
+ );
1027
+ registerTools(server, registry);
1028
+ registerResources(server, registry);
1029
+ return server;
1030
+ }
1031
+
1032
+ // src/http.ts
1033
+ import { createServer as createHttpServer } from "http";
1034
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
1035
+ function readBody(req) {
1036
+ return new Promise((resolve, reject) => {
1037
+ const chunks = [];
1038
+ req.on("data", (c) => chunks.push(c));
1039
+ req.on("end", () => {
1040
+ const raw = Buffer.concat(chunks).toString("utf8");
1041
+ if (!raw) return resolve(void 0);
1042
+ try {
1043
+ resolve(JSON.parse(raw));
1044
+ } catch (error) {
1045
+ reject(error);
1046
+ }
1047
+ });
1048
+ req.on("error", reject);
1049
+ });
1050
+ }
1051
+ function bearer(req) {
1052
+ const header = req.headers.authorization;
1053
+ return header?.startsWith("Bearer ") ? header.slice(7).trim() : void 0;
1054
+ }
1055
+ async function runHttp(options) {
1056
+ const registry = new ProjectRegistry();
1057
+ const path = options.path ?? "/mcp";
1058
+ const httpServer = createHttpServer((req, res) => {
1059
+ void handle(req, res).catch((error) => {
1060
+ if (!res.headersSent) res.writeHead(500, { "content-type": "application/json" });
1061
+ res.end(JSON.stringify({ jsonrpc: "2.0", error: { code: -32603, message: String(error) }, id: null }));
1062
+ });
1063
+ });
1064
+ async function handle(req, res) {
1065
+ const url = new URL(req.url ?? "/", "http://localhost");
1066
+ if (url.pathname === "/health") {
1067
+ res.writeHead(200, { "content-type": "application/json" }).end(JSON.stringify({ ok: true }));
1068
+ return;
1069
+ }
1070
+ if (url.pathname !== path) {
1071
+ res.writeHead(404, { "content-type": "application/json" }).end(JSON.stringify({ error: "not found" }));
1072
+ return;
1073
+ }
1074
+ if (options.authToken && bearer(req) !== options.authToken) {
1075
+ res.writeHead(401, { "content-type": "application/json" }).end(JSON.stringify({ error: "unauthorized" }));
1076
+ return;
1077
+ }
1078
+ if (req.method !== "POST") {
1079
+ res.writeHead(405, { "content-type": "application/json", allow: "POST" }).end(
1080
+ JSON.stringify({ jsonrpc: "2.0", error: { code: -32e3, message: "Method not allowed." }, id: null })
1081
+ );
1082
+ return;
1083
+ }
1084
+ const body = await readBody(req);
1085
+ const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: void 0 });
1086
+ const server = createServerForRegistry(registry);
1087
+ res.on("close", () => {
1088
+ void transport.close();
1089
+ void server.close();
1090
+ });
1091
+ await server.connect(transport);
1092
+ await transport.handleRequest(req, res, body);
1093
+ }
1094
+ await new Promise((resolve) => httpServer.listen(options.port, resolve));
1095
+ process.stderr.write(`myna-mcp: streamable HTTP transport listening on :${options.port}${path}
1096
+ `);
1097
+ }
1098
+
1099
+ // src/main.ts
1100
+ async function main(argv = process.argv.slice(2)) {
1101
+ const useHttp = argv.includes("--http") || process.env.MYNA_MCP_TRANSPORT === "http";
1102
+ if (useHttp) {
1103
+ const port = Number(process.env.MYNA_MCP_PORT ?? process.env.PORT ?? 3333);
1104
+ const authToken = process.env.MYNA_MCP_TOKEN ?? process.env.MYNA_TOKEN;
1105
+ await runHttp({ port, authToken });
1106
+ return;
1107
+ }
1108
+ const server = createServer();
1109
+ const transport = new StdioServerTransport();
1110
+ await server.connect(transport);
1111
+ process.stderr.write("myna-mcp: stdio transport ready\n");
1112
+ }
1113
+ export {
1114
+ createServer,
1115
+ main,
1116
+ runHttp
1117
+ };
1118
+ //# sourceMappingURL=main.js.map