@stubbedev/trimit-mcp 0.1.2 → 0.1.3
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 +154 -136
- package/dist/categories/customers.js +3 -3
- package/dist/categories/salesdocs.js +19 -16
- package/dist/server.js +379 -2
- package/dist/spec/entities.js +406 -0
- package/dist/spec/enums.js +55 -0
- package/dist/spec/lint.js +111 -0
- package/dist/spec/odata.js +294 -0
- package/dist/spec/payloads.js +89 -0
- package/dist/spec/types.js +1 -0
- package/dist/spec/validate.js +368 -0
- package/package.json +6 -5
package/dist/server.js
CHANGED
|
@@ -1,9 +1,29 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { dirname, resolve } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
5
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
6
|
import { z } from "zod";
|
|
4
7
|
import { ToolRegistry } from "./registry.js";
|
|
5
8
|
import { standardTools, masterdataTools, productsTools, inventoryTools, customersTools, salesdocsTools, postedsalesTools, exportedTools, metadataTools, } from "./categories/index.js";
|
|
6
9
|
import { HOST, STD_API_PATH, TRIMIT_API_PATH, TRIMIT_AUTH, buildHeaders, trimitBase } from "./common.js";
|
|
10
|
+
import { ENTITIES, ENTITY_NAMES, findEntityByResourcePath, getEntity } from "./spec/entities.js";
|
|
11
|
+
import { ENUMS } from "./spec/enums.js";
|
|
12
|
+
import { buildPayload, entitySummary } from "./spec/payloads.js";
|
|
13
|
+
import { checkOdata } from "./spec/odata.js";
|
|
14
|
+
import { validateRequest } from "./spec/validate.js";
|
|
15
|
+
import { lintSnippet } from "./spec/lint.js";
|
|
16
|
+
const PKG_VERSION = (() => {
|
|
17
|
+
try {
|
|
18
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
19
|
+
const pkgPath = resolve(here, "..", "package.json");
|
|
20
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
|
|
21
|
+
return pkg.version ?? "0.0.0";
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return "0.0.0";
|
|
25
|
+
}
|
|
26
|
+
})();
|
|
7
27
|
// Static search lookup for search_trimit_api bootstrap tool
|
|
8
28
|
const TRIMIT_API_SEARCH_MAP = [
|
|
9
29
|
// standard
|
|
@@ -86,9 +106,11 @@ export class TrimitMcpServer {
|
|
|
86
106
|
categoryHandles = new Map();
|
|
87
107
|
constructor() {
|
|
88
108
|
this.registry = new ToolRegistry();
|
|
89
|
-
this.mcpServer = new McpServer({ name: "trimit-dev-assistant", version:
|
|
109
|
+
this.mcpServer = new McpServer({ name: "trimit-dev-assistant", version: PKG_VERSION }, {
|
|
90
110
|
capabilities: {
|
|
91
111
|
tools: { listChanged: true },
|
|
112
|
+
resources: { listChanged: false, subscribe: false },
|
|
113
|
+
prompts: { listChanged: false },
|
|
92
114
|
},
|
|
93
115
|
instructions: "Use this server whenever the user is building, debugging, or learning about the TRIMIT API — the fashion/apparel ERP extension layered on Microsoft Dynamics 365 Business Central (api.businesscentral.dynamics.com). " +
|
|
94
116
|
"It documents 47 endpoints across master data, products, inventory, customers, sales documents, posted sales, exported markers, and metadata.\n\n" +
|
|
@@ -113,9 +135,27 @@ export class TrimitMcpServer {
|
|
|
113
135
|
" - @odata.nextLink / pagination → trimit_explain_paging\n" +
|
|
114
136
|
" - Sales document lifecycle / processed vs exported → trimit_explain_doc_lifecycle\n" +
|
|
115
137
|
" - Errors / 401 / 403 / 409 / 412 → trimit_explain_errors\n\n" +
|
|
138
|
+
"VALIDATING USER CODE — use the spec tools:\n" +
|
|
139
|
+
" - trimit_describe_entity — fields, types, required/mutable, enums, navigation properties\n" +
|
|
140
|
+
" - trimit_example_payload — minimal or full POST/PATCH body template per entity\n" +
|
|
141
|
+
" - trimit_validate_request — check a constructed request (endpoint/method/headers/body) against the TRIMIT spec\n" +
|
|
142
|
+
" - trimit_check_odata — validate $filter / $select / $expand / $orderby / $top / $skip\n" +
|
|
143
|
+
" - trimit_lint_snippet — scan user's integration code for BC pitfalls (no If-Match, no nextLink loop, hardcoded token, missing IEEE754, etc.)\n\n" +
|
|
144
|
+
"RESOURCES — readable spec catalog:\n" +
|
|
145
|
+
" - trimit://entity/{name} — entity schema as JSON\n" +
|
|
146
|
+
" - trimit://enum/{name} — enum allowed values\n" +
|
|
147
|
+
" - trimit://entities — list of all entities\n" +
|
|
148
|
+
" - trimit://enums — list of all enums\n" +
|
|
149
|
+
" - trimit://categories — tool category catalog\n\n" +
|
|
150
|
+
"PROMPTS — workflows:\n" +
|
|
151
|
+
" - review_sales_order_post — audit a /salesDocuments POST body + headers\n" +
|
|
152
|
+
" - add_idempotent_export_loop — guide adding exportedDocuments marker logic to a poller\n\n" +
|
|
116
153
|
"This server constructs TRIMIT API requests (endpoint, method, headers, body, code example, auth) — it does not execute them.",
|
|
117
154
|
});
|
|
118
155
|
this.registerBootstrapTools();
|
|
156
|
+
this.registerSpecTools();
|
|
157
|
+
this.registerResources();
|
|
158
|
+
this.registerPrompts();
|
|
119
159
|
}
|
|
120
160
|
registerBootstrapTools() {
|
|
121
161
|
// list_categories
|
|
@@ -667,6 +707,343 @@ https://learn.microsoft.com/en-us/dynamics365/business-central/dev-itpro/api-ref
|
|
|
667
707
|
return { content: [{ type: "text", text }] };
|
|
668
708
|
});
|
|
669
709
|
}
|
|
710
|
+
registerSpecTools() {
|
|
711
|
+
// trimit_describe_entity
|
|
712
|
+
this.mcpServer.tool("trimit_describe_entity", "Return the schema for a TRIMIT entity: fields with types, required/mutable flags, enums, decimal markers, navigation properties, default $expand, and notes. Pass entity name (e.g. 'customer', 'salesDocument', 'salesReturnOrder', 'exportedDocument', 'master', 'item', 'campaign', 'inventory') OR resourcePath (e.g. 'customers', 'salesDocuments'). Call list_entities() shape via the trimit://entities resource for the full list.", {
|
|
713
|
+
entity: z.string().optional().describe("Entity name from the registry"),
|
|
714
|
+
resourcePath: z.string().optional().describe("URL segment, e.g. 'customers'"),
|
|
715
|
+
}, async ({ entity, resourcePath }) => {
|
|
716
|
+
const resolved = entity ? getEntity(entity) : resourcePath ? findEntityByResourcePath(resourcePath) : undefined;
|
|
717
|
+
if (!resolved) {
|
|
718
|
+
return {
|
|
719
|
+
content: [
|
|
720
|
+
{
|
|
721
|
+
type: "text",
|
|
722
|
+
text: JSON.stringify({
|
|
723
|
+
error: `Unknown entity. Provide one of: ${ENTITY_NAMES.join(", ")}`,
|
|
724
|
+
available: ENTITY_NAMES,
|
|
725
|
+
}, null, 2),
|
|
726
|
+
},
|
|
727
|
+
],
|
|
728
|
+
};
|
|
729
|
+
}
|
|
730
|
+
return {
|
|
731
|
+
content: [
|
|
732
|
+
{ type: "text", text: JSON.stringify(entitySummary(resolved), null, 2) },
|
|
733
|
+
],
|
|
734
|
+
};
|
|
735
|
+
});
|
|
736
|
+
// trimit_example_payload
|
|
737
|
+
this.mcpServer.tool("trimit_example_payload", "Generate a request body template for an entity. shape='minimal' returns only required fields (create) or one mutable field (patch); shape='full' returns every writable field with placeholder values. Decimal fields are emitted as numbers — wrap as strings if you send IEEE754Compatible: true.", {
|
|
738
|
+
entity: z.string().describe("Entity name, e.g. 'customer', 'salesDocument', 'salesReturnOrder'"),
|
|
739
|
+
operation: z.enum(["create", "patch"]).default("create"),
|
|
740
|
+
shape: z.enum(["minimal", "full"]).default("minimal"),
|
|
741
|
+
}, async ({ entity, operation, shape }) => {
|
|
742
|
+
const e = getEntity(entity);
|
|
743
|
+
if (!e) {
|
|
744
|
+
return {
|
|
745
|
+
content: [
|
|
746
|
+
{
|
|
747
|
+
type: "text",
|
|
748
|
+
text: JSON.stringify({ error: `Unknown entity '${entity}'`, available: ENTITY_NAMES }, null, 2),
|
|
749
|
+
},
|
|
750
|
+
],
|
|
751
|
+
};
|
|
752
|
+
}
|
|
753
|
+
const payload = buildPayload(entity, operation, shape);
|
|
754
|
+
return {
|
|
755
|
+
content: [
|
|
756
|
+
{
|
|
757
|
+
type: "text",
|
|
758
|
+
text: JSON.stringify({
|
|
759
|
+
entity,
|
|
760
|
+
operation,
|
|
761
|
+
shape,
|
|
762
|
+
method: operation === "create" ? "POST" : "PATCH",
|
|
763
|
+
contentType: "application/json",
|
|
764
|
+
recommendedHeaders: operation === "patch"
|
|
765
|
+
? { "If-Match": "*", "IEEE754Compatible": "true" }
|
|
766
|
+
: { "IEEE754Compatible": "true" },
|
|
767
|
+
body: payload,
|
|
768
|
+
notes: e.notes,
|
|
769
|
+
}, null, 2),
|
|
770
|
+
},
|
|
771
|
+
],
|
|
772
|
+
};
|
|
773
|
+
});
|
|
774
|
+
// trimit_validate_request
|
|
775
|
+
this.mcpServer.tool("trimit_validate_request", "Validate a constructed TRIMIT/BC request against the spec. Checks base path, placeholder substitution, required headers (Authorization, Content-Type, If-Match on PATCH/DELETE, IEEE754Compatible when decimals are present), body shape (required fields, unknown fields, enum casing, type mismatches, immutable fields on PATCH), nested arrays (e.g. salesDocumentLines), $batch envelope, and embedded OData query parameters. Returns a list of issues with severity, code, path, and fix suggestion.", {
|
|
776
|
+
endpoint: z.string().describe("Full URL with or without placeholders"),
|
|
777
|
+
method: z.enum(["GET", "POST", "PATCH", "PUT", "DELETE"]),
|
|
778
|
+
headers: z.record(z.string(), z.string()).optional(),
|
|
779
|
+
body: z.unknown().optional(),
|
|
780
|
+
}, async ({ endpoint, method, headers, body }) => {
|
|
781
|
+
const issues = validateRequest({ endpoint, method, headers, body });
|
|
782
|
+
const errors = issues.filter((i) => i.severity === "error").length;
|
|
783
|
+
const warnings = issues.filter((i) => i.severity === "warning").length;
|
|
784
|
+
return {
|
|
785
|
+
content: [
|
|
786
|
+
{
|
|
787
|
+
type: "text",
|
|
788
|
+
text: JSON.stringify({
|
|
789
|
+
summary: { errors, warnings, info: issues.length - errors - warnings, total: issues.length },
|
|
790
|
+
ok: errors === 0,
|
|
791
|
+
issues,
|
|
792
|
+
}, null, 2),
|
|
793
|
+
},
|
|
794
|
+
],
|
|
795
|
+
};
|
|
796
|
+
});
|
|
797
|
+
// trimit_check_odata
|
|
798
|
+
this.mcpServer.tool("trimit_check_odata", "Validate an OData query against the TRIMIT spec. Checks $filter (parenthesis balance, quote escaping, '=' vs 'eq', null compares, datetime format, unknown fields), $select (unknown fields), $expand (unknown navigation properties, OData v4 nested syntax), $orderby, $top (page cap), $skip. Pass entity or resourcePath to enable field-name resolution.", {
|
|
799
|
+
entity: z.string().optional(),
|
|
800
|
+
resourcePath: z.string().optional(),
|
|
801
|
+
filter: z.string().optional(),
|
|
802
|
+
select: z.array(z.string()).optional(),
|
|
803
|
+
expand: z.string().optional(),
|
|
804
|
+
orderby: z.string().optional(),
|
|
805
|
+
top: z.number().optional(),
|
|
806
|
+
skip: z.number().optional(),
|
|
807
|
+
}, async (args) => {
|
|
808
|
+
const issues = checkOdata(args);
|
|
809
|
+
const errors = issues.filter((i) => i.severity === "error").length;
|
|
810
|
+
const warnings = issues.length - errors;
|
|
811
|
+
return {
|
|
812
|
+
content: [
|
|
813
|
+
{
|
|
814
|
+
type: "text",
|
|
815
|
+
text: JSON.stringify({
|
|
816
|
+
summary: { errors, warnings, total: issues.length },
|
|
817
|
+
ok: errors === 0,
|
|
818
|
+
issues,
|
|
819
|
+
}, null, 2),
|
|
820
|
+
},
|
|
821
|
+
],
|
|
822
|
+
};
|
|
823
|
+
});
|
|
824
|
+
// trimit_lint_snippet
|
|
825
|
+
this.mcpServer.tool("trimit_lint_snippet", "Scan a user's integration code (JS/TS/Python/curl) for common TRIMIT/BC pitfalls: hardcoded tokens, missing token-refresh, missing @odata.nextLink loop, PATCH without If-Match, decimal fields without IEEE754Compatible, no 429/Retry-After handling, polling /salesDocuments without exportedDocuments markers, $batch sub-requests without id or with absolute URLs, $filter using '=' instead of 'eq', unquoted string literals, case-wrong docType, wrong API base for TRIMIT resources.", {
|
|
826
|
+
snippet: z.string().describe("Source code, fetch call, or pseudocode to lint"),
|
|
827
|
+
}, async ({ snippet }) => {
|
|
828
|
+
const findings = lintSnippet(snippet);
|
|
829
|
+
const errors = findings.filter((f) => f.severity === "error").length;
|
|
830
|
+
const warnings = findings.filter((f) => f.severity === "warning").length;
|
|
831
|
+
return {
|
|
832
|
+
content: [
|
|
833
|
+
{
|
|
834
|
+
type: "text",
|
|
835
|
+
text: JSON.stringify({
|
|
836
|
+
summary: { errors, warnings, info: findings.length - errors - warnings, total: findings.length },
|
|
837
|
+
ok: errors === 0,
|
|
838
|
+
findings,
|
|
839
|
+
}, null, 2),
|
|
840
|
+
},
|
|
841
|
+
],
|
|
842
|
+
};
|
|
843
|
+
});
|
|
844
|
+
}
|
|
845
|
+
registerResources() {
|
|
846
|
+
// trimit://entities — list of all entities
|
|
847
|
+
this.mcpServer.registerResource("entities", "trimit://entities", {
|
|
848
|
+
title: "TRIMIT entities",
|
|
849
|
+
description: "List of all known TRIMIT/BC entities exposed in the spec catalog.",
|
|
850
|
+
mimeType: "application/json",
|
|
851
|
+
}, async (uri) => ({
|
|
852
|
+
contents: [
|
|
853
|
+
{
|
|
854
|
+
uri: uri.href,
|
|
855
|
+
mimeType: "application/json",
|
|
856
|
+
text: JSON.stringify({
|
|
857
|
+
entities: Object.values(ENTITIES).map((e) => ({
|
|
858
|
+
name: e.name,
|
|
859
|
+
resourcePath: e.resourcePath,
|
|
860
|
+
category: e.category,
|
|
861
|
+
apiBase: e.apiBase,
|
|
862
|
+
keys: e.keys,
|
|
863
|
+
})),
|
|
864
|
+
}, null, 2),
|
|
865
|
+
},
|
|
866
|
+
],
|
|
867
|
+
}));
|
|
868
|
+
// trimit://enums — list of all enums
|
|
869
|
+
this.mcpServer.registerResource("enums", "trimit://enums", {
|
|
870
|
+
title: "TRIMIT enums",
|
|
871
|
+
description: "List of all enums referenced by entity fields.",
|
|
872
|
+
mimeType: "application/json",
|
|
873
|
+
}, async (uri) => ({
|
|
874
|
+
contents: [
|
|
875
|
+
{
|
|
876
|
+
uri: uri.href,
|
|
877
|
+
mimeType: "application/json",
|
|
878
|
+
text: JSON.stringify({ enums: Object.values(ENUMS) }, null, 2),
|
|
879
|
+
},
|
|
880
|
+
],
|
|
881
|
+
}));
|
|
882
|
+
// trimit://categories — tool category catalog
|
|
883
|
+
this.mcpServer.registerResource("categories", "trimit://categories", {
|
|
884
|
+
title: "TRIMIT tool categories",
|
|
885
|
+
description: "Lazy-loadable tool categories with descriptions and tool counts.",
|
|
886
|
+
mimeType: "application/json",
|
|
887
|
+
}, async (uri) => ({
|
|
888
|
+
contents: [
|
|
889
|
+
{
|
|
890
|
+
uri: uri.href,
|
|
891
|
+
mimeType: "application/json",
|
|
892
|
+
text: JSON.stringify({
|
|
893
|
+
categories: Object.entries(CATEGORY_DESCRIPTIONS).map(([name, description]) => ({
|
|
894
|
+
name,
|
|
895
|
+
description,
|
|
896
|
+
toolCount: CATEGORY_TOOL_MAP[name]?.length ?? 0,
|
|
897
|
+
loaded: this.registry.loadedCategories.has(name),
|
|
898
|
+
})),
|
|
899
|
+
}, null, 2),
|
|
900
|
+
},
|
|
901
|
+
],
|
|
902
|
+
}));
|
|
903
|
+
// trimit://entity/{name}
|
|
904
|
+
this.mcpServer.registerResource("entity", new ResourceTemplate("trimit://entity/{name}", {
|
|
905
|
+
list: async () => ({
|
|
906
|
+
resources: Object.values(ENTITIES).map((e) => ({
|
|
907
|
+
uri: `trimit://entity/${e.name}`,
|
|
908
|
+
name: e.name,
|
|
909
|
+
description: `${e.resourcePath} (${e.category}) — ${e.fields.length} fields`,
|
|
910
|
+
mimeType: "application/json",
|
|
911
|
+
})),
|
|
912
|
+
}),
|
|
913
|
+
complete: {
|
|
914
|
+
name: async (value) => ENTITY_NAMES.filter((n) => n.toLowerCase().startsWith(value.toLowerCase())).slice(0, 20),
|
|
915
|
+
},
|
|
916
|
+
}), {
|
|
917
|
+
title: "TRIMIT entity schema",
|
|
918
|
+
description: "Field-level schema for a single entity.",
|
|
919
|
+
mimeType: "application/json",
|
|
920
|
+
}, async (uri, variables) => {
|
|
921
|
+
const name = String(variables.name);
|
|
922
|
+
const entity = getEntity(name);
|
|
923
|
+
if (!entity) {
|
|
924
|
+
return {
|
|
925
|
+
contents: [
|
|
926
|
+
{
|
|
927
|
+
uri: uri.href,
|
|
928
|
+
mimeType: "application/json",
|
|
929
|
+
text: JSON.stringify({ error: `Unknown entity '${name}'`, available: ENTITY_NAMES }, null, 2),
|
|
930
|
+
},
|
|
931
|
+
],
|
|
932
|
+
};
|
|
933
|
+
}
|
|
934
|
+
return {
|
|
935
|
+
contents: [
|
|
936
|
+
{
|
|
937
|
+
uri: uri.href,
|
|
938
|
+
mimeType: "application/json",
|
|
939
|
+
text: JSON.stringify(entitySummary(entity), null, 2),
|
|
940
|
+
},
|
|
941
|
+
],
|
|
942
|
+
};
|
|
943
|
+
});
|
|
944
|
+
// trimit://enum/{name}
|
|
945
|
+
this.mcpServer.registerResource("enum", new ResourceTemplate("trimit://enum/{name}", {
|
|
946
|
+
list: async () => ({
|
|
947
|
+
resources: Object.values(ENUMS).map((e) => ({
|
|
948
|
+
uri: `trimit://enum/${e.name}`,
|
|
949
|
+
name: e.name,
|
|
950
|
+
description: e.description,
|
|
951
|
+
mimeType: "application/json",
|
|
952
|
+
})),
|
|
953
|
+
}),
|
|
954
|
+
complete: {
|
|
955
|
+
name: async (value) => Object.keys(ENUMS)
|
|
956
|
+
.filter((n) => n.toLowerCase().startsWith(value.toLowerCase()))
|
|
957
|
+
.slice(0, 20),
|
|
958
|
+
},
|
|
959
|
+
}), {
|
|
960
|
+
title: "TRIMIT enum",
|
|
961
|
+
description: "Allowed values for a single enum.",
|
|
962
|
+
mimeType: "application/json",
|
|
963
|
+
}, async (uri, variables) => {
|
|
964
|
+
const name = String(variables.name);
|
|
965
|
+
const e = ENUMS[name];
|
|
966
|
+
if (!e) {
|
|
967
|
+
return {
|
|
968
|
+
contents: [
|
|
969
|
+
{
|
|
970
|
+
uri: uri.href,
|
|
971
|
+
mimeType: "application/json",
|
|
972
|
+
text: JSON.stringify({ error: `Unknown enum '${name}'`, available: Object.keys(ENUMS) }, null, 2),
|
|
973
|
+
},
|
|
974
|
+
],
|
|
975
|
+
};
|
|
976
|
+
}
|
|
977
|
+
return {
|
|
978
|
+
contents: [
|
|
979
|
+
{ uri: uri.href, mimeType: "application/json", text: JSON.stringify(e, null, 2) },
|
|
980
|
+
],
|
|
981
|
+
};
|
|
982
|
+
});
|
|
983
|
+
}
|
|
984
|
+
registerPrompts() {
|
|
985
|
+
this.mcpServer.registerPrompt("review_sales_order_post", {
|
|
986
|
+
title: "Review /salesDocuments POST",
|
|
987
|
+
description: "Audit a sales document create payload against the TRIMIT spec, with emphasis on docType casing, line types, decimals, releaseDocument behavior, and IEEE754Compatible.",
|
|
988
|
+
argsSchema: {
|
|
989
|
+
body: z.string().describe("JSON body the client intends to POST to /salesDocuments"),
|
|
990
|
+
headers: z.string().optional().describe("Optional JSON object of headers"),
|
|
991
|
+
},
|
|
992
|
+
}, ({ body, headers }) => ({
|
|
993
|
+
messages: [
|
|
994
|
+
{
|
|
995
|
+
role: "user",
|
|
996
|
+
content: {
|
|
997
|
+
type: "text",
|
|
998
|
+
text: "Audit this /salesDocuments POST against the TRIMIT spec. Then call trimit_validate_request with the parsed body/headers and report the findings inline.\n\n" +
|
|
999
|
+
"Specifically check:\n" +
|
|
1000
|
+
"1. docType casing — must match enum exactly ('Order', not 'order').\n" +
|
|
1001
|
+
"2. salesDocumentLines[].type — must be in salesLineType enum, case-sensitive.\n" +
|
|
1002
|
+
"3. Decimals (unitPrice, quantity, discountAmount) — recommend IEEE754Compatible: true with string values.\n" +
|
|
1003
|
+
"4. orderDate — YYYY-MM-DD only.\n" +
|
|
1004
|
+
"5. releaseDocument behavior — true releases in BC immediately; ensure caller intends this.\n" +
|
|
1005
|
+
"6. additionalFields shape on header vs lines.\n" +
|
|
1006
|
+
"7. Header completeness — Authorization, Content-Type, IEEE754Compatible.\n\n" +
|
|
1007
|
+
"Body:\n```json\n" +
|
|
1008
|
+
body +
|
|
1009
|
+
"\n```\n" +
|
|
1010
|
+
(headers ? "Headers:\n```json\n" + headers + "\n```\n" : "") +
|
|
1011
|
+
"\nReturn: a numbered list of issues with severity, then the corrected body if applicable.",
|
|
1012
|
+
},
|
|
1013
|
+
},
|
|
1014
|
+
],
|
|
1015
|
+
}));
|
|
1016
|
+
this.mcpServer.registerPrompt("add_idempotent_export_loop", {
|
|
1017
|
+
title: "Add idempotent export loop",
|
|
1018
|
+
description: "Guide refactoring a /salesDocuments poller to use /exportedDocuments markers so the same doc is not re-processed.",
|
|
1019
|
+
argsSchema: {
|
|
1020
|
+
snippet: z.string().describe("Current polling code"),
|
|
1021
|
+
language: z.string().optional().describe("Source language hint, e.g. typescript / python"),
|
|
1022
|
+
},
|
|
1023
|
+
}, ({ snippet, language }) => ({
|
|
1024
|
+
messages: [
|
|
1025
|
+
{
|
|
1026
|
+
role: "user",
|
|
1027
|
+
content: {
|
|
1028
|
+
type: "text",
|
|
1029
|
+
text: "Refactor this TRIMIT /salesDocuments poller into an idempotent export loop using /exportedDocuments markers. Call trimit_lint_snippet on the input first and address any findings related to lifecycle/paging/auth.\n\n" +
|
|
1030
|
+
"Required behavior of the rewrite:\n" +
|
|
1031
|
+
"1. GET /salesDocuments() with $filter=(processedDate gt 0001-01-01) so only processed docs come back.\n" +
|
|
1032
|
+
"2. Iterate @odata.nextLink until exhausted.\n" +
|
|
1033
|
+
"3. For each doc, after successful downstream handoff, POST /exportedDocuments {type, number}.\n" +
|
|
1034
|
+
"4. On downstream failure, do NOT POST the marker — the doc must reappear on next poll.\n" +
|
|
1035
|
+
"5. Handle 401 by refreshing the token and retrying once.\n" +
|
|
1036
|
+
"6. Respect 429 Retry-After.\n" +
|
|
1037
|
+
"7. Recovery: document how to DELETE /exportedDocuments(type, number) to re-include a doc.\n\n" +
|
|
1038
|
+
(language ? `Source language: ${language}\n\n` : "") +
|
|
1039
|
+
"Current code:\n```\n" +
|
|
1040
|
+
snippet +
|
|
1041
|
+
"\n```",
|
|
1042
|
+
},
|
|
1043
|
+
},
|
|
1044
|
+
],
|
|
1045
|
+
}));
|
|
1046
|
+
}
|
|
670
1047
|
registerCategoryTools(_categoryName, tools) {
|
|
671
1048
|
const handles = [];
|
|
672
1049
|
for (const tool of tools) {
|