Skip to content

Commit a14c22d

Browse files
authored
feat(httpapi): bridge mcp control endpoints (anomalyco#24403)
1 parent 58c6587 commit a14c22d

5 files changed

Lines changed: 294 additions & 32 deletions

File tree

.opencode/skills/effect/SKILL.md

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,30 @@
11
---
22
name: effect
3-
description: Answer questions about the Effect framework
3+
description: Work with Effect v4 / effect-smol TypeScript code in this repo
44
---
55

66
# Effect
77

8-
This codebase uses Effect, a framework for writing typescript.
8+
This codebase uses Effect for typed, composable TypeScript services, schemas, and workflows.
99

10-
## How to Answer Effect Questions
10+
## Source Of Truth
1111

12-
1. Clone the Effect repository: `https://github.com/Effect-TS/effect-smol` to
13-
`.opencode/references/effect-smol` in this project NOT the skill folder.
14-
2. Use the explore agent to search the codebase for answers about Effect patterns, APIs, and concepts
15-
3. Provide responses based on the actual Effect source code and documentation
12+
Use the current Effect v4 / effect-smol source, not memory or older Effect v2/v3 examples.
13+
14+
1. If `.opencode/references/effect-smol` is missing, clone `https://github.com/Effect-TS/effect-smol` there. Do this in the project, not in the skill folder.
15+
2. Search `.opencode/references/effect-smol` for exact APIs, examples, tests, and naming patterns before answering or implementing Effect-specific code.
16+
3. Also inspect existing repo code for local house style before introducing new patterns.
17+
4. Prefer answers and implementations backed by specific source files or nearby repo examples.
1618

1719
## Guidelines
1820

19-
- Always use the explore agent with the cloned repository when answering Effect-related questions
20-
- Reference specific files and patterns found in the Effect codebase
21-
- Do not answer from memory - always verify against the source
21+
- Prefer current Effect v4 APIs and project-local patterns over old blog posts, examples, or package-memory guesses.
22+
- Use `Effect.gen(function* () { ... })` for multi-step workflows.
23+
- Use `Effect.fn("Name")` or `Effect.fnUntraced(...)` for named effects when adding reusable service methods or important workflows.
24+
- Prefer Effect `Schema` for API and domain data shapes. Use branded schemas for IDs and `Schema.TaggedErrorClass` for typed domain errors when modeling new error surfaces.
25+
- Keep HTTP handlers thin: decode input, read request context, call services, and map transport errors. Put business rules in services.
26+
- In Effect service code, prefer Effect-aware platform abstractions and dependencies over ad hoc promises where the surrounding code already does so.
27+
- Keep layer composition explicit. Avoid broad hidden provisioning that makes missing dependencies hard to see.
28+
- In tests, prefer the repo's existing Effect test helpers and live tests for filesystem, git, child process, locks, or timing behavior.
29+
- Do not introduce `any`, non-null assertions, unchecked casts, or older Effect APIs just to satisfy types.
30+
- Do not answer from memory. Verify against `.opencode/references/effect-smol` or nearby code first.

packages/opencode/specs/effect/http-api.md

Lines changed: 173 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -178,8 +178,8 @@ Use raw Effect HTTP routes where `HttpApi` does not fit. The goal is deleting Ho
178178
| `config` | `bridged` | read, providers, update |
179179
| `project` | `bridged` | list, current, git init, update |
180180
| `file` | `bridged` partial | find text/file/symbol, list/content/status |
181-
| `mcp` | `bridged` partial | status only |
182-
| `workspace` | `bridged` | list, get, enter |
181+
| `mcp` | `bridged` partial | status, add, connect/disconnect; OAuth remains |
182+
| `workspace` | `bridged` partial | adaptor/list/status; create/remove/session-restore remain |
183183
| top-level instance routes | `bridged` | path, vcs, command, agent, skill, lsp, formatter, dispose |
184184
| experimental JSON routes | `bridged` partial | console reads, tool ids, worktree list/mutations, resource list; global session list remains later |
185185
| `session` | `later/special` | large stateful surface plus streaming |
@@ -188,24 +188,180 @@ Use raw Effect HTTP routes where `HttpApi` does not fit. The goal is deleting Ho
188188
| `pty` | `special` | websocket |
189189
| `tui` | `special` | UI bridge |
190190

191+
## Full Route Checklist
192+
193+
This checklist tracks bridge parity only. Checked routes are available through the experimental `HttpApi` bridge; Hono deletion is tracked separately by the deletion checklist above.
194+
195+
### Top-Level Instance Routes
196+
197+
- [x] `POST /instance/dispose` - dispose active instance after response.
198+
- [x] `GET /path` - current directory and worktree paths.
199+
- [x] `GET /vcs` - current VCS status.
200+
- [x] `GET /vcs/diff` - VCS diff summary.
201+
- [x] `GET /command` - command catalog.
202+
- [x] `GET /agent` - agent catalog.
203+
- [x] `GET /skill` - skill catalog.
204+
- [x] `GET /lsp` - LSP status.
205+
- [x] `GET /formatter` - formatter status.
206+
207+
### Config Routes
208+
209+
- [x] `GET /config` - read config.
210+
- [x] `PATCH /config` - update config and dispose active instance after response.
211+
- [x] `GET /config/providers` - config provider summary.
212+
213+
### Project Routes
214+
215+
- [x] `GET /project` - list projects.
216+
- [x] `GET /project/current` - current project.
217+
- [x] `POST /project/git/init` - initialize git and reload active instance after response.
218+
- [x] `PATCH /project/:projectID` - update project metadata.
219+
220+
### Provider Routes
221+
222+
- [x] `GET /provider` - list providers.
223+
- [x] `GET /provider/auth` - list provider auth methods.
224+
- [x] `POST /provider/:providerID/oauth/authorize` - start provider OAuth.
225+
- [x] `POST /provider/:providerID/oauth/callback` - finish provider OAuth.
226+
227+
### Question Routes
228+
229+
- [x] `GET /question` - list questions.
230+
- [x] `POST /question/:requestID/reply` - reply to question.
231+
- [x] `POST /question/:requestID/reject` - reject question.
232+
233+
### Permission Routes
234+
235+
- [x] `GET /permission` - list permission requests.
236+
- [x] `POST /permission/:requestID/reply` - reply to permission request.
237+
238+
### File Routes
239+
240+
- [x] `GET /find` - text search.
241+
- [x] `GET /find/file` - file search.
242+
- [x] `GET /find/symbol` - symbol search.
243+
- [x] `GET /file` - list directory entries.
244+
- [x] `GET /file/content` - read file content.
245+
- [x] `GET /file/status` - file status.
246+
247+
### MCP Routes
248+
249+
- [x] `GET /mcp` - MCP status.
250+
- [x] `POST /mcp` - add MCP server at runtime.
251+
- [ ] `POST /mcp/:name/auth` - start MCP OAuth.
252+
- [ ] `POST /mcp/:name/auth/callback` - finish MCP OAuth callback.
253+
- [ ] `POST /mcp/:name/auth/authenticate` - run MCP OAuth authenticate flow.
254+
- [ ] `DELETE /mcp/:name/auth` - remove MCP OAuth credentials.
255+
- [x] `POST /mcp/:name/connect` - connect MCP server.
256+
- [x] `POST /mcp/:name/disconnect` - disconnect MCP server.
257+
258+
### Experimental Routes
259+
260+
- [x] `GET /experimental/console` - active Console provider metadata.
261+
- [x] `GET /experimental/console/orgs` - switchable Console orgs.
262+
- [ ] `POST /experimental/console/switch` - switch active Console org.
263+
- [x] `GET /experimental/tool/ids` - tool IDs.
264+
- [ ] `GET /experimental/tool` - tools for provider/model.
265+
- [x] `GET /experimental/worktree` - list worktrees.
266+
- [x] `POST /experimental/worktree` - create worktree.
267+
- [x] `DELETE /experimental/worktree` - remove worktree.
268+
- [x] `POST /experimental/worktree/reset` - reset worktree.
269+
- [ ] `GET /experimental/session` - global session list.
270+
- [x] `GET /experimental/resource` - MCP resources.
271+
272+
### Workspace Routes
273+
274+
- [x] `GET /experimental/workspace/adaptor` - list workspace adaptors.
275+
- [ ] `POST /experimental/workspace` - create workspace.
276+
- [x] `GET /experimental/workspace` - list workspaces.
277+
- [x] `GET /experimental/workspace/status` - workspace status.
278+
- [ ] `DELETE /experimental/workspace/:id` - remove workspace.
279+
- [ ] `POST /experimental/workspace/:id/session-restore` - restore session into workspace.
280+
281+
### Sync Routes
282+
283+
- [ ] `POST /sync/start` - start workspace sync.
284+
- [ ] `POST /sync/replay` - replay sync events.
285+
- [ ] `POST /sync/history` - list sync event history.
286+
287+
### Session Routes
288+
289+
- [ ] `GET /session` - list sessions.
290+
- [ ] `GET /session/status` - session status map.
291+
- [ ] `GET /session/:sessionID` - get session.
292+
- [ ] `GET /session/:sessionID/children` - get child sessions.
293+
- [ ] `GET /session/:sessionID/todo` - get session todos.
294+
- [ ] `POST /session` - create session.
295+
- [ ] `DELETE /session/:sessionID` - delete session.
296+
- [ ] `PATCH /session/:sessionID` - update session metadata.
297+
- [ ] `POST /session/:sessionID/init` - run project init command.
298+
- [ ] `POST /session/:sessionID/fork` - fork session.
299+
- [ ] `POST /session/:sessionID/abort` - abort session.
300+
- [ ] `POST /session/:sessionID/share` - share session.
301+
- [ ] `GET /session/:sessionID/diff` - session diff.
302+
- [ ] `DELETE /session/:sessionID/share` - unshare session.
303+
- [ ] `POST /session/:sessionID/summarize` - summarize session.
304+
- [ ] `GET /session/:sessionID/message` - list session messages.
305+
- [ ] `GET /session/:sessionID/message/:messageID` - get message.
306+
- [ ] `DELETE /session/:sessionID/message/:messageID` - delete message.
307+
- [ ] `DELETE /session/:sessionID/message/:messageID/part/:partID` - delete part.
308+
- [ ] `PATCH /session/:sessionID/message/:messageID/part/:partID` - update part.
309+
- [ ] `POST /session/:sessionID/message` - prompt with streaming response.
310+
- [ ] `POST /session/:sessionID/prompt_async` - async prompt.
311+
- [ ] `POST /session/:sessionID/command` - run command.
312+
- [ ] `POST /session/:sessionID/shell` - run shell command.
313+
- [ ] `POST /session/:sessionID/revert` - revert message.
314+
- [ ] `POST /session/:sessionID/unrevert` - restore reverted messages.
315+
- [ ] `POST /session/:sessionID/permissions/:permissionID` - deprecated permission response route.
316+
317+
### Event Routes
318+
319+
- [ ] `GET /event` - SSE event stream; replace with raw Effect HTTP, not `HttpApi`.
320+
321+
### PTY Routes
322+
323+
- [ ] `GET /pty` - list PTY sessions.
324+
- [ ] `POST /pty` - create PTY session.
325+
- [ ] `GET /pty/:ptyID` - get PTY session.
326+
- [ ] `PUT /pty/:ptyID` - update PTY session.
327+
- [ ] `DELETE /pty/:ptyID` - remove PTY session.
328+
- [ ] `GET /pty/:ptyID/connect` - PTY websocket; replace with raw Effect HTTP/websocket support.
329+
330+
### TUI Routes
331+
332+
- [ ] `POST /tui/append-prompt` - append prompt.
333+
- [ ] `POST /tui/open-help` - open help.
334+
- [ ] `POST /tui/open-sessions` - open sessions.
335+
- [ ] `POST /tui/open-themes` - open themes.
336+
- [ ] `POST /tui/open-models` - open models.
337+
- [ ] `POST /tui/submit-prompt` - submit prompt.
338+
- [ ] `POST /tui/clear-prompt` - clear prompt.
339+
- [ ] `POST /tui/execute-command` - execute command.
340+
- [ ] `POST /tui/show-toast` - show toast.
341+
- [ ] `POST /tui/publish` - publish TUI event.
342+
- [ ] `POST /tui/select-session` - select session.
343+
- [ ] `GET /tui/control/next` - get next TUI request.
344+
- [ ] `POST /tui/control/response` - submit TUI control response.
345+
191346
## Remaining PR Plan
192347

193348
Prefer smaller PRs from here so route behavior and SDK/OpenAPI fallout stays reviewable.
194349

195-
1. Bridge `PATCH /project/:projectID`.
196-
2. Bridge MCP add/connect/disconnect routes.
197-
3. Bridge MCP OAuth routes: start, callback, authenticate, remove.
198-
4. Bridge experimental console switch and tool list routes.
199-
5. Bridge experimental global session list.
200-
6. Bridge sync start/replay/history routes.
201-
7. Bridge session read routes: list, status, get, children, todo, diff, messages.
202-
8. Bridge session lifecycle mutation routes: create, delete, update, fork, abort.
203-
9. Bridge session share/summary/message/part mutation routes.
204-
10. Replace event SSE with non-Hono Effect HTTP.
205-
11. Replace pty websocket/control routes with non-Hono Effect HTTP.
206-
12. Replace tui bridge routes or explicitly isolate them behind a non-Hono compatibility layer.
207-
13. Switch OpenAPI/SDK generation to Effect routes and compare SDK output.
208-
14. Flip ported JSON routes default-on, keep a short fallback, then delete replaced Hono route files.
350+
1. [x] Bridge `PATCH /project/:projectID`.
351+
2. [x] Bridge MCP add/connect/disconnect routes.
352+
3. [ ] Bridge MCP OAuth routes: start, callback, authenticate, remove.
353+
4. [ ] Bridge experimental console switch and tool list routes.
354+
5. [ ] Bridge experimental global session list.
355+
6. [ ] Bridge workspace create/remove/session-restore routes.
356+
7. [ ] Bridge sync start/replay/history routes.
357+
8. [ ] Bridge session read routes: list, status, get, children, todo, diff, messages.
358+
9. [ ] Bridge session lifecycle mutation routes: create, delete, update, fork, abort.
359+
10. [ ] Bridge session share/summary/message/part mutation routes.
360+
11. [ ] Replace event SSE with non-Hono Effect HTTP.
361+
12. [ ] Replace pty websocket/control routes with non-Hono Effect HTTP.
362+
13. [ ] Replace tui bridge routes or explicitly isolate them behind a non-Hono compatibility layer.
363+
14. [ ] Switch OpenAPI/SDK generation to Effect routes and compare SDK output.
364+
15. [ ] Flip ported JSON routes default-on, keep a short fallback, then delete replaced Hono route files.
209365

210366
## Checklist
211367

@@ -216,7 +372,7 @@ Prefer smaller PRs from here so route behavior and SDK/OpenAPI fallout stays rev
216372
- [x] Attach auth middleware in route modules.
217373
- [x] Support `auth_token` as a query security scheme.
218374
- [x] Add bridge-level auth and instance tests.
219-
- [ ] Complete exact Hono route inventory.
375+
- [x] Complete exact Hono route inventory.
220376
- [x] Resolve implemented-but-unmounted route groups.
221377
- [x] Port remaining top-level JSON reads.
222378
- [ ] Generate SDK/OpenAPI from Effect routes.

packages/opencode/src/server/routes/instance/httpapi/mcp.ts

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,20 @@
11
import { MCP } from "@/mcp"
2+
import { ConfigMCP } from "@/config/mcp"
23
import { Effect, Layer, Schema } from "effect"
34
import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
45
import { Authorization } from "./auth"
56

7+
const AddPayload = Schema.Struct({
8+
name: Schema.String,
9+
config: ConfigMCP.Info,
10+
}).annotate({ identifier: "McpAddInput" })
11+
12+
const StatusMap = Schema.Record(Schema.String, MCP.Status)
13+
614
export const McpPaths = {
715
status: "/mcp",
16+
connect: "/mcp/:name/connect",
17+
disconnect: "/mcp/:name/disconnect",
818
} as const
919

1020
export const McpApi = HttpApi.make("mcp")
@@ -20,6 +30,34 @@ export const McpApi = HttpApi.make("mcp")
2030
description: "Get the status of all Model Context Protocol (MCP) servers.",
2131
}),
2232
),
33+
HttpApiEndpoint.post("add", McpPaths.status, {
34+
payload: AddPayload,
35+
success: StatusMap,
36+
}).annotateMerge(
37+
OpenApi.annotations({
38+
identifier: "mcp.add",
39+
summary: "Add MCP server",
40+
description: "Dynamically add a new Model Context Protocol (MCP) server to the system.",
41+
}),
42+
),
43+
HttpApiEndpoint.post("connect", McpPaths.connect, {
44+
params: { name: Schema.String },
45+
success: Schema.Boolean,
46+
}).annotateMerge(
47+
OpenApi.annotations({
48+
identifier: "mcp.connect",
49+
description: "Connect an MCP server.",
50+
}),
51+
),
52+
HttpApiEndpoint.post("disconnect", McpPaths.disconnect, {
53+
params: { name: Schema.String },
54+
success: Schema.Boolean,
55+
}).annotateMerge(
56+
OpenApi.annotations({
57+
identifier: "mcp.disconnect",
58+
description: "Disconnect an MCP server.",
59+
}),
60+
),
2361
)
2462
.annotateMerge(
2563
OpenApi.annotations({
@@ -45,6 +83,24 @@ export const mcpHandlers = Layer.unwrap(
4583
return yield* mcp.status()
4684
})
4785

48-
return HttpApiBuilder.group(McpApi, "mcp", (handlers) => handlers.handle("status", status))
86+
const add = Effect.fn("McpHttpApi.add")(function* (ctx: { payload: typeof AddPayload.Type }) {
87+
const payload = Schema.decodeUnknownSync(AddPayload)(ctx.payload)
88+
const result = (yield* mcp.add(payload.name, payload.config)).status
89+
return Schema.decodeUnknownSync(StatusMap)("status" in result ? { [payload.name]: result } : result)
90+
})
91+
92+
const connect = Effect.fn("McpHttpApi.connect")(function* (ctx: { params: { name: string } }) {
93+
yield* mcp.connect(ctx.params.name)
94+
return true
95+
})
96+
97+
const disconnect = Effect.fn("McpHttpApi.disconnect")(function* (ctx: { params: { name: string } }) {
98+
yield* mcp.disconnect(ctx.params.name)
99+
return true
100+
})
101+
102+
return HttpApiBuilder.group(McpApi, "mcp", (handlers) =>
103+
handlers.handle("status", status).handle("add", add).handle("connect", connect).handle("disconnect", disconnect),
104+
)
49105
}),
50106
).pipe(Layer.provide(MCP.defaultLayer))

packages/opencode/src/server/routes/instance/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,9 @@ export const InstanceRoutes = (upgrade: UpgradeWebSocket): Hono => {
7979
app.get(InstancePaths.lsp, (c) => handler(c.req.raw, context))
8080
app.get(InstancePaths.formatter, (c) => handler(c.req.raw, context))
8181
app.get(McpPaths.status, (c) => handler(c.req.raw, context))
82+
app.post(McpPaths.status, (c) => handler(c.req.raw, context))
83+
app.post(McpPaths.connect, (c) => handler(c.req.raw, context))
84+
app.post(McpPaths.disconnect, (c) => handler(c.req.raw, context))
8285
}
8386

8487
return app

0 commit comments

Comments
 (0)