@chaprola/mcp-server 1.9.0 → 1.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.
Files changed (2) hide show
  1. package/dist/index.js +32 -6
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -585,7 +585,7 @@ server.tool("chaprola_download", "Get a presigned S3 URL to download any file yo
585
585
  server.tool("chaprola_query", "SQL-free data query with WHERE, SELECT, aggregation, ORDER BY, JOIN, pivot, and Mercury scoring", {
586
586
  project: z.string().describe("Project name"),
587
587
  file: z.string().describe("Data file to query"),
588
- where: z.string().optional().describe("JSON object of filter conditions, e.g. {\"field\": \"status\", \"op\": \"eq\", \"value\": \"active\"}. Ops: eq, ne, gt, ge, lt, le, between, contains, starts_with"),
588
+ where: z.string().optional().describe("JSON object of filter conditions, e.g. {\"field\": \"status\", \"op\": \"eq\", \"value\": \"active\"}. Ops: eq, ne, gt, ge, lt, le, between, contains, starts_with, in, not_in. For in/not_in, value must be a JSON array of strings, e.g. {\"field\": \"status\", \"op\": \"in\", \"value\": [\"active\", \"pending\"]}"),
589
589
  select: z.array(z.string()).optional().describe("Fields to include in output"),
590
590
  aggregate: z.string().optional().describe("JSON array of aggregation specs, e.g. [{\"field\": \"amount\", \"func\": \"sum\"}]. Funcs: count, sum, avg, min, max, stddev"),
591
591
  order_by: z.string().optional().describe("JSON array of sort specs, e.g. [{\"field\": \"name\", \"dir\": \"asc\"}]"),
@@ -868,15 +868,22 @@ server.tool("chaprola_schedule_delete", "Delete a scheduled job by name", {
868
868
  return textResult(res);
869
869
  }));
870
870
  // --- Record CRUD ---
871
- server.tool("chaprola_insert_record", "Insert a new record into a data file's merge file (.MRG). The record appears at the end of the file until consolidation.", {
871
+ server.tool("chaprola_insert_record", "Insert one or more records into a data file. For single insert, use record. For batch insert (up to 1000), use records. All records are validated before any writes invalid batch = no partial writes.", {
872
872
  project: z.string().describe("Project name"),
873
873
  file: z.string().describe("Data file name (without extension)"),
874
- record: z.string().describe("JSON object of the record to insert, e.g. {\"name\": \"foo\", \"status\": \"active\"}. Unspecified fields default to blanks."),
874
+ record: z.string().optional().describe("JSON object of a single record to insert, e.g. {\"name\": \"foo\", \"status\": \"active\"}. Use this OR records, not both."),
875
+ records: z.string().optional().describe("JSON array of records for batch insert (max 1000), e.g. [{\"name\": \"foo\"}, {\"name\": \"bar\"}]. Use this OR record, not both."),
875
876
  userid: z.string().optional().describe("Project owner's username. Required when accessing a shared project where you are a writer. Defaults to the authenticated user."),
876
- }, async ({ project, file, record: recordStr, userid }) => withBaaCheck(async () => {
877
- const record = typeof recordStr === 'string' ? JSON.parse(recordStr) : recordStr;
877
+ }, async ({ project, file, record: recordStr, records: recordsStr, userid }) => withBaaCheck(async () => {
878
+ const record = recordStr ? (typeof recordStr === 'string' ? JSON.parse(recordStr) : recordStr) : undefined;
879
+ const records = recordsStr ? (typeof recordsStr === 'string' ? JSON.parse(recordsStr) : recordsStr) : undefined;
878
880
  const { username } = getCredentials();
879
- const res = await authedFetch("/insert-record", { userid: userid || username, project, file, record });
881
+ const body = { userid: userid || username, project, file };
882
+ if (record)
883
+ body.record = record;
884
+ if (records)
885
+ body.records = records;
886
+ const res = await authedFetch("/insert-record", body);
880
887
  return textResult(res);
881
888
  }));
882
889
  server.tool("chaprola_update_record", "Update fields in a single record matched by a where clause. If no sort-key changes, updates in place; otherwise marks old record ignored and appends to merge file.", {
@@ -903,6 +910,25 @@ server.tool("chaprola_delete_record", "Delete a single record matched by a where
903
910
  const res = await authedFetch("/delete-record", { userid: userid || username, project, file, where: whereClause });
904
911
  return textResult(res);
905
912
  }));
913
+ server.tool("chaprola_upsert_record", "Insert or update a record by key field. If a record with the matching key value exists, update it in place. If not, insert it. Supports batch: pass records array to upsert multiple records in one call.", {
914
+ project: z.string().describe("Project name"),
915
+ file: z.string().describe("Data file name (without extension)"),
916
+ key: z.string().describe("Field name to match on, e.g. \"contact_id\". Must exist in the format file."),
917
+ record: z.string().optional().describe("JSON object of a single record to upsert, e.g. {\"contact_id\": \"c-42\", \"name\": \"Alice\"}. Must contain the key field. Use this OR records, not both."),
918
+ records: z.string().optional().describe("JSON array of records for batch upsert (max 1000). Each must contain the key field. Use this OR record, not both."),
919
+ userid: z.string().optional().describe("Project owner's username. Required when accessing a shared project where you are a writer. Defaults to the authenticated user."),
920
+ }, async ({ project, file, key, record: recordStr, records: recordsStr, userid }) => withBaaCheck(async () => {
921
+ const record = recordStr ? (typeof recordStr === 'string' ? JSON.parse(recordStr) : recordStr) : undefined;
922
+ const records = recordsStr ? (typeof recordsStr === 'string' ? JSON.parse(recordsStr) : recordsStr) : undefined;
923
+ const { username } = getCredentials();
924
+ const body = { userid: userid || username, project, file, key };
925
+ if (record)
926
+ body.record = record;
927
+ if (records)
928
+ body.records = records;
929
+ const res = await authedFetch("/upsert-record", body);
930
+ return textResult(res);
931
+ }));
906
932
  server.tool("chaprola_consolidate", "Merge a .MRG file into its parent .DA, producing a clean sorted data file. Deletes .MRG and .IGN after success. Aborts if .MRG was modified during the operation.", {
907
933
  project: z.string().describe("Project name"),
908
934
  file: z.string().describe("Data file name (without extension)"),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chaprola/mcp-server",
3
- "version": "1.9.0",
3
+ "version": "1.10.0",
4
4
  "description": "MCP server for Chaprola — agent-first data platform. Gives AI agents tools for structured data storage, record CRUD, querying, schema inspection, documentation lookup, web search, URL fetching, scheduled jobs, scoped site keys, and execution via plain HTTP.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",