@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 CHANGED
@@ -1,11 +1,12 @@
1
1
  # @agdf/mcp-server
2
2
 
3
- Local STDIO MCP adapter for AGDF dispatch and control inspection.
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
- The bounded write groups, selected contract/status reads, original-operation recovery and
8
- focused app-only decision facade are described in [Core and MCP command flow](../../docs/architecture/core-mcp-command-flow.md).
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. Repository assemblies and local plugin builds still
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.1",
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.1"
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 } from "./ui-capability.js";
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 server = new McpServer(
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
- server.registerTool(
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: fromJsonSchema(definition.inputSchema, deferredSchemas),
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 (!uiAllowed(context) || runtime.mode === 'decision' && !supportsUi(context)) throw new ProtocolError(ProtocolErrorCode.InvalidParams, "resource_denied");
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
  }, {
@@ -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