@dokebi/mcp 0.1.1 → 0.1.2
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/AI.md +0 -4
- package/dist/plugin.js +1 -1
- package/package.json +3 -11
- package/dist/actions.d.ts +0 -19
- package/dist/actions.js +0 -88
package/AI.md
CHANGED
|
@@ -163,10 +163,6 @@ The optional members are read at start, so a subclass defines them without `over
|
|
|
163
163
|
|
|
164
164
|
Prompts and resources run with the same `checkGrant`, time budget and size limit (the JSON of the prompt or resource answer), and take tokens of the **`read` bucket** (with `limits` set, a `read` policy is required when prompts or resources exist; refused: `-32602` with `data: { code: LIMIT_EXCEEDED, retry_after }`); their failures are JSON-RPC errors: a DokebiError is `-32602` with `data: { code, message, hint?, next?, data? }` — one with a status of 500 or more (the server's state: a storage that is stopping answers `NOT_READY` 503, a limiter whose Redis is down `UNAVAILABLE`) is `-32603` with the same `data`, its `retryAfterMs` included —, a refused grant `-32602` `GRANT_REFUSED`, a timeout `-32603` `TOOL_TIMEOUT`, an unexpected error `-32603` `INTERNAL`.
|
|
165
165
|
|
|
166
|
-
### Module actions as tools (`@dokebi/mcp/actions`, D-0202)
|
|
167
|
-
|
|
168
|
-
`actionTools(module, { scope?, only? })` (module 0.4.2) makes one `McpTool` per action of a feature module's contract (`defineModule({ actions })`, @dokebi/module AI.md §2c): name `<module>.<action>`, `title` and `description` from the action (its permission, the version rule, the two steps of a dangerous one), `inputSchema` the action's fields (a dangerous one adds `confirm`), `annotations.destructiveHint` for a dangerous one. `handle` runs the module's general write with `context.request`: the same permission (`request.auth.can`), scope, input check, preview and confirm as `POST /actions/<module>/<action>`. A refusal is a tool error with its code (`VALIDATION_FAILED` with `data.rules`, `ACTION_CONFLICT`, `CONFIRM_REQUIRED` with `data.preview` and `data.confirm`); a result is `envelope({ fields: { action, result } })`. The run's `actor` is `{ type: 'ai', id: <the grant's user>, client: <its client name> }` for the audit row. Contribute them from the module's setup: `for (const tool of actionTools(module)) module.contribute(mcpTools, tool)` with `contributes: [mcpTools]`. **`scope`** defaults to the action's permission key: a token carries its scopes as abilities and `can(permission)` needs the permission among them, so list the permission keys (or `<module>.*`) in the resource's `scopes`; another scope refuses every call with `FORBIDDEN`. `only` picks actions by name; a name the module does not have throws `INVALID_ARGUMENT` (a typo never leaves a tool out quietly). The entry imports @dokebi/module (an optional peer).
|
|
169
|
-
|
|
170
166
|
## 6. One tool call
|
|
171
167
|
|
|
172
168
|
After the scope check of §3: **limit** (`limits.<bucket>`, key `<bucket>:<grant id>`) → **checkGrant** → **input** → `handle` within `toolTimeoutMs` → **the envelope** → **transformResult** → **the picture limits** → **size** → the result. Every failure is a tool result with `isError: true`, never an HTTP error and never a 5xx.
|
package/dist/plugin.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dokebi/mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "MCP server for @dokebi/web on @dokebi/oauth: stateless Streamable HTTP, tool classes with checked schemas, a forced result envelope, per-grant limits and coded tool errors.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dokebi",
|
|
@@ -28,10 +28,6 @@
|
|
|
28
28
|
"./testing": {
|
|
29
29
|
"types": "./dist/testing.d.ts",
|
|
30
30
|
"import": "./dist/testing.js"
|
|
31
|
-
},
|
|
32
|
-
"./actions": {
|
|
33
|
-
"types": "./dist/actions.d.ts",
|
|
34
|
-
"import": "./dist/actions.js"
|
|
35
31
|
}
|
|
36
32
|
},
|
|
37
33
|
"files": [
|
|
@@ -55,22 +51,18 @@
|
|
|
55
51
|
"@dokebi/web": "^0.4.3",
|
|
56
52
|
"@dokebi/oauth": "^0.1.0",
|
|
57
53
|
"@dokebi/rate-limit": "^0.1.1",
|
|
58
|
-
"fastify": "^5.12.7"
|
|
59
|
-
"@dokebi/module": "^0.4.2"
|
|
54
|
+
"fastify": "^5.12.7"
|
|
60
55
|
},
|
|
61
56
|
"peerDependenciesMeta": {
|
|
62
57
|
"@dokebi/rate-limit": {
|
|
63
58
|
"optional": true
|
|
64
|
-
},
|
|
65
|
-
"@dokebi/module": {
|
|
66
|
-
"optional": true
|
|
67
59
|
}
|
|
68
60
|
},
|
|
69
61
|
"devDependencies": {
|
|
70
62
|
"@dokebi/app": "0.4.6",
|
|
71
63
|
"@dokebi/auth": "0.4.2",
|
|
72
64
|
"@dokebi/cluster": "0.4.2",
|
|
73
|
-
"@dokebi/module": "0.4.
|
|
65
|
+
"@dokebi/module": "0.4.3",
|
|
74
66
|
"@dokebi/oauth": "0.1.1",
|
|
75
67
|
"@dokebi/rate-limit": "0.1.1",
|
|
76
68
|
"@dokebi/redis": "0.4.1",
|
package/dist/actions.d.ts
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { type ModuleAction, type ModuleContext } from '@dokebi/module';
|
|
2
|
-
import { McpTool } from './tool.js';
|
|
3
|
-
/** Options of `actionTools`. */
|
|
4
|
-
export interface ActionToolOptions {
|
|
5
|
-
/**
|
|
6
|
-
* The OAuth scope a grant needs for a tool, or a list of which any one is enough; a function
|
|
7
|
-
* picks it per action. Default: the action's permission key (`members.update`). An OAuth token
|
|
8
|
-
* carries its scopes as abilities and `request.auth.can(permission)` needs the permission among
|
|
9
|
-
* them, so a scope other than the permission (or `<module>.*`) refuses every call with FORBIDDEN.
|
|
10
|
-
*/
|
|
11
|
-
readonly scope?: string | readonly string[] | ((action: {
|
|
12
|
-
readonly id: string;
|
|
13
|
-
readonly declaration: ModuleAction;
|
|
14
|
-
}) => string | readonly string[]);
|
|
15
|
-
/** The actions to offer, by name (default every action of the module); an unknown name throws. */
|
|
16
|
-
readonly only?: readonly string[];
|
|
17
|
-
}
|
|
18
|
-
/** One MCP tool per action of `module` (named `<module>.<action>`). */
|
|
19
|
-
export declare function actionTools(module: ModuleContext, options?: ActionToolOptions): McpTool[];
|
package/dist/actions.js
DELETED
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
// Module actions as MCP tools (D-0202, `@dokebi/mcp/actions`): the module that owns the actions
|
|
2
|
-
// contributes one tool per action to @dokebi/mcp (D-0130), and the tool runs the same runner as
|
|
3
|
-
// the general write — the same permission, scope, input check, preview and confirm, result and
|
|
4
|
-
// errors. The AI client's name reaches the run as `actor` (`{ type: 'ai', client }`) for the audit.
|
|
5
|
-
//
|
|
6
|
-
// import { actionTools } from '@dokebi/mcp/actions';
|
|
7
|
-
// defineModule({ contributes: [mcpTools], actions, setup(module) {
|
|
8
|
-
// for (const tool of actionTools(module)) module.contribute(mcpTools, tool);
|
|
9
|
-
// } })
|
|
10
|
-
import { inputSchema, runAction } from '@dokebi/module';
|
|
11
|
-
import { envelope } from './envelope.js';
|
|
12
|
-
import { McpError } from './errors.js';
|
|
13
|
-
import { McpTool } from './tool.js';
|
|
14
|
-
const text = (value) => value === undefined
|
|
15
|
-
? ''
|
|
16
|
-
: typeof value === 'string'
|
|
17
|
-
? value
|
|
18
|
-
: (value.en ?? Object.values(value)[0] ?? '');
|
|
19
|
-
class ActionTool extends McpTool {
|
|
20
|
-
name;
|
|
21
|
-
title;
|
|
22
|
-
description;
|
|
23
|
-
inputSchema;
|
|
24
|
-
scope;
|
|
25
|
-
annotations;
|
|
26
|
-
#module;
|
|
27
|
-
#declaration;
|
|
28
|
-
constructor(module, name, declaration, scope) {
|
|
29
|
-
super();
|
|
30
|
-
this.#module = module;
|
|
31
|
-
this.#declaration = declaration;
|
|
32
|
-
this.name = `${module.module}.${name}`;
|
|
33
|
-
this.title = text(declaration.title);
|
|
34
|
-
const about = text(declaration.description);
|
|
35
|
-
this.description = [
|
|
36
|
-
`${this.title}${about ? `: ${about}` : ''}.`,
|
|
37
|
-
`Needs the permission ${declaration.permission}.`,
|
|
38
|
-
declaration.version
|
|
39
|
-
? `Send ${declaration.version} as read with the row: a newer row answers ACTION_CONFLICT (read it again).`
|
|
40
|
-
: '',
|
|
41
|
-
declaration.danger
|
|
42
|
-
? 'Dangerous: the first call answers CONFIRM_REQUIRED with data.preview and data.confirm. Show the preview to the user and call again with the same input and confirm only when they agree.'
|
|
43
|
-
: '',
|
|
44
|
-
'Fields left out are not changed; null clears a field that is not required.',
|
|
45
|
-
]
|
|
46
|
-
.filter(Boolean)
|
|
47
|
-
.join(' ');
|
|
48
|
-
this.inputSchema = inputSchema(declaration);
|
|
49
|
-
this.scope = scope;
|
|
50
|
-
this.annotations = { destructiveHint: declaration.danger === true, readOnlyHint: false };
|
|
51
|
-
}
|
|
52
|
-
async handle(input, context) {
|
|
53
|
-
const { confirm, ...values } = input;
|
|
54
|
-
const answer = await runAction({
|
|
55
|
-
id: this.name,
|
|
56
|
-
module: this.#module.module,
|
|
57
|
-
declaration: this.#declaration,
|
|
58
|
-
context: () => this.#module,
|
|
59
|
-
}, context.request, confirm === undefined ? { input: values } : { input: values, confirm }, this.#module.logger);
|
|
60
|
-
return envelope({
|
|
61
|
-
fields: { action: answer.action, result: answer.result },
|
|
62
|
-
note: 'The action ran. result holds what the server returned (ids, counts).',
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
/** One MCP tool per action of `module` (named `<module>.<action>`). */
|
|
67
|
-
export function actionTools(module, options = {}) {
|
|
68
|
-
// A name the module does not have is a typo, never a tool left out quietly (D-0202 review).
|
|
69
|
-
const missing = options.only?.find((name) => !Object.hasOwn(module.contract.actions, name));
|
|
70
|
-
if (missing !== undefined)
|
|
71
|
-
throw new McpError('INVALID_ARGUMENT', `Module ${module.module} has no action ${missing}`, {
|
|
72
|
-
fix: `Name only actions of module ${module.module} in only: ${Object.keys(module.contract.actions).join(', ')}.`,
|
|
73
|
-
});
|
|
74
|
-
const tools = [];
|
|
75
|
-
for (const [name, declaration] of Object.entries(module.contract.actions)) {
|
|
76
|
-
if (options.only && !options.only.includes(name))
|
|
77
|
-
continue;
|
|
78
|
-
const scope = typeof options.scope === 'function'
|
|
79
|
-
? options.scope({ id: `${module.module}.${name}`, declaration })
|
|
80
|
-
: (options.scope ?? declaration.permission);
|
|
81
|
-
tools.push(new ActionTool(module, name, declaration, scope));
|
|
82
|
-
}
|
|
83
|
-
module.logger.log('debug', 'Action tools made', {
|
|
84
|
-
module: module.module,
|
|
85
|
-
tools: tools.map((tool) => tool.name),
|
|
86
|
-
});
|
|
87
|
-
return tools;
|
|
88
|
-
}
|