@kb1slnlabs/dsh-linear 0.0.0-stage → 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 KB1SLN-Labs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,357 @@
1
- # Temporary Holding Version
1
+ # @kb1slnlabs/dsh-linear
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Linear issue-tracking tools for the DeepSeek Harness. A Cordis function/namespace
4
+ plugin (`export { name, inject, apply }`, no default export) that registers
5
+ model-facing tools through `ctx.tools.register`.
6
+
7
+ It exposes **two tool surfaces**:
8
+
9
+ 1. **GraphQL tools (`linear_*`)** — 20 hand-written tools over the Linear GraphQL
10
+ API (`https://api.linear.app/graphql`), authenticated with a personal API token.
11
+ 2. **MCP parity tools (`linear_mcp_*`)** — a dynamic bridge to Linear's **hosted
12
+ MCP server** (`https://mcp.linear.app/mcp`) over the official Streamable HTTP
13
+ transport. These tool definitions are not written here: they are **discovered at
14
+ runtime** with `tools/list`, so their number and shape are whatever Linear serves.
15
+
16
+ > Status: current release **0.1.0**, published as the public GitHub repository
17
+ > `KB1SLN-Labs/dsh-linear` and on npm as `@kb1slnlabs/dsh-linear`. The offline suites below are mock-tested, **except** `test-loader.mjs`
18
+ > and `test-image-runtime.mjs`, which run against the real host services — the plugin
19
+ > loader plus the real `ToolRuntime`/`SystemPrompt` — rather than stubs; the live checks
20
+ > this repo has run are the introspection guard in `test.js` and a real discovery of the
21
+ > hosted MCP tool set — see [Testing](#testing) for exactly what is and is not proven.
22
+
23
+ ## Tools
24
+
25
+ ### GraphQL tools (`linear_*`) — 20 registrations
26
+
27
+ | Tool | Purpose |
28
+ |---|---|
29
+ | `linear_issue_list` | List issues (filter by team key, state name, free-text search; `limit` default 20, max 50) |
30
+ | `linear_issue_get` | One issue by id or identifier (e.g. `APT-123`), incl. relations |
31
+ | `linear_issue_create` | Create an issue in a team |
32
+ | `linear_issue_update` | Update title, description, state, assignee, labels, priority |
33
+ | `linear_comment_add` | Add a comment to an issue |
34
+ | `linear_comment_list` | List comments on an issue (newest first) |
35
+ | `linear_relation_add` | Link a related issue (`related` / `blockedBy` / `blocks` / `parent` / `child`) |
36
+ | `linear_team_list` | List teams |
37
+ | `linear_team_get` | One team by id (UUID) or key |
38
+ | `linear_state_list` | List workflow states for a team |
39
+ | `linear_label_list` | List labels for a team |
40
+ | `linear_label_get` | One label by id (UUID) |
41
+ | `linear_label_create` | Create a label for a team |
42
+ | `linear_project_list` | List projects |
43
+ | `linear_project_get` | One project by id (UUID) |
44
+ | `linear_project_create` | Create a project (optional team keys) |
45
+ | `linear_cycle_list` | List cycles for a team |
46
+ | `linear_cycle_get` | One cycle by id (UUID) |
47
+ | `linear_user_list` | List users in the workspace |
48
+ | `linear_user_get` | One user by id (UUID) or email |
49
+
50
+ These are the "legacy" surface in the sense that they predate the MCP bridge and
51
+ are hand-modelled against Linear's GraphQL schema. They remain the default and are
52
+ the only tools that work with no MCP token configured.
53
+
54
+ ### MCP parity tools (`linear_mcp_*`) — discovered, not declared
55
+
56
+ There is **no fixed list** of these tools in this repo. On connect the bridge pages
57
+ through `tools/list` and registers one harness tool per discovered MCP tool:
58
+
59
+ - public name = `linear_mcp_<rawName>` when it fits the function-name contract
60
+ (≤64 chars, `[A-Za-z0-9_-]`); otherwise a lossy normalization plus a 12-hex
61
+ SHA-256 hash of the identity, so distinct MCP tools never collapse onto one name.
62
+ Names are a pure function of the discovered raw name, so they are stable across
63
+ reconnects.
64
+ - calls are forwarded with `tools/call` using the **raw wire name**, never the public name.
65
+ - the discovered `inputSchema` is passed through unchanged; an advertised
66
+ `outputSchema` is kept only if the harness supports that JSON Schema vocabulary,
67
+ otherwise the output is unconstrained.
68
+ - `notifications/tools/list_changed` triggers re-discovery and an atomic generation
69
+ swap (the full new list is fetched first; only then are the old registrations disposed).
70
+
71
+ ## Configuration
72
+
73
+ Two independent credentials, resolved by the plugin at apply time:
74
+
75
+ | Setting | Env var | Plugin config | Used for | Header sent |
76
+ |---|---|---|---|---|
77
+ | GraphQL key | `LINEAR_API_KEY` | `apiKey` | `linear_*` tools | `Authorization: <raw key>` — **not** `Bearer` |
78
+ | MCP bearer token | `LINEAR_MCP_TOKEN` | `mcp.token` | `linear_mcp_*` bridge | `Authorization: Bearer <token>` |
79
+
80
+ The MCP token falls back in this order: `mcp.token` → `LINEAR_MCP_TOKEN` →
81
+ `apiKey` → `LINEAR_API_KEY`. So a single Linear API key can drive both surfaces.
82
+ The token is placed **only** in the `Authorization` header of requests to `mcp.url`;
83
+ it never appears in a tool name, description, schema, or log line (error text is
84
+ passed through a secret-redaction guard first).
85
+
86
+ ```powershell
87
+ # PowerShell (current session)
88
+ $env:LINEAR_API_KEY = "li_api_..."
89
+ $env:LINEAR_MCP_TOKEN = "..." # optional; falls back to LINEAR_API_KEY
90
+
91
+ # or persist for the user
92
+ setx LINEAR_API_KEY "li_api_..."
93
+ ```
94
+
95
+ The `dsh` launcher inherits the parent environment, so any harness started after a
96
+ variable is set will see it.
97
+
98
+ ### MCP bridge options (plugin config)
99
+
100
+ ```yaml
101
+ # in a profile's cordis.patch.yml
102
+ - insert:
103
+ - id: linear
104
+ name: '@kb1slnlabs/dsh-linear' # or a local path — see Install
105
+ config:
106
+ apiKey: li_api_... # optional, falls back to LINEAR_API_KEY
107
+ mcp:
108
+ token: '' # optional; env fallbacks as above
109
+ url: https://mcp.linear.app/mcp
110
+ toolCallTimeoutMs: 60000 # per-call timeout
111
+ reconnect:
112
+ enabled: true
113
+ initialDelayMs: 500 # bounded exponential backoff
114
+ maxDelayMs: 30000
115
+ maxAttempts: 10
116
+ ```
117
+
118
+ The repo's own `cordis.patch.yml` inserts the plugin with **no config at all** — it
119
+ relies on the environment variables.
120
+
121
+ `LINEAR_MCP_DISABLED=1` turns the bridge off entirely. The bridge is **fail-soft**:
122
+ with no token, an unloadable MCP SDK, or a host context that lacks `ctx.effect`, the
123
+ GraphQL suite still registers and the bridge simply stays off (a warning is logged).
124
+ After `maxAttempts` consecutive failures the MCP tools are unregistered until the
125
+ plugin reloads or the host restarts.
126
+
127
+ Override env vars used to pin dependency resolution (mainly for offline tests):
128
+ `DSH_LINEAR_TOOLS`, `DSH_LINEAR_SCHEMA`, `DSH_LINEAR_MCP_SDK`,
129
+ `DSH_LINEAR_MCP_SDK_TRANSPORT`, `DSH_LINEAR_MCP_SDK_TYPES`, `DSH_LINEAR_MCP_SDK_ZOD`.
130
+
131
+ ### What MCP result content becomes model-visible
132
+
133
+ The harness model vocabulary carries images as **durable attachment references**, not
134
+ inline bytes, so a `linear_mcp_*` result is projected before the model sees it:
135
+
136
+ - An `image` block is admitted **only when the current model route declares image
137
+ input** — positive proof via the host's `llm.resolveModelInfo` for the exact
138
+ provider/model that will read this result, plus a mounted `attachments` service. The
139
+ bytes then go through the host's own admission pipeline (`attachments.saveImages`),
140
+ which owns the count / aggregate-byte / media-type limits.
141
+ - Anything else — a model that does not declare image input, no mounted store, an
142
+ unresolvable route, an invalid block (non-canonical base64, or a media type outside
143
+ PNG/JPEG/WebP/GIF), or an admission rejection (`TOO_MANY_IMAGES`, `IMAGE_TOO_LARGE`,
144
+ …) — yields a text diagnostic in place of that image:
145
+ `[image unavailable: <media type>; <reason>; raw image data remains available to
146
+ programmatic callers]`. One invalid block falls back every image in the same result.
147
+ - **The canonical raw result is never rewritten.** Base64 stays in `result.value` for
148
+ programmatic callers either way; only the model-visible content carries references or
149
+ diagnostics. Text and error behaviour are unchanged.
150
+ - `audio`, embedded `resource`, and `resource_link` blocks stay **text placeholders** —
151
+ they are not admitted as attachments (`resource_link` becomes
152
+ `Resource link: <name> (<uri>)`).
153
+
154
+ ## Install
155
+
156
+ Release **0.1.0** is published on the public GitHub repository
157
+ `KB1SLN-Labs/dsh-linear` and on npm as `@kb1slnlabs/dsh-linear`. Four install routes:
158
+
159
+ ```powershell
160
+ # 0. install this plugin's own dependencies (local checkout route only)
161
+ npm install
162
+
163
+ # 1. local link with an absolute path (working checkout, edits picked up in place)
164
+ dsh plugin --profile web add link:<absolute-path-to-this-directory>
165
+
166
+ # 2. from the public GitHub repository (default branch — unpinned)
167
+ dsh plugin --profile web add github:KB1SLN-Labs/dsh-linear
168
+
169
+ # 3. from npm, by package name (latest published version)
170
+ dsh plugin --profile web add @kb1slnlabs/dsh-linear
171
+
172
+ # 4. version-pinned release asset — download the tarball attached to the v0.1.0
173
+ # GitHub release, then install it from disk:
174
+ # https://github.com/KB1SLN-Labs/dsh-linear/releases/download/v0.1.0/kb1slnlabs-dsh-linear.tgz
175
+ dsh plugin --profile web add file:./kb1slnlabs-dsh-linear.tgz
176
+ ```
177
+
178
+ Route 1 (`link:`) points pnpm at the directory rather than copying it, so the profile
179
+ picks up edits in place. Use an **absolute** path — that is what this repo's own install
180
+ used, and it is what makes the spec unambiguous regardless of where `dsh plugin` runs
181
+ from. Routes 2–4 install a copy instead, so later source edits do not leak into the
182
+ profile. Only route 4 pins an exact version: `kb1slnlabs-dsh-linear.tgz` is the release
183
+ asset attached to the v0.1.0 GitHub release — a fixed name renamed from the output of
184
+ `npm pack`, not the filename npm itself generates. Route 2 follows the repository's
185
+ default branch and route 3 follows npm's `latest`, so neither is guaranteed to install
186
+ 0.1.0 once newer work or releases exist; use route 4 when reproducibility matters.
187
+
188
+ The clean scratch profile behind the verification described under
189
+ [Testing](#testing) was installed through the **DSH plugin manager**, not by hand.
190
+ Adding the package also adds `@kb1slnlabs/dsh-linear` to the
191
+ profile's `dsh.profile.bundles` layer list, because its manifest declares
192
+ `dsh.bundle.patch`.
193
+
194
+ To add it as a plain dependency instead of a link, use `file:<absolute-path>` or a
195
+ packaged tarball (`npm pack`, then `dsh plugin --profile web add file:<path-to-tgz>`) —
196
+ that is how the scratch profile used by [Testing](#testing) was built.
197
+
198
+ Peer dependencies `@deepseek-ai/dsh-tools` and `@deepseek-ai/cordis`, plus
199
+ `@modelcontextprotocol/sdk` and `zod`, normally resolve from the **dsh install's own**
200
+ `node_modules`. The plugin resolves each dependency as a bare specifier first, then
201
+ from an anchor derived portably from `process.execPath` (the npm/pnpm global prefix),
202
+ then `%APPDATA%/npm/node_modules` on Windows or `/usr/local/lib/node_modules` /
203
+ `/usr/lib/node_modules` on POSIX. The MCP SDK and its zod must come from one and the
204
+ same tree — a partial tree pins nothing.
205
+
206
+ The `@deepseek-ai/dsh-tools` this package pins (`0.1.5-rc.2`) was verified against host
207
+ **0.2.1-rc.3**. In a base-only scratch profile it can log **missing-peer warnings** —
208
+ that is expected: the host supplies those services, and the pinned copy exists so the
209
+ plugin resolves its own peer at all. Do not read such a warning as a failure.
210
+
211
+ ## Testing
212
+
213
+ Four suites: the mock halves are **offline** (loopback mock servers only; no real
214
+ credential), but `test.js` additionally runs a live schema check against
215
+ `api.linear.app` whenever `LINEAR_API_KEY` is set — see below, and note that
216
+ `test-loader.mjs` and `test-image-runtime.mjs` are not mocks at all.
217
+
218
+ ```
219
+ node test.js # GraphQL half: module surface, 20 tool registrations,
220
+ # 40 GraphQL calls against a mock that mirrors the real
221
+ # Linear schema (flat UUID inputs, plural-field resolution,
222
+ # auth header), plus negative cases; and, when
223
+ # LINEAR_API_KEY is set, the live introspection guard
224
+ node test-mcp-bridge.js # MCP half: naming contract, paginated discovery, Bearer
225
+ # auth with no token leak, raw-name forwarding, isError
226
+ # handling, list_changed re-sync, disposal, fail-soft paths,
227
+ # and image-result projection/admission through the bridge
228
+ node test-loader.mjs # loader half — needs two environment variables, see below
229
+ node test-image-runtime.mjs # image half: real ToolRuntime + SystemPrompt, a stubbed
230
+ # llm route that declares image input and a stubbed
231
+ # attachment store; asserts an MCP image result comes back
232
+ # as a model-visible image block while the raw base64 stays
233
+ # in result.value. No network, no profile, no env vars.
234
+ ```
235
+
236
+ ### `test-loader.mjs` — what it runs and what it needs
237
+
238
+ Unlike the mock suites, `test-loader.mjs` exercises the **real plugin loader and the real
239
+ host tool runtime**: it boots a Cordis `Context`, attaches the host's real
240
+ `SystemPrompt` and `ToolRuntime` services (imported from the dsh installation, not
241
+ stubbed), runs `cordis-plugin-loader`, installs this plugin into that context through
242
+ the profile, asserts the entry got a live fiber and that exactly the 20 `linear_*`
243
+ names are known to the runtime — then disposes the fiber and asserts all 20 are gone.
244
+ It carries a 15-second step deadline: before each phase it checks whether 15 seconds
245
+ have elapsed and, if so, fails with `15 second bound exceeded at: <label>`. That
246
+ deadline is a progress budget between steps, **not** a hard timeout — it cannot
247
+ interrupt an asynchronous call that never settles, so a hung import or await stalls the
248
+ run instead of being cut off at 15 seconds.
249
+
250
+ Both variables are **required**; a missing or malformed one fails immediately with an
251
+ actionable message rather than an import error:
252
+
253
+ | Variable | Meaning | Accepted form |
254
+ |---|---|---|
255
+ | `DSH_ROOT` | the dsh installation directory — the one holding `node_modules/@deepseek-ai/cordis`, from which `cordis`, `cordis-plugin-loader`, `dsh-system-prompt` and `dsh-tools` are resolved | absolute filesystem path (a trailing slash is added for you) |
256
+ | `LINEAR_TEST_PROFILE` | the profile directory to load the plugin against — it must already have this package installed as a dependency/bundle layer | absolute filesystem path **or** a `file:` URL; converted with `pathToFileURL` plus a trailing slash so it is a directory URL, and checked for existence |
257
+
258
+ A non-`file:` URL (`http:`, `npm:`, …) is rejected. Relative paths are not accepted —
259
+ there is no sensible anchor for them here.
260
+
261
+ ```powershell
262
+ $env:DSH_ROOT = "C:/Users/<you>/AppData/Roaming/npm/node_modules/@deepseek-ai/dsh"
263
+ $env:LINEAR_TEST_PROFILE = "<dsh-home>/profiles/<profile>" # or file:///<same path>
264
+ node test-loader.mjs
265
+ ```
266
+
267
+ The suite deletes `LINEAR_API_KEY`, `LINEAR_MCP_TOKEN`, `LINEAR_APIKEY` and
268
+ `LINEAR_KEY` from its own environment before loading, so the plugin it installs cannot
269
+ pick up a live credential and the bridge stays off. That clearing is scoped to this
270
+ process — other suites behave differently: `test.js` deliberately runs live against
271
+ `api.linear.app` when a real key is present (see below).
272
+
273
+ **What this does not cover:** it is not a full headless boot. No LLM, session log,
274
+ filesystem sandbox, or app entry tree is started — only the loader plus those two host
275
+ services inside a bare `Context`. And it never contacts Linear: no GraphQL call and no
276
+ connection to the hosted MCP server are made, so the discovered `linear_mcp_*` surface
277
+ is not exercised here. The scratch profile it runs against is a throwaway directory,
278
+ not your day-to-day profile.
279
+
280
+ `test.js` additionally carries a **live introspection guard** that runs only when
281
+ `LINEAR_API_KEY` is set: it introspects the real Linear schema and asserts every input
282
+ field and query field the plugin emits actually exists. Without a key it prints
283
+ `skip - introspection guard`. So in an environment with no `LINEAR_API_KEY` the suite
284
+ passes with that guard skipped, but **with a key set, `test.js` does use live network** —
285
+ it sends a real introspection request to `https://api.linear.app/graphql`, and a schema
286
+ mismatch makes the run throw rather than skip.
287
+
288
+ ### `test-image-runtime.mjs` — what it runs and what it needs
289
+
290
+ No environment variables and no profile: it builds a bare Cordis `Context`, attaches the
291
+ real `SystemPrompt` and `ToolRuntime` (mode `native`), registers one hand-built MCP
292
+ definition, executes it through `tools.execute`, and asserts the finalized content is
293
+ `[text, image-with-attachment-ref]` while `result.value` still carries the raw base64.
294
+ The `llm` route (`inputModalities: ['image']`) and the attachment store are stubs — so
295
+ this proves the bridge's admission **decision** and projection against the real runtime,
296
+ not a real model's image support or the host's real byte/count limits. It needs the same
297
+ peer resolution as the plugin itself (a dsh installation to import `cordis`,
298
+ `dsh-tools`, and `dsh-system-prompt` from), and it can emit the missing-peer warnings
299
+ described under [Install](#install).
300
+
301
+ ### What is verified vs. what is not
302
+
303
+ - **Verified (mock-tested):** what `test.js` and `test-mcp-bridge.js` assert —
304
+ registration, GraphQL query/mutation shape, the auth header forms, identifier
305
+ resolution, error payloads, MCP discovery/naming/forwarding/reconnect/disposal
306
+ lifecycle, and image admission refusals against stubbed host services.
307
+ - **Verified against real host services (not mocks):** `test-loader.mjs` loads this
308
+ package through the real plugin loader into a real `ToolRuntime`, and
309
+ `test-image-runtime.mjs` finalizes an MCP image result through the real
310
+ `ToolRuntime`/`SystemPrompt`. The scratch profile behind that run was installed via
311
+ the **DSH plugin manager** and verified on **desktop runtime 0.2.1-rc.3** with the
312
+ Cordis loader plus `ToolRuntime` — a bare host context, **not** a full desktop app
313
+ boot: no LLM turn, session log, sandbox, or app entry tree was started.
314
+ - **Verified (CLI install smoke, separate from the line above):** a clean scratch
315
+ profile based on the **shipped headless template** took the **packed** plugin through
316
+ `dsh plugin`, and `dsh --profile <scratch> --help` exited **0** on **Windows CLI
317
+ 0.1.5-rc.2**. That is an install-and-launch smoke check only: no model turn and no
318
+ full desktop GUI boot were performed, and it says nothing about the loader/`ToolRuntime`
319
+ run above, which was on desktop runtime 0.2.1-rc.3.
320
+ - **Verified against live services:** the hosted MCP bridge has been checked against
321
+ the real `https://mcp.linear.app/mcp` endpoint — a real run through the DSH
322
+ `ToolRuntime`, installing this package from a clean scratch package, discovered
323
+ **68** `linear_mcp_*` tools. The live Linear GraphQL introspection guard in `test.js`
324
+ has likewise run with a real key and passed. So the MCP tool count, names, and schemas
325
+ are what Linear actually served at that time, not guesses.
326
+ - **Not verified:** no run in this repo's check has exercised a real `linear_*` **write**
327
+ end-to-end against `api.linear.app` — the GraphQL half is proven against a mock that
328
+ mirrors the introspected schema (plus the live schema-shape guard above), not against
329
+ real issue mutations. Image admission likewise has **not** been exercised with a real
330
+ model route or a real attachment store — only with declared-modalities and stored-ref
331
+ stand-ins. Nothing here was run on more than that one Windows host/runtime, so treat
332
+ other platforms as untested. Linear's hosted MCP tool set can also change server-side,
333
+ so re-discover it with your own key before relying on the exact count or shapes above.
334
+
335
+ ## Notes
336
+
337
+ - All GraphQL tools return JSON text; failures (a resolution error, an API error, a
338
+ network error) come back as `{ "error": "…" }` rather than a thrown stack trace.
339
+ - Issue lookups accept either a UUID or a `TEAM-123` identifier.
340
+ - `linear_issue_update` with `assignee: ""` unassigns.
341
+ - The plugin registers no prompt sections and no client UI; it is purely a tool provider.
342
+
343
+ ### How identifiers are resolved (resolve-then-mutate)
344
+
345
+ Linear's GraphQL inputs are **flat UUID fields** — `IssueCreateInput` has no nested
346
+ `team`/`state` objects, and the singular `Query.team` field accepts only a UUID `id`.
347
+ So before any write, the plugin resolves human identifiers to UUIDs through the
348
+ **plural** query fields, which *do* accept filters:
349
+
350
+ - a team key/name → `teams(filter: { key: { eq: … } })` → `teamId`
351
+ - a state name → `workflowStates(filter: { team: { id: { eq: <teamId> } }, name: { eq: … } })` → `stateId`
352
+
353
+ If a team key/name does not resolve to exactly one team, the tool fails with a clear
354
+ `Team '<x>' not found` error instead of sending a malformed mutation. An issue
355
+ identifier is resolved by splitting it into team key + number and querying
356
+ `issues(filter: { team: { key: { eq: … } }, number: { eq: … } })`, since `IssueFilter`
357
+ has no `identifier` field.
@@ -0,0 +1,50 @@
1
+ # Third-party notices
2
+
3
+ This plugin, `@kb1slnlabs/dsh-linear`, is licensed under the MIT license in
4
+ `LICENSE` (Copyright (c) 2026 KB1SLN-Labs). This file records the third-party
5
+ material that informed or is reflected in its implementation.
6
+
7
+ ## @deepseek-ai/dsh-mcp-client
8
+
9
+ The MCP parity tools in this plugin (`linear_mcp_*`) are a bridge to Linear's
10
+ hosted MCP server. That bridge is **informed by and includes closely related
11
+ implementation patterns** from `@deepseek-ai/dsh-mcp-client`, the DeepSeek
12
+ Harness MCP client bridge. In particular it mirrors that package's naming
13
+ contract for registering discovered MCP tools as harness tools, its handling of
14
+ advertised output schemas (unsupported vocabulary falls back to
15
+ annotation-only), its raw record result schema, and its use of the official
16
+ Model Context Protocol SDK's Streamable HTTP transport with uncached paginated
17
+ `tools/list` discovery.
18
+
19
+ - Package: `@deepseek-ai/dsh-mcp-client`
20
+ - Version: `0.1.5-rc.2`
21
+ - License: MIT (full text reproduced verbatim below)
22
+ - Copyright: (c) 2026 DeepSeek
23
+ - Repository: https://github.com/deepseek-ai/deepseek-harness/tree/main/packages/mcp/mcp-client
24
+
25
+ The full MIT license text as shipped in that package's `LICENSE` file,
26
+ reproduced verbatim:
27
+
28
+ ```
29
+ MIT License
30
+
31
+ Copyright (c) 2026 DeepSeek
32
+
33
+ Permission is hereby granted, free of charge, to any person obtaining a copy
34
+ of this software and associated documentation files (the "Software"), to deal
35
+ in the Software without restriction, including without limitation the rights
36
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
37
+ copies of the Software, and to permit persons to whom the Software is
38
+ furnished to do so, subject to the following conditions:
39
+
40
+ The above copyright notice and this permission notice shall be included in all
41
+ copies or substantial portions of the Software.
42
+
43
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
44
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
45
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
46
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
47
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
48
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
49
+ SOFTWARE.
50
+ ```
@@ -0,0 +1,19 @@
1
+ # @kb1slnlabs/dsh-linear — profile patch layer
2
+ #
3
+ # Adds the Linear issue-tracking tool suite to a profile. The plugin registers
4
+ # its tools through ctx.tools.register at apply time; no further composition
5
+ # is needed.
6
+ #
7
+ # Config:
8
+ # apiKey: Linear personal API key (li_api_...). Optional — falls back to the
9
+ # LINEAR_API_KEY environment variable.
10
+ #
11
+ # Example row (in a profile's cordis.patch.yml):
12
+ # - id: linear
13
+ # name: '@kb1slnlabs/dsh-linear'
14
+ # config:
15
+ # apiKey: li_api_...
16
+
17
+ - insert:
18
+ - id: linear
19
+ name: '@kb1slnlabs/dsh-linear'