@empyria/mcp 0.1.3 → 0.1.5
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/.github/dependabot.yml +11 -0
- package/AGENTS.md +17 -0
- package/lib/Server.js +7 -2
- package/package.json +2 -2
- package/test/Server.test.js +77 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# To get started with Dependabot version updates, you'll need to specify which
|
|
2
|
+
# package ecosystems to update and where the package manifests are located.
|
|
3
|
+
# Please see the documentation for all configuration options:
|
|
4
|
+
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
|
|
5
|
+
|
|
6
|
+
version: 2
|
|
7
|
+
updates:
|
|
8
|
+
- package-ecosystem: "" # See documentation for possible values
|
|
9
|
+
directory: "/" # Location of package manifests
|
|
10
|
+
schedule:
|
|
11
|
+
interval: "daily"
|
package/AGENTS.md
CHANGED
|
@@ -53,6 +53,23 @@ don't special-case the "no params" path back to an omitted `inputSchema`, it sil
|
|
|
53
53
|
of whether it takes params). See the regression test in `test/Server.test.js` ("still receives a
|
|
54
54
|
real extra context, not undefined").
|
|
55
55
|
|
|
56
|
+
## Optional params (`defineSchema(params, { optional: true })`)
|
|
57
|
+
|
|
58
|
+
`@empyria/common`'s `defineSchema` (>= 0.1.4) accepts an `{ optional: true }` option: a property
|
|
59
|
+
is excluded from JSON Schema `required` when its own schema carries `optional: true` (that
|
|
60
|
+
marker is stripped before the property is embedded — not a real JSON Schema keyword, never
|
|
61
|
+
leaked into the tool's advertised inputSchema). Without the option (the default, and every call
|
|
62
|
+
elsewhere in the Empyria ecosystem — restate, moleculer), behavior is unchanged from before this
|
|
63
|
+
existed: every declared property is required, even one with its own `default`.
|
|
64
|
+
`registerService` in `lib/Server.js` always passes `{ optional: true }`, so any action's `params`
|
|
65
|
+
here can mark a field optional this way. Added upstream (not as a local derivative in this repo —
|
|
66
|
+
see git history for that discarded first attempt and why) 2026-08-26 for a real downstream need:
|
|
67
|
+
a service wrapping an S3-shaped API had several genuinely optional/defaulted params
|
|
68
|
+
(`contentType`, `expiresInSeconds`, ...) and `registerService`'s previous unconditional
|
|
69
|
+
`defineSchema` call would have silently advertised all of them as required — a real API-contract
|
|
70
|
+
regression, not a style preference. Usage: `params: { limit: { ...number(10), optional: true } }`.
|
|
71
|
+
Requires `@empyria/common` >= 0.1.4 — this package's own `package.json` is pinned there or later.
|
|
72
|
+
|
|
56
73
|
## Auth (`lib/Guard.js`)
|
|
57
74
|
|
|
58
75
|
`extra.http.req` (the raw `Request`, with real header access via `.headers.get(name)`) is
|
package/lib/Server.js
CHANGED
|
@@ -10,7 +10,9 @@ import { withMetaGuard } from './Guard.js'
|
|
|
10
10
|
* @property {string} [description] - Shown to MCP clients as the tool's description.
|
|
11
11
|
* @property {Object<string, Object>} [params] - Map of param name to JSON schema, built with
|
|
12
12
|
* `@empyria/common`'s schema helpers (`string()`, `number()`, `bool()`, ...). Omit for a
|
|
13
|
-
* tool that takes no arguments.
|
|
13
|
+
* tool that takes no arguments. A property's own schema may additionally carry
|
|
14
|
+
* `optional: true` to exclude it from the tool's `required` list — `registerService` passes
|
|
15
|
+
* `defineSchema`'s own `{ optional: true }` option (`@empyria/common` >= 0.1.4) to honor it.
|
|
14
16
|
* @property {(params: Object, extra: Object) => *} handler - Receives the validated params
|
|
15
17
|
* (or `{}` if no `params` schema was declared) and the MCP request's `extra` context.
|
|
16
18
|
* May return anything JSON-serializable, a string, or an already MCP-shaped
|
|
@@ -64,7 +66,10 @@ function registerService(server, service) {
|
|
|
64
66
|
// no-params action drops to a single-argument (extra) call instead — silently
|
|
65
67
|
// handing the caller's own context object to `args` and leaving `extra` (and
|
|
66
68
|
// therefore any auth context) undefined.
|
|
67
|
-
config.inputSchema = fromJsonSchema(
|
|
69
|
+
config.inputSchema = fromJsonSchema(
|
|
70
|
+
defineSchema(params ?? {}, { optional: true }),
|
|
71
|
+
empyriaJsonSchemaValidator,
|
|
72
|
+
)
|
|
68
73
|
|
|
69
74
|
server.registerTool(`${service.name}.${actionName}`, config, async (args, extra) => {
|
|
70
75
|
const result = await handler(args ?? {}, extra)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@empyria/mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "MCP (Model Context Protocol) helpers for the Empyria nanoservice framework",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Imre Fazekas <imre.fazekas@icloud.com>",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"@empyria/classification": "0.1.3",
|
|
28
|
-
"@empyria/common": "0.1.
|
|
28
|
+
"@empyria/common": "0.1.5",
|
|
29
29
|
"@modelcontextprotocol/client": "2.0.0",
|
|
30
30
|
"@modelcontextprotocol/server": "2.0.0"
|
|
31
31
|
},
|
package/test/Server.test.js
CHANGED
|
@@ -169,4 +169,81 @@ describe('createMcpServer', () => {
|
|
|
169
169
|
}),
|
|
170
170
|
).toThrow(/handler/)
|
|
171
171
|
})
|
|
172
|
+
|
|
173
|
+
test('a param marked optional: true is excluded from required', async () => {
|
|
174
|
+
const Service = {
|
|
175
|
+
name: 'Svc',
|
|
176
|
+
actions: {
|
|
177
|
+
search: {
|
|
178
|
+
params: { query: string(), limit: { ...number(10), optional: true } },
|
|
179
|
+
handler: async (params) => params,
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
}
|
|
183
|
+
const { client } = await connected([Service])
|
|
184
|
+
const { tools } = await client.listTools()
|
|
185
|
+
expect(tools[0].inputSchema.required).toEqual(['query'])
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
test('the optional marker itself is stripped, never leaked into the advertised schema', async () => {
|
|
189
|
+
const Service = {
|
|
190
|
+
name: 'Svc',
|
|
191
|
+
actions: {
|
|
192
|
+
search: {
|
|
193
|
+
params: { limit: { ...number(10), optional: true } },
|
|
194
|
+
handler: async (params) => params,
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
}
|
|
198
|
+
const { client } = await connected([Service])
|
|
199
|
+
const { tools } = await client.listTools()
|
|
200
|
+
expect(tools[0].inputSchema.properties.limit).toEqual({ type: 'number', default: 10 })
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
test('a call omitting an optional param still succeeds, and its default is applied by the validator', async () => {
|
|
204
|
+
const Service = {
|
|
205
|
+
name: 'Svc',
|
|
206
|
+
actions: {
|
|
207
|
+
search: {
|
|
208
|
+
params: { query: string(), limit: { ...number(10), optional: true } },
|
|
209
|
+
handler: async (params) => params,
|
|
210
|
+
},
|
|
211
|
+
},
|
|
212
|
+
}
|
|
213
|
+
const { client } = await connected([Service])
|
|
214
|
+
const result = await client.callTool({ name: 'Svc.search', arguments: { query: 'x' } })
|
|
215
|
+
expect(result.isError).toBeFalsy()
|
|
216
|
+
expect(JSON.parse(result.content[0].text)).toEqual({ query: 'x', limit: 10 })
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
test('a call omitting an optional param with no default leaves it absent', async () => {
|
|
220
|
+
const Service = {
|
|
221
|
+
name: 'Svc',
|
|
222
|
+
actions: {
|
|
223
|
+
search: {
|
|
224
|
+
params: { query: string(), tag: { ...string(), optional: true } },
|
|
225
|
+
handler: async (params) => params,
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
}
|
|
229
|
+
const { client } = await connected([Service])
|
|
230
|
+
const result = await client.callTool({ name: 'Svc.search', arguments: { query: 'x' } })
|
|
231
|
+
expect(result.isError).toBeFalsy()
|
|
232
|
+
expect(JSON.parse(result.content[0].text)).toEqual({ query: 'x' })
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
test("a param with only a default (no explicit optional) is still required — unchanged from @empyria/common's own defineSchema", async () => {
|
|
236
|
+
const Service = {
|
|
237
|
+
name: 'Svc',
|
|
238
|
+
actions: {
|
|
239
|
+
search: {
|
|
240
|
+
params: { limit: number(10) },
|
|
241
|
+
handler: async (params) => params,
|
|
242
|
+
},
|
|
243
|
+
},
|
|
244
|
+
}
|
|
245
|
+
const { client } = await connected([Service])
|
|
246
|
+
const { tools } = await client.listTools()
|
|
247
|
+
expect(tools[0].inputSchema.required).toEqual(['limit'])
|
|
248
|
+
})
|
|
172
249
|
})
|