@upstash/mcp-server 0.2.1 → 0.2.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 +33 -136
- package/dist/index.js +312 -203
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -10,6 +10,12 @@ import { createServer } from "http";
|
|
|
10
10
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
11
11
|
|
|
12
12
|
// src/log.ts
|
|
13
|
+
import { appendFileSync } from "fs";
|
|
14
|
+
import path from "path";
|
|
15
|
+
var debugLogPath = null;
|
|
16
|
+
function initDebugLog(projectRoot) {
|
|
17
|
+
debugLogPath = path.resolve(projectRoot, "upstash-debug.log");
|
|
18
|
+
}
|
|
13
19
|
function log(...args) {
|
|
14
20
|
const msg = `[DEBUG ${(/* @__PURE__ */ new Date()).toISOString()}] ${args.map((arg) => typeof arg === "string" ? arg : JSON.stringify(arg)).join(" ")}
|
|
15
21
|
`;
|
|
@@ -17,11 +23,25 @@ function log(...args) {
|
|
|
17
23
|
logs.push(msg);
|
|
18
24
|
}
|
|
19
25
|
process.stderr.write(msg);
|
|
26
|
+
if (debugLogPath) {
|
|
27
|
+
try {
|
|
28
|
+
appendFileSync(debugLogPath, msg);
|
|
29
|
+
} catch {
|
|
30
|
+
}
|
|
31
|
+
}
|
|
20
32
|
}
|
|
21
33
|
var logsStore = /* @__PURE__ */ new Map();
|
|
22
34
|
|
|
23
35
|
// src/tools/utils.ts
|
|
24
36
|
import { z } from "zod";
|
|
37
|
+
|
|
38
|
+
// src/tools/helpers.ts
|
|
39
|
+
var json = (value) => typeof value === "string" ? value : JSON.stringify(value, null, 2);
|
|
40
|
+
function tool(t) {
|
|
41
|
+
return t;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// src/tools/utils.ts
|
|
25
45
|
var utilTools = {
|
|
26
46
|
util_timestamps_to_date: tool({
|
|
27
47
|
description: `Use this tool to convert a timestamp to a human-readable date`,
|
|
@@ -49,7 +69,8 @@ import { z as z2 } from "zod";
|
|
|
49
69
|
// src/config.ts
|
|
50
70
|
var config = {
|
|
51
71
|
apiKey: "",
|
|
52
|
-
email: ""
|
|
72
|
+
email: "",
|
|
73
|
+
disableTelemetry: false
|
|
53
74
|
};
|
|
54
75
|
|
|
55
76
|
// src/middlewares.ts
|
|
@@ -91,27 +112,56 @@ var applyMiddlewares = async (req, func) => {
|
|
|
91
112
|
return next();
|
|
92
113
|
};
|
|
93
114
|
|
|
115
|
+
// src/version.ts
|
|
116
|
+
var VERSION = "v0.2.2";
|
|
117
|
+
|
|
118
|
+
// src/telemetry.ts
|
|
119
|
+
function getRuntime() {
|
|
120
|
+
var _a, _b, _c;
|
|
121
|
+
if (globalThis.Bun !== void 0) {
|
|
122
|
+
return `bun@${globalThis.Bun.version}`;
|
|
123
|
+
}
|
|
124
|
+
if (globalThis.Deno !== void 0) {
|
|
125
|
+
const denoVersion = (_b = (_a = globalThis.Deno) == null ? void 0 : _a.version) == null ? void 0 : _b.deno;
|
|
126
|
+
return denoVersion ? `deno@${denoVersion}` : "deno";
|
|
127
|
+
}
|
|
128
|
+
if (typeof process !== "undefined" && ((_c = process.versions) == null ? void 0 : _c.node)) {
|
|
129
|
+
return `node@${process.versions.node}`;
|
|
130
|
+
}
|
|
131
|
+
return "unknown";
|
|
132
|
+
}
|
|
133
|
+
function getPlatform() {
|
|
134
|
+
if (typeof process !== "undefined" && process.platform) {
|
|
135
|
+
return process.platform;
|
|
136
|
+
}
|
|
137
|
+
return "unknown";
|
|
138
|
+
}
|
|
139
|
+
var telemetry = {
|
|
140
|
+
runtime: getRuntime(),
|
|
141
|
+
platform: getPlatform(),
|
|
142
|
+
sdk: `@upstash/mcp-server@${VERSION}`
|
|
143
|
+
};
|
|
144
|
+
|
|
94
145
|
// src/http.ts
|
|
95
|
-
import fetch from "node-fetch";
|
|
96
146
|
var HttpClient = class {
|
|
97
147
|
constructor(config2) {
|
|
98
148
|
this.baseUrl = config2.baseUrl.replace(/\/$/, "");
|
|
99
149
|
this.qstashToken = config2.qstashToken;
|
|
100
150
|
}
|
|
101
|
-
async get(
|
|
102
|
-
return this.requestWithMiddleware({ method: "GET", path, query });
|
|
151
|
+
async get(path2, query) {
|
|
152
|
+
return this.requestWithMiddleware({ method: "GET", path: path2, query });
|
|
103
153
|
}
|
|
104
|
-
async post(
|
|
105
|
-
return this.requestWithMiddleware({ method: "POST", path, body, headers });
|
|
154
|
+
async post(path2, body, headers) {
|
|
155
|
+
return this.requestWithMiddleware({ method: "POST", path: path2, body, headers });
|
|
106
156
|
}
|
|
107
|
-
async put(
|
|
108
|
-
return this.requestWithMiddleware({ method: "PUT", path, body, headers });
|
|
157
|
+
async put(path2, body, headers) {
|
|
158
|
+
return this.requestWithMiddleware({ method: "PUT", path: path2, body, headers });
|
|
109
159
|
}
|
|
110
|
-
async patch(
|
|
111
|
-
return this.requestWithMiddleware({ method: "PATCH", path, body });
|
|
160
|
+
async patch(path2, body) {
|
|
161
|
+
return this.requestWithMiddleware({ method: "PATCH", path: path2, body });
|
|
112
162
|
}
|
|
113
|
-
async delete(
|
|
114
|
-
return this.requestWithMiddleware({ method: "DELETE", path, body });
|
|
163
|
+
async delete(path2, body) {
|
|
164
|
+
return this.requestWithMiddleware({ method: "DELETE", path: path2, body });
|
|
115
165
|
}
|
|
116
166
|
async requestWithMiddleware(req) {
|
|
117
167
|
const res = await applyMiddlewares(req, async (req2) => {
|
|
@@ -145,11 +195,17 @@ var HttpClient = class {
|
|
|
145
195
|
const token = [config.email, config.apiKey].join(":");
|
|
146
196
|
authHeader = `Basic ${Buffer.from(token).toString("base64")}`;
|
|
147
197
|
}
|
|
198
|
+
const telemetryHeaders = config.disableTelemetry ? {} : {
|
|
199
|
+
"Upstash-Telemetry-Runtime": telemetry.runtime,
|
|
200
|
+
"Upstash-Telemetry-Platform": telemetry.platform,
|
|
201
|
+
"Upstash-Telemetry-Sdk": telemetry.sdk
|
|
202
|
+
};
|
|
148
203
|
const init = {
|
|
149
204
|
method: req.method,
|
|
150
205
|
headers: {
|
|
151
206
|
"Content-Type": "application/json",
|
|
152
207
|
Authorization: authHeader,
|
|
208
|
+
...telemetryHeaders,
|
|
153
209
|
...req.headers
|
|
154
210
|
}
|
|
155
211
|
};
|
|
@@ -269,7 +325,6 @@ var redisBackupTools = {
|
|
|
269
325
|
|
|
270
326
|
// src/tools/redis/command.ts
|
|
271
327
|
import { z as z3 } from "zod";
|
|
272
|
-
import fetch2 from "node-fetch";
|
|
273
328
|
var redisCommandTools = {
|
|
274
329
|
redis_database_run_redis_commands: tool({
|
|
275
330
|
description: `Run one or more Redis commands on a specific Upstash redis database. Either provide database_id OR both database_rest_url and database_rest_token.
|
|
@@ -308,7 +363,7 @@ NOTE: Multiple commands will be executed as a pipeline for better performance.`,
|
|
|
308
363
|
const isSingleCommand = commands.length === 1;
|
|
309
364
|
const url = isSingleCommand ? restUrl : restUrl + "/pipeline";
|
|
310
365
|
const body = isSingleCommand ? JSON.stringify(commands[0]) : JSON.stringify(commands);
|
|
311
|
-
const req = await
|
|
366
|
+
const req = await fetch(url, {
|
|
312
367
|
method: "POST",
|
|
313
368
|
body,
|
|
314
369
|
headers: {
|
|
@@ -525,6 +580,7 @@ var parseUsageData = (data) => {
|
|
|
525
580
|
if (!Array.isArray(data)) return "INVALID DATA";
|
|
526
581
|
if (data.length === 0 || data.length === 1) return "NO DATA";
|
|
527
582
|
const filteredData = data.filter((d) => d.x && d.y);
|
|
583
|
+
if (filteredData.length === 0) return "NO DATA";
|
|
528
584
|
return {
|
|
529
585
|
start: filteredData[0].x,
|
|
530
586
|
// last one can be null, so use the second last
|
|
@@ -543,57 +599,76 @@ var redisTools = {
|
|
|
543
599
|
};
|
|
544
600
|
|
|
545
601
|
// src/tools/qstash/qstash.ts
|
|
546
|
-
import { z as
|
|
602
|
+
import { z as z6 } from "zod";
|
|
547
603
|
|
|
548
604
|
// src/tools/qstash/utils.ts
|
|
549
|
-
var
|
|
550
|
-
var
|
|
551
|
-
|
|
605
|
+
var LOCAL_QSTASH_TOKEN = "eyJVc2VySUQiOiJkZWZhdWx0VXNlciIsIlBhc3N3b3JkIjoiZGVmYXVsdFBhc3N3b3JkIn0=";
|
|
606
|
+
var REGION_URLS = {
|
|
607
|
+
eu: "https://qstash-eu-central-1.upstash.io",
|
|
608
|
+
us: "https://qstash-us-east-1.upstash.io"
|
|
609
|
+
};
|
|
610
|
+
var REGION_API_NAMES = {
|
|
611
|
+
eu: "eu-central-1",
|
|
612
|
+
us: "us-east-1"
|
|
613
|
+
};
|
|
614
|
+
var cachedUsers = null;
|
|
615
|
+
var cacheExpiry = 0;
|
|
616
|
+
async function getQStashCredentials(region) {
|
|
552
617
|
const now = Date.now();
|
|
553
|
-
if (
|
|
554
|
-
|
|
618
|
+
if (!cachedUsers || now >= cacheExpiry) {
|
|
619
|
+
try {
|
|
620
|
+
cachedUsers = await http.get("v2/qstash/users");
|
|
621
|
+
cacheExpiry = now + 60 * 1e3;
|
|
622
|
+
} catch (error) {
|
|
623
|
+
throw new Error(
|
|
624
|
+
`Failed to get QStash credentials: ${error instanceof Error ? error.message : String(error)}`
|
|
625
|
+
);
|
|
626
|
+
}
|
|
555
627
|
}
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
return user.token;
|
|
561
|
-
} catch (error) {
|
|
562
|
-
throw new Error(
|
|
563
|
-
`Failed to get QStash token: ${error instanceof Error ? error.message : String(error)}`
|
|
564
|
-
);
|
|
628
|
+
const apiRegion = REGION_API_NAMES[region] ?? REGION_API_NAMES["eu"];
|
|
629
|
+
const match = cachedUsers.find((u) => u.region === apiRegion);
|
|
630
|
+
if (!match) {
|
|
631
|
+
throw new Error(`No QStash user found for region '${region}' (${apiRegion})`);
|
|
565
632
|
}
|
|
633
|
+
return { token: match.token };
|
|
566
634
|
}
|
|
567
|
-
async function createQStashClientWithToken(
|
|
568
|
-
|
|
569
|
-
|
|
635
|
+
async function createQStashClientWithToken(options) {
|
|
636
|
+
const { qstash_creds, region, local_mode_port } = options;
|
|
637
|
+
if (qstash_creds) {
|
|
638
|
+
return createQStashClient({ url: qstash_creds.url, token: qstash_creds.token });
|
|
570
639
|
}
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
640
|
+
if (region === "local") {
|
|
641
|
+
return createQStashClient({
|
|
642
|
+
url: `http://localhost:${local_mode_port}`,
|
|
643
|
+
token: LOCAL_QSTASH_TOKEN
|
|
644
|
+
});
|
|
645
|
+
}
|
|
646
|
+
const effectiveRegion = region && REGION_URLS[region] ? region : "eu";
|
|
647
|
+
const fetched = await getQStashCredentials(effectiveRegion);
|
|
648
|
+
return createQStashClient({ url: REGION_URLS[effectiveRegion], token: fetched.token });
|
|
575
649
|
}
|
|
576
650
|
|
|
577
|
-
// src/tools/qstash/
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
// ),
|
|
651
|
+
// src/tools/qstash/common.ts
|
|
652
|
+
import { z as z5 } from "zod";
|
|
653
|
+
var qstashCommon = {
|
|
654
|
+
region: z5.enum(["eu", "us", "local"]).default("eu").describe("QStash region to use. To use local mode, pick `local`"),
|
|
655
|
+
local_mode_port: z5.number().default(8080).describe(
|
|
656
|
+
"Only provide when using local mode and if default does not work, the port is usually in the .env file"
|
|
657
|
+
),
|
|
658
|
+
qstash_creds: z5.object({
|
|
659
|
+
url: z5.string(),
|
|
660
|
+
token: z5.string()
|
|
661
|
+
}).optional().describe("Custom qstash credentials, overrides `region`")
|
|
589
662
|
};
|
|
663
|
+
|
|
664
|
+
// src/tools/qstash/qstash.ts
|
|
590
665
|
var qstashTools = {
|
|
591
666
|
qstash_get_user_token: tool({
|
|
592
667
|
description: `Get the QSTASH_TOKEN and QSTASH_URL of the current user. This
|
|
593
668
|
is not needed for the mcp tools since the token is automatically fetched from
|
|
594
669
|
the Upstash API for them.`,
|
|
595
670
|
handler: async () => {
|
|
596
|
-
const user = await http.get("qstash/user");
|
|
671
|
+
const user = await http.get("v2/qstash/user");
|
|
597
672
|
return [json(user)];
|
|
598
673
|
}
|
|
599
674
|
}),
|
|
@@ -601,42 +676,42 @@ var qstashTools = {
|
|
|
601
676
|
description: `Publish a message to a destination URL using QStash. This
|
|
602
677
|
sends an HTTP request to the specified destination via QStash's message
|
|
603
678
|
queue. This can also be used to trigger a upstash workflow run.`,
|
|
604
|
-
inputSchema:
|
|
605
|
-
destination:
|
|
606
|
-
body:
|
|
607
|
-
method:
|
|
608
|
-
delay:
|
|
609
|
-
retries:
|
|
610
|
-
callback:
|
|
611
|
-
failureCallback:
|
|
612
|
-
timeout:
|
|
613
|
-
queueName:
|
|
679
|
+
inputSchema: z6.object({
|
|
680
|
+
destination: z6.string().describe("The destination URL to send the message to (e.g., 'https://example.com')"),
|
|
681
|
+
body: z6.string().optional().describe("Request body (JSON string or plain text)"),
|
|
682
|
+
method: z6.enum(["GET", "POST", "PUT", "DELETE", "PATCH"]).optional().describe("HTTP method (optional, defaults to POST)").default("POST"),
|
|
683
|
+
delay: z6.string().optional().describe("Delay before message delivery (e.g., '10s', '5m', '1h')"),
|
|
684
|
+
retries: z6.number().optional().describe("Number of retries on failure, default is 3"),
|
|
685
|
+
callback: z6.string().optional().describe("Callback URL that will be called when the message is successfully delivered"),
|
|
686
|
+
failureCallback: z6.string().optional().describe("Callback URL that will be called when the message is failed to deliver"),
|
|
687
|
+
timeout: z6.string().optional().describe("Request timeout (e.g., '30s', '1h')"),
|
|
688
|
+
queueName: z6.string().optional().describe(
|
|
614
689
|
"Queue name to use, you have to first create the queue in upstash. Prefer the flow control key instead"
|
|
615
690
|
),
|
|
616
|
-
flow_control:
|
|
617
|
-
key:
|
|
618
|
-
parallelism:
|
|
619
|
-
rate:
|
|
620
|
-
period:
|
|
691
|
+
flow_control: z6.object({
|
|
692
|
+
key: z6.string().describe("Unique identifier for grouping messages under same flow control rules"),
|
|
693
|
+
parallelism: z6.number().optional().describe("Max concurrent active calls (default: unlimited)"),
|
|
694
|
+
rate: z6.number().optional().describe("Max calls per period (default: unlimited)"),
|
|
695
|
+
period: z6.string().optional().describe("Time window for rate limit (e.g., '1s', '1m', '1h', default: '1s')")
|
|
621
696
|
}).optional().describe("Flow control for rate limiting and parallelism management"),
|
|
622
|
-
extraHeaders:
|
|
623
|
-
...
|
|
697
|
+
extraHeaders: z6.record(z6.string()).optional().describe("Extra headers to add to the request"),
|
|
698
|
+
...qstashCommon
|
|
624
699
|
}),
|
|
625
|
-
handler: async ({
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
const client = await createQStashClientWithToken(
|
|
700
|
+
handler: async (params) => {
|
|
701
|
+
const {
|
|
702
|
+
destination,
|
|
703
|
+
body,
|
|
704
|
+
method,
|
|
705
|
+
extraHeaders,
|
|
706
|
+
delay,
|
|
707
|
+
retries,
|
|
708
|
+
callback,
|
|
709
|
+
failureCallback,
|
|
710
|
+
timeout,
|
|
711
|
+
queueName,
|
|
712
|
+
flow_control
|
|
713
|
+
} = params;
|
|
714
|
+
const client = await createQStashClientWithToken(params);
|
|
640
715
|
const requestHeaders = {};
|
|
641
716
|
if (method) {
|
|
642
717
|
requestHeaders["Upstash-Method"] = method;
|
|
@@ -687,10 +762,10 @@ var qstashTools = {
|
|
|
687
762
|
}),
|
|
688
763
|
qstash_logs_list: tool({
|
|
689
764
|
description: `List QStash logs with optional filtering. Returns a paginated list of message logs without their bodies.`,
|
|
690
|
-
inputSchema:
|
|
691
|
-
cursor:
|
|
692
|
-
messageId:
|
|
693
|
-
state:
|
|
765
|
+
inputSchema: z6.object({
|
|
766
|
+
cursor: z6.string().optional().describe("Cursor for pagination"),
|
|
767
|
+
messageId: z6.string().optional().describe("Filter logs by message ID"),
|
|
768
|
+
state: z6.enum([
|
|
694
769
|
"CREATED",
|
|
695
770
|
"ACTIVE",
|
|
696
771
|
"RETRY",
|
|
@@ -701,21 +776,27 @@ var qstashTools = {
|
|
|
701
776
|
"CANCEL_REQUESTED",
|
|
702
777
|
"CANCELLED"
|
|
703
778
|
]).optional().describe("Filter logs by state"),
|
|
704
|
-
url:
|
|
705
|
-
topicName:
|
|
706
|
-
scheduleId:
|
|
707
|
-
queueName:
|
|
708
|
-
fromDate:
|
|
709
|
-
toDate:
|
|
710
|
-
count:
|
|
711
|
-
...
|
|
779
|
+
url: z6.string().optional().describe("Filter logs by URL"),
|
|
780
|
+
topicName: z6.string().optional().describe("Filter logs by topic name"),
|
|
781
|
+
scheduleId: z6.string().optional().describe("Filter logs by schedule ID"),
|
|
782
|
+
queueName: z6.string().optional().describe("Filter logs by queue name"),
|
|
783
|
+
fromDate: z6.number().optional().describe("Filter logs from date (Unix timestamp in milliseconds)"),
|
|
784
|
+
toDate: z6.number().optional().describe("Filter logs to date (Unix timestamp in milliseconds)"),
|
|
785
|
+
count: z6.number().max(1e3).optional().describe("Number of logs to return (max 1000)"),
|
|
786
|
+
...qstashCommon
|
|
712
787
|
}),
|
|
713
788
|
handler: async (params) => {
|
|
714
|
-
const
|
|
789
|
+
const {
|
|
790
|
+
region: _region,
|
|
791
|
+
local_mode_port: _local_mode_port,
|
|
792
|
+
qstash_creds: _qstash_creds,
|
|
793
|
+
...query
|
|
794
|
+
} = params;
|
|
795
|
+
const client = await createQStashClientWithToken(params);
|
|
715
796
|
const response = await client.get("v2/logs", {
|
|
716
797
|
trimBody: 0,
|
|
717
798
|
groupBy: "messageId",
|
|
718
|
-
...
|
|
799
|
+
...query
|
|
719
800
|
});
|
|
720
801
|
const firstMessageFields = Object.fromEntries(
|
|
721
802
|
Object.entries(response.messages[0] ?? {}).filter(
|
|
@@ -738,12 +819,13 @@ var qstashTools = {
|
|
|
738
819
|
}),
|
|
739
820
|
qstash_logs_get: tool({
|
|
740
821
|
description: `Get details of a single QStash log item by message ID without trimming the body.`,
|
|
741
|
-
inputSchema:
|
|
742
|
-
messageId:
|
|
743
|
-
...
|
|
822
|
+
inputSchema: z6.object({
|
|
823
|
+
messageId: z6.string().describe("The message ID to get details for"),
|
|
824
|
+
...qstashCommon
|
|
744
825
|
}),
|
|
745
|
-
handler: async (
|
|
746
|
-
const
|
|
826
|
+
handler: async (params) => {
|
|
827
|
+
const { messageId } = params;
|
|
828
|
+
const client = await createQStashClientWithToken(params);
|
|
747
829
|
const response = await client.get("v2/logs", { messageId });
|
|
748
830
|
if (response.messages.length === 0) {
|
|
749
831
|
return "No log entry found for the specified message ID";
|
|
@@ -754,25 +836,31 @@ var qstashTools = {
|
|
|
754
836
|
}),
|
|
755
837
|
qstash_dlq_list: tool({
|
|
756
838
|
description: `List messages in the QStash Dead Letter Queue (DLQ) with optional filtering.`,
|
|
757
|
-
inputSchema:
|
|
758
|
-
cursor:
|
|
759
|
-
messageId:
|
|
760
|
-
url:
|
|
761
|
-
topicName:
|
|
762
|
-
scheduleId:
|
|
763
|
-
queueName:
|
|
764
|
-
fromDate:
|
|
765
|
-
toDate:
|
|
766
|
-
responseStatus:
|
|
767
|
-
callerIp:
|
|
768
|
-
count:
|
|
769
|
-
...
|
|
839
|
+
inputSchema: z6.object({
|
|
840
|
+
cursor: z6.string().optional().describe("Cursor for pagination"),
|
|
841
|
+
messageId: z6.string().optional().describe("Filter DLQ messages by message ID"),
|
|
842
|
+
url: z6.string().optional().describe("Filter DLQ messages by URL"),
|
|
843
|
+
topicName: z6.string().optional().describe("Filter DLQ messages by topic name"),
|
|
844
|
+
scheduleId: z6.string().optional().describe("Filter DLQ messages by schedule ID"),
|
|
845
|
+
queueName: z6.string().optional().describe("Filter DLQ messages by queue name"),
|
|
846
|
+
fromDate: z6.number().optional().describe("Filter from date (Unix timestamp in milliseconds)"),
|
|
847
|
+
toDate: z6.number().optional().describe("Filter to date (Unix timestamp in milliseconds)"),
|
|
848
|
+
responseStatus: z6.number().optional().describe("Filter by HTTP response status code"),
|
|
849
|
+
callerIp: z6.string().optional().describe("Filter by IP address of the publisher"),
|
|
850
|
+
count: z6.number().max(100).optional().describe("Number of messages to return (max 100)"),
|
|
851
|
+
...qstashCommon
|
|
770
852
|
}),
|
|
771
853
|
handler: async (params) => {
|
|
772
|
-
const
|
|
854
|
+
const {
|
|
855
|
+
region: _region,
|
|
856
|
+
local_mode_port: _local_mode_port,
|
|
857
|
+
qstash_creds: _qstash_creds,
|
|
858
|
+
...query
|
|
859
|
+
} = params;
|
|
860
|
+
const client = await createQStashClientWithToken(params);
|
|
773
861
|
const response = await client.get("v2/dlq", {
|
|
774
862
|
trimBody: 0,
|
|
775
|
-
...
|
|
863
|
+
...query
|
|
776
864
|
});
|
|
777
865
|
return [
|
|
778
866
|
`Found ${response.messages.length} DLQ messages`,
|
|
@@ -783,59 +871,63 @@ var qstashTools = {
|
|
|
783
871
|
}),
|
|
784
872
|
qstash_dlq_get: tool({
|
|
785
873
|
description: `Get details of a single DLQ message by DLQ ID.`,
|
|
786
|
-
inputSchema:
|
|
787
|
-
dlqId:
|
|
788
|
-
...
|
|
874
|
+
inputSchema: z6.object({
|
|
875
|
+
dlqId: z6.string().describe("The DLQ ID of the message to retrieve"),
|
|
876
|
+
...qstashCommon
|
|
789
877
|
}),
|
|
790
|
-
handler: async (
|
|
791
|
-
const
|
|
878
|
+
handler: async (params) => {
|
|
879
|
+
const { dlqId } = params;
|
|
880
|
+
const client = await createQStashClientWithToken(params);
|
|
792
881
|
const message = await client.get(`v2/dlq/${dlqId}`);
|
|
793
882
|
return [`DLQ message details for ID: ${dlqId}`, json(message)];
|
|
794
883
|
}
|
|
795
884
|
}),
|
|
796
885
|
qstash_schedules_list: tool({
|
|
797
886
|
description: `List all QStash schedules.`,
|
|
798
|
-
|
|
799
|
-
|
|
887
|
+
inputSchema: z6.object({
|
|
888
|
+
...qstashCommon
|
|
889
|
+
}),
|
|
890
|
+
handler: async (params) => {
|
|
891
|
+
const client = await createQStashClientWithToken(params);
|
|
800
892
|
const schedules = await client.get("v2/schedules");
|
|
801
893
|
return [`Found ${schedules.length} schedules`, json(schedules)];
|
|
802
894
|
}
|
|
803
895
|
}),
|
|
804
896
|
qstash_schedules_manage: tool({
|
|
805
897
|
description: `Create, update, or manage QStash schedules. This tool handles create, update (by providing scheduleId), pause, resume, and delete operations in one unified interface.`,
|
|
806
|
-
inputSchema:
|
|
807
|
-
operation:
|
|
808
|
-
scheduleId:
|
|
809
|
-
destination:
|
|
810
|
-
cron:
|
|
811
|
-
method:
|
|
812
|
-
headers:
|
|
813
|
-
body:
|
|
814
|
-
delay:
|
|
815
|
-
retries:
|
|
816
|
-
callback:
|
|
817
|
-
failureCallback:
|
|
818
|
-
timeout:
|
|
819
|
-
queueName:
|
|
820
|
-
...
|
|
898
|
+
inputSchema: z6.object({
|
|
899
|
+
operation: z6.enum(["create", "update", "pause", "resume", "delete"]).describe("The operation to perform"),
|
|
900
|
+
scheduleId: z6.string().optional().describe("Schedule ID (required for update, pause, resume, delete operations)"),
|
|
901
|
+
destination: z6.string().optional().describe("Destination URL or topic name (required for create/update)"),
|
|
902
|
+
cron: z6.string().optional().describe("Cron expression (required for create/update)"),
|
|
903
|
+
method: z6.enum(["GET", "POST", "PUT", "DELETE", "PATCH"]).optional().describe("HTTP method (optional, defaults to POST)"),
|
|
904
|
+
headers: z6.record(z6.string()).optional().describe("Request headers as key-value pairs"),
|
|
905
|
+
body: z6.string().optional().describe("Request body"),
|
|
906
|
+
delay: z6.string().optional().describe("Delay before message delivery (e.g., '10s', '5m', '1h')"),
|
|
907
|
+
retries: z6.number().optional().describe("Number of retries on failure"),
|
|
908
|
+
callback: z6.string().optional().describe("Callback URL for successful delivery"),
|
|
909
|
+
failureCallback: z6.string().optional().describe("Callback URL for failed delivery"),
|
|
910
|
+
timeout: z6.string().optional().describe("Request timeout (e.g., '30s')"),
|
|
911
|
+
queueName: z6.string().optional().describe("Queue name to use"),
|
|
912
|
+
...qstashCommon
|
|
821
913
|
}),
|
|
822
|
-
handler: async ({
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
const client = await createQStashClientWithToken(
|
|
914
|
+
handler: async (params) => {
|
|
915
|
+
const {
|
|
916
|
+
operation,
|
|
917
|
+
scheduleId,
|
|
918
|
+
destination,
|
|
919
|
+
cron,
|
|
920
|
+
method = "POST",
|
|
921
|
+
headers,
|
|
922
|
+
body,
|
|
923
|
+
delay,
|
|
924
|
+
retries,
|
|
925
|
+
callback,
|
|
926
|
+
failureCallback,
|
|
927
|
+
timeout,
|
|
928
|
+
queueName
|
|
929
|
+
} = params;
|
|
930
|
+
const client = await createQStashClientWithToken(params);
|
|
839
931
|
switch (operation) {
|
|
840
932
|
case "create":
|
|
841
933
|
case "update": {
|
|
@@ -915,25 +1007,31 @@ var qstashTools = {
|
|
|
915
1007
|
};
|
|
916
1008
|
|
|
917
1009
|
// src/tools/qstash/workflow.ts
|
|
918
|
-
import { z as
|
|
1010
|
+
import { z as z7 } from "zod";
|
|
919
1011
|
var workflowTools = {
|
|
920
1012
|
workflow_logs_list: tool({
|
|
921
1013
|
description: `List Upstash Workflow logs with optional filtering. Returns grouped workflow runs with their execution details.`,
|
|
922
|
-
inputSchema:
|
|
923
|
-
cursor:
|
|
924
|
-
workflowRunId:
|
|
925
|
-
count:
|
|
926
|
-
state:
|
|
927
|
-
workflowUrl:
|
|
928
|
-
workflowCreatedAt:
|
|
929
|
-
...
|
|
1014
|
+
inputSchema: z7.object({
|
|
1015
|
+
cursor: z7.string().optional().describe("Cursor for pagination"),
|
|
1016
|
+
workflowRunId: z7.string().optional().describe("Filter by specific workflow run ID"),
|
|
1017
|
+
count: z7.number().optional().describe("Number of workflow runs to return"),
|
|
1018
|
+
state: z7.enum(["RUN_STARTED", "RUN_SUCCESS", "RUN_FAILED", "RUN_CANCELED"]).optional().describe("Filter by workflow state"),
|
|
1019
|
+
workflowUrl: z7.string().optional().describe("Filter by workflow URL (exact match)"),
|
|
1020
|
+
workflowCreatedAt: z7.number().optional().describe("Filter by workflow creation timestamp (Unix timestamp)"),
|
|
1021
|
+
...qstashCommon
|
|
930
1022
|
}),
|
|
931
1023
|
handler: async (params) => {
|
|
932
|
-
const
|
|
1024
|
+
const {
|
|
1025
|
+
region: _region,
|
|
1026
|
+
local_mode_port: _local_mode_port,
|
|
1027
|
+
qstash_creds: _qstash_creds,
|
|
1028
|
+
...query
|
|
1029
|
+
} = params;
|
|
1030
|
+
const client = await createQStashClientWithToken(params);
|
|
933
1031
|
const response = await client.get("v2/workflows/events", {
|
|
934
1032
|
trimBody: 0,
|
|
935
1033
|
groupBy: "workflowRunId",
|
|
936
|
-
...
|
|
1034
|
+
...query
|
|
937
1035
|
});
|
|
938
1036
|
const cleaned = response.runs.map(
|
|
939
1037
|
(run) => Object.fromEntries(Object.entries(run).filter(([key, _value]) => key !== "steps"))
|
|
@@ -952,16 +1050,20 @@ var workflowTools = {
|
|
|
952
1050
|
description: `Get details of a single workflow run by workflow run ID. There
|
|
953
1051
|
could be multiple workflow runs with the same workflow run ID, so you can
|
|
954
1052
|
use the workflowCreatedAt to get the details of the specific workflow run.`,
|
|
955
|
-
inputSchema:
|
|
956
|
-
workflowRunId:
|
|
957
|
-
workflowCreatedAt:
|
|
958
|
-
...
|
|
1053
|
+
inputSchema: z7.object({
|
|
1054
|
+
workflowRunId: z7.string().describe("The workflow run ID to get details for"),
|
|
1055
|
+
workflowCreatedAt: z7.number().optional().describe("The workflow creation timestamp (Unix timestamp)"),
|
|
1056
|
+
...qstashCommon
|
|
959
1057
|
}),
|
|
960
1058
|
handler: async (params) => {
|
|
961
|
-
const
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
1059
|
+
const {
|
|
1060
|
+
region: _region,
|
|
1061
|
+
local_mode_port: _local_mode_port,
|
|
1062
|
+
qstash_creds: _qstash_creds,
|
|
1063
|
+
...query
|
|
1064
|
+
} = params;
|
|
1065
|
+
const client = await createQStashClientWithToken(params);
|
|
1066
|
+
const response = await client.get("v2/workflows/logs", query);
|
|
965
1067
|
if (response.runs.length === 0) {
|
|
966
1068
|
return "No workflow run found";
|
|
967
1069
|
}
|
|
@@ -974,22 +1076,28 @@ var workflowTools = {
|
|
|
974
1076
|
}),
|
|
975
1077
|
workflow_dlq_list: tool({
|
|
976
1078
|
description: `List failed workflow runs in the Dead Letter Queue (DLQ) with optional filtering.`,
|
|
977
|
-
inputSchema:
|
|
978
|
-
cursor:
|
|
979
|
-
workflowRunId:
|
|
980
|
-
workflowUrl:
|
|
981
|
-
fromDate:
|
|
982
|
-
toDate:
|
|
983
|
-
responseStatus:
|
|
984
|
-
callerIP:
|
|
985
|
-
failureCallbackState:
|
|
986
|
-
count:
|
|
987
|
-
...
|
|
1079
|
+
inputSchema: z7.object({
|
|
1080
|
+
cursor: z7.string().optional().describe("Cursor for pagination"),
|
|
1081
|
+
workflowRunId: z7.string().optional().describe("Filter by workflow run ID"),
|
|
1082
|
+
workflowUrl: z7.string().optional().describe("Filter by workflow URL"),
|
|
1083
|
+
fromDate: z7.number().optional().describe("Filter from date (Unix timestamp in milliseconds)"),
|
|
1084
|
+
toDate: z7.number().optional().describe("Filter to date (Unix timestamp in milliseconds)"),
|
|
1085
|
+
responseStatus: z7.number().optional().describe("Filter by HTTP response status code"),
|
|
1086
|
+
callerIP: z7.string().optional().describe("Filter by IP address of the caller"),
|
|
1087
|
+
failureCallbackState: z7.string().optional().describe("Filter by failure callback state"),
|
|
1088
|
+
count: z7.number().optional().describe("Number of DLQ messages to return"),
|
|
1089
|
+
...qstashCommon
|
|
988
1090
|
}),
|
|
989
1091
|
handler: async (params) => {
|
|
990
|
-
const
|
|
1092
|
+
const {
|
|
1093
|
+
region: _region,
|
|
1094
|
+
local_mode_port: _local_mode_port,
|
|
1095
|
+
qstash_creds: _qstash_creds,
|
|
1096
|
+
...query
|
|
1097
|
+
} = params;
|
|
1098
|
+
const client = await createQStashClientWithToken(params);
|
|
991
1099
|
const response = await client.get("v2/workflows/dlq", {
|
|
992
|
-
...
|
|
1100
|
+
...query,
|
|
993
1101
|
trimBody: 0
|
|
994
1102
|
});
|
|
995
1103
|
const cleaned = response.messages.map(
|
|
@@ -1007,27 +1115,28 @@ var workflowTools = {
|
|
|
1007
1115
|
}),
|
|
1008
1116
|
workflow_dlq_get: tool({
|
|
1009
1117
|
description: `Get details of a single failed workflow run from the DLQ by DLQ ID.`,
|
|
1010
|
-
inputSchema:
|
|
1011
|
-
dlqId:
|
|
1012
|
-
...
|
|
1118
|
+
inputSchema: z7.object({
|
|
1119
|
+
dlqId: z7.string().describe("The DLQ ID of the failed workflow run to retrieve"),
|
|
1120
|
+
...qstashCommon
|
|
1013
1121
|
}),
|
|
1014
1122
|
handler: async (params) => {
|
|
1015
|
-
const client = await createQStashClientWithToken(params
|
|
1123
|
+
const client = await createQStashClientWithToken(params);
|
|
1016
1124
|
const message = await client.get(`v2/workflows/dlq/${params.dlqId}`);
|
|
1017
1125
|
return [`Failed workflow run details for DLQ ID: ${params.dlqId}`, json(message)];
|
|
1018
1126
|
}
|
|
1019
1127
|
}),
|
|
1020
1128
|
workflow_dlq_manage: tool({
|
|
1021
1129
|
description: `Delete, restart, and resume failed workflow runs in the DLQ using only the DLQ ID.`,
|
|
1022
|
-
inputSchema:
|
|
1023
|
-
dlqId:
|
|
1024
|
-
action:
|
|
1130
|
+
inputSchema: z7.object({
|
|
1131
|
+
dlqId: z7.string().describe("The DLQ ID of the failed workflow run"),
|
|
1132
|
+
action: z7.enum(["delete", "restart", "resume"]).describe(
|
|
1025
1133
|
"The action to perform: delete (remove from DLQ), restart (from beginning), or resume (from the failed step)"
|
|
1026
1134
|
),
|
|
1027
|
-
...
|
|
1135
|
+
...qstashCommon
|
|
1028
1136
|
}),
|
|
1029
|
-
handler: async (
|
|
1030
|
-
const
|
|
1137
|
+
handler: async (params) => {
|
|
1138
|
+
const { dlqId, action } = params;
|
|
1139
|
+
const client = await createQStashClientWithToken(params);
|
|
1031
1140
|
switch (action) {
|
|
1032
1141
|
case "delete": {
|
|
1033
1142
|
await client.delete(`v2/workflows/dlq/delete/${dlqId}`);
|
|
@@ -1061,14 +1170,10 @@ var qstashAllTools = {
|
|
|
1061
1170
|
};
|
|
1062
1171
|
|
|
1063
1172
|
// src/tools/index.ts
|
|
1064
|
-
var json = (json2) => typeof json2 === "string" ? json2 : JSON.stringify(json2, null, 2);
|
|
1065
1173
|
var tools = {
|
|
1066
1174
|
...redisTools,
|
|
1067
1175
|
...qstashAllTools
|
|
1068
1176
|
};
|
|
1069
|
-
function tool(tool2) {
|
|
1070
|
-
return tool2;
|
|
1071
|
-
}
|
|
1072
1177
|
|
|
1073
1178
|
// src/settings.ts
|
|
1074
1179
|
var MAX_MESSAGE_LENGTH = 3e4;
|
|
@@ -1087,7 +1192,7 @@ function handlerResponseToCallResult(response) {
|
|
|
1087
1192
|
}
|
|
1088
1193
|
|
|
1089
1194
|
// src/server.ts
|
|
1090
|
-
import
|
|
1195
|
+
import z8 from "zod";
|
|
1091
1196
|
function createServerInstance() {
|
|
1092
1197
|
const server = new McpServer(
|
|
1093
1198
|
{ name: "upstash", version: "0.1.0" },
|
|
@@ -1111,7 +1216,7 @@ function createServerInstance() {
|
|
|
1111
1216
|
toolName,
|
|
1112
1217
|
{
|
|
1113
1218
|
description: tool2.description,
|
|
1114
|
-
inputSchema: (tool2.inputSchema ??
|
|
1219
|
+
inputSchema: (tool2.inputSchema ?? z8.object({})).shape
|
|
1115
1220
|
},
|
|
1116
1221
|
// @ts-expect-error - Just ignore the types here
|
|
1117
1222
|
async (args) => {
|
|
@@ -1171,10 +1276,13 @@ var argv = process.argv.slice(2);
|
|
|
1171
1276
|
if (argv.length >= 3 && argv[0] === "run") {
|
|
1172
1277
|
argv = ["--email", argv[1], "--api-key", argv[2], ...argv.slice(3)];
|
|
1173
1278
|
}
|
|
1174
|
-
var program = new Command().option("--transport <stdio|http>", "transport type", "stdio").option("--port <number>", "port for HTTP transport", "3000").option("--email <email>", "Upstash email").option("--api-key <key>", "Upstash API key").option("--debug", "Enable debug mode").allowUnknownOption();
|
|
1279
|
+
var program = new Command().option("--transport <stdio|http>", "transport type", "stdio").option("--port <number>", "port for HTTP transport", "3000").option("--email <email>", "Upstash email").option("--api-key <key>", "Upstash API key").option("--debug", "Enable debug mode").option("--disable-telemetry", "Disable telemetry headers sent to Upstash APIs").allowUnknownOption();
|
|
1175
1280
|
program.parse(argv, { from: "user" });
|
|
1176
1281
|
var cliOptions = program.opts();
|
|
1177
1282
|
var DEBUG = cliOptions.debug ?? false;
|
|
1283
|
+
if (DEBUG) {
|
|
1284
|
+
initDebugLog(new URL("../", import.meta.url).pathname);
|
|
1285
|
+
}
|
|
1178
1286
|
var allowedTransports = ["stdio", "http"];
|
|
1179
1287
|
if (!allowedTransports.includes(cliOptions.transport)) {
|
|
1180
1288
|
console.error(
|
|
@@ -1203,6 +1311,7 @@ async function main() {
|
|
|
1203
1311
|
}
|
|
1204
1312
|
config.email = email;
|
|
1205
1313
|
config.apiKey = apiKey;
|
|
1314
|
+
config.disableTelemetry = cliOptions.disableTelemetry ?? false;
|
|
1206
1315
|
await testConnection();
|
|
1207
1316
|
const transportType = TRANSPORT_TYPE;
|
|
1208
1317
|
if (transportType === "http") {
|