@stigmer/mcp-server 3.3.0 → 3.4.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 (149) hide show
  1. package/README.md +60 -13
  2. package/cli/mcp-server-stigmer.js +5 -1
  3. package/domains/agentexecutions/approve.d.ts +9 -0
  4. package/domains/agentexecutions/approve.d.ts.map +1 -0
  5. package/domains/agentexecutions/approve.js +46 -0
  6. package/domains/agentexecutions/approve.js.map +1 -0
  7. package/domains/agentexecutions/fetch.d.ts +28 -0
  8. package/domains/agentexecutions/fetch.d.ts.map +1 -0
  9. package/domains/agentexecutions/fetch.js +82 -0
  10. package/domains/agentexecutions/fetch.js.map +1 -0
  11. package/domains/agentexecutions/run.d.ts +27 -0
  12. package/domains/agentexecutions/run.d.ts.map +1 -0
  13. package/domains/agentexecutions/run.js +87 -0
  14. package/domains/agentexecutions/run.js.map +1 -0
  15. package/domains/agentexecutions/tools.d.ts +5 -0
  16. package/domains/agentexecutions/tools.d.ts.map +1 -0
  17. package/domains/agentexecutions/tools.js +97 -0
  18. package/domains/agentexecutions/tools.js.map +1 -0
  19. package/domains/datastores/apply.d.ts +4 -0
  20. package/domains/datastores/apply.d.ts.map +1 -0
  21. package/domains/datastores/apply.js +28 -0
  22. package/domains/datastores/apply.js.map +1 -0
  23. package/domains/datastores/delete.d.ts +3 -0
  24. package/domains/datastores/delete.d.ts.map +1 -0
  25. package/domains/datastores/delete.js +38 -0
  26. package/domains/datastores/delete.js.map +1 -0
  27. package/domains/datastores/fetch.d.ts +3 -0
  28. package/domains/datastores/fetch.d.ts.map +1 -0
  29. package/domains/datastores/fetch.js +23 -0
  30. package/domains/datastores/fetch.js.map +1 -0
  31. package/domains/datastores/resources.d.ts +5 -0
  32. package/domains/datastores/resources.d.ts.map +1 -0
  33. package/domains/datastores/resources.js +16 -0
  34. package/domains/datastores/resources.js.map +1 -0
  35. package/domains/datastores/tools.d.ts +5 -0
  36. package/domains/datastores/tools.d.ts.map +1 -0
  37. package/domains/datastores/tools.js +46 -0
  38. package/domains/datastores/tools.js.map +1 -0
  39. package/domains/environments/apply.d.ts +4 -0
  40. package/domains/environments/apply.d.ts.map +1 -0
  41. package/domains/environments/apply.js +30 -0
  42. package/domains/environments/apply.js.map +1 -0
  43. package/domains/environments/delete.d.ts +3 -0
  44. package/domains/environments/delete.d.ts.map +1 -0
  45. package/domains/environments/delete.js +36 -0
  46. package/domains/environments/delete.js.map +1 -0
  47. package/domains/environments/fetch.d.ts +6 -0
  48. package/domains/environments/fetch.d.ts.map +1 -0
  49. package/domains/environments/fetch.js +30 -0
  50. package/domains/environments/fetch.js.map +1 -0
  51. package/domains/environments/resources.d.ts +5 -0
  52. package/domains/environments/resources.d.ts.map +1 -0
  53. package/domains/environments/resources.js +16 -0
  54. package/domains/environments/resources.js.map +1 -0
  55. package/domains/environments/tools.d.ts +5 -0
  56. package/domains/environments/tools.d.ts.map +1 -0
  57. package/domains/environments/tools.js +51 -0
  58. package/domains/environments/tools.js.map +1 -0
  59. package/domains/executions/cancel.d.ts +9 -0
  60. package/domains/executions/cancel.d.ts.map +1 -0
  61. package/domains/executions/cancel.js +114 -0
  62. package/domains/executions/cancel.js.map +1 -0
  63. package/domains/executions/tools.d.ts +5 -0
  64. package/domains/executions/tools.d.ts.map +1 -0
  65. package/domains/executions/tools.js +28 -0
  66. package/domains/executions/tools.js.map +1 -0
  67. package/domains/records/tools.d.ts.map +1 -1
  68. package/domains/records/tools.js +7 -3
  69. package/domains/records/tools.js.map +1 -1
  70. package/domains/resourceuri.d.ts.map +1 -1
  71. package/domains/resourceuri.js +2 -0
  72. package/domains/resourceuri.js.map +1 -1
  73. package/domains/search/tools.d.ts.map +1 -1
  74. package/domains/search/tools.js +10 -7
  75. package/domains/search/tools.js.map +1 -1
  76. package/domains/skills/tools.d.ts.map +1 -1
  77. package/domains/skills/tools.js +25 -1
  78. package/domains/skills/tools.js.map +1 -1
  79. package/domains/skills/versions.d.ts +9 -0
  80. package/domains/skills/versions.d.ts.map +1 -0
  81. package/domains/skills/versions.js +31 -0
  82. package/domains/skills/versions.js.map +1 -0
  83. package/domains/workflowexecutions/approvals.d.ts +17 -0
  84. package/domains/workflowexecutions/approvals.d.ts.map +1 -0
  85. package/domains/workflowexecutions/approvals.js +61 -0
  86. package/domains/workflowexecutions/approvals.js.map +1 -0
  87. package/domains/workflowexecutions/run.d.ts +12 -0
  88. package/domains/workflowexecutions/run.d.ts.map +1 -0
  89. package/domains/workflowexecutions/run.js +69 -0
  90. package/domains/workflowexecutions/run.js.map +1 -0
  91. package/domains/workflowexecutions/tools.d.ts.map +1 -1
  92. package/domains/workflowexecutions/tools.js +89 -6
  93. package/domains/workflowexecutions/tools.js.map +1 -1
  94. package/domains/workflows/tools.d.ts.map +1 -1
  95. package/domains/workflows/tools.js +69 -5
  96. package/domains/workflows/tools.js.map +1 -1
  97. package/domains/workflows/versions.d.ts +29 -0
  98. package/domains/workflows/versions.d.ts.map +1 -0
  99. package/domains/workflows/versions.js +99 -0
  100. package/domains/workflows/versions.js.map +1 -0
  101. package/gen/datastore.d.ts +861 -0
  102. package/gen/datastore.d.ts.map +1 -0
  103. package/gen/datastore.js +267 -0
  104. package/gen/datastore.js.map +1 -0
  105. package/gen/environment.d.ts +77 -0
  106. package/gen/environment.d.ts.map +1 -0
  107. package/gen/environment.js +62 -0
  108. package/gen/environment.js.map +1 -0
  109. package/package.json +3 -3
  110. package/server.d.ts.map +1 -1
  111. package/server.js +12 -0
  112. package/server.js.map +1 -1
  113. package/src/domains/agentexecutions/approve.ts +67 -0
  114. package/src/domains/agentexecutions/fetch.ts +112 -0
  115. package/src/domains/agentexecutions/run.ts +112 -0
  116. package/src/domains/agentexecutions/tools.ts +139 -0
  117. package/src/domains/apply.integration.test.ts +82 -6
  118. package/src/domains/datastores/apply.ts +33 -0
  119. package/src/domains/datastores/delete.ts +47 -0
  120. package/src/domains/datastores/fetch.ts +32 -0
  121. package/src/domains/datastores/resources.ts +21 -0
  122. package/src/domains/datastores/tools.ts +76 -0
  123. package/src/domains/deletes.integration.test.ts +49 -2
  124. package/src/domains/environments/apply.ts +40 -0
  125. package/src/domains/environments/delete.ts +45 -0
  126. package/src/domains/environments/fetch.ts +39 -0
  127. package/src/domains/environments/resources.ts +21 -0
  128. package/src/domains/environments/tools.ts +81 -0
  129. package/src/domains/executions/cancel.ts +148 -0
  130. package/src/domains/executions/tools.ts +45 -0
  131. package/src/domains/executions.integration.test.ts +376 -0
  132. package/src/domains/reads.integration.test.ts +51 -5
  133. package/src/domains/records/tools.ts +7 -3
  134. package/src/domains/resources.integration.test.ts +38 -2
  135. package/src/domains/resourceuri.test.ts +11 -2
  136. package/src/domains/resourceuri.ts +2 -0
  137. package/src/domains/search/search.integration.test.ts +18 -2
  138. package/src/domains/search/tools.ts +12 -7
  139. package/src/domains/skills/tools.ts +34 -1
  140. package/src/domains/skills/versions.ts +47 -0
  141. package/src/domains/versions.integration.test.ts +212 -0
  142. package/src/domains/workflowexecutions/approvals.ts +102 -0
  143. package/src/domains/workflowexecutions/run.ts +87 -0
  144. package/src/domains/workflowexecutions/tools.ts +120 -6
  145. package/src/domains/workflows/tools.ts +90 -7
  146. package/src/domains/workflows/versions.ts +147 -0
  147. package/src/gen/datastore.ts +264 -0
  148. package/src/gen/environment.ts +66 -0
  149. package/src/server.ts +12 -0
