@keelcodes/mcp 0.1.0
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/LICENSE +201 -0
- package/README.md +104 -0
- package/dist/builtin.d.ts +32 -0
- package/dist/builtin.d.ts.map +1 -0
- package/dist/builtin.js +114 -0
- package/dist/builtin.js.map +1 -0
- package/dist/gate.d.ts +46 -0
- package/dist/gate.d.ts.map +1 -0
- package/dist/gate.js +38 -0
- package/dist/gate.js.map +1 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/pim.d.ts +16 -0
- package/dist/pim.d.ts.map +1 -0
- package/dist/pim.js +108 -0
- package/dist/pim.js.map +1 -0
- package/dist/protocol.d.ts +84 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +32 -0
- package/dist/protocol.js.map +1 -0
- package/dist/server.d.ts +23 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +94 -0
- package/dist/server.js.map +1 -0
- package/dist/stdio.d.ts +31 -0
- package/dist/stdio.d.ts.map +1 -0
- package/dist/stdio.js +67 -0
- package/dist/stdio.js.map +1 -0
- package/dist/tools.d.ts +43 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +51 -0
- package/dist/tools.js.map +1 -0
- package/package.json +53 -0
package/dist/pim.js
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { trustLevelOf, validatePim } from '@keelcodes/manifest';
|
|
2
|
+
import { jsonResult } from './tools.js';
|
|
3
|
+
/**
|
|
4
|
+
* ERC-8313 Protocol Interaction Manifest tools.
|
|
5
|
+
*
|
|
6
|
+
* A manifest is an *untrusted input*: it arrives as JSON from a dApp, a
|
|
7
|
+
* registry or a peer agent. These tools let a model validate and summarise one
|
|
8
|
+
* before it is ever acted on. Both are read-only and therefore ungated —
|
|
9
|
+
* refusing to validate a manifest would only push the model to guess at its
|
|
10
|
+
* shape, which is exactly the failure mode the PIM standard exists to remove.
|
|
11
|
+
*
|
|
12
|
+
* `pim` is passed inline as a parsed object (not a path): this package
|
|
13
|
+
* deliberately has no `@types/node` dependency, so it never touches the
|
|
14
|
+
* filesystem. A host that wants path input reads the file itself.
|
|
15
|
+
*/
|
|
16
|
+
export function createPimTools() {
|
|
17
|
+
const validate = {
|
|
18
|
+
name: 'pim_validate',
|
|
19
|
+
title: 'Validate an ERC-8313 manifest',
|
|
20
|
+
description: 'Validate a Protocol Interaction Manifest (ERC-8313) against the nine mandatory sections and their rules. Returns { valid, errors, warnings }. Read-only and never gated.',
|
|
21
|
+
inputSchema: PIM_SCHEMA,
|
|
22
|
+
run: (args) => {
|
|
23
|
+
const pim = requirePim(args);
|
|
24
|
+
return jsonResult(validatePim(pim, executionContext(args)));
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
const inspect = {
|
|
28
|
+
name: 'pim_inspect',
|
|
29
|
+
title: 'Inspect an ERC-8313 manifest',
|
|
30
|
+
description: 'Summarise a Protocol Interaction Manifest: protocol identity, chains, intents, contracts and the assigned trust level (ERC-8313 Levels 0-3). Read-only and never gated.',
|
|
31
|
+
inputSchema: PIM_SCHEMA,
|
|
32
|
+
run: async (args) => {
|
|
33
|
+
const pim = requirePim(args);
|
|
34
|
+
const result = validatePim(pim, executionContext(args));
|
|
35
|
+
const trust = await trustLevelOf(pim);
|
|
36
|
+
const metadata = pim.metadata;
|
|
37
|
+
return jsonResult({
|
|
38
|
+
valid: result.valid,
|
|
39
|
+
protocol: metadata.protocol,
|
|
40
|
+
category: metadata.category,
|
|
41
|
+
pimVersion: metadata.pimVersion,
|
|
42
|
+
chainId: metadata.chainId,
|
|
43
|
+
contracts: describeContracts(pim),
|
|
44
|
+
intents: describeIntents(pim),
|
|
45
|
+
trustLevel: {
|
|
46
|
+
level: trust.level,
|
|
47
|
+
name: trust.name,
|
|
48
|
+
verified: trust.verified,
|
|
49
|
+
reason: trust.reason,
|
|
50
|
+
...(trust.signer !== undefined ? { signer: trust.signer } : {}),
|
|
51
|
+
...(trust.signerLabel !== undefined ? { signerLabel: trust.signerLabel } : {}),
|
|
52
|
+
},
|
|
53
|
+
errors: result.errors,
|
|
54
|
+
warnings: result.warnings,
|
|
55
|
+
});
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
return [validate, inspect];
|
|
59
|
+
}
|
|
60
|
+
const PIM_SCHEMA = {
|
|
61
|
+
type: 'object',
|
|
62
|
+
properties: {
|
|
63
|
+
pim: { type: 'object', description: 'A parsed ERC-8313 Protocol Interaction Manifest.' },
|
|
64
|
+
chainId: {
|
|
65
|
+
type: 'number',
|
|
66
|
+
description: 'Optional network id; a manifest whose metadata.chainId excludes it fails validation.',
|
|
67
|
+
},
|
|
68
|
+
now: {
|
|
69
|
+
type: 'number',
|
|
70
|
+
description: 'Optional unix time in seconds; enables the validFrom/validUntil checks.',
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
required: ['pim'],
|
|
74
|
+
additionalProperties: false,
|
|
75
|
+
};
|
|
76
|
+
function describeContracts(pim) {
|
|
77
|
+
return Object.entries(pim.contracts ?? {}).map(([name, contract]) => ({
|
|
78
|
+
name,
|
|
79
|
+
role: contract.role,
|
|
80
|
+
...(contract.address !== undefined ? { address: contract.address } : {}),
|
|
81
|
+
...(contract.lookup !== undefined ? { lookup: contract.lookup } : {}),
|
|
82
|
+
}));
|
|
83
|
+
}
|
|
84
|
+
function describeIntents(pim) {
|
|
85
|
+
return Object.entries(pim.intents ?? {}).map(([name, intent]) => ({
|
|
86
|
+
name,
|
|
87
|
+
description: intent.description,
|
|
88
|
+
requiredInputs: intent.requiredInputs,
|
|
89
|
+
}));
|
|
90
|
+
}
|
|
91
|
+
function executionContext(args) {
|
|
92
|
+
const options = {};
|
|
93
|
+
if (typeof args.now === 'number')
|
|
94
|
+
options.now = args.now;
|
|
95
|
+
if (typeof args.chainId === 'number')
|
|
96
|
+
options.chainId = args.chainId;
|
|
97
|
+
return options;
|
|
98
|
+
}
|
|
99
|
+
function requirePim(args) {
|
|
100
|
+
const pim = args.pim;
|
|
101
|
+
if (!isObject(pim))
|
|
102
|
+
throw new Error('"pim" is required and must be a parsed PIM object');
|
|
103
|
+
return pim;
|
|
104
|
+
}
|
|
105
|
+
function isObject(value) {
|
|
106
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=pim.js.map
|
package/dist/pim.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pim.js","sourceRoot":"","sources":["../src/pim.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The MCP wire types this server speaks, plus the JSON-RPC 2.0 envelope they
|
|
3
|
+
* ride on.
|
|
4
|
+
*
|
|
5
|
+
* Modelled on the Model Context Protocol revision `2025-06-18`
|
|
6
|
+
* (https://modelcontextprotocol.io/specification/2025-06-18) — specifically the
|
|
7
|
+
* `initialize`, `tools/list` and `tools/call` messages. Only the server side is
|
|
8
|
+
* implemented, and only the parts a tool server needs.
|
|
9
|
+
*/
|
|
10
|
+
/** MCP revisions this server can speak, newest first. */
|
|
11
|
+
export declare const MCP_SUPPORTED_VERSIONS: readonly ["2025-06-18", "2025-03-26", "2024-11-05"];
|
|
12
|
+
/** The revision advertised when a client asks for one we do not know. */
|
|
13
|
+
export declare const MCP_PROTOCOL_VERSION: "2025-06-18";
|
|
14
|
+
export declare const MCP_METHODS: {
|
|
15
|
+
readonly INITIALIZE: "initialize";
|
|
16
|
+
readonly INITIALIZED: "notifications/initialized";
|
|
17
|
+
readonly PING: "ping";
|
|
18
|
+
readonly TOOLS_LIST: "tools/list";
|
|
19
|
+
readonly TOOLS_CALL: "tools/call";
|
|
20
|
+
};
|
|
21
|
+
/** JSON-RPC 2.0 error codes (https://www.jsonrpc.org/specification#error_object). */
|
|
22
|
+
export declare const JSONRPC_ERROR: {
|
|
23
|
+
readonly PARSE_ERROR: -32700;
|
|
24
|
+
readonly INVALID_REQUEST: -32600;
|
|
25
|
+
readonly METHOD_NOT_FOUND: -32601;
|
|
26
|
+
readonly INVALID_PARAMS: -32602;
|
|
27
|
+
readonly INTERNAL_ERROR: -32603;
|
|
28
|
+
};
|
|
29
|
+
export type JsonRpcId = string | number | null;
|
|
30
|
+
export interface JsonRpcRequest {
|
|
31
|
+
jsonrpc: '2.0';
|
|
32
|
+
/** Absent on notifications, which must not be answered. */
|
|
33
|
+
id?: JsonRpcId;
|
|
34
|
+
method: string;
|
|
35
|
+
params?: unknown;
|
|
36
|
+
}
|
|
37
|
+
export interface JsonRpcSuccess {
|
|
38
|
+
jsonrpc: '2.0';
|
|
39
|
+
id: JsonRpcId;
|
|
40
|
+
result: unknown;
|
|
41
|
+
}
|
|
42
|
+
export interface JsonRpcErrorBody {
|
|
43
|
+
code: number;
|
|
44
|
+
message: string;
|
|
45
|
+
data?: unknown;
|
|
46
|
+
}
|
|
47
|
+
export interface JsonRpcFailure {
|
|
48
|
+
jsonrpc: '2.0';
|
|
49
|
+
id: JsonRpcId;
|
|
50
|
+
error: JsonRpcErrorBody;
|
|
51
|
+
}
|
|
52
|
+
export type JsonRpcResponse = JsonRpcSuccess | JsonRpcFailure;
|
|
53
|
+
export declare function rpcError(id: JsonRpcId, code: number, message: string, data?: unknown): JsonRpcFailure;
|
|
54
|
+
export interface McpServerInfo {
|
|
55
|
+
name: string;
|
|
56
|
+
version: string;
|
|
57
|
+
}
|
|
58
|
+
export interface McpInitializeResult {
|
|
59
|
+
protocolVersion: string;
|
|
60
|
+
capabilities: {
|
|
61
|
+
tools: {
|
|
62
|
+
listChanged: boolean;
|
|
63
|
+
};
|
|
64
|
+
};
|
|
65
|
+
serverInfo: McpServerInfo;
|
|
66
|
+
instructions?: string;
|
|
67
|
+
}
|
|
68
|
+
/** A text item in a tool result. The only content type this server emits. */
|
|
69
|
+
export interface McpTextContent {
|
|
70
|
+
type: 'text';
|
|
71
|
+
text: string;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* A tool result. `isError: true` marks a *tool* failure (including a policy
|
|
75
|
+
* denial), which the model sees as output and can react to — as opposed to a
|
|
76
|
+
* JSON-RPC error, which aborts the call.
|
|
77
|
+
*/
|
|
78
|
+
export interface McpToolResult {
|
|
79
|
+
content: McpTextContent[];
|
|
80
|
+
isError?: boolean;
|
|
81
|
+
/** Machine-readable payload mirroring `content`, when there is one. */
|
|
82
|
+
structuredContent?: unknown;
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=protocol.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,yDAAyD;AACzD,eAAO,MAAM,sBAAsB,qDAAsD,CAAC;AAE1F,yEAAyE;AACzE,eAAO,MAAM,oBAAoB,cAA4B,CAAC;AAE9D,eAAO,MAAM,WAAW;;;;;;CAMd,CAAC;AAEX,qFAAqF;AACrF,eAAO,MAAM,aAAa;;;;;;CAMhB,CAAC;AAEX,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;AAE/C,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,KAAK,CAAC;IACf,2DAA2D;IAC3D,EAAE,CAAC,EAAE,SAAS,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,KAAK,CAAC;IACf,EAAE,EAAE,SAAS,CAAC;IACd,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,KAAK,CAAC;IACf,EAAE,EAAE,SAAS,CAAC;IACd,KAAK,EAAE,gBAAgB,CAAC;CACzB;AAED,MAAM,MAAM,eAAe,GAAG,cAAc,GAAG,cAAc,CAAC;AAE9D,wBAAgB,QAAQ,CACtB,EAAE,EAAE,SAAS,EACb,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,IAAI,CAAC,EAAE,OAAO,GACb,cAAc,CAEhB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,mBAAmB;IAClC,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE;QAAE,KAAK,EAAE;YAAE,WAAW,EAAE,OAAO,CAAA;SAAE,CAAA;KAAE,CAAC;IAClD,UAAU,EAAE,aAAa,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,6EAA6E;AAC7E,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,cAAc,EAAE,CAAC;IAC1B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,uEAAuE;IACvE,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B"}
|
package/dist/protocol.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The MCP wire types this server speaks, plus the JSON-RPC 2.0 envelope they
|
|
3
|
+
* ride on.
|
|
4
|
+
*
|
|
5
|
+
* Modelled on the Model Context Protocol revision `2025-06-18`
|
|
6
|
+
* (https://modelcontextprotocol.io/specification/2025-06-18) — specifically the
|
|
7
|
+
* `initialize`, `tools/list` and `tools/call` messages. Only the server side is
|
|
8
|
+
* implemented, and only the parts a tool server needs.
|
|
9
|
+
*/
|
|
10
|
+
/** MCP revisions this server can speak, newest first. */
|
|
11
|
+
export const MCP_SUPPORTED_VERSIONS = ['2025-06-18', '2025-03-26', '2024-11-05'];
|
|
12
|
+
/** The revision advertised when a client asks for one we do not know. */
|
|
13
|
+
export const MCP_PROTOCOL_VERSION = MCP_SUPPORTED_VERSIONS[0];
|
|
14
|
+
export const MCP_METHODS = {
|
|
15
|
+
INITIALIZE: 'initialize',
|
|
16
|
+
INITIALIZED: 'notifications/initialized',
|
|
17
|
+
PING: 'ping',
|
|
18
|
+
TOOLS_LIST: 'tools/list',
|
|
19
|
+
TOOLS_CALL: 'tools/call',
|
|
20
|
+
};
|
|
21
|
+
/** JSON-RPC 2.0 error codes (https://www.jsonrpc.org/specification#error_object). */
|
|
22
|
+
export const JSONRPC_ERROR = {
|
|
23
|
+
PARSE_ERROR: -32700,
|
|
24
|
+
INVALID_REQUEST: -32600,
|
|
25
|
+
METHOD_NOT_FOUND: -32601,
|
|
26
|
+
INVALID_PARAMS: -32602,
|
|
27
|
+
INTERNAL_ERROR: -32603,
|
|
28
|
+
};
|
|
29
|
+
export function rpcError(id, code, message, data) {
|
|
30
|
+
return { jsonrpc: '2.0', id, error: data === undefined ? { code, message } : { code, message, data } };
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=protocol.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocol.js","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,yDAAyD;AACzD,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,YAAY,EAAE,YAAY,EAAE,YAAY,CAAU,CAAC;AAE1F,yEAAyE;AACzE,MAAM,CAAC,MAAM,oBAAoB,GAAG,sBAAsB,CAAC,CAAC,CAAC,CAAC;AAE9D,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,UAAU,EAAE,YAAY;IACxB,WAAW,EAAE,2BAA2B;IACxC,IAAI,EAAE,MAAM;IACZ,UAAU,EAAE,YAAY;IACxB,UAAU,EAAE,YAAY;CAChB,CAAC;AAEX,qFAAqF;AACrF,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B,WAAW,EAAE,CAAC,KAAK;IACnB,eAAe,EAAE,CAAC,KAAK;IACvB,gBAAgB,EAAE,CAAC,KAAK;IACxB,cAAc,EAAE,CAAC,KAAK;IACtB,cAAc,EAAE,CAAC,KAAK;CACd,CAAC;AAgCX,MAAM,UAAU,QAAQ,CACtB,EAAa,EACb,IAAY,EACZ,OAAe,EACf,IAAc;IAEd,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,CAAC;AACzG,CAAC"}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type PolicyGate } from './gate.js';
|
|
2
|
+
import { type JsonRpcRequest, type JsonRpcResponse } from './protocol.js';
|
|
3
|
+
import { ToolRegistry, type McpTool } from './tools.js';
|
|
4
|
+
export interface McpServerOptions {
|
|
5
|
+
/** Server name reported in `initialize`. */
|
|
6
|
+
name: string;
|
|
7
|
+
version: string;
|
|
8
|
+
tools: readonly McpTool[];
|
|
9
|
+
/**
|
|
10
|
+
* The gate every tool action passes through before it runs. Defaults to
|
|
11
|
+
* {@link DENY_ALL} — a server with no policy must not be able to act.
|
|
12
|
+
*/
|
|
13
|
+
gate?: PolicyGate;
|
|
14
|
+
/** Optional guidance returned to the client in `initialize`. */
|
|
15
|
+
instructions?: string;
|
|
16
|
+
}
|
|
17
|
+
export interface McpServer {
|
|
18
|
+
readonly tools: ToolRegistry;
|
|
19
|
+
/** Handles one JSON-RPC message; returns `undefined` for notifications. */
|
|
20
|
+
handle(request: JsonRpcRequest): Promise<JsonRpcResponse | undefined>;
|
|
21
|
+
}
|
|
22
|
+
export declare function createMcpServer(options: McpServerOptions): McpServer;
|
|
23
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,UAAU,EAAmB,MAAM,WAAW,CAAC;AACvE,OAAO,EAOL,KAAK,cAAc,EACnB,KAAK,eAAe,EAErB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,YAAY,EAAe,KAAK,OAAO,EAAE,MAAM,YAAY,CAAC;AAErE,MAAM,WAAW,gBAAgB;IAC/B,4CAA4C;IAC5C,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,SAAS,OAAO,EAAE,CAAC;IAC1B;;;OAGG;IACH,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,gEAAgE;IAChE,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;IAC7B,2EAA2E;IAC3E,MAAM,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,eAAe,GAAG,SAAS,CAAC,CAAC;CACvE;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,gBAAgB,GAAG,SAAS,CAkGpE"}
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { DENY_ALL } from './gate.js';
|
|
2
|
+
import { JSONRPC_ERROR, MCP_METHODS, MCP_PROTOCOL_VERSION, MCP_SUPPORTED_VERSIONS, rpcError, } from './protocol.js';
|
|
3
|
+
import { ToolRegistry, errorResult } from './tools.js';
|
|
4
|
+
export function createMcpServer(options) {
|
|
5
|
+
const tools = new ToolRegistry(options.tools);
|
|
6
|
+
const gate = options.gate ?? DENY_ALL;
|
|
7
|
+
function initialize(params) {
|
|
8
|
+
const requested = isObject(params) && typeof params.protocolVersion === 'string' ? params.protocolVersion : undefined;
|
|
9
|
+
const protocolVersion = requested !== undefined && MCP_SUPPORTED_VERSIONS.includes(requested)
|
|
10
|
+
? requested
|
|
11
|
+
: MCP_PROTOCOL_VERSION;
|
|
12
|
+
const result = {
|
|
13
|
+
protocolVersion,
|
|
14
|
+
capabilities: { tools: { listChanged: false } },
|
|
15
|
+
serverInfo: { name: options.name, version: options.version },
|
|
16
|
+
};
|
|
17
|
+
if (options.instructions !== undefined)
|
|
18
|
+
result.instructions = options.instructions;
|
|
19
|
+
return result;
|
|
20
|
+
}
|
|
21
|
+
async function callTool(id, params) {
|
|
22
|
+
if (!isObject(params) || typeof params.name !== 'string') {
|
|
23
|
+
return rpcError(id, JSONRPC_ERROR.INVALID_PARAMS, 'tools/call requires a "name" string');
|
|
24
|
+
}
|
|
25
|
+
const tool = tools.get(params.name);
|
|
26
|
+
if (tool === undefined) {
|
|
27
|
+
return rpcError(id, JSONRPC_ERROR.INVALID_PARAMS, `unknown tool "${params.name}"`);
|
|
28
|
+
}
|
|
29
|
+
const args = isObject(params.arguments) ? params.arguments : {};
|
|
30
|
+
// The gate stands in front of every action. A denial is a *tool* result, not
|
|
31
|
+
// a protocol error: the model sees why it was refused and can propose
|
|
32
|
+
// something else, which is the whole point of being policy-aware.
|
|
33
|
+
let action;
|
|
34
|
+
try {
|
|
35
|
+
action = tool.action?.(args);
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
return { jsonrpc: '2.0', id, result: errorResult(messageOf(error)) };
|
|
39
|
+
}
|
|
40
|
+
if (action !== undefined) {
|
|
41
|
+
const decision = await gate.check(action);
|
|
42
|
+
if (decision.verdict === 'deny') {
|
|
43
|
+
return {
|
|
44
|
+
jsonrpc: '2.0',
|
|
45
|
+
id,
|
|
46
|
+
result: errorResult(decision.reason ?? `policy denied ${action.kind}`, {
|
|
47
|
+
denied: true,
|
|
48
|
+
kind: action.kind,
|
|
49
|
+
...(decision.ruleIndex !== undefined ? { ruleIndex: decision.ruleIndex } : {}),
|
|
50
|
+
}),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
try {
|
|
55
|
+
return { jsonrpc: '2.0', id, result: await tool.run(args) };
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
return { jsonrpc: '2.0', id, result: errorResult(messageOf(error)) };
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
tools,
|
|
63
|
+
async handle(request) {
|
|
64
|
+
const id = request.id ?? null;
|
|
65
|
+
const isNotification = request.id === undefined;
|
|
66
|
+
switch (request.method) {
|
|
67
|
+
case MCP_METHODS.INITIALIZE:
|
|
68
|
+
return isNotification ? undefined : { jsonrpc: '2.0', id, result: initialize(request.params) };
|
|
69
|
+
// A notification: the client confirming it is ready. No reply, ever.
|
|
70
|
+
case MCP_METHODS.INITIALIZED:
|
|
71
|
+
return undefined;
|
|
72
|
+
case MCP_METHODS.PING:
|
|
73
|
+
return isNotification ? undefined : { jsonrpc: '2.0', id, result: {} };
|
|
74
|
+
case MCP_METHODS.TOOLS_LIST:
|
|
75
|
+
return isNotification
|
|
76
|
+
? undefined
|
|
77
|
+
: { jsonrpc: '2.0', id, result: { tools: tools.descriptors() } };
|
|
78
|
+
case MCP_METHODS.TOOLS_CALL:
|
|
79
|
+
return isNotification ? undefined : callTool(id, request.params);
|
|
80
|
+
default:
|
|
81
|
+
return isNotification
|
|
82
|
+
? undefined
|
|
83
|
+
: rpcError(id, JSONRPC_ERROR.METHOD_NOT_FOUND, `unknown method "${request.method}"`);
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
function isObject(value) {
|
|
89
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
90
|
+
}
|
|
91
|
+
function messageOf(error) {
|
|
92
|
+
return error instanceof Error ? error.message : String(error);
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
package/dist/stdio.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { McpServer } from './server.js';
|
|
2
|
+
/**
|
|
3
|
+
* The little of a Node readable stream this needs. Kept structural so the
|
|
4
|
+
* transport is testable with a plain object and carries no `@types/node`
|
|
5
|
+
* dependency.
|
|
6
|
+
*/
|
|
7
|
+
export interface ReadableLike {
|
|
8
|
+
setEncoding(encoding: string): unknown;
|
|
9
|
+
on(event: 'data', listener: (chunk: string) => void): unknown;
|
|
10
|
+
on(event: 'end', listener: () => void): unknown;
|
|
11
|
+
}
|
|
12
|
+
export interface WritableLike {
|
|
13
|
+
write(chunk: string): unknown;
|
|
14
|
+
}
|
|
15
|
+
export interface ServeStdioOptions {
|
|
16
|
+
server: McpServer;
|
|
17
|
+
input: ReadableLike;
|
|
18
|
+
output: WritableLike;
|
|
19
|
+
/** Called for transport-level failures that must not kill the stream. */
|
|
20
|
+
onError?: (error: unknown) => void;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Serves MCP over stdio using the newline-delimited JSON-RPC framing the spec
|
|
24
|
+
* prescribes for local servers: one JSON message per line, no embedded
|
|
25
|
+
* newlines, responses written back the same way.
|
|
26
|
+
*
|
|
27
|
+
* Notifications produce no output, so a client's `notifications/initialized`
|
|
28
|
+
* is simply consumed.
|
|
29
|
+
*/
|
|
30
|
+
export declare function serveStdio(options: ServeStdioOptions): void;
|
|
31
|
+
//# sourceMappingURL=stdio.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stdio.d.ts","sourceRoot":"","sources":["../src/stdio.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;IACvC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC;IAC9D,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC;CACjD;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;CAC/B;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,SAAS,CAAC;IAClB,KAAK,EAAE,YAAY,CAAC;IACpB,MAAM,EAAE,YAAY,CAAC;IACrB,yEAAyE;IACzE,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CACpC;AAED;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,IAAI,CAwB3D"}
|
package/dist/stdio.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { JSONRPC_ERROR, rpcError } from './protocol.js';
|
|
2
|
+
/**
|
|
3
|
+
* Serves MCP over stdio using the newline-delimited JSON-RPC framing the spec
|
|
4
|
+
* prescribes for local servers: one JSON message per line, no embedded
|
|
5
|
+
* newlines, responses written back the same way.
|
|
6
|
+
*
|
|
7
|
+
* Notifications produce no output, so a client's `notifications/initialized`
|
|
8
|
+
* is simply consumed.
|
|
9
|
+
*/
|
|
10
|
+
export function serveStdio(options) {
|
|
11
|
+
let buffer = '';
|
|
12
|
+
const write = (response) => {
|
|
13
|
+
options.output.write(`${JSON.stringify(response)}\n`);
|
|
14
|
+
};
|
|
15
|
+
options.input.setEncoding('utf8');
|
|
16
|
+
options.input.on('data', (chunk) => {
|
|
17
|
+
buffer += chunk;
|
|
18
|
+
let newline = buffer.indexOf('\n');
|
|
19
|
+
while (newline !== -1) {
|
|
20
|
+
const line = buffer.slice(0, newline).trim();
|
|
21
|
+
buffer = buffer.slice(newline + 1);
|
|
22
|
+
if (line.length > 0)
|
|
23
|
+
void handleLine(line, options, write);
|
|
24
|
+
newline = buffer.indexOf('\n');
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
options.input.on('end', () => {
|
|
28
|
+
const rest = buffer.trim();
|
|
29
|
+
buffer = '';
|
|
30
|
+
if (rest.length > 0)
|
|
31
|
+
void handleLine(rest, options, write);
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
async function handleLine(line, options, write) {
|
|
35
|
+
let parsed;
|
|
36
|
+
try {
|
|
37
|
+
parsed = JSON.parse(line);
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
write(rpcError(null, JSONRPC_ERROR.PARSE_ERROR, 'parse error'));
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
if (!isRequest(parsed)) {
|
|
44
|
+
write(rpcError(null, JSONRPC_ERROR.INVALID_REQUEST, 'invalid request'));
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
try {
|
|
48
|
+
const response = await options.server.handle(parsed);
|
|
49
|
+
if (response !== undefined)
|
|
50
|
+
write(response);
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
options.onError?.(error);
|
|
54
|
+
write(rpcError(parsed.id ?? null, JSONRPC_ERROR.INTERNAL_ERROR, messageOf(error)));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function isRequest(value) {
|
|
58
|
+
return (typeof value === 'object' &&
|
|
59
|
+
value !== null &&
|
|
60
|
+
!Array.isArray(value) &&
|
|
61
|
+
value.jsonrpc === '2.0' &&
|
|
62
|
+
typeof value.method === 'string');
|
|
63
|
+
}
|
|
64
|
+
function messageOf(error) {
|
|
65
|
+
return error instanceof Error ? error.message : String(error);
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=stdio.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stdio.js","sourceRoot":"","sources":["../src/stdio.ts"],"names":[],"mappings":"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"}
|
package/dist/tools.d.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { ToolAction } from './gate.js';
|
|
2
|
+
import type { McpToolResult } from './protocol.js';
|
|
3
|
+
/** Arguments as they arrive from a `tools/call`; validated by the tool itself. */
|
|
4
|
+
export type ToolArgs = Record<string, unknown>;
|
|
5
|
+
/** A JSON Schema object describing a tool's arguments. */
|
|
6
|
+
export type JsonSchema = Record<string, unknown>;
|
|
7
|
+
/** One tool a model can discover and invoke. */
|
|
8
|
+
export interface McpTool {
|
|
9
|
+
name: string;
|
|
10
|
+
title?: string;
|
|
11
|
+
description: string;
|
|
12
|
+
inputSchema: JsonSchema;
|
|
13
|
+
/**
|
|
14
|
+
* Derives the action the policy gate must approve before `run` is called.
|
|
15
|
+
* Omit it for a read-only tool (a summary, a dry-run) — those are never
|
|
16
|
+
* gated, because refusing a dry-run would only hide the policy from the model.
|
|
17
|
+
*/
|
|
18
|
+
action?(args: ToolArgs): ToolAction;
|
|
19
|
+
run(args: ToolArgs): Promise<McpToolResult> | McpToolResult;
|
|
20
|
+
}
|
|
21
|
+
/** The serialisable shape `tools/list` returns. */
|
|
22
|
+
export interface ToolDescriptor {
|
|
23
|
+
name: string;
|
|
24
|
+
title?: string;
|
|
25
|
+
description: string;
|
|
26
|
+
inputSchema: JsonSchema;
|
|
27
|
+
}
|
|
28
|
+
export declare function textResult(text: string, structuredContent?: unknown): McpToolResult;
|
|
29
|
+
export declare function errorResult(text: string, structuredContent?: unknown): McpToolResult;
|
|
30
|
+
/** JSON replacer that renders `bigint` as a decimal string, never losing precision. */
|
|
31
|
+
export declare function jsonReplacer(_key: string, value: unknown): unknown;
|
|
32
|
+
/** A result whose text is the pretty-printed JSON of `structured`. */
|
|
33
|
+
export declare function jsonResult(structured: unknown): McpToolResult;
|
|
34
|
+
/** An ordered, name-unique set of tools. */
|
|
35
|
+
export declare class ToolRegistry {
|
|
36
|
+
#private;
|
|
37
|
+
constructor(tools?: readonly McpTool[]);
|
|
38
|
+
register(tool: McpTool): void;
|
|
39
|
+
get(name: string): McpTool | undefined;
|
|
40
|
+
list(): McpTool[];
|
|
41
|
+
descriptors(): ToolDescriptor[];
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=tools.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAC5C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAEnD,kFAAkF;AAClF,MAAM,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAE/C,0DAA0D;AAC1D,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEjD,gDAAgD;AAChD,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,UAAU,CAAC;IACxB;;;;OAIG;IACH,MAAM,CAAC,CAAC,IAAI,EAAE,QAAQ,GAAG,UAAU,CAAC;IACpC,GAAG,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,aAAa,CAAC,GAAG,aAAa,CAAC;CAC7D;AAED,mDAAmD;AACnD,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,UAAU,CAAC;CACzB;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,iBAAiB,CAAC,EAAE,OAAO,GAAG,aAAa,CAInF;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,iBAAiB,CAAC,EAAE,OAAO,GAAG,aAAa,CAIpF;AAED,uFAAuF;AACvF,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAElE;AAED,sEAAsE;AACtE,wBAAgB,UAAU,CAAC,UAAU,EAAE,OAAO,GAAG,aAAa,CAE7D;AAED,4CAA4C;AAC5C,qBAAa,YAAY;;gBAGX,KAAK,GAAE,SAAS,OAAO,EAAO;IAI1C,QAAQ,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI;IAO7B,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS;IAItC,IAAI,IAAI,OAAO,EAAE;IAIjB,WAAW,IAAI,cAAc,EAAE;CAWhC"}
|
package/dist/tools.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export function textResult(text, structuredContent) {
|
|
2
|
+
return structuredContent === undefined
|
|
3
|
+
? { content: [{ type: 'text', text }] }
|
|
4
|
+
: { content: [{ type: 'text', text }], structuredContent };
|
|
5
|
+
}
|
|
6
|
+
export function errorResult(text, structuredContent) {
|
|
7
|
+
return structuredContent === undefined
|
|
8
|
+
? { content: [{ type: 'text', text }], isError: true }
|
|
9
|
+
: { content: [{ type: 'text', text }], isError: true, structuredContent };
|
|
10
|
+
}
|
|
11
|
+
/** JSON replacer that renders `bigint` as a decimal string, never losing precision. */
|
|
12
|
+
export function jsonReplacer(_key, value) {
|
|
13
|
+
return typeof value === 'bigint' ? value.toString() : value;
|
|
14
|
+
}
|
|
15
|
+
/** A result whose text is the pretty-printed JSON of `structured`. */
|
|
16
|
+
export function jsonResult(structured) {
|
|
17
|
+
return textResult(JSON.stringify(structured, jsonReplacer, 2), structured);
|
|
18
|
+
}
|
|
19
|
+
/** An ordered, name-unique set of tools. */
|
|
20
|
+
export class ToolRegistry {
|
|
21
|
+
#tools = new Map();
|
|
22
|
+
constructor(tools = []) {
|
|
23
|
+
for (const tool of tools)
|
|
24
|
+
this.register(tool);
|
|
25
|
+
}
|
|
26
|
+
register(tool) {
|
|
27
|
+
if (this.#tools.has(tool.name)) {
|
|
28
|
+
throw new Error(`tool "${tool.name}" is already registered`);
|
|
29
|
+
}
|
|
30
|
+
this.#tools.set(tool.name, tool);
|
|
31
|
+
}
|
|
32
|
+
get(name) {
|
|
33
|
+
return this.#tools.get(name);
|
|
34
|
+
}
|
|
35
|
+
list() {
|
|
36
|
+
return [...this.#tools.values()];
|
|
37
|
+
}
|
|
38
|
+
descriptors() {
|
|
39
|
+
return this.list().map((tool) => {
|
|
40
|
+
const descriptor = {
|
|
41
|
+
name: tool.name,
|
|
42
|
+
description: tool.description,
|
|
43
|
+
inputSchema: tool.inputSchema,
|
|
44
|
+
};
|
|
45
|
+
if (tool.title !== undefined)
|
|
46
|
+
descriptor.title = tool.title;
|
|
47
|
+
return descriptor;
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=tools.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tools.js","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAgCA,MAAM,UAAU,UAAU,CAAC,IAAY,EAAE,iBAA2B;IAClE,OAAO,iBAAiB,KAAK,SAAS;QACpC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE;QACvC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,iBAAiB,EAAE,CAAC;AAC/D,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,IAAY,EAAE,iBAA2B;IACnE,OAAO,iBAAiB,KAAK,SAAS;QACpC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE;QACtD,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,iBAAiB,EAAE,CAAC;AAC9E,CAAC;AAED,uFAAuF;AACvF,MAAM,UAAU,YAAY,CAAC,IAAY,EAAE,KAAc;IACvD,OAAO,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC;AAC9D,CAAC;AAED,sEAAsE;AACtE,MAAM,UAAU,UAAU,CAAC,UAAmB;IAC5C,OAAO,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,UAAU,EAAE,YAAY,EAAE,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC;AAC7E,CAAC;AAED,4CAA4C;AAC5C,MAAM,OAAO,YAAY;IACd,MAAM,GAAG,IAAI,GAAG,EAAmB,CAAC;IAE7C,YAAY,QAA4B,EAAE;QACxC,KAAK,MAAM,IAAI,IAAI,KAAK;YAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IAChD,CAAC;IAED,QAAQ,CAAC,IAAa;QACpB,IAAI,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YAC/B,MAAM,IAAI,KAAK,CAAC,SAAS,IAAI,CAAC,IAAI,yBAAyB,CAAC,CAAC;QAC/D,CAAC;QACD,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IACnC,CAAC;IAED,GAAG,CAAC,IAAY;QACd,OAAO,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC/B,CAAC;IAED,IAAI;QACF,OAAO,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;IACnC,CAAC;IAED,WAAW;QACT,OAAO,IAAI,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;YAC9B,MAAM,UAAU,GAAmB;gBACjC,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,WAAW,EAAE,IAAI,CAAC,WAAW;gBAC7B,WAAW,EAAE,IAAI,CAAC,WAAW;aAC9B,CAAC;YACF,IAAI,IAAI,CAAC,KAAK,KAAK,SAAS;gBAAE,UAAU,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;YAC5D,OAAO,UAAU,CAAC;QACpB,CAAC,CAAC,CAAC;IACL,CAAC;CACF"}
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@keelcodes/mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Policy-aware MCP server for Keel: expose on-chain agent actions to a model, gated by the authorization policy before anything executes.",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"homepage": "https://keel.codes",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/keelcodes/keel.git",
|
|
10
|
+
"directory": "packages/mcp"
|
|
11
|
+
},
|
|
12
|
+
"type": "module",
|
|
13
|
+
"main": "./dist/index.js",
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"import": "./dist/index.js"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist",
|
|
23
|
+
"README.md"
|
|
24
|
+
],
|
|
25
|
+
"sideEffects": false,
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@keelcodes/manifest": "0.1.0",
|
|
28
|
+
"@keelcodes/policy": "0.1.0"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"vitest": "^2.1.4"
|
|
32
|
+
},
|
|
33
|
+
"publishConfig": {
|
|
34
|
+
"access": "public"
|
|
35
|
+
},
|
|
36
|
+
"engines": {
|
|
37
|
+
"node": ">=20"
|
|
38
|
+
},
|
|
39
|
+
"keywords": [
|
|
40
|
+
"mcp",
|
|
41
|
+
"model-context-protocol",
|
|
42
|
+
"ai-agent",
|
|
43
|
+
"policy",
|
|
44
|
+
"authorization",
|
|
45
|
+
"account-abstraction"
|
|
46
|
+
],
|
|
47
|
+
"scripts": {
|
|
48
|
+
"build": "tsc -p tsconfig.json",
|
|
49
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
50
|
+
"test": "vitest run",
|
|
51
|
+
"clean": "rm -rf dist"
|
|
52
|
+
}
|
|
53
|
+
}
|