@stigmer/mcp-server 3.11.1-dev.20260812192248 → 3.12.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/README.md +4 -22
- package/cli/mcp-server-stigmer.js +11 -11
- package/config.d.ts +1 -5
- package/config.d.ts.map +1 -1
- package/config.js +2 -2
- package/config.js.map +1 -1
- package/domains/resourceuri.d.ts.map +1 -1
- package/domains/resourceuri.js +0 -1
- package/domains/resourceuri.js.map +1 -1
- package/domains/search/tools.d.ts.map +1 -1
- package/domains/search/tools.js +4 -5
- package/domains/search/tools.js.map +1 -1
- package/gen/agent.d.ts +0 -56
- package/gen/agent.d.ts.map +1 -1
- package/gen/agent.js +1 -24
- package/gen/agent.js.map +1 -1
- package/gen/workflow.d.ts +2 -2
- package/index.d.ts +1 -1
- package/index.d.ts.map +1 -1
- package/index.js +1 -1
- package/index.js.map +1 -1
- package/package.json +3 -3
- package/server.d.ts +6 -18
- package/server.d.ts.map +1 -1
- package/server.js +6 -36
- package/server.js.map +1 -1
- package/src/config.ts +3 -7
- package/src/domains/apply.integration.test.ts +3 -40
- package/src/domains/deletes.integration.test.ts +1 -25
- package/src/domains/reads.integration.test.ts +1 -18
- package/src/domains/resources.integration.test.ts +0 -18
- package/src/domains/resourceuri.test.ts +0 -2
- package/src/domains/resourceuri.ts +0 -1
- package/src/domains/search/search.integration.test.ts +3 -5
- package/src/domains/search/tools.ts +4 -5
- package/src/gen/agent.ts +1 -28
- package/src/http.integration.test.ts +2 -3
- package/src/index.ts +0 -2
- package/src/server.ts +6 -36
- package/domains/datastores/apply.d.ts +0 -4
- package/domains/datastores/apply.d.ts.map +0 -1
- package/domains/datastores/apply.js +0 -28
- package/domains/datastores/apply.js.map +0 -1
- package/domains/datastores/delete.d.ts +0 -3
- package/domains/datastores/delete.d.ts.map +0 -1
- package/domains/datastores/delete.js +0 -38
- package/domains/datastores/delete.js.map +0 -1
- package/domains/datastores/fetch.d.ts +0 -3
- package/domains/datastores/fetch.d.ts.map +0 -1
- package/domains/datastores/fetch.js +0 -23
- package/domains/datastores/fetch.js.map +0 -1
- package/domains/datastores/resources.d.ts +0 -5
- package/domains/datastores/resources.d.ts.map +0 -1
- package/domains/datastores/resources.js +0 -16
- package/domains/datastores/resources.js.map +0 -1
- package/domains/datastores/tools.d.ts +0 -5
- package/domains/datastores/tools.d.ts.map +0 -1
- package/domains/datastores/tools.js +0 -46
- package/domains/datastores/tools.js.map +0 -1
- package/domains/records/calls.d.ts +0 -48
- package/domains/records/calls.d.ts.map +0 -1
- package/domains/records/calls.js +0 -118
- package/domains/records/calls.js.map +0 -1
- package/domains/records/errors.d.ts +0 -24
- package/domains/records/errors.d.ts.map +0 -1
- package/domains/records/errors.js +0 -79
- package/domains/records/errors.js.map +0 -1
- package/domains/records/tools.d.ts +0 -7
- package/domains/records/tools.d.ts.map +0 -1
- package/domains/records/tools.js +0 -139
- package/domains/records/tools.js.map +0 -1
- package/gen/datastore.d.ts +0 -861
- package/gen/datastore.d.ts.map +0 -1
- package/gen/datastore.js +0 -267
- package/gen/datastore.js.map +0 -1
- package/src/domains/datastores/apply.ts +0 -33
- package/src/domains/datastores/delete.ts +0 -47
- package/src/domains/datastores/fetch.ts +0 -32
- package/src/domains/datastores/resources.ts +0 -21
- package/src/domains/datastores/tools.ts +0 -76
- package/src/domains/records/calls.ts +0 -209
- package/src/domains/records/errors.ts +0 -98
- package/src/domains/records/records.integration.test.ts +0 -313
- package/src/domains/records/tools.ts +0 -210
- package/src/gen/datastore.ts +0 -264
|
@@ -1,210 +0,0 @@
|
|
|
1
|
-
// MCP tools for the Datastore record domain — the five agent-facing
|
|
2
|
-
// record tools of DD-005: 1:1 projections of the record RPCs, with the
|
|
3
|
-
// records-own error mapper (errors.ts) and honest MCP annotations (the
|
|
4
|
-
// bridge's first use of annotations; the platform's own connect-time
|
|
5
|
-
// classifier honors destructiveHint fail-closed for external clients,
|
|
6
|
-
// while the runner-synthesized attachment is approval-free by
|
|
7
|
-
// construction and never passes through that classifier).
|
|
8
|
-
//
|
|
9
|
-
// Two audiences, one definition (registered per roster):
|
|
10
|
-
// - "agent" (the records-only roster the runner-synthesized
|
|
11
|
-
// attachment connects to): NO `org` argument — a session-bound
|
|
12
|
-
// caller's org is server-derived from the session, and an explicit
|
|
13
|
-
// org is rejected (T05 R3), so offering the argument would only
|
|
14
|
-
// invite rejected calls.
|
|
15
|
-
// - "direct" (the main roster external MCP clients see): optional
|
|
16
|
-
// `org`, which cloud direct principals must name (their credential
|
|
17
|
-
// carries no ambient org — the DD-006 session-14 amendment). OSS
|
|
18
|
-
// resolves an empty org to the local system org.
|
|
19
|
-
// `partition` is deliberately NOT a tool argument for either audience
|
|
20
|
-
// (DD-010: never from tool arguments); partition-explicit work is the
|
|
21
|
-
// console/CLI/SDK surface.
|
|
22
|
-
|
|
23
|
-
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
24
|
-
import { z } from "zod";
|
|
25
|
-
|
|
26
|
-
import { resolveToken, type BackendTarget } from "../client.js";
|
|
27
|
-
import {
|
|
28
|
-
deleteRecord,
|
|
29
|
-
describeDatastore,
|
|
30
|
-
findRecords,
|
|
31
|
-
insertRecord,
|
|
32
|
-
updateRecord,
|
|
33
|
-
type ConditionArg,
|
|
34
|
-
} from "./calls.js";
|
|
35
|
-
import { recordResult } from "./errors.js";
|
|
36
|
-
|
|
37
|
-
/** Which roster the tools are being registered on (see file header). */
|
|
38
|
-
export type RecordToolAudience = "agent" | "direct";
|
|
39
|
-
|
|
40
|
-
const ENCODINGS =
|
|
41
|
-
"Field value encodings: date YYYY-MM-DD, time HH:MM[:SS], timestamp RFC 3339 UTC " +
|
|
42
|
-
"(e.g. 2026-07-21T04:30:00Z).";
|
|
43
|
-
|
|
44
|
-
const scalar = z.union([z.string(), z.number(), z.boolean()]);
|
|
45
|
-
|
|
46
|
-
const conditionShape = z.object({
|
|
47
|
-
field: z
|
|
48
|
-
.string()
|
|
49
|
-
.describe("Declared field name, or a filterable system field (id, created_at, updated_at)."),
|
|
50
|
-
op: z
|
|
51
|
-
.enum(["eq", "neq", "gt", "gte", "lt", "lte", "in", "not_in", "is_null", "not_null"])
|
|
52
|
-
.describe(
|
|
53
|
-
"Comparison operator. eq/neq/in/not_in for string and enum fields; " +
|
|
54
|
-
"gt/gte/lt/lte for numeric and temporal fields; is_null/not_null for optional fields.",
|
|
55
|
-
),
|
|
56
|
-
value: scalar
|
|
57
|
-
.nullable()
|
|
58
|
-
.optional()
|
|
59
|
-
.describe("Comparison value for scalar operators, in the field's canonical encoding."),
|
|
60
|
-
values: z.array(scalar).optional().describe("Comparison values for in / not_in."),
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
/** Register the five record tools for one audience; returns the tool names. */
|
|
64
|
-
export function registerRecordTools(
|
|
65
|
-
server: McpServer,
|
|
66
|
-
target: BackendTarget,
|
|
67
|
-
audience: RecordToolAudience,
|
|
68
|
-
): string[] {
|
|
69
|
-
// The org argument exists only on the direct roster (file header).
|
|
70
|
-
const orgShape: { org?: z.ZodOptional<z.ZodString> } = {};
|
|
71
|
-
if (audience === "direct") {
|
|
72
|
-
orgShape.org = z
|
|
73
|
-
.string()
|
|
74
|
-
.optional()
|
|
75
|
-
.describe(
|
|
76
|
-
"Organization that owns the datastore. Required on Stigmer Cloud; " +
|
|
77
|
-
"omit against a local backend.",
|
|
78
|
-
);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
server.registerTool(
|
|
82
|
-
"describe_datastore",
|
|
83
|
-
{
|
|
84
|
-
description:
|
|
85
|
-
"Describe a datastore: its collections, field declarations and encodings, constraint " +
|
|
86
|
-
"messages, data partitions, and the verbs you are allowed to use per collection " +
|
|
87
|
-
"(empty access means you are not allowed to touch that collection). A read verb may " +
|
|
88
|
-
"carry readable_fields: reads then return only those fields, and only they may appear " +
|
|
89
|
-
"in filter conditions and order_by (empty readable_fields means every field). Call " +
|
|
90
|
-
`this before the first record operation against an unfamiliar datastore. ${ENCODINGS}`,
|
|
91
|
-
inputSchema: {
|
|
92
|
-
datastore: z.string().describe("Datastore slug (e.g. clinic)."),
|
|
93
|
-
...orgShape,
|
|
94
|
-
},
|
|
95
|
-
annotations: { readOnlyHint: true },
|
|
96
|
-
},
|
|
97
|
-
(args, extra) =>
|
|
98
|
-
recordResult(`datastore "${args.datastore}"`, () =>
|
|
99
|
-
describeDatastore(target.serverAddress, resolveToken(extra, target.apiKey), args),
|
|
100
|
-
),
|
|
101
|
-
);
|
|
102
|
-
|
|
103
|
-
server.registerTool(
|
|
104
|
-
"find_records",
|
|
105
|
-
{
|
|
106
|
-
description:
|
|
107
|
-
"Find records in a datastore collection with a typed filter (conditions are AND-combined). " +
|
|
108
|
-
"Returns records plus total/limit/offset for paging. Results are ordered by created_at " +
|
|
109
|
-
"descending unless order_by is given. Records carry only the fields your grant allows " +
|
|
110
|
-
"(describe_datastore lists them as readable_fields); conditions and order_by on other " +
|
|
111
|
-
`fields are rejected. ${ENCODINGS}`,
|
|
112
|
-
inputSchema: {
|
|
113
|
-
datastore: z.string().describe("Datastore slug (e.g. clinic)."),
|
|
114
|
-
...orgShape,
|
|
115
|
-
collection: z.string().describe("Collection to query (e.g. bookings)."),
|
|
116
|
-
conditions: z
|
|
117
|
-
.array(conditionShape)
|
|
118
|
-
.optional()
|
|
119
|
-
.describe("Filter conditions, AND-combined. Omit to list all readable records."),
|
|
120
|
-
order_by: z
|
|
121
|
-
.object({
|
|
122
|
-
field: z.string().describe("Field to sort by."),
|
|
123
|
-
direction: z.enum(["asc", "desc"]).optional().describe("Sort direction (default asc)."),
|
|
124
|
-
})
|
|
125
|
-
.optional()
|
|
126
|
-
.describe("Sort order. Omit for created_at descending with id tiebreak."),
|
|
127
|
-
limit: z.number().int().optional().describe("Page size (default 25, max 100)."),
|
|
128
|
-
offset: z.number().int().optional().describe("Records to skip for paging."),
|
|
129
|
-
},
|
|
130
|
-
annotations: { readOnlyHint: true },
|
|
131
|
-
},
|
|
132
|
-
(args, extra) =>
|
|
133
|
-
recordResult(`records in "${args.datastore}/${args.collection}"`, () =>
|
|
134
|
-
findRecords(target.serverAddress, resolveToken(extra, target.apiKey), {
|
|
135
|
-
...args,
|
|
136
|
-
conditions: args.conditions as ConditionArg[] | undefined,
|
|
137
|
-
}),
|
|
138
|
-
),
|
|
139
|
-
);
|
|
140
|
-
|
|
141
|
-
server.registerTool(
|
|
142
|
-
"insert_record",
|
|
143
|
-
{
|
|
144
|
-
description:
|
|
145
|
-
"Insert one record into a datastore collection. System fields (id, created_at, " +
|
|
146
|
-
"updated_at, created_by) are server-stamped — never include them. Declared constraints " +
|
|
147
|
-
"are enforced by the store; a violation returns the constraint's message verbatim " +
|
|
148
|
-
"(e.g. a taken slot returns ALREADY_EXISTS — pick another, do not retry the same one). " +
|
|
149
|
-
`When uncertain whether a record already exists, find first. ${ENCODINGS}`,
|
|
150
|
-
inputSchema: {
|
|
151
|
-
datastore: z.string().describe("Datastore slug (e.g. clinic)."),
|
|
152
|
-
...orgShape,
|
|
153
|
-
collection: z.string().describe("Collection to insert into (e.g. bookings)."),
|
|
154
|
-
record: z
|
|
155
|
-
.record(z.unknown())
|
|
156
|
-
.describe("Declared fields for the new record, in canonical encodings."),
|
|
157
|
-
},
|
|
158
|
-
},
|
|
159
|
-
(args, extra) =>
|
|
160
|
-
recordResult(`record in "${args.datastore}/${args.collection}"`, () =>
|
|
161
|
-
insertRecord(target.serverAddress, resolveToken(extra, target.apiKey), args),
|
|
162
|
-
),
|
|
163
|
-
);
|
|
164
|
-
|
|
165
|
-
server.registerTool(
|
|
166
|
-
"update_record",
|
|
167
|
-
{
|
|
168
|
-
description:
|
|
169
|
-
"Update one record by id with a partial merge: only the supplied fields change, and an " +
|
|
170
|
-
"explicit null clears a field. Constraints are re-evaluated on the merged result. " +
|
|
171
|
-
`Returns the full updated record. ${ENCODINGS}`,
|
|
172
|
-
inputSchema: {
|
|
173
|
-
datastore: z.string().describe("Datastore slug (e.g. clinic)."),
|
|
174
|
-
...orgShape,
|
|
175
|
-
collection: z.string().describe("Collection holding the record."),
|
|
176
|
-
id: z.string().describe("Record id (from a previous find or insert)."),
|
|
177
|
-
fields: z
|
|
178
|
-
.record(z.unknown())
|
|
179
|
-
.describe("Fields to merge. Explicit null clears a field; omitted fields keep their values."),
|
|
180
|
-
},
|
|
181
|
-
annotations: { idempotentHint: true },
|
|
182
|
-
},
|
|
183
|
-
(args, extra) =>
|
|
184
|
-
recordResult(`record "${args.id}" in "${args.datastore}/${args.collection}"`, () =>
|
|
185
|
-
updateRecord(target.serverAddress, resolveToken(extra, target.apiKey), args),
|
|
186
|
-
),
|
|
187
|
-
);
|
|
188
|
-
|
|
189
|
-
server.registerTool(
|
|
190
|
-
"delete_record",
|
|
191
|
-
{
|
|
192
|
-
description:
|
|
193
|
-
"Delete one record by id. Returns the deleted record. Record tools never delete " +
|
|
194
|
-
"collections or datastores.",
|
|
195
|
-
inputSchema: {
|
|
196
|
-
datastore: z.string().describe("Datastore slug (e.g. clinic)."),
|
|
197
|
-
...orgShape,
|
|
198
|
-
collection: z.string().describe("Collection holding the record."),
|
|
199
|
-
id: z.string().describe("Record id (from a previous find or insert)."),
|
|
200
|
-
},
|
|
201
|
-
annotations: { destructiveHint: true, idempotentHint: true },
|
|
202
|
-
},
|
|
203
|
-
(args, extra) =>
|
|
204
|
-
recordResult(`record "${args.id}" in "${args.datastore}/${args.collection}"`, () =>
|
|
205
|
-
deleteRecord(target.serverAddress, resolveToken(extra, target.apiKey), args),
|
|
206
|
-
),
|
|
207
|
-
);
|
|
208
|
-
|
|
209
|
-
return ["describe_datastore", "find_records", "insert_record", "update_record", "delete_record"];
|
|
210
|
-
}
|
package/src/gen/datastore.ts
DELETED
|
@@ -1,264 +0,0 @@
|
|
|
1
|
-
// Code generated by stigmer-codegen --target=mcp-ts. DO NOT EDIT.
|
|
2
|
-
//
|
|
3
|
-
// Flattened apply-input zod schema + toProto bridge for the Datastore resource.
|
|
4
|
-
// Source proto package: ai.stigmer.agentic.datastore.v1
|
|
5
|
-
|
|
6
|
-
import { generateSlug, visibilityFromString, enumFromString } from "./apply-runtime.js";
|
|
7
|
-
import { create, fromJson, type JsonValue } from "@bufbuild/protobuf";
|
|
8
|
-
import { ValueSchema } from "@bufbuild/protobuf/wkt";
|
|
9
|
-
import { DatastoreSchema, type Datastore } from "@stigmer/protos/ai/stigmer/agentic/datastore/v1/api_pb";
|
|
10
|
-
import { DatastoreSpecSchema, DatastoreRoleSchema, ChannelSenderSubjectSchema, DatastoreSubjectSchema, DatastoreRoleBindingSchema, DatastoreAuthorizationSchema, FieldDeclarationSchema, FieldType, UniqueWhereSchema, UniqueConstraintSchema, CheckConstraintSchema, ExistsConstraintSchema, DatastoreGrantSchema, DatastoreVerb, DatastoreGrantScope, CollectionDeclarationSchema } from "@stigmer/protos/ai/stigmer/agentic/datastore/v1/spec_pb";
|
|
11
|
-
import { ApiResourceMetadataSchema } from "@stigmer/protos/ai/stigmer/commons/apiresource/metadata_pb";
|
|
12
|
-
import { ApiResourceRefSchema } from "@stigmer/protos/ai/stigmer/iam/iampolicy/v1/spec_pb";
|
|
13
|
-
import { z } from "zod";
|
|
14
|
-
|
|
15
|
-
/** DatastoreSpec declares typed record collections with constraints and role-aware access grants. */
|
|
16
|
-
export const DatastoreInputShape = {
|
|
17
|
-
name: z.string().describe("Human-readable name of the resource."),
|
|
18
|
-
slug: z.string().optional().describe("URL-friendly identifier (lowercase alphanumeric with hyphens). Auto-generated from name if omitted."),
|
|
19
|
-
org: z.string().describe("Organization that owns this resource (e.g. acme)."),
|
|
20
|
-
visibility: z.string().optional().describe("Resource visibility: PRIVATE or PUBLIC. Omit to leave unchanged on updates."),
|
|
21
|
-
labels: z.record(z.string()).optional().describe("Key-value labels for organization and filtering."),
|
|
22
|
-
tags: z.array(z.string()).optional().describe("Tags for categorization and discovery."),
|
|
23
|
-
description: z.string().optional().describe("Human-readable description of what this datastore holds."),
|
|
24
|
-
timezone: z.string().optional().describe("IANA timezone (e.g. 'Asia/Kolkata') for constraint evaluation. Bound as the constant 'tz' in check and exists constraint expressions. Optional; when unset, expressions that reference 'tz' are rejected at apply time."),
|
|
25
|
-
authorization: z.lazy(() => DatastoreAuthorizationInputSchema).optional().describe("Record-layer authorization: roles, subject bindings, and the default role for unbound callers."),
|
|
26
|
-
collections: z.array(z.lazy(() => CollectionDeclarationInputSchema)).optional().describe("Record collections declared by this datastore."),
|
|
27
|
-
} as const;
|
|
28
|
-
|
|
29
|
-
export const DatastoreInputSchema = z.object(DatastoreInputShape);
|
|
30
|
-
export type DatastoreInput = z.infer<typeof DatastoreInputSchema>;
|
|
31
|
-
|
|
32
|
-
const DatastoreRoleInputSchema = z.object({
|
|
33
|
-
name: z.string().describe("Role name, referenced by bindings, grants, and default_role."),
|
|
34
|
-
});
|
|
35
|
-
type DatastoreRoleInput = z.infer<typeof DatastoreRoleInputSchema>;
|
|
36
|
-
|
|
37
|
-
const ChannelSenderSubjectInputSchema = z.object({
|
|
38
|
-
sender_kind: z.string().describe("Sender identity namespace. Must be a namespace the platform's channel brokers can stamp — today 'whatsapp_phone'; unrecognized namespaces are refused at apply time (they could never match)."),
|
|
39
|
-
value: z.string().describe("Sender identity value within the namespace, in the exact wire shape the channel broker stamps. For 'whatsapp_phone' this is Meta's wa_id: DIGITS ONLY, no '+', no separators (e.g. '919800000001' — NOT the '+'-prefixed E.164 form). Apply-time validation refuses any other shape, because subject matching is exact string equality and a mis-shaped value would bind and silently never match."),
|
|
40
|
-
});
|
|
41
|
-
type ChannelSenderSubjectInput = z.infer<typeof ChannelSenderSubjectInputSchema>;
|
|
42
|
-
|
|
43
|
-
const ApiResourceRefInputSchema = z.object({
|
|
44
|
-
kind: z.string().describe("Type of the API resource being referenced This should be the resource kind as defined in ApiResourceKind enum. Examples: 'identity_account', 'team', 'organization', 'environment', 'cloud_resource', 'service', etc."),
|
|
45
|
-
id: z.string().describe("Unique identifier of the resource This is the resource's ID field (e.g., ia-01HQUSER123, tm-01HQTEAM456)"),
|
|
46
|
-
relation: z.string().optional().describe("Optional relation qualifier for the resource reference. Used when the reference needs additional context about the relationship. Primary use case: For team principals, this specifies the relation of the user to the team (e.g., 'member', 'admin'). Example: principal { kind: 'team', id: 'tm-123', relation: 'member' } means 'members of team tm-123' This field qualifies HOW the principal relates to this resource reference, NOT the permission being granted (that's IamPolicySpec.relation)."),
|
|
47
|
-
});
|
|
48
|
-
type ApiResourceRefInput = z.infer<typeof ApiResourceRefInputSchema>;
|
|
49
|
-
|
|
50
|
-
const DatastoreSubjectInputSchema = z.object({
|
|
51
|
-
channel_sender: z.lazy(() => ChannelSenderSubjectInputSchema).optional().describe("Channel sender identity, matched against the session metadata stamped by the channel broker."),
|
|
52
|
-
principal: z.lazy(() => ApiResourceRefInputSchema).optional().describe("Platform principal (an identity account today; teams later without schema change)."),
|
|
53
|
-
});
|
|
54
|
-
type DatastoreSubjectInput = z.infer<typeof DatastoreSubjectInputSchema>;
|
|
55
|
-
|
|
56
|
-
const DatastoreRoleBindingInputSchema = z.object({
|
|
57
|
-
subject: z.lazy(() => DatastoreSubjectInputSchema).describe("The caller identity this binding matches."),
|
|
58
|
-
role: z.string().describe("Name of a declared role."),
|
|
59
|
-
});
|
|
60
|
-
type DatastoreRoleBindingInput = z.infer<typeof DatastoreRoleBindingInputSchema>;
|
|
61
|
-
|
|
62
|
-
const DatastoreAuthorizationInputSchema = z.object({
|
|
63
|
-
roles: z.array(z.lazy(() => DatastoreRoleInputSchema)).optional().describe("Role names referenced by bindings and grants (e.g. 'admin', 'patient')."),
|
|
64
|
-
bindings: z.array(z.lazy(() => DatastoreRoleBindingInputSchema)).optional().describe("Subject-to-role bindings."),
|
|
65
|
-
default_role: z.string().optional().describe("Role an unbound caller resolves to. Applies to channel senders with no binding and platform principals with no binding. When empty, unbound callers are denied every verb."),
|
|
66
|
-
});
|
|
67
|
-
type DatastoreAuthorizationInput = z.infer<typeof DatastoreAuthorizationInputSchema>;
|
|
68
|
-
|
|
69
|
-
const FieldDeclarationInputSchema = z.object({
|
|
70
|
-
name: z.string().describe("Field name, unique within the collection. Format: lowercase snake_case. The record envelope's system field names (id, created_at, updated_at, created_by), 'org', and 'partition' are reserved."),
|
|
71
|
-
type: z.string().describe("Value type for this field. Allowed values: string, integer, number, bool, timestamp, date, time, json."),
|
|
72
|
-
required: z.boolean().optional().describe("Whether a value must be present on every record. Absent optional fields evaluate as null in constraint expressions."),
|
|
73
|
-
default: z.unknown().optional().describe("Default value applied when the field is absent on insert."),
|
|
74
|
-
enum_values: z.array(z.string()).optional().describe("Allowed values for string fields. Declaring enum_values on a non-string field is rejected."),
|
|
75
|
-
description: z.string().optional().describe("Human-readable description, surfaced to agents via describe_datastore."),
|
|
76
|
-
});
|
|
77
|
-
type FieldDeclarationInput = z.infer<typeof FieldDeclarationInputSchema>;
|
|
78
|
-
|
|
79
|
-
const UniqueWhereInputSchema = z.object({
|
|
80
|
-
field: z.string().describe("Declared field the condition tests."),
|
|
81
|
-
equals: z.unknown().describe("Value the field must equal for the constraint to apply."),
|
|
82
|
-
});
|
|
83
|
-
type UniqueWhereInput = z.infer<typeof UniqueWhereInputSchema>;
|
|
84
|
-
|
|
85
|
-
const UniqueConstraintInputSchema = z.object({
|
|
86
|
-
name: z.string().describe("Constraint name, unique within the collection."),
|
|
87
|
-
fields: z.array(z.string()).optional().describe("Declared fields whose combined values must be unique."),
|
|
88
|
-
where: z.lazy(() => UniqueWhereInputSchema).optional().describe("Limits the constraint to records matching this condition (e.g. only 'status == 'confirmed'' bookings must be unique per slot)."),
|
|
89
|
-
message: z.string().describe("Error message returned on violation, written to be relayed to an end user by an agent (e.g. 'that slot is already booked')."),
|
|
90
|
-
});
|
|
91
|
-
type UniqueConstraintInput = z.infer<typeof UniqueConstraintInputSchema>;
|
|
92
|
-
|
|
93
|
-
const CheckConstraintInputSchema = z.object({
|
|
94
|
-
name: z.string().describe("Constraint name, unique within the collection."),
|
|
95
|
-
expression: z.string().describe("CEL expression that must evaluate true for the record to be written."),
|
|
96
|
-
when: z.string().optional().describe("Optional CEL gate: the check applies only when this evaluates true."),
|
|
97
|
-
message: z.string().describe("Error message returned on violation, written to be relayed to an end user by an agent."),
|
|
98
|
-
});
|
|
99
|
-
type CheckConstraintInput = z.infer<typeof CheckConstraintInputSchema>;
|
|
100
|
-
|
|
101
|
-
const ExistsConstraintInputSchema = z.object({
|
|
102
|
-
name: z.string().describe("Constraint name, unique within the collection."),
|
|
103
|
-
collection: z.string().describe("Target collection in the same datastore."),
|
|
104
|
-
where: z.string().describe("CEL condition matching candidate ('this') against target records ('that')."),
|
|
105
|
-
when: z.string().optional().describe("Optional CEL gate: the constraint applies only when this evaluates true."),
|
|
106
|
-
message: z.string().describe("Error message returned on violation, written to be relayed to an end user by an agent."),
|
|
107
|
-
});
|
|
108
|
-
type ExistsConstraintInput = z.infer<typeof ExistsConstraintInputSchema>;
|
|
109
|
-
|
|
110
|
-
const DatastoreGrantInputSchema = z.object({
|
|
111
|
-
role: z.string().describe("Name of a declared role."),
|
|
112
|
-
verbs: z.array(z.string()).optional().describe("Verbs granted to the role on this collection. Allowed values: read, insert, update, delete."),
|
|
113
|
-
scope: z.string().optional().describe("Row scope of the grant. Defaults to all records. 'own' limits update and delete (and read) to records whose server-stamped created_by matches the caller. Allowed values: all, own."),
|
|
114
|
-
read_fields: z.array(z.string()).optional().describe("Field allowlist for the read verb: when set, reads by this role return only these fields, and filter conditions and order_by may reference only these fields. Empty means every field is readable. Entries name declared fields of the collection, or 'created_by' to expose the attribution subject. The system fields id, created_at, and updated_at are always readable."),
|
|
115
|
-
});
|
|
116
|
-
type DatastoreGrantInput = z.infer<typeof DatastoreGrantInputSchema>;
|
|
117
|
-
|
|
118
|
-
const CollectionDeclarationInputSchema = z.object({
|
|
119
|
-
name: z.string().describe("Collection name, unique within the datastore. Format: lowercase snake_case (e.g. 'bookings', 'schedule_exceptions')."),
|
|
120
|
-
description: z.string().optional().describe("Human-readable description of what records in this collection represent."),
|
|
121
|
-
fields: z.array(z.lazy(() => FieldDeclarationInputSchema)).optional().describe("Typed field declarations for records in this collection."),
|
|
122
|
-
uniques: z.array(z.lazy(() => UniqueConstraintInputSchema)).optional().describe("Uniqueness constraints, enforced by substrate indexes."),
|
|
123
|
-
checks: z.array(z.lazy(() => CheckConstraintInputSchema)).optional().describe("Single-record check constraints, evaluated on every insert and update."),
|
|
124
|
-
exists: z.array(z.lazy(() => ExistsConstraintInputSchema)).optional().describe("Cross-collection constraints requiring a matching record to exist."),
|
|
125
|
-
not_exists: z.array(z.lazy(() => ExistsConstraintInputSchema)).optional().describe("Cross-collection constraints requiring no matching record to exist."),
|
|
126
|
-
grants: z.array(z.lazy(() => DatastoreGrantInputSchema)).optional().describe("Per-role access grants for records in this collection. Deny by default: a role with no grant on a collection has no access to it."),
|
|
127
|
-
});
|
|
128
|
-
type CollectionDeclarationInput = z.infer<typeof CollectionDeclarationInputSchema>;
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
/** Build the fully-formed Datastore proto from the flat MCP apply input. */
|
|
132
|
-
export function datastoreInputToProto(input: DatastoreInput): Datastore {
|
|
133
|
-
const slug = input.slug && input.slug.length > 0 ? input.slug : generateSlug(input.name);
|
|
134
|
-
const spec = create(DatastoreSpecSchema);
|
|
135
|
-
if (input.description !== undefined) spec.description = input.description;
|
|
136
|
-
if (input.timezone !== undefined) spec.timezone = input.timezone;
|
|
137
|
-
if (input.authorization !== undefined) spec.authorization = datastoreAuthorizationInputToProto(input.authorization);
|
|
138
|
-
if (input.collections !== undefined) spec.collections = input.collections.map(collectionDeclarationInputToProto);
|
|
139
|
-
return Object.assign(create(DatastoreSchema), {
|
|
140
|
-
apiVersion: "agentic.stigmer.ai/v1",
|
|
141
|
-
kind: "Datastore",
|
|
142
|
-
metadata: Object.assign(create(ApiResourceMetadataSchema), {
|
|
143
|
-
name: input.name,
|
|
144
|
-
slug,
|
|
145
|
-
org: input.org,
|
|
146
|
-
...(input.visibility !== undefined && { visibility: visibilityFromString(input.visibility) }),
|
|
147
|
-
...(input.labels !== undefined && { labels: input.labels }),
|
|
148
|
-
...(input.tags !== undefined && { tags: input.tags }),
|
|
149
|
-
}),
|
|
150
|
-
spec,
|
|
151
|
-
}) as Datastore;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
function datastoreRoleInputToProto(input: DatastoreRoleInput) {
|
|
155
|
-
const result = create(DatastoreRoleSchema);
|
|
156
|
-
if (input.name !== undefined) result.name = input.name;
|
|
157
|
-
return result;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
function channelSenderSubjectInputToProto(input: ChannelSenderSubjectInput) {
|
|
161
|
-
const result = create(ChannelSenderSubjectSchema);
|
|
162
|
-
if (input.sender_kind !== undefined) result.senderKind = input.sender_kind;
|
|
163
|
-
if (input.value !== undefined) result.value = input.value;
|
|
164
|
-
return result;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
function apiResourceRefInputToProto(input: ApiResourceRefInput) {
|
|
168
|
-
const result = create(ApiResourceRefSchema);
|
|
169
|
-
if (input.kind !== undefined) result.kind = input.kind;
|
|
170
|
-
if (input.id !== undefined) result.id = input.id;
|
|
171
|
-
if (input.relation !== undefined) result.relation = input.relation;
|
|
172
|
-
return result;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
function datastoreSubjectInputToProto(input: DatastoreSubjectInput) {
|
|
176
|
-
const result = create(DatastoreSubjectSchema);
|
|
177
|
-
if (input.channel_sender !== undefined) result.kind = { case: "channelSender", value: channelSenderSubjectInputToProto(input.channel_sender) };
|
|
178
|
-
if (input.principal !== undefined) result.kind = { case: "principal", value: apiResourceRefInputToProto(input.principal) };
|
|
179
|
-
return result;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
function datastoreRoleBindingInputToProto(input: DatastoreRoleBindingInput) {
|
|
183
|
-
const result = create(DatastoreRoleBindingSchema);
|
|
184
|
-
if (input.subject !== undefined) result.subject = datastoreSubjectInputToProto(input.subject);
|
|
185
|
-
if (input.role !== undefined) result.role = input.role;
|
|
186
|
-
return result;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
function datastoreAuthorizationInputToProto(input: DatastoreAuthorizationInput) {
|
|
190
|
-
const result = create(DatastoreAuthorizationSchema);
|
|
191
|
-
if (input.roles !== undefined) result.roles = input.roles.map(datastoreRoleInputToProto);
|
|
192
|
-
if (input.bindings !== undefined) result.bindings = input.bindings.map(datastoreRoleBindingInputToProto);
|
|
193
|
-
if (input.default_role !== undefined) result.defaultRole = input.default_role;
|
|
194
|
-
return result;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
function fieldDeclarationInputToProto(input: FieldDeclarationInput) {
|
|
198
|
-
const result = create(FieldDeclarationSchema);
|
|
199
|
-
if (input.name !== undefined) result.name = input.name;
|
|
200
|
-
result.type = enumFromString(FieldType, input.type) as FieldType;
|
|
201
|
-
if (input.required !== undefined) result.required = input.required;
|
|
202
|
-
if (input.default !== undefined) result.default = fromJson(ValueSchema, input.default as JsonValue);
|
|
203
|
-
if (input.enum_values !== undefined) result.enumValues = input.enum_values;
|
|
204
|
-
if (input.description !== undefined) result.description = input.description;
|
|
205
|
-
return result;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
function uniqueWhereInputToProto(input: UniqueWhereInput) {
|
|
209
|
-
const result = create(UniqueWhereSchema);
|
|
210
|
-
if (input.field !== undefined) result.field = input.field;
|
|
211
|
-
if (input.equals !== undefined) result.equals = fromJson(ValueSchema, input.equals as JsonValue);
|
|
212
|
-
return result;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
function uniqueConstraintInputToProto(input: UniqueConstraintInput) {
|
|
216
|
-
const result = create(UniqueConstraintSchema);
|
|
217
|
-
if (input.name !== undefined) result.name = input.name;
|
|
218
|
-
if (input.fields !== undefined) result.fields = input.fields;
|
|
219
|
-
if (input.where !== undefined) result.where = uniqueWhereInputToProto(input.where);
|
|
220
|
-
if (input.message !== undefined) result.message = input.message;
|
|
221
|
-
return result;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
function checkConstraintInputToProto(input: CheckConstraintInput) {
|
|
225
|
-
const result = create(CheckConstraintSchema);
|
|
226
|
-
if (input.name !== undefined) result.name = input.name;
|
|
227
|
-
if (input.expression !== undefined) result.expression = input.expression;
|
|
228
|
-
if (input.when !== undefined) result.when = input.when;
|
|
229
|
-
if (input.message !== undefined) result.message = input.message;
|
|
230
|
-
return result;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
function existsConstraintInputToProto(input: ExistsConstraintInput) {
|
|
234
|
-
const result = create(ExistsConstraintSchema);
|
|
235
|
-
if (input.name !== undefined) result.name = input.name;
|
|
236
|
-
if (input.collection !== undefined) result.collection = input.collection;
|
|
237
|
-
if (input.where !== undefined) result.where = input.where;
|
|
238
|
-
if (input.when !== undefined) result.when = input.when;
|
|
239
|
-
if (input.message !== undefined) result.message = input.message;
|
|
240
|
-
return result;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
function datastoreGrantInputToProto(input: DatastoreGrantInput) {
|
|
244
|
-
const result = create(DatastoreGrantSchema);
|
|
245
|
-
if (input.role !== undefined) result.role = input.role;
|
|
246
|
-
if (input.verbs !== undefined) result.verbs = input.verbs.map((v) => enumFromString(DatastoreVerb, v) as DatastoreVerb);
|
|
247
|
-
result.scope = enumFromString(DatastoreGrantScope, input.scope) as DatastoreGrantScope;
|
|
248
|
-
if (input.read_fields !== undefined) result.readFields = input.read_fields;
|
|
249
|
-
return result;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
function collectionDeclarationInputToProto(input: CollectionDeclarationInput) {
|
|
253
|
-
const result = create(CollectionDeclarationSchema);
|
|
254
|
-
if (input.name !== undefined) result.name = input.name;
|
|
255
|
-
if (input.description !== undefined) result.description = input.description;
|
|
256
|
-
if (input.fields !== undefined) result.fields = input.fields.map(fieldDeclarationInputToProto);
|
|
257
|
-
if (input.uniques !== undefined) result.uniques = input.uniques.map(uniqueConstraintInputToProto);
|
|
258
|
-
if (input.checks !== undefined) result.checks = input.checks.map(checkConstraintInputToProto);
|
|
259
|
-
if (input.exists !== undefined) result.exists = input.exists.map(existsConstraintInputToProto);
|
|
260
|
-
if (input.not_exists !== undefined) result.notExists = input.not_exists.map(existsConstraintInputToProto);
|
|
261
|
-
if (input.grants !== undefined) result.grants = input.grants.map(datastoreGrantInputToProto);
|
|
262
|
-
return result;
|
|
263
|
-
}
|
|
264
|
-
|