wwise-waapi-mcp 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +19 -0
- package/README.md +221 -0
- package/README_ZH.md +221 -0
- package/config/domains.json +130 -0
- package/config/runtime.json +4 -0
- package/dist/src/core/server.js +80 -0
- package/dist/src/core/transport.js +92 -0
- package/dist/src/domains/audio/tools.js +193 -0
- package/dist/src/domains/catalog/tools.js +198 -0
- package/dist/src/domains/debug/tools.js +139 -0
- package/dist/src/domains/example/tools.js +56 -0
- package/dist/src/domains/log/tools.js +79 -0
- package/dist/src/domains/object/tools.js +499 -0
- package/dist/src/domains/plugin/tools.js +45 -0
- package/dist/src/domains/profiler/tools.js +266 -0
- package/dist/src/domains/project/tools.js +179 -0
- package/dist/src/domains/remote/tools.js +73 -0
- package/dist/src/domains/sound/tools.js +38 -0
- package/dist/src/domains/soundbank/tools.js +137 -0
- package/dist/src/domains/soundengine/tools.js +529 -0
- package/dist/src/domains/sourceControl/tools.js +191 -0
- package/dist/src/domains/switchContainer/tools.js +64 -0
- package/dist/src/domains/transport/tools.js +116 -0
- package/dist/src/domains/ui/tools.js +126 -0
- package/dist/src/domains/undo/tools.js +75 -0
- package/dist/src/index.js +95 -0
- package/dist/src/lib/errors.js +31 -0
- package/dist/src/lib/logger.js +43 -0
- package/dist/src/lib/referenceCatalog.js +167 -0
- package/dist/src/lib/response.js +88 -0
- package/dist/src/lib/runtimePaths.js +21 -0
- package/dist/src/lib/toolFactory.js +73 -0
- package/dist/src/lib/waapiClient.js +97 -0
- package/dist/src/lib/waapiSchemaResolver.js +120 -0
- package/dist/src/registry/toolRegistry.js +180 -0
- package/dist/src/registry/types.js +2 -0
- package/dist/tests/verify.js +119 -0
- package/package.json +56 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.resolveWaapiSchemaDirectory = resolveWaapiSchemaDirectory;
|
|
7
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const errors_js_1 = require("./errors.js");
|
|
10
|
+
const waapiClient_js_1 = require("./waapiClient.js");
|
|
11
|
+
const runtimePaths_js_1 = require("./runtimePaths.js");
|
|
12
|
+
const WAAPI_SCHEMA_SUBDIR = ["Authoring", "Data", "Schemas", "WAAPI"];
|
|
13
|
+
function readRuntimeConfig() {
|
|
14
|
+
const filePath = (0, runtimePaths_js_1.getConfigPath)("runtime.json");
|
|
15
|
+
if (!node_fs_1.default.existsSync(filePath)) {
|
|
16
|
+
return {};
|
|
17
|
+
}
|
|
18
|
+
try {
|
|
19
|
+
return JSON.parse(node_fs_1.default.readFileSync(filePath, "utf8"));
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return {};
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function toNonEmptyString(value) {
|
|
26
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
|
|
27
|
+
}
|
|
28
|
+
function toAbsolutePath(root) {
|
|
29
|
+
return node_path_1.default.isAbsolute(root) ? root : node_path_1.default.resolve(process.cwd(), root);
|
|
30
|
+
}
|
|
31
|
+
function getSchemaDir(wwiseRoot) {
|
|
32
|
+
return node_path_1.default.join(wwiseRoot, ...WAAPI_SCHEMA_SUBDIR);
|
|
33
|
+
}
|
|
34
|
+
function isValidSchemaDirectory(schemaDir) {
|
|
35
|
+
if (!node_fs_1.default.existsSync(schemaDir)) {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
const stat = node_fs_1.default.statSync(schemaDir);
|
|
39
|
+
if (!stat.isDirectory()) {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
const files = node_fs_1.default.readdirSync(schemaDir);
|
|
43
|
+
return files.some(file => file.endsWith(".json") && file !== "waapi_definitions.json");
|
|
44
|
+
}
|
|
45
|
+
function resolveFromRoot(source, rootValue) {
|
|
46
|
+
const root = toNonEmptyString(rootValue);
|
|
47
|
+
if (!root) {
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
const wwiseRoot = toAbsolutePath(root);
|
|
51
|
+
const schemaDir = getSchemaDir(wwiseRoot);
|
|
52
|
+
if (!isValidSchemaDirectory(schemaDir)) {
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
source,
|
|
57
|
+
wwiseRoot,
|
|
58
|
+
schemaDir
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
function getInstallDirFromGetInfoResult(value) {
|
|
62
|
+
if (!value || typeof value !== "object") {
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
const directories = value.directories;
|
|
66
|
+
if (!directories || typeof directories !== "object") {
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
return toNonEmptyString(directories.install);
|
|
70
|
+
}
|
|
71
|
+
async function resolveFromWaapi() {
|
|
72
|
+
// 先检查是否有工程打开;若没有,此调用通常会失败或返回无效项目信息。
|
|
73
|
+
const projectInfo = await (0, waapiClient_js_1.callWaapi)("ak.wwise.core.getProjectInfo", {}, {});
|
|
74
|
+
if (!projectInfo || typeof projectInfo !== "object") {
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
const projectPath = toNonEmptyString(projectInfo.path);
|
|
78
|
+
if (!projectPath) {
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
const wwiseInfo = await (0, waapiClient_js_1.callWaapi)("ak.wwise.core.getInfo", {}, {});
|
|
82
|
+
const installRoot = getInstallDirFromGetInfoResult(wwiseInfo);
|
|
83
|
+
if (!installRoot) {
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
return resolveFromRoot("waapi", installRoot);
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* 解析可用的 WAAPI schema 目录,优先级如下:
|
|
90
|
+
* 1) config/runtime.json: wwiseRoot
|
|
91
|
+
* 2) 环境变量 WWISEROOT
|
|
92
|
+
* 3) 通过 WAAPI 获取当前打开工程的 Wwise install 目录
|
|
93
|
+
*/
|
|
94
|
+
async function resolveWaapiSchemaDirectory() {
|
|
95
|
+
const checked = [];
|
|
96
|
+
const config = readRuntimeConfig();
|
|
97
|
+
const fromConfig = resolveFromRoot("config", config.wwiseRoot);
|
|
98
|
+
checked.push({ source: "config.runtime.wwiseRoot", value: toNonEmptyString(config.wwiseRoot) });
|
|
99
|
+
if (fromConfig) {
|
|
100
|
+
return fromConfig;
|
|
101
|
+
}
|
|
102
|
+
const fromEnv = resolveFromRoot("env", process.env.WWISEROOT);
|
|
103
|
+
checked.push({ source: "env.WWISEROOT", value: toNonEmptyString(process.env.WWISEROOT) });
|
|
104
|
+
if (fromEnv) {
|
|
105
|
+
return fromEnv;
|
|
106
|
+
}
|
|
107
|
+
try {
|
|
108
|
+
const fromWaapi = await resolveFromWaapi();
|
|
109
|
+
if (fromWaapi) {
|
|
110
|
+
return fromWaapi;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
// WAAPI 不可用或工程未打开时按失败路径继续。
|
|
115
|
+
}
|
|
116
|
+
throw new errors_js_1.AppError("waapi_schema_not_found", "Unable to locate WAAPI schema directory. Configure config/runtime.json (wwiseRoot), set WWISEROOT, or open a Wwise project and retry.", {
|
|
117
|
+
expectedSchemaSubdir: WAAPI_SCHEMA_SUBDIR.join(node_path_1.default.sep),
|
|
118
|
+
checked
|
|
119
|
+
});
|
|
120
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ToolRegistry = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* 工具注册表:集中管理领域元数据、可调用工具和参考目录条目。
|
|
6
|
+
* 为 MCP 服务器提供进展式揭露(列展领域→列展工具→获取 schema)所需的所有查询接口。
|
|
7
|
+
* 工具访问可通过 ToolAccessPolicy 按领域、风险等级或权限进行过滤。
|
|
8
|
+
*/
|
|
9
|
+
class ToolRegistry {
|
|
10
|
+
constructor(domainList, referenceCatalog) {
|
|
11
|
+
this.domains = new Map();
|
|
12
|
+
this.callableTools = new Map();
|
|
13
|
+
this.referenceCatalog = new Map();
|
|
14
|
+
for (const domain of domainList) {
|
|
15
|
+
this.domains.set(domain.name, domain);
|
|
16
|
+
}
|
|
17
|
+
for (const [name, entry] of referenceCatalog.entries()) {
|
|
18
|
+
this.referenceCatalog.set(name, entry);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/** 注册一组工具定义。已存在同名工具将被视为更新。 */
|
|
22
|
+
registerTools(tools) {
|
|
23
|
+
for (const tool of tools) {
|
|
24
|
+
this.callableTools.set(tool.name, tool);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/** 按名称获取领域元数据。 */
|
|
28
|
+
getDomain(name) {
|
|
29
|
+
return this.domains.get(name);
|
|
30
|
+
}
|
|
31
|
+
/** 返回符合策略过滤的所有可调用工具列表。 */
|
|
32
|
+
getCallableTools(policy) {
|
|
33
|
+
return [...this.callableTools.values()].filter(tool => this.matchesPolicy(tool, policy));
|
|
34
|
+
}
|
|
35
|
+
/** 按名称获取单个可调用工具定义。 */
|
|
36
|
+
getCallableTool(name) {
|
|
37
|
+
return this.callableTools.get(name);
|
|
38
|
+
}
|
|
39
|
+
/** 按名称获取单个参考目录条目。 */
|
|
40
|
+
getReferenceEntry(name) {
|
|
41
|
+
return this.referenceCatalog.get(name);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* 返回所有领域的摘要列表,附加工具数量统计。
|
|
45
|
+
* 用于 catalog.listDomains 进展式揭露的第一步。
|
|
46
|
+
*/
|
|
47
|
+
listDomains(policy) {
|
|
48
|
+
return [...this.domains.values()]
|
|
49
|
+
.map(domain => {
|
|
50
|
+
const callableTools = this.getCallableTools(policy).filter(tool => tool.domain === domain.name);
|
|
51
|
+
const discoveredReferenceCount = [...this.referenceCatalog.values()].filter(entry => entry.domain === domain.name).length;
|
|
52
|
+
return {
|
|
53
|
+
...domain,
|
|
54
|
+
callableToolCount: callableTools.length,
|
|
55
|
+
implementedToolCount: callableTools.filter(tool => tool.implementationStatus === "implemented").length,
|
|
56
|
+
discoveredReferenceCount
|
|
57
|
+
};
|
|
58
|
+
})
|
|
59
|
+
.sort((left, right) => left.name.localeCompare(right.name));
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* 返回指定领域下的工具摘要列表。
|
|
63
|
+
* includePlanned=true 时还会包含仅在参考目录中发现而未实现的工具(标记为 planned)。
|
|
64
|
+
* 用于 catalog.listTools 进展式揭露的第二步。
|
|
65
|
+
*/
|
|
66
|
+
listTools(domainName, policy, includePlanned = true) {
|
|
67
|
+
const byName = new Map();
|
|
68
|
+
for (const tool of this.getCallableTools(policy)) {
|
|
69
|
+
if (tool.domain !== domainName) {
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
byName.set(tool.name, this.toDiscovery(tool));
|
|
73
|
+
}
|
|
74
|
+
if (includePlanned) {
|
|
75
|
+
for (const reference of this.referenceCatalog.values()) {
|
|
76
|
+
if (reference.domain !== domainName || byName.has(reference.name)) {
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
byName.set(reference.name, {
|
|
80
|
+
name: reference.name,
|
|
81
|
+
title: reference.summary,
|
|
82
|
+
description: `Reference-discovered WAAPI interface for ${reference.name}.`,
|
|
83
|
+
domain: reference.domain,
|
|
84
|
+
risk: this.domains.get(reference.domain)?.risk ?? "medium",
|
|
85
|
+
permissions: [],
|
|
86
|
+
tags: ["reference", "planned"],
|
|
87
|
+
examples: [],
|
|
88
|
+
implementationStatus: "planned",
|
|
89
|
+
callable: false,
|
|
90
|
+
referenceFile: reference.referenceFile
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return [...byName.values()].sort((left, right) => left.name.localeCompare(right.name));
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* 获取单个工具的摘要信息。
|
|
98
|
+
* 首先查找可调用工具,如果不存在则回退到参考目录条目。
|
|
99
|
+
* 用于 catalog.getToolSchema 进展式揭露的第三步。
|
|
100
|
+
*/
|
|
101
|
+
getToolDiscovery(name) {
|
|
102
|
+
const callable = this.callableTools.get(name);
|
|
103
|
+
if (callable) {
|
|
104
|
+
return this.toDiscovery(callable);
|
|
105
|
+
}
|
|
106
|
+
const reference = this.referenceCatalog.get(name);
|
|
107
|
+
if (!reference) {
|
|
108
|
+
return undefined;
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
name: reference.name,
|
|
112
|
+
title: reference.summary,
|
|
113
|
+
description: `Reference-discovered WAAPI interface for ${reference.name}.`,
|
|
114
|
+
domain: reference.domain,
|
|
115
|
+
risk: this.domains.get(reference.domain)?.risk ?? "medium",
|
|
116
|
+
permissions: [],
|
|
117
|
+
tags: ["reference", "planned"],
|
|
118
|
+
examples: [],
|
|
119
|
+
implementationStatus: "planned",
|
|
120
|
+
callable: false,
|
|
121
|
+
referenceFile: reference.referenceFile
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* 从环境变量读取访问控制策略。
|
|
126
|
+
* - WWISE_MCP_ALLOWED_DOMAINS: 逗号分隔的允许领域列表
|
|
127
|
+
* - WWISE_MCP_ALLOWED_RISKS: 逗号分隔的允许风险等级列表
|
|
128
|
+
* - WWISE_MCP_ALLOWED_PERMISSIONS: 逗号分隔的允许权限列表
|
|
129
|
+
*/
|
|
130
|
+
static policyFromEnv(env) {
|
|
131
|
+
return {
|
|
132
|
+
allowedDomains: parseCsvSet(env.WWISE_MCP_ALLOWED_DOMAINS),
|
|
133
|
+
allowedRisks: parseCsvSet(env.WWISE_MCP_ALLOWED_RISKS),
|
|
134
|
+
allowedPermissions: parseCsvSet(env.WWISE_MCP_ALLOWED_PERMISSIONS)
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
/** 检查工具是否符合特定策略,undefined 策略表示允许一切。 */
|
|
138
|
+
matchesPolicy(tool, policy) {
|
|
139
|
+
if (!policy) {
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
if (policy.allowedDomains && !policy.allowedDomains.has(tool.domain)) {
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
if (policy.allowedRisks && !policy.allowedRisks.has(tool.risk)) {
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
if (policy.allowedPermissions && tool.permissions.length > 0) {
|
|
149
|
+
return tool.permissions.every(permission => policy.allowedPermissions?.has(permission));
|
|
150
|
+
}
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
/** 将 ToolDefinition 转换为进展式揭露用的公开字段子集。 */
|
|
154
|
+
toDiscovery(tool) {
|
|
155
|
+
return {
|
|
156
|
+
name: tool.name,
|
|
157
|
+
title: tool.title,
|
|
158
|
+
description: tool.description,
|
|
159
|
+
domain: tool.domain,
|
|
160
|
+
risk: tool.risk,
|
|
161
|
+
permissions: tool.permissions,
|
|
162
|
+
tags: tool.tags,
|
|
163
|
+
examples: tool.examples,
|
|
164
|
+
implementationStatus: tool.implementationStatus,
|
|
165
|
+
callable: tool.callable,
|
|
166
|
+
referenceFile: tool.referenceFile
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
exports.ToolRegistry = ToolRegistry;
|
|
171
|
+
/** 将逗号分隔的环境变量字符串解析为字符串集合。如果值为空则返回 undefined。 */
|
|
172
|
+
function parseCsvSet(value) {
|
|
173
|
+
if (!value) {
|
|
174
|
+
return undefined;
|
|
175
|
+
}
|
|
176
|
+
return new Set(value
|
|
177
|
+
.split(",")
|
|
178
|
+
.map(entry => entry.trim())
|
|
179
|
+
.filter(Boolean));
|
|
180
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const strict_1 = __importDefault(require("node:assert/strict"));
|
|
7
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
8
|
+
const index_js_1 = require("@modelcontextprotocol/sdk/client/index.js");
|
|
9
|
+
const stdio_js_1 = require("@modelcontextprotocol/sdk/client/stdio.js");
|
|
10
|
+
/**
|
|
11
|
+
* 将 callTool 的返回值解析为标准 JSON 响应包。
|
|
12
|
+
* 兼容两种 SDK 版本的返回格式:content 直接返回和 toolResult 嵌套返回。
|
|
13
|
+
*/
|
|
14
|
+
function parseStructuredContent(result) {
|
|
15
|
+
const normalized = (result.content ? result : result.toolResult);
|
|
16
|
+
if (!normalized?.content) {
|
|
17
|
+
throw new Error("Tool result does not contain content.");
|
|
18
|
+
}
|
|
19
|
+
const textContent = normalized.content.find(item => item.type === "text" && typeof item.text === "string");
|
|
20
|
+
if (!textContent?.text) {
|
|
21
|
+
throw new Error("Tool did not return JSON text content.");
|
|
22
|
+
}
|
|
23
|
+
return JSON.parse(textContent.text);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* 端到端验证脚本:启动 stdio MCP 服务器,演示渐进式披露工作流程:
|
|
27
|
+
* 1. 连接并检查只有4个发现工具暴露到MCP
|
|
28
|
+
* 2. 调用 catalog.listDomains 获取所有域的摘要
|
|
29
|
+
* 3. 调用 catalog.listTools 找到特定域的工具
|
|
30
|
+
* 4. 调用 catalog.getToolSchema 获取工具的详细schema
|
|
31
|
+
* 5. 调用 catalog.executeTool 动态执行任何已注册的工具
|
|
32
|
+
*/
|
|
33
|
+
async function main() {
|
|
34
|
+
const client = new index_js_1.Client({
|
|
35
|
+
name: "wwise-mcp-verify",
|
|
36
|
+
version: "0.1.0"
|
|
37
|
+
});
|
|
38
|
+
const transport = new stdio_js_1.StdioClientTransport({
|
|
39
|
+
command: process.execPath,
|
|
40
|
+
args: [node_path_1.default.join(process.cwd(), "dist", "src", "index.js")],
|
|
41
|
+
cwd: process.cwd(),
|
|
42
|
+
stderr: "pipe"
|
|
43
|
+
});
|
|
44
|
+
const stderr = transport.stderr;
|
|
45
|
+
if (stderr) {
|
|
46
|
+
stderr.on("data", chunk => {
|
|
47
|
+
process.stderr.write(chunk);
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
await client.connect(transport);
|
|
51
|
+
// Step 1: Verify only discovery tools are exposed to MCP
|
|
52
|
+
const toolsResult = await client.listTools();
|
|
53
|
+
const toolNames = toolsResult.tools.map(tool => tool.name);
|
|
54
|
+
console.log("\n=== Progressive Disclosure Verification ===");
|
|
55
|
+
console.log(`Step 1: MCP tools/list includes ${toolNames.length} discovery tools:`, toolNames);
|
|
56
|
+
strict_1.default.equal(toolNames.length, 4, "Should have exactly 4 discovery tools");
|
|
57
|
+
(0, strict_1.default)(toolNames.includes("catalog.listDomains"));
|
|
58
|
+
(0, strict_1.default)(toolNames.includes("catalog.listTools"));
|
|
59
|
+
(0, strict_1.default)(toolNames.includes("catalog.getToolSchema"));
|
|
60
|
+
(0, strict_1.default)(toolNames.includes("catalog.executeTool"));
|
|
61
|
+
// Step 2: List all domains
|
|
62
|
+
console.log("\nStep 2: Call catalog.listDomains");
|
|
63
|
+
const domainsResult = await client.callTool({
|
|
64
|
+
name: "catalog.listDomains",
|
|
65
|
+
arguments: {}
|
|
66
|
+
});
|
|
67
|
+
const parsedDomains = parseStructuredContent(domainsResult);
|
|
68
|
+
strict_1.default.equal(parsedDomains.ok, true);
|
|
69
|
+
const domains = parsedDomains.data?.domains ?? [];
|
|
70
|
+
console.log(` Found ${domains.length} domains`);
|
|
71
|
+
// Step 3: List tools in soundengine domain
|
|
72
|
+
console.log("\nStep 3: Call catalog.listTools for 'soundengine' domain");
|
|
73
|
+
const toolsListResult = await client.callTool({
|
|
74
|
+
name: "catalog.listTools",
|
|
75
|
+
arguments: {
|
|
76
|
+
domain: "soundengine",
|
|
77
|
+
includePlanned: false
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
const parsedToolsList = parseStructuredContent(toolsListResult);
|
|
81
|
+
strict_1.default.equal(parsedToolsList.ok, true);
|
|
82
|
+
const seTools = parsedToolsList.data?.tools ?? [];
|
|
83
|
+
const hasPostEvent = seTools.some((t) => t.name === "ak.soundengine.postEvent");
|
|
84
|
+
console.log(` Found ${seTools.length} soundengine tools (including postEvent: ${hasPostEvent})`);
|
|
85
|
+
(0, strict_1.default)(hasPostEvent, "ak.soundengine.postEvent should be discoverable");
|
|
86
|
+
// Step 4: Get detailed schema
|
|
87
|
+
console.log("\nStep 4: Call catalog.getToolSchema for 'ak.soundengine.postEvent'");
|
|
88
|
+
const schemaResult = await client.callTool({
|
|
89
|
+
name: "catalog.getToolSchema",
|
|
90
|
+
arguments: {
|
|
91
|
+
toolName: "ak.soundengine.postEvent"
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
const parsedSchema = parseStructuredContent(schemaResult);
|
|
95
|
+
strict_1.default.equal(parsedSchema.ok, true);
|
|
96
|
+
console.log(" Schema retrieved successfully");
|
|
97
|
+
// Step 5: Execute tool via catalog.executeTool
|
|
98
|
+
console.log("\nStep 5: Call catalog.executeTool to execute 'ak.soundengine.postEvent'");
|
|
99
|
+
const executeResult = await client.callTool({
|
|
100
|
+
name: "catalog.executeTool",
|
|
101
|
+
arguments: {
|
|
102
|
+
toolName: "ak.soundengine.postEvent",
|
|
103
|
+
arguments: {
|
|
104
|
+
event: "Play_Footstep",
|
|
105
|
+
gameObject: "Player"
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
const parsedExecute = parseStructuredContent(executeResult);
|
|
110
|
+
strict_1.default.equal(parsedExecute.ok, false);
|
|
111
|
+
(0, strict_1.default)(["waapi_unavailable", "waapi_call_failed"].includes(parsedExecute.error?.code ?? ""));
|
|
112
|
+
console.log(` Tool executed (expected failure due to no Wwise running): ${parsedExecute.error?.code}`);
|
|
113
|
+
await transport.close();
|
|
114
|
+
console.log("\n✓ Verification passed - Progressive disclosure working correctly.\n");
|
|
115
|
+
}
|
|
116
|
+
void main().catch(error => {
|
|
117
|
+
console.error("Verification failed.", error);
|
|
118
|
+
process.exit(1);
|
|
119
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "wwise-waapi-mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Progressive-disclosure MCP server scaffold for Wwise WAAPI",
|
|
5
|
+
"main": "dist/src/index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"build": "tsc -p tsconfig.json",
|
|
8
|
+
"dev": "tsx src/index.ts",
|
|
9
|
+
"start": "node dist/src/index.js",
|
|
10
|
+
"verify": "tsx tests/verify.ts",
|
|
11
|
+
"package:exe": "npm run build && pkg package.json --targets node18-win-x64 --output bin/wwise-mcp.exe"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"mcp",
|
|
15
|
+
"wwise",
|
|
16
|
+
"waapi",
|
|
17
|
+
"audio",
|
|
18
|
+
"game",
|
|
19
|
+
"typescript"
|
|
20
|
+
],
|
|
21
|
+
"author": "majorli",
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"type": "commonjs",
|
|
24
|
+
"bin": {
|
|
25
|
+
"wwise-waapi-mcp": "dist/src/index.js"
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"dist/**/*",
|
|
29
|
+
"config/**/*",
|
|
30
|
+
"README.md",
|
|
31
|
+
"README_ZH.md"
|
|
32
|
+
],
|
|
33
|
+
"pkg": {
|
|
34
|
+
"assets": [
|
|
35
|
+
"config/**/*.json"
|
|
36
|
+
],
|
|
37
|
+
"scripts": [
|
|
38
|
+
"dist/**/*.js"
|
|
39
|
+
]
|
|
40
|
+
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"@modelcontextprotocol/sdk": "^1.28.0",
|
|
43
|
+
"waapi-client": "^2017.2.1",
|
|
44
|
+
"zod": "^4.3.6"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@types/node": "^25.5.0",
|
|
48
|
+
"pkg": "^5.8.1",
|
|
49
|
+
"tsx": "^4.21.0",
|
|
50
|
+
"typescript": "^6.0.2"
|
|
51
|
+
},
|
|
52
|
+
"repository": {
|
|
53
|
+
"type": "git",
|
|
54
|
+
"url": "git+https://github.com/Li-Major/wwise-waapi-mcp.git"
|
|
55
|
+
}
|
|
56
|
+
}
|