Skip to content

Commit e4dde55

Browse files
Here we go
0 parents  commit e4dde55

16 files changed

Lines changed: 3532 additions & 0 deletions

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
.build
2+
.swiftpm/
3+
4+
Package.resolved

.spi.yml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
version: 1
2+
builder:
3+
configs:
4+
- platform: macos-spm
5+
target: AdaMCPCore
6+
- platform: macos-spm
7+
target: AdaMCPServer
8+
- platform: macos-spm
9+
target: AdaMCPPlugin
10+
- documentation_targets:
11+
- AdaMCPCore
12+
- AdaMCPServer
13+
- AdaMCPPlugin

Package.swift

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// swift-tools-version: 6.2
2+
import PackageDescription
3+
4+
let package = Package(
5+
name: "AdaMCP",
6+
platforms: [
7+
.macOS(.v15),
8+
.iOS(.v18),
9+
.tvOS(.v18),
10+
.visionOS(.v2)
11+
],
12+
products: [
13+
.library(name: "AdaMCPCore", targets: ["AdaMCPCore"]),
14+
.library(name: "AdaMCPServer", targets: ["AdaMCPServer"]),
15+
.library(name: "AdaMCPPlugin", targets: ["AdaMCPPlugin"])
16+
],
17+
dependencies: [
18+
.package(path: "../AdaEngine"),
19+
.package(url: "https://github.com/apple/swift-system.git", from: "1.0.0"),
20+
.package(url: "https://github.com/apple/swift-log.git", from: "1.6.0"),
21+
.package(url: "https://github.com/apple/swift-nio.git", from: "2.74.0"),
22+
.package(url: "https://github.com/modelcontextprotocol/swift-sdk.git", from: "0.11.0")
23+
],
24+
targets: [
25+
.target(
26+
name: "AdaMCPCore",
27+
dependencies: [
28+
.product(name: "AdaEngine", package: "AdaEngine"),
29+
.product(name: "MCP", package: "swift-sdk"),
30+
.product(name: "Logging", package: "swift-log")
31+
]
32+
),
33+
.target(
34+
name: "AdaMCPServer",
35+
dependencies: [
36+
"AdaMCPCore",
37+
.product(name: "MCP", package: "swift-sdk"),
38+
.product(name: "Logging", package: "swift-log"),
39+
.product(name: "NIOCore", package: "swift-nio"),
40+
.product(name: "NIOPosix", package: "swift-nio"),
41+
.product(name: "NIOHTTP1", package: "swift-nio")
42+
]
43+
),
44+
.target(
45+
name: "AdaMCPPlugin",
46+
dependencies: [
47+
"AdaMCPCore",
48+
"AdaMCPServer",
49+
.product(name: "AdaEngine", package: "AdaEngine"),
50+
.product(name: "Logging", package: "swift-log")
51+
]
52+
),
53+
.testTarget(
54+
name: "AdaMCPTests",
55+
dependencies: [
56+
"AdaMCPCore",
57+
"AdaMCPServer",
58+
"AdaMCPPlugin",
59+
.product(name: "AdaEngine", package: "AdaEngine"),
60+
.product(name: "MCP", package: "swift-sdk"),
61+
.product(name: "SystemPackage", package: "swift-system")
62+
],
63+
path: "Tests/AdaMCPTests"
64+
)
65+
]
66+
)

