@kilocode/protocol 0.0.0-stage → 0.0.0-v2-9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/dist/api.d.ts +58 -0
  2. package/dist/api.js +77 -0
  3. package/dist/client.d.ts +63 -0
  4. package/dist/client.js +55 -0
  5. package/dist/errors.d.ts +193 -0
  6. package/dist/errors.js +152 -0
  7. package/dist/groups/agent.d.ts +225 -0
  8. package/dist/groups/agent.js +30 -0
  9. package/dist/groups/command.d.ts +15 -0
  10. package/dist/groups/command.js +20 -0
  11. package/dist/groups/config.d.ts +14 -0
  12. package/dist/groups/config.js +32 -0
  13. package/dist/groups/credential.d.ts +93 -0
  14. package/dist/groups/credential.js +50 -0
  15. package/dist/groups/debug.d.ts +22 -0
  16. package/dist/groups/debug.js +49 -0
  17. package/dist/groups/event.d.ts +14853 -0
  18. package/dist/groups/event.js +51 -0
  19. package/dist/groups/form.d.ts +138 -0
  20. package/dist/groups/form.js +18 -0
  21. package/dist/groups/fs.d.ts +48 -0
  22. package/dist/groups/fs.js +73 -0
  23. package/dist/groups/generate.d.ts +33 -0
  24. package/dist/groups/generate.js +23 -0
  25. package/dist/groups/integration.d.ts +1724 -0
  26. package/dist/groups/integration.js +169 -0
  27. package/dist/groups/location.d.ts +23 -0
  28. package/dist/groups/location.js +45 -0
  29. package/dist/groups/mcp.d.ts +103 -0
  30. package/dist/groups/mcp.js +114 -0
  31. package/dist/groups/message.d.ts +1416 -0
  32. package/dist/groups/message.js +77 -0
  33. package/dist/groups/migration.d.ts +28 -0
  34. package/dist/groups/migration.js +21 -0
  35. package/dist/groups/model.d.ts +399 -0
  36. package/dist/groups/model.js +33 -0
  37. package/dist/groups/permission.d.ts +175 -0
  38. package/dist/groups/permission.js +110 -0
  39. package/dist/groups/persistent-pty.d.ts +242 -0
  40. package/dist/groups/persistent-pty.js +92 -0
  41. package/dist/groups/plugin.d.ts +86 -0
  42. package/dist/groups/plugin.js +45 -0
  43. package/dist/groups/project.d.ts +83 -0
  44. package/dist/groups/project.js +33 -0
  45. package/dist/groups/provider.d.ts +231 -0
  46. package/dist/groups/provider.js +34 -0
  47. package/dist/groups/pty.d.ts +151 -0
  48. package/dist/groups/pty.js +112 -0
  49. package/dist/groups/reference.d.ts +25 -0
  50. package/dist/groups/reference.js +33 -0
  51. package/dist/groups/rpc.d.ts +21 -0
  52. package/dist/groups/rpc.js +24 -0
  53. package/dist/groups/server.d.ts +44 -0
  54. package/dist/groups/server.js +53 -0
  55. package/dist/groups/session.d.ts +11380 -0
  56. package/dist/groups/session.js +611 -0
  57. package/dist/groups/shell.d.ts +125 -0
  58. package/dist/groups/shell.js +64 -0
  59. package/dist/groups/skill.d.ts +19 -0
  60. package/dist/groups/skill.js +20 -0
  61. package/dist/groups/vcs.d.ts +87 -0
  62. package/dist/groups/vcs.js +93 -0
  63. package/dist/groups/voice.d.ts +93 -0
  64. package/dist/groups/voice.js +89 -0
  65. package/dist/groups/websearch.d.ts +29 -0
  66. package/dist/groups/websearch.js +34 -0
  67. package/dist/groups/worktree.d.ts +41 -0
  68. package/dist/groups/worktree.js +52 -0
  69. package/dist/middleware/authorization.d.ts +13 -0
  70. package/dist/middleware/authorization.js +6 -0
  71. package/dist/middleware/schema-error.d.ts +13 -0
  72. package/dist/middleware/schema-error.js +4 -0
  73. package/dist/simulation.d.ts +1830 -0
  74. package/dist/simulation.js +497 -0
  75. package/package.json +45 -4
  76. package/README.md +0 -3
