opencodekit 0.9.2 → 0.10.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/dist/index.js +1 -1
- package/dist/template/.opencode/AGENTS.md +116 -47
- package/dist/template/.opencode/agent/build.md +16 -48
- package/dist/template/.opencode/agent/explore.md +13 -34
- package/dist/template/.opencode/agent/planner.md +38 -9
- package/dist/template/.opencode/agent/review.md +2 -23
- package/dist/template/.opencode/agent/rush.md +24 -65
- package/dist/template/.opencode/agent/scout.md +5 -21
- package/dist/template/.opencode/agent/vision.md +0 -14
- package/dist/template/.opencode/plugin/README.md +110 -98
- package/dist/template/.opencode/plugin/compactor.ts +95 -171
- package/dist/template/.opencode/plugin/enforcer.ts +177 -127
- package/dist/template/.opencode/plugin/injector.ts +150 -0
- package/dist/template/.opencode/plugin/lib/notify.ts +86 -0
- package/dist/template/.opencode/plugin/notification.ts +57 -123
- package/dist/template/.opencode/plugin/truncator.ts +60 -166
- package/dist/template/.opencode/skill/mqdh/SKILL.md +161 -0
- package/dist/template/.opencode/skill/v0/SKILL.md +154 -0
- package/package.json +1 -1
|
@@ -1,130 +1,64 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* OpenCode Notification Plugin
|
|
3
3
|
* Sends native notifications when sessions complete
|
|
4
|
-
*
|
|
5
|
-
* Cross-platform support:
|
|
6
|
-
* - macOS: Built-in (osascript)
|
|
7
|
-
* - Linux: notify-send (install: sudo apt install libnotify-bin)
|
|
8
|
-
* - WSL: notify-send + dunst (requires setup)
|
|
9
4
|
*/
|
|
10
5
|
|
|
11
6
|
import type { Plugin } from "@opencode-ai/plugin";
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
if (event.type === "session.idle") {
|
|
70
|
-
const sessionId = event.properties?.sessionID;
|
|
71
|
-
|
|
72
|
-
if (!sessionId || notifiedSessions.has(sessionId)) {
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
75
|
-
notifiedSessions.add(sessionId);
|
|
76
|
-
|
|
77
|
-
setTimeout(async () => {
|
|
78
|
-
try {
|
|
79
|
-
let summary = "Session completed";
|
|
80
|
-
|
|
81
|
-
const messagesResponse = await client.session.messages({
|
|
82
|
-
path: { id: sessionId },
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
if (messagesResponse.data && Array.isArray(messagesResponse.data)) {
|
|
86
|
-
const lastUserMessage = messagesResponse.data
|
|
87
|
-
.filter((m) => m.info.role === "user")
|
|
88
|
-
.pop();
|
|
89
|
-
|
|
90
|
-
const messageSummary = lastUserMessage?.info?.summary;
|
|
91
|
-
if (
|
|
92
|
-
messageSummary &&
|
|
93
|
-
typeof messageSummary === "object" &&
|
|
94
|
-
messageSummary !== null
|
|
95
|
-
) {
|
|
96
|
-
if ("body" in messageSummary && messageSummary.body) {
|
|
97
|
-
summary = String(messageSummary.body).trim().slice(0, 100);
|
|
98
|
-
} else if ("title" in messageSummary && messageSummary.title) {
|
|
99
|
-
summary = String(messageSummary.title).trim();
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
notify("OpenCode", summary);
|
|
105
|
-
|
|
106
|
-
client.app
|
|
107
|
-
.log({
|
|
108
|
-
body: {
|
|
109
|
-
service: "notification-plugin",
|
|
110
|
-
level: "info",
|
|
111
|
-
message: `✅ Notification sent: ${summary}`,
|
|
112
|
-
},
|
|
113
|
-
})
|
|
114
|
-
.catch(() => {});
|
|
115
|
-
} catch (error) {
|
|
116
|
-
client.app
|
|
117
|
-
.log({
|
|
118
|
-
body: {
|
|
119
|
-
service: "notification-plugin",
|
|
120
|
-
level: "warn",
|
|
121
|
-
message: `⚠️ Notification failed: ${(error as Error).message}`,
|
|
122
|
-
},
|
|
123
|
-
})
|
|
124
|
-
.catch(() => {});
|
|
125
|
-
}
|
|
126
|
-
}, 2000);
|
|
127
|
-
}
|
|
128
|
-
},
|
|
129
|
-
};
|
|
7
|
+
import { notify } from "./lib/notify";
|
|
8
|
+
|
|
9
|
+
export const NotificationPlugin: Plugin = async ({ client, $ }) => {
|
|
10
|
+
const notifiedSessions = new Set<string>();
|
|
11
|
+
|
|
12
|
+
return {
|
|
13
|
+
event: async ({ event }) => {
|
|
14
|
+
if (event.type === "session.idle") {
|
|
15
|
+
const props = event.properties as Record<string, unknown>;
|
|
16
|
+
const sessionId = props?.sessionID as string | undefined;
|
|
17
|
+
|
|
18
|
+
if (!sessionId || notifiedSessions.has(sessionId)) return;
|
|
19
|
+
notifiedSessions.add(sessionId);
|
|
20
|
+
|
|
21
|
+
setTimeout(async () => {
|
|
22
|
+
try {
|
|
23
|
+
let summary = "Session completed";
|
|
24
|
+
|
|
25
|
+
const messagesResponse = await client.session.messages({
|
|
26
|
+
path: { id: sessionId },
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
if (messagesResponse.data && Array.isArray(messagesResponse.data)) {
|
|
30
|
+
const lastUserMessage = messagesResponse.data
|
|
31
|
+
.filter((m) => m.info.role === "user")
|
|
32
|
+
.pop();
|
|
33
|
+
|
|
34
|
+
const messageSummary = lastUserMessage?.info?.summary;
|
|
35
|
+
if (
|
|
36
|
+
messageSummary &&
|
|
37
|
+
typeof messageSummary === "object" &&
|
|
38
|
+
messageSummary !== null
|
|
39
|
+
) {
|
|
40
|
+
if ("body" in messageSummary && messageSummary.body) {
|
|
41
|
+
summary = String(messageSummary.body).trim().slice(0, 100);
|
|
42
|
+
} else if ("title" in messageSummary && messageSummary.title) {
|
|
43
|
+
summary = String(messageSummary.title).trim();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
await notify($, "OpenCode", summary);
|
|
49
|
+
} catch (error) {
|
|
50
|
+
client.app
|
|
51
|
+
.log({
|
|
52
|
+
body: {
|
|
53
|
+
service: "notification",
|
|
54
|
+
level: "warn",
|
|
55
|
+
message: `Notification failed: ${(error as Error).message}`,
|
|
56
|
+
},
|
|
57
|
+
})
|
|
58
|
+
.catch(() => {});
|
|
59
|
+
}
|
|
60
|
+
}, 2000);
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
};
|
|
130
64
|
};
|
|
@@ -1,190 +1,84 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* OpenCode Truncator Plugin
|
|
3
|
-
*
|
|
3
|
+
* Monitors tool output sizes and warns about large outputs
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* output modification. This plugin provides utility functions that can be used
|
|
9
|
-
* by other plugins or integrated when the API supports it.
|
|
5
|
+
* NOTE: tool.execute.after hook is observation-only (returns void).
|
|
6
|
+
* Actual truncation would require OpenCode core changes.
|
|
7
|
+
* This plugin logs warnings when outputs are large under context pressure.
|
|
10
8
|
*/
|
|
11
9
|
|
|
12
10
|
import type { Plugin } from "@opencode-ai/plugin";
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
// Minimum output to preserve (chars)
|
|
32
|
-
MIN_OUTPUT: 2000,
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Get scale factor based on context usage percentage
|
|
37
|
-
*/
|
|
38
|
-
function getScaleFactor(contextPct: number): number {
|
|
39
|
-
for (const { threshold, factor } of CONFIG.SCALE_FACTORS) {
|
|
40
|
-
if (contextPct >= threshold) {
|
|
41
|
-
return factor;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
return 1.0;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Get dynamic limit for a tool based on context usage
|
|
49
|
-
*/
|
|
50
|
-
export function getLimit(tool: string, contextPct: number): number {
|
|
51
|
-
const baseLimit = CONFIG.BASE_LIMITS[tool] || CONFIG.BASE_LIMITS.default;
|
|
52
|
-
const scaleFactor = getScaleFactor(contextPct);
|
|
53
|
-
return Math.max(CONFIG.MIN_OUTPUT, Math.floor(baseLimit * scaleFactor));
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Ensure output is a string - handles objects, arrays, null, undefined
|
|
58
|
-
*/
|
|
59
|
-
function ensureString(output: unknown): string {
|
|
60
|
-
if (typeof output === "string") {
|
|
61
|
-
return output;
|
|
62
|
-
}
|
|
63
|
-
if (output === null || output === undefined) {
|
|
64
|
-
return "";
|
|
65
|
-
}
|
|
66
|
-
if (typeof output === "object") {
|
|
67
|
-
try {
|
|
68
|
-
return JSON.stringify(output, null, 2);
|
|
69
|
-
} catch {
|
|
70
|
-
return String(output);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
return String(output);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Smart truncation with context preservation
|
|
78
|
-
*/
|
|
79
|
-
export function smartTruncate(
|
|
80
|
-
output: unknown,
|
|
81
|
-
limit: number,
|
|
82
|
-
tool: string,
|
|
83
|
-
): string {
|
|
84
|
-
// Ensure we're working with a string
|
|
85
|
-
const str = ensureString(output);
|
|
86
|
-
|
|
87
|
-
// Return as-is if within limit or empty
|
|
88
|
-
if (!str || str.length <= limit) return str;
|
|
89
|
-
|
|
90
|
-
const truncatedChars = str.length - limit;
|
|
91
|
-
const truncationMsg = `\n\n[... truncated ${truncatedChars} chars to save context ...]`;
|
|
92
|
-
|
|
93
|
-
// Smart truncation strategies based on tool type
|
|
94
|
-
if (tool === "grep" || tool === "glob") {
|
|
95
|
-
// For search results: keep first and last results
|
|
96
|
-
const lines = str.split("\n");
|
|
97
|
-
const targetLines = Math.floor(limit / 80); // Assume ~80 chars per line
|
|
98
|
-
|
|
99
|
-
if (lines.length > targetLines && targetLines > 0) {
|
|
100
|
-
const keepStart = Math.max(1, Math.floor(targetLines * 0.7));
|
|
101
|
-
const keepEnd = Math.max(1, targetLines - keepStart);
|
|
102
|
-
return [
|
|
103
|
-
...lines.slice(0, keepStart),
|
|
104
|
-
`\n... [${lines.length - keepStart - keepEnd} lines truncated] ...\n`,
|
|
105
|
-
...lines.slice(-keepEnd),
|
|
106
|
-
].join("\n");
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
if (tool === "read") {
|
|
111
|
-
// For file reads: keep beginning and end
|
|
112
|
-
const keepStart = Math.floor(limit * 0.7);
|
|
113
|
-
const keepEnd = limit - keepStart;
|
|
114
|
-
if (keepStart > 0 && keepEnd > 0) {
|
|
115
|
-
return str.slice(0, keepStart) + truncationMsg + str.slice(-keepEnd);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
if (tool === "bash") {
|
|
120
|
-
// For bash: prefer keeping the end (usually most relevant)
|
|
121
|
-
const keepStart = Math.floor(limit * 0.3);
|
|
122
|
-
const keepEnd = limit - keepStart;
|
|
123
|
-
if (keepStart > 0 && keepEnd > 0) {
|
|
124
|
-
return str.slice(0, keepStart) + truncationMsg + str.slice(-keepEnd);
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// Default: balanced truncation
|
|
129
|
-
const keepStart = Math.floor(limit * 0.6);
|
|
130
|
-
const keepEnd = limit - keepStart;
|
|
131
|
-
if (keepStart > 0 && keepEnd > 0) {
|
|
132
|
-
return str.slice(0, keepStart) + truncationMsg + str.slice(-keepEnd);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// Fallback: just take first `limit` chars
|
|
136
|
-
return str.slice(0, limit) + truncationMsg;
|
|
11
|
+
import {
|
|
12
|
+
THRESHOLDS,
|
|
13
|
+
type TokenStats,
|
|
14
|
+
getContextPercentage,
|
|
15
|
+
} from "./lib/notify";
|
|
16
|
+
|
|
17
|
+
// Warning thresholds for output size (chars)
|
|
18
|
+
const OUTPUT_WARN = {
|
|
19
|
+
MODERATE: 20000, // Warn if output > 20k at 70%+ context
|
|
20
|
+
URGENT: 10000, // Warn if output > 10k at 85%+ context
|
|
21
|
+
CRITICAL: 5000, // Warn if output > 5k at 95%+ context
|
|
22
|
+
} as const;
|
|
23
|
+
|
|
24
|
+
function getOutputThreshold(percentage: number): number | null {
|
|
25
|
+
if (percentage >= THRESHOLDS.CRITICAL) return OUTPUT_WARN.CRITICAL;
|
|
26
|
+
if (percentage >= THRESHOLDS.URGENT) return OUTPUT_WARN.URGENT;
|
|
27
|
+
if (percentage >= THRESHOLDS.MODERATE) return OUTPUT_WARN.MODERATE;
|
|
28
|
+
return null; // No warning under 70%
|
|
137
29
|
}
|
|
138
30
|
|
|
139
31
|
export const TruncatorPlugin: Plugin = async ({ client }) => {
|
|
140
|
-
// Track
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
client.app
|
|
144
|
-
.log({
|
|
145
|
-
body: {
|
|
146
|
-
service: "truncator-plugin",
|
|
147
|
-
level: "info",
|
|
148
|
-
message:
|
|
149
|
-
"✂️ Truncator Plugin loaded - dynamic truncation utilities available",
|
|
150
|
-
},
|
|
151
|
-
})
|
|
152
|
-
.catch(() => {});
|
|
32
|
+
// Track context percentage per session
|
|
33
|
+
const sessionContext = new Map<string, number>();
|
|
153
34
|
|
|
154
35
|
return {
|
|
155
36
|
event: async ({ event }) => {
|
|
156
37
|
const props = event.properties as Record<string, unknown>;
|
|
157
38
|
|
|
158
|
-
//
|
|
39
|
+
// Update context tracking from session updates
|
|
159
40
|
if (event.type === "session.updated") {
|
|
160
41
|
const info = props?.info as Record<string, unknown> | undefined;
|
|
161
42
|
const tokenStats = (info?.tokens || props?.tokens) as
|
|
162
|
-
|
|
|
43
|
+
| TokenStats
|
|
163
44
|
| undefined;
|
|
45
|
+
const sessionId = (info?.id || props?.sessionID) as string | undefined;
|
|
164
46
|
|
|
165
|
-
if (tokenStats?.
|
|
166
|
-
|
|
167
|
-
currentContextPct = tokenStats.percentage;
|
|
168
|
-
|
|
169
|
-
// Log when crossing thresholds
|
|
170
|
-
const thresholds = [50, 70, 85, 95];
|
|
171
|
-
for (const t of thresholds) {
|
|
172
|
-
if (prevPct < t && currentContextPct >= t) {
|
|
173
|
-
const limit = getLimit("default", currentContextPct);
|
|
174
|
-
client.app
|
|
175
|
-
.log({
|
|
176
|
-
body: {
|
|
177
|
-
service: "truncator-plugin",
|
|
178
|
-
level: "debug",
|
|
179
|
-
message: `✂️ Context at ${currentContextPct}% - dynamic limit now ${limit} chars`,
|
|
180
|
-
},
|
|
181
|
-
})
|
|
182
|
-
.catch(() => {});
|
|
183
|
-
break;
|
|
184
|
-
}
|
|
185
|
-
}
|
|
47
|
+
if (sessionId && tokenStats?.used && tokenStats?.limit) {
|
|
48
|
+
sessionContext.set(sessionId, getContextPercentage(tokenStats));
|
|
186
49
|
}
|
|
187
50
|
}
|
|
51
|
+
|
|
52
|
+
if (event.type === "session.deleted") {
|
|
53
|
+
const sessionId = props?.sessionID as string | undefined;
|
|
54
|
+
if (sessionId) sessionContext.delete(sessionId);
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
"tool.execute.after": async (input, output) => {
|
|
59
|
+
const toolName = input.tool;
|
|
60
|
+
const sessionId = input.sessionID;
|
|
61
|
+
|
|
62
|
+
// Get current context pressure
|
|
63
|
+
const percentage = sessionContext.get(sessionId) || 0;
|
|
64
|
+
const threshold = getOutputThreshold(percentage);
|
|
65
|
+
|
|
66
|
+
// Only check when under pressure
|
|
67
|
+
if (!threshold) return;
|
|
68
|
+
|
|
69
|
+
// Check output size
|
|
70
|
+
const outputStr = output.output || "";
|
|
71
|
+
if (outputStr.length > threshold) {
|
|
72
|
+
client.app
|
|
73
|
+
.log({
|
|
74
|
+
body: {
|
|
75
|
+
service: "truncator",
|
|
76
|
+
level: percentage >= THRESHOLDS.CRITICAL ? "warn" : "info",
|
|
77
|
+
message: `Large output from ${toolName}: ${outputStr.length} chars (threshold: ${threshold}, context: ${percentage}%)`,
|
|
78
|
+
},
|
|
79
|
+
})
|
|
80
|
+
.catch(() => {});
|
|
81
|
+
}
|
|
188
82
|
},
|
|
189
83
|
};
|
|
190
84
|
};
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: mqdh
|
|
3
|
+
description: Meta Quest Developer Hub MCP for VR/AR development. Access Meta Horizon OS docs, ADB tools, device management. Use when building Quest apps or need Meta XR documentation.
|
|
4
|
+
mcp:
|
|
5
|
+
mqdh:
|
|
6
|
+
command: "${MQDH_MCP_PATH}"
|
|
7
|
+
args: []
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Meta Quest Developer Hub MCP
|
|
11
|
+
|
|
12
|
+
Access Meta Horizon OS development tools via the MQDH MCP server. This MCP is **bundled with the Meta Quest Developer Hub application** (v6.2.1+).
|
|
13
|
+
|
|
14
|
+
## Prerequisites
|
|
15
|
+
|
|
16
|
+
### 1. Install MQDH
|
|
17
|
+
|
|
18
|
+
Download and install Meta Quest Developer Hub v6.2.1 or later:
|
|
19
|
+
|
|
20
|
+
- **macOS**: [Download MQDH for Mac](https://developers.meta.com/horizon/documentation/android-apps/meta-quest-developer-hub)
|
|
21
|
+
- **Windows**: [Download MQDH for Windows](https://developers.meta.com/horizon/documentation/android-apps/meta-quest-developer-hub)
|
|
22
|
+
|
|
23
|
+
### 2. Set MCP Path Environment Variable
|
|
24
|
+
|
|
25
|
+
The MCP server is bundled with MQDH. Set the path to the MCP executable:
|
|
26
|
+
|
|
27
|
+
**macOS:**
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
export MQDH_MCP_PATH="/Applications/Meta Quest Developer Hub.app/Contents/Resources/mcp-server"
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
**Windows:**
|
|
34
|
+
|
|
35
|
+
```powershell
|
|
36
|
+
$env:MQDH_MCP_PATH = "C:\Program Files\Meta Quest Developer Hub\resources\mcp-server.exe"
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
> **Note**: Paths may vary based on your installation. Check MQDH's AI Tools settings tab for the exact path.
|
|
40
|
+
|
|
41
|
+
### 3. Alternative: Use MQDH's Built-in Setup
|
|
42
|
+
|
|
43
|
+
MQDH v6.2.1+ includes an **AI Tools settings tab** with:
|
|
44
|
+
|
|
45
|
+
- One-click install for **Cursor** and **VS Code**
|
|
46
|
+
- Guided setup for Claude Desktop, Gemini CLI, Android Studio
|
|
47
|
+
- Manual configuration export for other tools
|
|
48
|
+
|
|
49
|
+
## Quick Start
|
|
50
|
+
|
|
51
|
+
After setting up the environment variable and loading this skill:
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
skill_mcp(skill_name="mqdh", list_tools=true)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Then invoke tools:
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
skill_mcp(skill_name="mqdh", tool_name="fetch_meta_quest_doc", arguments='{"query": "hand tracking setup"}')
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Available Tools
|
|
64
|
+
|
|
65
|
+
### fetch_meta_quest_doc
|
|
66
|
+
|
|
67
|
+
Search and retrieve Meta Horizon OS documentation.
|
|
68
|
+
|
|
69
|
+
| Parameter | Type | Required | Description |
|
|
70
|
+
| --------- | ------ | -------- | -------------------------- |
|
|
71
|
+
| `query` | string | Yes | Documentation search query |
|
|
72
|
+
|
|
73
|
+
**Example:**
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
skill_mcp(skill_name="mqdh", tool_name="fetch_meta_quest_doc", arguments='{"query": "passthrough API Unity"}')
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### get_adb_path
|
|
80
|
+
|
|
81
|
+
Get the path to the bundled ADB executable.
|
|
82
|
+
|
|
83
|
+
**Example:**
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
skill_mcp(skill_name="mqdh", tool_name="get_adb_path", arguments='{}')
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### device_list
|
|
90
|
+
|
|
91
|
+
List connected Quest devices.
|
|
92
|
+
|
|
93
|
+
**Example:**
|
|
94
|
+
|
|
95
|
+
```
|
|
96
|
+
skill_mcp(skill_name="mqdh", tool_name="device_list", arguments='{}')
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### install_apk
|
|
100
|
+
|
|
101
|
+
Install an APK to a connected Quest device.
|
|
102
|
+
|
|
103
|
+
| Parameter | Type | Required | Description |
|
|
104
|
+
| ----------- | ------ | -------- | ---------------- |
|
|
105
|
+
| `apk_path` | string | Yes | Path to APK file |
|
|
106
|
+
| `device_id` | string | No | Target device ID |
|
|
107
|
+
|
|
108
|
+
**Example:**
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
skill_mcp(skill_name="mqdh", tool_name="install_apk", arguments='{"apk_path": "/path/to/app.apk"}')
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Use Cases
|
|
115
|
+
|
|
116
|
+
- **Documentation Lookup**: Quickly search Meta Horizon OS docs for APIs, best practices
|
|
117
|
+
- **Device Management**: List devices, install APKs, manage Quest headsets
|
|
118
|
+
- **Development Workflow**: Integrate ADB operations into AI-assisted development
|
|
119
|
+
- **Troubleshooting**: Find solutions in Meta's documentation
|
|
120
|
+
|
|
121
|
+
## Workflow Examples
|
|
122
|
+
|
|
123
|
+
### 1. Look Up API Documentation
|
|
124
|
+
|
|
125
|
+
```
|
|
126
|
+
# Search for hand tracking documentation
|
|
127
|
+
skill_mcp(skill_name="mqdh", tool_name="fetch_meta_quest_doc", arguments='{"query": "hand tracking gesture detection"}')
|
|
128
|
+
|
|
129
|
+
# Search for passthrough setup
|
|
130
|
+
skill_mcp(skill_name="mqdh", tool_name="fetch_meta_quest_doc", arguments='{"query": "mixed reality passthrough Unity"}')
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### 2. Device Operations
|
|
134
|
+
|
|
135
|
+
```
|
|
136
|
+
# List connected devices
|
|
137
|
+
skill_mcp(skill_name="mqdh", tool_name="device_list", arguments='{}')
|
|
138
|
+
|
|
139
|
+
# Install a build
|
|
140
|
+
skill_mcp(skill_name="mqdh", tool_name="install_apk", arguments='{"apk_path": "/builds/myapp.apk", "device_id": "1234567890"}')
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Transport
|
|
144
|
+
|
|
145
|
+
This MCP uses **STDIO** transport to communicate with the LLM agent.
|
|
146
|
+
|
|
147
|
+
## Troubleshooting
|
|
148
|
+
|
|
149
|
+
**"Command not found"**: Verify `MQDH_MCP_PATH` points to the correct executable. Check MQDH's AI Tools settings for the exact path.
|
|
150
|
+
|
|
151
|
+
**"MQDH not installed"**: Download MQDH v6.2.1+ from [Meta Developer Portal](https://developers.meta.com/horizon/documentation/android-apps/meta-quest-developer-hub).
|
|
152
|
+
|
|
153
|
+
**"Device not found"**: Ensure your Quest is connected via USB and developer mode is enabled.
|
|
154
|
+
|
|
155
|
+
**One-click setup preferred**: Use MQDH's built-in AI Tools settings tab for automatic configuration with Cursor or VS Code.
|
|
156
|
+
|
|
157
|
+
## References
|
|
158
|
+
|
|
159
|
+
- [MQDH MCP Documentation](https://developers.meta.com/horizon/documentation/unity/ts-mqdh-mcp/)
|
|
160
|
+
- [Install MQDH](https://developers.meta.com/horizon/documentation/android-apps/meta-quest-developer-hub)
|
|
161
|
+
- [Meta Horizon OS Developer Docs](https://developers.meta.com/horizon/documentation/)
|