@rainbow-robotics/muscat-plugin-kit 0.1.2-dev.46 → 0.1.2-dev.47
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/cli/src/mcp.mjs +70 -25
- package/cli/src/pluginDevServer.mjs +5 -6
- package/cli/src/util.mjs +3 -6
- package/cli/storybook-dist/project.json +1 -1
- package/package.json +3 -3
package/cli/src/mcp.mjs
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
// muscat-pk mcp — an MCP (Model Context Protocol) server
|
|
2
|
-
// coding agent (Claude Code, Codex, ...)
|
|
3
|
-
//
|
|
4
|
-
// node_modules for @rainbow-robotics/plugin-ui and @repo/rb-components:
|
|
1
|
+
// muscat-pk mcp — an MCP (Model Context Protocol) server exposing the Muscat
|
|
2
|
+
// plugin UI/SDK surface to an AI coding agent (Claude Code, Codex, ...) so it
|
|
3
|
+
// looks things up instead of guessing. Tools:
|
|
5
4
|
//
|
|
6
5
|
// Frontend UI (@rainbow-robotics/plugin-ui/react, via dist/react.d.ts):
|
|
7
6
|
// - list_components every exported component/hook/type.
|
|
@@ -42,11 +41,9 @@ const DESIGN_TOKENS_CSS = existsSync(SHIPPED_DESIGN_TOKENS)
|
|
|
42
41
|
|
|
43
42
|
let typesCache = null;
|
|
44
43
|
|
|
45
|
-
//
|
|
46
|
-
//
|
|
47
|
-
//
|
|
48
|
-
// @repo/rb-components separately, which wouldn't exist in an external install
|
|
49
|
-
// anyway).
|
|
44
|
+
// Parse plugin-ui/react's dist/react.d.ts. It's self-contained (rb-components
|
|
45
|
+
// types are inlined at build — see plugin-ui/build-dist.mjs), so this one file
|
|
46
|
+
// is enough even in an external install.
|
|
50
47
|
async function loadReactTypes() {
|
|
51
48
|
if (typesCache) return typesCache;
|
|
52
49
|
|
|
@@ -113,9 +110,7 @@ function findStorySource(name) {
|
|
|
113
110
|
/* README search */
|
|
114
111
|
/* -------------------------------------------------------------------------- */
|
|
115
112
|
|
|
116
|
-
//
|
|
117
|
-
// section — good enough for short-query lookups ("save form data", "toast",
|
|
118
|
-
// "manifest ui") without pulling in a real search dependency.
|
|
113
|
+
// Split the README on headings and keyword-score each section.
|
|
119
114
|
function searchReadme(query) {
|
|
120
115
|
if (!existsSync(TEMPLATE_README)) return [];
|
|
121
116
|
const text = readFileSync(TEMPLATE_README, "utf8");
|
|
@@ -200,9 +195,8 @@ function pythonCmd() {
|
|
|
200
195
|
return null;
|
|
201
196
|
}
|
|
202
197
|
|
|
203
|
-
//
|
|
204
|
-
//
|
|
205
|
-
// in a real plugin env it's importable; otherwise we return a clear hint.
|
|
198
|
+
// Run an introspection script against the installed rainbow.rb_sdk (a plugin's
|
|
199
|
+
// backend dependency). Returns a clear hint if it isn't importable.
|
|
206
200
|
function introspectRbSdk(script) {
|
|
207
201
|
const py = pythonCmd();
|
|
208
202
|
if (!py) {
|
|
@@ -478,20 +472,66 @@ print(json.dumps(out))
|
|
|
478
472
|
description:
|
|
479
473
|
"Get the signature(s) and docstring for a rainbow.rb_sdk class or function (e.g. " +
|
|
480
474
|
"'PluginProgramContext', 'RBManipulateSDK'). For a class, lists its public methods with " +
|
|
481
|
-
"signatures.
|
|
482
|
-
|
|
475
|
+
"signatures. To drill into ONE method of a class (e.g. RBManipulateSDK.call_move_j), pass " +
|
|
476
|
+
"`method` alongside `name` — do not put both in `name`. Requires rainbow-rb-sdk to be " +
|
|
477
|
+
"pip-installed.",
|
|
478
|
+
inputSchema: {
|
|
479
|
+
name: z.string().describe('Class or function name, e.g. "RBManipulateSDK"'),
|
|
480
|
+
method: z.string().optional().describe('Optional: a specific method on that class, e.g. "call_move_j"'),
|
|
481
|
+
},
|
|
483
482
|
},
|
|
484
|
-
async ({ name }) => {
|
|
483
|
+
async ({ name, method }) => {
|
|
484
|
+
// Tolerate a combined "ClassName method_name" in `name` (a common agent
|
|
485
|
+
// mistake) by splitting it ourselves instead of failing validation.
|
|
486
|
+
let className = name;
|
|
487
|
+
let methodName = method;
|
|
488
|
+
if (!methodName && /\s/.test(name.trim())) {
|
|
489
|
+
const parts = name.trim().split(/\s+/);
|
|
490
|
+
if (parts.length === 2) [className, methodName] = parts;
|
|
491
|
+
}
|
|
492
|
+
|
|
485
493
|
const { data, error } = introspectRbSdk(`
|
|
486
494
|
import inspect, json
|
|
487
495
|
mod = importlib.import_module("rainbow.rb_sdk")
|
|
488
|
-
name = ${JSON.stringify(
|
|
496
|
+
name = ${JSON.stringify(className)}
|
|
497
|
+
method = ${JSON.stringify(methodName ?? null)}
|
|
489
498
|
obj = getattr(mod, name, None)
|
|
490
499
|
if obj is None:
|
|
491
500
|
print(json.dumps({"__missing__": True})); raise SystemExit(0)
|
|
492
501
|
def sig(o):
|
|
493
502
|
try: return str(inspect.signature(o))
|
|
494
503
|
except Exception: return "(...)"
|
|
504
|
+
|
|
505
|
+
if method:
|
|
506
|
+
# Direct method on the class itself.
|
|
507
|
+
fn = getattr(obj, method, None) if inspect.isclass(obj) else None
|
|
508
|
+
# rb_sdk classes often group calls under a nested namespace set in
|
|
509
|
+
# __init__ (e.g. RBManipulateSDK().move.call_move_j) — that only exists on
|
|
510
|
+
# an INSTANCE, which we won't construct (side effects, needs a robot
|
|
511
|
+
# connection). Best-effort: search the class's own __init__ source text
|
|
512
|
+
# for self.<attr> = <NestedClass>(...) and check each nested class too.
|
|
513
|
+
owner = name
|
|
514
|
+
if fn is None and inspect.isclass(obj):
|
|
515
|
+
try:
|
|
516
|
+
src = inspect.getsource(obj.__init__)
|
|
517
|
+
except Exception:
|
|
518
|
+
src = ""
|
|
519
|
+
import re
|
|
520
|
+
for attr, nested_name in re.findall(r"self\\.(\\w+)\\s*=\\s*(\\w+)\\(", src):
|
|
521
|
+
nested = mod.__dict__.get(nested_name) or globals().get(nested_name)
|
|
522
|
+
cand = getattr(nested, method, None) if nested else None
|
|
523
|
+
if cand:
|
|
524
|
+
fn = cand
|
|
525
|
+
owner = f"{name}.{attr} ({nested_name})"
|
|
526
|
+
break
|
|
527
|
+
if fn is None:
|
|
528
|
+
print(json.dumps({"__missing__": True, "__method__": True})); raise SystemExit(0)
|
|
529
|
+
print(json.dumps({
|
|
530
|
+
"name": name, "kind": "method", "owner": owner,
|
|
531
|
+
"signature": method + sig(fn), "doc": inspect.getdoc(fn) or "",
|
|
532
|
+
}))
|
|
533
|
+
raise SystemExit(0)
|
|
534
|
+
|
|
495
535
|
res = {"name": name, "doc": inspect.getdoc(obj) or ""}
|
|
496
536
|
if inspect.isclass(obj):
|
|
497
537
|
res["kind"] = "class"
|
|
@@ -508,12 +548,17 @@ print(json.dumps(res))
|
|
|
508
548
|
`);
|
|
509
549
|
if (error) return { isError: true, content: [{ type: "text", text: error }] };
|
|
510
550
|
if (data.__missing__) {
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
};
|
|
551
|
+
const text = data.__method__
|
|
552
|
+
? `No method "${methodName}" found directly on "${className}" (nor in an obvious nested ` +
|
|
553
|
+
`namespace set in __init__). Call get_rb_sdk_api with just \`name: "${className}"\` to see ` +
|
|
554
|
+
`its full method list and constructor first.`
|
|
555
|
+
: `No "${className}" in rainbow.rb_sdk. Call list_rb_sdk for valid names.`;
|
|
556
|
+
return { isError: true, content: [{ type: "text", text }] };
|
|
557
|
+
}
|
|
558
|
+
if (data.kind === "method") {
|
|
559
|
+
const lines = [`# ${data.owner}.${data.signature.split("(")[0]}`, data.signature];
|
|
560
|
+
if (data.doc) lines.push("", data.doc);
|
|
561
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
517
562
|
}
|
|
518
563
|
const lines = [`# ${data.name} (${data.kind})`];
|
|
519
564
|
if (data.doc) lines.push(data.doc, "");
|
|
@@ -46,12 +46,11 @@ import { setHostServices } from "@rainbow-robotics/plugin-ui/host-ui";
|
|
|
46
46
|
import { RbWs, createLazyRbSdk } from "@rainbow-robotics/plugin-ui/ws";
|
|
47
47
|
import App from ${entry};
|
|
48
48
|
|
|
49
|
-
// HMR
|
|
50
|
-
// static \`App\` import
|
|
51
|
-
//
|
|
52
|
-
//
|
|
53
|
-
//
|
|
54
|
-
// root so live edits re-render it in place.
|
|
49
|
+
// HMR passes the new module to the accept callback but doesn't rebind the
|
|
50
|
+
// static \`App\` import, so we track the latest at module scope. Without this,
|
|
51
|
+
// navigating away and back (a fresh mount() on this cached module) renders the
|
|
52
|
+
// stale App from first import. \`activeRender\` points at the mounted root so
|
|
53
|
+
// live edits re-render in place.
|
|
55
54
|
let latestApp = App;
|
|
56
55
|
let activeRender = null;
|
|
57
56
|
if (import.meta.hot) {
|
package/cli/src/util.mjs
CHANGED
|
@@ -40,12 +40,9 @@ export function venvPython(venvDir) {
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
/**
|
|
43
|
-
* Create/refresh a
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
* never affects it. Uses `uv` if available (fast, pins Python 3.12 — matches
|
|
47
|
-
* the robot), else falls back to the detected system Python.
|
|
48
|
-
* Returns true if the venv is ready with deps installed.
|
|
43
|
+
* Create/refresh a local `.venv` with requirements.txt installed, for the
|
|
44
|
+
* editor/linter only (the backend actually runs in Docker). Uses `uv` if
|
|
45
|
+
* present, else the system Python. Returns true if the venv is ready.
|
|
49
46
|
*/
|
|
50
47
|
export function ensureIdeVenv(root) {
|
|
51
48
|
const venvDir = resolve(root, ".venv");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"generatedAt":
|
|
1
|
+
{"generatedAt":1783624567446,"userSince":1750932757926,"hasCustomBabel":false,"hasCustomWebpack":false,"hasStaticDirs":false,"hasStorybookEslint":true,"refCount":0,"testPackages":{"@vitest/browser":"3.2.4","@vitest/coverage-v8":"3.2.4","playwright":"1.59.1","vitest":"3.2.4"},"hasRouterPackage":false,"monorepo":"Turborepo","packageManager":{"type":"yarn","version":"berry","agent":"yarn@berry"},"preview":{"usesGlobals":false},"framework":{"name":"@storybook/react-vite","options":{}},"builder":"@storybook/builder-vite","renderer":"@storybook/react","portableStoriesFileCount":0,"applicationFileCount":0,"storybookVersion":"8.6.18","storybookVersionSpecifier":"^8.6.4","language":"typescript","storybookPackages":{"@chromatic-com/storybook":{"version":"3.2.7"},"@storybook/addon-essentials":{"version":"8.6.18"},"@storybook/addon-links":{"version":"8.6.18"},"@storybook/addon-onboarding":{"version":"8.6.18"},"@storybook/addon-themes":{"version":"8.6.18"},"@storybook/blocks":{"version":"8.6.18"},"@storybook/experimental-addon-test":{"version":"8.6.18"},"@storybook/react":{"version":"8.6.18"},"@storybook/react-vite":{"version":"8.6.18"},"@storybook/test":{"version":"8.6.18"},"@storybook/theming":{"version":"8.6.18"},"eslint-plugin-storybook":{"version":"0.11.6"},"storybook":{"version":"8.6.18"},"storybook-addon-react-docgen":{"version":"1.2.44"}},"addons":{"$SNIP/node_modules/@storybook/addon-links":{"version":null},"$SNIP/node_modules/@storybook/addon-essentials":{"version":null},"$SNIP/node_modules/@chromatic-com/storybook":{"version":null},"$SNIP/node_modules/@storybook/addon-themes":{"version":null},"$SNIP/node_modules/@storybook/experimental-addon-test":{"version":null}}}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rainbow-robotics/muscat-plugin-kit",
|
|
3
|
-
"version": "0.1.2-dev.
|
|
3
|
+
"version": "0.1.2-dev.47",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=20"
|
|
@@ -53,8 +53,8 @@
|
|
|
53
53
|
},
|
|
54
54
|
"dependencies": {
|
|
55
55
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
56
|
-
"@rainbow-robotics/plugin-ui": "0.1.2-dev.
|
|
57
|
-
"@rainbow-robotics/preview-host": "0.1.2-dev.
|
|
56
|
+
"@rainbow-robotics/plugin-ui": "0.1.2-dev.47",
|
|
57
|
+
"@rainbow-robotics/preview-host": "0.1.2-dev.47",
|
|
58
58
|
"postcss": "^8.5.8",
|
|
59
59
|
"socket.io": "^4.8.1",
|
|
60
60
|
"typescript": "^5.8.0",
|