@stigmer/mcp-server 3.2.3 → 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.
- package/README.md +60 -13
- package/cli/mcp-server-stigmer.js +13 -9
- package/config.d.ts +12 -0
- package/config.d.ts.map +1 -1
- package/config.js +5 -0
- package/config.js.map +1 -1
- package/domains/agentexecutions/approve.d.ts +9 -0
- package/domains/agentexecutions/approve.d.ts.map +1 -0
- package/domains/agentexecutions/approve.js +46 -0
- package/domains/agentexecutions/approve.js.map +1 -0
- package/domains/agentexecutions/fetch.d.ts +28 -0
- package/domains/agentexecutions/fetch.d.ts.map +1 -0
- package/domains/agentexecutions/fetch.js +82 -0
- package/domains/agentexecutions/fetch.js.map +1 -0
- package/domains/agentexecutions/run.d.ts +27 -0
- package/domains/agentexecutions/run.d.ts.map +1 -0
- package/domains/agentexecutions/run.js +87 -0
- package/domains/agentexecutions/run.js.map +1 -0
- package/domains/agentexecutions/tools.d.ts +5 -0
- package/domains/agentexecutions/tools.d.ts.map +1 -0
- package/domains/agentexecutions/tools.js +97 -0
- package/domains/agentexecutions/tools.js.map +1 -0
- package/domains/datastores/apply.d.ts +4 -0
- package/domains/datastores/apply.d.ts.map +1 -0
- package/domains/datastores/apply.js +28 -0
- package/domains/datastores/apply.js.map +1 -0
- package/domains/datastores/delete.d.ts +3 -0
- package/domains/datastores/delete.d.ts.map +1 -0
- package/domains/datastores/delete.js +38 -0
- package/domains/datastores/delete.js.map +1 -0
- package/domains/datastores/fetch.d.ts +3 -0
- package/domains/datastores/fetch.d.ts.map +1 -0
- package/domains/datastores/fetch.js +23 -0
- package/domains/datastores/fetch.js.map +1 -0
- package/domains/datastores/resources.d.ts +5 -0
- package/domains/datastores/resources.d.ts.map +1 -0
- package/domains/datastores/resources.js +16 -0
- package/domains/datastores/resources.js.map +1 -0
- package/domains/datastores/tools.d.ts +5 -0
- package/domains/datastores/tools.d.ts.map +1 -0
- package/domains/datastores/tools.js +46 -0
- package/domains/datastores/tools.js.map +1 -0
- package/domains/environments/apply.d.ts +4 -0
- package/domains/environments/apply.d.ts.map +1 -0
- package/domains/environments/apply.js +30 -0
- package/domains/environments/apply.js.map +1 -0
- package/domains/environments/delete.d.ts +3 -0
- package/domains/environments/delete.d.ts.map +1 -0
- package/domains/environments/delete.js +36 -0
- package/domains/environments/delete.js.map +1 -0
- package/domains/environments/fetch.d.ts +6 -0
- package/domains/environments/fetch.d.ts.map +1 -0
- package/domains/environments/fetch.js +30 -0
- package/domains/environments/fetch.js.map +1 -0
- package/domains/environments/resources.d.ts +5 -0
- package/domains/environments/resources.d.ts.map +1 -0
- package/domains/environments/resources.js +16 -0
- package/domains/environments/resources.js.map +1 -0
- package/domains/environments/tools.d.ts +5 -0
- package/domains/environments/tools.d.ts.map +1 -0
- package/domains/environments/tools.js +51 -0
- package/domains/environments/tools.js.map +1 -0
- package/domains/executions/cancel.d.ts +9 -0
- package/domains/executions/cancel.d.ts.map +1 -0
- package/domains/executions/cancel.js +114 -0
- package/domains/executions/cancel.js.map +1 -0
- package/domains/executions/tools.d.ts +5 -0
- package/domains/executions/tools.d.ts.map +1 -0
- package/domains/executions/tools.js +28 -0
- package/domains/executions/tools.js.map +1 -0
- package/domains/records/calls.d.ts +48 -0
- package/domains/records/calls.d.ts.map +1 -0
- package/domains/records/calls.js +118 -0
- package/domains/records/calls.js.map +1 -0
- package/domains/records/errors.d.ts +24 -0
- package/domains/records/errors.d.ts.map +1 -0
- package/domains/records/errors.js +79 -0
- package/domains/records/errors.js.map +1 -0
- package/domains/records/tools.d.ts +7 -0
- package/domains/records/tools.d.ts.map +1 -0
- package/domains/records/tools.js +139 -0
- package/domains/records/tools.js.map +1 -0
- package/domains/resourceuri.d.ts.map +1 -1
- package/domains/resourceuri.js +2 -0
- package/domains/resourceuri.js.map +1 -1
- package/domains/search/tools.d.ts.map +1 -1
- package/domains/search/tools.js +10 -7
- package/domains/search/tools.js.map +1 -1
- package/domains/skills/tools.d.ts.map +1 -1
- package/domains/skills/tools.js +25 -1
- package/domains/skills/tools.js.map +1 -1
- package/domains/skills/versions.d.ts +9 -0
- package/domains/skills/versions.d.ts.map +1 -0
- package/domains/skills/versions.js +31 -0
- package/domains/skills/versions.js.map +1 -0
- package/domains/workflowexecutions/approvals.d.ts +17 -0
- package/domains/workflowexecutions/approvals.d.ts.map +1 -0
- package/domains/workflowexecutions/approvals.js +61 -0
- package/domains/workflowexecutions/approvals.js.map +1 -0
- package/domains/workflowexecutions/run.d.ts +12 -0
- package/domains/workflowexecutions/run.d.ts.map +1 -0
- package/domains/workflowexecutions/run.js +69 -0
- package/domains/workflowexecutions/run.js.map +1 -0
- package/domains/workflowexecutions/tools.d.ts.map +1 -1
- package/domains/workflowexecutions/tools.js +89 -6
- package/domains/workflowexecutions/tools.js.map +1 -1
- package/domains/workflows/tools.d.ts.map +1 -1
- package/domains/workflows/tools.js +69 -5
- package/domains/workflows/tools.js.map +1 -1
- package/domains/workflows/versions.d.ts +29 -0
- package/domains/workflows/versions.d.ts.map +1 -0
- package/domains/workflows/versions.js +99 -0
- package/domains/workflows/versions.js.map +1 -0
- package/gen/agent.d.ts +56 -0
- package/gen/agent.d.ts.map +1 -1
- package/gen/agent.js +24 -1
- package/gen/agent.js.map +1 -1
- package/gen/datastore.d.ts +861 -0
- package/gen/datastore.d.ts.map +1 -0
- package/gen/datastore.js +267 -0
- package/gen/datastore.js.map +1 -0
- package/gen/environment.d.ts +77 -0
- package/gen/environment.d.ts.map +1 -0
- package/gen/environment.js +62 -0
- package/gen/environment.js.map +1 -0
- package/gen/workflow.d.ts +4 -4
- package/index.d.ts +2 -2
- package/index.d.ts.map +1 -1
- package/index.js +4 -4
- package/index.js.map +1 -1
- package/package.json +3 -3
- package/server.d.ts +31 -1
- package/server.d.ts.map +1 -1
- package/server.js +53 -3
- package/server.js.map +1 -1
- package/src/config.ts +19 -0
- package/src/domains/agentexecutions/approve.ts +67 -0
- package/src/domains/agentexecutions/fetch.ts +112 -0
- package/src/domains/agentexecutions/run.ts +112 -0
- package/src/domains/agentexecutions/tools.ts +139 -0
- package/src/domains/apply.integration.test.ts +82 -6
- package/src/domains/datastores/apply.ts +33 -0
- package/src/domains/datastores/delete.ts +47 -0
- package/src/domains/datastores/fetch.ts +32 -0
- package/src/domains/datastores/resources.ts +21 -0
- package/src/domains/datastores/tools.ts +76 -0
- package/src/domains/deletes.integration.test.ts +49 -2
- package/src/domains/environments/apply.ts +40 -0
- package/src/domains/environments/delete.ts +45 -0
- package/src/domains/environments/fetch.ts +39 -0
- package/src/domains/environments/resources.ts +21 -0
- package/src/domains/environments/tools.ts +81 -0
- package/src/domains/executions/cancel.ts +148 -0
- package/src/domains/executions/tools.ts +45 -0
- package/src/domains/executions.integration.test.ts +376 -0
- package/src/domains/reads.integration.test.ts +51 -5
- package/src/domains/records/calls.ts +209 -0
- package/src/domains/records/errors.ts +98 -0
- package/src/domains/records/records.integration.test.ts +313 -0
- package/src/domains/records/tools.ts +210 -0
- package/src/domains/resources.integration.test.ts +38 -2
- package/src/domains/resourceuri.test.ts +11 -2
- package/src/domains/resourceuri.ts +2 -0
- package/src/domains/search/search.integration.test.ts +18 -2
- package/src/domains/search/tools.ts +12 -7
- package/src/domains/skills/tools.ts +34 -1
- package/src/domains/skills/versions.ts +47 -0
- package/src/domains/versions.integration.test.ts +212 -0
- package/src/domains/workflowexecutions/approvals.ts +102 -0
- package/src/domains/workflowexecutions/run.ts +87 -0
- package/src/domains/workflowexecutions/tools.ts +120 -6
- package/src/domains/workflows/tools.ts +90 -7
- package/src/domains/workflows/versions.ts +147 -0
- package/src/gen/agent.ts +28 -1
- package/src/gen/datastore.ts +264 -0
- package/src/gen/environment.ts +66 -0
- package/src/http.integration.test.ts +1 -0
- package/src/index.ts +12 -5
- package/src/server.ts +71 -5
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// In-process integration test for the
|
|
1
|
+
// In-process integration test for the resource templates. Verifies template
|
|
2
2
|
// discovery, that each read returns the backend protojson as a single
|
|
3
3
|
// application/json entry, and that the skill templates resolve latest (empty
|
|
4
4
|
// version) vs a pinned version.
|
|
@@ -17,6 +17,10 @@ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
|
17
17
|
import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
|
|
18
18
|
import { AgentSchema } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/api_pb";
|
|
19
19
|
import { AgentQueryController } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/query_pb";
|
|
20
|
+
import { DatastoreSchema } from "@stigmer/protos/ai/stigmer/agentic/datastore/v1/api_pb";
|
|
21
|
+
import { DatastoreQueryController } from "@stigmer/protos/ai/stigmer/agentic/datastore/v1/query_pb";
|
|
22
|
+
import { EnvironmentSchema } from "@stigmer/protos/ai/stigmer/agentic/environment/v1/api_pb";
|
|
23
|
+
import { EnvironmentQueryController } from "@stigmer/protos/ai/stigmer/agentic/environment/v1/query_pb";
|
|
20
24
|
import { McpServerSchema } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/api_pb";
|
|
21
25
|
import { McpServerQueryController } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/query_pb";
|
|
22
26
|
import { SkillSchema } from "@stigmer/protos/ai/stigmer/agentic/skill/v1/api_pb";
|
|
@@ -42,6 +46,16 @@ const workflow = create(WorkflowSchema, {
|
|
|
42
46
|
kind: "workflow",
|
|
43
47
|
metadata: { slug: "w", org: "acme" },
|
|
44
48
|
});
|
|
49
|
+
const environment = create(EnvironmentSchema, {
|
|
50
|
+
apiVersion: "v1",
|
|
51
|
+
kind: "environment",
|
|
52
|
+
metadata: { slug: "e", org: "acme" },
|
|
53
|
+
});
|
|
54
|
+
const datastore = create(DatastoreSchema, {
|
|
55
|
+
apiVersion: "v1",
|
|
56
|
+
kind: "datastore",
|
|
57
|
+
metadata: { slug: "d", org: "acme" },
|
|
58
|
+
});
|
|
45
59
|
|
|
46
60
|
let backend: Http2Server;
|
|
47
61
|
let client: Client;
|
|
@@ -63,6 +77,8 @@ beforeAll(async () => {
|
|
|
63
77
|
},
|
|
64
78
|
});
|
|
65
79
|
router.service(WorkflowQueryController, { getByReference: () => workflow });
|
|
80
|
+
router.service(EnvironmentQueryController, { getByReference: () => environment });
|
|
81
|
+
router.service(DatastoreQueryController, { getByReference: () => datastore });
|
|
66
82
|
};
|
|
67
83
|
backend = createHttp2Server(connectNodeAdapter({ routes }));
|
|
68
84
|
backend.on("session", (session) => {
|
|
@@ -85,7 +101,7 @@ afterAll(async () => {
|
|
|
85
101
|
});
|
|
86
102
|
|
|
87
103
|
describe("resource templates integration", () => {
|
|
88
|
-
it("advertises
|
|
104
|
+
it("advertises every template", async () => {
|
|
89
105
|
const { resourceTemplates } = await client.listResourceTemplates();
|
|
90
106
|
expect(resourceTemplates.map((t) => t.name)).toEqual(
|
|
91
107
|
expect.arrayContaining([
|
|
@@ -94,6 +110,8 @@ describe("resource templates integration", () => {
|
|
|
94
110
|
"stigmer_skill",
|
|
95
111
|
"stigmer_skill_version",
|
|
96
112
|
"stigmer_workflow",
|
|
113
|
+
"stigmer_environment",
|
|
114
|
+
"stigmer_datastore",
|
|
97
115
|
]),
|
|
98
116
|
);
|
|
99
117
|
});
|
|
@@ -136,4 +154,22 @@ describe("resource templates integration", () => {
|
|
|
136
154
|
await client.readResource({ uri: "stigmer://skills/acme/s/v2.0.0" });
|
|
137
155
|
expect(lastSkillVersion).toBe("v2.0.0");
|
|
138
156
|
});
|
|
157
|
+
|
|
158
|
+
it("reads an environment resource", async () => {
|
|
159
|
+
const result = (await client.readResource({
|
|
160
|
+
uri: "stigmer://environments/acme/e",
|
|
161
|
+
})) as ResourceResult;
|
|
162
|
+
expect(JSON.parse(result.contents[0]?.text ?? "{}")).toEqual(
|
|
163
|
+
toJson(EnvironmentSchema, environment, { useProtoFieldName: true }),
|
|
164
|
+
);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it("reads a datastore resource", async () => {
|
|
168
|
+
const result = (await client.readResource({
|
|
169
|
+
uri: "stigmer://datastores/acme/d",
|
|
170
|
+
})) as ResourceResult;
|
|
171
|
+
expect(JSON.parse(result.contents[0]?.text ?? "{}")).toEqual(
|
|
172
|
+
toJson(DatastoreSchema, datastore, { useProtoFieldName: true }),
|
|
173
|
+
);
|
|
174
|
+
});
|
|
139
175
|
});
|
|
@@ -75,6 +75,8 @@ describe("buildResourceURI", () => {
|
|
|
75
75
|
expect(buildResourceURI("mcp_server", "acme", "m")).toBe("stigmer://mcp-servers/acme/m");
|
|
76
76
|
expect(buildResourceURI("skill", "acme", "s")).toBe("stigmer://skills/acme/s");
|
|
77
77
|
expect(buildResourceURI("workflow", "acme", "w")).toBe("stigmer://workflows/acme/w");
|
|
78
|
+
expect(buildResourceURI("environment", "acme", "e")).toBe("stigmer://environments/acme/e");
|
|
79
|
+
expect(buildResourceURI("datastore", "acme", "d")).toBe("stigmer://datastores/acme/d");
|
|
78
80
|
});
|
|
79
81
|
|
|
80
82
|
it("round-trips with parseResourceURI", () => {
|
|
@@ -91,7 +93,14 @@ describe("buildResourceURI", () => {
|
|
|
91
93
|
expect(buildResourceURI("agent", "acme", "")).toBe("");
|
|
92
94
|
});
|
|
93
95
|
|
|
94
|
-
it("covers exactly the
|
|
95
|
-
expect(Object.keys(kindToAuthority).sort()).toEqual([
|
|
96
|
+
it("covers exactly the templated kinds", () => {
|
|
97
|
+
expect(Object.keys(kindToAuthority).sort()).toEqual([
|
|
98
|
+
"agent",
|
|
99
|
+
"datastore",
|
|
100
|
+
"environment",
|
|
101
|
+
"mcp_server",
|
|
102
|
+
"skill",
|
|
103
|
+
"workflow",
|
|
104
|
+
]);
|
|
96
105
|
});
|
|
97
106
|
});
|
|
@@ -81,6 +81,8 @@ export function parseVersionedResourceURI(uri: string): VersionedResourceRef {
|
|
|
81
81
|
*/
|
|
82
82
|
export const kindToAuthority: Readonly<Record<string, string>> = {
|
|
83
83
|
agent: "agents",
|
|
84
|
+
datastore: "datastores",
|
|
85
|
+
environment: "environments",
|
|
84
86
|
mcp_server: "mcp-servers",
|
|
85
87
|
skill: "skills",
|
|
86
88
|
workflow: "workflows",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// In-process integration test for the search tool. Verifies resource_uri
|
|
2
|
-
// enrichment for
|
|
2
|
+
// enrichment for every searchable kind, the empty-response short-circuit,
|
|
3
3
|
// the unknown-kind validation error, and pagination passthrough.
|
|
4
4
|
|
|
5
5
|
import { create, toJson } from "@bufbuild/protobuf";
|
|
@@ -35,6 +35,12 @@ const allKindsResponse = create(SearchResponseSchema, {
|
|
|
35
35
|
create(SearchResultSchema, { kind: ApiResourceKind.skill, org: "acme", slug: "code-review" }),
|
|
36
36
|
create(SearchResultSchema, { kind: ApiResourceKind.mcp_server, org: "acme", slug: "github" }),
|
|
37
37
|
create(SearchResultSchema, { kind: ApiResourceKind.workflow, org: "acme", slug: "release" }),
|
|
38
|
+
create(SearchResultSchema, {
|
|
39
|
+
kind: ApiResourceKind.environment,
|
|
40
|
+
org: "acme",
|
|
41
|
+
slug: "github-creds",
|
|
42
|
+
}),
|
|
43
|
+
create(SearchResultSchema, { kind: ApiResourceKind.datastore, org: "acme", slug: "bookings" }),
|
|
38
44
|
],
|
|
39
45
|
});
|
|
40
46
|
const emptyResponse = create(SearchResponseSchema, { entries: [] });
|
|
@@ -96,6 +102,8 @@ describe("search tool integration", () => {
|
|
|
96
102
|
"stigmer://skills/acme/code-review",
|
|
97
103
|
"stigmer://mcp-servers/acme/github",
|
|
98
104
|
"stigmer://workflows/acme/release",
|
|
105
|
+
"stigmer://environments/acme/github-creds",
|
|
106
|
+
"stigmer://datastores/acme/bookings",
|
|
99
107
|
]);
|
|
100
108
|
});
|
|
101
109
|
|
|
@@ -112,7 +120,15 @@ describe("search tool integration", () => {
|
|
|
112
120
|
const result = await callSearch({ kinds: ["bogus"] });
|
|
113
121
|
expect(result.isError).toBe(true);
|
|
114
122
|
expect(result.content[0]?.text).toContain('unknown resource kind "bogus"');
|
|
115
|
-
expect(result.content[0]?.text).toContain(
|
|
123
|
+
expect(result.content[0]?.text).toContain(
|
|
124
|
+
"valid kinds: agent, skill, mcp_server, workflow, environment, datastore",
|
|
125
|
+
);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it("accepts the environment and datastore kinds", async () => {
|
|
129
|
+
nextResponse = emptyResponse;
|
|
130
|
+
const result = await callSearch({ kinds: ["environment", "datastore"] });
|
|
131
|
+
expect(result.isError).toBeFalsy();
|
|
116
132
|
});
|
|
117
133
|
|
|
118
134
|
it("forwards pagination only when requested", async () => {
|
|
@@ -24,12 +24,15 @@ import { rpcError } from "../rpcerr.js";
|
|
|
24
24
|
import { textOrError } from "../toolresult.js";
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
|
-
* The searchable kinds and their proto enum values.
|
|
28
|
-
*
|
|
29
|
-
*
|
|
27
|
+
* The searchable kinds and their proto enum values. Deliberately a curated
|
|
28
|
+
* subset (not every ApiResourceKind): the kinds an MCP client can also read
|
|
29
|
+
* and manage through tools. The backend's extractor registry supports more
|
|
30
|
+
* (sessions, executions, ...) — add here only alongside a tool surface.
|
|
30
31
|
*/
|
|
31
32
|
const knownKinds: Readonly<Record<string, ApiResourceKind>> = {
|
|
32
33
|
agent: ApiResourceKind.agent,
|
|
34
|
+
datastore: ApiResourceKind.datastore,
|
|
35
|
+
environment: ApiResourceKind.environment,
|
|
33
36
|
skill: ApiResourceKind.skill,
|
|
34
37
|
mcp_server: ApiResourceKind.mcp_server,
|
|
35
38
|
workflow: ApiResourceKind.workflow,
|
|
@@ -50,14 +53,16 @@ export function registerSearchTools(server: McpServer, target: BackendTarget): s
|
|
|
50
53
|
"search",
|
|
51
54
|
{
|
|
52
55
|
description:
|
|
53
|
-
"Search and list Stigmer resources (agents, skills, MCP servers, workflows
|
|
54
|
-
"Set 'kinds' to filter by resource type. Set 'query' for full-text search. " +
|
|
56
|
+
"Search and list Stigmer resources (agents, skills, MCP servers, workflows, environments, " +
|
|
57
|
+
"datastores). Set 'kinds' to filter by resource type. Set 'query' for full-text search. " +
|
|
55
58
|
"Set 'org' to scope to an organization. Omit 'query' to list all accessible resources.",
|
|
56
59
|
inputSchema: {
|
|
57
60
|
kinds: z
|
|
58
61
|
.array(z.string())
|
|
59
62
|
.optional()
|
|
60
|
-
.describe(
|
|
63
|
+
.describe(
|
|
64
|
+
"Resource kinds to search. Valid: agent, skill, mcp_server, workflow, environment, datastore. Empty searches all.",
|
|
65
|
+
),
|
|
61
66
|
query: z
|
|
62
67
|
.string()
|
|
63
68
|
.optional()
|
|
@@ -125,7 +130,7 @@ function parseKinds(raw: string[] | undefined): ApiResourceKind[] {
|
|
|
125
130
|
const kind = knownKinds[s];
|
|
126
131
|
if (kind === undefined) {
|
|
127
132
|
throw new Error(
|
|
128
|
-
`unknown resource kind "${s}"; valid kinds: agent, skill, mcp_server, workflow`,
|
|
133
|
+
`unknown resource kind "${s}"; valid kinds: agent, skill, mcp_server, workflow, environment, datastore`,
|
|
129
134
|
);
|
|
130
135
|
}
|
|
131
136
|
return kind;
|
|
@@ -12,6 +12,7 @@ import { resolveToken, type BackendTarget } from "../client.js";
|
|
|
12
12
|
import { textOrError } from "../toolresult.js";
|
|
13
13
|
import { deleteSkill } from "./delete.js";
|
|
14
14
|
import { fetchSkill } from "./fetch.js";
|
|
15
|
+
import { listSkillVersions } from "./versions.js";
|
|
15
16
|
|
|
16
17
|
/** Register every Skill-domain tool; returns the registered tool names. */
|
|
17
18
|
export function registerSkillTools(server: McpServer, target: BackendTarget): string[] {
|
|
@@ -63,5 +64,37 @@ export function registerSkillTools(server: McpServer, target: BackendTarget): st
|
|
|
63
64
|
),
|
|
64
65
|
);
|
|
65
66
|
|
|
66
|
-
|
|
67
|
+
server.registerTool(
|
|
68
|
+
"list_skill_versions",
|
|
69
|
+
{
|
|
70
|
+
description:
|
|
71
|
+
"List a skill's version history (newest first): version hash, tag, who pushed it and " +
|
|
72
|
+
"when, and which version is current. Fetch one version's full content with get_skill and " +
|
|
73
|
+
"its hash or tag.",
|
|
74
|
+
inputSchema: {
|
|
75
|
+
org: z.string().describe("Organization slug that owns the skill."),
|
|
76
|
+
slug: z.string().describe("Skill slug — unique identifier within the org."),
|
|
77
|
+
page_size: z
|
|
78
|
+
.number()
|
|
79
|
+
.int()
|
|
80
|
+
.optional()
|
|
81
|
+
.describe("Maximum versions per page (default 50)."),
|
|
82
|
+
page_token: z
|
|
83
|
+
.string()
|
|
84
|
+
.optional()
|
|
85
|
+
.describe("Pagination token from a previous response's next_page_token."),
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
(args, extra) =>
|
|
89
|
+
textOrError(() =>
|
|
90
|
+
listSkillVersions(target.serverAddress, resolveToken(extra, target.apiKey), {
|
|
91
|
+
org: args.org,
|
|
92
|
+
slug: args.slug,
|
|
93
|
+
pageSize: args.page_size,
|
|
94
|
+
pageToken: args.page_token,
|
|
95
|
+
}),
|
|
96
|
+
),
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
return ["get_skill", "delete_skill", "list_skill_versions"];
|
|
67
100
|
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// Skill version-timeline path (SkillQueryController.listVersions). Each push
|
|
2
|
+
// creates an immutable version identified by the artifact's SHA-256 hash;
|
|
3
|
+
// entries carry hash, tag, push audit, and provenance (no artifact content, so
|
|
4
|
+
// no projection is needed — unlike the workflow timeline).
|
|
5
|
+
|
|
6
|
+
import type { MessageInitShape } from "@bufbuild/protobuf";
|
|
7
|
+
import {
|
|
8
|
+
type ListSkillVersionsInputSchema,
|
|
9
|
+
ListSkillVersionsResponseSchema,
|
|
10
|
+
} from "@stigmer/protos/ai/stigmer/agentic/skill/v1/io_pb";
|
|
11
|
+
import { SkillQueryController } from "@stigmer/protos/ai/stigmer/agentic/skill/v1/query_pb";
|
|
12
|
+
|
|
13
|
+
import { withClient } from "../client.js";
|
|
14
|
+
import { toProtoJson } from "../marshal.js";
|
|
15
|
+
import { rpcError } from "../rpcerr.js";
|
|
16
|
+
|
|
17
|
+
export interface ListSkillVersionsArgs {
|
|
18
|
+
readonly org: string;
|
|
19
|
+
readonly slug: string;
|
|
20
|
+
readonly pageSize?: number;
|
|
21
|
+
readonly pageToken?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** List a skill's version history (newest first) as protojson. */
|
|
25
|
+
export async function listSkillVersions(
|
|
26
|
+
serverAddress: string,
|
|
27
|
+
token: string,
|
|
28
|
+
args: ListSkillVersionsArgs,
|
|
29
|
+
): Promise<string> {
|
|
30
|
+
return withClient(SkillQueryController, serverAddress, token, async (client, callOptions) => {
|
|
31
|
+
const req: MessageInitShape<typeof ListSkillVersionsInputSchema> = {
|
|
32
|
+
org: args.org,
|
|
33
|
+
slug: args.slug,
|
|
34
|
+
pageToken: args.pageToken ?? "",
|
|
35
|
+
};
|
|
36
|
+
// Forward page_size only when set, letting the server apply its default.
|
|
37
|
+
if ((args.pageSize ?? 0) > 0) {
|
|
38
|
+
req.pageSize = args.pageSize;
|
|
39
|
+
}
|
|
40
|
+
try {
|
|
41
|
+
const resp = await client.listVersions(req, callOptions);
|
|
42
|
+
return toProtoJson(ListSkillVersionsResponseSchema, resp);
|
|
43
|
+
} catch (err) {
|
|
44
|
+
throw rpcError(err, `versions of skill "${args.slug}" in org "${args.org}"`);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
// In-process integration test for the versioning tools: list_workflow_versions
|
|
2
|
+
// (timeline projection — validated_yaml stripped), get_workflow's version
|
|
3
|
+
// argument (slug→ID two-step into getVersion), tag_workflow_version, and
|
|
4
|
+
// list_skill_versions. Same harness as reads.integration.test.ts.
|
|
5
|
+
|
|
6
|
+
import { create, toJson } from "@bufbuild/protobuf";
|
|
7
|
+
import type { ConnectRouter } from "@connectrpc/connect";
|
|
8
|
+
import { connectNodeAdapter } from "@connectrpc/connect-node";
|
|
9
|
+
import {
|
|
10
|
+
createServer as createHttp2Server,
|
|
11
|
+
type Http2Server,
|
|
12
|
+
type ServerHttp2Session,
|
|
13
|
+
} from "node:http2";
|
|
14
|
+
import type { AddressInfo } from "node:net";
|
|
15
|
+
|
|
16
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
17
|
+
import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
|
|
18
|
+
import { SkillQueryController } from "@stigmer/protos/ai/stigmer/agentic/skill/v1/query_pb";
|
|
19
|
+
import {
|
|
20
|
+
ListSkillVersionsResponseSchema,
|
|
21
|
+
type ListSkillVersionsInput,
|
|
22
|
+
} from "@stigmer/protos/ai/stigmer/agentic/skill/v1/io_pb";
|
|
23
|
+
import { WorkflowSchema } from "@stigmer/protos/ai/stigmer/agentic/workflow/v1/api_pb";
|
|
24
|
+
import { WorkflowCommandController } from "@stigmer/protos/ai/stigmer/agentic/workflow/v1/command_pb";
|
|
25
|
+
import { WorkflowQueryController } from "@stigmer/protos/ai/stigmer/agentic/workflow/v1/query_pb";
|
|
26
|
+
import {
|
|
27
|
+
ListWorkflowVersionsResponseSchema,
|
|
28
|
+
WorkflowVersionEntrySchema,
|
|
29
|
+
type GetWorkflowVersionInput,
|
|
30
|
+
type TagWorkflowVersionInput,
|
|
31
|
+
} from "@stigmer/protos/ai/stigmer/agentic/workflow/v1/version_pb";
|
|
32
|
+
import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
|
33
|
+
|
|
34
|
+
import { configureLogger } from "../logger";
|
|
35
|
+
import { createServer } from "../server";
|
|
36
|
+
|
|
37
|
+
configureLogger({ level: "error", format: "text" });
|
|
38
|
+
|
|
39
|
+
const HASH_A = "a".repeat(64);
|
|
40
|
+
const HASH_B = "b".repeat(64);
|
|
41
|
+
|
|
42
|
+
const knownWorkflow = create(WorkflowSchema, {
|
|
43
|
+
apiVersion: "v1",
|
|
44
|
+
kind: "workflow",
|
|
45
|
+
metadata: { name: "Release", slug: "release", org: "acme", id: "wkf_1" },
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const workflowVersions = create(ListWorkflowVersionsResponseSchema, {
|
|
49
|
+
versions: [
|
|
50
|
+
{ versionHash: HASH_A, tag: "stable", isCurrent: true, validatedYaml: "document: {}" },
|
|
51
|
+
{ versionHash: HASH_B, validatedYaml: "document: {old: true}" },
|
|
52
|
+
],
|
|
53
|
+
totalCount: 2,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const versionEntry = create(WorkflowVersionEntrySchema, {
|
|
57
|
+
versionHash: HASH_B,
|
|
58
|
+
validatedYaml: "document: {old: true}",
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const skillVersions = create(ListSkillVersionsResponseSchema, {
|
|
62
|
+
versions: [{ versionHash: HASH_A, tag: "stable", isCurrent: true }],
|
|
63
|
+
totalCount: 1,
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
let backend: Http2Server;
|
|
67
|
+
let client: Client;
|
|
68
|
+
let lastGetVersion: GetWorkflowVersionInput | undefined;
|
|
69
|
+
let lastTagVersion: TagWorkflowVersionInput | undefined;
|
|
70
|
+
let lastSkillVersionsRequest: ListSkillVersionsInput | undefined;
|
|
71
|
+
const openSessions = new Set<ServerHttp2Session>();
|
|
72
|
+
|
|
73
|
+
interface ToolResult {
|
|
74
|
+
content: Array<{ type: string; text?: string }>;
|
|
75
|
+
isError?: boolean;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async function callTool(name: string, args: Record<string, unknown>): Promise<ToolResult> {
|
|
79
|
+
return (await client.callTool({ name, arguments: args })) as ToolResult;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function parseText(result: ToolResult): Record<string, unknown> {
|
|
83
|
+
return JSON.parse(result.content[0]?.text ?? "{}") as Record<string, unknown>;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
beforeAll(async () => {
|
|
87
|
+
const routes = (router: ConnectRouter) => {
|
|
88
|
+
router.service(WorkflowQueryController, {
|
|
89
|
+
getByReference: () => knownWorkflow,
|
|
90
|
+
listVersions: () => workflowVersions,
|
|
91
|
+
getVersion: (req) => {
|
|
92
|
+
lastGetVersion = req;
|
|
93
|
+
return versionEntry;
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
router.service(WorkflowCommandController, {
|
|
97
|
+
tagVersion: (req) => {
|
|
98
|
+
lastTagVersion = req;
|
|
99
|
+
return knownWorkflow;
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
router.service(SkillQueryController, {
|
|
103
|
+
listVersions: (req) => {
|
|
104
|
+
lastSkillVersionsRequest = req;
|
|
105
|
+
return skillVersions;
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
};
|
|
109
|
+
backend = createHttp2Server(connectNodeAdapter({ routes }));
|
|
110
|
+
backend.on("session", (session) => {
|
|
111
|
+
openSessions.add(session);
|
|
112
|
+
session.on("close", () => openSessions.delete(session));
|
|
113
|
+
});
|
|
114
|
+
await new Promise<void>((resolve) => backend.listen(0, "127.0.0.1", resolve));
|
|
115
|
+
const port = (backend.address() as AddressInfo).port;
|
|
116
|
+
|
|
117
|
+
const mcp = createServer({ serverAddress: `127.0.0.1:${port}`, apiKey: "" });
|
|
118
|
+
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
|
|
119
|
+
client = new Client({ name: "versions-integration", version: "test" });
|
|
120
|
+
await Promise.all([mcp.connect(serverTransport), client.connect(clientTransport)]);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
beforeEach(() => {
|
|
124
|
+
lastGetVersion = undefined;
|
|
125
|
+
lastTagVersion = undefined;
|
|
126
|
+
lastSkillVersionsRequest = undefined;
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
afterAll(async () => {
|
|
130
|
+
await client?.close();
|
|
131
|
+
for (const session of openSessions) session.destroy();
|
|
132
|
+
await new Promise<void>((resolve) => backend.close(() => resolve()));
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
describe("versioning tools integration", () => {
|
|
136
|
+
it("advertises the versioning tools", async () => {
|
|
137
|
+
const { tools } = await client.listTools();
|
|
138
|
+
expect(tools.map((t) => t.name)).toEqual(
|
|
139
|
+
expect.arrayContaining([
|
|
140
|
+
"list_workflow_versions",
|
|
141
|
+
"tag_workflow_version",
|
|
142
|
+
"list_skill_versions",
|
|
143
|
+
]),
|
|
144
|
+
);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("list_workflow_versions strips validated_yaml from the timeline", async () => {
|
|
148
|
+
const result = await callTool("list_workflow_versions", { org: "acme", slug: "release" });
|
|
149
|
+
expect(result.isError).toBeFalsy();
|
|
150
|
+
|
|
151
|
+
const body = parseText(result);
|
|
152
|
+
expect(body.total_count).toBe(2);
|
|
153
|
+
const versions = body.versions as Array<Record<string, unknown>>;
|
|
154
|
+
expect(versions).toHaveLength(2);
|
|
155
|
+
expect(versions[0]).toMatchObject({ version_hash: HASH_A, tag: "stable", is_current: true });
|
|
156
|
+
for (const entry of versions) {
|
|
157
|
+
expect(entry.validated_yaml).toBeUndefined();
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it("get_workflow with a version resolves the id and fetches that version", async () => {
|
|
162
|
+
const result = await callTool("get_workflow", {
|
|
163
|
+
org: "acme",
|
|
164
|
+
slug: "release",
|
|
165
|
+
version: HASH_B,
|
|
166
|
+
});
|
|
167
|
+
expect(result.isError).toBeFalsy();
|
|
168
|
+
expect(lastGetVersion?.workflowId).toBe("wkf_1");
|
|
169
|
+
expect(lastGetVersion?.versionHash).toBe(HASH_B);
|
|
170
|
+
// The version entry carries the YAML the timeline omits.
|
|
171
|
+
expect(parseText(result)).toEqual(
|
|
172
|
+
toJson(WorkflowVersionEntrySchema, versionEntry, { useProtoFieldName: true }),
|
|
173
|
+
);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it("get_workflow without a version returns the live workflow", async () => {
|
|
177
|
+
const result = await callTool("get_workflow", { org: "acme", slug: "release" });
|
|
178
|
+
expect(result.isError).toBeFalsy();
|
|
179
|
+
expect(lastGetVersion).toBeUndefined();
|
|
180
|
+
expect(parseText(result)).toEqual(
|
|
181
|
+
toJson(WorkflowSchema, knownWorkflow, { useProtoFieldName: true }),
|
|
182
|
+
);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it("tag_workflow_version resolves the id and forwards hash and tag", async () => {
|
|
186
|
+
const result = await callTool("tag_workflow_version", {
|
|
187
|
+
org: "acme",
|
|
188
|
+
slug: "release",
|
|
189
|
+
version: HASH_B,
|
|
190
|
+
tag: "stable",
|
|
191
|
+
});
|
|
192
|
+
expect(result.isError).toBeFalsy();
|
|
193
|
+
expect(lastTagVersion?.workflowId).toBe("wkf_1");
|
|
194
|
+
expect(lastTagVersion?.versionHash).toBe(HASH_B);
|
|
195
|
+
expect(lastTagVersion?.tag).toBe("stable");
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it("list_skill_versions forwards the reference and returns the timeline", async () => {
|
|
199
|
+
const result = await callTool("list_skill_versions", {
|
|
200
|
+
org: "acme",
|
|
201
|
+
slug: "code-review",
|
|
202
|
+
page_size: 10,
|
|
203
|
+
});
|
|
204
|
+
expect(result.isError).toBeFalsy();
|
|
205
|
+
expect(lastSkillVersionsRequest?.org).toBe("acme");
|
|
206
|
+
expect(lastSkillVersionsRequest?.slug).toBe("code-review");
|
|
207
|
+
expect(lastSkillVersionsRequest?.pageSize).toBe(10);
|
|
208
|
+
expect(parseText(result)).toEqual(
|
|
209
|
+
toJson(ListSkillVersionsResponseSchema, skillVersions, { useProtoFieldName: true }),
|
|
210
|
+
);
|
|
211
|
+
});
|
|
212
|
+
});
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// Workflow-execution approval paths: the org-wide pending-approvals inbox
|
|
2
|
+
// (listPendingApprovals) and the reviewer decision for a human_input task
|
|
3
|
+
// (submitWorkflowTaskApproval).
|
|
4
|
+
//
|
|
5
|
+
// listPendingApprovals is org-scoped by contract (io.proto), not
|
|
6
|
+
// execution-scoped — it answers "what is waiting on a human right now"
|
|
7
|
+
// across the org. Each entry carries everything a decision needs: the
|
|
8
|
+
// execution ID, task name, requester, timeout, and the form schema when the
|
|
9
|
+
// task defines one. Agent-execution approvals are a different surface
|
|
10
|
+
// entirely (embedded in get_agent_execution status; see
|
|
11
|
+
// agentexecutions/approve.ts).
|
|
12
|
+
|
|
13
|
+
import type { JsonObject, MessageInitShape } from "@bufbuild/protobuf";
|
|
14
|
+
import { WorkflowExecutionSchema } from "@stigmer/protos/ai/stigmer/agentic/workflowexecution/v1/api_pb";
|
|
15
|
+
import { WorkflowExecutionCommandController } from "@stigmer/protos/ai/stigmer/agentic/workflowexecution/v1/command_pb";
|
|
16
|
+
import {
|
|
17
|
+
type ListPendingApprovalsRequestSchema,
|
|
18
|
+
PendingApprovalsListSchema,
|
|
19
|
+
type SubmitWorkflowTaskApprovalInputSchema,
|
|
20
|
+
} from "@stigmer/protos/ai/stigmer/agentic/workflowexecution/v1/io_pb";
|
|
21
|
+
import { WorkflowExecutionQueryController } from "@stigmer/protos/ai/stigmer/agentic/workflowexecution/v1/query_pb";
|
|
22
|
+
|
|
23
|
+
import { withClient } from "../client.js";
|
|
24
|
+
import { toProtoJson } from "../marshal.js";
|
|
25
|
+
import { rpcError } from "../rpcerr.js";
|
|
26
|
+
|
|
27
|
+
export interface ListPendingApprovalsArgs {
|
|
28
|
+
readonly org: string;
|
|
29
|
+
readonly pageSize?: number;
|
|
30
|
+
readonly pageToken?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** List human_input tasks awaiting a reviewer decision across an org. */
|
|
34
|
+
export async function listPendingApprovals(
|
|
35
|
+
serverAddress: string,
|
|
36
|
+
token: string,
|
|
37
|
+
args: ListPendingApprovalsArgs,
|
|
38
|
+
): Promise<string> {
|
|
39
|
+
return withClient(
|
|
40
|
+
WorkflowExecutionQueryController,
|
|
41
|
+
serverAddress,
|
|
42
|
+
token,
|
|
43
|
+
async (client, callOptions) => {
|
|
44
|
+
const req: MessageInitShape<typeof ListPendingApprovalsRequestSchema> = {
|
|
45
|
+
org: args.org,
|
|
46
|
+
pageToken: args.pageToken ?? "",
|
|
47
|
+
};
|
|
48
|
+
// Forward page_size only when set, letting the server apply its default.
|
|
49
|
+
if ((args.pageSize ?? 0) > 0) {
|
|
50
|
+
req.pageSize = args.pageSize;
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
const resp = await client.listPendingApprovals(req, callOptions);
|
|
54
|
+
return toProtoJson(PendingApprovalsListSchema, resp);
|
|
55
|
+
} catch (err) {
|
|
56
|
+
throw rpcError(err, `pending approvals in org "${args.org}"`);
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface SubmitWorkflowTaskApprovalArgs {
|
|
63
|
+
readonly executionId: string;
|
|
64
|
+
readonly taskName: string;
|
|
65
|
+
readonly outcome: string;
|
|
66
|
+
readonly comment?: string;
|
|
67
|
+
readonly formData?: Record<string, unknown>;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Submit a reviewer decision for a waiting human_input task. */
|
|
71
|
+
export async function submitWorkflowTaskApproval(
|
|
72
|
+
serverAddress: string,
|
|
73
|
+
token: string,
|
|
74
|
+
args: SubmitWorkflowTaskApprovalArgs,
|
|
75
|
+
): Promise<string> {
|
|
76
|
+
return withClient(
|
|
77
|
+
WorkflowExecutionCommandController,
|
|
78
|
+
serverAddress,
|
|
79
|
+
token,
|
|
80
|
+
async (client, callOptions) => {
|
|
81
|
+
const req: MessageInitShape<typeof SubmitWorkflowTaskApprovalInputSchema> = {
|
|
82
|
+
executionId: args.executionId,
|
|
83
|
+
taskName: args.taskName,
|
|
84
|
+
outcome: args.outcome,
|
|
85
|
+
comment: args.comment ?? "",
|
|
86
|
+
};
|
|
87
|
+
if (args.formData !== undefined) {
|
|
88
|
+
// google.protobuf.Struct fields are plain JSON objects in protobuf-es v2.
|
|
89
|
+
req.formData = args.formData as JsonObject;
|
|
90
|
+
}
|
|
91
|
+
try {
|
|
92
|
+
const execution = await client.submitWorkflowTaskApproval(req, callOptions);
|
|
93
|
+
return toProtoJson(WorkflowExecutionSchema, execution);
|
|
94
|
+
} catch (err) {
|
|
95
|
+
throw rpcError(
|
|
96
|
+
err,
|
|
97
|
+
`approval for task "${args.taskName}" in workflow execution "${args.executionId}"`,
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
);
|
|
102
|
+
}
|