@stigmer/mcp-server 3.34.0 → 3.36.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 (157) hide show
  1. package/cli/mcp-server-stigmer.d.ts.map +1 -1
  2. package/cli/mcp-server-stigmer.js +2 -2
  3. package/cli/mcp-server-stigmer.js.map +1 -1
  4. package/config.d.ts.map +1 -1
  5. package/config.js.map +1 -1
  6. package/domains/agentexecutions/approve.d.ts.map +1 -1
  7. package/domains/agentexecutions/approve.js.map +1 -1
  8. package/domains/agentexecutions/fetch.d.ts.map +1 -1
  9. package/domains/agentexecutions/fetch.js.map +1 -1
  10. package/domains/agentexecutions/run.d.ts.map +1 -1
  11. package/domains/agentexecutions/run.js.map +1 -1
  12. package/domains/agentexecutions/tools.d.ts.map +1 -1
  13. package/domains/agentexecutions/tools.js.map +1 -1
  14. package/domains/agents/apply.d.ts.map +1 -1
  15. package/domains/agents/apply.js.map +1 -1
  16. package/domains/agents/delete.d.ts.map +1 -1
  17. package/domains/agents/delete.js.map +1 -1
  18. package/domains/agents/fetch.d.ts.map +1 -1
  19. package/domains/agents/fetch.js.map +1 -1
  20. package/domains/agents/resources.d.ts.map +1 -1
  21. package/domains/agents/resources.js.map +1 -1
  22. package/domains/agents/tools.d.ts.map +1 -1
  23. package/domains/agents/tools.js.map +1 -1
  24. package/domains/apply-visibility.d.ts.map +1 -1
  25. package/domains/apply-visibility.js.map +1 -1
  26. package/domains/channels/calls.d.ts.map +1 -1
  27. package/domains/channels/calls.js.map +1 -1
  28. package/domains/channels/errors.d.ts.map +1 -1
  29. package/domains/channels/errors.js.map +1 -1
  30. package/domains/channels/tools.d.ts.map +1 -1
  31. package/domains/channels/tools.js.map +1 -1
  32. package/domains/client.d.ts.map +1 -1
  33. package/domains/client.js.map +1 -1
  34. package/domains/conversation/calls.d.ts.map +1 -1
  35. package/domains/conversation/calls.js.map +1 -1
  36. package/domains/conversation/errors.d.ts.map +1 -1
  37. package/domains/conversation/errors.js.map +1 -1
  38. package/domains/conversation/tools.d.ts.map +1 -1
  39. package/domains/conversation/tools.js.map +1 -1
  40. package/domains/environments/apply.d.ts.map +1 -1
  41. package/domains/environments/apply.js.map +1 -1
  42. package/domains/environments/delete.d.ts.map +1 -1
  43. package/domains/environments/delete.js.map +1 -1
  44. package/domains/environments/fetch.d.ts.map +1 -1
  45. package/domains/environments/fetch.js.map +1 -1
  46. package/domains/environments/resources.d.ts.map +1 -1
  47. package/domains/environments/resources.js.map +1 -1
  48. package/domains/environments/tools.d.ts.map +1 -1
  49. package/domains/environments/tools.js.map +1 -1
  50. package/domains/executions/cancel.d.ts.map +1 -1
  51. package/domains/executions/cancel.js.map +1 -1
  52. package/domains/executions/tools.d.ts.map +1 -1
  53. package/domains/executions/tools.js.map +1 -1
  54. package/domains/marshal.d.ts.map +1 -1
  55. package/domains/marshal.js.map +1 -1
  56. package/domains/mcpservers/apply.d.ts.map +1 -1
  57. package/domains/mcpservers/apply.js.map +1 -1
  58. package/domains/mcpservers/delete.d.ts.map +1 -1
  59. package/domains/mcpservers/delete.js.map +1 -1
  60. package/domains/mcpservers/fetch.d.ts.map +1 -1
  61. package/domains/mcpservers/fetch.js.map +1 -1
  62. package/domains/mcpservers/resources.d.ts.map +1 -1
  63. package/domains/mcpservers/resources.js.map +1 -1
  64. package/domains/mcpservers/tools.d.ts.map +1 -1
  65. package/domains/mcpservers/tools.js.map +1 -1
  66. package/domains/memory/calls.d.ts.map +1 -1
  67. package/domains/memory/calls.js.map +1 -1
  68. package/domains/memory/context.d.ts.map +1 -1
  69. package/domains/memory/context.js.map +1 -1
  70. package/domains/memory/errors.d.ts.map +1 -1
  71. package/domains/memory/errors.js.map +1 -1
  72. package/domains/memory/tools.d.ts.map +1 -1
  73. package/domains/memory/tools.js.map +1 -1
  74. package/domains/resourcehandler.d.ts.map +1 -1
  75. package/domains/resourcehandler.js.map +1 -1
  76. package/domains/resourceuri.d.ts.map +1 -1
  77. package/domains/resourceuri.js.map +1 -1
  78. package/domains/rpcerr.d.ts.map +1 -1
  79. package/domains/rpcerr.js.map +1 -1
  80. package/domains/search/tools.d.ts.map +1 -1
  81. package/domains/search/tools.js.map +1 -1
  82. package/domains/skills/delete.d.ts.map +1 -1
  83. package/domains/skills/delete.js.map +1 -1
  84. package/domains/skills/fetch.d.ts.map +1 -1
  85. package/domains/skills/fetch.js.map +1 -1
  86. package/domains/skills/resources.d.ts.map +1 -1
  87. package/domains/skills/resources.js.map +1 -1
  88. package/domains/skills/tools.d.ts.map +1 -1
  89. package/domains/skills/tools.js.map +1 -1
  90. package/domains/skills/versions.d.ts.map +1 -1
  91. package/domains/skills/versions.js.map +1 -1
  92. package/domains/toolresult.d.ts.map +1 -1
  93. package/domains/toolresult.js.map +1 -1
  94. package/domains/workflowexecutions/approvals.d.ts.map +1 -1
  95. package/domains/workflowexecutions/approvals.js.map +1 -1
  96. package/domains/workflowexecutions/run.d.ts.map +1 -1
  97. package/domains/workflowexecutions/run.js.map +1 -1
  98. package/domains/workflowexecutions/tools.d.ts.map +1 -1
  99. package/domains/workflowexecutions/tools.js.map +1 -1
  100. package/domains/workflows/apply.d.ts.map +1 -1
  101. package/domains/workflows/apply.js.map +1 -1
  102. package/domains/workflows/delete.d.ts.map +1 -1
  103. package/domains/workflows/delete.js.map +1 -1
  104. package/domains/workflows/fetch.d.ts.map +1 -1
  105. package/domains/workflows/fetch.js.map +1 -1
  106. package/domains/workflows/resources.d.ts.map +1 -1
  107. package/domains/workflows/resources.js.map +1 -1
  108. package/domains/workflows/taskkinds.d.ts.map +1 -1
  109. package/domains/workflows/taskkinds.js.map +1 -1
  110. package/domains/workflows/tools.d.ts.map +1 -1
  111. package/domains/workflows/tools.js.map +1 -1
  112. package/domains/workflows/validate.d.ts.map +1 -1
  113. package/domains/workflows/validate.js.map +1 -1
  114. package/domains/workflows/versions.d.ts.map +1 -1
  115. package/domains/workflows/versions.js.map +1 -1
  116. package/gen/agent.d.ts.map +1 -1
  117. package/gen/agent.js.map +1 -1
  118. package/gen/apply-runtime.d.ts.map +1 -1
  119. package/gen/apply-runtime.js.map +1 -1
  120. package/gen/environment.d.ts.map +1 -1
  121. package/gen/environment.js.map +1 -1
  122. package/gen/mcpserver.d.ts.map +1 -1
  123. package/gen/mcpserver.js.map +1 -1
  124. package/gen/workflow.d.ts.map +1 -1
  125. package/gen/workflow.js +2 -2
  126. package/gen/workflow.js.map +1 -1
  127. package/index.d.ts.map +1 -1
  128. package/index.js.map +1 -1
  129. package/logger.d.ts.map +1 -1
  130. package/logger.js.map +1 -1
  131. package/package.json +3 -3
  132. package/readiness.d.ts.map +1 -1
  133. package/readiness.js.map +1 -1
  134. package/server.d.ts.map +1 -1
  135. package/server.js.map +1 -1
  136. package/src/gen/workflow.ts +2 -2
  137. package/src/config.test.ts +0 -88
  138. package/src/domains/apply.integration.test.ts +0 -313
  139. package/src/domains/channels/channels.integration.test.ts +0 -272
  140. package/src/domains/channels/errors.test.ts +0 -52
  141. package/src/domains/conversation/conversation.integration.test.ts +0 -233
  142. package/src/domains/deletes.integration.test.ts +0 -147
  143. package/src/domains/executions.integration.test.ts +0 -376
  144. package/src/domains/memory/context.test.ts +0 -95
  145. package/src/domains/memory/errors.test.ts +0 -54
  146. package/src/domains/memory/memory.integration.test.ts +0 -253
  147. package/src/domains/reads.integration.test.ts +0 -163
  148. package/src/domains/resources.integration.test.ts +0 -157
  149. package/src/domains/resourceuri.test.ts +0 -104
  150. package/src/domains/rpcerr.test.ts +0 -62
  151. package/src/domains/search/search.integration.test.ts +0 -141
  152. package/src/domains/versions.integration.test.ts +0 -212
  153. package/src/domains/workflows/validate.integration.test.ts +0 -117
  154. package/src/domains/workflows/workflow-tools.integration.test.ts +0 -148
  155. package/src/http.integration.test.ts +0 -235
  156. package/src/readiness.test.ts +0 -90
  157. package/src/server.integration.test.ts +0 -82
