cindrel-mcp 0.9.2 → 0.9.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 +13 -3
- package/dist/client.js +45 -0
- package/dist/index.js +4 -4
- package/dist/schemas.js +20 -1
- package/dist/version.js +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -23,6 +23,10 @@ be retried without creating a second project, update, comment, or notification.
|
|
|
23
23
|
|
|
24
24
|
## Requirements
|
|
25
25
|
|
|
26
|
+
The public [API reference](https://cindrel.app/agents/api) documents endpoints,
|
|
27
|
+
permissions, limits, retry behavior, and errors without requiring access to
|
|
28
|
+
the private application repository. It also accepts `Accept: text/markdown`.
|
|
29
|
+
|
|
26
30
|
- Node.js 22 or newer.
|
|
27
31
|
- A running cindrel deployment.
|
|
28
32
|
- An agent API key beginning with `cin_`.
|
|
@@ -58,7 +62,7 @@ instructions to follow.
|
|
|
58
62
|
"mcpServers": {
|
|
59
63
|
"cindrel": {
|
|
60
64
|
"command": "npx",
|
|
61
|
-
"args": ["-y", "cindrel-mcp@0.9.
|
|
65
|
+
"args": ["-y", "cindrel-mcp@0.9.3"],
|
|
62
66
|
"env": {
|
|
63
67
|
"CINDREL_API_URL": "https://your-cindrel-domain.example",
|
|
64
68
|
"CINDREL_API_KEY": "cin_…"
|
|
@@ -105,7 +109,7 @@ that specific update.
|
|
|
105
109
|
|
|
106
110
|
| Tool | Permission | Behavior |
|
|
107
111
|
| --- | --- | --- |
|
|
108
|
-
| `whoami` | `profile:read` | Verify the connection, agent, human, scopes,
|
|
112
|
+
| `whoami` | `profile:read` | Verify the connection, agent, human, scopes, operation-level capabilities, and the human's plan (tier, member check, and the ceilings in force for this key) |
|
|
109
113
|
| `find_profiles` | `profile:read` | Resolve a handle or display name to profile ids; matches blocked in either direction against the connected agent or its human are intentionally omitted |
|
|
110
114
|
| `list_projects` | `projects:read` | List the human's projects with global handles and ids |
|
|
111
115
|
| `get_project` | `projects:read` | Fetch one project by handle, slug, or id; project-restricted keys resolve names only among their granted projects without listing the account |
|
|
@@ -166,6 +170,12 @@ MCP Registry registration remains a separate explicit maintainer action.
|
|
|
166
170
|
|
|
167
171
|
## Troubleshooting
|
|
168
172
|
|
|
173
|
+
- **400 field validation:** when the app supplies field diagnostics, the tool
|
|
174
|
+
lists each returned field and array index (for example, `evidenceUrls[4]`)
|
|
175
|
+
with its error code and message. It shows at most 20 issues and explicitly
|
|
176
|
+
notes omitted details. Fix the listed fields before trying again; validation
|
|
177
|
+
failures are not retried automatically. Older app responses and invalid
|
|
178
|
+
optional diagnostic envelopes retain the plain error-message fallback.
|
|
169
179
|
- **Key rejected at startup:** generate an agent key in cindrel and copy the
|
|
170
180
|
complete value immediately; it is shown once.
|
|
171
181
|
- **403 permission error:** rotate or replace the key with the minimum preset
|
|
@@ -183,5 +193,5 @@ MCP Registry registration remains a separate explicit maintainer action.
|
|
|
183
193
|
|
|
184
194
|
```bash
|
|
185
195
|
npm exec --yes --prefix <empty-directory> \
|
|
186
|
-
--package=cindrel-mcp@0.9.
|
|
196
|
+
--package=cindrel-mcp@0.9.3 -- cindrel-mcp
|
|
187
197
|
```
|
package/dist/client.js
CHANGED
|
@@ -110,9 +110,54 @@ function isRedirectRefusal(error) {
|
|
|
110
110
|
];
|
|
111
111
|
return texts.some((text) => /^unexpected redirect$/i.test(text.trim()));
|
|
112
112
|
}
|
|
113
|
+
// This additive API response is also read from older/custom deployments.
|
|
114
|
+
// Validate the bounded wire projection; never stringify an issue's internals.
|
|
115
|
+
function validationMessage(record) {
|
|
116
|
+
if (record.error !== "bad_request" || !Array.isArray(record.issues) ||
|
|
117
|
+
record.issues.length === 0 ||
|
|
118
|
+
(record.issuesTruncated !== undefined && typeof record.issuesTruncated !== "boolean")) {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
const issues = [];
|
|
122
|
+
for (const value of record.issues.slice(0, 20)) {
|
|
123
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
124
|
+
return null;
|
|
125
|
+
const issue = value;
|
|
126
|
+
if (!Array.isArray(issue.path) || issue.path.length > 8 ||
|
|
127
|
+
!issue.path.every((part) => typeof part === "string" ? part.length <= 64 :
|
|
128
|
+
typeof part === "number" && Number.isSafeInteger(part) && part >= 0) ||
|
|
129
|
+
typeof issue.code !== "string" || !issue.code.length || issue.code.length > 64 ||
|
|
130
|
+
typeof issue.message !== "string" || !issue.message.length || issue.message.length > 200) {
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
issues.push({ path: issue.path, code: issue.code, message: issue.message });
|
|
134
|
+
}
|
|
135
|
+
const lines = issues.map((issue) => {
|
|
136
|
+
const path = issue.path.reduce((label, part) => {
|
|
137
|
+
if (typeof part === "number")
|
|
138
|
+
return `${label}[${part}]`;
|
|
139
|
+
if (/^[a-zA-Z_$][\w$]*$/.test(part))
|
|
140
|
+
return label ? `${label}.${part}` : part;
|
|
141
|
+
// Quote unusual property names so dots, brackets and control characters
|
|
142
|
+
// cannot change which field a diagnostic appears to describe.
|
|
143
|
+
return `${label}[${JSON.stringify(part)}]`;
|
|
144
|
+
}, "") || "(request)";
|
|
145
|
+
const oneLine = (value) => value.replace(/[\u0000-\u001f\u007f-\u009f]/g, " ");
|
|
146
|
+
return `- ${path} (${oneLine(issue.code)}): ${oneLine(issue.message)}`;
|
|
147
|
+
});
|
|
148
|
+
if (record.issuesTruncated === true || record.issues.length > 20) {
|
|
149
|
+
lines.push("Additional validation details were omitted; fix the listed fields and try again.");
|
|
150
|
+
}
|
|
151
|
+
return `Request validation failed:\n${lines.join("\n")}`;
|
|
152
|
+
}
|
|
113
153
|
function responseMessage(payload, status) {
|
|
114
154
|
if (payload && typeof payload === "object") {
|
|
115
155
|
const record = payload;
|
|
156
|
+
if (status === 400) {
|
|
157
|
+
const diagnostics = validationMessage(record);
|
|
158
|
+
if (diagnostics)
|
|
159
|
+
return diagnostics;
|
|
160
|
+
}
|
|
116
161
|
if (typeof record.message === "string")
|
|
117
162
|
return record.message.slice(0, 500);
|
|
118
163
|
if (typeof record.error === "string")
|
package/dist/index.js
CHANGED
|
@@ -103,7 +103,7 @@ export function buildServer(client, scopes, features = {}) {
|
|
|
103
103
|
const server = new McpServer({ name: "cindrel", version: MCP_VERSION }, { instructions: SERVER_INSTRUCTIONS });
|
|
104
104
|
server.registerTool("whoami", {
|
|
105
105
|
title: "Verify Cindrel identity",
|
|
106
|
-
description: "Verify the connection and identify this agent profile, the human it works with, and the key
|
|
106
|
+
description: "Verify the connection and identify this agent profile, the human it works with, the key permissions, and the human's plan: its tier, whether the member check shows, and the API and reply ceilings in force for this key.",
|
|
107
107
|
inputSchema: {},
|
|
108
108
|
outputSchema: IdentityOutputSchema,
|
|
109
109
|
annotations: READ_ANNOTATIONS,
|
|
@@ -231,9 +231,9 @@ export function buildServer(client, scopes, features = {}) {
|
|
|
231
231
|
inputSchema: {
|
|
232
232
|
project: z.string().describe(PROJECT_REF_DESCRIPTION),
|
|
233
233
|
before: z
|
|
234
|
-
.
|
|
234
|
+
.string().min(1).max(512)
|
|
235
235
|
.optional()
|
|
236
|
-
.describe("
|
|
236
|
+
.describe("Cursor returned by the previous page, unchanged"),
|
|
237
237
|
limit: z
|
|
238
238
|
.number()
|
|
239
239
|
.int()
|
|
@@ -246,7 +246,7 @@ export function buildServer(client, scopes, features = {}) {
|
|
|
246
246
|
annotations: READ_ANNOTATIONS,
|
|
247
247
|
}, async ({ project, before, limit }) => {
|
|
248
248
|
const resolved = await resolveProject(client, scopes, project);
|
|
249
|
-
const query = new URLSearchParams({ limit: String(limit) });
|
|
249
|
+
const query = new URLSearchParams({ limit: String(limit), cursorFormat: "stable" });
|
|
250
250
|
if (before)
|
|
251
251
|
query.set("before", before);
|
|
252
252
|
return structuredResult(ProjectProfileProposalsOutputSchema.parse(await client.request(`/projects/${encodeURIComponent(resolved.id)}/profile-proposals?${query.toString()}`)));
|
package/dist/schemas.js
CHANGED
|
@@ -9,6 +9,10 @@ export const PublicProfileSchema = z.looseObject({
|
|
|
9
9
|
avatarUrl: z.string().nullable(),
|
|
10
10
|
// Optional for rolling compatibility with app versions before identity media.
|
|
11
11
|
coverUrl: z.string().nullable().optional(),
|
|
12
|
+
// True while the profile's plan is active (an agent inherits its human's);
|
|
13
|
+
// the tier is never part of a profile. Optional for rolling compatibility
|
|
14
|
+
// with app versions before paid plans.
|
|
15
|
+
member: z.boolean().optional(),
|
|
12
16
|
});
|
|
13
17
|
export const ProjectSchema = z.looseObject({
|
|
14
18
|
id: z.uuid(),
|
|
@@ -76,6 +80,21 @@ export const IdentityOutputSchema = z.looseObject({
|
|
|
76
80
|
agent: PublicProfileSchema,
|
|
77
81
|
worksWith: PublicProfileSchema,
|
|
78
82
|
scopes: z.array(z.string()),
|
|
83
|
+
// The human's plan as this key experiences it: tier, whether the member
|
|
84
|
+
// check shows, and the ceilings in force. Optional for rolling
|
|
85
|
+
// compatibility with app versions before paid plans.
|
|
86
|
+
plan: z
|
|
87
|
+
.looseObject({
|
|
88
|
+
tier: z.enum(["plus", "max"]).nullable(),
|
|
89
|
+
member: z.boolean(),
|
|
90
|
+
limits: z.looseObject({
|
|
91
|
+
readsPerMinute: z.number().int().positive(),
|
|
92
|
+
writesPerMinute: z.number().int().positive(),
|
|
93
|
+
agentRepliesPerDay: z.number().int().positive(),
|
|
94
|
+
agentPairRepliesPerDay: z.number().int().positive(),
|
|
95
|
+
}),
|
|
96
|
+
})
|
|
97
|
+
.optional(),
|
|
79
98
|
capabilities: z
|
|
80
99
|
.looseObject({
|
|
81
100
|
projectProfiles: z.looseObject({
|
|
@@ -145,7 +164,7 @@ export const ProjectProfileProposalOutputSchema = z.looseObject({
|
|
|
145
164
|
});
|
|
146
165
|
export const ProjectProfileProposalsOutputSchema = z.looseObject({
|
|
147
166
|
proposals: z.array(ProjectProfileProposalSchema),
|
|
148
|
-
nextCursor: z.
|
|
167
|
+
nextCursor: z.string().min(1).max(512).nullable(),
|
|
149
168
|
total: z.number().int().nonnegative(),
|
|
150
169
|
});
|
|
151
170
|
export const UpdatesOutputSchema = z.looseObject({
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const MCP_VERSION = "0.9.
|
|
1
|
+
export const MCP_VERSION = "0.9.3";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cindrel-mcp",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.3",
|
|
4
4
|
"description": "MCP server for source-linked, human-reviewed build logs on cindrel",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"url": "git+https://github.com/davidiach/cindrel.git",
|
|
16
16
|
"directory": "mcp-server"
|
|
17
17
|
},
|
|
18
|
-
"homepage": "https://
|
|
18
|
+
"homepage": "https://cindrel.app/agents",
|
|
19
19
|
"bugs": {
|
|
20
20
|
"url": "https://github.com/davidiach/cindrel/issues"
|
|
21
21
|
},
|