@agdf/mcp-server 0.20.1 → 0.20.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/README.md +5 -5
- package/package.json +2 -2
- package/src/server.js +21 -8
- package/src/ui-capability.js +49 -1
package/README.md
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
# @agdf/mcp-server
|
|
2
2
|
|
|
3
|
-
Local STDIO MCP adapter
|
|
3
|
+
Local STDIO MCP adapter.
|
|
4
4
|
|
|
5
5
|
MCP tools: `agdf_dispatch`, `agdf_inspect`, `agdf_prepare`, `agdf_record`, `agdf_approve`, `agdf_revise`, `agdf_closeout`.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
[Integration guide](../../docs/architecture/09-mcp-integrationsrichtlinien.md).
|
|
8
|
+
[Command flow](../../docs/architecture/core-mcp-command-flow.md): bounded writes,
|
|
9
|
+
contract/status reads, original-operation recovery and the app-only decision facade.
|
|
9
10
|
Host tool permission is separate from a deliberate gate decision. The source candidate's
|
|
10
11
|
click actions remain disabled while native app-only mediation is unqualified.
|
|
11
12
|
|
|
@@ -53,8 +54,7 @@ service itself remains read-only. Accordingly, `agdf_dispatch` declares `readOnl
|
|
|
53
54
|
|
|
54
55
|
> [!IMPORTANT]
|
|
55
56
|
> This package documents the repository development build. The previously published AGDF 0.14.5 release
|
|
56
|
-
> did not include the MCP support described here.
|
|
57
|
-
> carry the canonical version `0.14.5`, but can contain these newer MCP features. The version alone
|
|
57
|
+
> did not include the MCP support described here. Current assemblies and local plugin builds take their version from the canonical package manifests. The version alone
|
|
58
58
|
> does not identify the payload: verify the assembly or installed runtime digests and the loaded session.
|
|
59
59
|
> These development instructions do not establish what a current npm `@latest` resolution contains.
|
|
60
60
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agdf/mcp-server",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.2",
|
|
4
4
|
"description": "Local MCP adapter for version-matched AGDF dispatch and control inspection.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"scripts": {},
|
|
20
20
|
"dependencies": {
|
|
21
21
|
"@modelcontextprotocol/server": "2.0.0",
|
|
22
|
-
"create-agdf": "0.20.
|
|
22
|
+
"create-agdf": "0.20.2"
|
|
23
23
|
},
|
|
24
24
|
"engines": {
|
|
25
25
|
"node": ">=22"
|
package/src/server.js
CHANGED
|
@@ -5,7 +5,8 @@ import { randomUUID } from 'node:crypto';
|
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
6
|
import { DispatchExecutionError, createWorkerDispatchExecutor } from "./worker.js";
|
|
7
7
|
import { loadCockpitResource } from './cockpit-resource.js';
|
|
8
|
-
import { requestDeclaresMcpAppUi, requestMcpClientInfo, requestMcpClientCapabilities
|
|
8
|
+
import { requestDeclaresMcpAppUi, requestMcpClientInfo, requestMcpClientCapabilities, requiresMcpAppUi,
|
|
9
|
+
missingMcpAppUi, capabilityTransport, modernToolUiDenial } from "./ui-capability.js";
|
|
9
10
|
|
|
10
11
|
export const SERVER_NAME = "agdf-mcp";
|
|
11
12
|
|
|
@@ -38,7 +39,7 @@ function toolResult(runtime, name, input, result, serialize = runtime.serialize,
|
|
|
38
39
|
return response;
|
|
39
40
|
}
|
|
40
41
|
|
|
41
|
-
export function buildAgdfServer({ runtime, executor } = {}) {
|
|
42
|
+
export function buildAgdfServer({ runtime, executor, era } = {}) {
|
|
42
43
|
if (!runtime?.definition || typeof runtime.serialize !== "function") {
|
|
43
44
|
throw new TypeError("AGDF MCP runtime is required");
|
|
44
45
|
}
|
|
@@ -57,7 +58,14 @@ export function buildAgdfServer({ runtime, executor } = {}) {
|
|
|
57
58
|
},
|
|
58
59
|
close: async () => runtime.close?.(),
|
|
59
60
|
};
|
|
60
|
-
const
|
|
61
|
+
const registrations = new Map();
|
|
62
|
+
class AgdfServer extends McpServer {
|
|
63
|
+
async connect(transport) {
|
|
64
|
+
return super.connect(capabilityTransport(transport, message => modernToolUiDenial(message,
|
|
65
|
+
{ era: era ?? (this.server.getNegotiatedProtocolVersion() === '2026-07-28' ? 'modern' : 'legacy'), runtime, registrations })));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const server = new AgdfServer(
|
|
61
69
|
{ name: SERVER_NAME, version: runtime.trustedContext.expectedVersion },
|
|
62
70
|
{ capabilities: { tools: {}, ...(runtime.ui ? { resources: {} } : {}) } },
|
|
63
71
|
);
|
|
@@ -87,12 +95,13 @@ export function buildAgdfServer({ runtime, executor } = {}) {
|
|
|
87
95
|
} };
|
|
88
96
|
for (const tool of tools) {
|
|
89
97
|
const { name, definition, serialize } = tool;
|
|
90
|
-
|
|
98
|
+
const schema = fromJsonSchema(definition.inputSchema, deferredSchemas);
|
|
99
|
+
const registered = server.registerTool(
|
|
91
100
|
name,
|
|
92
101
|
{
|
|
93
102
|
description: definition.description,
|
|
94
103
|
annotations: definition.annotations,
|
|
95
|
-
inputSchema:
|
|
104
|
+
inputSchema: schema,
|
|
96
105
|
...(definition.outputSchema ? { outputSchema: fromJsonSchema(definition.outputSchema, deferredSchemas) } : {}),
|
|
97
106
|
...(definition._meta ? { _meta: definition._meta } : {}),
|
|
98
107
|
},
|
|
@@ -125,12 +134,16 @@ export function buildAgdfServer({ runtime, executor } = {}) {
|
|
|
125
134
|
}
|
|
126
135
|
},
|
|
127
136
|
);
|
|
137
|
+
registrations.set(name, { tool, schema, registered });
|
|
128
138
|
}
|
|
129
139
|
if (runtime.ui) {
|
|
130
140
|
const resource = loadCockpitResource(runtime.ui);
|
|
131
141
|
server.registerResource(runtime.mode === 'decision' ? 'AGDF Gate Decision' : runtime.ui.label ?? 'AGDF Cockpit', resource.uri, { mimeType: resource.mimeType, _meta: resource._meta },
|
|
132
142
|
async (_uri, context) => {
|
|
133
|
-
if (
|
|
143
|
+
if (requiresMcpAppUi(runtime, { requiresUi: runtime.mode === 'decision' }) && !supportsUi(context)) {
|
|
144
|
+
if ((era ?? (server.server.getNegotiatedProtocolVersion() === '2026-07-28' ? 'modern' : 'legacy')) === 'modern') throw missingMcpAppUi(runtime.ui.mimeType);
|
|
145
|
+
throw new ProtocolError(ProtocolErrorCode.InvalidParams, "resource_denied");
|
|
146
|
+
}
|
|
134
147
|
return { contents: [resource] };
|
|
135
148
|
});
|
|
136
149
|
if (gated || runtime.mode === 'decision') {
|
|
@@ -155,8 +168,8 @@ export function buildAgdfServer({ runtime, executor } = {}) {
|
|
|
155
168
|
|
|
156
169
|
export function serveAgdfStdio({ runtime, executor, onerror } = {}) {
|
|
157
170
|
const instances = new Set();
|
|
158
|
-
const handle = serveStdio(() => {
|
|
159
|
-
const server = buildAgdfServer({ runtime, executor });
|
|
171
|
+
const handle = serveStdio(({ era }) => {
|
|
172
|
+
const server = buildAgdfServer({ runtime, executor, era });
|
|
160
173
|
instances.add(server);
|
|
161
174
|
return server;
|
|
162
175
|
}, {
|
package/src/ui-capability.js
CHANGED
|
@@ -1,7 +1,55 @@
|
|
|
1
|
-
import { CLIENT_CAPABILITIES_META_KEY, CLIENT_INFO_META_KEY } from "@modelcontextprotocol/server";
|
|
1
|
+
import { CLIENT_CAPABILITIES_META_KEY, CLIENT_INFO_META_KEY, MissingRequiredClientCapabilityError, classifyInboundRequest } from "@modelcontextprotocol/server";
|
|
2
2
|
|
|
3
3
|
// Extension id under which MCP Apps hosts declare UI support (@modelcontextprotocol/ext-apps EXTENSION_ID).
|
|
4
4
|
export const MCP_APP_UI_EXTENSION = "io.modelcontextprotocol/ui";
|
|
5
|
+
export const requiresMcpAppUi = (runtime, tool) => runtime.mode === 'cockpit' && runtime.uiCapabilityRequired === true || tool?.requiresUi === true;
|
|
6
|
+
export const missingMcpAppUi = mimeType => new MissingRequiredClientCapabilityError({
|
|
7
|
+
requiredCapabilities: { extensions: { [MCP_APP_UI_EXTENSION]: { mimeTypes: [mimeType] } } },
|
|
8
|
+
}, 'resource_denied');
|
|
9
|
+
|
|
10
|
+
// One narrow Transport decorator; framing, request dispatch, validation and
|
|
11
|
+
// pending-id/drain accounting remain with the original SDK connection.
|
|
12
|
+
export function capabilityTransport(transport, denial) {
|
|
13
|
+
const wrapped = {
|
|
14
|
+
onmessage: transport.onmessage,
|
|
15
|
+
onerror: transport.onerror,
|
|
16
|
+
onclose: transport.onclose,
|
|
17
|
+
start: () => transport.start(),
|
|
18
|
+
send: (message, options) => transport.send(message, options),
|
|
19
|
+
close: () => transport.close(),
|
|
20
|
+
get sessionId() { return transport.sessionId; },
|
|
21
|
+
set sessionId(value) { transport.sessionId = value; },
|
|
22
|
+
get hasPerRequestStream() { return transport.hasPerRequestStream; },
|
|
23
|
+
};
|
|
24
|
+
for (const method of ['setProtocolVersion', 'setSupportedProtocolVersions']) {
|
|
25
|
+
if (transport[method]) wrapped[method] = (...args) => transport[method](...args);
|
|
26
|
+
}
|
|
27
|
+
transport.onerror = error => wrapped.onerror?.(error);
|
|
28
|
+
transport.onclose = () => wrapped.onclose?.();
|
|
29
|
+
transport.onmessage = (message, extra) => {
|
|
30
|
+
const error = denial(message);
|
|
31
|
+
if (!error) { wrapped.onmessage?.(message, extra); return; }
|
|
32
|
+
// Sending through the original channel settles its pending request id.
|
|
33
|
+
Promise.resolve().then(() => transport.send({ jsonrpc: '2.0', id: message.id,
|
|
34
|
+
error: { code: error.code, message: error.message, data: error.data } }))
|
|
35
|
+
.catch(cause => wrapped.onerror?.(cause));
|
|
36
|
+
};
|
|
37
|
+
return wrapped;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function modernToolUiDenial(message, { era, runtime, registrations }) {
|
|
41
|
+
if (era !== 'modern' || message?.method !== 'tools/call') return null;
|
|
42
|
+
const classified = classifyInboundRequest({ httpMethod: 'POST', body: message });
|
|
43
|
+
if (classified.kind !== 'modern' || classified.messageKind !== 'request') return null;
|
|
44
|
+
const entry = registrations.get(message.params?.name);
|
|
45
|
+
if (!entry?.registered.enabled || !requiresMcpAppUi(runtime, entry.tool)
|
|
46
|
+
|| declaresMcpAppUi(message.params?._meta?.[CLIENT_CAPABILITIES_META_KEY], runtime.ui.mimeType)) return null;
|
|
47
|
+
const checked = entry.schema['~standard'].validate(message.params.arguments ?? {});
|
|
48
|
+
// The pinned fromJsonSchema provider is synchronous. Never treat an unknown
|
|
49
|
+
// asynchronous provider as proof that arguments passed validation.
|
|
50
|
+
if (checked?.then || checked.issues?.length) return null;
|
|
51
|
+
return missingMcpAppUi(runtime.ui.mimeType);
|
|
52
|
+
}
|
|
5
53
|
export const requestMcpClientInfo = (server, context) => context?.mcpReq?.envelope?.[CLIENT_INFO_META_KEY] ?? server.getClientVersion();
|
|
6
54
|
export const requestMcpClientCapabilities = (server, context) => context?.mcpReq?.envelope?.[CLIENT_CAPABILITIES_META_KEY] ?? server.getClientCapabilities();
|
|
7
55
|
|