@executioncontrolprotocol/mcp 0.10.0

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.
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@executioncontrolprotocol/mcp",
3
+ "version": "0.10.0",
4
+ "description": "MCP server adapter for ECP environments",
5
+ "license": "Apache-2.0",
6
+ "type": "module",
7
+ "main": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js"
13
+ }
14
+ },
15
+ "scripts": {
16
+ "build": "tsc -b"
17
+ },
18
+ "peerDependencies": {
19
+ "@executioncontrolprotocol/core": "^0.10.0",
20
+ "@executioncontrolprotocol/types": "^0.10.0",
21
+ "zod": "^3.24.0"
22
+ },
23
+ "dependencies": {
24
+ "@executioncontrolprotocol/node": "0.10.0",
25
+ "@modelcontextprotocol/sdk": "^1.12.0"
26
+ },
27
+ "devDependencies": {
28
+ "@executioncontrolprotocol/core": "0.10.0",
29
+ "@executioncontrolprotocol/format-toon": "0.10.0",
30
+ "@executioncontrolprotocol/types": "0.10.0",
31
+ "typescript": "^5.7.0",
32
+ "zod": "^3.24.0"
33
+ }
34
+ }
package/src/index.ts ADDED
@@ -0,0 +1,436 @@
1
+ import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js"
2
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
3
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"
4
+ import { z } from "zod"
5
+ import type { Ecp } from "@executioncontrolprotocol/core"
6
+ import type { EcpEncodeInput, RunResult, WorkflowManifest } from "@executioncontrolprotocol/types"
7
+ import { ECP_FORMATS } from "@executioncontrolprotocol/types"
8
+ import { createServer, type Server } from "node:http"
9
+
10
+ /** Options for MCP server creation. @category MCP */
11
+ export interface CreateEcpMcpServerOptions {
12
+ /** Initialized operational ECP instance. */
13
+ ecp: Ecp
14
+ name?: string
15
+ version?: string
16
+ }
17
+
18
+ /** JSON mime type used for ECP MCP resources. */
19
+ const JSON_MIME = "application/json"
20
+
21
+ /** In-process run store shared across MCP server instances in this process. */
22
+ const runs = new Map<string, RunResult>()
23
+
24
+ /** Wrap a value as an MCP tool text result. */
25
+ function toolText(value: unknown): { content: { type: "text"; text: string }[] } {
26
+ return { content: [{ type: "text", text: JSON.stringify(value, null, 2) }] }
27
+ }
28
+
29
+ /** Wrap a value as MCP resource JSON contents. */
30
+ function jsonContents(uri: URL, value: unknown) {
31
+ return {
32
+ contents: [
33
+ { uri: uri.href, mimeType: JSON_MIME, text: JSON.stringify(value, null, 2) },
34
+ ],
35
+ }
36
+ }
37
+
38
+ /** Register operational tools on the server. */
39
+ function registerEcpTools(server: McpServer, ecp: Ecp): void {
40
+ server.tool(
41
+ "ecp.describe_environment",
42
+ { query: z.record(z.unknown()).optional() },
43
+ async ({ query }) => toolText(await ecp.describe(query))
44
+ )
45
+
46
+ server.tool(
47
+ "ecp.search",
48
+ { query: z.string(), options: z.record(z.unknown()).optional() },
49
+ async ({ query, options: searchOpts }) =>
50
+ toolText(await ecp.search(query, searchOpts as import("@executioncontrolprotocol/types").SearchOptions))
51
+ )
52
+
53
+ server.tool(
54
+ "ecp.validate_workflow",
55
+ { workflow: z.record(z.unknown()) },
56
+ async ({ workflow }) =>
57
+ toolText(await ecp.validate(workflow as unknown as WorkflowManifest))
58
+ )
59
+
60
+ server.tool(
61
+ "ecp.run_workflow",
62
+ {
63
+ workflow: z.record(z.unknown()),
64
+ input: z.record(z.unknown()).optional(),
65
+ dryRun: z.boolean().optional(),
66
+ },
67
+ async ({ workflow, input, dryRun }) => {
68
+ const result = await ecp.run(workflow as unknown as WorkflowManifest, {
69
+ input,
70
+ dryRun,
71
+ })
72
+ runs.set(result.run.id, result)
73
+ return toolText(result)
74
+ }
75
+ )
76
+
77
+ const formatSchema = z.enum(["json", "toon", "fluent"])
78
+
79
+ server.tool(
80
+ "ecp.encode",
81
+ {
82
+ source: z.union([z.record(z.unknown()), z.string()]),
83
+ format: formatSchema.optional(),
84
+ compact: z.boolean().optional(),
85
+ },
86
+ async ({ source, format, compact }) => {
87
+ let op = ecp.encode(source as EcpEncodeInput["source"])
88
+ if (format === "toon") op = op.uses("@executioncontrolprotocol/format-toon")
89
+ else if (format === "fluent") op = op.uses("@executioncontrolprotocol/format-fluent")
90
+ else op = op.uses("@executioncontrolprotocol/format-json")
91
+ if (compact) op = op.compact()
92
+ return toolText(await op.process())
93
+ }
94
+ )
95
+
96
+ server.tool(
97
+ "ecp.decode",
98
+ {
99
+ content: z.union([z.record(z.unknown()), z.string()]),
100
+ format: z.enum(["json", "toon"]).optional(),
101
+ strict: z.boolean().optional(),
102
+ targetSchema: z.string().optional(),
103
+ },
104
+ async ({ content, format, strict, targetSchema }) => {
105
+ let op = ecp.decode(content)
106
+ if (format === "toon") op = op.uses("@executioncontrolprotocol/format-toon")
107
+ else op = op.uses("@executioncontrolprotocol/format-json")
108
+ if (strict) op = op.strict()
109
+ if (targetSchema) op = op.to(targetSchema as "@executioncontrolprotocol.workflow")
110
+ else if (!format || format === ECP_FORMATS.JSON) op = op.to("@executioncontrolprotocol.workflow")
111
+ return toolText(await op.process())
112
+ }
113
+ )
114
+
115
+ server.tool("ecp.get_run_status", { runId: z.string() }, async ({ runId }) =>
116
+ toolText(runs.get(runId) ?? { error: "Run not found", runId })
117
+ )
118
+ }
119
+
120
+ /** Register discovery resources on the server. */
121
+ function registerEcpResources(server: McpServer, ecp: Ecp): void {
122
+ server.registerResource(
123
+ "environment",
124
+ "ecp://environment/describe",
125
+ {
126
+ title: "Environment descriptor",
127
+ description: "Full ECP environment descriptor: runtime, extensions, capabilities, policies.",
128
+ mimeType: JSON_MIME,
129
+ },
130
+ async (uri) => jsonContents(uri, await ecp.describe())
131
+ )
132
+
133
+ server.registerResource(
134
+ "capabilities",
135
+ "ecp://capabilities",
136
+ {
137
+ title: "Capabilities",
138
+ description: "All capabilities available in this environment.",
139
+ mimeType: JSON_MIME,
140
+ },
141
+ async (uri) => jsonContents(uri, (await ecp.describe()).capabilities)
142
+ )
143
+
144
+ server.registerResource(
145
+ "policies",
146
+ "ecp://policies",
147
+ {
148
+ title: "Policies",
149
+ description: "All policies governing this environment.",
150
+ mimeType: JSON_MIME,
151
+ },
152
+ async (uri) => jsonContents(uri, (await ecp.describe()).policies)
153
+ )
154
+
155
+ // Reserved expansion `{+id}` so capability ids containing `/` and `.`
156
+ // (e.g. `@executioncontrolprotocol/test.echo`) match and round-trip correctly.
157
+ server.registerResource(
158
+ "capability",
159
+ new ResourceTemplate("ecp://capabilities/{+id}", {
160
+ list: async () => {
161
+ const descriptor = await ecp.describe()
162
+ return {
163
+ resources: descriptor.capabilities.map((c) => ({
164
+ uri: `ecp://capabilities/${c.id}`,
165
+ name: c.id,
166
+ ...(c.label ? { title: c.label } : {}),
167
+ mimeType: JSON_MIME,
168
+ })),
169
+ }
170
+ },
171
+ }),
172
+ {
173
+ title: "Capability detail",
174
+ description: "A single capability's schemas and metadata by id.",
175
+ mimeType: JSON_MIME,
176
+ },
177
+ async (uri, variables) => {
178
+ const id = String(variables.id)
179
+ const descriptor = await ecp.describe()
180
+ const capability = descriptor.capabilities.find((c) => c.id === id)
181
+ return jsonContents(uri, capability ?? { error: "Capability not found", id })
182
+ }
183
+ )
184
+
185
+ server.registerResource(
186
+ "run",
187
+ new ResourceTemplate("ecp://runs/{runId}", {
188
+ list: async () => ({
189
+ resources: [...runs.keys()].map((id) => ({
190
+ uri: `ecp://runs/${id}`,
191
+ name: id,
192
+ mimeType: JSON_MIME,
193
+ })),
194
+ }),
195
+ }),
196
+ {
197
+ title: "Run detail",
198
+ description: "Result of a workflow run executed via ecp.run_workflow in this process.",
199
+ mimeType: JSON_MIME,
200
+ },
201
+ async (uri, variables) => {
202
+ const runId = String(variables.runId)
203
+ return jsonContents(uri, runs.get(runId) ?? { error: "Run not found", runId })
204
+ }
205
+ )
206
+ }
207
+
208
+ /** Format the environment's capabilities as a bullet list for prompts. */
209
+ function capabilityList(
210
+ capabilities: { id: string; label?: string }[]
211
+ ): string {
212
+ if (capabilities.length === 0) return "(no capabilities are registered)"
213
+ return capabilities
214
+ .map((c) => `- ${c.id}${c.label ? ` — ${c.label}` : ""}`)
215
+ .join("\n")
216
+ }
217
+
218
+ /** Register guidance prompts on the server. */
219
+ function registerEcpPrompts(server: McpServer, ecp: Ecp): void {
220
+ server.registerPrompt(
221
+ "ecp.author_workflow",
222
+ {
223
+ title: "Author an ECP workflow",
224
+ description: "Guide the agent to build a valid @executioncontrolprotocol.workflow manifest for a goal.",
225
+ argsSchema: { goal: z.string() },
226
+ },
227
+ async ({ goal }) => {
228
+ const descriptor = await ecp.describe()
229
+ return {
230
+ messages: [
231
+ {
232
+ role: "user",
233
+ content: {
234
+ type: "text",
235
+ text: [
236
+ `Author an ECP workflow manifest (schema "@executioncontrolprotocol.workflow", version "1.0") for this goal:`,
237
+ "",
238
+ goal,
239
+ "",
240
+ "Only use capabilities available in this environment:",
241
+ capabilityList(descriptor.capabilities),
242
+ "",
243
+ "Rules:",
244
+ "- Output a single JSON workflow manifest.",
245
+ "- Each step's `uses` must be one of the capability ids above.",
246
+ "- Use `as` to commit a step output to state; reference prior state with { \"$ref\": \"state.<key>\" }.",
247
+ "- Do not include runtime, extension, policy, or secret configuration.",
248
+ "Then call ecp.validate_workflow to confirm it is valid before running.",
249
+ ].join("\n"),
250
+ },
251
+ },
252
+ ],
253
+ }
254
+ }
255
+ )
256
+
257
+ server.registerPrompt(
258
+ "ecp.repair_workflow",
259
+ {
260
+ title: "Repair an ECP workflow",
261
+ description: "Guide the agent to fix a workflow using validation errors.",
262
+ argsSchema: { workflow: z.string(), errors: z.string().optional() },
263
+ },
264
+ ({ workflow, errors }) => ({
265
+ messages: [
266
+ {
267
+ role: "user",
268
+ content: {
269
+ type: "text",
270
+ text: [
271
+ "Fix this ECP workflow manifest so it validates against the environment.",
272
+ "",
273
+ "Workflow:",
274
+ workflow,
275
+ "",
276
+ "Validation errors:",
277
+ errors && errors.trim().length > 0
278
+ ? errors
279
+ : "(none provided — call ecp.validate_workflow to obtain them)",
280
+ "",
281
+ "Return the corrected JSON workflow manifest only, then re-validate.",
282
+ ].join("\n"),
283
+ },
284
+ },
285
+ ],
286
+ })
287
+ )
288
+
289
+ server.registerPrompt(
290
+ "ecp.explain_environment",
291
+ {
292
+ title: "Explain the environment",
293
+ description: "Summarize the capabilities and policies available in this environment.",
294
+ },
295
+ async () => {
296
+ const descriptor = await ecp.describe()
297
+ const policies =
298
+ descriptor.policies.length > 0
299
+ ? descriptor.policies
300
+ .map((p) => `- ${p.id}${p.summary ? ` — ${p.summary}` : ""}`)
301
+ .join("\n")
302
+ : "(no policies are bound)"
303
+ return {
304
+ messages: [
305
+ {
306
+ role: "user",
307
+ content: {
308
+ type: "text",
309
+ text: [
310
+ `Explain what this ECP environment ("${descriptor.environment.id}") can do.`,
311
+ "",
312
+ "Capabilities:",
313
+ capabilityList(descriptor.capabilities),
314
+ "",
315
+ "Policies:",
316
+ policies,
317
+ ].join("\n"),
318
+ },
319
+ },
320
+ ],
321
+ }
322
+ }
323
+ )
324
+ }
325
+
326
+ /**
327
+ * Create an MCP server exposing ECP operational APIs as tools, resources, and prompts.
328
+ * @category MCP
329
+ */
330
+ export function createEcpMcpServer(options: CreateEcpMcpServerOptions): McpServer {
331
+ const { ecp } = options
332
+ const server = new McpServer({
333
+ name: options.name ?? "ecp",
334
+ version: options.version ?? "1.0.0",
335
+ })
336
+
337
+ registerEcpTools(server, ecp)
338
+ registerEcpResources(server, ecp)
339
+ registerEcpPrompts(server, ecp)
340
+
341
+ return server
342
+ }
343
+
344
+ /** Serve MCP over stdio. @category MCP */
345
+ export async function serveStdio(options: {
346
+ environment: import("@executioncontrolprotocol/core").Environment
347
+ name?: string
348
+ version?: string
349
+ }): Promise<void> {
350
+ const ecp = await options.environment.init()
351
+ const server = createEcpMcpServer({ ecp, name: options.name, version: options.version })
352
+ const transport = new StdioServerTransport()
353
+ await server.connect(transport)
354
+ }
355
+
356
+ /** Default MCP endpoint path for {@link serveHttp}. */
357
+ const DEFAULT_MCP_PATH = "/mcp"
358
+
359
+ /**
360
+ * Serve MCP over Streamable HTTP.
361
+ *
362
+ * Implements the MCP Streamable HTTP transport in stateless mode: each POST to
363
+ * the MCP endpoint gets a fresh {@link McpServer} + transport bound to the same
364
+ * initialized {@link Ecp}, avoiding request-id collisions between concurrent
365
+ * clients. Returns the underlying `http.Server` so callers can close it.
366
+ *
367
+ * @category MCP
368
+ */
369
+ export async function serveHttp(options: {
370
+ environment: import("@executioncontrolprotocol/core").Environment
371
+ port?: number
372
+ path?: string
373
+ name?: string
374
+ version?: string
375
+ }): Promise<Server> {
376
+ const ecp = await options.environment.init()
377
+ const port = options.port ?? 8787
378
+ const path = options.path ?? DEFAULT_MCP_PATH
379
+
380
+ const httpServer = createServer((req, res) => {
381
+ void (async () => {
382
+ const url = req.url ?? ""
383
+ const pathname = url.split("?")[0]
384
+ if (pathname !== path) {
385
+ res.writeHead(404, { "Content-Type": JSON_MIME })
386
+ res.end(JSON.stringify({ error: "Not found" }))
387
+ return
388
+ }
389
+
390
+ if (req.method !== "POST") {
391
+ // Stateless mode: no server-initiated streams, so GET/DELETE are
392
+ // not supported. Mirror the MCP SDK's stateless guidance.
393
+ res.writeHead(405, { "Content-Type": JSON_MIME, Allow: "POST" })
394
+ res.end(
395
+ JSON.stringify({
396
+ jsonrpc: "2.0",
397
+ error: { code: -32000, message: "Method not allowed. Use POST." },
398
+ id: null,
399
+ })
400
+ )
401
+ return
402
+ }
403
+
404
+ const server = createEcpMcpServer({
405
+ ecp,
406
+ name: options.name,
407
+ version: options.version,
408
+ })
409
+ const transport = new StreamableHTTPServerTransport({
410
+ sessionIdGenerator: undefined,
411
+ })
412
+ res.on("close", () => {
413
+ void transport.close()
414
+ void server.close()
415
+ })
416
+ try {
417
+ await server.connect(transport)
418
+ await transport.handleRequest(req, res)
419
+ } catch {
420
+ if (!res.headersSent) {
421
+ res.writeHead(500, { "Content-Type": JSON_MIME })
422
+ res.end(
423
+ JSON.stringify({
424
+ jsonrpc: "2.0",
425
+ error: { code: -32603, message: "Internal server error" },
426
+ id: null,
427
+ })
428
+ )
429
+ }
430
+ }
431
+ })()
432
+ })
433
+
434
+ await new Promise<void>((resolve) => httpServer.listen(port, resolve))
435
+ return httpServer
436
+ }
@@ -0,0 +1,158 @@
1
+ import { afterEach, beforeEach, describe, expect, it } from "vitest"
2
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js"
3
+ import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js"
4
+ import { createEcpMcpServer } from "../../src/index.js"
5
+ import { extension, workflow, step, registerTestExtension } from "@executioncontrolprotocol/core"
6
+ import { environment } from "@executioncontrolprotocol/node"
7
+ import type { Ecp } from "@executioncontrolprotocol/core"
8
+
9
+ type TextContent = { type: string; text: string }
10
+
11
+ /** Parse the first text content block of a tool/resource result as JSON. */
12
+ function parseText(content: TextContent[] | undefined): unknown {
13
+ const text = content?.find((c) => c.type === "text")?.text
14
+ if (text === undefined) throw new Error("no text content")
15
+ return JSON.parse(text)
16
+ }
17
+
18
+ const echoWorkflow = workflow("Echo")
19
+ .run([step("@executioncontrolprotocol/test.echo", "Echo").with({ value: "hi" }).as("out")])
20
+ .toManifest()
21
+
22
+ describe("@executioncontrolprotocol/mcp wire protocol (Client <-> Server)", () => {
23
+ let ecp: Ecp
24
+ let client: Client
25
+
26
+ beforeEach(async () => {
27
+ await registerTestExtension()
28
+ const env = (await environment("mcp-wire")).withExtensions([
29
+ extension("@executioncontrolprotocol/test", "Test").with({}),
30
+ ])
31
+ ecp = await env.init()
32
+
33
+ const server = createEcpMcpServer({ ecp })
34
+ client = new Client({ name: "test-client", version: "1.0.0" })
35
+ const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair()
36
+ await Promise.all([
37
+ server.connect(serverTransport),
38
+ client.connect(clientTransport),
39
+ ])
40
+ })
41
+
42
+ afterEach(async () => {
43
+ await client.close()
44
+ await ecp.terminate()
45
+ })
46
+
47
+ it("lists the operational tools", async () => {
48
+ const { tools } = await client.listTools()
49
+ const names = tools.map((t) => t.name)
50
+ expect(names).toEqual(
51
+ expect.arrayContaining([
52
+ "ecp.describe_environment",
53
+ "ecp.search",
54
+ "ecp.validate_workflow",
55
+ "ecp.run_workflow",
56
+ "ecp.encode",
57
+ "ecp.decode",
58
+ "ecp.get_run_status",
59
+ ])
60
+ )
61
+ })
62
+
63
+ it("describes the environment over the wire", async () => {
64
+ const res = await client.callTool({ name: "ecp.describe_environment", arguments: {} })
65
+ const descriptor = parseText(res.content as TextContent[]) as {
66
+ schema: string
67
+ capabilities: { id: string }[]
68
+ }
69
+ expect(descriptor.schema).toBe("@executioncontrolprotocol.environment.describe")
70
+ expect(descriptor.capabilities.some((c) => c.id.includes("echo"))).toBe(true)
71
+ })
72
+
73
+ it("validates a workflow over the wire", async () => {
74
+ const res = await client.callTool({
75
+ name: "ecp.validate_workflow",
76
+ arguments: { workflow: echoWorkflow as unknown as Record<string, unknown> },
77
+ })
78
+ const validation = parseText(res.content as TextContent[]) as { valid: boolean }
79
+ expect(validation.valid).toBe(true)
80
+ })
81
+
82
+ it("reports validation failure for an unknown capability", async () => {
83
+ const broken = workflow("Broken")
84
+ .run([step("@executioncontrolprotocol/test.does-not-exist", "X").with({}).as("o")])
85
+ .toManifest()
86
+ const res = await client.callTool({
87
+ name: "ecp.validate_workflow",
88
+ arguments: { workflow: broken as unknown as Record<string, unknown> },
89
+ })
90
+ const validation = parseText(res.content as TextContent[]) as { valid: boolean }
91
+ expect(validation.valid).toBe(false)
92
+ })
93
+
94
+ it("runs a workflow and retrieves its status", async () => {
95
+ const runRes = await client.callTool({
96
+ name: "ecp.run_workflow",
97
+ arguments: { workflow: echoWorkflow as unknown as Record<string, unknown> },
98
+ })
99
+ const result = parseText(runRes.content as TextContent[]) as {
100
+ run: { id: string; status: string }
101
+ }
102
+ expect(result.run.status).toBe("completed")
103
+
104
+ const statusRes = await client.callTool({
105
+ name: "ecp.get_run_status",
106
+ arguments: { runId: result.run.id },
107
+ })
108
+ const status = parseText(statusRes.content as TextContent[]) as {
109
+ run: { id: string }
110
+ }
111
+ expect(status.run.id).toBe(result.run.id)
112
+ })
113
+
114
+ it("returns a serialized error for an unknown tool", async () => {
115
+ const res = await client.callTool({ name: "ecp.not_a_tool", arguments: {} })
116
+ expect(res.isError).toBe(true)
117
+ const text = (res.content as TextContent[])
118
+ .map((c) => c.text)
119
+ .join("\n")
120
+ expect(text).toContain("not found")
121
+ })
122
+
123
+ it("lists and reads the capabilities resource", async () => {
124
+ const { resources } = await client.listResources()
125
+ expect(resources.some((r) => r.uri === "ecp://capabilities")).toBe(true)
126
+
127
+ const read = await client.readResource({ uri: "ecp://capabilities" })
128
+ const capabilities = JSON.parse(read.contents[0]!.text as string) as { id: string }[]
129
+ expect(capabilities.some((c) => c.id.includes("echo"))).toBe(true)
130
+ })
131
+
132
+ it("reads a single capability via the templated resource", async () => {
133
+ const read = await client.readResource({ uri: "ecp://capabilities/@executioncontrolprotocol/test.echo" })
134
+ const capability = JSON.parse(read.contents[0]!.text as string) as { id: string }
135
+ expect(capability.id).toBe("@executioncontrolprotocol/test.echo")
136
+ })
137
+
138
+ it("lists prompts and renders author_workflow", async () => {
139
+ const { prompts } = await client.listPrompts()
140
+ expect(prompts.map((p) => p.name)).toEqual(
141
+ expect.arrayContaining([
142
+ "ecp.author_workflow",
143
+ "ecp.repair_workflow",
144
+ "ecp.explain_environment",
145
+ ])
146
+ )
147
+
148
+ const prompt = await client.getPrompt({
149
+ name: "ecp.author_workflow",
150
+ arguments: { goal: "echo a value" },
151
+ })
152
+ const text = prompt.messages
153
+ .map((m) => (m.content.type === "text" ? m.content.text : ""))
154
+ .join("\n")
155
+ expect(text).toContain("echo a value")
156
+ expect(text).toContain("@executioncontrolprotocol/test.echo")
157
+ })
158
+ })
package/tsconfig.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": { "outDir": "dist", "rootDir": "src", "composite": true },
4
+ "include": ["src/**/*.ts"],
5
+ "references": [{ "path": "../types" }, { "path": "../core" }]
6
+ }