@cubis/foundry 0.3.47 → 0.3.48
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 +19 -0
- package/mcp/dist/index.js +337 -105
- package/mcp/src/server.ts +41 -179
- package/mcp/src/tools/future/README.md +3 -3
- package/mcp/src/tools/index.ts +14 -0
- package/mcp/src/tools/registry.test.ts +121 -0
- package/mcp/src/tools/registry.ts +318 -0
- package/package.json +17 -1
- package/workflows/workflows/agent-environment-setup/platforms/antigravity/rules/GEMINI.md +189 -36
- package/workflows/workflows/agent-environment-setup/platforms/codex/rules/AGENTS.md +193 -44
- package/workflows/workflows/agent-environment-setup/platforms/copilot/rules/AGENTS.md +187 -40
- package/workflows/workflows/agent-environment-setup/platforms/copilot/rules/copilot-instructions.md +187 -39
package/mcp/src/server.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Cubis Foundry MCP Server – server factory.
|
|
3
3
|
*
|
|
4
|
-
* Creates and configures the McpServer instance with built-in tools
|
|
5
|
-
* dynamic Postman/Stitch passthrough namespaces.
|
|
4
|
+
* Creates and configures the McpServer instance with built-in tools
|
|
5
|
+
* (via declarative registry) plus dynamic Postman/Stitch passthrough namespaces.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
@@ -10,59 +10,12 @@ import { z } from "zod";
|
|
|
10
10
|
import type { ServerConfig } from "./config/schema.js";
|
|
11
11
|
import type { ConfigScope } from "./cbxConfig/types.js";
|
|
12
12
|
import type { VaultManifest } from "./vault/types.js";
|
|
13
|
-
import {
|
|
14
|
-
// Skill tools
|
|
15
|
-
skillListCategoriesName,
|
|
16
|
-
skillListCategoriesDescription,
|
|
17
|
-
skillListCategoriesSchema,
|
|
18
|
-
handleSkillListCategories,
|
|
19
|
-
skillBrowseCategoryName,
|
|
20
|
-
skillBrowseCategoryDescription,
|
|
21
|
-
skillBrowseCategorySchema,
|
|
22
|
-
handleSkillBrowseCategory,
|
|
23
|
-
skillSearchName,
|
|
24
|
-
skillSearchDescription,
|
|
25
|
-
skillSearchSchema,
|
|
26
|
-
handleSkillSearch,
|
|
27
|
-
skillGetName,
|
|
28
|
-
skillGetDescription,
|
|
29
|
-
skillGetSchema,
|
|
30
|
-
handleSkillGet,
|
|
31
|
-
skillBudgetReportName,
|
|
32
|
-
skillBudgetReportDescription,
|
|
33
|
-
skillBudgetReportSchema,
|
|
34
|
-
handleSkillBudgetReport,
|
|
35
|
-
// Postman tools
|
|
36
|
-
postmanGetModeName,
|
|
37
|
-
postmanGetModeDescription,
|
|
38
|
-
postmanGetModeSchema,
|
|
39
|
-
handlePostmanGetMode,
|
|
40
|
-
postmanSetModeName,
|
|
41
|
-
postmanSetModeDescription,
|
|
42
|
-
postmanSetModeSchema,
|
|
43
|
-
handlePostmanSetMode,
|
|
44
|
-
postmanGetStatusName,
|
|
45
|
-
postmanGetStatusDescription,
|
|
46
|
-
postmanGetStatusSchema,
|
|
47
|
-
handlePostmanGetStatus,
|
|
48
|
-
// Stitch tools
|
|
49
|
-
stitchGetModeName,
|
|
50
|
-
stitchGetModeDescription,
|
|
51
|
-
stitchGetModeSchema,
|
|
52
|
-
handleStitchGetMode,
|
|
53
|
-
stitchSetProfileName,
|
|
54
|
-
stitchSetProfileDescription,
|
|
55
|
-
stitchSetProfileSchema,
|
|
56
|
-
handleStitchSetProfile,
|
|
57
|
-
stitchGetStatusName,
|
|
58
|
-
stitchGetStatusDescription,
|
|
59
|
-
stitchGetStatusSchema,
|
|
60
|
-
handleStitchGetStatus,
|
|
61
|
-
} from "./tools/index.js";
|
|
13
|
+
import { TOOL_REGISTRY, type ToolRuntimeContext } from "./tools/registry.js";
|
|
62
14
|
import {
|
|
63
15
|
callUpstreamTool,
|
|
64
16
|
discoverUpstreamCatalogs,
|
|
65
17
|
} from "./upstream/passthrough.js";
|
|
18
|
+
import { logger } from "./utils/logger.js";
|
|
66
19
|
|
|
67
20
|
export interface CreateServerOptions {
|
|
68
21
|
config: ServerConfig;
|
|
@@ -102,137 +55,37 @@ export async function createServer({
|
|
|
102
55
|
config,
|
|
103
56
|
manifest,
|
|
104
57
|
defaultConfigScope = "auto",
|
|
105
|
-
}: CreateServerOptions): McpServer {
|
|
58
|
+
}: CreateServerOptions): Promise<McpServer> {
|
|
106
59
|
const server = new McpServer({
|
|
107
60
|
name: config.server.name,
|
|
108
61
|
version: config.server.version,
|
|
109
62
|
});
|
|
110
63
|
|
|
111
|
-
|
|
112
|
-
const charsPerToken = config.telemetry?.charsPerToken ?? 4;
|
|
113
|
-
const withDefaultScope = (
|
|
114
|
-
args: Record<string, unknown> | undefined,
|
|
115
|
-
): Record<string, unknown> => {
|
|
116
|
-
const safeArgs = args ?? {};
|
|
117
|
-
return {
|
|
118
|
-
...safeArgs,
|
|
119
|
-
scope:
|
|
120
|
-
typeof safeArgs.scope === "string" ? safeArgs.scope : defaultConfigScope,
|
|
121
|
-
};
|
|
122
|
-
};
|
|
123
|
-
|
|
124
|
-
// ─── Skill vault tools ───────────────────────────────────────
|
|
125
|
-
|
|
126
|
-
server.tool(
|
|
127
|
-
skillListCategoriesName,
|
|
128
|
-
skillListCategoriesDescription,
|
|
129
|
-
skillListCategoriesSchema.shape,
|
|
130
|
-
async () => handleSkillListCategories(manifest, charsPerToken),
|
|
131
|
-
);
|
|
132
|
-
|
|
133
|
-
server.tool(
|
|
134
|
-
skillBrowseCategoryName,
|
|
135
|
-
skillBrowseCategoryDescription,
|
|
136
|
-
skillBrowseCategorySchema.shape,
|
|
137
|
-
async (args) =>
|
|
138
|
-
handleSkillBrowseCategory(args, manifest, maxLen, charsPerToken),
|
|
139
|
-
);
|
|
140
|
-
|
|
141
|
-
server.tool(
|
|
142
|
-
skillSearchName,
|
|
143
|
-
skillSearchDescription,
|
|
144
|
-
skillSearchSchema.shape,
|
|
145
|
-
async (args) => handleSkillSearch(args, manifest, maxLen, charsPerToken),
|
|
146
|
-
);
|
|
147
|
-
|
|
148
|
-
server.tool(
|
|
149
|
-
skillGetName,
|
|
150
|
-
skillGetDescription,
|
|
151
|
-
skillGetSchema.shape,
|
|
152
|
-
async (args) => handleSkillGet(args, manifest, charsPerToken),
|
|
153
|
-
);
|
|
154
|
-
|
|
155
|
-
server.tool(
|
|
156
|
-
skillBudgetReportName,
|
|
157
|
-
skillBudgetReportDescription,
|
|
158
|
-
skillBudgetReportSchema.shape,
|
|
159
|
-
async (args) => handleSkillBudgetReport(args, manifest, charsPerToken),
|
|
160
|
-
);
|
|
64
|
+
// ─── Built-in tools from declarative registry ─────────────
|
|
161
65
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
async (args) =>
|
|
169
|
-
handlePostmanGetMode(
|
|
170
|
-
withDefaultScope(args as Record<string, unknown>) as z.infer<
|
|
171
|
-
typeof postmanGetModeSchema
|
|
172
|
-
>,
|
|
173
|
-
),
|
|
174
|
-
);
|
|
175
|
-
|
|
176
|
-
server.tool(
|
|
177
|
-
postmanSetModeName,
|
|
178
|
-
postmanSetModeDescription,
|
|
179
|
-
postmanSetModeSchema.shape,
|
|
180
|
-
async (args) =>
|
|
181
|
-
handlePostmanSetMode(
|
|
182
|
-
withDefaultScope(args as Record<string, unknown>) as z.infer<
|
|
183
|
-
typeof postmanSetModeSchema
|
|
184
|
-
>,
|
|
185
|
-
),
|
|
186
|
-
);
|
|
187
|
-
|
|
188
|
-
server.tool(
|
|
189
|
-
postmanGetStatusName,
|
|
190
|
-
postmanGetStatusDescription,
|
|
191
|
-
postmanGetStatusSchema.shape,
|
|
192
|
-
async (args) =>
|
|
193
|
-
handlePostmanGetStatus(
|
|
194
|
-
withDefaultScope(args as Record<string, unknown>) as z.infer<
|
|
195
|
-
typeof postmanGetStatusSchema
|
|
196
|
-
>,
|
|
197
|
-
),
|
|
198
|
-
);
|
|
199
|
-
|
|
200
|
-
// ─── Stitch tools ──────────────────────────────────────────
|
|
201
|
-
|
|
202
|
-
server.tool(
|
|
203
|
-
stitchGetModeName,
|
|
204
|
-
stitchGetModeDescription,
|
|
205
|
-
stitchGetModeSchema.shape,
|
|
206
|
-
async (args) =>
|
|
207
|
-
handleStitchGetMode(
|
|
208
|
-
withDefaultScope(args as Record<string, unknown>) as z.infer<
|
|
209
|
-
typeof stitchGetModeSchema
|
|
210
|
-
>,
|
|
211
|
-
),
|
|
212
|
-
);
|
|
66
|
+
const runtimeCtx: ToolRuntimeContext = {
|
|
67
|
+
manifest,
|
|
68
|
+
charsPerToken: config.telemetry?.charsPerToken ?? 4,
|
|
69
|
+
summaryMaxLength: config.vault.summaryMaxLength,
|
|
70
|
+
defaultConfigScope,
|
|
71
|
+
};
|
|
213
72
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
73
|
+
for (const entry of TOOL_REGISTRY) {
|
|
74
|
+
const handler = entry.createHandler(runtimeCtx);
|
|
75
|
+
// Cast is safe: registry handler signatures are compatible at runtime.
|
|
76
|
+
// The overload ambiguity arises because an empty ZodRawShape `{}`
|
|
77
|
+
// is structurally assignable to the annotations object overload.
|
|
78
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
79
|
+
(server as any).tool(
|
|
80
|
+
entry.name,
|
|
81
|
+
entry.description,
|
|
82
|
+
entry.schema.shape,
|
|
83
|
+
handler,
|
|
84
|
+
);
|
|
85
|
+
}
|
|
225
86
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
stitchGetStatusDescription,
|
|
229
|
-
stitchGetStatusSchema.shape,
|
|
230
|
-
async (args) =>
|
|
231
|
-
handleStitchGetStatus(
|
|
232
|
-
withDefaultScope(args as Record<string, unknown>) as z.infer<
|
|
233
|
-
typeof stitchGetStatusSchema
|
|
234
|
-
>,
|
|
235
|
-
),
|
|
87
|
+
logger.debug(
|
|
88
|
+
`Registered ${TOOL_REGISTRY.length} built-in tools from registry`,
|
|
236
89
|
);
|
|
237
90
|
|
|
238
91
|
// ─── Dynamic upstream passthrough tools ────────────────────
|
|
@@ -242,21 +95,30 @@ export async function createServer({
|
|
|
242
95
|
for (const catalog of [upstreamCatalogs.postman, upstreamCatalogs.stitch]) {
|
|
243
96
|
for (const tool of catalog.tools) {
|
|
244
97
|
const namespaced = tool.namespacedName;
|
|
245
|
-
|
|
98
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
99
|
+
(server as any).tool(
|
|
246
100
|
namespaced,
|
|
247
101
|
`[${catalog.service} passthrough] ${tool.description || tool.name}`,
|
|
248
102
|
dynamicArgsShape,
|
|
249
|
-
async (args) => {
|
|
103
|
+
async (args: Record<string, unknown>) => {
|
|
250
104
|
try {
|
|
251
105
|
const result = await callUpstreamTool({
|
|
252
106
|
service: catalog.service,
|
|
253
107
|
name: tool.name,
|
|
254
108
|
argumentsValue:
|
|
255
|
-
args && typeof args === "object"
|
|
109
|
+
args && typeof args === "object"
|
|
110
|
+
? args
|
|
111
|
+
: ({} as Record<string, unknown>),
|
|
256
112
|
});
|
|
257
113
|
return {
|
|
258
|
-
|
|
259
|
-
|
|
114
|
+
// SDK content is typed broadly; cast to the expected array shape.
|
|
115
|
+
content: (result.content ?? []) as Array<{
|
|
116
|
+
type: string;
|
|
117
|
+
[k: string]: unknown;
|
|
118
|
+
}>,
|
|
119
|
+
structuredContent: result.structuredContent as
|
|
120
|
+
| Record<string, unknown>
|
|
121
|
+
| undefined,
|
|
260
122
|
isError: Boolean(result.isError),
|
|
261
123
|
};
|
|
262
124
|
} catch (error) {
|
|
@@ -16,6 +16,6 @@ When adding a new tool:
|
|
|
16
16
|
|
|
17
17
|
1. Create `toolName.ts` in `../` (parent `tools/` directory)
|
|
18
18
|
2. Export name, description, schema, and handler
|
|
19
|
-
3.
|
|
20
|
-
4.
|
|
21
|
-
5.
|
|
19
|
+
3. Add a `ToolRegistryEntry` in `../registry.ts` (this auto-registers the tool)
|
|
20
|
+
4. Add tests in a `toolName.test.ts` file
|
|
21
|
+
5. Run `npm run generate:mcp-manifest` to update the build-time manifest
|
package/mcp/src/tools/index.ts
CHANGED
|
@@ -3,8 +3,22 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Central registry exporting all tool metadata and handlers.
|
|
5
5
|
* Used by server.ts to register tools with McpServer.
|
|
6
|
+
*
|
|
7
|
+
* The declarative registry (registry.ts) is the preferred way to register
|
|
8
|
+
* new tools. Individual tool exports are kept for backward compatibility
|
|
9
|
+
* and direct unit testing.
|
|
6
10
|
*/
|
|
7
11
|
|
|
12
|
+
export {
|
|
13
|
+
TOOL_REGISTRY,
|
|
14
|
+
getToolsByCategory,
|
|
15
|
+
getRegisteredToolNames,
|
|
16
|
+
buildRegistrySummary,
|
|
17
|
+
type ToolCategory,
|
|
18
|
+
type ToolRegistryEntry,
|
|
19
|
+
type ToolRuntimeContext,
|
|
20
|
+
} from "./registry.js";
|
|
21
|
+
|
|
8
22
|
export {
|
|
9
23
|
skillListCategoriesName,
|
|
10
24
|
skillListCategoriesDescription,
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
TOOL_REGISTRY,
|
|
4
|
+
getToolsByCategory,
|
|
5
|
+
getRegisteredToolNames,
|
|
6
|
+
buildRegistrySummary,
|
|
7
|
+
type ToolRegistryEntry,
|
|
8
|
+
type ToolRuntimeContext,
|
|
9
|
+
} from "./registry.js";
|
|
10
|
+
import type { VaultManifest } from "../vault/types.js";
|
|
11
|
+
|
|
12
|
+
function createTestContext(): ToolRuntimeContext {
|
|
13
|
+
const manifest: VaultManifest = {
|
|
14
|
+
categories: ["frontend", "backend"],
|
|
15
|
+
skills: [
|
|
16
|
+
{
|
|
17
|
+
id: "react-expert",
|
|
18
|
+
category: "frontend",
|
|
19
|
+
path: "/tmp/react-expert/SKILL.md",
|
|
20
|
+
fileBytes: 100,
|
|
21
|
+
},
|
|
22
|
+
],
|
|
23
|
+
fullCatalogBytes: 100,
|
|
24
|
+
fullCatalogEstimatedTokens: 25,
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
manifest,
|
|
29
|
+
charsPerToken: 4,
|
|
30
|
+
summaryMaxLength: 200,
|
|
31
|
+
defaultConfigScope: "auto",
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
describe("tool registry", () => {
|
|
36
|
+
it("contains all expected built-in tools", () => {
|
|
37
|
+
const names = getRegisteredToolNames();
|
|
38
|
+
expect(names).toContain("skill_list_categories");
|
|
39
|
+
expect(names).toContain("skill_browse_category");
|
|
40
|
+
expect(names).toContain("skill_search");
|
|
41
|
+
expect(names).toContain("skill_get");
|
|
42
|
+
expect(names).toContain("skill_budget_report");
|
|
43
|
+
expect(names).toContain("postman_get_mode");
|
|
44
|
+
expect(names).toContain("postman_set_mode");
|
|
45
|
+
expect(names).toContain("postman_get_status");
|
|
46
|
+
expect(names).toContain("stitch_get_mode");
|
|
47
|
+
expect(names).toContain("stitch_set_profile");
|
|
48
|
+
expect(names).toContain("stitch_get_status");
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("has exactly 11 built-in tools", () => {
|
|
52
|
+
expect(TOOL_REGISTRY).toHaveLength(11);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("has no duplicate tool names", () => {
|
|
56
|
+
const names = getRegisteredToolNames();
|
|
57
|
+
const unique = new Set(names);
|
|
58
|
+
expect(unique.size).toBe(names.length);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("every entry has required fields", () => {
|
|
62
|
+
for (const entry of TOOL_REGISTRY) {
|
|
63
|
+
expect(entry.name).toBeTruthy();
|
|
64
|
+
expect(entry.description).toBeTruthy();
|
|
65
|
+
expect(entry.schema).toBeTruthy();
|
|
66
|
+
expect(entry.category).toMatch(/^(skill|postman|stitch)$/);
|
|
67
|
+
expect(typeof entry.createHandler).toBe("function");
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("filters by category", () => {
|
|
72
|
+
const skillTools = getToolsByCategory("skill");
|
|
73
|
+
expect(skillTools).toHaveLength(5);
|
|
74
|
+
expect(skillTools.every((t) => t.category === "skill")).toBe(true);
|
|
75
|
+
|
|
76
|
+
const postmanTools = getToolsByCategory("postman");
|
|
77
|
+
expect(postmanTools).toHaveLength(3);
|
|
78
|
+
|
|
79
|
+
const stitchTools = getToolsByCategory("stitch");
|
|
80
|
+
expect(stitchTools).toHaveLength(3);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("creates handlers from runtime context", () => {
|
|
84
|
+
const ctx = createTestContext();
|
|
85
|
+
for (const entry of TOOL_REGISTRY) {
|
|
86
|
+
const handler = entry.createHandler(ctx);
|
|
87
|
+
expect(typeof handler).toBe("function");
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("skill_list_categories handler returns valid result from registry", async () => {
|
|
92
|
+
const ctx = createTestContext();
|
|
93
|
+
const entry = TOOL_REGISTRY.find(
|
|
94
|
+
(t) => t.name === "skill_list_categories",
|
|
95
|
+
)!;
|
|
96
|
+
const handler = entry.createHandler(ctx);
|
|
97
|
+
const result = (await handler({})) as {
|
|
98
|
+
content: Array<{ text: string }>;
|
|
99
|
+
};
|
|
100
|
+
const data = JSON.parse(result.content[0].text);
|
|
101
|
+
expect(data.totalSkills).toBe(1);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("buildRegistrySummary produces correct structure", () => {
|
|
105
|
+
const summary = buildRegistrySummary();
|
|
106
|
+
expect(summary.totalTools).toBe(11);
|
|
107
|
+
expect(summary.categories).toHaveProperty("skill");
|
|
108
|
+
expect(summary.categories).toHaveProperty("postman");
|
|
109
|
+
expect(summary.categories).toHaveProperty("stitch");
|
|
110
|
+
expect(summary.categories.skill.tools).toHaveLength(5);
|
|
111
|
+
expect(summary.categories.postman.tools).toHaveLength(3);
|
|
112
|
+
expect(summary.categories.stitch.tools).toHaveLength(3);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("each schema has a valid .shape property", () => {
|
|
116
|
+
for (const entry of TOOL_REGISTRY) {
|
|
117
|
+
expect(entry.schema.shape).toBeDefined();
|
|
118
|
+
expect(typeof entry.schema.shape).toBe("object");
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
});
|