@@ -1,62 +0,0 @@
1
- import { Code, ConnectError } from "@connectrpc/connect";
2
- import { beforeAll, describe, expect, it } from "vitest";
3
-
4
- import { configureLogger } from "../logger";
5
- import { rpcError } from "./rpcerr";
6
-
7
- beforeAll(() => configureLogger({ level: "error", format: "text" }));
8
-
9
- const resource = 'agent "code-reviewer" in org "stigmer"';
10
-
11
- describe("rpcError", () => {
12
- it("maps NotFound to a slug-guidance message", () => {
13
- expect(rpcError(new ConnectError("missing", Code.NotFound), resource).message).toBe(
14
- 'agent "code-reviewer" in org "stigmer" not found. Verify the org and slug are correct.',
15
- );
16
- });
17
-
18
- it("maps PermissionDenied to an API-key-permissions message", () => {
19
- expect(rpcError(new ConnectError("nope", Code.PermissionDenied), resource).message).toBe(
20
- 'Permission denied for agent "code-reviewer" in org "stigmer". Check your API key permissions.',
21
- );
22
- });
23
-
24
- it("maps Unauthenticated", () => {
25
- expect(rpcError(new ConnectError("bad token", Code.Unauthenticated), resource).message).toBe(
26
- "Authentication failed. Check your API key.",
27
- );
28
- });
29
-
30
- it("maps Unavailable", () => {
31
- expect(rpcError(new ConnectError("down", Code.Unavailable), resource).message).toBe(
32
- "Stigmer server is unavailable. Ensure it is running and reachable.",
33
- );
34
- });
35
-
36
- it("maps DeadlineExceeded", () => {
37
- expect(rpcError(new ConnectError("slow", Code.DeadlineExceeded), resource).message).toBe(
38
- "Request timed out contacting stigmer-server.",
39
- );
40
- });
41
-
42
- it("passes through the raw message for InvalidArgument", () => {
43
- expect(rpcError(new ConnectError("field x is required", Code.InvalidArgument), resource).message).toBe(
44
- "field x is required",
45
- );
46
- });
47
-
48
- it("wraps non-gRPC/unknown errors", () => {
49
- expect(rpcError(new Error("boom"), resource).message).toBe("unexpected error: boom");
50
- });
51
-
52
- it("maps every gRPC code to a non-empty message without throwing", () => {
53
- // Completeness guard: the default branch must catch any code the explicit
54
- // switch does not handle, so no gRPC status can ever surface a blank or
55
- // thrown error to the client.
56
- const codes = Object.values(Code).filter((c): c is Code => typeof c === "number");
57
- for (const code of codes) {
58
- const msg = rpcError(new ConnectError("detail", code), resource).message;
59
- expect(msg, `code ${Code[code]}`).toBeTruthy();
60
- }
61
- });
62
- });
@@ -1,141 +0,0 @@
1
- // In-process integration test for the search tool. Verifies resource_uri
2
- // enrichment for every searchable kind, the empty-response short-circuit,
3
- // the unknown-kind validation error, and pagination passthrough.
4
-
5
- import { create, toJson } from "@bufbuild/protobuf";
6
- import type { ConnectRouter } from "@connectrpc/connect";
7
- import { connectNodeAdapter } from "@connectrpc/connect-node";
8
- import {
9
- createServer as createHttp2Server,
10
- type Http2Server,
11
- type ServerHttp2Session,
12
- } from "node:http2";
13
- import type { AddressInfo } from "node:net";
14
-
15
- import { Client } from "@modelcontextprotocol/sdk/client/index.js";
16
- import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
17
- import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
18
- import type { PageInfo } from "@stigmer/protos/ai/stigmer/commons/rpc/pagination_pb";
19
- import {
20
- type SearchResponse,
21
- SearchResponseSchema,
22
- SearchResultSchema,
23
- } from "@stigmer/protos/ai/stigmer/search/v1/io_pb";
24
- import { SearchService } from "@stigmer/protos/ai/stigmer/search/v1/query_pb";
25
- import { afterAll, beforeAll, describe, expect, it } from "vitest";
26
-
27
- import { configureLogger } from "../../logger";
28
- import { createServer } from "../../server";
29
-
30
- configureLogger({ level: "error", format: "text" });
31
-
32
- const allKindsResponse = create(SearchResponseSchema, {
33
- entries: [
34
- create(SearchResultSchema, { kind: ApiResourceKind.agent, org: "acme", slug: "code-reviewer" }),
35
- create(SearchResultSchema, { kind: ApiResourceKind.skill, org: "acme", slug: "code-review" }),
36
- create(SearchResultSchema, { kind: ApiResourceKind.mcp_server, org: "acme", slug: "github" }),
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
- ],
44
- });
45
- const emptyResponse = create(SearchResponseSchema, { entries: [] });
46
-
47
- let backend: Http2Server;
48
- let client: Client;
49
- let nextResponse: SearchResponse = allKindsResponse;
50
- let lastPage: PageInfo | undefined;
51
- const openSessions = new Set<ServerHttp2Session>();
52
-
53
- interface ToolResult {
54
- content: Array<{ type: string; text?: string }>;
55
- isError?: boolean;
56
- }
57
-
58
- async function callSearch(args: Record<string, unknown>): Promise<ToolResult> {
59
- return (await client.callTool({ name: "search", arguments: args })) as ToolResult;
60
- }
61
-
62
- beforeAll(async () => {
63
- const routes = (router: ConnectRouter) => {
64
- router.service(SearchService, {
65
- search: (req) => {
66
- lastPage = req.page;
67
- return nextResponse;
68
- },
69
- });
70
- };
71
- backend = createHttp2Server(connectNodeAdapter({ routes }));
72
- backend.on("session", (session) => {
73
- openSessions.add(session);
74
- session.on("close", () => openSessions.delete(session));
75
- });
76
- await new Promise<void>((resolve) => backend.listen(0, "127.0.0.1", resolve));
77
- const port = (backend.address() as AddressInfo).port;
78
-
79
- const mcp = createServer({ serverAddress: `127.0.0.1:${port}`, apiKey: "" });
80
- const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
81
- client = new Client({ name: "search-integration", version: "test" });
82
- await Promise.all([mcp.connect(serverTransport), client.connect(clientTransport)]);
83
- });
84
-
85
- afterAll(async () => {
86
- await client?.close();
87
- for (const session of openSessions) session.destroy();
88
- await new Promise<void>((resolve) => backend.close(() => resolve()));
89
- });
90
-
91
- describe("search tool integration", () => {
92
- it("enriches every kind with a resource_uri", async () => {
93
- nextResponse = allKindsResponse;
94
- const result = await callSearch({ query: "code" });
95
- expect(result.isError).toBeFalsy();
96
- const data = JSON.parse(result.content[0]?.text ?? "{}") as {
97
- entries: Array<{ resource_uri?: string }>;
98
- };
99
- expect(data.entries.map((e) => e.resource_uri)).toEqual([
100
- "stigmer://agents/acme/code-reviewer",
101
- "stigmer://skills/acme/code-review",
102
- "stigmer://mcp-servers/acme/github",
103
- "stigmer://workflows/acme/release",
104
- "stigmer://environments/acme/github-creds",
105
- ]);
106
- });
107
-
108
- it("short-circuits an empty response to the plain marshal", async () => {
109
- nextResponse = emptyResponse;
110
- const result = await callSearch({ query: "nothing" });
111
- expect(result.isError).toBeFalsy();
112
- expect(JSON.parse(result.content[0]?.text ?? "{}")).toEqual(
113
- toJson(SearchResponseSchema, emptyResponse, { useProtoFieldName: true }),
114
- );
115
- });
116
-
117
- it("rejects an unknown kind without calling the backend", async () => {
118
- const result = await callSearch({ kinds: ["bogus"] });
119
- expect(result.isError).toBe(true);
120
- expect(result.content[0]?.text).toContain('unknown resource kind "bogus"');
121
- expect(result.content[0]?.text).toContain(
122
- "valid kinds: agent, skill, mcp_server, workflow, environment",
123
- );
124
- });
125
-
126
- it("accepts the environment kind", async () => {
127
- nextResponse = emptyResponse;
128
- const result = await callSearch({ kinds: ["environment"] });
129
- expect(result.isError).toBeFalsy();
130
- });
131
-
132
- it("forwards pagination only when requested", async () => {
133
- nextResponse = allKindsResponse;
134
- lastPage = undefined;
135
- await callSearch({ query: "code" });
136
- expect(lastPage).toBeUndefined();
137
-
138
- await callSearch({ query: "code", page_size: 5, page_num: 2 });
139
- expect(lastPage).toMatchObject({ size: 5, num: 2 });
140
- });
141
- });
@@ -1,212 +0,0 @@
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
- });
@@ -1,117 +0,0 @@
1
- // In-process integration test for validate_workflow_yaml. Verifies the
2
- // required-yaml and parse errors, that every registry task kind (including
3
- // `eval`, which the Go map omits) is accepted, and that the parsed Workflow is
4
- // forwarded to validateSpec.
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 { WorkflowCommandController } from "@stigmer/protos/ai/stigmer/agentic/workflow/v1/command_pb";
19
- import { WorkflowTaskKind } from "@stigmer/protos/ai/stigmer/agentic/workflow/v1/enum_pb";
20
- import { ServerlessWorkflowValidationSchema } from "@stigmer/protos/ai/stigmer/agentic/workflow/v1/serverless/validation_pb";
21
- import type { Workflow } from "@stigmer/protos/ai/stigmer/agentic/workflow/v1/api_pb";
22
- import { afterAll, beforeAll, describe, expect, it } from "vitest";
23
- import { stringify as stringifyYaml } from "yaml";
24
-
25
- import { configureLogger } from "../../logger";
26
- import { createServer } from "../../server";
27
-
28
- configureLogger({ level: "error", format: "text" });
29
-
30
- const registryKinds = Object.values(WorkflowTaskKind).filter(
31
- (v): v is string => typeof v === "string" && v !== "workflow_task_kind_unspecified",
32
- );
33
- const validation = create(ServerlessWorkflowValidationSchema, {});
34
-
35
- let backend: Http2Server;
36
- let client: Client;
37
- let lastWorkflow: Workflow | undefined;
38
- const openSessions = new Set<ServerHttp2Session>();
39
-
40
- interface ToolResult {
41
- content: Array<{ type: string; text?: string }>;
42
- isError?: boolean;
43
- }
44
-
45
- async function validate(yaml: string): Promise<ToolResult> {
46
- return (await client.callTool({ name: "validate_workflow_yaml", arguments: { yaml } })) as ToolResult;
47
- }
48
-
49
- beforeAll(async () => {
50
- const routes = (router: ConnectRouter) => {
51
- router.service(WorkflowCommandController, {
52
- validateSpec: (req) => {
53
- lastWorkflow = req;
54
- return validation;
55
- },
56
- });
57
- };
58
- backend = createHttp2Server(connectNodeAdapter({ routes }));
59
- backend.on("session", (session) => {
60
- openSessions.add(session);
61
- session.on("close", () => openSessions.delete(session));
62
- });
63
- await new Promise<void>((resolve) => backend.listen(0, "127.0.0.1", resolve));
64
- const port = (backend.address() as AddressInfo).port;
65
-
66
- const mcp = createServer({ serverAddress: `127.0.0.1:${port}`, apiKey: "" });
67
- const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
68
- client = new Client({ name: "validate-integration", version: "test" });
69
- await Promise.all([mcp.connect(serverTransport), client.connect(clientTransport)]);
70
- });
71
-
72
- afterAll(async () => {
73
- await client?.close();
74
- for (const session of openSessions) session.destroy();
75
- await new Promise<void>((resolve) => backend.close(() => resolve()));
76
- });
77
-
78
- describe("validate_workflow_yaml integration", () => {
79
- it("requires non-empty yaml", async () => {
80
- const result = await validate(" ");
81
- expect(result.isError).toBe(true);
82
- expect(result.content[0]?.text).toContain("yaml is required");
83
- });
84
-
85
- it("rejects yaml without a spec", async () => {
86
- const result = await validate(stringifyYaml({ apiVersion: "v1", kind: "workflow" }));
87
- expect(result.isError).toBe(true);
88
- expect(result.content[0]?.text).toContain(
89
- "failed to parse workflow YAML: missing or invalid 'spec' field",
90
- );
91
- });
92
-
93
- it("rejects an unknown task kind with its index", async () => {
94
- const doc = { spec: { tasks: [{ name: "t0", kind: "bogus_kind" }] } };
95
- const result = await validate(stringifyYaml(doc));
96
- expect(result.isError).toBe(true);
97
- expect(result.content[0]?.text).toContain(
98
- 'failed to parse workflow YAML: unknown task kind "bogus_kind" at tasks[0]',
99
- );
100
- });
101
-
102
- it("accepts every registry task kind (including eval) and forwards the workflow", async () => {
103
- const doc = {
104
- apiVersion: "v1",
105
- kind: "workflow",
106
- metadata: { name: "wf", org: "acme", slug: "wf" },
107
- spec: { tasks: registryKinds.map((kind, i) => ({ name: `t${i}`, kind })) },
108
- };
109
- const result = await validate(stringifyYaml(doc));
110
- expect(result.isError).toBeFalsy();
111
- expect(JSON.parse(result.content[0]?.text ?? "{}")).toEqual(
112
- toJson(ServerlessWorkflowValidationSchema, validation, { useProtoFieldName: true }),
113
- );
114
- expect(lastWorkflow?.spec?.tasks.length).toBe(registryKinds.length);
115
- expect(lastWorkflow?.spec?.tasks.some((t) => t.kind === WorkflowTaskKind.eval)).toBe(true);
116
- });
117
- });
@@ -1,148 +0,0 @@
1
- // In-process integration test for the workflow query tools: the task-kind
2
- // registry pair (get_task_kind_registry, get_task_kind) and the execution pair
3
- // (get_workflow_execution, get_workflow_execution_events). Verifies registry
4
- // selection (case-insensitive), the required-field and not-found errors, and
5
- // that page_size is forwarded only when set.
6
-
7
- import { create, toJson } from "@bufbuild/protobuf";
8
- import type { ConnectRouter } from "@connectrpc/connect";
9
- import { connectNodeAdapter } from "@connectrpc/connect-node";
10
- import {
11
- createServer as createHttp2Server,
12
- type Http2Server,
13
- type ServerHttp2Session,
14
- } from "node:http2";
15
- import type { AddressInfo } from "node:net";
16
-
17
- import { Client } from "@modelcontextprotocol/sdk/client/index.js";
18
- import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
19
- import { WorkflowTaskKind } from "@stigmer/protos/ai/stigmer/agentic/workflow/v1/enum_pb";
20
- import {
21
- GetTaskKindRegistryResponseSchema,
22
- TaskKindDescriptorSchema,
23
- } from "@stigmer/protos/ai/stigmer/agentic/workflow/v1/task_kind_descriptor_pb";
24
- import { TaskKindRegistryQueryController } from "@stigmer/protos/ai/stigmer/agentic/workflow/v1/task_kind_registry_query_pb";
25
- import { WorkflowExecutionSchema } from "@stigmer/protos/ai/stigmer/agentic/workflowexecution/v1/api_pb";
26
- import {
27
- type GetEventLogRequest,
28
- GetEventLogResponseSchema,
29
- } from "@stigmer/protos/ai/stigmer/agentic/workflowexecution/v1/io_pb";
30
- import { WorkflowExecutionQueryController } from "@stigmer/protos/ai/stigmer/agentic/workflowexecution/v1/query_pb";
31
- import { afterAll, beforeAll, describe, expect, it } from "vitest";
32
-
33
- import { configureLogger } from "../../logger";
34
- import { createServer } from "../../server";
35
-
36
- configureLogger({ level: "error", format: "text" });
37
-
38
- const registry = create(GetTaskKindRegistryResponseSchema, {
39
- descriptors: [
40
- create(TaskKindDescriptorSchema, { kind: WorkflowTaskKind.set_vars }),
41
- create(TaskKindDescriptorSchema, { kind: WorkflowTaskKind.for_each }),
42
- ],
43
- });
44
- const execution = create(WorkflowExecutionSchema, { apiVersion: "v1", kind: "workflow_execution" });
45
- const eventLog = create(GetEventLogResponseSchema, {});
46
-
47
- let backend: Http2Server;
48
- let client: Client;
49
- let lastExecutionId: string | undefined;
50
- let lastEventReq: GetEventLogRequest | undefined;
51
- const openSessions = new Set<ServerHttp2Session>();
52
-
53
- interface ToolResult {
54
- content: Array<{ type: string; text?: string }>;
55
- isError?: boolean;
56
- }
57
-
58
- async function callTool(name: string, args: Record<string, unknown>): Promise<ToolResult> {
59
- return (await client.callTool({ name, arguments: args })) as ToolResult;
60
- }
61
-
62
- beforeAll(async () => {
63
- const routes = (router: ConnectRouter) => {
64
- router.service(TaskKindRegistryQueryController, { getTaskKindRegistry: () => registry });
65
- router.service(WorkflowExecutionQueryController, {
66
- get: (req) => {
67
- lastExecutionId = req.value;
68
- return execution;
69
- },
70
- getEventLog: (req) => {
71
- lastEventReq = req;
72
- return eventLog;
73
- },
74
- });
75
- };
76
- backend = createHttp2Server(connectNodeAdapter({ routes }));
77
- backend.on("session", (session) => {
78
- openSessions.add(session);
79
- session.on("close", () => openSessions.delete(session));
80
- });
81
- await new Promise<void>((resolve) => backend.listen(0, "127.0.0.1", resolve));
82
- const port = (backend.address() as AddressInfo).port;
83
-
84
- const mcp = createServer({ serverAddress: `127.0.0.1:${port}`, apiKey: "" });
85
- const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
86
- client = new Client({ name: "workflow-tools-integration", version: "test" });
87
- await Promise.all([mcp.connect(serverTransport), client.connect(clientTransport)]);
88
- });
89
-
90
- afterAll(async () => {
91
- await client?.close();
92
- for (const session of openSessions) session.destroy();
93
- await new Promise<void>((resolve) => backend.close(() => resolve()));
94
- });
95
-
96
- describe("workflow query tools integration", () => {
97
- it("get_task_kind_registry returns the full registry", async () => {
98
- const result = await callTool("get_task_kind_registry", {});
99
- expect(result.isError).toBeFalsy();
100
- expect(JSON.parse(result.content[0]?.text ?? "{}")).toEqual(
101
- toJson(GetTaskKindRegistryResponseSchema, registry, { useProtoFieldName: true }),
102
- );
103
- });
104
-
105
- it("get_task_kind selects one descriptor case-insensitively", async () => {
106
- const result = await callTool("get_task_kind", { kind: "FOR_EACH" });
107
- expect(result.isError).toBeFalsy();
108
- expect(JSON.parse(result.content[0]?.text ?? "{}")).toEqual(
109
- toJson(TaskKindDescriptorSchema, registry.descriptors[1]!, { useProtoFieldName: true }),
110
- );
111
- });
112
-
113
- it("get_task_kind requires a kind", async () => {
114
- const result = await callTool("get_task_kind", { kind: "" });
115
- expect(result.isError).toBe(true);
116
- expect(result.content[0]?.text).toContain("kind is required");
117
- });
118
-
119
- it("get_task_kind reports an unknown kind", async () => {
120
- const result = await callTool("get_task_kind", { kind: "does_not_exist" });
121
- expect(result.isError).toBe(true);
122
- expect(result.content[0]?.text).toContain('task kind "does_not_exist" not found in registry');
123
- });
124
-
125
- it("get_workflow_execution requires an execution_id", async () => {
126
- const result = await callTool("get_workflow_execution", { execution_id: "" });
127
- expect(result.isError).toBe(true);
128
- expect(result.content[0]?.text).toContain("execution_id is required");
129
- });
130
-
131
- it("get_workflow_execution forwards the id and returns the execution", async () => {
132
- const result = await callTool("get_workflow_execution", { execution_id: "wex_123" });
133
- expect(result.isError).toBeFalsy();
134
- expect(lastExecutionId).toBe("wex_123");
135
- expect(JSON.parse(result.content[0]?.text ?? "{}")).toEqual(
136
- toJson(WorkflowExecutionSchema, execution, { useProtoFieldName: true }),
137
- );
138
- });
139
-
140
- it("get_workflow_execution_events forwards page_size only when set", async () => {
141
- await callTool("get_workflow_execution_events", { execution_id: "wex_123", task_name: "build" });
142
- expect(lastEventReq?.taskName).toBe("build");
143
- expect(lastEventReq?.pageSize).toBe(0);
144
-
145
- await callTool("get_workflow_execution_events", { execution_id: "wex_123", page_size: 50 });
146
- expect(lastEventReq?.pageSize).toBe(50);
147
- });
148
- });