@buildinternet/uploads 0.35.0 → 0.36.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/commands/mcp.js +16 -3
- package/dist/github-gh.d.ts +7 -0
- package/dist/github-gh.js +70 -22
- package/dist/mcp/server.d.ts +26 -4
- package/dist/mcp/server.js +56 -108
- package/package.json +2 -1
- package/dist/mcp/stdio.d.ts +0 -3
- package/dist/mcp/stdio.js +0 -14
package/dist/commands/mcp.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import { serveStdio } from "@modelcontextprotocol/server/stdio";
|
|
2
|
+
import { AjvJsonSchemaValidator } from "@modelcontextprotocol/server/validators/ajv";
|
|
1
3
|
import { parseCommandArgs } from "../cli-args.js";
|
|
2
4
|
import { resolveApiUrl } from "../config.js";
|
|
3
5
|
import { createMcpServer } from "../mcp/server.js";
|
|
4
|
-
import { serveStdio } from "../mcp/stdio.js";
|
|
5
6
|
import { createUploadsMcpTools } from "../mcp/tools.js";
|
|
6
7
|
import { packageVersion } from "../package-version.js";
|
|
7
8
|
import { writeCommandHelp } from "../cli-style.js";
|
|
@@ -29,11 +30,23 @@ export async function runMcp(args, opts, help = false) {
|
|
|
29
30
|
writeCommandHelp(MCP_HELP);
|
|
30
31
|
return 0;
|
|
31
32
|
}
|
|
32
|
-
|
|
33
|
+
// Ajv is the right provider here and only here: it compiles schemas at
|
|
34
|
+
// runtime, which Node allows and workerd does not (apps/mcp passes the
|
|
35
|
+
// `@cfworker/json-schema` provider instead).
|
|
36
|
+
const validator = new AjvJsonSchemaValidator();
|
|
37
|
+
const handle = serveStdio(() => createMcpServer({
|
|
33
38
|
serverInfo: { name: "uploads", version: packageVersion() },
|
|
34
39
|
tools: createUploadsMcpTools({ globals: opts.globals }),
|
|
35
40
|
apiUrl: resolveApiUrl(opts.globals),
|
|
41
|
+
validator,
|
|
42
|
+
}));
|
|
43
|
+
// `serveStdio` hands back only a teardown handle, so the command owns the
|
|
44
|
+
// wait: serving ends when the client closes our stdin, which is what the
|
|
45
|
+
// previous readline loop returned on.
|
|
46
|
+
await new Promise((resolve) => {
|
|
47
|
+
process.stdin.once("end", resolve);
|
|
48
|
+
process.stdin.once("close", resolve);
|
|
36
49
|
});
|
|
37
|
-
await
|
|
50
|
+
await handle.close();
|
|
38
51
|
return 0;
|
|
39
52
|
}
|
package/dist/github-gh.d.ts
CHANGED
|
@@ -74,6 +74,13 @@ export declare function ghMetadataFromTargetWithTitle(target: GhTarget, run?: Co
|
|
|
74
74
|
* deleted best-effort via `gh api -X DELETE`; a failed delete is swallowed
|
|
75
75
|
* and never fails the caller's command, and the next sync retries anyway.
|
|
76
76
|
*
|
|
77
|
+
* A create additionally re-hunts once it has written (issue #553, mirroring
|
|
78
|
+
* the bot path): find-or-create is not atomic, so a concurrent writer can
|
|
79
|
+
* create its own comment in the same window. Both writers independently agree
|
|
80
|
+
* the OLDEST marker comment wins, fold their body into it and delete the rest
|
|
81
|
+
* — including their own create — so the race converges instead of leaving a
|
|
82
|
+
* stale orphan behind for a PR that never syncs again.
|
|
83
|
+
*
|
|
77
84
|
* On why this duplicates the bot path rather than deferring to it: the gh
|
|
78
85
|
* fallback is a supported path, not a stopgap, so it is held at behavioral
|
|
79
86
|
* parity deliberately. This file already reimplements the hunt, the legacy
|
package/dist/github-gh.js
CHANGED
|
@@ -266,6 +266,13 @@ function findManagedComment(target, run, marker) {
|
|
|
266
266
|
* deleted best-effort via `gh api -X DELETE`; a failed delete is swallowed
|
|
267
267
|
* and never fails the caller's command, and the next sync retries anyway.
|
|
268
268
|
*
|
|
269
|
+
* A create additionally re-hunts once it has written (issue #553, mirroring
|
|
270
|
+
* the bot path): find-or-create is not atomic, so a concurrent writer can
|
|
271
|
+
* create its own comment in the same window. Both writers independently agree
|
|
272
|
+
* the OLDEST marker comment wins, fold their body into it and delete the rest
|
|
273
|
+
* — including their own create — so the race converges instead of leaving a
|
|
274
|
+
* stale orphan behind for a PR that never syncs again.
|
|
275
|
+
*
|
|
269
276
|
* On why this duplicates the bot path rather than deferring to it: the gh
|
|
270
277
|
* fallback is a supported path, not a stopgap, so it is held at behavioral
|
|
271
278
|
* parity deliberately. This file already reimplements the hunt, the legacy
|
|
@@ -281,26 +288,9 @@ function findManagedComment(target, run, marker) {
|
|
|
281
288
|
export function upsertAttachmentsComment(target, body, run = execRunner, marker = ATTACHMENTS_MARKER, opts = {}) {
|
|
282
289
|
const createIfMissing = opts.createIfMissing ?? true;
|
|
283
290
|
const { comment: existing, extras } = findManagedComment(target, run, marker);
|
|
284
|
-
const deleteExtras = () => {
|
|
285
|
-
for (const extra of extras ?? []) {
|
|
286
|
-
try {
|
|
287
|
-
run("gh", ["api", `repos/${target.repo}/issues/comments/${extra.id}`, "-X", "DELETE"]);
|
|
288
|
-
}
|
|
289
|
-
catch {
|
|
290
|
-
// Best effort only — a failed delete must never fail the caller's command.
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
};
|
|
294
291
|
if (existing) {
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
`repos/${target.repo}/issues/comments/${existing.id}`,
|
|
298
|
-
"-X",
|
|
299
|
-
"PATCH",
|
|
300
|
-
"-F",
|
|
301
|
-
"body=@-",
|
|
302
|
-
], body);
|
|
303
|
-
deleteExtras();
|
|
292
|
+
patchComment(target, run, existing.id, body);
|
|
293
|
+
deleteComments(target, run, extras);
|
|
304
294
|
return { action: "updated" };
|
|
305
295
|
}
|
|
306
296
|
// Patch-only (createIfMissing false, i.e. an empty body) with no existing
|
|
@@ -308,7 +298,65 @@ export function upsertAttachmentsComment(target, body, run = execRunner, marker
|
|
|
308
298
|
if (!createIfMissing)
|
|
309
299
|
return { action: "skipped" };
|
|
310
300
|
// No existing marker hit means `extras` is necessarily empty here (see
|
|
311
|
-
// `findManagedComment`) — nothing to delete
|
|
312
|
-
run("gh", ["api", `repos/${target.repo}/issues/${target.num}/comments`, "-F", "body=@-"], body);
|
|
313
|
-
return { action: "created" };
|
|
301
|
+
// `findManagedComment`) — nothing to delete before the create.
|
|
302
|
+
const created = run("gh", ["api", `repos/${target.repo}/issues/${target.num}/comments`, "-F", "body=@-"], body);
|
|
303
|
+
return reconcileAfterCreate(target, body, run, marker, created) ?? { action: "created" };
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Re-hunt right after a create and collapse whatever a concurrent writer left
|
|
307
|
+
* behind (issue #553). Returns `{ action: "updated" }` when this run lost the
|
|
308
|
+
* race — its body has been folded into the older winning comment and its own
|
|
309
|
+
* create deleted — or null when nothing needed folding, including every
|
|
310
|
+
* failure: a verification problem must never fail a successful create.
|
|
311
|
+
*
|
|
312
|
+
* The winner is patched BEFORE any delete, so a failed fold leaves both
|
|
313
|
+
* comments (the next sync's hunt retries) rather than deleting the one that
|
|
314
|
+
* carries the current body.
|
|
315
|
+
*/
|
|
316
|
+
function reconcileAfterCreate(target, body, run, marker, createdRaw) {
|
|
317
|
+
let createdId;
|
|
318
|
+
try {
|
|
319
|
+
createdId = JSON.parse(createdRaw).id;
|
|
320
|
+
}
|
|
321
|
+
catch {
|
|
322
|
+
// `gh` printed something unparseable — fall through to the hunt, which
|
|
323
|
+
// identifies the winner on its own.
|
|
324
|
+
}
|
|
325
|
+
try {
|
|
326
|
+
const { comment: winner, extras } = findManagedComment(target, run, marker);
|
|
327
|
+
// `extras` is undefined in legacy mode, where a second hit may belong to
|
|
328
|
+
// another workspace — the adopt-only contract holds here too.
|
|
329
|
+
if (!winner || !extras?.length)
|
|
330
|
+
return null;
|
|
331
|
+
if (winner.id === createdId) {
|
|
332
|
+
// Ours is the oldest and already carries the body we just wrote — only
|
|
333
|
+
// the other writer's duplicate needs to go.
|
|
334
|
+
deleteComments(target, run, extras);
|
|
335
|
+
return null;
|
|
336
|
+
}
|
|
337
|
+
patchComment(target, run, winner.id, body);
|
|
338
|
+
deleteComments(target, run, extras);
|
|
339
|
+
return { action: "updated" };
|
|
340
|
+
}
|
|
341
|
+
catch {
|
|
342
|
+
// A failed listing or fold leaves the freshly created comment in place —
|
|
343
|
+
// correct content, one duplicate, healed by the next sync's hunt.
|
|
344
|
+
return null;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
/** PATCH one comment's body via stdin, so the body is never shell-interpolated. */
|
|
348
|
+
function patchComment(target, run, id, body) {
|
|
349
|
+
run("gh", ["api", `repos/${target.repo}/issues/comments/${id}`, "-X", "PATCH", "-F", "body=@-"], body);
|
|
350
|
+
}
|
|
351
|
+
/** Best-effort delete: a failed delete must never fail the caller's command,
|
|
352
|
+
* and the next sync's hunt retries anyway. */
|
|
353
|
+
function deleteComments(target, run, comments) {
|
|
354
|
+
for (const c of comments ?? []) {
|
|
355
|
+
try {
|
|
356
|
+
run("gh", ["api", `repos/${target.repo}/issues/comments/${c.id}`, "-X", "DELETE"]);
|
|
357
|
+
}
|
|
358
|
+
catch {
|
|
359
|
+
// Best effort only.
|
|
360
|
+
}
|
|
361
|
+
}
|
|
314
362
|
}
|
package/dist/mcp/server.d.ts
CHANGED
|
@@ -1,6 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP (Model Context Protocol) server core, built on the v2 TypeScript SDK
|
|
3
|
+
* (`@modelcontextprotocol/server`), which speaks spec `2026-07-28` and the
|
|
4
|
+
* 2025-era revisions side by side.
|
|
5
|
+
*
|
|
6
|
+
* Tools stay declarative: callers pass `McpTool[]` with hand-written JSON
|
|
7
|
+
* Schema and this module registers them with the SDK. That keeps the two tool
|
|
8
|
+
* sets (./tools.ts and apps/mcp/src/tools.ts) free of SDK imports and confines
|
|
9
|
+
* the dependency to this file.
|
|
10
|
+
*
|
|
11
|
+
* Runtime-agnostic, so the JSON Schema validator is injected rather than
|
|
12
|
+
* chosen here: the SDK bundles no provider in its root entry, and the two
|
|
13
|
+
* available ones are not interchangeable — Ajv compiles schemas at runtime and
|
|
14
|
+
* workerd rejects that, so Workers callers must pass the `@cfworker/json-schema`
|
|
15
|
+
* provider. See `@modelcontextprotocol/server/validators/{ajv,cf-worker}`.
|
|
16
|
+
*
|
|
17
|
+
* The stdio transport comes from `@modelcontextprotocol/server/stdio`; logs
|
|
18
|
+
* must never go to stdout.
|
|
19
|
+
*/
|
|
20
|
+
import { McpServer, type jsonSchemaValidator } from "@modelcontextprotocol/server";
|
|
1
21
|
export { appProp, canonicalMetaFromArgs, METADATA_DESCRIPTION, metadataArgWithCanonical, metadataProp, stateProp, optBool, optPosInt, optString, optStringArray, optStringRecord, usage, type ToolArgs, } from "./args.js";
|
|
2
22
|
export { ToolBatchError, batchFailureMessage } from "./batch-error.js";
|
|
3
23
|
export { mapBounded } from "../async.js";
|
|
24
|
+
export { McpServer, type jsonSchemaValidator };
|
|
4
25
|
export interface McpTool {
|
|
5
26
|
name: string;
|
|
6
27
|
description: string;
|
|
@@ -8,10 +29,6 @@ export interface McpTool {
|
|
|
8
29
|
inputSchema: Record<string, unknown>;
|
|
9
30
|
handler: (args: Record<string, unknown>) => Promise<unknown>;
|
|
10
31
|
}
|
|
11
|
-
export interface McpServer {
|
|
12
|
-
/** Handle one JSON-RPC line. Undefined for notifications / client responses. */
|
|
13
|
-
handleLine(line: string): Promise<string | undefined>;
|
|
14
|
-
}
|
|
15
32
|
export declare function createMcpServer(opts: {
|
|
16
33
|
serverInfo: {
|
|
17
34
|
name: string;
|
|
@@ -20,4 +37,9 @@ export declare function createMcpServer(opts: {
|
|
|
20
37
|
tools: McpTool[];
|
|
21
38
|
/** API base for telemetry (honors uploads --api-url). */
|
|
22
39
|
apiUrl?: string;
|
|
40
|
+
/**
|
|
41
|
+
* Runtime's JSON Schema validator. Node callers pass the Ajv provider;
|
|
42
|
+
* Workers callers must pass the `@cfworker/json-schema` one.
|
|
43
|
+
*/
|
|
44
|
+
validator: jsonSchemaValidator;
|
|
23
45
|
}): McpServer;
|
package/dist/mcp/server.js
CHANGED
|
@@ -1,58 +1,62 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* MCP (Model Context Protocol) server core, built on the v2 TypeScript SDK
|
|
3
|
+
* (`@modelcontextprotocol/server`), which speaks spec `2026-07-28` and the
|
|
4
|
+
* 2025-era revisions side by side.
|
|
3
5
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* Tools stay declarative: callers pass `McpTool[]` with hand-written JSON
|
|
7
|
+
* Schema and this module registers them with the SDK. That keeps the two tool
|
|
8
|
+
* sets (./tools.ts and apps/mcp/src/tools.ts) free of SDK imports and confines
|
|
9
|
+
* the dependency to this file.
|
|
10
|
+
*
|
|
11
|
+
* Runtime-agnostic, so the JSON Schema validator is injected rather than
|
|
12
|
+
* chosen here: the SDK bundles no provider in its root entry, and the two
|
|
13
|
+
* available ones are not interchangeable — Ajv compiles schemas at runtime and
|
|
14
|
+
* workerd rejects that, so Workers callers must pass the `@cfworker/json-schema`
|
|
15
|
+
* provider. See `@modelcontextprotocol/server/validators/{ajv,cf-worker}`.
|
|
16
|
+
*
|
|
17
|
+
* The stdio transport comes from `@modelcontextprotocol/server/stdio`; logs
|
|
18
|
+
* must never go to stdout.
|
|
9
19
|
*/
|
|
20
|
+
import { fromJsonSchema, McpServer, } from "@modelcontextprotocol/server";
|
|
10
21
|
import { UploadsError } from "../errors.js";
|
|
11
22
|
import { errorCodeFromUnknown, recordEvent } from "../telemetry.js";
|
|
12
23
|
import { ToolBatchError } from "./batch-error.js";
|
|
13
24
|
export { appProp, canonicalMetaFromArgs, METADATA_DESCRIPTION, metadataArgWithCanonical, metadataProp, stateProp, optBool, optPosInt, optString, optStringArray, optStringRecord, usage, } from "./args.js";
|
|
14
25
|
export { ToolBatchError, batchFailureMessage } from "./batch-error.js";
|
|
15
26
|
export { mapBounded } from "../async.js";
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
27
|
+
export { McpServer };
|
|
28
|
+
/**
|
|
29
|
+
* The tool catalog is fixed for the lifetime of a deploy, so a generous
|
|
30
|
+
* freshness hint is honest. `private` rather than `public` because the list is
|
|
31
|
+
* behind auth and, on the hosted worker, filtered by the caller's token
|
|
32
|
+
* scopes — a shared intermediary must never serve one caller's tool list to
|
|
33
|
+
* another.
|
|
34
|
+
*/
|
|
35
|
+
const TOOLS_LIST_CACHE_HINT = { ttlMs: 3_600_000, cacheScope: "private" };
|
|
24
36
|
/** Tool failures become tool results (isError), never JSON-RPC errors. */
|
|
25
37
|
function toolErrorText(err) {
|
|
26
38
|
if (err instanceof UploadsError)
|
|
27
39
|
return `${err.message} (${err.code})`;
|
|
28
40
|
return err instanceof Error ? err.message : String(err);
|
|
29
41
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
return errorResponse(id, -32602, "tool arguments must be an object");
|
|
40
|
-
}
|
|
42
|
+
/**
|
|
43
|
+
* Wraps a tool handler so its outcome becomes a `CallToolResult` and every
|
|
44
|
+
* call is recorded. A throw is reported to the client as an errored tool
|
|
45
|
+
* result rather than a protocol error, which is what lets an agent read the
|
|
46
|
+
* message and retry.
|
|
47
|
+
*/
|
|
48
|
+
function wrapHandler(tool, apiUrl) {
|
|
49
|
+
const command = `tool ${tool.name}`.slice(0, 120);
|
|
50
|
+
return async (args) => {
|
|
41
51
|
const start = Date.now();
|
|
42
|
-
const command = `tool ${tool.name}`.slice(0, 120);
|
|
43
52
|
try {
|
|
44
|
-
const result = await tool.handler(args);
|
|
45
|
-
recordEvent({
|
|
46
|
-
|
|
47
|
-
command,
|
|
48
|
-
exitCode: 0,
|
|
49
|
-
durationMs: Date.now() - start,
|
|
50
|
-
}, { apiUrl });
|
|
51
|
-
return response(id, {
|
|
53
|
+
const result = await tool.handler(args ?? {});
|
|
54
|
+
recordEvent({ surface: "mcp", command, exitCode: 0, durationMs: Date.now() - start }, { apiUrl });
|
|
55
|
+
return {
|
|
52
56
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
53
57
|
structuredContent: result,
|
|
54
58
|
isError: false,
|
|
55
|
-
}
|
|
59
|
+
};
|
|
56
60
|
}
|
|
57
61
|
catch (err) {
|
|
58
62
|
recordEvent({
|
|
@@ -65,82 +69,26 @@ export function createMcpServer(opts) {
|
|
|
65
69
|
// Multi-file total failure: keep structuredContent so agents see every
|
|
66
70
|
// per-file error, not only the first message string.
|
|
67
71
|
if (err instanceof ToolBatchError) {
|
|
68
|
-
return
|
|
69
|
-
content: [
|
|
70
|
-
{
|
|
71
|
-
type: "text",
|
|
72
|
-
text: JSON.stringify(err.structuredContent, null, 2),
|
|
73
|
-
},
|
|
74
|
-
],
|
|
72
|
+
return {
|
|
73
|
+
content: [{ type: "text", text: JSON.stringify(err.structuredContent, null, 2) }],
|
|
75
74
|
structuredContent: err.structuredContent,
|
|
76
75
|
isError: true,
|
|
77
|
-
}
|
|
76
|
+
};
|
|
78
77
|
}
|
|
79
|
-
return
|
|
80
|
-
content: [{ type: "text", text: toolErrorText(err) }],
|
|
81
|
-
isError: true,
|
|
82
|
-
});
|
|
78
|
+
return { content: [{ type: "text", text: toolErrorText(err) }], isError: true };
|
|
83
79
|
}
|
|
84
|
-
}
|
|
85
|
-
return {
|
|
86
|
-
async handleLine(line) {
|
|
87
|
-
let msg;
|
|
88
|
-
try {
|
|
89
|
-
msg = JSON.parse(line);
|
|
90
|
-
}
|
|
91
|
-
catch {
|
|
92
|
-
return errorResponse(null, -32700, "Parse error");
|
|
93
|
-
}
|
|
94
|
-
// JSON-RPC batching was removed from MCP: arrays are invalid requests.
|
|
95
|
-
if (typeof msg !== "object" || msg === null || Array.isArray(msg)) {
|
|
96
|
-
return errorResponse(null, -32600, "Invalid Request");
|
|
97
|
-
}
|
|
98
|
-
const record = msg;
|
|
99
|
-
const { method, params } = record;
|
|
100
|
-
// A response from the client (has result/error, no method): ignore.
|
|
101
|
-
if (method === undefined && ("result" in record || "error" in record))
|
|
102
|
-
return undefined;
|
|
103
|
-
const id = typeof record.id === "string" || typeof record.id === "number" ? record.id : null;
|
|
104
|
-
if (typeof method !== "string")
|
|
105
|
-
return errorResponse(id, -32600, "Invalid Request");
|
|
106
|
-
if (method.startsWith("notifications/"))
|
|
107
|
-
return undefined;
|
|
108
|
-
// A request without an id is a notification — never respond.
|
|
109
|
-
if (!("id" in record))
|
|
110
|
-
return undefined;
|
|
111
|
-
const p = (typeof params === "object" && params !== null && !Array.isArray(params) ? params : {});
|
|
112
|
-
try {
|
|
113
|
-
switch (method) {
|
|
114
|
-
case "initialize": {
|
|
115
|
-
const requested = typeof p.protocolVersion === "string" ? p.protocolVersion : "";
|
|
116
|
-
const protocolVersion = SUPPORTED_PROTOCOL_VERSIONS.has(requested)
|
|
117
|
-
? requested
|
|
118
|
-
: LATEST_PROTOCOL_VERSION;
|
|
119
|
-
return response(id, {
|
|
120
|
-
protocolVersion,
|
|
121
|
-
capabilities: { tools: {} },
|
|
122
|
-
serverInfo,
|
|
123
|
-
});
|
|
124
|
-
}
|
|
125
|
-
case "ping":
|
|
126
|
-
return response(id, {});
|
|
127
|
-
case "tools/list":
|
|
128
|
-
return response(id, {
|
|
129
|
-
tools: tools.map(({ name, description, inputSchema }) => ({
|
|
130
|
-
name,
|
|
131
|
-
description,
|
|
132
|
-
inputSchema,
|
|
133
|
-
})),
|
|
134
|
-
});
|
|
135
|
-
case "tools/call":
|
|
136
|
-
return await callTool(id, p);
|
|
137
|
-
default:
|
|
138
|
-
return errorResponse(id, -32601, `method not found: ${method}`);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
catch (err) {
|
|
142
|
-
return errorResponse(id, -32603, err instanceof Error ? err.message : String(err));
|
|
143
|
-
}
|
|
144
|
-
},
|
|
145
80
|
};
|
|
146
81
|
}
|
|
82
|
+
export function createMcpServer(opts) {
|
|
83
|
+
const { serverInfo, tools, apiUrl, validator } = opts;
|
|
84
|
+
const server = new McpServer(serverInfo, {
|
|
85
|
+
cacheHints: { "tools/list": TOOLS_LIST_CACHE_HINT },
|
|
86
|
+
});
|
|
87
|
+
for (const tool of tools) {
|
|
88
|
+
server.registerTool(tool.name, {
|
|
89
|
+
description: tool.description,
|
|
90
|
+
inputSchema: fromJsonSchema(tool.inputSchema, validator),
|
|
91
|
+
}, wrapHandler(tool, apiUrl));
|
|
92
|
+
}
|
|
93
|
+
return server;
|
|
94
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@buildinternet/uploads",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.36.0",
|
|
4
4
|
"description": "CLI and client for uploads.sh — workspace-scoped image hosting for GitHub embeds",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -57,6 +57,7 @@
|
|
|
57
57
|
"provenance": true
|
|
58
58
|
},
|
|
59
59
|
"dependencies": {
|
|
60
|
+
"@modelcontextprotocol/server": "^2.0.0",
|
|
60
61
|
"exif-reader": "^2.0.3",
|
|
61
62
|
"opentype.js": "^2.0.0",
|
|
62
63
|
"perfect-freehand": "^1.2.3",
|
package/dist/mcp/stdio.d.ts
DELETED
package/dist/mcp/stdio.js
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
/** Stdio transport for the MCP server core (Node-only; the core itself is runtime-agnostic). */
|
|
2
|
-
import { createInterface } from "node:readline";
|
|
3
|
-
import { writeStdout } from "../io.js";
|
|
4
|
-
/** Serve the MCP protocol on stdin/stdout; resolves when stdin ends. */
|
|
5
|
-
export async function serveStdio(server) {
|
|
6
|
-
const rl = createInterface({ input: process.stdin, crlfDelay: Infinity, terminal: false });
|
|
7
|
-
for await (const line of rl) {
|
|
8
|
-
if (!line.trim())
|
|
9
|
-
continue;
|
|
10
|
-
const out = await server.handleLine(line);
|
|
11
|
-
if (out !== undefined)
|
|
12
|
-
await writeStdout(out + "\n");
|
|
13
|
-
}
|
|
14
|
-
}
|