httpapi-mcp.test.ts 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. import { afterEach, describe, expect, test } from "bun:test"
  2. import { Context } from "effect"
  3. import { ExperimentalHttpApiServer } from "../../src/server/routes/instance/httpapi/server"
  4. import { McpPaths } from "../../src/server/routes/instance/httpapi/mcp"
  5. import { Instance } from "../../src/project/instance"
  6. import { Log } from "../../src/util"
  7. import { resetDatabase } from "../fixture/db"
  8. import { tmpdir } from "../fixture/fixture"
  9. void Log.init({ print: false })
  10. const context = Context.empty() as Context.Context<unknown>
  11. function request(route: string, directory: string, init?: RequestInit) {
  12. const headers = new Headers(init?.headers)
  13. headers.set("x-opencode-directory", directory)
  14. return ExperimentalHttpApiServer.webHandler().handler(
  15. new Request(`http://localhost${route}`, {
  16. ...init,
  17. headers,
  18. }),
  19. context,
  20. )
  21. }
  22. afterEach(async () => {
  23. await Instance.disposeAll()
  24. await resetDatabase()
  25. })
  26. describe("mcp HttpApi", () => {
  27. test("serves status endpoint", async () => {
  28. await using tmp = await tmpdir({
  29. config: {
  30. mcp: {
  31. demo: {
  32. type: "local",
  33. command: ["echo", "demo"],
  34. enabled: false,
  35. },
  36. },
  37. },
  38. })
  39. const response = await request(McpPaths.status, tmp.path)
  40. expect(response.status).toBe(200)
  41. expect(await response.json()).toEqual({ demo: { status: "disabled" } })
  42. })
  43. test("serves add, connect, and disconnect endpoints", async () => {
  44. await using tmp = await tmpdir({
  45. config: {
  46. mcp: {
  47. demo: {
  48. type: "local",
  49. command: ["echo", "demo"],
  50. enabled: false,
  51. },
  52. },
  53. },
  54. })
  55. const added = await request(McpPaths.status, tmp.path, {
  56. method: "POST",
  57. headers: { "content-type": "application/json" },
  58. body: JSON.stringify({
  59. name: "added",
  60. config: {
  61. type: "local",
  62. command: ["echo", "added"],
  63. enabled: false,
  64. },
  65. }),
  66. })
  67. expect(added.status).toBe(200)
  68. expect(await added.json()).toMatchObject({ added: { status: "disabled" } })
  69. const connected = await request("/mcp/demo/connect", tmp.path, { method: "POST" })
  70. expect(connected.status).toBe(200)
  71. expect(await connected.json()).toBe(true)
  72. const disconnected = await request("/mcp/demo/disconnect", tmp.path, { method: "POST" })
  73. expect(disconnected.status).toBe(200)
  74. expect(await disconnected.json()).toBe(true)
  75. })
  76. test("serves deterministic OAuth endpoints", async () => {
  77. await using tmp = await tmpdir({
  78. config: {
  79. mcp: {
  80. demo: {
  81. type: "local",
  82. command: ["echo", "demo"],
  83. enabled: false,
  84. },
  85. },
  86. },
  87. })
  88. const start = await request("/mcp/demo/auth", tmp.path, { method: "POST" })
  89. expect(start.status).toBe(400)
  90. const authenticate = await request("/mcp/demo/auth/authenticate", tmp.path, { method: "POST" })
  91. expect(authenticate.status).toBe(400)
  92. const removed = await request("/mcp/demo/auth", tmp.path, { method: "DELETE" })
  93. expect(removed.status).toBe(200)
  94. expect(await removed.json()).toEqual({ success: true })
  95. })
  96. })