@@ -0,0 +1,21 @@
1
+ // Datastore resource template (stigmer://datastores/{org}/{slug}).
2
+
3
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4
+
5
+ import type { BackendTarget } from "../client.js";
6
+ import { registerResource } from "../resourcehandler.js";
7
+ import { fetchDatastore } from "./fetch.js";
8
+
9
+ /** Register the datastore resource template; returns the registered resource names. */
10
+ export function registerDatastoreResources(server: McpServer, target: BackendTarget): string[] {
11
+ registerResource(server, target, {
12
+ name: "stigmer_datastore",
13
+ title: "Stigmer Datastore",
14
+ description:
15
+ "Full definition of a Stigmer datastore (collections, constraints, grants), " +
16
+ "identified by organization and slug.",
17
+ template: "stigmer://datastores/{org}/{slug}",
18
+ fetch: fetchDatastore,
19
+ });
20
+ return ["stigmer_datastore"];
21
+ }
@@ -0,0 +1,76 @@
1
+ // MCP tools for the Datastore *definition* domain — the structure surface
2
+ // (collections, fields, constraints, roles, grants). The record tools
3
+ // (records/tools.ts) operate on the living data inside these structures;
4
+ // before this domain existed, an assistant could insert records into a
5
+ // datastore it had no way to create.
6
+
7
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
8
+ import { z } from "zod";
9
+
10
+ import { DatastoreInputShape } from "../../gen/datastore.js";
11
+ import { resolveToken, type BackendTarget } from "../client.js";
12
+ import { textOrError } from "../toolresult.js";
13
+ import { applyDatastore } from "./apply.js";
14
+ import { deleteDatastore } from "./delete.js";
15
+ import { fetchDatastore } from "./fetch.js";
16
+
17
+ /** Register every Datastore-domain tool; returns the registered tool names. */
18
+ export function registerDatastoreTools(server: McpServer, target: BackendTarget): string[] {
19
+ server.registerTool(
20
+ "get_datastore",
21
+ {
22
+ description:
23
+ "Get the full definition of a Stigmer datastore by its org and slug: collections, field " +
24
+ "declarations, constraints, roles, and grants. For the record-facing view of the same " +
25
+ "structure (allowed verbs, readable fields), use describe_datastore; for the data " +
26
+ "itself, use the record tools.",
27
+ inputSchema: {
28
+ org: z.string().describe("Organization slug that owns the datastore (e.g. stigmer)."),
29
+ slug: z
30
+ .string()
31
+ .describe("Datastore slug — the unique identifier within the org (e.g. clinic-bookings)."),
32
+ },
33
+ },
34
+ (args, extra) =>
35
+ textOrError(() =>
36
+ fetchDatastore(target.serverAddress, resolveToken(extra, target.apiKey), args.org, args.slug),
37
+ ),
38
+ );
39
+
40
+ server.registerTool(
41
+ "apply_datastore",
42
+ {
43
+ description:
44
+ "Create or update a Stigmer datastore definition (idempotent). The manifest is " +
45
+ "authoritative for structure only — schema changes sync on apply (the server enforces " +
46
+ "its additive-plus change rules), and records are never touched; use the record tools " +
47
+ "to manage data.",
48
+ inputSchema: DatastoreInputShape,
49
+ },
50
+ (args, extra) =>
51
+ textOrError(() =>
52
+ applyDatastore(target.serverAddress, resolveToken(extra, target.apiKey), args),
53
+ ),
54
+ );
55
+
56
+ server.registerTool(
57
+ "delete_datastore",
58
+ {
59
+ description:
60
+ "Delete a Stigmer datastore by its org and slug, destroying its collections and all " +
61
+ "records in them. This is irreversible. Returns the deleted datastore definition.",
62
+ inputSchema: {
63
+ org: z.string().describe("Organization slug that owns the datastore (e.g. stigmer)."),
64
+ slug: z
65
+ .string()
66
+ .describe("Datastore slug — the unique identifier within the org (e.g. clinic-bookings)."),
67
+ },
68
+ },
69
+ (args, extra) =>
70
+ textOrError(() =>
71
+ deleteDatastore(target.serverAddress, resolveToken(extra, target.apiKey), args.org, args.slug),
72
+ ),
73
+ );
74
+
75
+ return ["get_datastore", "apply_datastore", "delete_datastore"];
76
+ }
@@ -1,7 +1,8 @@
1
1
  // In-process integration test for the delete tools. Verifies the two-step
