@konseki/mcp 1.0.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Konseki
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,135 @@
1
+ # Konseki MCP
2
+
3
+ Official Model Context Protocol server for Konseki.
4
+
5
+ Konseki MCP is a direct wrapper around the Konseki public API. It lets AI agents and AI trading tools call Konseki endpoints through MCP tools while preserving the raw API response JSON for the user or downstream application to interpret.
6
+
7
+ ## Requirements
8
+
9
+ - Node.js 20 or newer.
10
+ - A Konseki API key.
11
+
12
+ ## Design Principles
13
+
14
+ - Direct API wrapper: the MCP server fetches Konseki API responses and returns them without interpretation.
15
+ - User-owned interpretation: users, builders, and downstream AI clients decide how to analyze or summarize the returned JSON.
16
+ - Public API only: the server uses `X-API-Key` against documented Konseki endpoints.
17
+ - No internal credentials: non-public service or operational credentials are never required for this package.
18
+
19
+ ## Configuration
20
+
21
+ The server reads configuration from environment variables:
22
+
23
+ ```sh
24
+ KONSEKI_API_KEY=ks_live_your_api_key
25
+ ```
26
+
27
+ Do not commit real API keys.
28
+
29
+ ## Intended Architecture
30
+
31
+ ```text
32
+ AI client
33
+ |
34
+ v
35
+ Konseki MCP server
36
+ |
37
+ v
38
+ Konseki public API
39
+ |
40
+ v
41
+ Raw historical market context JSON
42
+ ```
43
+
44
+ The MCP server should not bypass Konseki public API behavior. It should behave like any other public API client.
45
+
46
+ ## Tools
47
+
48
+ ### `get_konseki_metadata`
49
+
50
+ Fetches raw JSON from `GET /v1/metadata`.
51
+
52
+ Input: none.
53
+
54
+ ### `list_konseki_symbols`
55
+
56
+ Fetches raw JSON from `GET /v1/symbols`.
57
+
58
+ Input: none.
59
+
60
+ ### `get_konseki_analysis`
61
+
62
+ Fetches raw JSON from `GET /v1/analysis/{symbol}-{exchange}?lookback={lookback}`.
63
+
64
+ Input:
65
+
66
+ ```json
67
+ {
68
+ "symbol": "AAPL",
69
+ "exchange": "NASDAQ",
70
+ "lookback": 15
71
+ }
72
+ ```
73
+
74
+ Supported `lookback` values: `5`, `10`, `15`, `20`, `25`, `30`, `40`, `50`.
75
+
76
+ ## Response Compression
77
+
78
+ The server requests gzip-compressed API responses and decompresses them locally before returning JSON to the MCP client. This is handled automatically; users do not need to configure compression.
79
+
80
+ ## Installation
81
+
82
+ Use the package through an MCP client with `npx`:
83
+
84
+ ```json
85
+ {
86
+ "mcpServers": {
87
+ "konseki": {
88
+ "command": "npx",
89
+ "args": ["-y", "@konseki/mcp"],
90
+ "env": {
91
+ "KONSEKI_API_KEY": "ks_live_your_api_key"
92
+ }
93
+ }
94
+ }
95
+ }
96
+ ```
97
+
98
+ For local development from this checkout, configure your MCP client to run the built server:
99
+
100
+ ```json
101
+ {
102
+ "mcpServers": {
103
+ "konseki": {
104
+ "command": "node",
105
+ "args": ["/absolute/path/to/konseki-mcp/dist/index.js"],
106
+ "env": {
107
+ "KONSEKI_API_KEY": "ks_live_your_api_key"
108
+ }
109
+ }
110
+ }
111
+ }
112
+ ```
113
+
114
+ ## Development
115
+
116
+ Install dependencies:
117
+
118
+ ```sh
119
+ npm install
120
+ ```
121
+
122
+ Run verification:
123
+
124
+ ```sh
125
+ npm run typecheck
126
+ npm test
127
+ npm run build
128
+ ```
129
+
130
+ ## Security
131
+
132
+ - Never commit real API keys.
133
+ - Never log raw API keys.
134
+ - Never include user credentials in test snapshots or examples.
135
+ - Use fake keys in documentation and tests.
package/SECURITY.md ADDED
@@ -0,0 +1,14 @@
1
+ # Security
2
+
3
+ ## Reporting Vulnerabilities
4
+
5
+ Please report suspected security vulnerabilities privately through the repository's GitHub security advisory flow when available.
6
+
7
+ Do not include real API keys, customer data, or other secrets in public issues, pull requests, tests, or examples.
8
+
9
+ ## Secret Handling
10
+
11
+ - Configure the server with `KONSEKI_API_KEY` in your local MCP client environment.
12
+ - Do not commit real API keys.
13
+ - Do not paste real API keys into bug reports or screenshots.
14
+ - Use fake keys in tests and documentation.
@@ -0,0 +1,33 @@
1
+ import { type KonsekiMcpConfig } from "./config.js";
2
+ export type ApiJsonObject = Record<string, unknown>;
3
+ export type KonsekiApiResult = {
4
+ json: ApiJsonObject;
5
+ ok: true;
6
+ status: number;
7
+ } | {
8
+ json?: ApiJsonObject;
9
+ message: string;
10
+ ok: false;
11
+ status?: number;
12
+ };
13
+ type FetchLike = typeof fetch;
14
+ export type KonsekiApiClientOptions = {
15
+ fetchImpl?: FetchLike;
16
+ timeoutMs?: number;
17
+ };
18
+ export declare class KonsekiApiClient {
19
+ private readonly apiKey;
20
+ private readonly fetchImpl;
21
+ private readonly timeoutMs;
22
+ constructor(config: KonsekiMcpConfig, options?: KonsekiApiClientOptions);
23
+ getMetadata(): Promise<KonsekiApiResult>;
24
+ listSymbols(): Promise<KonsekiApiResult>;
25
+ getAnalysis(params: {
26
+ exchange: string;
27
+ lookback: string;
28
+ symbol: string;
29
+ }): Promise<KonsekiApiResult>;
30
+ private request;
31
+ }
32
+ export {};
33
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAEA,OAAO,EAAkD,KAAK,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEpG,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAIpD,MAAM,MAAM,gBAAgB,GACxB;IACE,IAAI,EAAE,aAAa,CAAC;IACpB,EAAE,EAAE,IAAI,CAAC;IACT,MAAM,EAAE,MAAM,CAAC;CAChB,GACD;IACE,IAAI,CAAC,EAAE,aAAa,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,EAAE,EAAE,KAAK,CAAC;IACV,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEN,KAAK,SAAS,GAAG,OAAO,KAAK,CAAC;AAE9B,MAAM,MAAM,uBAAuB,GAAG;IACpC,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAY;IACtC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;gBAEvB,MAAM,EAAE,gBAAgB,EAAE,OAAO,CAAC,EAAE,uBAAuB;IAMjE,WAAW,IAAI,OAAO,CAAC,gBAAgB,CAAC;IAIxC,WAAW,IAAI,OAAO,CAAC,gBAAgB,CAAC;IAIxC,WAAW,CAAC,MAAM,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,gBAAgB,CAAC;YAS9F,OAAO;CAsDtB"}
package/dist/client.js ADDED
@@ -0,0 +1,108 @@
1
+ import { gunzip } from "node:zlib";
2
+ import { promisify } from "node:util";
3
+ import { KONSEKI_API_ORIGIN, KONSEKI_REQUEST_TIMEOUT_MS } from "./config.js";
4
+ const gunzipAsync = promisify(gunzip);
5
+ export class KonsekiApiClient {
6
+ apiKey;
7
+ fetchImpl;
8
+ timeoutMs;
9
+ constructor(config, options) {
10
+ this.apiKey = config.apiKey;
11
+ this.fetchImpl = options?.fetchImpl ?? fetch;
12
+ this.timeoutMs = options?.timeoutMs ?? KONSEKI_REQUEST_TIMEOUT_MS;
13
+ }
14
+ async getMetadata() {
15
+ return this.request("/v1/metadata");
16
+ }
17
+ async listSymbols() {
18
+ return this.request("/v1/symbols");
19
+ }
20
+ async getAnalysis(params) {
21
+ const symbolExchange = `${encodeURIComponent(params.symbol)}-${encodeURIComponent(params.exchange)}`;
22
+ const searchParams = new URLSearchParams({
23
+ lookback: params.lookback,
24
+ });
25
+ return this.request(`/v1/analysis/${symbolExchange}?${searchParams.toString()}`);
26
+ }
27
+ async request(pathAndQuery) {
28
+ const url = new URL(pathAndQuery, KONSEKI_API_ORIGIN);
29
+ const controller = new AbortController();
30
+ const timeout = setTimeout(() => controller.abort(), this.timeoutMs);
31
+ try {
32
+ const response = await this.fetchImpl(url, {
33
+ headers: {
34
+ Accept: "application/json",
35
+ "Accept-Encoding": "gzip",
36
+ "X-API-Key": this.apiKey,
37
+ },
38
+ method: "GET",
39
+ signal: controller.signal,
40
+ });
41
+ const parsed = await parseJsonObject(response);
42
+ if (!parsed.ok) {
43
+ return {
44
+ message: parsed.message,
45
+ ok: false,
46
+ status: response.status,
47
+ };
48
+ }
49
+ if (!response.ok) {
50
+ return {
51
+ json: parsed.json,
52
+ message: `Konseki API returned HTTP ${response.status}.`,
53
+ ok: false,
54
+ status: response.status,
55
+ };
56
+ }
57
+ return {
58
+ json: parsed.json,
59
+ ok: true,
60
+ status: response.status,
61
+ };
62
+ }
63
+ catch (error) {
64
+ const message = error instanceof DOMException && error.name === "AbortError"
65
+ ? "Konseki API request timed out."
66
+ : "Unable to reach Konseki API.";
67
+ return {
68
+ message,
69
+ ok: false,
70
+ };
71
+ }
72
+ finally {
73
+ clearTimeout(timeout);
74
+ }
75
+ }
76
+ }
77
+ async function parseJsonObject(response) {
78
+ try {
79
+ const json = JSON.parse(await responseBodyText(response));
80
+ if (!isJsonObject(json)) {
81
+ return {
82
+ message: "Konseki API returned JSON that is not an object.",
83
+ ok: false,
84
+ };
85
+ }
86
+ return {
87
+ json,
88
+ ok: true,
89
+ };
90
+ }
91
+ catch {
92
+ return {
93
+ message: "Konseki API returned a non-JSON response.",
94
+ ok: false,
95
+ };
96
+ }
97
+ }
98
+ async function responseBodyText(response) {
99
+ const bytes = new Uint8Array(await response.arrayBuffer());
100
+ const decompressed = isGzip(bytes) ? await gunzipAsync(bytes) : bytes;
101
+ return new TextDecoder().decode(decompressed);
102
+ }
103
+ function isGzip(bytes) {
104
+ return bytes.length >= 2 && bytes[0] === 0x1f && bytes[1] === 0x8b;
105
+ }
106
+ function isJsonObject(value) {
107
+ return typeof value === "object" && value !== null && !Array.isArray(value);
108
+ }
@@ -0,0 +1,7 @@
1
+ export type KonsekiMcpConfig = {
2
+ apiKey: string;
3
+ };
4
+ export declare const KONSEKI_API_ORIGIN = "https://api.konseki.io";
5
+ export declare const KONSEKI_REQUEST_TIMEOUT_MS = 30000;
6
+ export declare function loadConfigFromEnv(env?: Record<string, string | undefined>): KonsekiMcpConfig;
7
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,gBAAgB,GAAG;IAC7B,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,eAAO,MAAM,kBAAkB,2BAA2B,CAAC;AAC3D,eAAO,MAAM,0BAA0B,QAAS,CAAC;AAEjD,wBAAgB,iBAAiB,CAAC,GAAG,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAe,GAAG,gBAAgB,CAUzG"}
package/dist/config.js ADDED
@@ -0,0 +1,11 @@
1
+ export const KONSEKI_API_ORIGIN = "https://api.konseki.io";
2
+ export const KONSEKI_REQUEST_TIMEOUT_MS = 30_000;
3
+ export function loadConfigFromEnv(env = process.env) {
4
+ const apiKey = env.KONSEKI_API_KEY?.trim();
5
+ if (!apiKey) {
6
+ throw new Error("KONSEKI_API_KEY is required.");
7
+ }
8
+ return {
9
+ apiKey,
10
+ };
11
+ }
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env node
2
+ export { KonsekiApiClient } from "./client.js";
3
+ export { KONSEKI_API_ORIGIN, KONSEKI_REQUEST_TIMEOUT_MS, loadConfigFromEnv, type KonsekiMcpConfig } from "./config.js";
4
+ export { createKonsekiMcpServer } from "./server.js";
5
+ export { normalizeAnalysisInput } from "./tools.js";
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAOA,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,kBAAkB,EAAE,0BAA0B,EAAE,iBAAiB,EAAE,KAAK,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACvH,OAAO,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AACrD,OAAO,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env node
2
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
+ import { pathToFileURL } from "node:url";
4
+ import { KonsekiApiClient } from "./client.js";
5
+ import { loadConfigFromEnv } from "./config.js";
6
+ import { createKonsekiMcpServer } from "./server.js";
7
+ export { KonsekiApiClient } from "./client.js";
8
+ export { KONSEKI_API_ORIGIN, KONSEKI_REQUEST_TIMEOUT_MS, loadConfigFromEnv } from "./config.js";
9
+ export { createKonsekiMcpServer } from "./server.js";
10
+ export { normalizeAnalysisInput } from "./tools.js";
11
+ async function main() {
12
+ const config = loadConfigFromEnv();
13
+ const client = new KonsekiApiClient(config);
14
+ const server = createKonsekiMcpServer(client);
15
+ await server.connect(new StdioServerTransport());
16
+ }
17
+ if (isCliEntryPoint()) {
18
+ main().catch((error) => {
19
+ const message = error instanceof Error ? error.message : "Unable to start Konseki MCP server.";
20
+ console.error(message);
21
+ process.exit(1);
22
+ });
23
+ }
24
+ function isCliEntryPoint() {
25
+ const entrypoint = process.argv[1];
26
+ return entrypoint ? import.meta.url === pathToFileURL(entrypoint).href : false;
27
+ }
@@ -0,0 +1,4 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { KonsekiApiClient } from "./client.js";
3
+ export declare function createKonsekiMcpServer(client: KonsekiApiClient): McpServer;
4
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAQpD,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,gBAAgB,GAAG,SAAS,CAmC1E"}
package/dist/server.js ADDED
@@ -0,0 +1,22 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { analysisInputSchema, createAnalysisToolHandler, createMetadataToolHandler, createSymbolsToolHandler, } from "./tools.js";
3
+ export function createKonsekiMcpServer(client) {
4
+ const server = new McpServer({
5
+ name: "konseki-mcp",
6
+ version: "1.0.0",
7
+ });
8
+ server.registerTool("get_konseki_metadata", {
9
+ description: "Fetch raw metadata JSON from the Konseki public API.",
10
+ title: "Get Konseki Metadata",
11
+ }, createMetadataToolHandler(client));
12
+ server.registerTool("list_konseki_symbols", {
13
+ description: "Fetch raw supported symbols JSON from the Konseki public API.",
14
+ title: "List Konseki Symbols",
15
+ }, createSymbolsToolHandler(client));
16
+ server.registerTool("get_konseki_analysis", {
17
+ description: "Fetch raw analysis JSON for a symbol, exchange, and lookback from the Konseki public API.",
18
+ inputSchema: analysisInputSchema,
19
+ title: "Get Konseki Analysis",
20
+ }, createAnalysisToolHandler(client));
21
+ return server;
22
+ }
@@ -0,0 +1,25 @@
1
+ import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
2
+ import { z } from "zod";
3
+ import type { ApiJsonObject, KonsekiApiClient, KonsekiApiResult } from "./client.js";
4
+ export declare const analysisInputSchema: {
5
+ exchange: z.ZodString;
6
+ lookback: z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>;
7
+ symbol: z.ZodString;
8
+ };
9
+ export type AnalysisInput = {
10
+ exchange: string;
11
+ lookback: number | string;
12
+ symbol: string;
13
+ };
14
+ export declare function normalizeAnalysisInput(input: AnalysisInput): {
15
+ exchange: string;
16
+ lookback: string;
17
+ symbol: string;
18
+ };
19
+ export declare function jsonToolResult(json: ApiJsonObject): CallToolResult;
20
+ export declare function apiResultToToolResult(result: KonsekiApiResult): CallToolResult;
21
+ export declare function safeErrorResult(message: string): CallToolResult;
22
+ export declare function createMetadataToolHandler(client: KonsekiApiClient): () => Promise<CallToolResult>;
23
+ export declare function createSymbolsToolHandler(client: KonsekiApiClient): () => Promise<CallToolResult>;
24
+ export declare function createAnalysisToolHandler(client: KonsekiApiClient): (input: AnalysisInput) => Promise<CallToolResult>;
25
+ //# 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,cAAc,EAAE,MAAM,oCAAoC,CAAC;AACzE,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAIrF,eAAO,MAAM,mBAAmB;;;;CAI/B,CAAC;AAIF,MAAM,MAAM,aAAa,GAAG;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC;IAC1B,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,aAAa,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAYnH;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,aAAa,GAAG,cAAc,CAUlE;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,gBAAgB,GAAG,cAAc,CAa9E;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,cAAc,CAU/D;AAED,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,gBAAgB,GAAG,MAAM,OAAO,CAAC,cAAc,CAAC,CAEjG;AAED,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,gBAAgB,GAAG,MAAM,OAAO,CAAC,cAAc,CAAC,CAEhG;AAED,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,gBAAgB,GAAG,CAAC,KAAK,EAAE,aAAa,KAAK,OAAO,CAAC,cAAc,CAAC,CAiBrH"}
package/dist/tools.js ADDED
@@ -0,0 +1,75 @@
1
+ import { z } from "zod";
2
+ const supportedLookbacks = new Set(["5", "10", "15", "20", "25", "30", "40", "50"]);
3
+ export const analysisInputSchema = {
4
+ exchange: z.string().trim().min(1).regex(/^[A-Za-z]+$/, "exchange must contain only letters"),
5
+ lookback: z.union([z.string(), z.number()]),
6
+ symbol: z.string().trim().min(1).regex(/^[A-Za-z0-9.]+$/, "symbol must contain only letters, numbers, and dots"),
7
+ };
8
+ const analysisInputObjectSchema = z.object(analysisInputSchema);
9
+ export function normalizeAnalysisInput(input) {
10
+ const lookback = String(input.lookback).trim();
11
+ if (!supportedLookbacks.has(lookback)) {
12
+ throw new Error("Unsupported lookback value.");
13
+ }
14
+ return {
15
+ exchange: input.exchange.trim().toUpperCase(),
16
+ lookback,
17
+ symbol: input.symbol.trim().toUpperCase(),
18
+ };
19
+ }
20
+ export function jsonToolResult(json) {
21
+ return {
22
+ content: [
23
+ {
24
+ text: JSON.stringify(json, null, 2),
25
+ type: "text",
26
+ },
27
+ ],
28
+ structuredContent: json,
29
+ };
30
+ }
31
+ export function apiResultToToolResult(result) {
32
+ if (result.ok) {
33
+ return jsonToolResult(result.json);
34
+ }
35
+ if (result.json) {
36
+ return {
37
+ ...jsonToolResult(result.json),
38
+ isError: true,
39
+ };
40
+ }
41
+ return safeErrorResult(result.message);
42
+ }
43
+ export function safeErrorResult(message) {
44
+ return {
45
+ content: [
46
+ {
47
+ text: message,
48
+ type: "text",
49
+ },
50
+ ],
51
+ isError: true,
52
+ };
53
+ }
54
+ export function createMetadataToolHandler(client) {
55
+ return async () => apiResultToToolResult(await client.getMetadata());
56
+ }
57
+ export function createSymbolsToolHandler(client) {
58
+ return async () => apiResultToToolResult(await client.listSymbols());
59
+ }
60
+ export function createAnalysisToolHandler(client) {
61
+ return async (input) => {
62
+ try {
63
+ const normalizedInput = normalizeAnalysisInput(analysisInputObjectSchema.parse(input));
64
+ return apiResultToToolResult(await client.getAnalysis(normalizedInput));
65
+ }
66
+ catch (error) {
67
+ const message = error instanceof z.ZodError
68
+ ? "Invalid analysis request."
69
+ : error instanceof Error
70
+ ? error.message
71
+ : "Invalid analysis request.";
72
+ return safeErrorResult(message);
73
+ }
74
+ };
75
+ }
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@konseki/mcp",
3
+ "version": "1.0.0",
4
+ "description": "Official Konseki MCP server: a direct AI-agent wrapper around the Konseki public API for historical market context.",
5
+ "private": false,
6
+ "type": "module",
7
+ "bin": {
8
+ "konseki-mcp": "dist/index.js"
9
+ },
10
+ "files": [
11
+ "dist",
12
+ "LICENSE",
13
+ "README.md",
14
+ "SECURITY.md"
15
+ ],
16
+ "scripts": {
17
+ "build": "tsc -p tsconfig.json",
18
+ "typecheck": "tsc --noEmit -p tsconfig.json && tsc --noEmit -p tsconfig.test.json",
19
+ "test": "vitest run",
20
+ "lint": "npm run typecheck",
21
+ "prepublishOnly": "npm run typecheck && npm test && npm run build"
22
+ },
23
+ "keywords": [
24
+ "konseki",
25
+ "mcp",
26
+ "model-context-protocol",
27
+ "trading",
28
+ "market-data",
29
+ "historical-context"
30
+ ],
31
+ "author": "Konseki",
32
+ "license": "MIT",
33
+ "engines": {
34
+ "node": ">=20"
35
+ },
36
+ "dependencies": {
37
+ "@modelcontextprotocol/sdk": "^1.29.0",
38
+ "zod": "^4.4.3"
39
+ },
40
+ "devDependencies": {
41
+ "@types/node": "^20.0.0",
42
+ "typescript": "^6.0.3",
43
+ "vitest": "^4.1.9"
44
+ }
45
+ }