@@ -0,0 +1,169 @@
1
+ import { Integration } from "@opencode/schema/integration";
2
+ import { Location } from "@opencode/schema/location";
3
+ import { Form } from "@opencode/schema/form";
4
+ import { Schema } from "effect";
5
+ import { HttpApiEndpoint, HttpApiGroup, HttpApiSchema, OpenApi } from "effect/http-api";
6
+ import { IntegrationAttemptNotFoundError, IntegrationMethodNotFoundError, IntegrationNotFoundError, InvalidRequestError, } from "../errors.js";
7
+ import { LocationQuery, locationQueryOpenApi } from "./location.js";
8
+ export const IntegrationGroup = HttpApiGroup.make("server.integration")
9
+ .add(HttpApiEndpoint.get("integration.list", "/api/integration", {
10
+ query: LocationQuery,
11
+ success: Location.response(Schema.Array(Integration.Info)),
12
+ })
13
+ .annotateMerge(locationQueryOpenApi)
14
+ .annotateMerge(OpenApi.annotations({
15
+ identifier: "integration.list",
16
+ summary: "List integrations",
17
+ description: "Retrieve available integrations and their authentication methods.",
18
+ })))
19
+ .add(HttpApiEndpoint.get("integration.get", "/api/integration/:integrationID", {
20
+ params: { integrationID: Integration.ID },
21
+ query: LocationQuery,
22
+ success: Location.response(Integration.Info),
23
+ error: IntegrationNotFoundError,
24
+ })
25
+ .annotateMerge(locationQueryOpenApi)
26
+ .annotateMerge(OpenApi.annotations({
27
+ identifier: "integration.get",
28
+ summary: "Get integration",
29
+ description: "Retrieve one integration and its authentication methods.",
30
+ })))
31
+ .add(HttpApiEndpoint.post("integration.wellknown.add", "/api/experimental/integration/wellknown", {
32
+ query: LocationQuery,
33
+ payload: Schema.Struct({ url: Schema.String }),
34
+ success: HttpApiSchema.NoContent,
35
+ error: InvalidRequestError,
36
+ })
37
+ .annotateMerge(locationQueryOpenApi)
38
+ .annotateMerge(OpenApi.annotations({
39
+ identifier: "experimental.integration.wellknown.add",
40
+ summary: "Add wellknown integration",
41
+ description: "Discover and persist an experimental wellknown integration source.",
42
+ })))
43
+ .add(HttpApiEndpoint.post("integration.connect.key", "/api/integration/:integrationID/connect/key", {
44
+ params: { integrationID: Integration.ID },
45
+ query: LocationQuery,
46
+ payload: Schema.Struct({
47
+ key: Schema.String,
48
+ answer: Schema.optional(Form.Answer),
49
+ label: Schema.optional(Schema.String),
50
+ }),
51
+ success: HttpApiSchema.NoContent,
52
+ error: [IntegrationNotFoundError, InvalidRequestError],
53
+ })
54
+ .annotateMerge(locationQueryOpenApi)
55
+ .annotateMerge(OpenApi.annotations({
56
+ identifier: "integration.connect.key",
57
+ summary: "Connect with key",
58
+ description: "Run a key authentication method and store the resulting credential.",
59
+ })))
60
+ .add(HttpApiEndpoint.post("integration.connect.external", "/api/integration/:integrationID/connect/external", {
61
+ params: { integrationID: Integration.ID },
62
+ query: LocationQuery,
63
+ payload: Schema.Struct({
64
+ methodID: Integration.MethodID,
65
+ answer: Schema.optional(Form.Answer),
66
+ label: Schema.optional(Schema.String),
67
+ }),
68
+ success: HttpApiSchema.NoContent,
69
+ error: [IntegrationNotFoundError, InvalidRequestError],
70
+ })
71
+ .annotateMerge(locationQueryOpenApi)
72
+ .annotateMerge(OpenApi.annotations({
73
+ identifier: "integration.connect.external",
74
+ summary: "Connect with external credentials",
75
+ description: "Run an external authentication method and store a reference to its credential source.",
76
+ })))
77
+ .add(HttpApiEndpoint.post("integration.oauth.connect", "/api/integration/:integrationID/connect/oauth", {
78
+ params: { integrationID: Integration.ID },
79
+ query: LocationQuery,
80
+ payload: Schema.Struct({
81
+ methodID: Integration.MethodID,
82
+ answer: Schema.optional(Form.Answer),
83
+ label: Schema.optional(Schema.String),
84
+ }),
85
+ success: Location.response(Integration.Attempt),
86
+ error: InvalidRequestError,
87
+ })
88
+ .annotateMerge(locationQueryOpenApi)
89
+ .annotateMerge(OpenApi.annotations({
90
+ identifier: "integration.oauth.connect",
91
+ summary: "Begin OAuth connection",
92
+ description: "Start an OAuth attempt and return the authorization details.",
93
+ })))
94
+ .add(HttpApiEndpoint.get("integration.oauth.status", "/api/integration/:integrationID/connect/oauth/:attemptID", {
95
+ params: { integrationID: Integration.ID, attemptID: Integration.AttemptID },
96
+ query: LocationQuery,
97
+ success: Location.response(Integration.AttemptStatus),
98
+ error: [IntegrationNotFoundError, IntegrationAttemptNotFoundError],
99
+ })
100
+ .annotateMerge(locationQueryOpenApi)
101
+ .annotateMerge(OpenApi.annotations({
102
+ identifier: "integration.oauth.status",
103
+ summary: "Get OAuth attempt status",
104
+ description: "Poll the current status of an OAuth attempt.",
105
+ })))
106
+ .add(HttpApiEndpoint.post("integration.oauth.complete", "/api/integration/:integrationID/connect/oauth/:attemptID/complete", {
107
+ params: { integrationID: Integration.ID, attemptID: Integration.AttemptID },
108
+ query: LocationQuery,
109
+ payload: Schema.Struct({ code: Schema.optional(Schema.String) }),
110
+ success: HttpApiSchema.NoContent,
111
+ error: [IntegrationNotFoundError, IntegrationAttemptNotFoundError, InvalidRequestError],
112
+ })
113
+ .annotateMerge(locationQueryOpenApi)
114
+ .annotateMerge(OpenApi.annotations({
115
+ identifier: "integration.oauth.complete",
116
+ summary: "Complete OAuth connection",
117
+ description: "Complete a code-based OAuth attempt and store the resulting credential.",
118
+ })))
119
+ .add(HttpApiEndpoint.delete("integration.oauth.cancel", "/api/integration/:integrationID/connect/oauth/:attemptID", {
120
+ params: { integrationID: Integration.ID, attemptID: Integration.AttemptID },
121
+ query: LocationQuery,
122
+ success: HttpApiSchema.NoContent,
123
+ })
124
+ .annotateMerge(locationQueryOpenApi)
125
+ .annotateMerge(OpenApi.annotations({
126
+ identifier: "integration.oauth.cancel",
127
+ summary: "Cancel OAuth connection",
128
+ description: "Cancel an OAuth attempt and release its resources.",
129
+ })))
130
+ .add(HttpApiEndpoint.post("integration.command.connect", "/api/integration/:integrationID/connect/command", {
131
+ params: { integrationID: Integration.ID },
132
+ query: LocationQuery,
133
+ payload: Schema.Struct({
134
+ methodID: Integration.MethodID,
135
+ label: Schema.optional(Schema.String),
136
+ }),
137
+ success: Location.response(Integration.CommandAttempt),
138
+ error: [IntegrationNotFoundError, IntegrationMethodNotFoundError, InvalidRequestError],
139
+ })
140
+ .annotateMerge(locationQueryOpenApi)
141
+ .annotateMerge(OpenApi.annotations({
142
+ identifier: "integration.command.connect",
143
+ summary: "Begin command connection",
144
+ description: "Start a command authentication attempt.",
145
+ })))
146
+ .add(HttpApiEndpoint.get("integration.command.status", "/api/integration/:integrationID/connect/command/:attemptID", {
147
+ params: { integrationID: Integration.ID, attemptID: Integration.AttemptID },
148
+ query: LocationQuery,
149
+ success: Location.response(Integration.CommandAttemptStatus),
150
+ error: [IntegrationNotFoundError, IntegrationAttemptNotFoundError],
151
+ })
152
+ .annotateMerge(locationQueryOpenApi)
153
+ .annotateMerge(OpenApi.annotations({
154
+ identifier: "integration.command.status",
155
+ summary: "Get command attempt status",
156
+ description: "Poll the current status and output of a command authentication attempt.",
157
+ })))
158
+ .add(HttpApiEndpoint.delete("integration.command.cancel", "/api/integration/:integrationID/connect/command/:attemptID", {
159
+ params: { integrationID: Integration.ID, attemptID: Integration.AttemptID },
160
+ query: LocationQuery,
161
+ success: HttpApiSchema.NoContent,
162
+ })
163
+ .annotateMerge(locationQueryOpenApi)
164
+ .annotateMerge(OpenApi.annotations({
165
+ identifier: "integration.command.cancel",
166
+ summary: "Cancel command connection",
167
+ description: "Cancel a command authentication attempt and terminate its process.",
168
+ })))
169
+ .annotateMerge(OpenApi.annotations({ title: "integration", description: "Integration discovery and authentication routes." }));
@@ -0,0 +1,23 @@
1
+ import { Context, Schema } from "effect";
2
+ import { HttpApiEndpoint, HttpApiGroup, HttpApiMiddleware, HttpApiSchema } from "effect/http-api";
3
+ import { ServiceUnavailableError } from "../errors.js";
4
+ export declare const LocationQuery: Schema.Struct<{
5
+ readonly location: Schema.optional<Schema.Struct<{
6
+ readonly directory: Schema.optional<Schema.String>;
7
+ }>>;
8
+ }>;
9
+ export declare const locationQueryOpenApi: Context.Context<never>;
10
+ export declare const makeLocationGroup: <LocationId extends HttpApiMiddleware.AnyId, LocationService>(locationMiddleware: Context.Key<LocationId, LocationService>) => HttpApiGroup.HttpApiGroup<"server.location", HttpApiEndpoint.HttpApiEndpoint<"location.get", "GET", "/api/location", never, Schema.toCodecStringTree<Schema.Struct<{
11
+ readonly location: Schema.optional<Schema.Struct<{
12
+ readonly directory: Schema.optional<Schema.String>;
13
+ }>>;
14
+ }>>, never, never, Schema.toCodecJson<Schema.Struct<{
15
+ readonly directory: Schema.brand<Schema.String, "AbsolutePath">;
16
+ readonly project: Schema.Struct<{
17
+ readonly id: Schema.brand<Schema.String, "Project.ID"> & {
18
+ global: string & import("effect/Brand").Brand<"Project.ID">;
19
+ };
20
+ readonly directory: Schema.brand<Schema.String, "AbsolutePath">;
21
+ readonly canonical: Schema.brand<Schema.String, "AbsolutePath">;
22
+ }>;
23
+ }>>, never, LocationId, HttpApiMiddleware.Requires<LocationId>> | HttpApiEndpoint.HttpApiEndpoint<"location.reload", "POST", "/api/location/reload", never, never, never, never, Schema.toCodecJson<HttpApiSchema.NoContent>, Schema.toCodecJson<typeof ServiceUnavailableError>, never, never>, false>;
@@ -0,0 +1,45 @@
1
+ import { Location } from "@opencode/schema/location";
2
+ import { Context, Schema } from "effect";
3
+ import { HttpApiEndpoint, HttpApiGroup, HttpApiMiddleware, HttpApiSchema, OpenApi } from "effect/http-api";
4
+ import { ServiceUnavailableError } from "../errors.js";
5
+ export const LocationQuery = Schema.Struct({
6
+ location: Schema.optional(Schema.Struct({
7
+ directory: Schema.optional(Schema.String),
8
+ })),
9
+ }).annotate({ identifier: "LocationQuery" });
10
+ export const locationQueryOpenApi = OpenApi.annotations({
11
+ transform: (operation) => {
12
+ const parameters = operation.parameters;
13
+ if (!Array.isArray(parameters))
14
+ return operation;
15
+ return {
16
+ ...operation,
17
+ parameters: parameters.map((parameter) => parameter?.name === "location" && parameter?.in === "query"
18
+ ? { ...parameter, style: "deepObject", explode: true }
19
+ : parameter),
20
+ };
21
+ },
22
+ });
23
+ // Middleware is applied per endpoint: reload acts on every loaded location and
24
+ // must not boot the caller's location first.
25
+ export const makeLocationGroup = (locationMiddleware) => HttpApiGroup.make("server.location")
26
+ .add(HttpApiEndpoint.get("location.get", "/api/location", {
27
+ query: LocationQuery,
28
+ success: Location.PublicInfo,
29
+ })
30
+ .middleware(locationMiddleware)
31
+ .annotateMerge(locationQueryOpenApi)
32
+ .annotateMerge(OpenApi.annotations({
33
+ identifier: "location.get",
34
+ summary: "Get location",
35
+ description: "Resolve the requested location or the server default location.",
36
+ })))
37
+ .add(HttpApiEndpoint.post("location.reload", "/api/location/reload", {
38
+ success: HttpApiSchema.NoContent,
39
+ error: ServiceUnavailableError,
40
+ }).annotateMerge(OpenApi.annotations({
41
+ identifier: "location.reload",
42
+ summary: "Reload configuration",
43
+ description: "Shut down and rebuild every loaded location. Pending permissions and forms are cancelled; running sessions continue with fresh services at the next step boundary. Emits location.shutdown for client recovery and responds once all replacement builds settle.",
44
+ })))
45
+ .annotateMerge(OpenApi.annotations({ title: "location" }));
@@ -0,0 +1,103 @@
1
+ import { Mcp } from "@opencode/schema/mcp";
2
+ import { Context, Schema } from "effect";
3
+ import { HttpApiEndpoint, HttpApiGroup, HttpApiMiddleware, HttpApiSchema } from "effect/http-api";
4
+ import { ForbiddenError, LocationNotFoundError, McpServerNotFoundError } from "../errors.js";
5
+ export declare const MCP_CLIENT_PATH = "/api/experimental/client-mcp";
6
+ export declare function hasMcpClientTicketURL(url: URL): boolean;
7
+ export declare const makeMcpGroup: <LocationId extends HttpApiMiddleware.AnyId, LocationService>(locationMiddleware: Context.Key<LocationId, LocationService>) => HttpApiGroup.HttpApiGroup<"server.mcp", HttpApiEndpoint.HttpApiEndpoint<"mcp.add", "PUT", "/api/experimental/mcp/:server", Schema.toCodecStringTree<Schema.Struct<{
8
+ server: Schema.String;
9
+ }>>, Schema.toCodecStringTree<Schema.Struct<{
10
+ readonly location: Schema.optional<Schema.Struct<{
11
+ readonly directory: Schema.optional<Schema.String>;
12
+ }>>;
13
+ }>>, Schema.toCodecJson<Schema.Struct<{
14
+ readonly config: Schema.toTaggedUnion<"type", readonly [typeof Mcp.LocalConfig, typeof Mcp.RemoteConfig]>;
15
+ }>>, never, Schema.toCodecJson<HttpApiSchema.NoContent>, never, LocationId, HttpApiMiddleware.Requires<LocationId>> | HttpApiEndpoint.HttpApiEndpoint<"mcp.list", "GET", "/api/mcp", never, Schema.toCodecStringTree<Schema.Struct<{
16
+ readonly location: Schema.optional<Schema.Struct<{
17
+ readonly directory: Schema.optional<Schema.String>;
18
+ }>>;
19
+ }>>, never, never, Schema.toCodecJson<Schema.Struct<{
20
+ readonly location: Schema.Struct<{
21
+ readonly directory: Schema.brand<Schema.String, "AbsolutePath">;
22
+ }>;
23
+ readonly data: Schema.$Array<Schema.Struct<{
24
+ readonly name: Schema.String;
25
+ readonly status: Schema.toTaggedUnion<"status", readonly [Schema.Struct<{
26
+ readonly status: Schema.Literal<"connected">;
27
+ }>, Schema.Struct<{
28
+ readonly status: Schema.Literal<"pending">;
29
+ }>, Schema.Struct<{
30
+ readonly status: Schema.Literal<"disabled">;
31
+ }>, Schema.Struct<{
32
+ readonly status: Schema.Literal<"failed">;
33
+ readonly error: Schema.String;
34
+ }>, Schema.Struct<{
35
+ readonly status: Schema.Literal<"needs_auth">;
36
+ readonly error: Schema.String;
37
+ }>]>;
38
+ readonly integrationID: Schema.decodeTo<Schema.optional<Schema.toType<Schema.brand<Schema.String, "Integration.ID">>>, Schema.optionalKey<Schema.brand<Schema.String, "Integration.ID">>, never, never>;
39
+ }>>;
40
+ }>>, never, LocationId, HttpApiMiddleware.Requires<LocationId>> | HttpApiEndpoint.HttpApiEndpoint<"mcp.resource.catalog", "GET", "/api/mcp/resource", never, Schema.toCodecStringTree<Schema.Struct<{
41
+ readonly location: Schema.optional<Schema.Struct<{
42
+ readonly directory: Schema.optional<Schema.String>;
43
+ }>>;
44
+ }>>, never, never, Schema.toCodecJson<Schema.Struct<{
45
+ readonly location: Schema.Struct<{
46
+ readonly directory: Schema.brand<Schema.String, "AbsolutePath">;
47
+ }>;
48
+ readonly data: Schema.Struct<{
49
+ readonly resources: Schema.$Array<Schema.Struct<{
50
+ readonly server: Schema.String;
51
+ readonly name: Schema.String;
52
+ readonly uri: Schema.String;
53
+ readonly description: Schema.decodeTo<Schema.optional<Schema.toType<Schema.String>>, Schema.optionalKey<Schema.String>, never, never>;
54
+ readonly mimeType: Schema.decodeTo<Schema.optional<Schema.toType<Schema.String>>, Schema.optionalKey<Schema.String>, never, never>;
55
+ }>>;
56
+ readonly templates: Schema.$Array<Schema.Struct<{
57
+ readonly server: Schema.String;
58
+ readonly name: Schema.String;
59
+ readonly uriTemplate: Schema.String;
60
+ readonly description: Schema.decodeTo<Schema.optional<Schema.toType<Schema.String>>, Schema.optionalKey<Schema.String>, never, never>;
61
+ readonly mimeType: Schema.decodeTo<Schema.optional<Schema.toType<Schema.String>>, Schema.optionalKey<Schema.String>, never, never>;
62
+ }>>;
63
+ }>;
64
+ }>>, never, LocationId, HttpApiMiddleware.Requires<LocationId>> | HttpApiEndpoint.HttpApiEndpoint<"mcp.connect", "POST", "/api/experimental/mcp/:server/connect", Schema.toCodecStringTree<Schema.Struct<{
65
+ server: Schema.String;
66
+ }>>, Schema.toCodecStringTree<Schema.Struct<{
67
+ readonly location: Schema.optional<Schema.Struct<{
68
+ readonly directory: Schema.optional<Schema.String>;
69
+ }>>;
70
+ }>>, never, never, Schema.toCodecJson<HttpApiSchema.NoContent>, Schema.toCodecJson<typeof McpServerNotFoundError>, LocationId, HttpApiMiddleware.Requires<LocationId>> | HttpApiEndpoint.HttpApiEndpoint<"mcp.disconnect", "POST", "/api/experimental/mcp/:server/disconnect", Schema.toCodecStringTree<Schema.Struct<{
71
+ server: Schema.String;
72
+ }>>, Schema.toCodecStringTree<Schema.Struct<{
73
+ readonly location: Schema.optional<Schema.Struct<{
74
+ readonly directory: Schema.optional<Schema.String>;
75
+ }>>;
76
+ }>>, never, never, Schema.toCodecJson<HttpApiSchema.NoContent>, Schema.toCodecJson<typeof McpServerNotFoundError>, LocationId, HttpApiMiddleware.Requires<LocationId>> | HttpApiEndpoint.HttpApiEndpoint<"mcp.remove", "DELETE", "/api/experimental/mcp/:server", Schema.toCodecStringTree<Schema.Struct<{
77
+ server: Schema.String;
78
+ }>>, Schema.toCodecStringTree<Schema.Struct<{
79
+ readonly location: Schema.optional<Schema.Struct<{
80
+ readonly directory: Schema.optional<Schema.String>;
81
+ }>>;
82
+ }>>, never, never, Schema.toCodecJson<HttpApiSchema.NoContent>, Schema.toCodecJson<typeof McpServerNotFoundError>, LocationId, HttpApiMiddleware.Requires<LocationId>> | HttpApiEndpoint.HttpApiEndpoint<"mcp.clientToken", "POST", "/api/experimental/client-mcp/token", never, Schema.toCodecStringTree<Schema.Struct<{
83
+ readonly location: Schema.optional<Schema.Struct<{
84
+ readonly directory: Schema.optional<Schema.String>;
85
+ }>>;
86
+ readonly name: Schema.String;
87
+ }>>, never, Schema.toCodecStringTree<Schema.Struct<{
88
+ readonly "x-opencode-ticket": Schema.optional<Schema.String>;
89
+ }>>, Schema.toCodecJson<Schema.Struct<{
90
+ readonly location: Schema.Struct<{
91
+ readonly directory: Schema.brand<Schema.String, "AbsolutePath">;
92
+ }>;
93
+ readonly data: Schema.Struct<{
94
+ readonly ticket: Schema.String;
95
+ readonly expires_in: Schema.Int;
96
+ }>;
97
+ }>>, Schema.toCodecJson<typeof ForbiddenError>, LocationId, HttpApiMiddleware.Requires<LocationId>> | HttpApiEndpoint.HttpApiEndpoint<"mcp.client", "GET", "/api/experimental/client-mcp", never, Schema.toCodecStringTree<Schema.Struct<{
98
+ readonly location: Schema.optional<Schema.Struct<{
99
+ readonly directory: Schema.optional<Schema.String>;
100
+ }>>;
101
+ readonly name: Schema.String;
102
+ readonly ticket: Schema.optional<Schema.String>;
103
+ }>>, never, never, Schema.toCodecJson<Schema.Boolean>, Schema.toCodecJson<typeof LocationNotFoundError | typeof ForbiddenError>, never, never>, false>;
@@ -0,0 +1,114 @@
1
+ import { Mcp } from "@opencode/schema/mcp";
2
+ import { Location } from "@opencode/schema/location";
3
+ import { PtyTicket } from "@opencode/schema/pty-ticket";
4
+ import { Context, Schema } from "effect";
5
+ import { HttpApiEndpoint, HttpApiGroup, HttpApiMiddleware, HttpApiSchema, OpenApi } from "effect/http-api";
6
+ import { ForbiddenError, LocationNotFoundError, McpServerNotFoundError } from "../errors.js";
7
+ import { LocationQuery, locationQueryOpenApi } from "./location.js";
8
+ import { PTY_CONNECT_TICKET_QUERY, PTY_CONNECT_TOKEN_HEADER } from "./pty.js";
9
+ export const MCP_CLIENT_PATH = "/api/experimental/client-mcp";
10
+ export function hasMcpClientTicketURL(url) {
11
+ return url.pathname === MCP_CLIENT_PATH && !!url.searchParams.get(PTY_CONNECT_TICKET_QUERY);
12
+ }
13
+ const McpClientQuery = Schema.Struct({
14
+ ...LocationQuery.fields,
15
+ name: Schema.String.check(Schema.isPattern(/^[a-zA-Z0-9_-]+$/)),
16
+ });
17
+ export const makeMcpGroup = (locationMiddleware) => HttpApiGroup.make("server.mcp")
18
+ .add(HttpApiEndpoint.get("mcp.list", "/api/mcp", {
19
+ query: LocationQuery,
20
+ success: Location.response(Schema.Array(Mcp.Server)),
21
+ })
22
+ .annotateMerge(locationQueryOpenApi)
23
+ .annotateMerge(OpenApi.annotations({
24
+ identifier: "mcp.list",
25
+ summary: "List MCP servers",
26
+ description: "Retrieve configured MCP servers and their connection status.",
27
+ })))
28
+ .add(HttpApiEndpoint.put("mcp.add", "/api/experimental/mcp/:server", {
29
+ params: { server: Schema.String },
30
+ query: LocationQuery,
31
+ // Wrapped in a struct because the client codegen flattens payload fields and cannot
32
+ // represent a top-level union payload.
33
+ payload: Schema.Struct({ config: Mcp.ServerConfig }),
34
+ success: HttpApiSchema.NoContent,
35
+ })
36
+ .annotateMerge(locationQueryOpenApi)
37
+ .annotateMerge(OpenApi.annotations({
38
+ identifier: "experimental.mcp.add",
39
+ summary: "Add MCP server",
40
+ description: "Add an MCP server at runtime or replace an existing one, connecting it immediately.",
41
+ })))
42
+ .add(HttpApiEndpoint.delete("mcp.remove", "/api/experimental/mcp/:server", {
43
+ params: { server: Schema.String },
44
+ query: LocationQuery,
45
+ success: HttpApiSchema.NoContent,
46
+ error: McpServerNotFoundError,
47
+ })
48
+ .annotateMerge(locationQueryOpenApi)
49
+ .annotateMerge(OpenApi.annotations({
50
+ identifier: "experimental.mcp.remove",
51
+ summary: "Remove MCP server",
52
+ description: "Stop an MCP server and remove it from the runtime set until restart.",
53
+ })))
54
+ .add(HttpApiEndpoint.post("mcp.connect", "/api/experimental/mcp/:server/connect", {
55
+ params: { server: Schema.String },
56
+ query: LocationQuery,
57
+ success: HttpApiSchema.NoContent,
58
+ error: McpServerNotFoundError,
59
+ })
60
+ .annotateMerge(locationQueryOpenApi)
61
+ .annotateMerge(OpenApi.annotations({
62
+ identifier: "experimental.mcp.connect",
63
+ summary: "Connect MCP server",
64
+ description: "Connect an MCP server at runtime, overriding a disabled configuration until restart.",
65
+ })))
66
+ .add(HttpApiEndpoint.post("mcp.disconnect", "/api/experimental/mcp/:server/disconnect", {
67
+ params: { server: Schema.String },
68
+ query: LocationQuery,
69
+ success: HttpApiSchema.NoContent,
70
+ error: McpServerNotFoundError,
71
+ })
72
+ .annotateMerge(locationQueryOpenApi)
73
+ .annotateMerge(OpenApi.annotations({
74
+ identifier: "experimental.mcp.disconnect",
75
+ summary: "Disconnect MCP server",
76
+ description: "Disconnect an MCP server at runtime, removing its tools until reconnected.",
77
+ })))
78
+ .add(HttpApiEndpoint.get("mcp.resource.catalog", "/api/mcp/resource", {
79
+ query: LocationQuery,
80
+ success: Location.response(Mcp.ResourceCatalog),
81
+ })
82
+ .annotateMerge(locationQueryOpenApi)
83
+ .annotateMerge(OpenApi.annotations({
84
+ identifier: "mcp.resource.catalog",
85
+ summary: "List MCP resources",
86
+ description: "Retrieve resources and resource templates from connected MCP servers.",
87
+ })))
88
+ .add(HttpApiEndpoint.post("mcp.clientToken", `${MCP_CLIENT_PATH}/token`, {
89
+ query: McpClientQuery,
90
+ headers: Schema.Struct({ [PTY_CONNECT_TOKEN_HEADER]: Schema.optional(Schema.String) }),
91
+ success: Location.response(PtyTicket.ConnectToken),
92
+ error: ForbiddenError,
93
+ })
94
+ .annotateMerge(locationQueryOpenApi)
95
+ .annotateMerge(OpenApi.annotations({
96
+ identifier: "experimental.mcp.client.token",
97
+ summary: "Create client MCP WebSocket token",
98
+ description: "Create a short-lived single-use ticket for opening a client MCP WebSocket connection from a browser.",
99
+ })))
100
+ // The client handler checks origin and ticket before it boots the location itself.
101
+ .middleware(locationMiddleware)
102
+ .add(HttpApiEndpoint.get("mcp.client", MCP_CLIENT_PATH, {
103
+ query: Schema.Struct({ ...McpClientQuery.fields, [PTY_CONNECT_TICKET_QUERY]: Schema.optional(Schema.String) }),
104
+ success: Schema.Boolean,
105
+ error: [ForbiddenError, LocationNotFoundError],
106
+ })
107
+ .annotateMerge(locationQueryOpenApi)
108
+ .annotateMerge(OpenApi.annotations({
109
+ identifier: "experimental.mcp.client",
110
+ summary: "Attach client MCP server",
111
+ description: "Upgrade to a WebSocket over which the client hosts an MCP server named after `name`; its tools stay available while the socket is open. One JSON-RPC message per text frame. Experimental: may change without compatibility guarantees.",
112
+ transform: (operation) => ({ ...operation, "x-websocket": true }),
113
+ })))
114
+ .annotateMerge(OpenApi.annotations({ title: "mcp", description: "MCP server and resource routes." }));