( mcp: McpServer, server: ReactotronServer, commandBuffer: Command[], serverRedactionConfig: McpRedactionServerConfig )
| 67 | } |
| 68 | |
| 69 | export function registerTools( |
| 70 | mcp: McpServer, |
| 71 | server: ReactotronServer, |
| 72 | commandBuffer: Command[], |
| 73 | serverRedactionConfig: McpRedactionServerConfig |
| 74 | ) { |
| 75 | mcp.registerTool("dispatch_action", { |
| 76 | description: [ |
| 77 | "Dispatch a Redux action to the connected app.", |
| 78 | "Requires the Reactotron Redux plugin to be configured in the app.", |
| 79 | "Example: { type: 'user/setName', payload: { name: 'Alice' } }", |
| 80 | ].join(" "), |
| 81 | inputSchema: { |
| 82 | actionType: z.string().describe("Action type, e.g. 'counter/increment' or 'RESET'"), |
| 83 | actionPayload: z.any().optional().describe("Optional action payload, e.g. { name: 'Alice' }"), |
| 84 | clientId: z.string().optional().describe("Target app clientId (required when multiple apps connected). Get from the apps resource."), |
| 85 | }, |
| 86 | }, async (args) => { |
| 87 | const { clientId, error } = resolveClientId(server, args.clientId) |
| 88 | if (error) return textResult({ status: "error", message: error }) |
| 89 | |
| 90 | const action = { type: args.actionType, payload: args.actionPayload } |
| 91 | server.send("state.action.dispatch", { action }, clientId) |
| 92 | |
| 93 | const redactor = createRedactor(server, serverRedactionConfig) |
| 94 | |
| 95 | // Poll for confirmation (state.action.complete) |
| 96 | const start = Date.now() |
| 97 | const startLen = commandBuffer.length |
| 98 | while (Date.now() - start < 1500) { |
| 99 | await new Promise((r) => setTimeout(r, 100)) |
| 100 | for (let i = startLen; i < commandBuffer.length; i++) { |
| 101 | const cmd = commandBuffer[i] |
| 102 | if (cmd.type === "state.action.complete" && cmd.clientId === clientId) { |
| 103 | return textResult({ status: "dispatched", action: redactor.redact(action, clientId), confirmed: true }) |
| 104 | } |
| 105 | } |
| 106 | } |
| 107 | return textResult({ status: "dispatched", action: redactor.redact(action, clientId), confirmed: false, note: "Action was sent but no confirmation received. The app may not have the Redux plugin configured." }) |
| 108 | }) |
| 109 | |
| 110 | mcp.registerTool("request_state", { |
| 111 | description: [ |
| 112 | "Request a fresh state snapshot from the connected app.", |
| 113 | "Requires Redux or MST plugin configured in the app.", |
| 114 | "IMPORTANT: Always specify a path to avoid oversized responses.", |
| 115 | "The full state tree can be millions of characters.", |
| 116 | "Use request_state_keys first to explore the state shape, then request specific slices.", |
| 117 | "Example path: 'user.profile' to get just that slice.", |
| 118 | ].join(" "), |
| 119 | inputSchema: { |
| 120 | path: z.string().optional().describe("Dot-separated state path, e.g. 'user.profile'. STRONGLY RECOMMENDED — omitting this returns the full state tree which may be too large."), |
| 121 | clientId: z.string().optional().describe("Target app clientId (required when multiple apps connected)."), |
| 122 | }, |
| 123 | }, async (args) => { |
| 124 | const { clientId, error } = resolveClientId(server, args.clientId) |
| 125 | if (error) return textResult({ status: "error", message: error }) |
| 126 |
no test coverage detected