@topgunbuild/mcp-server 0.9.0 → 0.10.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/index.mjs CHANGED
@@ -17,7 +17,7 @@ var QueryArgsSchema = z.object({
17
17
  order: z.enum(["asc", "desc"]).describe("Sort order: ascending or descending")
18
18
  }).optional().describe("Sort configuration"),
19
19
  limit: z.number().optional().default(10).describe("Maximum number of results to return"),
20
- offset: z.number().optional().default(0).describe("Number of results to skip (for pagination)")
20
+ cursor: z.string().optional().describe("Opaque cursor for pagination (from previous response nextCursor)")
21
21
  });
22
22
  var MutateArgsSchema = z.object({
23
23
  map: z.string().describe("Name of the map to modify (e.g., 'tasks', 'users')"),
@@ -71,7 +71,7 @@ var toolSchemas = {
71
71
  description: "Sort configuration"
72
72
  },
73
73
  limit: { type: "number", description: "Maximum number of results to return", default: 10 },
74
- offset: { type: "number", description: "Number of results to skip (for pagination)", default: 0 }
74
+ cursor: { type: "string", description: "Opaque cursor for pagination (from previous response nextCursor)" }
75
75
  },
76
76
  required: ["map"]
77
77
  },
@@ -161,7 +161,7 @@ var toolSchemas = {
161
161
  // src/tools/query.ts
162
162
  var queryTool = {
163
163
  name: "topgun_query",
164
- description: "Query data from a TopGun map with filters and sorting. Use this to read data from the database. Supports filtering by field values and sorting by any field.",
164
+ description: "Query data from a TopGun map with filters and sorting. Use this to read data from the database. Supports filtering by field values, sorting, and cursor-based pagination.",
165
165
  inputSchema: toolSchemas.query
166
166
  };
167
167
  async function handleQuery(rawArgs, ctx) {
@@ -173,7 +173,7 @@ async function handleQuery(rawArgs, ctx) {
173
173
  isError: true
174
174
  };
175
175
  }
176
- const { map, filter, sort, limit, offset } = parseResult.data;
176
+ const { map, filter, sort, limit, cursor } = parseResult.data;
177
177
  if (ctx.config.allowedMaps && !ctx.config.allowedMaps.includes(map)) {
178
178
  return {
179
179
  content: [
@@ -186,39 +186,26 @@ async function handleQuery(rawArgs, ctx) {
186
186
  };
187
187
  }
188
188
  const effectiveLimit = Math.min(limit ?? ctx.config.defaultLimit, ctx.config.maxLimit);
189
- const effectiveOffset = offset ?? 0;
190
189
  try {
191
- const lwwMap = ctx.client.getMap(map);
192
- const allEntries = [];
193
- for (const [key, value] of lwwMap.entries()) {
194
- if (value !== null && typeof value === "object") {
195
- let matches = true;
196
- if (filter) {
197
- for (const [filterKey, filterValue] of Object.entries(filter)) {
198
- if (value[filterKey] !== filterValue) {
199
- matches = false;
200
- break;
201
- }
202
- }
203
- }
204
- if (matches) {
205
- allEntries.push({ key: String(key), value });
206
- }
207
- }
208
- }
190
+ const queryFilter = {
191
+ where: filter,
192
+ limit: effectiveLimit
193
+ };
209
194
  if (sort?.field) {
210
- allEntries.sort((a, b) => {
211
- const aVal = a.value[sort.field];
212
- const bVal = b.value[sort.field];
213
- if (aVal === bVal) return 0;
214
- if (aVal === void 0 || aVal === null) return 1;
215
- if (bVal === void 0 || bVal === null) return -1;
216
- const comparison = aVal < bVal ? -1 : 1;
217
- return sort.order === "desc" ? -comparison : comparison;
218
- });
195
+ queryFilter.sort = { [sort.field]: sort.order };
219
196
  }
220
- const paginatedEntries = allEntries.slice(effectiveOffset, effectiveOffset + effectiveLimit);
221
- if (paginatedEntries.length === 0) {
197
+ if (cursor) {
198
+ queryFilter.cursor = cursor;
199
+ }
200
+ const handle = ctx.client.query(map, queryFilter);
201
+ const results = await new Promise((resolve) => {
202
+ const unsubscribe = handle.subscribe((data) => {
203
+ unsubscribe();
204
+ resolve(data);
205
+ });
206
+ });
207
+ const paginationInfo = handle.getPaginationInfo();
208
+ if (results.length === 0) {
222
209
  return {
223
210
  content: [
224
211
  {
@@ -228,17 +215,24 @@ async function handleQuery(rawArgs, ctx) {
228
215
  ]
229
216
  };
230
217
  }
231
- const formatted = paginatedEntries.map((entry, idx) => `${idx + 1 + effectiveOffset}. [${entry.key}]: ${JSON.stringify(entry.value, null, 2)}`).join("\n\n");
232
- const totalInfo = allEntries.length > effectiveLimit ? `
218
+ const formatted = results.map((entry, idx) => {
219
+ const { _key, ...value } = entry;
220
+ return `${idx + 1}. [${_key}]: ${JSON.stringify(value, null, 2)}`;
221
+ }).join("\n\n");
222
+ let paginationText = "";
223
+ if (paginationInfo.hasMore && paginationInfo.nextCursor) {
224
+ paginationText = `
233
225
 
234
- (Showing ${effectiveOffset + 1}-${effectiveOffset + paginatedEntries.length} of ${allEntries.length} total)` : "";
226
+ ---
227
+ More results available. Use cursor: "${paginationInfo.nextCursor}" to fetch next page.`;
228
+ }
235
229
  return {
236
230
  content: [
237
231
  {
238
232
  type: "text",
239
- text: `Found ${paginatedEntries.length} result(s) in map '${map}':
233
+ text: `Found ${results.length} result(s) in map '${map}':
240
234
 
241
- ${formatted}${totalInfo}`
235
+ ${formatted}${paginationText}`
242
236
  }
243
237
  ]
244
238
  };