@basegrid_tech/mcp 0.16.2 → 0.17.1

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.
Files changed (2) hide show
  1. package/dist/index.js +403 -2
  2. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -136,6 +136,8 @@ var MANAGED_WORKTREE_MARKERS = [
136
136
 
137
137
  // ../server/src/basegrid-paths-fs.ts
138
138
  function getDataDir() {
139
+ const override = process.env.BASEGRID_DATA_DIR?.trim();
140
+ if (override) return override;
139
141
  return path.join(os.homedir(), BASEGRID_DIR_NAME);
140
142
  }
141
143
  function getDbPath() {
@@ -1688,6 +1690,7 @@ var DEFAULTS_KEYS_MAP = {
1688
1690
  rowHeight: true,
1689
1691
  defaultAgent: true,
1690
1692
  defaultSessionView: true,
1693
+ collapseAgentSteps: true,
1691
1694
  notifications: true,
1692
1695
  hasSeenOnboarding: true,
1693
1696
  widgetEnabled: true,
@@ -1702,7 +1705,6 @@ var DEFAULTS_KEYS_MAP = {
1702
1705
  activeWorkspace: true,
1703
1706
  webAccessEnabled: true,
1704
1707
  mobileAccessEnabled: true,
1705
- networkAccessEnabled: true,
1706
1708
  proxy: true,
1707
1709
  proxySource: true,
1708
1710
  showHeadlessAgents: true,
@@ -3941,6 +3943,404 @@ function registerAskUserQuestionTool(server) {
3941
3943
  );
3942
3944
  }
3943
3945
 
3946
+ // src/tools/emulator.ts
3947
+ import { z as z8 } from "zod";
3948
+ var TARGET_FIELDS = {
3949
+ device: V.shortText.optional().describe("Device serial (e.g. emulator-5554) or AVD name. Defaults to the active device."),
3950
+ worktree_path: V.absolutePath.optional().describe("Worktree path whose active device to use when `device` is omitted")
3951
+ };
3952
+ function target(args) {
3953
+ return { device: args.device, worktreePath: args.worktree_path };
3954
+ }
3955
+ function cameraSource(args) {
3956
+ if (args.source === "webcam") {
3957
+ return { kind: "webcam", name: args.webcam_name };
3958
+ }
3959
+ if (args.source === "file") {
3960
+ if (!args.file_path) {
3961
+ throw new Error('file_path is required when source is "file"');
3962
+ }
3963
+ return { kind: "file", path: args.file_path };
3964
+ }
3965
+ return { kind: "placeholder" };
3966
+ }
3967
+ function text(value) {
3968
+ return { content: [{ type: "text", text: JSON.stringify(value, null, 2) }] };
3969
+ }
3970
+ function ok(message) {
3971
+ return { content: [{ type: "text", text: message }] };
3972
+ }
3973
+ function failure(err) {
3974
+ return {
3975
+ content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }],
3976
+ isError: true
3977
+ };
3978
+ }
3979
+ function registerEmulatorTools(server) {
3980
+ server.tool(
3981
+ "emulator_devices",
3982
+ "List Android emulators/AVDs and connected devices, plus SDK availability. Run this first to get a device serial.",
3983
+ {},
3984
+ async () => {
3985
+ try {
3986
+ const availability = await rpcCall("emulator:availability");
3987
+ return text(availability);
3988
+ } catch (err) {
3989
+ return failure(err);
3990
+ }
3991
+ }
3992
+ );
3993
+ server.tool(
3994
+ "emulator_attach",
3995
+ "Boot the device/AVD if needed and make it the active emulator for a worktree. Later commands can then omit `device`.",
3996
+ {
3997
+ device: V.shortText.describe("Device serial or AVD name"),
3998
+ worktree_path: V.absolutePath.optional().describe("Worktree this device becomes active for")
3999
+ },
4000
+ async (args) => {
4001
+ try {
4002
+ const active = await rpcCall("emulator:attach", {
4003
+ device: args.device,
4004
+ worktreePath: args.worktree_path
4005
+ });
4006
+ return text(active);
4007
+ } catch (err) {
4008
+ return failure(err);
4009
+ }
4010
+ }
4011
+ );
4012
+ server.tool(
4013
+ "emulator_screenshot",
4014
+ "Capture the current device screen as a PNG image.",
4015
+ TARGET_FIELDS,
4016
+ async (args) => {
4017
+ try {
4018
+ const shot = await rpcCall("emulator:screenshot", target(args));
4019
+ return {
4020
+ content: [{ type: "image", data: shot.data, mimeType: shot.mimeType }]
4021
+ };
4022
+ } catch (err) {
4023
+ return failure(err);
4024
+ }
4025
+ }
4026
+ );
4027
+ server.tool(
4028
+ "emulator_ax",
4029
+ "Dump the accessibility tree (uiautomator). Use element bounds to compute a tap point: x=(left+right)/2/screenWidth, y=(top+bottom)/2/screenHeight.",
4030
+ TARGET_FIELDS,
4031
+ async (args) => {
4032
+ try {
4033
+ return text(await rpcCall("emulator:ax", target(args)));
4034
+ } catch (err) {
4035
+ return failure(err);
4036
+ }
4037
+ }
4038
+ );
4039
+ server.tool(
4040
+ "emulator_tap",
4041
+ "Tap at normalized coordinates (0..1, top-left origin). Preferred over swipe for single taps.",
4042
+ {
4043
+ x: z8.number().min(0).max(1).describe("Normalized X (0..1)"),
4044
+ y: z8.number().min(0).max(1).describe("Normalized Y (0..1)"),
4045
+ ...TARGET_FIELDS
4046
+ },
4047
+ async (args) => {
4048
+ try {
4049
+ await rpcCall("emulator:tap", { ...target(args), x: args.x, y: args.y });
4050
+ return ok(`Tapped at ${args.x.toFixed(3)}, ${args.y.toFixed(3)}`);
4051
+ } catch (err) {
4052
+ return failure(err);
4053
+ }
4054
+ }
4055
+ );
4056
+ server.tool(
4057
+ "emulator_swipe",
4058
+ "Swipe from one normalized point to another (0..1). adb approximates the path by its endpoints, so this fits scroll/swipe, not true multi-touch paths.",
4059
+ {
4060
+ from_x: z8.number().min(0).max(1).describe("Start X (0..1)"),
4061
+ from_y: z8.number().min(0).max(1).describe("Start Y (0..1)"),
4062
+ to_x: z8.number().min(0).max(1).describe("End X (0..1)"),
4063
+ to_y: z8.number().min(0).max(1).describe("End Y (0..1)"),
4064
+ duration_ms: z8.number().int().min(50).max(1e4).optional().describe("Swipe duration in ms (default 300)"),
4065
+ ...TARGET_FIELDS
4066
+ },
4067
+ async (args) => {
4068
+ try {
4069
+ await rpcCall("emulator:gesture", {
4070
+ ...target(args),
4071
+ points: [
4072
+ { x: args.from_x, y: args.from_y },
4073
+ { x: args.to_x, y: args.to_y, delayMs: args.duration_ms }
4074
+ ]
4075
+ });
4076
+ return ok("Swipe sent");
4077
+ } catch (err) {
4078
+ return failure(err);
4079
+ }
4080
+ }
4081
+ );
4082
+ server.tool(
4083
+ "emulator_type",
4084
+ 'Type text into the focused field. US ASCII only; newlines are not supported \u2014 send button "enter" instead.',
4085
+ {
4086
+ text: z8.string().min(1).max(2e3).describe("Text to type"),
4087
+ ...TARGET_FIELDS
4088
+ },
4089
+ async (args) => {
4090
+ try {
4091
+ await rpcCall("emulator:type", { ...target(args), text: args.text });
4092
+ return ok("Text typed");
4093
+ } catch (err) {
4094
+ return failure(err);
4095
+ }
4096
+ }
4097
+ );
4098
+ server.tool(
4099
+ "emulator_button",
4100
+ "Press a hardware/system button.",
4101
+ {
4102
+ name: z8.enum([
4103
+ "home",
4104
+ "back",
4105
+ "recents",
4106
+ "power",
4107
+ "volume_up",
4108
+ "volume_down",
4109
+ "enter",
4110
+ "tab",
4111
+ "delete",
4112
+ "escape",
4113
+ "menu",
4114
+ "search"
4115
+ ]).describe("Button name"),
4116
+ ...TARGET_FIELDS
4117
+ },
4118
+ async (args) => {
4119
+ try {
4120
+ await rpcCall("emulator:button", { ...target(args), name: args.name });
4121
+ return ok(`Pressed ${args.name}`);
4122
+ } catch (err) {
4123
+ return failure(err);
4124
+ }
4125
+ }
4126
+ );
4127
+ server.tool(
4128
+ "emulator_rotate",
4129
+ "Rotate the device. Disables auto-rotate and sets a fixed orientation.",
4130
+ {
4131
+ orientation: z8.enum(["portrait", "portrait_upside_down", "landscape_left", "landscape_right"]).describe("Target orientation"),
4132
+ ...TARGET_FIELDS
4133
+ },
4134
+ async (args) => {
4135
+ try {
4136
+ await rpcCall("emulator:rotate", { ...target(args), orientation: args.orientation });
4137
+ return ok(`Rotated to ${args.orientation}`);
4138
+ } catch (err) {
4139
+ return failure(err);
4140
+ }
4141
+ }
4142
+ );
4143
+ server.tool(
4144
+ "emulator_install",
4145
+ "Install an APK onto the device.",
4146
+ {
4147
+ apk_path: V.absolutePath.describe("Absolute path to the .apk file"),
4148
+ reinstall: z8.boolean().optional().describe("Reinstall over an existing app (adb install -r)"),
4149
+ ...TARGET_FIELDS
4150
+ },
4151
+ async (args) => {
4152
+ try {
4153
+ await rpcCall("emulator:install", {
4154
+ ...target(args),
4155
+ apkPath: args.apk_path,
4156
+ reinstall: args.reinstall
4157
+ });
4158
+ return ok(`Installed ${args.apk_path}`);
4159
+ } catch (err) {
4160
+ return failure(err);
4161
+ }
4162
+ }
4163
+ );
4164
+ server.tool(
4165
+ "emulator_launch",
4166
+ "Launch an app by package name. Without `activity` the default LAUNCHER activity is used.",
4167
+ {
4168
+ package_name: V.shortText.describe("Application id, e.g. com.acme.app"),
4169
+ activity: V.shortText.optional().describe("Activity name, e.g. .MainActivity"),
4170
+ ...TARGET_FIELDS
4171
+ },
4172
+ async (args) => {
4173
+ try {
4174
+ await rpcCall("emulator:launch", {
4175
+ ...target(args),
4176
+ packageName: args.package_name,
4177
+ activity: args.activity
4178
+ });
4179
+ return ok(`Launched ${args.package_name}`);
4180
+ } catch (err) {
4181
+ return failure(err);
4182
+ }
4183
+ }
4184
+ );
4185
+ server.tool(
4186
+ "emulator_permissions",
4187
+ "Grant or revoke a runtime permission, or reset all runtime grants on the device.",
4188
+ {
4189
+ op: z8.enum(["grant", "revoke", "reset"]).describe("Operation"),
4190
+ package_name: V.shortText.describe("Application id, e.g. com.acme.app"),
4191
+ permission: V.shortText.optional().describe("Permission, e.g. android.permission.CAMERA (required for grant/revoke)"),
4192
+ ...TARGET_FIELDS
4193
+ },
4194
+ async (args) => {
4195
+ try {
4196
+ await rpcCall("emulator:permissions", {
4197
+ ...target(args),
4198
+ op: args.op,
4199
+ packageName: args.package_name,
4200
+ permission: args.permission
4201
+ });
4202
+ return ok(`Permission ${args.op} done`);
4203
+ } catch (err) {
4204
+ return failure(err);
4205
+ }
4206
+ }
4207
+ );
4208
+ server.tool(
4209
+ "emulator_logcat",
4210
+ "Capture a one-shot logcat dump from the device.",
4211
+ {
4212
+ lines: z8.number().int().min(1).max(5e3).optional().describe("Tail size (default: all)"),
4213
+ filters: z8.array(V.shortText).max(20).optional().describe('logcat filterspec tokens, e.g. ["MyTag:D", "*:S"]'),
4214
+ ...TARGET_FIELDS
4215
+ },
4216
+ async (args) => {
4217
+ try {
4218
+ return text(
4219
+ await rpcCall("emulator:logcat", {
4220
+ ...target(args),
4221
+ lines: args.lines,
4222
+ filters: args.filters
4223
+ })
4224
+ );
4225
+ } catch (err) {
4226
+ return failure(err);
4227
+ }
4228
+ }
4229
+ );
4230
+ server.tool(
4231
+ "emulator_exec",
4232
+ "Run an arbitrary adb shell command on the device and return its stdout.",
4233
+ {
4234
+ command: z8.string().min(1).max(2e3).describe('Shell command, e.g. "getprop ro.build.version.sdk"'),
4235
+ ...TARGET_FIELDS
4236
+ },
4237
+ async (args) => {
4238
+ try {
4239
+ return text(
4240
+ await rpcCall("emulator:exec", { ...target(args), command: args.command })
4241
+ );
4242
+ } catch (err) {
4243
+ return failure(err);
4244
+ }
4245
+ }
4246
+ );
4247
+ server.tool(
4248
+ "emulator_camera",
4249
+ "iOS only. Inject a synthetic camera feed into an app and launch it. Source: placeholder (animated test pattern), webcam (host camera), or file (image/video). Calling it again hot-swaps the feed without relaunching.",
4250
+ {
4251
+ bundle_id: V.shortText.describe("App bundle id, e.g. com.acme.MyApp"),
4252
+ source: z8.enum(["placeholder", "webcam", "file"]).describe("Feed source (default: placeholder)").optional(),
4253
+ webcam_name: V.shortText.optional().describe('Host camera name when source is "webcam"'),
4254
+ file_path: V.absolutePath.optional().describe('Image or video path when source is "file"'),
4255
+ mirror: z8.enum(["on", "off", "auto"]).optional().describe("Preview mirroring (default: auto)"),
4256
+ ...TARGET_FIELDS
4257
+ },
4258
+ async (args) => {
4259
+ try {
4260
+ return text(
4261
+ await rpcCall("emulator:camera", {
4262
+ ...target(args),
4263
+ bundleId: args.bundle_id,
4264
+ source: cameraSource(args),
4265
+ mirror: args.mirror
4266
+ })
4267
+ );
4268
+ } catch (err) {
4269
+ return failure(err);
4270
+ }
4271
+ }
4272
+ );
4273
+ server.tool(
4274
+ "emulator_camera_switch",
4275
+ "iOS only. Hot-swap the injected camera feed on a device where injection is already running.",
4276
+ {
4277
+ source: z8.enum(["placeholder", "webcam", "file"]).describe("New feed source"),
4278
+ webcam_name: V.shortText.optional().describe('Host camera name when source is "webcam"'),
4279
+ file_path: V.absolutePath.optional().describe('Image or video path when source is "file"'),
4280
+ ...TARGET_FIELDS
4281
+ },
4282
+ async (args) => {
4283
+ try {
4284
+ return text(
4285
+ await rpcCall("emulator:cameraSwitch", {
4286
+ ...target(args),
4287
+ source: cameraSource(args)
4288
+ })
4289
+ );
4290
+ } catch (err) {
4291
+ return failure(err);
4292
+ }
4293
+ }
4294
+ );
4295
+ server.tool(
4296
+ "emulator_debug_overlay",
4297
+ "iOS only. Toggle a CoreAnimation debug render flag \u2014 useful for spotting blended layers, offscreen rendering, or misaligned images.",
4298
+ {
4299
+ overlay: z8.enum(["blended", "copies", "misaligned", "offscreen", "slow-animations"]).describe("Debug flag"),
4300
+ on: z8.boolean().describe("Enable or disable"),
4301
+ ...TARGET_FIELDS
4302
+ },
4303
+ async (args) => {
4304
+ try {
4305
+ return text(
4306
+ await rpcCall("emulator:debugOverlay", {
4307
+ ...target(args),
4308
+ overlay: args.overlay,
4309
+ on: args.on
4310
+ })
4311
+ );
4312
+ } catch (err) {
4313
+ return failure(err);
4314
+ }
4315
+ }
4316
+ );
4317
+ server.tool(
4318
+ "emulator_memory_warning",
4319
+ "iOS only. Simulate a memory warning on the device to test how the app releases resources.",
4320
+ TARGET_FIELDS,
4321
+ async (args) => {
4322
+ try {
4323
+ return text(await rpcCall("emulator:memoryWarning", target(args)));
4324
+ } catch (err) {
4325
+ return failure(err);
4326
+ }
4327
+ }
4328
+ );
4329
+ server.tool(
4330
+ "emulator_shutdown",
4331
+ "Power off the emulator (adb emu kill). A physical device cannot be shut down this way.",
4332
+ TARGET_FIELDS,
4333
+ async (args) => {
4334
+ try {
4335
+ await rpcCall("emulator:shutdown", target(args));
4336
+ return ok("Device shut down");
4337
+ } catch (err) {
4338
+ return failure(err);
4339
+ }
4340
+ }
4341
+ );
4342
+ }
4343
+
3944
4344
  // src/server.ts
3945
4345
  function createMcpServer(version) {
3946
4346
  const server = new McpServer({ name: "basegrid", version }, { capabilities: { tools: {} } });
@@ -3951,6 +4351,7 @@ function createMcpServer(version) {
3951
4351
  registerWorkflowTools(server);
3952
4352
  registerWorkspaceTools(server);
3953
4353
  registerAskUserQuestionTool(server);
4354
+ registerEmulatorTools(server);
3954
4355
  return server;
3955
4356
  }
3956
4357
 
@@ -3963,7 +4364,7 @@ console.warn = (...args) => _origError("[mcp:warn]", ...args);
3963
4364
  console.error = (...args) => _origError("[mcp:error]", ...args);
3964
4365
  async function main() {
3965
4366
  configManager.init();
3966
- const version = true ? "0.16.2" : createRequire(import.meta.url)("../package.json").version;
4367
+ const version = true ? "0.17.1" : createRequire(import.meta.url)("../package.json").version;
3967
4368
  const server = createMcpServer(version);
3968
4369
  const transport = new StdioServerTransport();
3969
4370
  await server.connect(transport);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@basegrid_tech/mcp",
3
- "version": "0.16.2",
3
+ "version": "0.17.1",
4
4
  "description": "BaseGrid MCP server — task management, git, and workflow tools for AI coding agents",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -42,8 +42,8 @@
42
42
  "zod": "^4.3.6"
43
43
  },
44
44
  "devDependencies": {
45
- "@basegrid/server": "0.16.2",
46
- "@basegrid/shared": "0.16.2",
45
+ "@basegrid/server": "0.17.1",
46
+ "@basegrid/shared": "0.17.1",
47
47
  "tsup": "^8.5.1",
48
48
  "tsx": "^4.21.0",
49
49
  "typescript": "^6.0.3"