toolcraft 0.0.39 → 0.0.41
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 +41 -4
- package/dist/cli.js +40 -9
- package/dist/http-errors.d.ts +57 -0
- package/dist/http-errors.js +72 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +15 -0
- package/dist/mcp-proxy.js +16 -1
- package/dist/mcp.js +224 -12
- package/dist/number-schema.js +9 -2
- package/node_modules/@poe-code/agent-defs/dist/agents/claude-code.js +1 -1
- package/node_modules/@poe-code/agent-defs/dist/agents/claude-desktop.js +6 -1
- package/node_modules/@poe-code/agent-defs/dist/agents/cursor.js +1 -1
- package/node_modules/@poe-code/agent-defs/dist/agents/kimi.js +1 -1
- package/node_modules/@poe-code/agent-defs/dist/agents/opencode.js +1 -1
- package/node_modules/@poe-code/agent-defs/dist/specifier.js +15 -6
- package/node_modules/@poe-code/agent-defs/dist/types.d.ts +5 -0
- package/node_modules/@poe-code/agent-mcp-config/dist/apply.js +36 -3
- package/node_modules/@poe-code/frontmatter/README.md +35 -0
- package/node_modules/@poe-code/frontmatter/dist/fences.d.ts +8 -0
- package/node_modules/@poe-code/frontmatter/dist/fences.js +72 -0
- package/node_modules/@poe-code/frontmatter/dist/index.d.ts +3 -0
- package/node_modules/@poe-code/frontmatter/dist/index.js +3 -0
- package/node_modules/@poe-code/frontmatter/dist/parse.d.ts +20 -0
- package/node_modules/@poe-code/frontmatter/dist/parse.js +137 -0
- package/node_modules/@poe-code/frontmatter/dist/stringify.d.ts +1 -0
- package/node_modules/@poe-code/frontmatter/dist/stringify.js +35 -0
- package/node_modules/@poe-code/frontmatter/package.json +25 -0
- package/node_modules/mcp-oauth/dist/server/jwks-token-verifier.d.ts +1 -0
- package/node_modules/mcp-oauth/dist/server/jwks-token-verifier.js +25 -1
- package/node_modules/tiny-mcp-client/README.md +7 -0
- package/node_modules/tiny-mcp-client/dist/internal.d.ts +2 -0
- package/node_modules/tiny-mcp-client/dist/internal.js +3 -0
- package/node_modules/tiny-mcp-client/dist/mcp-tool-types.compile-check.js +10 -0
- package/node_modules/tiny-mcp-client/src/http-oauth.test.ts +2 -8
- package/node_modules/tiny-mcp-client/src/internal.ts +6 -0
- package/node_modules/tiny-mcp-client/src/mcp-tool-types.compile-check.ts +10 -0
- package/node_modules/toolcraft-design/README.md +12 -0
- package/node_modules/toolcraft-design/dist/prompts/index.d.ts +39 -14
- package/node_modules/toolcraft-design/dist/prompts/index.js +10 -6
- package/node_modules/toolcraft-design/dist/prompts/interactive/cancel-symbol.d.ts +2 -0
- package/node_modules/toolcraft-design/dist/prompts/interactive/cancel-symbol.js +4 -0
- package/node_modules/toolcraft-design/dist/prompts/interactive/confirm.d.ts +9 -0
- package/node_modules/toolcraft-design/dist/prompts/interactive/confirm.js +47 -0
- package/node_modules/toolcraft-design/dist/prompts/interactive/core.d.ts +55 -0
- package/node_modules/toolcraft-design/dist/prompts/interactive/core.js +274 -0
- package/node_modules/toolcraft-design/dist/prompts/interactive/glyphs.d.ts +20 -0
- package/node_modules/toolcraft-design/dist/prompts/interactive/glyphs.js +53 -0
- package/node_modules/toolcraft-design/dist/prompts/interactive/index.d.ts +6 -0
- package/node_modules/toolcraft-design/dist/prompts/interactive/index.js +6 -0
- package/node_modules/toolcraft-design/dist/prompts/interactive/keys.d.ts +2 -0
- package/node_modules/toolcraft-design/dist/prompts/interactive/keys.js +28 -0
- package/node_modules/toolcraft-design/dist/prompts/interactive/multiselect.d.ts +13 -0
- package/node_modules/toolcraft-design/dist/prompts/interactive/multiselect.js +131 -0
- package/node_modules/toolcraft-design/dist/prompts/interactive/pagination.d.ts +10 -0
- package/node_modules/toolcraft-design/dist/prompts/interactive/pagination.js +52 -0
- package/node_modules/toolcraft-design/dist/prompts/interactive/password.d.ts +10 -0
- package/node_modules/toolcraft-design/dist/prompts/interactive/password.js +55 -0
- package/node_modules/toolcraft-design/dist/prompts/interactive/select.d.ts +18 -0
- package/node_modules/toolcraft-design/dist/prompts/interactive/select.js +89 -0
- package/node_modules/toolcraft-design/dist/prompts/interactive/test-helpers.d.ts +21 -0
- package/node_modules/toolcraft-design/dist/prompts/interactive/test-helpers.js +32 -0
- package/node_modules/toolcraft-design/dist/prompts/interactive/text.d.ts +12 -0
- package/node_modules/toolcraft-design/dist/prompts/interactive/text.js +60 -0
- package/node_modules/toolcraft-design/dist/prompts/interactive/wrap.d.ts +4 -0
- package/node_modules/toolcraft-design/dist/prompts/interactive/wrap.js +18 -0
- package/node_modules/toolcraft-design/dist/prompts/primitives/cancel.d.ts +1 -1
- package/node_modules/toolcraft-design/dist/prompts/primitives/cancel.js +1 -1
- package/node_modules/toolcraft-design/dist/terminal-markdown/parser/frontmatter.js +20 -453
- package/node_modules/toolcraft-design/package.json +6 -3
- package/package.json +9 -6
package/dist/mcp.js
CHANGED
|
@@ -128,14 +128,24 @@ function formatAvailableList(values) {
|
|
|
128
128
|
return `Available: ${[...values].sort().join(", ")}.`;
|
|
129
129
|
}
|
|
130
130
|
function applySchemaCasing(schema, casing) {
|
|
131
|
+
const oneOf = schema.oneOf?.map((child) => applySchemaCasing(child, casing));
|
|
132
|
+
const additionalProperties = typeof schema.additionalProperties === "object" && schema.additionalProperties !== null
|
|
133
|
+
? applySchemaCasing(schema.additionalProperties, casing)
|
|
134
|
+
: schema.additionalProperties;
|
|
131
135
|
if (schema.type !== "object" || schema.properties === undefined) {
|
|
132
136
|
if (schema.type === "array" && schema.items !== undefined) {
|
|
133
137
|
return {
|
|
134
138
|
...schema,
|
|
135
|
-
|
|
139
|
+
...(additionalProperties === undefined ? {} : { additionalProperties }),
|
|
140
|
+
items: applySchemaCasing(schema.items, casing),
|
|
141
|
+
...(oneOf === undefined ? {} : { oneOf })
|
|
136
142
|
};
|
|
137
143
|
}
|
|
138
|
-
return
|
|
144
|
+
return {
|
|
145
|
+
...schema,
|
|
146
|
+
...(additionalProperties === undefined ? {} : { additionalProperties }),
|
|
147
|
+
...(oneOf === undefined ? {} : { oneOf })
|
|
148
|
+
};
|
|
139
149
|
}
|
|
140
150
|
const properties = Object.fromEntries(Object.entries(schema.properties).map(([key, value]) => [
|
|
141
151
|
formatSegment(key, casing),
|
|
@@ -144,8 +154,10 @@ function applySchemaCasing(schema, casing) {
|
|
|
144
154
|
const required = schema.required?.map((key) => formatSegment(key, casing));
|
|
145
155
|
return {
|
|
146
156
|
...schema,
|
|
157
|
+
...(additionalProperties === undefined ? {} : { additionalProperties }),
|
|
147
158
|
properties,
|
|
148
|
-
...(required === undefined ? {} : { required })
|
|
159
|
+
...(required === undefined ? {} : { required }),
|
|
160
|
+
...(oneOf === undefined ? {} : { oneOf })
|
|
149
161
|
};
|
|
150
162
|
}
|
|
151
163
|
function collectParamSummaries(schema, casing, path = [], inheritedOptional = false) {
|
|
@@ -162,16 +174,32 @@ function collectParamSummaries(schema, casing, path = [], inheritedOptional = fa
|
|
|
162
174
|
}
|
|
163
175
|
return summaries;
|
|
164
176
|
}
|
|
165
|
-
function buildToolDescription(description, params, casing) {
|
|
177
|
+
function buildToolDescription(description, params, examples, commandName, casing) {
|
|
166
178
|
const summary = collectParamSummaries(params, casing);
|
|
167
179
|
const parameterSummary = summary.length === 0 ? "" : `Parameters: ${summary.join(", ")}.`;
|
|
180
|
+
const exampleSummary = examples.length === 0
|
|
181
|
+
? ""
|
|
182
|
+
: `\n\nExamples:\n${examples
|
|
183
|
+
.map((example) => `- ${example.title}: ${commandName} ${formatMcpExampleParams(example.params)}`)
|
|
184
|
+
.join("\n")}`;
|
|
168
185
|
if (description === undefined) {
|
|
169
|
-
return parameterSummary
|
|
186
|
+
return `${parameterSummary}${exampleSummary}`;
|
|
170
187
|
}
|
|
171
188
|
if (parameterSummary.length === 0) {
|
|
172
|
-
return description
|
|
189
|
+
return `${description}${exampleSummary}`;
|
|
173
190
|
}
|
|
174
|
-
return `${description} ${parameterSummary}`;
|
|
191
|
+
return `${description} ${parameterSummary}${exampleSummary}`;
|
|
192
|
+
}
|
|
193
|
+
function formatMcpExampleParams(params) {
|
|
194
|
+
return Object.entries(params)
|
|
195
|
+
.map(([key, value]) => `${key}=${formatMcpExampleValue(value)}`)
|
|
196
|
+
.join(" ");
|
|
197
|
+
}
|
|
198
|
+
function formatMcpExampleValue(value) {
|
|
199
|
+
if (typeof value === "string") {
|
|
200
|
+
return value;
|
|
201
|
+
}
|
|
202
|
+
return JSON.stringify(value);
|
|
175
203
|
}
|
|
176
204
|
function matchesAllowlist(toolName, allowlist) {
|
|
177
205
|
if (allowlist === undefined) {
|
|
@@ -216,6 +244,9 @@ function enumerateTools(root, casing, allowlist, omitRootToolNamePrefix) {
|
|
|
216
244
|
throw new ToolcraftBugError(`command "${name}" must define an object params schema for MCP.`);
|
|
217
245
|
}
|
|
218
246
|
validateUniqueMCPParameterFields(params, casing);
|
|
247
|
+
if (node.result !== undefined) {
|
|
248
|
+
validateUniqueMCPParameterFields(node.result, casing);
|
|
249
|
+
}
|
|
219
250
|
const resolvedCommandPath = [...commandPath, node.name].join(".");
|
|
220
251
|
const existingPath = commandPathsByToolName.get(name);
|
|
221
252
|
if (existingPath !== undefined) {
|
|
@@ -226,8 +257,14 @@ function enumerateTools(root, casing, allowlist, omitRootToolNamePrefix) {
|
|
|
226
257
|
command: node,
|
|
227
258
|
commandPath: resolvedCommandPath,
|
|
228
259
|
name,
|
|
229
|
-
description: buildToolDescription(node.description, params, casing),
|
|
230
|
-
inputSchema: applySchemaCasing(toJsonSchema(params), casing)
|
|
260
|
+
description: buildToolDescription(node.description, params, node.examples, node.name, casing),
|
|
261
|
+
inputSchema: applySchemaCasing(toJsonSchema(params), casing),
|
|
262
|
+
...(node.result === undefined
|
|
263
|
+
? {}
|
|
264
|
+
: {
|
|
265
|
+
outputSchema: applySchemaCasing(toJsonSchema(node.result), casing),
|
|
266
|
+
resultSchema: node.result
|
|
267
|
+
})
|
|
231
268
|
});
|
|
232
269
|
return;
|
|
233
270
|
}
|
|
@@ -466,6 +503,166 @@ function validateToolArguments(schema, argumentsValue, casing) {
|
|
|
466
503
|
throwValidationErrors(errors);
|
|
467
504
|
return result;
|
|
468
505
|
}
|
|
506
|
+
function serializeResultValue(schema, value, casing, label, errors) {
|
|
507
|
+
const unwrappedSchema = unwrapOptional(schema);
|
|
508
|
+
if (value === null && unwrappedSchema.nullable === true) {
|
|
509
|
+
return null;
|
|
510
|
+
}
|
|
511
|
+
switch (unwrappedSchema.kind) {
|
|
512
|
+
case "object":
|
|
513
|
+
return serializeResultObject(unwrappedSchema, value, casing, label, errors);
|
|
514
|
+
case "array":
|
|
515
|
+
if (!Array.isArray(value)) {
|
|
516
|
+
errors.push({
|
|
517
|
+
path: label,
|
|
518
|
+
message: `Invalid value for "${label}". Expected an array, got ${describeReceived(value)}.`
|
|
519
|
+
});
|
|
520
|
+
return value;
|
|
521
|
+
}
|
|
522
|
+
return value.map((item, index) => serializeResultValue(unwrappedSchema.item, item, casing, `${label}[${index}]`, errors));
|
|
523
|
+
case "record":
|
|
524
|
+
if (!isPlainObject(value)) {
|
|
525
|
+
errors.push({
|
|
526
|
+
path: label,
|
|
527
|
+
message: `Invalid value for "${label}". Expected an object, got ${describeReceived(value)}.`
|
|
528
|
+
});
|
|
529
|
+
return value;
|
|
530
|
+
}
|
|
531
|
+
return Object.fromEntries(Object.entries(value).map(([key, item]) => [
|
|
532
|
+
key,
|
|
533
|
+
serializeResultValue(unwrappedSchema.value, item, casing, `${label}.${key}`, errors)
|
|
534
|
+
]));
|
|
535
|
+
case "oneOf": {
|
|
536
|
+
if (!isPlainObject(value)) {
|
|
537
|
+
errors.push({
|
|
538
|
+
path: label,
|
|
539
|
+
message: `Invalid value for "${label}". Expected an object, got ${describeReceived(value)}.`
|
|
540
|
+
});
|
|
541
|
+
return value;
|
|
542
|
+
}
|
|
543
|
+
const discriminator = value[unwrappedSchema.discriminator];
|
|
544
|
+
const branch = typeof discriminator === "string" ? unwrappedSchema.branches[discriminator] : undefined;
|
|
545
|
+
if (branch === undefined) {
|
|
546
|
+
const branchNames = Object.keys(unwrappedSchema.branches);
|
|
547
|
+
errors.push({
|
|
548
|
+
path: label.length === 0 ? unwrappedSchema.discriminator : `${label}.${unwrappedSchema.discriminator}`,
|
|
549
|
+
message: `Invalid value for "${label.length === 0 ? unwrappedSchema.discriminator : `${label}.${unwrappedSchema.discriminator}`}". Expected one of: ${branchNames.join(", ")}, got ${describeReceived(discriminator)}.`
|
|
550
|
+
});
|
|
551
|
+
return value;
|
|
552
|
+
}
|
|
553
|
+
const { [unwrappedSchema.discriminator]: ignoredDiscriminator, ...branchValue } = value;
|
|
554
|
+
void ignoredDiscriminator;
|
|
555
|
+
return {
|
|
556
|
+
[formatSegment(unwrappedSchema.discriminator, casing)]: discriminator,
|
|
557
|
+
...serializeResultObject(branch, branchValue, casing, label, errors)
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
case "union": {
|
|
561
|
+
if (!isPlainObject(value)) {
|
|
562
|
+
errors.push({
|
|
563
|
+
path: label,
|
|
564
|
+
message: `Invalid value for "${label}". Expected an object, got ${describeReceived(value)}.`
|
|
565
|
+
});
|
|
566
|
+
return value;
|
|
567
|
+
}
|
|
568
|
+
const branch = unwrappedSchema.branches.find((candidate) => Object.keys(candidate.shape).every((key) => candidate.shape[key]?.kind === "optional" ||
|
|
569
|
+
Object.prototype.hasOwnProperty.call(value, key)));
|
|
570
|
+
if (branch === undefined) {
|
|
571
|
+
errors.push({
|
|
572
|
+
path: label,
|
|
573
|
+
message: `Invalid value for "${label}". Expected one union branch, got ${describeReceived(value)}.`
|
|
574
|
+
});
|
|
575
|
+
return value;
|
|
576
|
+
}
|
|
577
|
+
return serializeResultObject(branch, value, casing, label, errors);
|
|
578
|
+
}
|
|
579
|
+
default:
|
|
580
|
+
return validateSchemaValue(schema, value, casing, label, errors);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
function serializeResultObject(schema, value, casing, label, errors) {
|
|
584
|
+
if (!isPlainObject(value)) {
|
|
585
|
+
errors.push({
|
|
586
|
+
path: label,
|
|
587
|
+
message: `Invalid value for "${label}". Expected an object, got ${describeReceived(value)}.`
|
|
588
|
+
});
|
|
589
|
+
return {};
|
|
590
|
+
}
|
|
591
|
+
const result = {};
|
|
592
|
+
const expectedKeys = new Set(Object.keys(schema.shape));
|
|
593
|
+
for (const key of Object.keys(value)) {
|
|
594
|
+
if (!expectedKeys.has(key) && schema.additionalProperties !== true) {
|
|
595
|
+
const fieldLabel = label.length === 0 ? key : `${label}.${key}`;
|
|
596
|
+
errors.push({
|
|
597
|
+
path: fieldLabel,
|
|
598
|
+
message: `Unexpected result field "${fieldLabel}". ${formatAvailableList([...expectedKeys].map((expectedKey) => label.length === 0 ? expectedKey : `${label}.${expectedKey}`))}`
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
for (const [sourceKey, rawChildSchema] of Object.entries(schema.shape)) {
|
|
603
|
+
const childSchema = unwrapOptional(rawChildSchema);
|
|
604
|
+
const hasValue = Object.prototype.hasOwnProperty.call(value, sourceKey);
|
|
605
|
+
const wireKey = formatSegment(sourceKey, casing);
|
|
606
|
+
const fieldLabel = label.length === 0 ? sourceKey : `${label}.${sourceKey}`;
|
|
607
|
+
if (!hasValue) {
|
|
608
|
+
if (childSchema.default !== undefined) {
|
|
609
|
+
Object.defineProperty(result, wireKey, {
|
|
610
|
+
value: childSchema.default,
|
|
611
|
+
enumerable: true,
|
|
612
|
+
configurable: true,
|
|
613
|
+
writable: true
|
|
614
|
+
});
|
|
615
|
+
continue;
|
|
616
|
+
}
|
|
617
|
+
if (isOptional(rawChildSchema)) {
|
|
618
|
+
continue;
|
|
619
|
+
}
|
|
620
|
+
errors.push({ path: fieldLabel, message: `Missing required result field "${fieldLabel}".` });
|
|
621
|
+
continue;
|
|
622
|
+
}
|
|
623
|
+
Object.defineProperty(result, wireKey, {
|
|
624
|
+
value: serializeResultValue(rawChildSchema, value[sourceKey], casing, fieldLabel, errors),
|
|
625
|
+
enumerable: true,
|
|
626
|
+
configurable: true,
|
|
627
|
+
writable: true
|
|
628
|
+
});
|
|
629
|
+
}
|
|
630
|
+
if (schema.additionalProperties === true) {
|
|
631
|
+
for (const key of Object.keys(value)) {
|
|
632
|
+
if (!expectedKeys.has(key)) {
|
|
633
|
+
Object.defineProperty(result, key, {
|
|
634
|
+
value: value[key],
|
|
635
|
+
enumerable: true,
|
|
636
|
+
configurable: true,
|
|
637
|
+
writable: true
|
|
638
|
+
});
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
return result;
|
|
643
|
+
}
|
|
644
|
+
function validateCommandResult(schema, value, casing) {
|
|
645
|
+
const errors = [];
|
|
646
|
+
const result = serializeResultObject(schema, value, casing, "", errors);
|
|
647
|
+
throwResultValidationErrors(errors);
|
|
648
|
+
return result;
|
|
649
|
+
}
|
|
650
|
+
function throwResultValidationErrors(errors) {
|
|
651
|
+
if (errors.length === 0) {
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
if (errors.length === 1) {
|
|
655
|
+
throw new ToolError(JSON_RPC_ERROR_CODES.INTERNAL_ERROR, errors[0]?.message ?? "Invalid command result.");
|
|
656
|
+
}
|
|
657
|
+
const rendered = errors
|
|
658
|
+
.slice(0, 10)
|
|
659
|
+
.map((error) => ` - ${error.path}: ${error.message}`);
|
|
660
|
+
const remaining = errors.length - rendered.length;
|
|
661
|
+
if (remaining > 0) {
|
|
662
|
+
rendered.push(` ... and ${remaining} more`);
|
|
663
|
+
}
|
|
664
|
+
throw new ToolError(JSON_RPC_ERROR_CODES.INTERNAL_ERROR, `${errors.length} result errors:\n${rendered.join("\n")}`);
|
|
665
|
+
}
|
|
469
666
|
function isContentBlock(value) {
|
|
470
667
|
if (!isPlainObject(value) || typeof value.type !== "string") {
|
|
471
668
|
return false;
|
|
@@ -491,7 +688,9 @@ function toToolContent(result) {
|
|
|
491
688
|
if (isContentBlock(result)) {
|
|
492
689
|
return [result];
|
|
493
690
|
}
|
|
494
|
-
|
|
691
|
+
const fallbackValue = result;
|
|
692
|
+
const fallbackText = JSON.stringify(fallbackValue);
|
|
693
|
+
return [{ type: "text", text: fallbackText }];
|
|
495
694
|
}
|
|
496
695
|
function toToolError(error) {
|
|
497
696
|
if (error instanceof ToolError) {
|
|
@@ -524,7 +723,7 @@ function createResolvedMCPServer(root, options) {
|
|
|
524
723
|
validateToolArguments: false
|
|
525
724
|
});
|
|
526
725
|
for (const tool of tools) {
|
|
527
|
-
|
|
726
|
+
const handler = async (argumentsValue) => {
|
|
528
727
|
let params;
|
|
529
728
|
let secrets;
|
|
530
729
|
try {
|
|
@@ -548,6 +747,13 @@ function createResolvedMCPServer(root, options) {
|
|
|
548
747
|
if (isHumanInLoopPending(result)) {
|
|
549
748
|
return renderPendingApproval(result);
|
|
550
749
|
}
|
|
750
|
+
if (tool.resultSchema !== undefined) {
|
|
751
|
+
const structuredContent = validateCommandResult(tool.resultSchema, result, casing);
|
|
752
|
+
return {
|
|
753
|
+
content: [{ type: "text", text: JSON.stringify(structuredContent) }],
|
|
754
|
+
structuredContent
|
|
755
|
+
};
|
|
756
|
+
}
|
|
551
757
|
return toToolContent(result);
|
|
552
758
|
}
|
|
553
759
|
catch (error) {
|
|
@@ -566,7 +772,13 @@ function createResolvedMCPServer(root, options) {
|
|
|
566
772
|
});
|
|
567
773
|
throw toToolError(error);
|
|
568
774
|
}
|
|
569
|
-
}
|
|
775
|
+
};
|
|
776
|
+
if (tool.outputSchema === undefined) {
|
|
777
|
+
server.tool(tool.name, tool.description, tool.inputSchema, handler);
|
|
778
|
+
}
|
|
779
|
+
else {
|
|
780
|
+
server.tool(tool.name, tool.description, tool.inputSchema, handler, tool.outputSchema);
|
|
781
|
+
}
|
|
570
782
|
}
|
|
571
783
|
return {
|
|
572
784
|
...server,
|
package/dist/number-schema.js
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
export function isValidNumberSchemaValue(value, schema) {
|
|
2
2
|
return (typeof value === "number" &&
|
|
3
3
|
Number.isFinite(value) &&
|
|
4
|
-
(schema.jsonType !== "integer" || Number.isInteger(value))
|
|
4
|
+
(schema.jsonType !== "integer" || Number.isInteger(value)) &&
|
|
5
|
+
(schema.minimum === undefined || value >= schema.minimum) &&
|
|
6
|
+
(schema.maximum === undefined || value <= schema.maximum));
|
|
5
7
|
}
|
|
6
8
|
export function getExpectedNumberDescription(schema) {
|
|
7
|
-
|
|
9
|
+
const type = schema.jsonType === "integer" ? "an integer" : "a number";
|
|
10
|
+
const bounds = [
|
|
11
|
+
schema.minimum === undefined ? undefined : `greater than or equal to ${schema.minimum}`,
|
|
12
|
+
schema.maximum === undefined ? undefined : `less than or equal to ${schema.maximum}`
|
|
13
|
+
].filter((bound) => bound !== undefined);
|
|
14
|
+
return bounds.length === 0 ? type : `${type} ${bounds.join(" and ")}`;
|
|
8
15
|
}
|
|
@@ -3,7 +3,12 @@ export const claudeDesktopAgent = {
|
|
|
3
3
|
name: "claude-desktop",
|
|
4
4
|
label: "Claude Desktop",
|
|
5
5
|
summary: "Anthropic's official desktop application for Claude",
|
|
6
|
-
configPath: "~/.
|
|
6
|
+
configPath: "~/.config/Claude/claude_desktop_config.json",
|
|
7
|
+
configPaths: {
|
|
8
|
+
darwin: "~/Library/Application Support/Claude/claude_desktop_config.json",
|
|
9
|
+
linux: "~/.config/Claude/claude_desktop_config.json",
|
|
10
|
+
win32: "~/AppData/Roaming/Claude/claude_desktop_config.json"
|
|
11
|
+
},
|
|
7
12
|
branding: {
|
|
8
13
|
colors: {
|
|
9
14
|
dark: "#D97757",
|
|
@@ -2,24 +2,33 @@ import { resolveAgentId } from "./registry.js";
|
|
|
2
2
|
function getOwnModel(specifier) {
|
|
3
3
|
return Object.prototype.hasOwnProperty.call(specifier, "model") ? specifier.model : undefined;
|
|
4
4
|
}
|
|
5
|
+
function requireNonBlank(value, field) {
|
|
6
|
+
const trimmed = value.trim();
|
|
7
|
+
if (trimmed.length === 0) {
|
|
8
|
+
throw new TypeError(`${field} must not be empty`);
|
|
9
|
+
}
|
|
10
|
+
return trimmed;
|
|
11
|
+
}
|
|
5
12
|
export function parseAgentSpecifier(input) {
|
|
6
13
|
const colonIndex = input.indexOf(":");
|
|
7
14
|
if (colonIndex === -1) {
|
|
8
|
-
return { agent: input
|
|
15
|
+
return { agent: requireNonBlank(input, "agent") };
|
|
9
16
|
}
|
|
10
|
-
const agent = input.slice(0, colonIndex)
|
|
17
|
+
const agent = requireNonBlank(input.slice(0, colonIndex), "agent");
|
|
11
18
|
const model = input.slice(colonIndex + 1).trim();
|
|
12
19
|
return {
|
|
13
20
|
agent,
|
|
14
|
-
...(model.length > 0 ? { model } : {})
|
|
21
|
+
...(model.length > 0 ? { model } : {})
|
|
15
22
|
};
|
|
16
23
|
}
|
|
17
24
|
export function formatAgentSpecifier(specifier) {
|
|
25
|
+
const agent = requireNonBlank(specifier.agent, "agent");
|
|
18
26
|
const model = getOwnModel(specifier);
|
|
19
|
-
|
|
20
|
-
|
|
27
|
+
const normalizedModel = model?.trim();
|
|
28
|
+
if (normalizedModel) {
|
|
29
|
+
return `${agent}:${normalizedModel}`;
|
|
21
30
|
}
|
|
22
|
-
return
|
|
31
|
+
return agent;
|
|
23
32
|
}
|
|
24
33
|
export function normalizeAgentId(input) {
|
|
25
34
|
const specifier = parseAgentSpecifier(input.trim());
|
|
@@ -14,6 +14,11 @@ export interface AgentDefinition {
|
|
|
14
14
|
readonly apiShapes?: readonly ApiShapeId[];
|
|
15
15
|
readonly otelCapture?: OtelCaptureDefinition;
|
|
16
16
|
configPath: string;
|
|
17
|
+
readonly configPaths?: {
|
|
18
|
+
readonly darwin: string;
|
|
19
|
+
readonly linux: string;
|
|
20
|
+
readonly win32: string;
|
|
21
|
+
};
|
|
17
22
|
branding: {
|
|
18
23
|
colors: {
|
|
19
24
|
dark: string;
|
|
@@ -15,6 +15,32 @@ export class UnsupportedAgentError extends Error {
|
|
|
15
15
|
function isConfigObject(value) {
|
|
16
16
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
17
17
|
}
|
|
18
|
+
function assertNonEmptyString(value, message) {
|
|
19
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
20
|
+
throw new Error(message);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function assertHttpUrl(value) {
|
|
24
|
+
assertNonEmptyString(value, "MCP HTTP URL must be a valid http or https URL.");
|
|
25
|
+
let parsed;
|
|
26
|
+
try {
|
|
27
|
+
parsed = new URL(value);
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
throw new Error("MCP HTTP URL must be a valid http or https URL.");
|
|
31
|
+
}
|
|
32
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
33
|
+
throw new Error("MCP HTTP URL must be a valid http or https URL.");
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function validateServerEntry(server) {
|
|
37
|
+
assertNonEmptyString(server.name, "MCP server name must be a non-empty string.");
|
|
38
|
+
if (server.config.transport === "stdio") {
|
|
39
|
+
assertNonEmptyString(server.config.command, "MCP stdio command must be a non-empty string.");
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
assertHttpUrl(server.config.url);
|
|
43
|
+
}
|
|
18
44
|
function resolveServerMap(document, configKey) {
|
|
19
45
|
const value = document[configKey];
|
|
20
46
|
if (value === undefined) {
|
|
@@ -32,12 +58,15 @@ export async function configure(agentId, server, options) {
|
|
|
32
58
|
if (!isSupported(agentId)) {
|
|
33
59
|
throw new UnsupportedAgentError(agentId);
|
|
34
60
|
}
|
|
61
|
+
validateServerEntry(server);
|
|
35
62
|
const config = getAgentConfig(agentId);
|
|
36
63
|
const configPath = resolveConfigPath(config, options.platform);
|
|
37
64
|
const shapeTransformer = getShapeTransformer(config.shape);
|
|
38
65
|
const shaped = shapeTransformer(server);
|
|
66
|
+
const enabledServer = { ...server, enabled: true };
|
|
67
|
+
const enabledShaped = shapeTransformer(enabledServer);
|
|
39
68
|
if (shaped === undefined) {
|
|
40
|
-
await unconfigure(agentId,
|
|
69
|
+
await unconfigure(agentId, enabledServer, options);
|
|
41
70
|
return;
|
|
42
71
|
}
|
|
43
72
|
const configDir = getConfigDirectory(configPath);
|
|
@@ -57,10 +86,13 @@ export async function configure(agentId, server, options) {
|
|
|
57
86
|
? servers[server.name]
|
|
58
87
|
: undefined;
|
|
59
88
|
const shapedServer = shaped;
|
|
89
|
+
const enabledShapedServer = enabledShaped;
|
|
60
90
|
if (existingServer !== undefined && isDeepStrictEqual(existingServer, shapedServer)) {
|
|
61
91
|
return { changed: false, content: document };
|
|
62
92
|
}
|
|
63
|
-
if (existingServer !== undefined &&
|
|
93
|
+
if (existingServer !== undefined &&
|
|
94
|
+
(enabledShapedServer === undefined ||
|
|
95
|
+
!isDeepStrictEqual(existingServer, enabledShapedServer))) {
|
|
64
96
|
throw new Error(`MCP server "${server.name}" already exists with different configuration in ${configPath}.`);
|
|
65
97
|
}
|
|
66
98
|
const newServers = {
|
|
@@ -98,7 +130,8 @@ export async function unconfigure(agentId, server, options) {
|
|
|
98
130
|
if (!Object.hasOwn(servers, serverName)) {
|
|
99
131
|
return { changed: false, content: document };
|
|
100
132
|
}
|
|
101
|
-
if (expectedServer !== undefined &&
|
|
133
|
+
if (expectedServer !== undefined &&
|
|
134
|
+
!isDeepStrictEqual(servers[serverName], expectedServer)) {
|
|
102
135
|
return { changed: false, content: document };
|
|
103
136
|
}
|
|
104
137
|
const newServers = { ...servers };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# @poe-code/frontmatter
|
|
2
|
+
|
|
3
|
+
Shared YAML frontmatter parsing for poe-code packages.
|
|
4
|
+
|
|
5
|
+
## API
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import {
|
|
9
|
+
FrontmatterParseError,
|
|
10
|
+
parseFrontmatter,
|
|
11
|
+
parseFrontmatterDocument,
|
|
12
|
+
stringifyFrontmatter
|
|
13
|
+
} from "@poe-code/frontmatter";
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
- `parseFrontmatter(source)` returns `{ frontmatter, body }`.
|
|
17
|
+
- `parseFrontmatterDocument(source)` returns `{ frontmatter, body, errors, lineCounter }` for callers that need YAML diagnostics.
|
|
18
|
+
- `stringifyFrontmatter(frontmatter, body)` writes `---` fences, YAML, and the body.
|
|
19
|
+
- `FrontmatterParseError` is thrown for malformed frontmatter, invalid YAML, non-object frontmatter, and stringify failures.
|
|
20
|
+
|
|
21
|
+
When no leading frontmatter block exists, parsing returns `{ frontmatter: {}, body: source }`.
|
|
22
|
+
The returned `body` is sliced from the original input and is otherwise byte-for-byte unchanged.
|
|
23
|
+
YAML mappings that contain `__proto__` keep it as an own property without changing the object's prototype.
|
|
24
|
+
|
|
25
|
+
Duplicate mapping keys use YAML's last-wins behavior by default, matching the
|
|
26
|
+
legacy frontmatter readers. Callers that need strict YAML mappings can pass
|
|
27
|
+
`{ uniqueKeys: true }` to `parseFrontmatter` or `parseFrontmatterDocument`.
|
|
28
|
+
|
|
29
|
+
## Environment
|
|
30
|
+
|
|
31
|
+
This package reads no environment variables.
|
|
32
|
+
|
|
33
|
+
## Config
|
|
34
|
+
|
|
35
|
+
This package has no configuration options.
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
export function splitFrontmatterBlock(source) {
|
|
2
|
+
const content = source.startsWith("\uFEFF") ? source.slice(1) : source;
|
|
3
|
+
const opening = readOpeningFence(content);
|
|
4
|
+
if (opening === undefined) {
|
|
5
|
+
return { body: source };
|
|
6
|
+
}
|
|
7
|
+
const closing = findClosingFence(content, opening.next);
|
|
8
|
+
if (closing === undefined) {
|
|
9
|
+
throw new Error("Missing YAML frontmatter end delimiter (---).");
|
|
10
|
+
}
|
|
11
|
+
return {
|
|
12
|
+
raw: content.slice(opening.next, closing.index),
|
|
13
|
+
body: content.slice(closing.end + closing.lineBreakLength)
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
function readOpeningFence(source) {
|
|
17
|
+
if (!source.startsWith("---")) {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
const lineEnd = findLineEnd(source, 0);
|
|
21
|
+
if (lineEnd.lineBreakLength === 0 || source.slice(0, lineEnd.index) !== "---") {
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
return { next: lineEnd.index + lineEnd.lineBreakLength };
|
|
25
|
+
}
|
|
26
|
+
function findClosingFence(source, start) {
|
|
27
|
+
let lineStart = start;
|
|
28
|
+
while (lineStart <= source.length) {
|
|
29
|
+
const lineEnd = findLineEnd(source, lineStart);
|
|
30
|
+
if (isClosingFenceLine(source.slice(lineStart, lineEnd.index))) {
|
|
31
|
+
return {
|
|
32
|
+
index: lineStart,
|
|
33
|
+
end: lineEnd.index,
|
|
34
|
+
lineBreakLength: lineEnd.lineBreakLength
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
if (lineEnd.lineBreakLength === 0) {
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
lineStart = lineEnd.index + lineEnd.lineBreakLength;
|
|
41
|
+
}
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
function isClosingFenceLine(line) {
|
|
45
|
+
if (!line.startsWith("---")) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
for (let index = 3; index < line.length; index += 1) {
|
|
49
|
+
const character = line[index];
|
|
50
|
+
if (character !== " " && character !== "\t") {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
function findLineEnd(source, start) {
|
|
57
|
+
let index = start;
|
|
58
|
+
while (index < source.length) {
|
|
59
|
+
const character = source[index];
|
|
60
|
+
if (character === "\n") {
|
|
61
|
+
return { index, lineBreakLength: 1 };
|
|
62
|
+
}
|
|
63
|
+
if (character === "\r") {
|
|
64
|
+
return {
|
|
65
|
+
index,
|
|
66
|
+
lineBreakLength: source[index + 1] === "\n" ? 2 : 1
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
index += 1;
|
|
70
|
+
}
|
|
71
|
+
return { index: source.length, lineBreakLength: 0 };
|
|
72
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { splitFrontmatterBlock, type FrontmatterBlock, type SplitFrontmatterResult } from "./fences.js";
|
|
2
|
+
export { FrontmatterParseError, parseFrontmatter, parseFrontmatterDocument, type ParsedFrontmatter, type ParsedFrontmatterDocument, type ParseFrontmatterOptions } from "./parse.js";
|
|
3
|
+
export { stringifyFrontmatter } from "./stringify.js";
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { LineCounter } from "yaml";
|
|
2
|
+
export interface ParsedFrontmatter {
|
|
3
|
+
frontmatter: Record<string, unknown>;
|
|
4
|
+
body: string;
|
|
5
|
+
}
|
|
6
|
+
export interface ParsedFrontmatterDocument extends ParsedFrontmatter {
|
|
7
|
+
errors: readonly {
|
|
8
|
+
message: string;
|
|
9
|
+
pos?: [number, number];
|
|
10
|
+
}[];
|
|
11
|
+
lineCounter: LineCounter;
|
|
12
|
+
}
|
|
13
|
+
export interface ParseFrontmatterOptions {
|
|
14
|
+
uniqueKeys?: boolean;
|
|
15
|
+
}
|
|
16
|
+
export declare class FrontmatterParseError extends Error {
|
|
17
|
+
constructor(message: string);
|
|
18
|
+
}
|
|
19
|
+
export declare function parseFrontmatter(source: string, options?: ParseFrontmatterOptions): ParsedFrontmatter;
|
|
20
|
+
export declare function parseFrontmatterDocument(source: string, options?: ParseFrontmatterOptions): ParsedFrontmatterDocument;
|