@skeletiq/mcp 0.1.0 → 0.1.1
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/README.md +51 -4
- package/dist/index.js +94 -6
- package/package.json +8 -1
package/README.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# @skeletiq/mcp
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/@skeletiq/mcp)
|
|
4
|
+
[](./LICENSE)
|
|
5
|
+
[](https://nodejs.org)
|
|
6
|
+
[](https://github.com/Sabhahith-Works/skeletiq-mcp/actions/workflows/ci.yml)
|
|
7
|
+
|
|
3
8
|
Design in [SkeletIQ](https://skeletiq.com), build with your coding agent.
|
|
4
9
|
|
|
5
10
|
SkeletIQ turns a prompt into a critiqued system architecture — components, data stores,
|
|
@@ -58,11 +63,20 @@ A token grants only what you tick. The server's tools need:
|
|
|
58
63
|
| Scope | What it unlocks | Tools |
|
|
59
64
|
|---|---|---|
|
|
60
65
|
| `read` | Projects, designs, briefs, build order, readiness, gaps, jobs | `list_projects`, `get_design`, `get_generation_status` |
|
|
61
|
-
| `generate` | Running generations and payload critique
|
|
62
|
-
| `report` | Recording what got built | `check_drift` |
|
|
66
|
+
| `generate` | Running generations — **that spends credits** — and payload critique, which is free | `generate_architecture`, `critique_architecture` |
|
|
67
|
+
| `report` | Recording what got built. **Needs `read` as well** | `check_drift` |
|
|
63
68
|
|
|
64
69
|
`read` alone is a good starting point: the agent can orient and build, but cannot spend anything.
|
|
65
70
|
|
|
71
|
+
Two things the table above cannot say in a cell:
|
|
72
|
+
|
|
73
|
+
- **`report` on its own does nothing.** `check_drift` looks the project and the version up before it
|
|
74
|
+
can report against them, and those lookups are `read`. A `report`-only token is refused.
|
|
75
|
+
- **`generate` without `read` still generates**, but the answer is thinner: the tool reads back the
|
|
76
|
+
design it just created to describe it, and reports that failure as a thinner answer rather than a
|
|
77
|
+
failed generation — telling an agent the generation failed would invite it to pay for the whole
|
|
78
|
+
thing again.
|
|
79
|
+
|
|
66
80
|
Everything else is out of reach by construction — a token cannot mint another token, read or
|
|
67
81
|
change your provider keys, see billing, or delete your account, whatever scopes it carries.
|
|
68
82
|
|
|
@@ -112,7 +126,40 @@ warns when a default was applied. Read those before reporting a score to a perso
|
|
|
112
126
|
Whichever one the account holder chose under **Settings → Agent access**. The tools take no runtime
|
|
113
127
|
argument, deliberately: the model asking for a design does not get to choose what it costs you.
|
|
114
128
|
|
|
129
|
+
## Development
|
|
130
|
+
|
|
131
|
+
This repository is the source of the published `@skeletiq/mcp` package. The connector is developed
|
|
132
|
+
in SkeletIQ's monorepo, alongside the API it talks to, and mirrored here — so the history you see is
|
|
133
|
+
the package's real history, not a squashed snapshot. A pull request opened here cannot be merged,
|
|
134
|
+
because the next sync would overwrite it; [CONTRIBUTING.md](./CONTRIBUTING.md) explains what to do
|
|
135
|
+
instead.
|
|
136
|
+
|
|
137
|
+
Node 20 or newer.
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
npm install
|
|
141
|
+
npm test # vitest — hermetic: no network, no services, nothing to seed
|
|
142
|
+
npm run build # tsup, to dist/index.js
|
|
143
|
+
npm run typecheck
|
|
144
|
+
npm run lint
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
The tests mock the SkeletIQ API rather than calling it, so a clean clone runs them without a token
|
|
148
|
+
and without an account.
|
|
149
|
+
|
|
150
|
+
**If `npm install` fails with `Cannot read properties of null (reading 'edgesOut')`,** you are on npm
|
|
151
|
+
10.9.x — the version Node 22 ships — which cannot resolve this tree; `vitest@4` alone triggers it.
|
|
152
|
+
`npm install -g npm@11` fixes it. This affects cloning and building only: installing the published
|
|
153
|
+
package with `npx` works on that npm.
|
|
154
|
+
|
|
115
155
|
## Licence
|
|
116
156
|
|
|
117
|
-
MIT — see [LICENSE](./LICENSE).
|
|
118
|
-
|
|
157
|
+
MIT — see [LICENSE](./LICENSE). Source:
|
|
158
|
+
[Sabhahith-Works/skeletiq-mcp](https://github.com/Sabhahith-Works/skeletiq-mcp) — issues and questions
|
|
159
|
+
go [there](https://github.com/Sabhahith-Works/skeletiq-mcp/issues).
|
|
160
|
+
|
|
161
|
+
The SkeletIQ platform is AGPL-3.0-or-later; this connector is MIT so it can be embedded, vendored
|
|
162
|
+
and forked freely.
|
|
163
|
+
|
|
164
|
+
"SkeletIQ" is a mark of Sabhahith Works Private Limited — see [NOTICE](./NOTICE). Security reports go
|
|
165
|
+
to security@skeletiq.com, not to the issue tracker: [SECURITY.md](./SECURITY.md).
|
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import { serveStdio } from "@modelcontextprotocol/server/stdio";
|
|
|
5
5
|
|
|
6
6
|
// src/config.ts
|
|
7
7
|
var SERVER_NAME = "skeletiq";
|
|
8
|
-
var SERVER_VERSION = "0.1.
|
|
8
|
+
var SERVER_VERSION = "0.1.1";
|
|
9
9
|
var DEFAULT_API_URL = "https://api.skeletiq.com";
|
|
10
10
|
var TOKEN_PREFIX = "skq_";
|
|
11
11
|
var ConfigError = class extends Error {
|
|
@@ -303,12 +303,20 @@ var GapEntrySchema = z.union([
|
|
|
303
303
|
impact: z.string().nullable().optional()
|
|
304
304
|
})
|
|
305
305
|
]);
|
|
306
|
+
var GroundedDecisionSchema = z.looseObject({
|
|
307
|
+
text: z.string(),
|
|
308
|
+
source_ids: z.array(z.string()).nullable().optional(),
|
|
309
|
+
requirement_ids: z.array(z.string()).nullable().optional(),
|
|
310
|
+
assumption: z.boolean().optional(),
|
|
311
|
+
confidence: z.string().optional()
|
|
312
|
+
});
|
|
306
313
|
var ArchitectureJsonSchema = z.looseObject({
|
|
307
314
|
title: z.string().optional(),
|
|
308
315
|
description: z.string().optional(),
|
|
309
316
|
components: z.array(ComponentSchema).optional(),
|
|
310
317
|
connections: z.array(ConnectionSchema).optional(),
|
|
311
318
|
design_decisions: z.array(z.string()).nullable().optional(),
|
|
319
|
+
grounded_decisions: z.array(GroundedDecisionSchema).nullable().optional(),
|
|
312
320
|
trade_offs: z.array(z.string()).nullable().optional(),
|
|
313
321
|
scalability_notes: z.string().nullable().optional(),
|
|
314
322
|
assumptions: z.array(GapEntrySchema).nullable().optional(),
|
|
@@ -387,7 +395,15 @@ var ReadinessSchema = z.looseObject({
|
|
|
387
395
|
/** Keys of advisory rows whose check never ran. Named, never counted. */
|
|
388
396
|
unrun_checks: z.array(z.string()).optional(),
|
|
389
397
|
rows: z.array(ReadinessRowSchema),
|
|
390
|
-
release: ReleaseFactsSchema
|
|
398
|
+
release: ReleaseFactsSchema,
|
|
399
|
+
/**
|
|
400
|
+
* What releasing this version would be carrying, one sentence each. A warning, never a refusal.
|
|
401
|
+
*
|
|
402
|
+
* The same list the app shows beside its Release button, under the heading "It will carry:".
|
|
403
|
+
* Optional here only so a server that predates the field still parses — it is required on the
|
|
404
|
+
* wire, and an empty array is the server saying it looked and found nothing.
|
|
405
|
+
*/
|
|
406
|
+
release_warnings: z.array(z.string()).optional()
|
|
391
407
|
});
|
|
392
408
|
var DesignGapSchema = z.looseObject({
|
|
393
409
|
gap_id: z.string(),
|
|
@@ -398,12 +414,21 @@ var DesignGapSchema = z.looseObject({
|
|
|
398
414
|
note: z.string().nullable().optional(),
|
|
399
415
|
adr_id: z.string().nullable().optional()
|
|
400
416
|
});
|
|
417
|
+
var OrphanedAnswerSchema = z.looseObject({
|
|
418
|
+
gap_id: z.string(),
|
|
419
|
+
kind: z.string(),
|
|
420
|
+
action: z.string(),
|
|
421
|
+
note: z.string().nullable().optional(),
|
|
422
|
+
adr_id: z.string().nullable().optional()
|
|
423
|
+
});
|
|
401
424
|
var DesignGapListSchema = z.looseObject({
|
|
402
425
|
project_id: z.string(),
|
|
403
426
|
architecture_id: z.string().nullable().optional(),
|
|
404
427
|
version: z.number().nullable().optional(),
|
|
405
428
|
gaps: z.array(DesignGapSchema),
|
|
406
|
-
unresolved_count: z.number()
|
|
429
|
+
unresolved_count: z.number(),
|
|
430
|
+
/** Answers whose question is not in this version — recorded, not silently dropped. */
|
|
431
|
+
orphaned_answers: z.array(OrphanedAnswerSchema).optional()
|
|
407
432
|
});
|
|
408
433
|
var ComponentRefSchema = z.looseObject({
|
|
409
434
|
id: z.string(),
|
|
@@ -1349,10 +1374,24 @@ function sliceComponent(design, componentId) {
|
|
|
1349
1374
|
related_decisions: relatedDecisions(design, component)
|
|
1350
1375
|
};
|
|
1351
1376
|
}
|
|
1377
|
+
function decisionTexts(design) {
|
|
1378
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1379
|
+
const out = [];
|
|
1380
|
+
const push2 = (text) => {
|
|
1381
|
+
if (typeof text !== "string") return;
|
|
1382
|
+
const trimmed = text.trim();
|
|
1383
|
+
if (trimmed.length === 0 || seen.has(trimmed)) return;
|
|
1384
|
+
seen.add(trimmed);
|
|
1385
|
+
out.push(trimmed);
|
|
1386
|
+
};
|
|
1387
|
+
for (const decision of design.design_decisions ?? []) push2(decision);
|
|
1388
|
+
for (const decision of design.grounded_decisions ?? []) push2(decision?.text);
|
|
1389
|
+
return out;
|
|
1390
|
+
}
|
|
1352
1391
|
function relatedDecisions(design, component) {
|
|
1353
1392
|
const needles = [component.name, component.technology].filter((value) => typeof value === "string" && value.trim().length > 2).map((value) => value.toLowerCase());
|
|
1354
1393
|
if (needles.length === 0) return [];
|
|
1355
|
-
return (design
|
|
1394
|
+
return decisionTexts(design).filter((decision) => {
|
|
1356
1395
|
const haystack = decision.toLowerCase();
|
|
1357
1396
|
return needles.some((needle) => haystack.includes(needle));
|
|
1358
1397
|
});
|
|
@@ -1363,7 +1402,7 @@ function overview(design) {
|
|
|
1363
1402
|
description: design.description ?? "",
|
|
1364
1403
|
components: design.components ?? [],
|
|
1365
1404
|
connections: design.connections ?? [],
|
|
1366
|
-
design_decisions: design
|
|
1405
|
+
design_decisions: decisionTexts(design),
|
|
1367
1406
|
trade_offs: design.trade_offs ?? []
|
|
1368
1407
|
};
|
|
1369
1408
|
}
|
|
@@ -1423,6 +1462,21 @@ function registerGetDesign(server, client, resolver) {
|
|
|
1423
1462
|
...data.components.map(
|
|
1424
1463
|
(c) => `- ${c.id} \u2014 ${c.name} (${c.type}${c.technology ? `, ${c.technology}` : ""})`
|
|
1425
1464
|
),
|
|
1465
|
+
// The design's own reasoning, which this text used to omit entirely
|
|
1466
|
+
// while carrying it in the structured half — so an agent working from
|
|
1467
|
+
// what it was shown rebuilt every choice from scratch, and could
|
|
1468
|
+
// silently undo one. Said here because it is the answer to "why is it
|
|
1469
|
+
// like this", and the overview is where that gets asked.
|
|
1470
|
+
...data.design_decisions.length ? [
|
|
1471
|
+
"",
|
|
1472
|
+
"Decisions taken \u2014 these are settled, build to them:",
|
|
1473
|
+
...data.design_decisions.map((decision) => `- ${decision}`)
|
|
1474
|
+
] : [],
|
|
1475
|
+
...data.trade_offs.length ? [
|
|
1476
|
+
"",
|
|
1477
|
+
"Trade-offs the design accepts:",
|
|
1478
|
+
...data.trade_offs.map((tradeOff) => `- ${tradeOff}`)
|
|
1479
|
+
] : [],
|
|
1426
1480
|
"",
|
|
1427
1481
|
versionLine(facts)
|
|
1428
1482
|
].join("\n"));
|
|
@@ -1448,7 +1502,20 @@ function registerGetDesign(server, client, resolver) {
|
|
|
1448
1502
|
"",
|
|
1449
1503
|
outgoing.length ? `Calls: ${outgoing.map((c) => `${c.target}${c.protocol ? ` (${c.protocol})` : ""}`).join(", ")}` : "Calls nothing.",
|
|
1450
1504
|
incoming.length ? `Called by: ${incoming.map((c) => c.source).join(", ")}` : "Called by nothing in the design.",
|
|
1451
|
-
|
|
1505
|
+
// Labelled, and only when there are any. These bullets used to be
|
|
1506
|
+
// printed bare under "Called by:", so a decision about the component
|
|
1507
|
+
// read as a continuation of its edge list — and a design with no
|
|
1508
|
+
// matching decision printed nothing at all, which reads as a design
|
|
1509
|
+
// with no reasoning rather than as a decision that names other boxes.
|
|
1510
|
+
...slice.related_decisions.length ? [
|
|
1511
|
+
"",
|
|
1512
|
+
"Decisions that mention it:",
|
|
1513
|
+
...slice.related_decisions.map((decision) => `- ${decision}`)
|
|
1514
|
+
] : [
|
|
1515
|
+
"",
|
|
1516
|
+
"No decision in this design names this component. That is not the same as",
|
|
1517
|
+
`no reason \u2014 use mode "overview" for the design's decisions in full.`
|
|
1518
|
+
],
|
|
1452
1519
|
"",
|
|
1453
1520
|
versionLine(facts)
|
|
1454
1521
|
].join("\n"));
|
|
@@ -1500,6 +1567,16 @@ function registerGetDesign(server, client, resolver) {
|
|
|
1500
1567
|
...advisories.map(
|
|
1501
1568
|
(row) => `- ${row.label}: ${describeRow(row)}${unrun.has(row.key) ? " (never run on this version)" : ""}`
|
|
1502
1569
|
),
|
|
1570
|
+
// The same list, under the same heading, as the app shows beside its
|
|
1571
|
+
// Release button. A person clicking Release is told what the release
|
|
1572
|
+
// takes with it; an agent asking the same question of the same version
|
|
1573
|
+
// was told nothing at all. A warning, never a refusal — it does not
|
|
1574
|
+
// move the verdict and it does not stop anyone.
|
|
1575
|
+
...readiness.release_warnings?.length ? [
|
|
1576
|
+
"",
|
|
1577
|
+
"It will carry:",
|
|
1578
|
+
...readiness.release_warnings.map((warning) => `- ${warning}`)
|
|
1579
|
+
] : [],
|
|
1503
1580
|
"",
|
|
1504
1581
|
"Only a person can clear a gate. Ask; do not decide on their behalf.",
|
|
1505
1582
|
versionLine(facts)
|
|
@@ -1530,6 +1607,7 @@ function registerGetDesign(server, client, resolver) {
|
|
|
1530
1607
|
const facts = factsFrom(resolved);
|
|
1531
1608
|
const unresolved = gaps.gaps.filter((gap) => !gap.resolved);
|
|
1532
1609
|
const settled = gaps.gaps.filter((gap) => gap.resolved && gap.action !== "dismissed");
|
|
1610
|
+
const orphaned = gaps.orphaned_answers ?? [];
|
|
1533
1611
|
return ok({ ...base, ...facts, data: gaps }, [
|
|
1534
1612
|
unresolved.length === 0 ? "Every open question and assumption in this design has been dealt with." : `${unresolved.length} unresolved:`,
|
|
1535
1613
|
...unresolved.map((gap) => `- [${gap.kind}] ${gap.text}`),
|
|
@@ -1538,6 +1616,16 @@ function registerGetDesign(server, client, resolver) {
|
|
|
1538
1616
|
`${settled.length} settled during review \u2014 treat these as decided:`,
|
|
1539
1617
|
...settled.map((gap) => `- [${gap.kind}] ${gap.text} \u2192 ${settledAnswer(gap)}`)
|
|
1540
1618
|
] : [],
|
|
1619
|
+
// A gap is identified by a hash of its own text, so a regeneration that
|
|
1620
|
+
// re-words a question strands the answer that settled it. The server
|
|
1621
|
+
// reports those rather than dropping them, and then this dropped them.
|
|
1622
|
+
// A count and a sentence, not rows: the stored answer has no text to
|
|
1623
|
+
// show, and it is not work anyone can pick up.
|
|
1624
|
+
...orphaned.length ? [
|
|
1625
|
+
"",
|
|
1626
|
+
`${orphaned.length} earlier answer(s) do not match any question in this version \u2014 re-worded or dropped when it was generated, or asked only in a different version. Any Decision they minted still stands.`,
|
|
1627
|
+
...orphaned.filter((answer) => answer.adr_id).map((answer) => `- [${answer.kind}] ${answer.action} \u2192 Decision ${answer.adr_id}`)
|
|
1628
|
+
] : [],
|
|
1541
1629
|
"",
|
|
1542
1630
|
"The unresolved ones are questions for a person, not for you to answer. Raise",
|
|
1543
1631
|
"them; do not guess and build on the guess. An API token cannot resolve them \u2014",
|
package/package.json
CHANGED
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@skeletiq/mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Design in SkeletIQ, build with your coding agent. MCP server for the SkeletIQ architecture platform.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Sabhahith Works Private Limited",
|
|
7
7
|
"homepage": "https://skeletiq.com",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/Sabhahith-Works/skeletiq-mcp.git"
|
|
11
|
+
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/Sabhahith-Works/skeletiq-mcp/issues"
|
|
14
|
+
},
|
|
8
15
|
"keywords": [
|
|
9
16
|
"mcp",
|
|
10
17
|
"modelcontextprotocol",
|