2
2
  // resolve→delete flow forwards the resolved id into the correct per-domain
3
- // delete-input shape: typed {value} for agent/skill/workflow, and the
4
- // ApiResourceDeleteInput {resource_id} outlier for mcp_server.
3
+ // delete-input shape: typed {value} for agent/skill/workflow, and
4
+ // ApiResourceDeleteInput {resource_id} for mcp_server, environment, and
5
+ // datastore.
5
6
 
6
7
  import { create, toJson } from "@bufbuild/protobuf";
7
8
  import type { ConnectRouter } from "@connectrpc/connect";
@@ -18,6 +19,12 @@ import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
18
19
  import { AgentSchema } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/api_pb";
19
20
  import { AgentCommandController } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/command_pb";
20
21
  import { AgentQueryController } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/query_pb";
22
+ import { DatastoreSchema } from "@stigmer/protos/ai/stigmer/agentic/datastore/v1/api_pb";
23
+ import { DatastoreCommandController } from "@stigmer/protos/ai/stigmer/agentic/datastore/v1/command_pb";
24
+ import { DatastoreQueryController } from "@stigmer/protos/ai/stigmer/agentic/datastore/v1/query_pb";
25
+ import { EnvironmentSchema } from "@stigmer/protos/ai/stigmer/agentic/environment/v1/api_pb";
26
+ import { EnvironmentCommandController } from "@stigmer/protos/ai/stigmer/agentic/environment/v1/command_pb";
27
+ import { EnvironmentQueryController } from "@stigmer/protos/ai/stigmer/agentic/environment/v1/query_pb";
21
28
  import { McpServerSchema } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/api_pb";
