Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
72 commits
Select commit Hold shift + click to select a range
5cb2871
feat: open proxy service seams for a headless caller
Oct 3, 2026
887e228
feat(mcp): add the mcp module with a JSON-RPC shell over HTTP
Oct 3, 2026
8f07535
feat(mcp): add a bounded packet log read by cursor
Oct 3, 2026
ca58631
feat(mcp): add headless sessions, the packet tap and four tools
Oct 3, 2026
604a45b
feat(mcp): add the in-client bridge plugin
Oct 3, 2026
e120caa
feat(mcp): add the bridge hub, link and plugin jar installer
Oct 3, 2026
6849906
feat(mcp): connect sessions to their client through the bridge
Oct 3, 2026
d451b6c
feat(mcp): add the client read tools
Oct 3, 2026
e60f304
feat(mcp): add the client input tools
Oct 3, 2026
74897ad
docs(mcp): describe how to run and use the MCP server
Oct 3, 2026
8e613ef
fix(mcp): require a login password and wait for the login screen
Oct 3, 2026
315370b
feat(mcp): run without a physical display
Oct 3, 2026
cdae832
style(mcp): space statements into groups and mark empty bodies
Oct 3, 2026
03f71ce
refactor(mcp): give each default one owner and test both halves together
Oct 3, 2026
312b987
feat(mcp): interact with the game through real input
Oct 3, 2026
9bdfe4b
fix(mcp): tie each interaction to its click and wait in client cycles
Oct 3, 2026
2a73f8f
docs(mcp): rewrap the paragraph on what the tests cover
Oct 3, 2026
72d8b44
fix(mcp): turn the camera when an interface covers the target
Oct 3, 2026
4fd935e
feat(proxy): let listeners observe the clients the proxy launches
Oct 8, 2026
4873847
feat(proxy): add the MCP endpoint properties and proxy extensions
Oct 8, 2026
9a4059c
feat(mcp): attach sessions to clients that something else launched
Oct 8, 2026
7cafac6
feat(mcp): serve the endpoint from inside the GUI
Oct 8, 2026
f31576b
docs(mcp): describe the endpoint inside the GUI and attached sessions
Oct 8, 2026
369c796
docs(mcp): drop the comments the attach code does not need
Oct 8, 2026
f2474b0
refactor(mcp): build the standalone stores from the proxy's own
Oct 8, 2026
ffb965f
refactor(mcp): delegate the callbacks the packet taps ignore
Oct 8, 2026
93f711f
refactor(mcp): give every client state its launch
Oct 8, 2026
e724ea9
refactor(mcp): turn the session events into methods of the session
Oct 8, 2026
807246e
refactor(mcp): name the shared tool arguments once
Oct 8, 2026
df57de0
refactor(mcp): build the endpoint in one place
Oct 8, 2026
4966302
refactor(mcp): make firstBound a method of its one caller
Oct 8, 2026
ee6a8eb
refactor(mcp): build both pages of a packet read with one method
Oct 8, 2026
945734f
refactor(mcp): drop two rendezvous fields and two results nobody reads
Oct 8, 2026
0b0783f
refactor(mcp): skip the proxy ports in the standalone command
Oct 8, 2026
8fcf779
fix(mcp): report the capture file from the moment of the login
Oct 8, 2026
235879f
refactor(mcp-bridge): use streams and a comparator chain in place of …
Oct 8, 2026
4c8585c
refactor(mcp-bridge): merge the NPC and player targets into one class
Oct 8, 2026
1646aee
refactor(mcp-bridge): check the scene tile of a menu entry in one place
Oct 8, 2026
8f3c970
refactor(mcp-bridge): drop three helpers that repeat another
Oct 8, 2026
be7513f
refactor(mcp-bridge): keep the software rendering flag as a boolean
Oct 8, 2026
70fca29
refactor(mcp-bridge): use the JDK's interfaces for two callbacks
Oct 8, 2026
f99d005
refactor(mcp-bridge): build the three login failures where they are t…
Oct 8, 2026
11e0d52
refactor(mcp-bridge): stop sending two hello fields that rsprox never…
Oct 8, 2026
d064a1e
refactor(mcp-bridge): drop the handling of JSON null arguments
Oct 8, 2026
d53cfdd
refactor(mcp-bridge): walk the widget tree in one place
Oct 8, 2026
6df7bcd
test(mcp): let the test hub dial its plugins and close them
Oct 8, 2026
427b356
test(mcp): pin in the hub test that a bad token is not reported
Oct 8, 2026
85c1e9f
test(mcp): call the tools through one shared test server
Oct 8, 2026
168c1d0
test(mcp): drop a packet log test that repeats another
Oct 8, 2026
ef1db38
test(proxy): fold four fan-out monitor tests into two
Oct 8, 2026
cb86780
test(mcp): cover the login monitor asked for its own login
Oct 8, 2026
54de4eb
docs(mcp): cut the README to what the tool descriptions do not say
Oct 8, 2026
512ff10
docs(mcp): say when the capture file of a login first exists
Oct 8, 2026
37c3fed
feat(mcp): refuse the tools that send input on the official game
Oct 8, 2026
1bdfcec
feat(mcp-bridge): build the ops that send input only when input is al…
Oct 8, 2026
71436d9
feat(mcp): add a switch that launches clients without the bridge plugin
Oct 8, 2026
a31b4da
refactor(mcp): wrap the lines that the access field pushed past 120 c…
Oct 8, 2026
27b72cb
docs(mcp): say that the server never sends input to the official game
Oct 8, 2026
20f241d
refactor(mcp-bridge): hand the dial a local of the game access
Oct 8, 2026
1b494b4
test(mcp): wrap the two attached-session lines over 120 columns
Oct 8, 2026
95ab0f9
feat(proxy): leave the MCP endpoint of the GUI off unless it is turne…
Oct 8, 2026
f63d18e
test(mcp): cover that an endpoint that is off serves nothing
Oct 8, 2026
ab48e9e
feat(proxy): add a setter that saves whether the MCP endpoint is on
Oct 8, 2026
162780d
feat(gui): add a File menu item that turns the MCP endpoint on and off
Oct 8, 2026
4eebe80
docs(mcp): say that the GUI serves the endpoint only once it is turne…
Oct 8, 2026
d7cdb37
feat(mcp): let a gate decide whether a request may still act on a client
Oct 8, 2026
5f4f057
fix(mcp): end the threads of the endpoint when it is closed
Oct 8, 2026
c35825a
feat(proxy): let the GUI ask its extensions to serve and to stop serving
Oct 8, 2026
173c6c6
feat(mcp): turn the endpoint of the GUI on and off while the GUI runs
Oct 8, 2026
90622d0
feat(gui): make Serve MCP Endpoint take effect without a restart
Oct 8, 2026
de21f80
fix(gui): start the item unticked when the endpoint is not served
Oct 8, 2026
34a0ec3
docs(mcp): say that the menu item of the GUI switches the endpoint at…
Oct 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ subprojects {
dependencies {
runtimeOnly(projects.gui.proxyTool)
runtimeOnly(projects.proxy)
runtimeOnly(projects.mcp)
}

buildscript {
Expand Down
33 changes: 32 additions & 1 deletion gui/proxy-tool/src/main/kotlin/net/rsprox/gui/App.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import net.rsprox.gui.dialogs.AboutDialog
import net.rsprox.gui.sessions.SessionsPanel
import net.rsprox.gui.sidebar.SideBar
import net.rsprox.proxy.ProxyService
import net.rsprox.proxy.StartedExtensions
import net.rsprox.proxy.config.BINARY_PATH
import net.rsprox.proxy.config.ERROR_LOGS_PATH
import java.awt.CardLayout
Expand All @@ -24,6 +25,7 @@ import java.awt.event.WindowEvent
import java.net.URI
import java.util.prefs.Preferences
import javax.swing.BorderFactory
import javax.swing.JCheckBoxMenuItem
import javax.swing.JFrame
import javax.swing.JMenu
import javax.swing.JMenuItem
Expand All @@ -33,7 +35,9 @@ import javax.swing.KeyStroke
import javax.swing.WindowConstants.DO_NOTHING_ON_CLOSE
import kotlin.system.exitProcess

public class App {
public class App(
private val extensions: StartedExtensions,
) {
private val frame: JFrame = JFrame()
private val sessionsPanel: SessionsPanel = SessionsPanel(this)
private val launchBar: LaunchBar = LaunchBar(sessionsPanel)
Expand Down Expand Up @@ -169,6 +173,20 @@ public class App {

addSeparator()

val serveMcpEndpoint =
JCheckBoxMenuItem(
"Serve MCP Endpoint",
isMcpEndpointServed(service.getMcpEnabled(), extensions::serve),
)
serveMcpEndpoint.mnemonic = 'M'.code
serveMcpEndpoint.toolTipText = MCP_ENDPOINT_TOOLTIP
serveMcpEndpoint.addActionListener {
this@App.switchMcpEndpoint(serveMcpEndpoint)
}
add(serveMcpEndpoint)

addSeparator()

val exitItem = JMenuItem("Exit")
exitItem.mnemonic = 'X'.code
exitItem.accelerator = KeyStroke.getKeyStroke("alt F4")
Expand Down Expand Up @@ -283,6 +301,19 @@ public class App {
homePanel.refreshRecentDumps()
}

private fun switchMcpEndpoint(item: JCheckBoxMenuItem) {
val switch =
switchMcpEndpoint(item.isSelected, extensions::serve, extensions::stopServing, service::setMcpEnabled)
item.isSelected = switch.served
val failure = switch.failure ?: return
JOptionPane.showMessageDialog(
frame,
failure,
"MCP Endpoint",
JOptionPane.ERROR_MESSAGE,
)
}

private fun openBinaryLogsFolder() {
openFolder(BINARY_PATH, "You have not created any logs yet.")
}
Expand Down
43 changes: 43 additions & 0 deletions gui/proxy-tool/src/main/kotlin/net/rsprox/gui/McpEndpointNotice.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package net.rsprox.gui

internal const val MCP_ENDPOINT_TOOLTIP: String =
"No password: any program on this computer can read every session's packets, chat included. " +
"While off, nothing is served; sessions of clients launched in this run are kept in memory and " +
"listed again when it is turned back on."

internal data class McpEndpointSwitch(
val served: Boolean,
val failure: String?,
)

/**
* Whether the item starts ticked. An endpoint that is on was served at start-up, unless its port was
* taken then, so it is asked once more. The saved choice is left alone, so the next start tries again.
*/
internal fun isMcpEndpointServed(
enabled: Boolean,
serve: () -> String?,
): Boolean = enabled && serve() == null

/**
* Turns the endpoint on or off at once, and saves the choice once it holds. An endpoint that cannot be
* served stays off with nothing saved, and the result says why.
*/
internal fun switchMcpEndpoint(
enabled: Boolean,
serve: () -> String?,
stopServing: () -> Unit,
save: (Boolean) -> Unit,
): McpEndpointSwitch {
if (!enabled) {
stopServing()
save(false)
return McpEndpointSwitch(served = false, failure = null)
}
val refusal = serve()
if (refusal != null) {
return McpEndpointSwitch(served = false, failure = refusal.replace(". ", ".\n"))
}
save(true)
return McpEndpointSwitch(served = true, failure = null)
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package net.rsprox.gui

import net.rsprox.proxy.ProxyExtension
import java.util.Locale
import javax.swing.ImageIcon
import javax.swing.SwingUtilities
Expand Down Expand Up @@ -28,6 +29,7 @@ public fun main(args: Array<String>) {
App.service.start(rspsJavConfigUrl, rspsModulus) { percentage, actionText, subActionText, progressText ->
SplashScreen.stage(percentage, actionText, subActionText, progressText)
}
val extensions = ProxyExtension.startAll(App.service)
SplashScreen.stop()
SwingUtilities.invokeLater {
// Disable icons in all trees.
Expand All @@ -42,7 +44,7 @@ public fun main(args: Array<String>) {
// This ensures that the theme you see is the same on initial boot as well as swapping back and forth
AppThemes.applyThemeEdt(App.service.getAppTheme())

val app = App()
val app = App(extensions)
app.init()
app.start()
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package net.rsprox.gui

import kotlin.test.Test
import kotlin.test.assertEquals

class McpEndpointNoticeTest {
private val calls = ArrayList<String>()

private fun switch(
enabled: Boolean,
refusal: String? = null,
): McpEndpointSwitch =
switchMcpEndpoint(
enabled,
serve = {
calls += "serve"
refusal
},
stopServing = { calls += "stop serving" },
save = { calls += "save $it" },
)

@Test
fun `turning the endpoint on serves it at once and then saves the choice`() {
assertEquals(McpEndpointSwitch(served = true, failure = null), switch(enabled = true))
assertEquals(listOf("serve", "save true"), calls)
}

@Test
fun `turning the endpoint off stops serving it at once and then saves the choice`() {
assertEquals(McpEndpointSwitch(served = false, failure = null), switch(enabled = false))
assertEquals(listOf("stop serving", "save false"), calls)
}

@Test
fun `an endpoint that cannot be served stays off, saves nothing and says why with a sentence to a line`() {
val refusal =
"The MCP endpoint is not served: 127.0.0.1:43999 cannot be bound (Address already in use). " +
"Another rsprox may hold it. Close that one."

assertEquals(
McpEndpointSwitch(
served = false,
failure =
"The MCP endpoint is not served: 127.0.0.1:43999 cannot be bound (Address already in use).\n" +
"Another rsprox may hold it.\n" +
"Close that one.",
),
switch(enabled = true, refusal),
)
assertEquals(listOf("serve"), calls)
}

@Test
fun `two clicks in a row leave the endpoint as the second click says`() {
switch(enabled = true)

assertEquals(McpEndpointSwitch(served = false, failure = null), switch(enabled = false))
assertEquals(listOf("serve", "save true", "stop serving", "save false"), calls)
}

@Test
fun `the item starts ticked only for an endpoint that is on and served`() {
assertEquals(true, isMcpEndpointServed(enabled = true) { null })
assertEquals(false, isMcpEndpointServed(enabled = true) { "127.0.0.1:43999 cannot be bound" })
assertEquals(false, isMcpEndpointServed(enabled = false) { error("an endpoint that is off is not served") })
}
}
Loading
Loading