@cubis/foundry 0.3.51 → 0.3.52
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 +26 -12
- package/bin/cubis.js +662 -80
- package/mcp/dist/index.js +108 -46
- package/mcp/src/server.ts +51 -39
- package/mcp/src/upstream/passthrough.test.ts +30 -0
- package/mcp/src/upstream/passthrough.ts +113 -17
- package/package.json +1 -1
- package/workflows/skills/postman/SKILL.md +31 -25
- package/workflows/skills/postman/references/full-mode-setup.md +36 -0
- package/workflows/skills/postman/references/troubleshooting.md +25 -0
- package/workflows/skills/postman/references/workspace-policy.md +20 -0
- package/workflows/workflows/agent-environment-setup/manifest.json +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/antigravity/workflows/database.md +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/codex/workflows/database.md +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/copilot/rules/AGENTS.md +4 -3
- package/workflows/workflows/agent-environment-setup/platforms/copilot/rules/copilot-instructions.md +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/copilot/skills/postman/SKILL.md +31 -25
- package/workflows/workflows/agent-environment-setup/platforms/copilot/skills/postman/references/full-mode-setup.md +36 -0
- package/workflows/workflows/agent-environment-setup/platforms/copilot/skills/postman/references/troubleshooting.md +25 -0
- package/workflows/workflows/agent-environment-setup/platforms/copilot/skills/postman/references/workspace-policy.md +20 -0
- package/workflows/workflows/agent-environment-setup/platforms/copilot/workflows/database.md +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/cursor/skills/postman/SKILL.md +31 -25
- package/workflows/workflows/agent-environment-setup/platforms/cursor/skills/postman/references/full-mode-setup.md +36 -0
- package/workflows/workflows/agent-environment-setup/platforms/cursor/skills/postman/references/troubleshooting.md +25 -0
- package/workflows/workflows/agent-environment-setup/platforms/cursor/skills/postman/references/workspace-policy.md +20 -0
- package/workflows/workflows/agent-environment-setup/platforms/windsurf/skills/postman/SKILL.md +31 -25
- package/workflows/workflows/agent-environment-setup/platforms/windsurf/skills/postman/references/full-mode-setup.md +36 -0
- package/workflows/workflows/agent-environment-setup/platforms/windsurf/skills/postman/references/troubleshooting.md +25 -0
- package/workflows/workflows/agent-environment-setup/platforms/windsurf/skills/postman/references/workspace-policy.md +20 -0
- package/workflows/workflows/agent-environment-setup/shared/workflows/database.md +1 -1
package/mcp/dist/index.js
CHANGED
|
@@ -1539,7 +1539,7 @@ var TOOL_REGISTRY = [
|
|
|
1539
1539
|
];
|
|
1540
1540
|
|
|
1541
1541
|
// src/upstream/passthrough.ts
|
|
1542
|
-
import { mkdir, writeFile } from "fs/promises";
|
|
1542
|
+
import { mkdir, readFile as readFile2, writeFile } from "fs/promises";
|
|
1543
1543
|
import path6 from "path";
|
|
1544
1544
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
1545
1545
|
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
@@ -1550,6 +1550,45 @@ function resolveCatalogDir(configPath) {
|
|
|
1550
1550
|
}
|
|
1551
1551
|
return path6.join(configDir, ".cbx", "mcp", "catalog");
|
|
1552
1552
|
}
|
|
1553
|
+
function buildPassthroughAliasName(service, toolName) {
|
|
1554
|
+
const normalizedTool = String(toolName || "").trim().replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^A-Za-z0-9]+/g, "_").replace(/^_+|_+$/g, "").replace(/_+/g, "_").toLowerCase();
|
|
1555
|
+
if (!normalizedTool) return `${service}_tool`;
|
|
1556
|
+
return `${service}_${normalizedTool}`;
|
|
1557
|
+
}
|
|
1558
|
+
function buildUpstreamToolInfo(service, tool) {
|
|
1559
|
+
const name = typeof tool?.name === "string" ? tool.name.trim() : "";
|
|
1560
|
+
if (!name) return null;
|
|
1561
|
+
const namespacedName = `${service}.${name}`;
|
|
1562
|
+
const aliasNames = [buildPassthroughAliasName(service, name)];
|
|
1563
|
+
return {
|
|
1564
|
+
name,
|
|
1565
|
+
namespacedName,
|
|
1566
|
+
aliasNames,
|
|
1567
|
+
description: typeof tool?.description === "string" ? tool.description : void 0,
|
|
1568
|
+
inputSchema: tool?.inputSchema ?? void 0,
|
|
1569
|
+
outputSchema: tool?.outputSchema ?? void 0
|
|
1570
|
+
};
|
|
1571
|
+
}
|
|
1572
|
+
async function loadCachedCatalogTools({
|
|
1573
|
+
service,
|
|
1574
|
+
scope,
|
|
1575
|
+
configPath
|
|
1576
|
+
}) {
|
|
1577
|
+
if (!configPath) return [];
|
|
1578
|
+
const catalogDir = resolveCatalogDir(configPath);
|
|
1579
|
+
const catalogPath = path6.join(catalogDir, `${service}.json`);
|
|
1580
|
+
try {
|
|
1581
|
+
const raw = await readFile2(catalogPath, "utf8");
|
|
1582
|
+
const parsed = JSON.parse(raw);
|
|
1583
|
+
if (scope && typeof parsed.scope === "string" && parsed.scope.trim() && parsed.scope !== scope) {
|
|
1584
|
+
return [];
|
|
1585
|
+
}
|
|
1586
|
+
const tools = Array.isArray(parsed.tools) ? parsed.tools : [];
|
|
1587
|
+
return tools.map((tool) => buildUpstreamToolInfo(service, tool)).filter((tool) => Boolean(tool));
|
|
1588
|
+
} catch {
|
|
1589
|
+
return [];
|
|
1590
|
+
}
|
|
1591
|
+
}
|
|
1553
1592
|
function getServiceAuth(config, service) {
|
|
1554
1593
|
if (service === "postman") {
|
|
1555
1594
|
const state2 = parsePostmanState(config);
|
|
@@ -1646,8 +1685,8 @@ async function persistCatalog(catalog) {
|
|
|
1646
1685
|
await writeFile(catalogPath, `${JSON.stringify(payload, null, 2)}
|
|
1647
1686
|
`, "utf8");
|
|
1648
1687
|
}
|
|
1649
|
-
async function discoverUpstreamCatalogs() {
|
|
1650
|
-
const effective = readEffectiveConfig(
|
|
1688
|
+
async function discoverUpstreamCatalogs(scope = "auto") {
|
|
1689
|
+
const effective = readEffectiveConfig(scope);
|
|
1651
1690
|
if (!effective) {
|
|
1652
1691
|
const missing = {
|
|
1653
1692
|
service: "postman",
|
|
@@ -1686,6 +1725,16 @@ async function discoverUpstreamCatalogs() {
|
|
|
1686
1725
|
discoveryError: auth.error
|
|
1687
1726
|
};
|
|
1688
1727
|
if (!auth.configured || !auth.mcpUrl || auth.error) {
|
|
1728
|
+
const cachedTools = await loadCachedCatalogTools({
|
|
1729
|
+
service,
|
|
1730
|
+
scope: baseInfo.scope,
|
|
1731
|
+
configPath: baseInfo.configPath
|
|
1732
|
+
});
|
|
1733
|
+
if (cachedTools.length > 0) {
|
|
1734
|
+
catalog.tools = cachedTools;
|
|
1735
|
+
catalog.toolCount = cachedTools.length;
|
|
1736
|
+
catalog.discoveryError = auth.error ? `${auth.error} (using cached tool catalog)` : "Upstream not configured; using cached tool catalog";
|
|
1737
|
+
}
|
|
1689
1738
|
await persistCatalog(catalog);
|
|
1690
1739
|
return catalog;
|
|
1691
1740
|
}
|
|
@@ -1696,20 +1745,20 @@ async function discoverUpstreamCatalogs() {
|
|
|
1696
1745
|
fn: async (client) => client.listTools()
|
|
1697
1746
|
});
|
|
1698
1747
|
const rawTools = Array.isArray(listed.tools) ? listed.tools : [];
|
|
1699
|
-
catalog.tools = rawTools.map((tool) => {
|
|
1700
|
-
const name = typeof tool?.name === "string" ? tool.name.trim() : "";
|
|
1701
|
-
if (!name) return null;
|
|
1702
|
-
return {
|
|
1703
|
-
name,
|
|
1704
|
-
namespacedName: `${service}.${name}`,
|
|
1705
|
-
description: typeof tool?.description === "string" ? tool.description : void 0,
|
|
1706
|
-
inputSchema: tool?.inputSchema ?? void 0,
|
|
1707
|
-
outputSchema: tool?.outputSchema ?? void 0
|
|
1708
|
-
};
|
|
1709
|
-
}).filter((tool) => Boolean(tool));
|
|
1748
|
+
catalog.tools = rawTools.map((tool) => buildUpstreamToolInfo(service, tool || {})).filter((tool) => Boolean(tool));
|
|
1710
1749
|
catalog.toolCount = catalog.tools.length;
|
|
1711
1750
|
} catch (error) {
|
|
1712
1751
|
catalog.discoveryError = `Tool discovery failed: ${String(error)}`;
|
|
1752
|
+
const cachedTools = await loadCachedCatalogTools({
|
|
1753
|
+
service,
|
|
1754
|
+
scope: baseInfo.scope,
|
|
1755
|
+
configPath: baseInfo.configPath
|
|
1756
|
+
});
|
|
1757
|
+
if (cachedTools.length > 0) {
|
|
1758
|
+
catalog.tools = cachedTools;
|
|
1759
|
+
catalog.toolCount = cachedTools.length;
|
|
1760
|
+
catalog.discoveryError = `${catalog.discoveryError} (using cached tool catalog)`;
|
|
1761
|
+
}
|
|
1713
1762
|
}
|
|
1714
1763
|
await persistCatalog(catalog);
|
|
1715
1764
|
return catalog;
|
|
@@ -1722,9 +1771,10 @@ async function discoverUpstreamCatalogs() {
|
|
|
1722
1771
|
async function callUpstreamTool({
|
|
1723
1772
|
service,
|
|
1724
1773
|
name,
|
|
1725
|
-
argumentsValue
|
|
1774
|
+
argumentsValue,
|
|
1775
|
+
scope = "auto"
|
|
1726
1776
|
}) {
|
|
1727
|
-
const effective = readEffectiveConfig(
|
|
1777
|
+
const effective = readEffectiveConfig(scope);
|
|
1728
1778
|
if (!effective) {
|
|
1729
1779
|
throw new Error("cbx_config.json not found");
|
|
1730
1780
|
}
|
|
@@ -1799,40 +1849,52 @@ async function createServer({
|
|
|
1799
1849
|
logger.debug(
|
|
1800
1850
|
`Registered ${TOOL_REGISTRY.length} built-in tools from registry`
|
|
1801
1851
|
);
|
|
1802
|
-
const upstreamCatalogs = await discoverUpstreamCatalogs();
|
|
1852
|
+
const upstreamCatalogs = await discoverUpstreamCatalogs(defaultConfigScope);
|
|
1803
1853
|
const dynamicSchema = z13.object({}).passthrough();
|
|
1854
|
+
const registeredDynamicToolNames = /* @__PURE__ */ new Set();
|
|
1804
1855
|
for (const catalog of [upstreamCatalogs.postman, upstreamCatalogs.stitch]) {
|
|
1805
1856
|
for (const tool of catalog.tools) {
|
|
1806
|
-
const
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
{
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
async (args) => {
|
|
1815
|
-
try {
|
|
1816
|
-
const result = await callUpstreamTool({
|
|
1817
|
-
service: catalog.service,
|
|
1818
|
-
name: tool.name,
|
|
1819
|
-
argumentsValue: args && typeof args === "object" ? args : {}
|
|
1820
|
-
});
|
|
1821
|
-
return {
|
|
1822
|
-
// SDK content is typed broadly; cast to the expected array shape.
|
|
1823
|
-
content: result.content ?? [],
|
|
1824
|
-
structuredContent: result.structuredContent,
|
|
1825
|
-
isError: Boolean(result.isError)
|
|
1826
|
-
};
|
|
1827
|
-
} catch (error) {
|
|
1828
|
-
return toolCallErrorResult({
|
|
1829
|
-
service: catalog.service,
|
|
1830
|
-
namespacedName: namespaced,
|
|
1831
|
-
error
|
|
1832
|
-
});
|
|
1833
|
-
}
|
|
1857
|
+
const registrationNames = [tool.namespacedName, ...tool.aliasNames || []];
|
|
1858
|
+
const uniqueRegistrationNames = [...new Set(registrationNames)];
|
|
1859
|
+
for (const registrationName of uniqueRegistrationNames) {
|
|
1860
|
+
if (registeredDynamicToolNames.has(registrationName)) {
|
|
1861
|
+
logger.warn(
|
|
1862
|
+
`Skipping duplicate dynamic tool registration name '${registrationName}' from ${catalog.service}.${tool.name}`
|
|
1863
|
+
);
|
|
1864
|
+
continue;
|
|
1834
1865
|
}
|
|
1835
|
-
|
|
1866
|
+
registeredDynamicToolNames.add(registrationName);
|
|
1867
|
+
server.registerTool(
|
|
1868
|
+
registrationName,
|
|
1869
|
+
{
|
|
1870
|
+
description: `[${catalog.service} passthrough] ${tool.description || tool.name}`,
|
|
1871
|
+
inputSchema: dynamicSchema,
|
|
1872
|
+
annotations: {}
|
|
1873
|
+
},
|
|
1874
|
+
async (args) => {
|
|
1875
|
+
try {
|
|
1876
|
+
const result = await callUpstreamTool({
|
|
1877
|
+
service: catalog.service,
|
|
1878
|
+
name: tool.name,
|
|
1879
|
+
argumentsValue: args && typeof args === "object" ? args : {},
|
|
1880
|
+
scope: defaultConfigScope
|
|
1881
|
+
});
|
|
1882
|
+
return {
|
|
1883
|
+
// SDK content is typed broadly; cast to the expected array shape.
|
|
1884
|
+
content: result.content ?? [],
|
|
1885
|
+
structuredContent: result.structuredContent,
|
|
1886
|
+
isError: Boolean(result.isError)
|
|
1887
|
+
};
|
|
1888
|
+
} catch (error) {
|
|
1889
|
+
return toolCallErrorResult({
|
|
1890
|
+
service: catalog.service,
|
|
1891
|
+
namespacedName: registrationName,
|
|
1892
|
+
error
|
|
1893
|
+
});
|
|
1894
|
+
}
|
|
1895
|
+
}
|
|
1896
|
+
);
|
|
1897
|
+
}
|
|
1836
1898
|
}
|
|
1837
1899
|
}
|
|
1838
1900
|
return server;
|
package/mcp/src/server.ts
CHANGED
|
@@ -92,49 +92,61 @@ export async function createServer({
|
|
|
92
92
|
);
|
|
93
93
|
|
|
94
94
|
// ─── Dynamic upstream passthrough tools ────────────────────
|
|
95
|
-
const upstreamCatalogs = await discoverUpstreamCatalogs();
|
|
95
|
+
const upstreamCatalogs = await discoverUpstreamCatalogs(defaultConfigScope);
|
|
96
96
|
const dynamicSchema = z.object({}).passthrough();
|
|
97
|
+
const registeredDynamicToolNames = new Set<string>();
|
|
97
98
|
|
|
98
99
|
for (const catalog of [upstreamCatalogs.postman, upstreamCatalogs.stitch]) {
|
|
99
100
|
for (const tool of catalog.tools) {
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
{
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
101
|
+
const registrationNames = [tool.namespacedName, ...(tool.aliasNames || [])];
|
|
102
|
+
const uniqueRegistrationNames = [...new Set(registrationNames)];
|
|
103
|
+
for (const registrationName of uniqueRegistrationNames) {
|
|
104
|
+
if (registeredDynamicToolNames.has(registrationName)) {
|
|
105
|
+
logger.warn(
|
|
106
|
+
`Skipping duplicate dynamic tool registration name '${registrationName}' from ${catalog.service}.${tool.name}`,
|
|
107
|
+
);
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
registeredDynamicToolNames.add(registrationName);
|
|
111
|
+
server.registerTool(
|
|
112
|
+
registrationName,
|
|
113
|
+
{
|
|
114
|
+
description: `[${catalog.service} passthrough] ${tool.description || tool.name}`,
|
|
115
|
+
inputSchema: dynamicSchema,
|
|
116
|
+
annotations: {},
|
|
117
|
+
},
|
|
118
|
+
async (args: Record<string, unknown>) => {
|
|
119
|
+
try {
|
|
120
|
+
const result = await callUpstreamTool({
|
|
121
|
+
service: catalog.service,
|
|
122
|
+
name: tool.name,
|
|
123
|
+
argumentsValue:
|
|
124
|
+
args && typeof args === "object"
|
|
125
|
+
? args
|
|
126
|
+
: ({} as Record<string, unknown>),
|
|
127
|
+
scope: defaultConfigScope,
|
|
128
|
+
});
|
|
129
|
+
return {
|
|
130
|
+
// SDK content is typed broadly; cast to the expected array shape.
|
|
131
|
+
content: (result.content ?? []) as Array<{
|
|
132
|
+
type: string;
|
|
133
|
+
[k: string]: unknown;
|
|
134
|
+
}>,
|
|
135
|
+
structuredContent: result.structuredContent as
|
|
136
|
+
| Record<string, unknown>
|
|
137
|
+
| undefined,
|
|
138
|
+
isError: Boolean(result.isError),
|
|
139
|
+
};
|
|
140
|
+
} catch (error) {
|
|
141
|
+
return toolCallErrorResult({
|
|
142
|
+
service: catalog.service,
|
|
143
|
+
namespacedName: registrationName,
|
|
144
|
+
error,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
);
|
|
149
|
+
}
|
|
138
150
|
}
|
|
139
151
|
}
|
|
140
152
|
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
buildPassthroughAliasName,
|
|
4
|
+
buildUpstreamToolInfo,
|
|
5
|
+
} from "./passthrough.js";
|
|
6
|
+
|
|
7
|
+
describe("upstream passthrough aliasing", () => {
|
|
8
|
+
it("builds deterministic underscore aliases for postman tools", () => {
|
|
9
|
+
expect(buildPassthroughAliasName("postman", "runCollection")).toBe(
|
|
10
|
+
"postman_run_collection",
|
|
11
|
+
);
|
|
12
|
+
expect(buildPassthroughAliasName("postman", "get-workspaces")).toBe(
|
|
13
|
+
"postman_get_workspaces",
|
|
14
|
+
);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("maps upstream tools to namespaced + alias registrations", () => {
|
|
18
|
+
const info = buildUpstreamToolInfo("stitch", {
|
|
19
|
+
name: "list_tools",
|
|
20
|
+
description: "List available tools",
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
expect(info).toMatchObject({
|
|
24
|
+
name: "list_tools",
|
|
25
|
+
namespacedName: "stitch.list_tools",
|
|
26
|
+
aliasNames: ["stitch_list_tools"],
|
|
27
|
+
description: "List available tools",
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
});
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Cubis Foundry MCP Server – upstream Postman/Stitch passthrough support.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { mkdir, writeFile } from "node:fs/promises";
|
|
5
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
8
8
|
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
@@ -11,13 +11,14 @@ import {
|
|
|
11
11
|
parseStitchState,
|
|
12
12
|
readEffectiveConfig,
|
|
13
13
|
} from "../cbxConfig/index.js";
|
|
14
|
-
import type { CbxConfig } from "../cbxConfig/types.js";
|
|
14
|
+
import type { CbxConfig, ConfigScope } from "../cbxConfig/types.js";
|
|
15
15
|
|
|
16
16
|
type ServiceId = "postman" | "stitch";
|
|
17
17
|
|
|
18
18
|
export interface UpstreamToolInfo {
|
|
19
19
|
name: string;
|
|
20
20
|
namespacedName: string;
|
|
21
|
+
aliasNames: string[];
|
|
21
22
|
description?: string;
|
|
22
23
|
inputSchema?: unknown;
|
|
23
24
|
outputSchema?: unknown;
|
|
@@ -44,6 +45,86 @@ function resolveCatalogDir(configPath: string): string {
|
|
|
44
45
|
return path.join(configDir, ".cbx", "mcp", "catalog");
|
|
45
46
|
}
|
|
46
47
|
|
|
48
|
+
export function buildPassthroughAliasName(
|
|
49
|
+
service: ServiceId,
|
|
50
|
+
toolName: string,
|
|
51
|
+
): string {
|
|
52
|
+
const normalizedTool = String(toolName || "")
|
|
53
|
+
.trim()
|
|
54
|
+
.replace(/([a-z0-9])([A-Z])/g, "$1_$2")
|
|
55
|
+
.replace(/[^A-Za-z0-9]+/g, "_")
|
|
56
|
+
.replace(/^_+|_+$/g, "")
|
|
57
|
+
.replace(/_+/g, "_")
|
|
58
|
+
.toLowerCase();
|
|
59
|
+
if (!normalizedTool) return `${service}_tool`;
|
|
60
|
+
return `${service}_${normalizedTool}`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function buildUpstreamToolInfo(
|
|
64
|
+
service: ServiceId,
|
|
65
|
+
tool: {
|
|
66
|
+
name?: unknown;
|
|
67
|
+
description?: unknown;
|
|
68
|
+
inputSchema?: unknown;
|
|
69
|
+
outputSchema?: unknown;
|
|
70
|
+
},
|
|
71
|
+
): UpstreamToolInfo | null {
|
|
72
|
+
const name = typeof tool?.name === "string" ? tool.name.trim() : "";
|
|
73
|
+
if (!name) return null;
|
|
74
|
+
const namespacedName = `${service}.${name}`;
|
|
75
|
+
const aliasNames = [buildPassthroughAliasName(service, name)];
|
|
76
|
+
return {
|
|
77
|
+
name,
|
|
78
|
+
namespacedName,
|
|
79
|
+
aliasNames,
|
|
80
|
+
description:
|
|
81
|
+
typeof tool?.description === "string" ? tool.description : undefined,
|
|
82
|
+
inputSchema: tool?.inputSchema ?? undefined,
|
|
83
|
+
outputSchema: tool?.outputSchema ?? undefined,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async function loadCachedCatalogTools({
|
|
88
|
+
service,
|
|
89
|
+
scope,
|
|
90
|
+
configPath,
|
|
91
|
+
}: {
|
|
92
|
+
service: ServiceId;
|
|
93
|
+
scope: ConfigScope | null;
|
|
94
|
+
configPath: string | null;
|
|
95
|
+
}): Promise<UpstreamToolInfo[]> {
|
|
96
|
+
if (!configPath) return [];
|
|
97
|
+
const catalogDir = resolveCatalogDir(configPath);
|
|
98
|
+
const catalogPath = path.join(catalogDir, `${service}.json`);
|
|
99
|
+
try {
|
|
100
|
+
const raw = await readFile(catalogPath, "utf8");
|
|
101
|
+
const parsed = JSON.parse(raw) as {
|
|
102
|
+
scope?: string;
|
|
103
|
+
tools?: Array<{
|
|
104
|
+
name?: unknown;
|
|
105
|
+
description?: unknown;
|
|
106
|
+
inputSchema?: unknown;
|
|
107
|
+
outputSchema?: unknown;
|
|
108
|
+
}>;
|
|
109
|
+
};
|
|
110
|
+
if (
|
|
111
|
+
scope &&
|
|
112
|
+
typeof parsed.scope === "string" &&
|
|
113
|
+
parsed.scope.trim() &&
|
|
114
|
+
parsed.scope !== scope
|
|
115
|
+
) {
|
|
116
|
+
// Cross-scope catalogs can be stale for the active config.
|
|
117
|
+
return [];
|
|
118
|
+
}
|
|
119
|
+
const tools = Array.isArray(parsed.tools) ? parsed.tools : [];
|
|
120
|
+
return tools
|
|
121
|
+
.map((tool) => buildUpstreamToolInfo(service, tool))
|
|
122
|
+
.filter((tool): tool is UpstreamToolInfo => Boolean(tool));
|
|
123
|
+
} catch {
|
|
124
|
+
return [];
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
47
128
|
function getServiceAuth(config: CbxConfig, service: ServiceId): {
|
|
48
129
|
mcpUrl: string | null;
|
|
49
130
|
activeProfileName: string | null;
|
|
@@ -154,11 +235,13 @@ async function persistCatalog(catalog: UpstreamCatalog): Promise<void> {
|
|
|
154
235
|
await writeFile(catalogPath, `${JSON.stringify(payload, null, 2)}\n`, "utf8");
|
|
155
236
|
}
|
|
156
237
|
|
|
157
|
-
export async function discoverUpstreamCatalogs(
|
|
238
|
+
export async function discoverUpstreamCatalogs(
|
|
239
|
+
scope: ConfigScope | "auto" = "auto",
|
|
240
|
+
): Promise<{
|
|
158
241
|
postman: UpstreamCatalog;
|
|
159
242
|
stitch: UpstreamCatalog;
|
|
160
243
|
}> {
|
|
161
|
-
const effective = readEffectiveConfig(
|
|
244
|
+
const effective = readEffectiveConfig(scope);
|
|
162
245
|
if (!effective) {
|
|
163
246
|
const missing: UpstreamCatalog = {
|
|
164
247
|
service: "postman",
|
|
@@ -200,6 +283,18 @@ export async function discoverUpstreamCatalogs(): Promise<{
|
|
|
200
283
|
};
|
|
201
284
|
|
|
202
285
|
if (!auth.configured || !auth.mcpUrl || auth.error) {
|
|
286
|
+
const cachedTools = await loadCachedCatalogTools({
|
|
287
|
+
service,
|
|
288
|
+
scope: baseInfo.scope,
|
|
289
|
+
configPath: baseInfo.configPath,
|
|
290
|
+
});
|
|
291
|
+
if (cachedTools.length > 0) {
|
|
292
|
+
catalog.tools = cachedTools;
|
|
293
|
+
catalog.toolCount = cachedTools.length;
|
|
294
|
+
catalog.discoveryError = auth.error
|
|
295
|
+
? `${auth.error} (using cached tool catalog)`
|
|
296
|
+
: "Upstream not configured; using cached tool catalog";
|
|
297
|
+
}
|
|
203
298
|
await persistCatalog(catalog);
|
|
204
299
|
return catalog;
|
|
205
300
|
}
|
|
@@ -212,22 +307,21 @@ export async function discoverUpstreamCatalogs(): Promise<{
|
|
|
212
307
|
});
|
|
213
308
|
const rawTools = Array.isArray(listed.tools) ? listed.tools : [];
|
|
214
309
|
catalog.tools = rawTools
|
|
215
|
-
.map((tool) => {
|
|
216
|
-
const name = typeof tool?.name === "string" ? tool.name.trim() : "";
|
|
217
|
-
if (!name) return null;
|
|
218
|
-
return {
|
|
219
|
-
name,
|
|
220
|
-
namespacedName: `${service}.${name}`,
|
|
221
|
-
description:
|
|
222
|
-
typeof tool?.description === "string" ? tool.description : undefined,
|
|
223
|
-
inputSchema: tool?.inputSchema ?? undefined,
|
|
224
|
-
outputSchema: tool?.outputSchema ?? undefined,
|
|
225
|
-
} satisfies UpstreamToolInfo;
|
|
226
|
-
})
|
|
310
|
+
.map((tool) => buildUpstreamToolInfo(service, tool || {}))
|
|
227
311
|
.filter((tool): tool is UpstreamToolInfo => Boolean(tool));
|
|
228
312
|
catalog.toolCount = catalog.tools.length;
|
|
229
313
|
} catch (error) {
|
|
230
314
|
catalog.discoveryError = `Tool discovery failed: ${String(error)}`;
|
|
315
|
+
const cachedTools = await loadCachedCatalogTools({
|
|
316
|
+
service,
|
|
317
|
+
scope: baseInfo.scope,
|
|
318
|
+
configPath: baseInfo.configPath,
|
|
319
|
+
});
|
|
320
|
+
if (cachedTools.length > 0) {
|
|
321
|
+
catalog.tools = cachedTools;
|
|
322
|
+
catalog.toolCount = cachedTools.length;
|
|
323
|
+
catalog.discoveryError = `${catalog.discoveryError} (using cached tool catalog)`;
|
|
324
|
+
}
|
|
231
325
|
}
|
|
232
326
|
|
|
233
327
|
await persistCatalog(catalog);
|
|
@@ -244,12 +338,14 @@ export async function callUpstreamTool({
|
|
|
244
338
|
service,
|
|
245
339
|
name,
|
|
246
340
|
argumentsValue,
|
|
341
|
+
scope = "auto",
|
|
247
342
|
}: {
|
|
248
343
|
service: ServiceId;
|
|
249
344
|
name: string;
|
|
250
345
|
argumentsValue: Record<string, unknown>;
|
|
346
|
+
scope?: ConfigScope | "auto";
|
|
251
347
|
}) {
|
|
252
|
-
const effective = readEffectiveConfig(
|
|
348
|
+
const effective = readEffectiveConfig(scope);
|
|
253
349
|
if (!effective) {
|
|
254
350
|
throw new Error("cbx_config.json not found");
|
|
255
351
|
}
|
package/package.json
CHANGED
|
@@ -1,41 +1,53 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: postman
|
|
3
|
-
description:
|
|
3
|
+
description: Automate API testing and collection management with Postman MCP using full-mode defaults and workspace-aware execution.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# Postman MCP
|
|
6
|
+
# Postman MCP Skill
|
|
7
7
|
|
|
8
|
-
Use this skill when you need
|
|
8
|
+
Use this skill when you need Postman workspace, collection, environment, or run operations through MCP.
|
|
9
|
+
|
|
10
|
+
References:
|
|
11
|
+
- [Full-Mode Setup](./references/full-mode-setup.md)
|
|
12
|
+
- [Workspace Policy](./references/workspace-policy.md)
|
|
13
|
+
- [Troubleshooting](./references/troubleshooting.md)
|
|
9
14
|
|
|
10
15
|
## MCP-First Rule
|
|
11
16
|
|
|
12
|
-
- Prefer Postman MCP tools
|
|
17
|
+
- Prefer Postman MCP tools for all Postman operations.
|
|
18
|
+
- Accept both dynamic naming styles from clients:
|
|
19
|
+
- dotted: `postman.<tool>`
|
|
20
|
+
- alias: `postman_<tool>`
|
|
13
21
|
- Do not use Newman/Postman CLI fallback unless the user explicitly asks for fallback.
|
|
14
|
-
- If required Postman MCP tools are unavailable,
|
|
22
|
+
- If required Postman MCP tools are unavailable, report discovery/remediation steps first.
|
|
15
23
|
|
|
16
|
-
##
|
|
24
|
+
## Setup Baseline
|
|
17
25
|
|
|
18
|
-
|
|
19
|
-
- `
|
|
26
|
+
1. Install with Postman enabled and explicit full mode:
|
|
27
|
+
- `cbx workflows install --platform <codex|antigravity|copilot> --scope global --bundle agent-environment-setup --postman --postman-mode full --mcp-runtime docker --mcp-fallback local --mcp-tool-sync --yes`
|
|
28
|
+
2. Persist env aliases once (no per-session re-export):
|
|
29
|
+
- `cbx workflows config keys persist-env --service postman --scope global`
|
|
30
|
+
3. Verify mode/config:
|
|
31
|
+
- `cbx workflows config --scope global --show`
|
|
32
|
+
- `cbx mcp tools sync --service postman --scope global`
|
|
33
|
+
- `cbx mcp tools list --service postman --scope global`
|
|
20
34
|
|
|
21
|
-
## Preflight
|
|
35
|
+
## Preflight
|
|
22
36
|
|
|
23
37
|
1. Read Postman status first:
|
|
24
|
-
- Call `postman_get_status
|
|
25
|
-
2.
|
|
26
|
-
- If not
|
|
27
|
-
- If mode is not `full`, call `postman_set_mode` with `mode: full`.
|
|
38
|
+
- Call `postman_get_status`.
|
|
39
|
+
2. Ensure mode is `full`:
|
|
40
|
+
- If not, call `postman_set_mode` with `mode: full`.
|
|
28
41
|
3. Discover upstream tools:
|
|
29
|
-
-
|
|
30
|
-
- Confirm required tool names before proceeding (for example `getWorkspaces`, `getCollections`, `runCollection`).
|
|
42
|
+
- Confirm required tools exist before execution (for example workspaces/collections/runs).
|
|
31
43
|
|
|
32
|
-
## Default Workspace
|
|
44
|
+
## Default Workspace Policy
|
|
33
45
|
|
|
34
46
|
Resolve workspace in this order:
|
|
35
47
|
|
|
36
48
|
1. User-provided workspace ID.
|
|
37
49
|
2. `postman_get_status.defaultWorkspaceId`.
|
|
38
|
-
3. Auto-detect from
|
|
50
|
+
3. Auto-detect from workspace listing:
|
|
39
51
|
- If exactly one workspace exists, use it and state that choice.
|
|
40
52
|
4. If multiple workspaces and no default:
|
|
41
53
|
- Ask user to choose one.
|
|
@@ -46,17 +58,11 @@ When a Postman tool requires a workspace argument, always pass the resolved work
|
|
|
46
58
|
|
|
47
59
|
## Common Workflows
|
|
48
60
|
|
|
49
|
-
### List/Inspect
|
|
50
|
-
|
|
51
|
-
- `postman.getWorkspaces`
|
|
52
|
-
- `postman.getCollections` (with resolved workspace ID)
|
|
53
|
-
- `postman.getEnvironments` (with resolved workspace ID)
|
|
54
|
-
|
|
55
61
|
### Collection Run
|
|
56
62
|
|
|
57
63
|
1. Resolve workspace ID (policy above).
|
|
58
64
|
2. Resolve `collectionId` and optional `environmentId`.
|
|
59
|
-
3. Call `postman.runCollection
|
|
65
|
+
3. Call Postman run tool (`postman.runCollection` or alias equivalent).
|
|
60
66
|
4. Return a concise run summary:
|
|
61
67
|
- total requests
|
|
62
68
|
- passed/failed tests
|
|
@@ -65,7 +71,7 @@ When a Postman tool requires a workspace argument, always pass the resolved work
|
|
|
65
71
|
|
|
66
72
|
## Failure Handling
|
|
67
73
|
|
|
68
|
-
If dynamic Postman tools are missing
|
|
74
|
+
If dynamic Postman tools are missing:
|
|
69
75
|
|
|
70
76
|
1. Verify env alias expected by config is set.
|
|
71
77
|
2. Resync catalog:
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Postman Full-Mode Setup
|
|
2
|
+
|
|
3
|
+
Use CLI mode flags instead of manual `jq` edits.
|
|
4
|
+
|
|
5
|
+
## Install/Upgrade with Full Mode
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
cbx workflows install \
|
|
9
|
+
--platform codex \
|
|
10
|
+
--scope global \
|
|
11
|
+
--bundle agent-environment-setup \
|
|
12
|
+
--postman \
|
|
13
|
+
--postman-mode full \
|
|
14
|
+
--mcp-runtime docker \
|
|
15
|
+
--mcp-fallback local \
|
|
16
|
+
--mcp-tool-sync \
|
|
17
|
+
--overwrite \
|
|
18
|
+
--yes
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Change Mode Later
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
cbx workflows config --scope global --platform codex --postman-mode full
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
This updates:
|
|
28
|
+
- `cbx_config.json` Postman URL
|
|
29
|
+
- managed Postman MCP definition under `.cbx/mcp/<platform>/postman.json`
|
|
30
|
+
- platform MCP runtime target patch (when platform is resolvable)
|
|
31
|
+
|
|
32
|
+
## Persist Env Aliases
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
cbx workflows config keys persist-env --service postman --scope global
|
|
36
|
+
```
|