@stigmer/mcp-server 3.0.8-dev.20260612122433
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/LICENSE +190 -0
- package/README.md +157 -0
- package/cli/mcp-server-stigmer.d.ts +3 -0
- package/cli/mcp-server-stigmer.d.ts.map +1 -0
- package/cli/mcp-server-stigmer.js +201 -0
- package/cli/mcp-server-stigmer.js.map +1 -0
- package/config.d.ts +44 -0
- package/config.d.ts.map +1 -0
- package/config.js +92 -0
- package/config.js.map +1 -0
- package/domains/agents/apply.d.ts +4 -0
- package/domains/agents/apply.d.ts.map +1 -0
- package/domains/agents/apply.js +25 -0
- package/domains/agents/apply.js.map +1 -0
- package/domains/agents/delete.d.ts +3 -0
- package/domains/agents/delete.d.ts.map +1 -0
- package/domains/agents/delete.js +35 -0
- package/domains/agents/delete.js.map +1 -0
- package/domains/agents/fetch.d.ts +6 -0
- package/domains/agents/fetch.d.ts.map +1 -0
- package/domains/agents/fetch.js +24 -0
- package/domains/agents/fetch.js.map +1 -0
- package/domains/agents/resources.d.ts +5 -0
- package/domains/agents/resources.d.ts.map +1 -0
- package/domains/agents/resources.js +16 -0
- package/domains/agents/resources.js.map +1 -0
- package/domains/agents/tools.d.ts +5 -0
- package/domains/agents/tools.d.ts.map +1 -0
- package/domains/agents/tools.js +41 -0
- package/domains/agents/tools.js.map +1 -0
- package/domains/client.d.ts +53 -0
- package/domains/client.d.ts.map +1 -0
- package/domains/client.js +62 -0
- package/domains/client.js.map +1 -0
- package/domains/marshal.d.ts +8 -0
- package/domains/marshal.d.ts.map +1 -0
- package/domains/marshal.js +17 -0
- package/domains/marshal.js.map +1 -0
- package/domains/mcpservers/apply.d.ts +4 -0
- package/domains/mcpservers/apply.d.ts.map +1 -0
- package/domains/mcpservers/apply.js +26 -0
- package/domains/mcpservers/apply.js.map +1 -0
- package/domains/mcpservers/delete.d.ts +6 -0
- package/domains/mcpservers/delete.d.ts.map +1 -0
- package/domains/mcpservers/delete.js +42 -0
- package/domains/mcpservers/delete.js.map +1 -0
- package/domains/mcpservers/fetch.d.ts +7 -0
- package/domains/mcpservers/fetch.d.ts.map +1 -0
- package/domains/mcpservers/fetch.js +26 -0
- package/domains/mcpservers/fetch.js.map +1 -0
- package/domains/mcpservers/resources.d.ts +5 -0
- package/domains/mcpservers/resources.d.ts.map +1 -0
- package/domains/mcpservers/resources.js +16 -0
- package/domains/mcpservers/resources.js.map +1 -0
- package/domains/mcpservers/tools.d.ts +5 -0
- package/domains/mcpservers/tools.d.ts.map +1 -0
- package/domains/mcpservers/tools.js +39 -0
- package/domains/mcpservers/tools.js.map +1 -0
- package/domains/resourcehandler.d.ts +27 -0
- package/domains/resourcehandler.d.ts.map +1 -0
- package/domains/resourcehandler.js +32 -0
- package/domains/resourcehandler.js.map +1 -0
- package/domains/resourceuri.d.ts +34 -0
- package/domains/resourceuri.d.ts.map +1 -0
- package/domains/resourceuri.js +100 -0
- package/domains/resourceuri.js.map +1 -0
- package/domains/rpcerr.d.ts +8 -0
- package/domains/rpcerr.d.ts.map +1 -0
- package/domains/rpcerr.js +42 -0
- package/domains/rpcerr.js.map +1 -0
- package/domains/search/tools.d.ts +5 -0
- package/domains/search/tools.d.ts.map +1 -0
- package/domains/search/tools.js +125 -0
- package/domains/search/tools.js.map +1 -0
- package/domains/skills/delete.d.ts +6 -0
- package/domains/skills/delete.d.ts.map +1 -0
- package/domains/skills/delete.js +38 -0
- package/domains/skills/delete.js.map +1 -0
- package/domains/skills/fetch.d.ts +6 -0
- package/domains/skills/fetch.d.ts.map +1 -0
- package/domains/skills/fetch.js +28 -0
- package/domains/skills/fetch.js.map +1 -0
- package/domains/skills/resources.d.ts +5 -0
- package/domains/skills/resources.d.ts.map +1 -0
- package/domains/skills/resources.js +25 -0
- package/domains/skills/resources.js.map +1 -0
- package/domains/skills/tools.d.ts +5 -0
- package/domains/skills/tools.d.ts.map +1 -0
- package/domains/skills/tools.js +39 -0
- package/domains/skills/tools.js.map +1 -0
- package/domains/toolresult.d.ts +12 -0
- package/domains/toolresult.d.ts.map +1 -0
- package/domains/toolresult.js +30 -0
- package/domains/toolresult.js.map +1 -0
- package/domains/workflowexecutions/tools.d.ts +5 -0
- package/domains/workflowexecutions/tools.d.ts.map +1 -0
- package/domains/workflowexecutions/tools.js +80 -0
- package/domains/workflowexecutions/tools.js.map +1 -0
- package/domains/workflows/apply.d.ts +4 -0
- package/domains/workflows/apply.d.ts.map +1 -0
- package/domains/workflows/apply.js +30 -0
- package/domains/workflows/apply.js.map +1 -0
- package/domains/workflows/delete.d.ts +3 -0
- package/domains/workflows/delete.d.ts.map +1 -0
- package/domains/workflows/delete.js +35 -0
- package/domains/workflows/delete.js.map +1 -0
- package/domains/workflows/fetch.d.ts +6 -0
- package/domains/workflows/fetch.d.ts.map +1 -0
- package/domains/workflows/fetch.js +25 -0
- package/domains/workflows/fetch.js.map +1 -0
- package/domains/workflows/resources.d.ts +5 -0
- package/domains/workflows/resources.d.ts.map +1 -0
- package/domains/workflows/resources.js +16 -0
- package/domains/workflows/resources.js.map +1 -0
- package/domains/workflows/taskkinds.d.ts +5 -0
- package/domains/workflows/taskkinds.d.ts.map +1 -0
- package/domains/workflows/taskkinds.js +66 -0
- package/domains/workflows/taskkinds.js.map +1 -0
- package/domains/workflows/tools.d.ts +5 -0
- package/domains/workflows/tools.d.ts.map +1 -0
- package/domains/workflows/tools.js +35 -0
- package/domains/workflows/tools.js.map +1 -0
- package/domains/workflows/validate.d.ts +5 -0
- package/domains/workflows/validate.d.ts.map +1 -0
- package/domains/workflows/validate.js +113 -0
- package/domains/workflows/validate.js.map +1 -0
- package/gen/agent.d.ts +385 -0
- package/gen/agent.d.ts.map +1 -0
- package/gen/agent.js +170 -0
- package/gen/agent.js.map +1 -0
- package/gen/apply-runtime.d.ts +18 -0
- package/gen/apply-runtime.d.ts.map +1 -0
- package/gen/apply-runtime.js +50 -0
- package/gen/apply-runtime.js.map +1 -0
- package/gen/mcpserver.d.ts +289 -0
- package/gen/mcpserver.d.ts.map +1 -0
- package/gen/mcpserver.js +166 -0
- package/gen/mcpserver.js.map +1 -0
- package/gen/workflow.d.ts +805 -0
- package/gen/workflow.d.ts.map +1 -0
- package/gen/workflow.js +842 -0
- package/gen/workflow.js.map +1 -0
- package/index.d.ts +20 -0
- package/index.d.ts.map +1 -0
- package/index.js +58 -0
- package/index.js.map +1 -0
- package/logger.d.ts +20 -0
- package/logger.d.ts.map +1 -0
- package/logger.js +41 -0
- package/logger.js.map +1 -0
- package/package.json +43 -0
- package/server.d.ts +60 -0
- package/server.d.ts.map +1 -0
- package/server.js +366 -0
- package/server.js.map +1 -0
- package/src/cli/mcp-server-stigmer.ts +42 -0
- package/src/config.test.ts +88 -0
- package/src/config.ts +151 -0
- package/src/domains/agents/apply.ts +30 -0
- package/src/domains/agents/delete.ts +41 -0
- package/src/domains/agents/fetch.ts +33 -0
- package/src/domains/agents/resources.ts +20 -0
- package/src/domains/agents/tools.ts +68 -0
- package/src/domains/apply.integration.test.ts +220 -0
- package/src/domains/client.ts +95 -0
- package/src/domains/deletes.integration.test.ts +124 -0
- package/src/domains/marshal.ts +21 -0
- package/src/domains/mcpservers/apply.ts +36 -0
- package/src/domains/mcpservers/delete.ts +51 -0
- package/src/domains/mcpservers/fetch.ts +35 -0
- package/src/domains/mcpservers/resources.ts +20 -0
- package/src/domains/mcpservers/tools.ts +74 -0
- package/src/domains/reads.integration.test.ts +134 -0
- package/src/domains/resourcehandler.ts +90 -0
- package/src/domains/resources.integration.test.ts +139 -0
- package/src/domains/resourceuri.test.ts +97 -0
- package/src/domains/resourceuri.ts +124 -0
- package/src/domains/rpcerr.test.ts +62 -0
- package/src/domains/rpcerr.ts +46 -0
- package/src/domains/search/search.integration.test.ts +127 -0
- package/src/domains/search/tools.ts +160 -0
- package/src/domains/skills/delete.ts +44 -0
- package/src/domains/skills/fetch.ts +38 -0
- package/src/domains/skills/resources.ts +33 -0
- package/src/domains/skills/tools.ts +67 -0
- package/src/domains/toolresult.ts +33 -0
- package/src/domains/workflowexecutions/tools.ts +133 -0
- package/src/domains/workflows/apply.ts +40 -0
- package/src/domains/workflows/delete.ts +44 -0
- package/src/domains/workflows/fetch.ts +34 -0
- package/src/domains/workflows/resources.ts +20 -0
- package/src/domains/workflows/taskkinds.ts +103 -0
- package/src/domains/workflows/tools.ts +68 -0
- package/src/domains/workflows/validate.integration.test.ts +117 -0
- package/src/domains/workflows/validate.ts +144 -0
- package/src/domains/workflows/workflow-tools.integration.test.ts +148 -0
- package/src/gen/agent.ts +173 -0
- package/src/gen/apply-runtime.ts +52 -0
- package/src/gen/mcpserver.ts +163 -0
- package/src/gen/workflow.ts +858 -0
- package/src/http.integration.test.ts +140 -0
- package/src/index.ts +66 -0
- package/src/logger.ts +49 -0
- package/src/server.integration.test.ts +82 -0
- package/src/server.ts +414 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// McpServer apply path: create-or-update via the McpServerCommandController.apply
|
|
2
|
+
// RPC. The flat MCP input is projected into a fully-formed McpServer proto by the
|
|
3
|
+
// generated mcpServerInputToProto bridge (codegen, src/gen/mcpserver.ts), which
|
|
4
|
+
// also rebuilds the stdio/http oneof, before the call.
|
|
5
|
+
// Go parity: mcp-server/internal/domains/mcpservers/apply.go.
|
|
6
|
+
|
|
7
|
+
import { McpServerSchema } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/api_pb";
|
|
8
|
+
import { McpServerCommandController } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/command_pb";
|
|
9
|
+
|
|
10
|
+
import { mcpServerInputToProto, type McpServerInput } from "../../gen/mcpserver.js";
|
|
11
|
+
import { withClient } from "../client.js";
|
|
12
|
+
import { toProtoJson } from "../marshal.js";
|
|
13
|
+
import { rpcError } from "../rpcerr.js";
|
|
14
|
+
|
|
15
|
+
/** Create or update an MCP server, returning the persisted server as protojson. */
|
|
16
|
+
export async function applyMcpServer(
|
|
17
|
+
serverAddress: string,
|
|
18
|
+
token: string,
|
|
19
|
+
input: McpServerInput,
|
|
20
|
+
): Promise<string> {
|
|
21
|
+
const server = mcpServerInputToProto(input);
|
|
22
|
+
const desc = `mcp server "${server.metadata?.slug ?? ""}" in org "${server.metadata?.org ?? ""}"`;
|
|
23
|
+
return withClient(
|
|
24
|
+
McpServerCommandController,
|
|
25
|
+
serverAddress,
|
|
26
|
+
token,
|
|
27
|
+
async (client, callOptions) => {
|
|
28
|
+
try {
|
|
29
|
+
const result = await client.apply(server, callOptions);
|
|
30
|
+
return toProtoJson(McpServerSchema, result);
|
|
31
|
+
} catch (err) {
|
|
32
|
+
throw rpcError(err, desc);
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
);
|
|
36
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// MCP server delete path: resolve org/slug → id, then delete, both over a single
|
|
2
|
+
// shared transport.
|
|
3
|
+
// Go parity: mcp-server/internal/domains/mcpservers/delete.go.
|
|
4
|
+
//
|
|
5
|
+
// Outlier: unlike the agent/skill/workflow command controllers (which take a
|
|
6
|
+
// typed {X}Id), McpServerCommandController.delete takes the generic
|
|
7
|
+
// ApiResourceDeleteInput{resource_id}.
|
|
8
|
+
|
|
9
|
+
import { createClient } from "@connectrpc/connect";
|
|
10
|
+
import { McpServerSchema } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/api_pb";
|
|
11
|
+
import { McpServerCommandController } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/command_pb";
|
|
12
|
+
import { McpServerQueryController } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/query_pb";
|
|
13
|
+
import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
|
|
14
|
+
|
|
15
|
+
import { withTransport } from "../client.js";
|
|
16
|
+
import { toProtoJson } from "../marshal.js";
|
|
17
|
+
import { rpcError } from "../rpcerr.js";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Delete an MCP server by org and slug, returning the deleted MCP server as
|
|
21
|
+
* protojson.
|
|
22
|
+
*/
|
|
23
|
+
export async function deleteMcpServer(
|
|
24
|
+
serverAddress: string,
|
|
25
|
+
token: string,
|
|
26
|
+
org: string,
|
|
27
|
+
slug: string,
|
|
28
|
+
): Promise<string> {
|
|
29
|
+
const desc = `MCP server "${slug}" in org "${org}"`;
|
|
30
|
+
return withTransport(serverAddress, token, async (transport, callOptions) => {
|
|
31
|
+
const query = createClient(McpServerQueryController, transport);
|
|
32
|
+
let id: string;
|
|
33
|
+
try {
|
|
34
|
+
const mcpServer = await query.getByReference(
|
|
35
|
+
{ org, kind: ApiResourceKind.mcp_server, slug },
|
|
36
|
+
callOptions,
|
|
37
|
+
);
|
|
38
|
+
id = mcpServer.metadata?.id ?? "";
|
|
39
|
+
} catch (err) {
|
|
40
|
+
throw rpcError(err, desc);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const command = createClient(McpServerCommandController, transport);
|
|
44
|
+
try {
|
|
45
|
+
const deleted = await command.delete({ resourceId: id }, callOptions);
|
|
46
|
+
return toProtoJson(McpServerSchema, deleted);
|
|
47
|
+
} catch (err) {
|
|
48
|
+
throw rpcError(err, desc);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// MCP server read path: the single RPC both the get_mcp_server tool and the
|
|
2
|
+
// mcp-server resource template delegate to.
|
|
3
|
+
// Go parity: mcp-server/internal/domains/mcpservers/fetch.go.
|
|
4
|
+
|
|
5
|
+
import { McpServerSchema } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/api_pb";
|
|
6
|
+
import { McpServerQueryController } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/query_pb";
|
|
7
|
+
import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
|
|
8
|
+
|
|
9
|
+
import { withClient } from "../client.js";
|
|
10
|
+
import { toProtoJson } from "../marshal.js";
|
|
11
|
+
import { rpcError } from "../rpcerr.js";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Retrieve an MCP server by org and slug, returning its protojson
|
|
15
|
+
* representation. Errors are classified into user-facing messages via
|
|
16
|
+
* {@link rpcError}.
|
|
17
|
+
*/
|
|
18
|
+
export async function fetchMcpServer(
|
|
19
|
+
serverAddress: string,
|
|
20
|
+
token: string,
|
|
21
|
+
org: string,
|
|
22
|
+
slug: string,
|
|
23
|
+
): Promise<string> {
|
|
24
|
+
return withClient(McpServerQueryController, serverAddress, token, async (client, callOptions) => {
|
|
25
|
+
try {
|
|
26
|
+
const mcpServer = await client.getByReference(
|
|
27
|
+
{ org, kind: ApiResourceKind.mcp_server, slug },
|
|
28
|
+
callOptions,
|
|
29
|
+
);
|
|
30
|
+
return toProtoJson(McpServerSchema, mcpServer);
|
|
31
|
+
} catch (err) {
|
|
32
|
+
throw rpcError(err, `MCP server "${slug}" in org "${org}"`);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// MCP server resource template (stigmer://mcp-servers/{org}/{slug}).
|
|
2
|
+
// Go parity: mcp-server/internal/domains/mcpservers/resources.go.
|
|
3
|
+
|
|
4
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
|
+
|
|
6
|
+
import type { BackendTarget } from "../client.js";
|
|
7
|
+
import { registerResource } from "../resourcehandler.js";
|
|
8
|
+
import { fetchMcpServer } from "./fetch.js";
|
|
9
|
+
|
|
10
|
+
/** Register the MCP server resource template; returns the registered resource names. */
|
|
11
|
+
export function registerMcpServerResources(server: McpServer, target: BackendTarget): string[] {
|
|
12
|
+
registerResource(server, target, {
|
|
13
|
+
name: "stigmer_mcp_server",
|
|
14
|
+
title: "Stigmer MCP Server",
|
|
15
|
+
description: "Full definition of a Stigmer MCP server, identified by organization and slug.",
|
|
16
|
+
template: "stigmer://mcp-servers/{org}/{slug}",
|
|
17
|
+
fetch: fetchMcpServer,
|
|
18
|
+
});
|
|
19
|
+
return ["stigmer_mcp_server"];
|
|
20
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// MCP tools for the McpServer domain.
|
|
2
|
+
// Go parity: mcp-server/internal/domains/mcpservers/tools.go.
|
|
3
|
+
//
|
|
4
|
+
// Tool name, description, and per-field input descriptions are part of the
|
|
5
|
+
// parity contract (clients surface them to the model verbatim).
|
|
6
|
+
|
|
7
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
8
|
+
import { z } from "zod";
|
|
9
|
+
|
|
10
|
+
import { McpServerInputShape } from "../../gen/mcpserver.js";
|
|
11
|
+
import { resolveToken, type BackendTarget } from "../client.js";
|
|
12
|
+
import { textOrError } from "../toolresult.js";
|
|
13
|
+
import { applyMcpServer } from "./apply.js";
|
|
14
|
+
import { deleteMcpServer } from "./delete.js";
|
|
15
|
+
import { fetchMcpServer } from "./fetch.js";
|
|
16
|
+
|
|
17
|
+
/** Register every McpServer-domain tool; returns the registered tool names. */
|
|
18
|
+
export function registerMcpServerTools(server: McpServer, target: BackendTarget): string[] {
|
|
19
|
+
server.registerTool(
|
|
20
|
+
"get_mcp_server",
|
|
21
|
+
{
|
|
22
|
+
description:
|
|
23
|
+
"Get full details of a Stigmer MCP server by its org and slug (e.g. org=acme slug=my-server).",
|
|
24
|
+
inputSchema: {
|
|
25
|
+
org: z.string().describe("Organization slug that owns the MCP server (e.g. acme)."),
|
|
26
|
+
slug: z
|
|
27
|
+
.string()
|
|
28
|
+
.describe("MCP server slug — the unique identifier within the org (e.g. my-server)."),
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
(args, extra) =>
|
|
32
|
+
textOrError(() =>
|
|
33
|
+
fetchMcpServer(target.serverAddress, resolveToken(extra, target.apiKey), args.org, args.slug),
|
|
34
|
+
),
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
server.registerTool(
|
|
38
|
+
"apply_mcp_server",
|
|
39
|
+
{
|
|
40
|
+
description:
|
|
41
|
+
"Create or update a Stigmer MCP server definition (idempotent). Provide identity fields (name, org) and server configuration (stdio/http, tools, env, etc.).",
|
|
42
|
+
inputSchema: McpServerInputShape,
|
|
43
|
+
},
|
|
44
|
+
(args, extra) =>
|
|
45
|
+
textOrError(() =>
|
|
46
|
+
applyMcpServer(target.serverAddress, resolveToken(extra, target.apiKey), args),
|
|
47
|
+
),
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
server.registerTool(
|
|
51
|
+
"delete_mcp_server",
|
|
52
|
+
{
|
|
53
|
+
description:
|
|
54
|
+
"Delete a Stigmer MCP server definition by its org and slug. Returns the deleted MCP server.",
|
|
55
|
+
inputSchema: {
|
|
56
|
+
org: z.string().describe("Organization slug that owns the MCP server (e.g. acme)."),
|
|
57
|
+
slug: z
|
|
58
|
+
.string()
|
|
59
|
+
.describe("MCP server slug — the unique identifier within the org (e.g. github)."),
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
(args, extra) =>
|
|
63
|
+
textOrError(() =>
|
|
64
|
+
deleteMcpServer(
|
|
65
|
+
target.serverAddress,
|
|
66
|
+
resolveToken(extra, target.apiKey),
|
|
67
|
+
args.org,
|
|
68
|
+
args.slug,
|
|
69
|
+
),
|
|
70
|
+
),
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
return ["get_mcp_server", "apply_mcp_server", "delete_mcp_server"];
|
|
74
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
// In-process integration test for the read tools (get_mcp_server, get_skill,
|
|
2
|
+
// get_workflow). Stands up a real Connect backend serving the three query
|
|
3
|
+
// controllers, drives the MCP server through an in-memory client, and asserts
|
|
4
|
+
// each tool returns the backend's protojson verbatim (the parity contract) and
|
|
5
|
+
// that get_skill forwards its optional version to the backend.
|
|
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 { McpServerSchema } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/api_pb";
|
|
20
|
+
import { McpServerQueryController } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/query_pb";
|
|
21
|
+
import { SkillSchema } from "@stigmer/protos/ai/stigmer/agentic/skill/v1/api_pb";
|
|
22
|
+
import { SkillQueryController } from "@stigmer/protos/ai/stigmer/agentic/skill/v1/query_pb";
|
|
23
|
+
import { WorkflowSchema } from "@stigmer/protos/ai/stigmer/agentic/workflow/v1/api_pb";
|
|
24
|
+
import { WorkflowQueryController } from "@stigmer/protos/ai/stigmer/agentic/workflow/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 knownMcpServer = create(McpServerSchema, {
|
|
33
|
+
apiVersion: "v1",
|
|
34
|
+
kind: "mcp_server",
|
|
35
|
+
metadata: { name: "GitHub", slug: "github", org: "acme", id: "mcp-1" },
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const knownSkill = create(SkillSchema, {
|
|
39
|
+
apiVersion: "v1",
|
|
40
|
+
kind: "skill",
|
|
41
|
+
metadata: { name: "Code Review", slug: "code-review", org: "acme", id: "skl-1" },
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const knownWorkflow = create(WorkflowSchema, {
|
|
45
|
+
apiVersion: "v1",
|
|
46
|
+
kind: "workflow",
|
|
47
|
+
metadata: { name: "Release", slug: "release", org: "acme", id: "wkf-1" },
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
let backend: Http2Server;
|
|
51
|
+
let client: Client;
|
|
52
|
+
let lastSkillVersion: string | undefined;
|
|
53
|
+
const openSessions = new Set<ServerHttp2Session>();
|
|
54
|
+
|
|
55
|
+
interface ToolResult {
|
|
56
|
+
content: Array<{ type: string; text?: string }>;
|
|
57
|
+
isError?: boolean;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function callTool(name: string, args: Record<string, unknown>): Promise<ToolResult> {
|
|
61
|
+
return (await client.callTool({ name, arguments: args })) as ToolResult;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
beforeAll(async () => {
|
|
65
|
+
const routes = (router: ConnectRouter) => {
|
|
66
|
+
router.service(McpServerQueryController, { getByReference: () => knownMcpServer });
|
|
67
|
+
router.service(SkillQueryController, {
|
|
68
|
+
getByReference: (req) => {
|
|
69
|
+
lastSkillVersion = req.version;
|
|
70
|
+
return knownSkill;
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
router.service(WorkflowQueryController, { getByReference: () => knownWorkflow });
|
|
74
|
+
};
|
|
75
|
+
backend = createHttp2Server(connectNodeAdapter({ routes }));
|
|
76
|
+
// Force keep-alive sessions closed on teardown, else backend.close() blocks.
|
|
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: "reads-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("read tools integration", () => {
|
|
97
|
+
it("advertises all read tools", async () => {
|
|
98
|
+
const { tools } = await client.listTools();
|
|
99
|
+
expect(tools.map((t) => t.name)).toEqual(
|
|
100
|
+
expect.arrayContaining(["get_agent", "get_mcp_server", "get_skill", "get_workflow"]),
|
|
101
|
+
);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("get_mcp_server returns the backend protojson", async () => {
|
|
105
|
+
const result = await callTool("get_mcp_server", { org: "acme", slug: "github" });
|
|
106
|
+
expect(result.isError).toBeFalsy();
|
|
107
|
+
expect(JSON.parse(result.content[0]?.text ?? "{}")).toEqual(
|
|
108
|
+
toJson(McpServerSchema, knownMcpServer, { useProtoFieldName: true }),
|
|
109
|
+
);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("get_workflow returns the backend protojson", async () => {
|
|
113
|
+
const result = await callTool("get_workflow", { org: "acme", slug: "release" });
|
|
114
|
+
expect(result.isError).toBeFalsy();
|
|
115
|
+
expect(JSON.parse(result.content[0]?.text ?? "{}")).toEqual(
|
|
116
|
+
toJson(WorkflowSchema, knownWorkflow, { useProtoFieldName: true }),
|
|
117
|
+
);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("get_skill returns the backend protojson and defaults version to latest", async () => {
|
|
121
|
+
const result = await callTool("get_skill", { org: "acme", slug: "code-review" });
|
|
122
|
+
expect(result.isError).toBeFalsy();
|
|
123
|
+
expect(JSON.parse(result.content[0]?.text ?? "{}")).toEqual(
|
|
124
|
+
toJson(SkillSchema, knownSkill, { useProtoFieldName: true }),
|
|
125
|
+
);
|
|
126
|
+
// Omitted version is forwarded as the empty string ("latest").
|
|
127
|
+
expect(lastSkillVersion).toBe("");
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it("get_skill forwards an explicit version to the backend", async () => {
|
|
131
|
+
await callTool("get_skill", { org: "acme", slug: "code-review", version: "stable" });
|
|
132
|
+
expect(lastSkillVersion).toBe("stable");
|
|
133
|
+
});
|
|
134
|
+
});
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// Registration helpers for MCP resource templates.
|
|
2
|
+
// Go parity: mcp-server/internal/domains/resourcehandler.go.
|
|
3
|
+
//
|
|
4
|
+
// Every Stigmer resource template resolves the per-request credential the same
|
|
5
|
+
// way tools do (resolveToken over extra.authInfo), parses org/slug[/version]
|
|
6
|
+
// from the request URI, delegates to the domain fetch, and returns a single
|
|
7
|
+
// application/json content entry. These helpers keep each domain's resources.ts
|
|
8
|
+
// a one-liner and ensure the auth + result shape stay identical across domains.
|
|
9
|
+
|
|
10
|
+
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
11
|
+
import type { ReadResourceResult } from "@modelcontextprotocol/sdk/types.js";
|
|
12
|
+
|
|
13
|
+
import { resolveToken, type BackendTarget } from "./client.js";
|
|
14
|
+
import { parseResourceURI, parseVersionedResourceURI } from "./resourceuri.js";
|
|
15
|
+
|
|
16
|
+
/** Domain read used by a non-versioned resource template. */
|
|
17
|
+
export type ResourceFetch = (
|
|
18
|
+
serverAddress: string,
|
|
19
|
+
token: string,
|
|
20
|
+
org: string,
|
|
21
|
+
slug: string,
|
|
22
|
+
) => Promise<string>;
|
|
23
|
+
|
|
24
|
+
/** Domain read used by a versioned resource template ("" version means latest). */
|
|
25
|
+
export type VersionedResourceFetch = (
|
|
26
|
+
serverAddress: string,
|
|
27
|
+
token: string,
|
|
28
|
+
org: string,
|
|
29
|
+
slug: string,
|
|
30
|
+
version: string,
|
|
31
|
+
) => Promise<string>;
|
|
32
|
+
|
|
33
|
+
/** Options shared by both registration helpers. */
|
|
34
|
+
interface ResourceOptions {
|
|
35
|
+
/** Stable resource name surfaced in resources/templates/list (e.g. stigmer_agent). */
|
|
36
|
+
readonly name: string;
|
|
37
|
+
/** Human title surfaced to clients (e.g. "Stigmer Agent"). */
|
|
38
|
+
readonly title: string;
|
|
39
|
+
/** Human description surfaced to clients. */
|
|
40
|
+
readonly description: string;
|
|
41
|
+
/** RFC-6570 URI template, e.g. stigmer://agents/{org}/{slug}. */
|
|
42
|
+
readonly template: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Register a stigmer://{authority}/{org}/{slug} resource template. */
|
|
46
|
+
export function registerResource(
|
|
47
|
+
server: McpServer,
|
|
48
|
+
target: BackendTarget,
|
|
49
|
+
opts: ResourceOptions & { readonly fetch: ResourceFetch },
|
|
50
|
+
): void {
|
|
51
|
+
server.registerResource(
|
|
52
|
+
opts.name,
|
|
53
|
+
new ResourceTemplate(opts.template, { list: undefined }),
|
|
54
|
+
{ title: opts.title, description: opts.description, mimeType: "application/json" },
|
|
55
|
+
async (uri, _vars, extra) => {
|
|
56
|
+
const { org, slug } = parseResourceURI(uri.href);
|
|
57
|
+
const text = await opts.fetch(target.serverAddress, resolveToken(extra, target.apiKey), org, slug);
|
|
58
|
+
return jsonResource(uri.href, text);
|
|
59
|
+
},
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Register a stigmer://{authority}/{org}/{slug}[/{version}] resource template. */
|
|
64
|
+
export function registerVersionedResource(
|
|
65
|
+
server: McpServer,
|
|
66
|
+
target: BackendTarget,
|
|
67
|
+
opts: ResourceOptions & { readonly fetch: VersionedResourceFetch },
|
|
68
|
+
): void {
|
|
69
|
+
server.registerResource(
|
|
70
|
+
opts.name,
|
|
71
|
+
new ResourceTemplate(opts.template, { list: undefined }),
|
|
72
|
+
{ title: opts.title, description: opts.description, mimeType: "application/json" },
|
|
73
|
+
async (uri, _vars, extra) => {
|
|
74
|
+
const { org, slug, version } = parseVersionedResourceURI(uri.href);
|
|
75
|
+
const text = await opts.fetch(
|
|
76
|
+
target.serverAddress,
|
|
77
|
+
resolveToken(extra, target.apiKey),
|
|
78
|
+
org,
|
|
79
|
+
slug,
|
|
80
|
+
version,
|
|
81
|
+
);
|
|
82
|
+
return jsonResource(uri.href, text);
|
|
83
|
+
},
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Wrap fetched JSON text into the single-entry ReadResourceResult tools expect. */
|
|
88
|
+
function jsonResource(uri: string, text: string): ReadResourceResult {
|
|
89
|
+
return { contents: [{ uri, mimeType: "application/json", text }] };
|
|
90
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
// In-process integration test for the 5 resource templates. Verifies template
|
|
2
|
+
// discovery, that each read returns the backend protojson as a single
|
|
3
|
+
// application/json entry, and that the skill templates resolve latest (empty
|
|
4
|
+
// version) vs a pinned version.
|
|
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 { AgentSchema } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/api_pb";
|
|
19
|
+
import { AgentQueryController } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/query_pb";
|
|
20
|
+
import { McpServerSchema } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/api_pb";
|
|
21
|
+
import { McpServerQueryController } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/query_pb";
|
|
22
|
+
import { SkillSchema } from "@stigmer/protos/ai/stigmer/agentic/skill/v1/api_pb";
|
|
23
|
+
import { SkillQueryController } from "@stigmer/protos/ai/stigmer/agentic/skill/v1/query_pb";
|
|
24
|
+
import { WorkflowSchema } from "@stigmer/protos/ai/stigmer/agentic/workflow/v1/api_pb";
|
|
25
|
+
import { WorkflowQueryController } from "@stigmer/protos/ai/stigmer/agentic/workflow/v1/query_pb";
|
|
26
|
+
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
|
27
|
+
|
|
28
|
+
import { configureLogger } from "../logger";
|
|
29
|
+
import { createServer } from "../server";
|
|
30
|
+
|
|
31
|
+
configureLogger({ level: "error", format: "text" });
|
|
32
|
+
|
|
33
|
+
const agent = create(AgentSchema, { apiVersion: "v1", kind: "agent", metadata: { slug: "a", org: "acme" } });
|
|
34
|
+
const mcpServer = create(McpServerSchema, {
|
|
35
|
+
apiVersion: "v1",
|
|
36
|
+
kind: "mcp_server",
|
|
37
|
+
metadata: { slug: "m", org: "acme" },
|
|
38
|
+
});
|
|
39
|
+
const skill = create(SkillSchema, { apiVersion: "v1", kind: "skill", metadata: { slug: "s", org: "acme" } });
|
|
40
|
+
const workflow = create(WorkflowSchema, {
|
|
41
|
+
apiVersion: "v1",
|
|
42
|
+
kind: "workflow",
|
|
43
|
+
metadata: { slug: "w", org: "acme" },
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
let backend: Http2Server;
|
|
47
|
+
let client: Client;
|
|
48
|
+
let lastSkillVersion: string | undefined;
|
|
49
|
+
const openSessions = new Set<ServerHttp2Session>();
|
|
50
|
+
|
|
51
|
+
interface ResourceResult {
|
|
52
|
+
contents: Array<{ uri: string; mimeType?: string; text?: string }>;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
beforeAll(async () => {
|
|
56
|
+
const routes = (router: ConnectRouter) => {
|
|
57
|
+
router.service(AgentQueryController, { getByReference: () => agent });
|
|
58
|
+
router.service(McpServerQueryController, { getByReference: () => mcpServer });
|
|
59
|
+
router.service(SkillQueryController, {
|
|
60
|
+
getByReference: (req) => {
|
|
61
|
+
lastSkillVersion = req.version;
|
|
62
|
+
return skill;
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
router.service(WorkflowQueryController, { getByReference: () => workflow });
|
|
66
|
+
};
|
|
67
|
+
backend = createHttp2Server(connectNodeAdapter({ routes }));
|
|
68
|
+
backend.on("session", (session) => {
|
|
69
|
+
openSessions.add(session);
|
|
70
|
+
session.on("close", () => openSessions.delete(session));
|
|
71
|
+
});
|
|
72
|
+
await new Promise<void>((resolve) => backend.listen(0, "127.0.0.1", resolve));
|
|
73
|
+
const port = (backend.address() as AddressInfo).port;
|
|
74
|
+
|
|
75
|
+
const mcp = createServer({ serverAddress: `127.0.0.1:${port}`, apiKey: "" });
|
|
76
|
+
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
|
|
77
|
+
client = new Client({ name: "resources-integration", version: "test" });
|
|
78
|
+
await Promise.all([mcp.connect(serverTransport), client.connect(clientTransport)]);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
afterAll(async () => {
|
|
82
|
+
await client?.close();
|
|
83
|
+
for (const session of openSessions) session.destroy();
|
|
84
|
+
await new Promise<void>((resolve) => backend.close(() => resolve()));
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
describe("resource templates integration", () => {
|
|
88
|
+
it("advertises all five templates", async () => {
|
|
89
|
+
const { resourceTemplates } = await client.listResourceTemplates();
|
|
90
|
+
expect(resourceTemplates.map((t) => t.name)).toEqual(
|
|
91
|
+
expect.arrayContaining([
|
|
92
|
+
"stigmer_agent",
|
|
93
|
+
"stigmer_mcp_server",
|
|
94
|
+
"stigmer_skill",
|
|
95
|
+
"stigmer_skill_version",
|
|
96
|
+
"stigmer_workflow",
|
|
97
|
+
]),
|
|
98
|
+
);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("reads an agent resource", async () => {
|
|
102
|
+
const result = (await client.readResource({ uri: "stigmer://agents/acme/a" })) as ResourceResult;
|
|
103
|
+
expect(result.contents[0]?.mimeType).toBe("application/json");
|
|
104
|
+
expect(JSON.parse(result.contents[0]?.text ?? "{}")).toEqual(
|
|
105
|
+
toJson(AgentSchema, agent, { useProtoFieldName: true }),
|
|
106
|
+
);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("reads an mcp-server resource", async () => {
|
|
110
|
+
const result = (await client.readResource({
|
|
111
|
+
uri: "stigmer://mcp-servers/acme/m",
|
|
112
|
+
})) as ResourceResult;
|
|
113
|
+
expect(JSON.parse(result.contents[0]?.text ?? "{}")).toEqual(
|
|
114
|
+
toJson(McpServerSchema, mcpServer, { useProtoFieldName: true }),
|
|
115
|
+
);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("reads a workflow resource", async () => {
|
|
119
|
+
const result = (await client.readResource({
|
|
120
|
+
uri: "stigmer://workflows/acme/w",
|
|
121
|
+
})) as ResourceResult;
|
|
122
|
+
expect(JSON.parse(result.contents[0]?.text ?? "{}")).toEqual(
|
|
123
|
+
toJson(WorkflowSchema, workflow, { useProtoFieldName: true }),
|
|
124
|
+
);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("reads the latest skill (empty version)", async () => {
|
|
128
|
+
const result = (await client.readResource({ uri: "stigmer://skills/acme/s" })) as ResourceResult;
|
|
129
|
+
expect(JSON.parse(result.contents[0]?.text ?? "{}")).toEqual(
|
|
130
|
+
toJson(SkillSchema, skill, { useProtoFieldName: true }),
|
|
131
|
+
);
|
|
132
|
+
expect(lastSkillVersion).toBe("");
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("reads a pinned skill version", async () => {
|
|
136
|
+
await client.readResource({ uri: "stigmer://skills/acme/s/v2.0.0" });
|
|
137
|
+
expect(lastSkillVersion).toBe("v2.0.0");
|
|
138
|
+
});
|
|
139
|
+
});
|