22
29
  import { McpServerCommandController } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/command_pb";
23
30
  import { McpServerQueryController } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/query_pb";
@@ -38,11 +45,23 @@ const resolvedMcpServer = create(McpServerSchema, {
38
45
  kind: "mcp_server",
39
46
  metadata: { name: "GitHub", slug: "github", org: "acme", id: "mcp-456" },
40
47
  });
48
+ const resolvedEnvironment = create(EnvironmentSchema, {
49
+ apiVersion: "v1",
50
+ kind: "environment",
51
+ metadata: { name: "GitHub Creds", slug: "github-creds", org: "acme", id: "env-789" },
52
+ });
53
+ const resolvedDatastore = create(DatastoreSchema, {
54
+ apiVersion: "v1",
55
+ kind: "datastore",
56
+ metadata: { name: "Bookings", slug: "bookings", org: "acme", id: "dst-012" },
57
+ });
41
58
 
42
59
  let backend: Http2Server;
43
60
  let client: Client;
44
61
  let deletedAgentId: string | undefined;
45
62
  let deletedMcpResourceId: string | undefined;
63
+ let deletedEnvironmentResourceId: string | undefined;
64
+ let deletedDatastoreResourceId: string | undefined;
46
65
  const openSessions = new Set<ServerHttp2Session>();
47
66
 
48
67
  interface ToolResult {
@@ -70,6 +89,20 @@ beforeAll(async () => {
70
89
  return resolvedMcpServer;
71
90
  },
72
91
  });
92
+ router.service(EnvironmentQueryController, { getByReference: () => resolvedEnvironment });
93
+ router.service(EnvironmentCommandController, {
94
+ delete: (req) => {
95
+ deletedEnvironmentResourceId = req.resourceId;
96
+ return resolvedEnvironment;
97
+ },
98
+ });
99
+ router.service(DatastoreQueryController, { getByReference: () => resolvedDatastore });
100
+ router.service(DatastoreCommandController, {
101
+ delete: (req) => {
102
+ deletedDatastoreResourceId = req.resourceId;
103
+ return resolvedDatastore;
104
+ },
105
+ });
73
106
  };
74
107
  backend = createHttp2Server(connectNodeAdapter({ routes }));
75
108
  backend.on("session", (session) => {
@@ -100,6 +133,8 @@ describe("delete tools integration", () => {
100
133
  "delete_skill",
101
134
  "delete_workflow",
102
135
  "delete_mcp_server",
136
+ "delete_environment",
137
+ "delete_datastore",
103
138
  ]),
104
139
  );
105
140
  });
@@ -121,4 +156,16 @@ describe("delete tools integration", () => {
121
156
  toJson(McpServerSchema, resolvedMcpServer, { useProtoFieldName: true }),
122
157
  );
123
158
  });
159
+
160
+ it("delete_environment resolves the id then deletes via ApiResourceDeleteInput", async () => {
161
+ const result = await callTool("delete_environment", { org: "acme", slug: "github-creds" });
162
+ expect(result.isError).toBeFalsy();
163
+ expect(deletedEnvironmentResourceId).toBe("env-789");
164
+ });
165
+
166
+ it("delete_datastore resolves the id then deletes via ApiResourceDeleteInput", async () => {
167
+ const result = await callTool("delete_datastore", { org: "acme", slug: "bookings" });
168
+ expect(result.isError).toBeFalsy();
169
+ expect(deletedDatastoreResourceId).toBe("dst-012");
170
+ });
124
171
  });