README.md

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
# AdaMCP
2+
3+
`AdaMCP` exposes a live AdaEngine application as an MCP server. It is built for inspection-first workflows: world/entity/resource introspection, render capture, and AdaUI tree inspection with a small set of safe UI actions.
4+
5+
## Modules
6+
7+
- `AdaMCPCore`: runtime surface, type introspection, AdaUI inspection, and tool/resource payloads.
8+
- `AdaMCPServer`: MCP server bootstrapping plus HTTP and stdio transports.
9+
- `AdaMCPPlugin`: AdaEngine plugin for embedding `AdaMCP` directly into an app.
10+
11+
## Current platform support
12+
13+
`AdaMCP` currently ships for Apple platforms used by AdaEngine hosts today: `macOS 15+`, `iOS 18+`, `tvOS 18+`, and `visionOS 2+`.
14+
15+
The important distinction is:
16+
17+
- MCP clients can live anywhere and connect over HTTP or stdio.
18+
- The host runtime now builds on the broader Apple surface, while `stdio` remains primarily a local host transport and HTTP is the main cross-device option.
19+
20+
So `SloppyClient` no longer needs a `macOS`-only gate just to embed the MCP plugin.
21+
22+
## What you get
23+
24+
- World, entity, component, resource, and asset inspection.
25+
- Screenshot capture for live render output.
26+
- AdaUI inspection tools such as `ui.list_windows`, `ui.get_tree`, `ui.get_node`, `ui.find_nodes`, and `ui.hit_test`.
27+
- AdaUI diagnostics and safe actions such as focus traversal, deterministic tap, and scroll-to-node.
28+
- MCP resources under `ada://...`, including `ada://ui/windows`, `ada://ui/window/{id}`, `ada://ui/tree/{id}`, and `ada://ui/node/{windowId}/{nodeRef}`.
29+
30+
## Embedding in an AdaEngine app
31+
32+
Until the package is published, add it as a local SwiftPM dependency:
33+
34+
```swift
35+
dependencies: [
36+
.package(path: "../AdaMCP")
37+
]
38+
```
39+
40+
Then add the plugin to your app:
41+
42+
```swift
43+
import AdaEngine
44+
import AdaMCPPlugin
45+
46+
@main
47+
struct ExampleApp: App {
48+
var body: some AppScene {
49+
WindowGroup {
50+
RootView()
51+
}
52+
.addPlugins(
53+
MCPPlugin(configuration: .init(
54+
enableHTTP: true,
55+
enableStdio: true,
56+
host: "127.0.0.1",
57+
port: 25102,
58+
endpoint: "/mcp",
59+
serverName: "example-app",
60+
serverVersion: "0.1.0",
61+
instructions: "Inspect the live AdaEngine runtime."
62+
))
63+
)
64+
}
65+
}
66+
```
67+
68+
## Runtime architecture
69+
70+
`AdaMCP` is split into three layers:
71+
72+
1. `AdaMCPRuntime` builds tool/resource payloads from live AdaEngine and AdaUI state.
73+
2. `AdaMCPServerFactory` exposes that runtime through the official MCP Swift SDK.
74+
3. `MCPPlugin` wires the runtime into an AdaEngine app and starts HTTP and/or stdio transports.
75+
76+
## AdaUI surface
77+
78+
AdaUI support is intentionally inspection-first. The main flow is:
79+
80+
1. Locate windows or nodes.
81+
2. Inspect the live tree and layout diagnostics.
82+
3. Apply a limited, deterministic action.
83+
4. Re-read the tree or diagnostics to verify the result.
84+
85+
External callers should target nodes by `accessibilityIdentifier`. `runtimeId` is returned in payloads as a session-local helper, but it is not intended to be a durable contract.
86+
87+
## Documentation
88+
89+
- [DocC landing page](./Sources/AdaMCPCore/AdaMCPCore.docc/AdaMCPCore.md)
90+
- [Getting Started With AdaMCP](./Sources/AdaMCPCore/AdaMCPCore.docc/Getting-Started-With-AdaMCP.md)
91+
- [AdaUI Inspection and Automation](./Sources/AdaMCPCore/AdaMCPCore.docc/AdaUI-Inspection-and-Automation.md)
92+
93+
Swift Package Index configuration lives in [`./.spi.yml`](./.spi.yml).
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# ``AdaMCPCore``
2+
3+
`AdaMCPCore` turns a live AdaEngine application into an inspection-friendly MCP runtime.
4+
5+
## Overview
6+
7+
Use `AdaMCPCore` when you need to expose live engine state to tools, agents, or external automation:
8+
9+
- Inspect worlds, entities, components, resources, and assets.
10+
- Capture render output for visual verification.
11+
- Inspect AdaUI windows and view trees.
12+
- Run a small, deterministic set of AdaUI actions such as focus traversal, scrolling, and tapping a resolved node.
13+
14+
`AdaMCP` is transport-agnostic at the runtime layer. `AdaMCPServer` handles MCP server wiring, while `AdaMCPPlugin` embeds the runtime inside an AdaEngine app.
15+
16+
## Topics
17+
18+
### Essentials
19+
20+
- <doc:Getting-Started-With-AdaMCP>
21+
- <doc:AdaUI-Inspection-and-Automation>
22+
23+
### Core types
24+
25+
- ``AdaMCPRuntime``
26+
- ``MCPServerConfiguration``
27+
- ``RenderCaptureService``
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
# AdaUI Inspection and Automation
2+
3+
The AdaUI surface is designed to be inspection-first. The main goal is to let an MCP client understand the live UI tree before attempting any action.
4+
5+
## Selector model
6+
7+
External callers should prefer `accessibilityIdentifier`.
8+
9+
- `accessibilityIdentifier` is the stable external selector.
10+
- `runtimeId` is returned by the runtime for follow-up calls inside the same session.
11+
12+
If a selector is ambiguous, the runtime returns a structured error with candidate nodes. If a selector does not match anything, the runtime returns a stable not-found error.
13+
14+
## Inspection tools
15+
16+
The core read tools are:
17+
18+
- `ui.list_windows`
19+
- `ui.get_window`
20+
- `ui.get_tree`
21+
- `ui.get_node`
22+
- `ui.find_nodes`
23+
- `ui.hit_test`
24+
- `ui.get_layout_diagnostics`
25+
26+
These tools return stable DTOs such as `UIWindowSummary`, `UIWindowSnapshot`, `UINodeSummary`, `UINodeSnapshot`, `UIHitTestResult`, and `UILayoutDiagnostics`.
27+
28+
## AdaUI resources
29+
30+
The same surface is available through resources:
31+
32+
- `ada://ui/windows`
33+
- `ada://ui/window/{windowId}`
34+
- `ada://ui/tree/{windowId}`
35+
- `ada://ui/node/{windowId}/{nodeRef}`
36+
37+
`nodeRef` supports:
38+
39+
- `accessibility:<id>`
40+
- `runtime:<object-identifier>`
41+
42+
## Safe actions
43+
44+
Version 1 intentionally limits automation to deterministic actions:
45+
46+
- `ui.focus_node`
47+
- `ui.focus_next`
48+
- `ui.focus_previous`
49+
- `ui.scroll_to_node`
50+
- `ui.tap_node`
51+
- `ui.set_debug_overlay`
52+
53+
These are designed to work on resolved nodes, not on arbitrary event injection.
54+
55+
## Debug overlays
56+
57+
`ui.set_debug_overlay` controls developer-facing drawing modes:
58+
59+
- `off`
60+
- `layout_bounds`
61+
- `focused_node`
62+
- `hit_test_target`
63+
64+
The overlay uses the existing debug drawing path inside AdaUI rather than a parallel renderer.
65+
66+
## Explicit non-goals for v1
67+
68+
The current surface does not expose:
69+
70+
- arbitrary coordinate-based event injection
71+
- raw keyboard event synthesis
72+
- direct text entry APIs such as `ui.type_text`
73+
74+
Those workflows need a stricter contract around focus ownership and text input semantics. For now, the safe pattern is inspection, deterministic action, and verification.
75+
76+
## Typical workflow
77+
78+
```json
79+
{
80+
"sequence": [
81+
"ui.list_windows",
82+
"ui.find_nodes(accessibilityIdentifier: \"button.primary\")",
83+
"ui.get_layout_diagnostics(accessibilityIdentifier: \"button.primary\")",
84+
"ui.tap_node(accessibilityIdentifier: \"button.primary\")",
85+
"ui.get_tree"
86+
]
87+
}
88+
```
89+
90+
This keeps the MCP client grounded in the live tree instead of guessing from screen coordinates.

0 commit comments

Comments
 (0)