@@ -0,0 +1,40 @@
1
+ // Environment apply path: create-or-update via EnvironmentCommandController.apply.
2
+ // The flat MCP input is projected into a fully-formed Environment proto by the
3
+ // generated environmentInputToProto bridge (codegen, src/gen/environment.ts).
4
+ //
5
+ // Secret round-trip contract: a get → edit → apply loop is safe even when
6
+ // secrets are present. The server redacts secret values to ***REDACTED*** on
7
+ // read, and its update pipeline treats an echoed marker as "preserve the
8
+ // existing secret" — so callers only ever send real secret values when
9
+ // setting or rotating them.
10
+
11
+ import { EnvironmentSchema } from "@stigmer/protos/ai/stigmer/agentic/environment/v1/api_pb";
12
+ import { EnvironmentCommandController } from "@stigmer/protos/ai/stigmer/agentic/environment/v1/command_pb";
13
+
14
+ import { environmentInputToProto, type EnvironmentInput } from "../../gen/environment.js";
15
+ import { withClient } from "../client.js";
16
+ import { toProtoJson } from "../marshal.js";
17
+ import { rpcError } from "../rpcerr.js";
18
+
19
+ /** Create or update an environment, returning the persisted resource as protojson. */
20
+ export async function applyEnvironment(
21
+ serverAddress: string,
22
+ token: string,
23
+ input: EnvironmentInput,
24
+ ): Promise<string> {
25
+ const environment = environmentInputToProto(input);
26
+ const desc = `environment "${environment.metadata?.slug ?? ""}" in org "${environment.metadata?.org ?? ""}"`;
27
+ return withClient(
28
+ EnvironmentCommandController,
29
+ serverAddress,
30
+ token,
31
+ async (client, callOptions) => {
32
+ try {
33
+ const result = await client.apply(environment, callOptions);
34
+ return toProtoJson(EnvironmentSchema, result);
35
+ } catch (err) {
36
+ throw rpcError(err, desc);
37
+ }
38
+ },
39
+ );
40
+ }
@@ -0,0 +1,45 @@
1
+ // Environment delete path: resolve org/slug → id via the Query controller,
2
+ // then delete via the Command controller, both over a single shared transport.
3
+ // Like McpServer, the command controller takes the generic
4
+ // ApiResourceDeleteInput{resource_id}.
5
+
6
+ import { createClient } from "@connectrpc/connect";
7
+ import { EnvironmentSchema } from "@stigmer/protos/ai/stigmer/agentic/environment/v1/api_pb";
8
+ import { EnvironmentCommandController } from "@stigmer/protos/ai/stigmer/agentic/environment/v1/command_pb";
9
+ import { EnvironmentQueryController } from "@stigmer/protos/ai/stigmer/agentic/environment/v1/query_pb";
10
+ import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
11
+
12
+ import { withTransport } from "../client.js";
13
+ import { toProtoJson } from "../marshal.js";
14
+ import { rpcError } from "../rpcerr.js";
15
+
16
+ /** Delete an environment by org and slug, returning the deleted resource as protojson. */
17
+ export async function deleteEnvironment(
18
+ serverAddress: string,
19
+ token: string,
20
+ org: string,
21
+ slug: string,
22
+ ): Promise<string> {
23
+ const desc = `environment "${slug}" in org "${org}"`;
24
+ return withTransport(serverAddress, token, async (transport, callOptions) => {
25
+ const query = createClient(EnvironmentQueryController, transport);
26
+ let id: string;
27
+ try {
28
+ const environment = await query.getByReference(
29
+ { org, kind: ApiResourceKind.environment, slug },
30
+ callOptions,
31
+ );
32
+ id = environment.metadata?.id ?? "";
33
+ } catch (err) {
34
+ throw rpcError(err, desc);
35
+ }
36
+
37
+ const command = createClient(EnvironmentCommandController, transport);
38
+ try {
39
+ const deleted = await command.delete({ resourceId: id }, callOptions);
40
+ return toProtoJson(EnvironmentSchema, deleted);
41
+ } catch (err) {
42
+ throw rpcError(err, desc);
43
+ }
44
+ });
45
+ }
@@ -0,0 +1,39 @@
1
+ // Environment read path: the single RPC both the get_environment tool and the
2
+ // environment resource template delegate to.
3
+ //
4
+ // Secret handling happens entirely server-side: the response pipeline replaces
5
+ // every is_secret value with the ***REDACTED*** sentinel before it leaves the
6
+ // server (backend environment domain, preserve_redacted_secrets.go), so this
7
+ // layer adds no redaction logic of its own — the server is the single
8
+ // enforcement point.
9
+
10
+ import { EnvironmentSchema } from "@stigmer/protos/ai/stigmer/agentic/environment/v1/api_pb";
11
+ import { EnvironmentQueryController } from "@stigmer/protos/ai/stigmer/agentic/environment/v1/query_pb";
12
+ import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
13
+
14
+ import { withClient } from "../client.js";
15
+ import { toProtoJson } from "../marshal.js";
16
+ import { rpcError } from "../rpcerr.js";
17
+
18
+ /**
19
+ * Retrieve an environment by org and slug, returning its protojson
20
+ * representation (secret values arrive already redacted by the server).
21
+ */
22
+ export async function fetchEnvironment(
23
+ serverAddress: string,
24
+ token: string,
25
+ org: string,
26
+ slug: string,
27
+ ): Promise<string> {
28
+ return withClient(EnvironmentQueryController, serverAddress, token, async (client, callOptions) => {
29
+ try {
30
+ const environment = await client.getByReference(
31
+ { org, kind: ApiResourceKind.environment, slug },
32
+ callOptions,
33
+ );
34
+ return toProtoJson(EnvironmentSchema, environment);
35
+ } catch (err) {
36
+ throw rpcError(err, `environment "${slug}" in org "${org}"`);
37
+ }
38
+ });
39
+ }
@@ -0,0 +1,21 @@
1
+ // Environment resource template (stigmer://environments/{org}/{slug}).
2
+
3
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4
+
5
+ import type { BackendTarget } from "../client.js";
6
+ import { registerResource } from "../resourcehandler.js";
7
+ import { fetchEnvironment } from "./fetch.js";
8
+
9
+ /** Register the environment resource template; returns the registered resource names. */
10
+ export function registerEnvironmentResources(server: McpServer, target: BackendTarget): string[] {
11
+ registerResource(server, target, {
12
+ name: "stigmer_environment",
13
+ title: "Stigmer Environment",
14
+ description:
15
+ "Full definition of a Stigmer environment, identified by organization and slug. " +
16
+ "Secret values are redacted.",
17
+ template: "stigmer://environments/{org}/{slug}",
18
+ fetch: fetchEnvironment,
19
+ });
20
+ return ["stigmer_environment"];
21
+ }
@@ -0,0 +1,81 @@
1
+ // MCP tools for the Environment domain. Environments hold the configuration
2
+ // and secrets that agents, workflows, and MCP servers resolve at runtime —
3
+ // without these tools, an assistant authoring an agent hits a dead end the
4
+ // moment env config is involved.
5
+ //
6
+ // The secret contract every description teaches: reads return secret values
7
+ // redacted to ***REDACTED*** (server-enforced); an apply that echoes the
8
+ // marker back preserves the existing secret, so get → edit → apply is always
9
+ // safe. Secret values are only ever sent when setting or rotating them, and
10
+ // they can never be read back through MCP.
11
+
12
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
13
+ import { z } from "zod";
14
+
15
+ import { EnvironmentInputShape } from "../../gen/environment.js";
16
+ import { resolveToken, type BackendTarget } from "../client.js";
17
+ import { textOrError } from "../toolresult.js";
18
+ import { applyEnvironment } from "./apply.js";
19
+ import { deleteEnvironment } from "./delete.js";
20
+ import { fetchEnvironment } from "./fetch.js";
21
+
22
+ /** Register every Environment-domain tool; returns the registered tool names. */
23
+ export function registerEnvironmentTools(server: McpServer, target: BackendTarget): string[] {
24
+ server.registerTool(
25
+ "get_environment",
26
+ {
27
+ description:
28
+ "Get full details of a Stigmer environment by its org and slug, including its variable " +
29
+ "declarations. Secret values are redacted to ***REDACTED*** — they can never be read " +
30
+ "back through this tool.",
31
+ inputSchema: {
32
+ org: z.string().describe("Organization slug that owns the environment (e.g. stigmer)."),
33
+ slug: z
34
+ .string()
35
+ .describe("Environment slug — the unique identifier within the org (e.g. github-creds)."),
36
+ },
37
+ },
38
+ (args, extra) =>
39
+ textOrError(() =>
40
+ fetchEnvironment(target.serverAddress, resolveToken(extra, target.apiKey), args.org, args.slug),
41
+ ),
42
+ );
43
+
44
+ server.registerTool(
45
+ "apply_environment",
46
+ {
47
+ description:
48
+ "Create or update a Stigmer environment (idempotent). The spec is applied in full, so " +
49
+ "include every variable the environment should keep. When updating, secret values read " +
50
+ "back as ***REDACTED*** — echoing that marker preserves the existing secret, so a " +
51
+ "get → edit → apply round-trip never destroys secrets; send a real value only to set " +
52
+ "or rotate one.",
53
+ inputSchema: EnvironmentInputShape,
54
+ },
55
+ (args, extra) =>
56
+ textOrError(() =>
57
+ applyEnvironment(target.serverAddress, resolveToken(extra, target.apiKey), args),
58
+ ),
59
+ );
60
+
61
+ server.registerTool(
62
+ "delete_environment",
63
+ {
64
+ description:
65
+ "Delete a Stigmer environment by its org and slug. Returns the deleted environment. " +
66
+ "Agents and workflows referencing it will fail to resolve their variables at run time.",
67
+ inputSchema: {
68
+ org: z.string().describe("Organization slug that owns the environment (e.g. stigmer)."),
69
+ slug: z
70
+ .string()
71
+ .describe("Environment slug — the unique identifier within the org (e.g. github-creds)."),
72
+ },
73
+ },
74
+ (args, extra) =>
75
+ textOrError(() =>
76
+ deleteEnvironment(target.serverAddress, resolveToken(extra, target.apiKey), args.org, args.slug),
77
+ ),
78
+ );
79
+
80
+ return ["get_environment", "apply_environment", "delete_environment"];
81
+ }
@@ -0,0 +1,148 @@
1
+ // Cross-kind execution cancellation for the cancel_execution tool.
2
+ //
3
+ // Executions are the one place the MCP surface routes on ID prefix rather
4
+ // than a kind argument: agent (aex_*) and workflow (wex_*) executions live on
5
+ // dedicated controllers but cancel with identical arguments, so one tool
6
+ // serves both — mirroring the CLI's routing seam
7
+ // (client-apps/cli/src/resources/execution.ts), including its cancel
8
+ // semantics: read the execution first and short-circuit with
9
+ // "already terminal" instead of issuing a futile cancel, so the
10
+ // success/no-op distinction comes from authoritative state.
11
+
12
+ import { createClient } from "@connectrpc/connect";
13
+ import { type AgentExecution } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
14
+ import { AgentExecutionCommandController } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/command_pb";
15
+ import { ExecutionPhase as AgentExecutionPhase } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
16
+ import { AgentExecutionQueryController } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/query_pb";
17
+ import {
18
+ WorkflowExecutionSchema,
19
+ type WorkflowExecution,
20
+ } from "@stigmer/protos/ai/stigmer/agentic/workflowexecution/v1/api_pb";
21
+ import { WorkflowExecutionCommandController } from "@stigmer/protos/ai/stigmer/agentic/workflowexecution/v1/command_pb";
22
+ import { ExecutionPhase as WorkflowExecutionPhase } from "@stigmer/protos/ai/stigmer/agentic/workflowexecution/v1/enum_pb";
23
+ import { WorkflowExecutionQueryController } from "@stigmer/protos/ai/stigmer/agentic/workflowexecution/v1/query_pb";
24
+
25
+ import { compactExecution, DEFAULT_MESSAGE_LIMIT } from "../agentexecutions/fetch.js";
26
+ import { withTransport } from "../client.js";
27
+ import { toProtoJson } from "../marshal.js";
28
+ import { rpcError } from "../rpcerr.js";
29
+
30
+ // Terminal phases per kind. The two ExecutionPhase enums live in different
31
+ // proto packages with different numeric values (agent TERMINATED is 8,
32
+ // workflow TERMINATED is 6), so these are deliberately separate sets.
33
+ const TERMINAL_AGENT_PHASES: ReadonlySet<AgentExecutionPhase> = new Set([
34
+ AgentExecutionPhase.EXECUTION_COMPLETED,
35
+ AgentExecutionPhase.EXECUTION_FAILED,
36
+ AgentExecutionPhase.EXECUTION_CANCELLED,
37
+ AgentExecutionPhase.EXECUTION_TERMINATED,
38
+ ]);
39
+
40
+ const TERMINAL_WORKFLOW_PHASES: ReadonlySet<WorkflowExecutionPhase> = new Set([
41
+ WorkflowExecutionPhase.EXECUTION_COMPLETED,
42
+ WorkflowExecutionPhase.EXECUTION_FAILED,
43
+ WorkflowExecutionPhase.EXECUTION_CANCELLED,
44
+ WorkflowExecutionPhase.EXECUTION_TERMINATED,
45
+ ]);
46
+
47
+ /**
48
+ * Resolve an execution ID to its controller family by prefix. Matching is
49
+ * case-sensitive and accepts both separators the backend does ("_" canonical,
50
+ * "-" legacy), mirroring the CLI's resolveExecutionType.
51
+ */
52
+ function resolveExecutionType(id: string): "agent" | "workflow" {
53
+ const trimmed = id.trim();
54
+ if (trimmed.startsWith("aex_") || trimmed.startsWith("aex-")) return "agent";
55
+ if (trimmed.startsWith("wex_") || trimmed.startsWith("wex-")) return "workflow";
56
+ throw new Error(
57
+ `unrecognized execution ID format: ${id}\n\n` +
58
+ "Expected formats:\n" +
59
+ " Agent execution: aex_<26-char-ulid>\n" +
60
+ " Workflow execution: wex_<26-char-ulid>",
61
+ );
62
+ }
63
+
64
+ /**
65
+ * Cancel an execution of either kind. Returns a wrapper documenting whether a
66
+ * cancel was actually issued: `{"already_terminal": bool, "execution": …}`.
67
+ * Agent executions are returned in the compact projection (their status embeds
68
+ * the full message history); workflow executions as plain protojson, matching
69
+ * get_workflow_execution.
70
+ */
71
+ export async function cancelExecution(
72
+ serverAddress: string,
73
+ token: string,
74
+ executionId: string,
75
+ reason: string,
76
+ ): Promise<string> {
77
+ return resolveExecutionType(executionId) === "agent"
78
+ ? cancelAgentExecution(serverAddress, token, executionId, reason)
79
+ : cancelWorkflowExecution(serverAddress, token, executionId, reason);
80
+ }
81
+
82
+ async function cancelAgentExecution(
83
+ serverAddress: string,
84
+ token: string,
85
+ id: string,
86
+ reason: string,
87
+ ): Promise<string> {
88
+ const desc = `agent execution "${id}"`;
89
+ return withTransport(serverAddress, token, async (transport, callOptions) => {
90
+ try {
91
+ const query = createClient(AgentExecutionQueryController, transport);
92
+ const current = await query.get({ value: id }, callOptions);
93
+ const phase = current.status?.phase ?? AgentExecutionPhase.EXECUTION_PHASE_UNSPECIFIED;
94
+ if (TERMINAL_AGENT_PHASES.has(phase)) {
95
+ return wrapAgent(current, true);
96
+ }
97
+ const command = createClient(AgentExecutionCommandController, transport);
98
+ const cancelled = await command.cancel({ id, reason }, callOptions);
99
+ return wrapAgent(cancelled, false);
100
+ } catch (err) {
101
+ throw rpcError(err, desc);
102
+ }
103
+ });
104
+ }
105
+
106
+ async function cancelWorkflowExecution(
107
+ serverAddress: string,
108
+ token: string,
109
+ id: string,
110
+ reason: string,
111
+ ): Promise<string> {
112
+ const desc = `workflow execution "${id}"`;
113
+ return withTransport(serverAddress, token, async (transport, callOptions) => {
114
+ try {
115
+ const query = createClient(WorkflowExecutionQueryController, transport);
116
+ const current = await query.get({ value: id }, callOptions);
117
+ const phase = current.status?.phase ?? WorkflowExecutionPhase.EXECUTION_PHASE_UNSPECIFIED;
118
+ if (TERMINAL_WORKFLOW_PHASES.has(phase)) {
119
+ return wrapWorkflow(current, true);
120
+ }
121
+ const command = createClient(WorkflowExecutionCommandController, transport);
122
+ const cancelled = await command.cancel({ id, reason }, callOptions);
123
+ return wrapWorkflow(cancelled, false);
124
+ } catch (err) {
125
+ throw rpcError(err, desc);
126
+ }
127
+ });
128
+ }
129
+
130
+ function wrapAgent(execution: AgentExecution, alreadyTerminal: boolean): string {
131
+ const { totalMessages, data } = compactExecution(execution, DEFAULT_MESSAGE_LIMIT);
132
+ return JSON.stringify(
133
+ { already_terminal: alreadyTerminal, view: "compact", total_messages: totalMessages, execution: data },
134
+ null,
135
+ 2,
136
+ );
137
+ }
138
+
139
+ function wrapWorkflow(execution: WorkflowExecution, alreadyTerminal: boolean): string {
140
+ return JSON.stringify(
141
+ {
142
+ already_terminal: alreadyTerminal,
143
+ execution: JSON.parse(toProtoJson(WorkflowExecutionSchema, execution)),
144
+ },
145
+ null,
146
+ 2,
147
+ );
148
+ }
@@ -0,0 +1,45 @@
1
+ // Cross-kind execution-control tools. Currently just cancel_execution — the
2
+ // one control verb whose argument shape is identical for agent and workflow
3
+ // executions, so a single ID-prefix-dispatched tool serves both (the
4
+ // "crisp schemas over fewer tools" principle cuts the other way for
5
+ // approvals, whose shapes are disjoint). pause/resume/terminate stay
6
+ // CLI-only until a real MCP need shows up.
7
+
8
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
9
+ import { z } from "zod";
10
+
11
+ import { resolveToken, type BackendTarget } from "../client.js";
12
+ import { textOrError } from "../toolresult.js";
13
+ import { cancelExecution } from "./cancel.js";
14
+
15
+ /** Register the cross-kind execution-control tools; returns the registered tool names. */
16
+ export function registerExecutionControlTools(server: McpServer, target: BackendTarget): string[] {
17
+ server.registerTool(
18
+ "cancel_execution",
19
+ {
20
+ description:
21
+ "Gracefully cancel a running agent (aex_*) or workflow (wex_*) execution — the kind is " +
22
+ "inferred from the ID prefix. Cancellation is terminal: the run stops after cleanup and " +
23
+ "cannot be resumed. Executions already in a terminal phase are returned with " +
24
+ "already_terminal=true instead of an error.",
25
+ inputSchema: {
26
+ execution_id: z.string().describe("Execution ID to cancel (aex_* or wex_* format)."),
27
+ reason: z
28
+ .string()
29
+ .optional()
30
+ .describe("Human-readable reason for the cancellation, stored in the audit trail."),
31
+ },
32
+ },
33
+ (args, extra) =>
34
+ textOrError(() =>
35
+ cancelExecution(
36
+ target.serverAddress,
37
+ resolveToken(extra, target.apiKey),
38
+ args.execution_id,
39
+ args.reason ?? "",
40
+ ),
41
+ ),
42
+ );
43
+
44
+ return ["cancel_execution"];
45
+ }