@studiometa/forge-mcp 0.0.1 → 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/README.md +102 -0
- package/dist/auth.d.ts +15 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +18 -0
- package/dist/auth.js.map +1 -0
- package/dist/errors.d.ts +36 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/formatters.d.ts +179 -0
- package/dist/formatters.d.ts.map +1 -0
- package/dist/handlers/backups.d.ts +2 -0
- package/dist/handlers/backups.d.ts.map +1 -0
- package/dist/handlers/certificates.d.ts +2 -0
- package/dist/handlers/certificates.d.ts.map +1 -0
- package/dist/handlers/commands.d.ts +2 -0
- package/dist/handlers/commands.d.ts.map +1 -0
- package/dist/handlers/daemons.d.ts +2 -0
- package/dist/handlers/daemons.d.ts.map +1 -0
- package/dist/handlers/database-users.d.ts +2 -0
- package/dist/handlers/database-users.d.ts.map +1 -0
- package/dist/handlers/databases.d.ts +2 -0
- package/dist/handlers/databases.d.ts.map +1 -0
- package/dist/handlers/deployments.d.ts +9 -0
- package/dist/handlers/deployments.d.ts.map +1 -0
- package/dist/handlers/env.d.ts +2 -0
- package/dist/handlers/env.d.ts.map +1 -0
- package/dist/handlers/factory.d.ts +71 -0
- package/dist/handlers/factory.d.ts.map +1 -0
- package/dist/handlers/firewall-rules.d.ts +2 -0
- package/dist/handlers/firewall-rules.d.ts.map +1 -0
- package/dist/handlers/help.d.ts +16 -0
- package/dist/handlers/help.d.ts.map +1 -0
- package/dist/handlers/index.d.ts +18 -0
- package/dist/handlers/index.d.ts.map +1 -0
- package/dist/handlers/monitors.d.ts +2 -0
- package/dist/handlers/monitors.d.ts.map +1 -0
- package/dist/handlers/nginx-config.d.ts +2 -0
- package/dist/handlers/nginx-config.d.ts.map +1 -0
- package/dist/handlers/nginx-templates.d.ts +2 -0
- package/dist/handlers/nginx-templates.d.ts.map +1 -0
- package/dist/handlers/recipes.d.ts +2 -0
- package/dist/handlers/recipes.d.ts.map +1 -0
- package/dist/handlers/redirect-rules.d.ts +2 -0
- package/dist/handlers/redirect-rules.d.ts.map +1 -0
- package/dist/handlers/scheduled-jobs.d.ts +2 -0
- package/dist/handlers/scheduled-jobs.d.ts.map +1 -0
- package/dist/handlers/schema.d.ts +16 -0
- package/dist/handlers/schema.d.ts.map +1 -0
- package/dist/handlers/security-rules.d.ts +2 -0
- package/dist/handlers/security-rules.d.ts.map +1 -0
- package/dist/handlers/servers.d.ts +2 -0
- package/dist/handlers/servers.d.ts.map +1 -0
- package/dist/handlers/sites.d.ts +2 -0
- package/dist/handlers/sites.d.ts.map +1 -0
- package/dist/handlers/ssh-keys.d.ts +2 -0
- package/dist/handlers/ssh-keys.d.ts.map +1 -0
- package/dist/handlers/types.d.ts +33 -0
- package/dist/handlers/types.d.ts.map +1 -0
- package/dist/handlers/user.d.ts +2 -0
- package/dist/handlers/user.d.ts.map +1 -0
- package/dist/handlers/utils.d.ts +26 -0
- package/dist/handlers/utils.d.ts.map +1 -0
- package/dist/hints.d.ts +60 -0
- package/dist/hints.d.ts.map +1 -0
- package/dist/http-BJUKoZdb.js +253 -0
- package/dist/http-BJUKoZdb.js.map +1 -0
- package/dist/http.d.ts +47 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +3 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +127 -0
- package/dist/index.js.map +1 -0
- package/dist/instructions.d.ts +11 -0
- package/dist/instructions.d.ts.map +1 -0
- package/dist/server.d.ts +26 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +67 -0
- package/dist/server.js.map +1 -0
- package/dist/sessions.d.ts +64 -0
- package/dist/sessions.d.ts.map +1 -0
- package/dist/stdio.d.ts +21 -0
- package/dist/stdio.d.ts.map +1 -0
- package/dist/tools.d.ts +22 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/version-Cw8OGt4r.js +3250 -0
- package/dist/version-Cw8OGt4r.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/package.json +53 -1
- package/skills/SKILL.md +189 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/handlers/schema.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAsU7C;;GAEG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,UAAU,CAWzD;AAED;;GAEG;AACH,wBAAgB,oBAAoB,IAAI,UAAU,CAWjD"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"security-rules.d.ts","sourceRoot":"","sources":["../../src/handlers/security-rules.ts"],"names":[],"mappings":"AAYA,eAAO,MAAM,mBAAmB,+IA8B9B,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"servers.d.ts","sourceRoot":"","sources":["../../src/handlers/servers.ts"],"names":[],"mappings":"AAcA,eAAO,MAAM,aAAa,+IAwDxB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sites.d.ts","sourceRoot":"","sources":["../../src/handlers/sites.ts"],"names":[],"mappings":"AAQA,eAAO,MAAM,WAAW,+IAsDtB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ssh-keys.d.ts","sourceRoot":"","sources":["../../src/handlers/ssh-keys.ts"],"names":[],"mappings":"AAQA,eAAO,MAAM,aAAa,+IAkCxB,CAAC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { ExecutorContext } from "@studiometa/forge-core";
|
|
2
|
+
/**
|
|
3
|
+
* Result from MCP tool handlers.
|
|
4
|
+
*/
|
|
5
|
+
export interface ToolResult {
|
|
6
|
+
content: Array<{
|
|
7
|
+
type: "text";
|
|
8
|
+
text: string;
|
|
9
|
+
}>;
|
|
10
|
+
isError?: boolean;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Handler context — wraps ExecutorContext with MCP-specific options.
|
|
14
|
+
*/
|
|
15
|
+
export interface HandlerContext {
|
|
16
|
+
executorContext: ExecutorContext;
|
|
17
|
+
compact: boolean;
|
|
18
|
+
/** Whether to include contextual hints in get responses (default: false) */
|
|
19
|
+
includeHints?: boolean;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Common args present in all tool calls.
|
|
23
|
+
*/
|
|
24
|
+
export interface CommonArgs {
|
|
25
|
+
resource: string;
|
|
26
|
+
action: string;
|
|
27
|
+
id?: string;
|
|
28
|
+
server_id?: string;
|
|
29
|
+
site_id?: string;
|
|
30
|
+
compact?: boolean;
|
|
31
|
+
[key: string]: unknown;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/handlers/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAE9D;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC/C,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,eAAe,EAAE,eAAe,CAAC;IACjC,OAAO,EAAE,OAAO,CAAC;IACjB,4EAA4E;IAC5E,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"user.d.ts","sourceRoot":"","sources":["../../src/handlers/user.ts"],"names":[],"mappings":"AAOA,eAAO,MAAM,UAAU,+IASrB,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { ToolResult } from "./types.ts";
|
|
2
|
+
import { UserInputError } from "../errors.ts";
|
|
3
|
+
/**
|
|
4
|
+
* Create a successful JSON result.
|
|
5
|
+
* Accepts a string or an object (which will be JSON-serialized).
|
|
6
|
+
*/
|
|
7
|
+
export declare function jsonResult(data: string | Record<string, unknown> | unknown): ToolResult;
|
|
8
|
+
/**
|
|
9
|
+
* Validate an ID-like value (must be alphanumeric/dashes only).
|
|
10
|
+
* Prevents path traversal via `../` in URL segments.
|
|
11
|
+
*
|
|
12
|
+
* @returns true if the value is safe, false otherwise.
|
|
13
|
+
*/
|
|
14
|
+
export declare function sanitizeId(value: string): boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Create an error result.
|
|
17
|
+
*/
|
|
18
|
+
export declare function errorResult(message: string): ToolResult;
|
|
19
|
+
/**
|
|
20
|
+
* Create an input error result from a UserInputError or a plain message with an optional suggestion.
|
|
21
|
+
*
|
|
22
|
+
* When a UserInputError is passed, its formatted message (including hints) is used.
|
|
23
|
+
* When a plain string is passed, the optional suggestion is appended.
|
|
24
|
+
*/
|
|
25
|
+
export declare function inputErrorResult(error: UserInputError | string, suggestion?: string): ToolResult;
|
|
26
|
+
//# sourceMappingURL=utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/handlers/utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAE9C;;;GAGG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,GAAG,UAAU,CAKvF;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAEjD;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU,CAKvD;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,cAAc,GAAG,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,UAAU,CAWhG"}
|
package/dist/hints.d.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Contextual hints for AI agents.
|
|
3
|
+
*
|
|
4
|
+
* After get/list actions, suggests related resources and common next steps
|
|
5
|
+
* to help agents discover what's available.
|
|
6
|
+
*/
|
|
7
|
+
interface ResourceHint {
|
|
8
|
+
resource: string;
|
|
9
|
+
description: string;
|
|
10
|
+
example: Record<string, unknown>;
|
|
11
|
+
}
|
|
12
|
+
export interface ContextualHints {
|
|
13
|
+
related_resources?: ResourceHint[];
|
|
14
|
+
common_actions?: {
|
|
15
|
+
action: string;
|
|
16
|
+
example: Record<string, unknown>;
|
|
17
|
+
}[];
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Hints after getting a server.
|
|
21
|
+
*/
|
|
22
|
+
export declare function getServerHints(serverId: string): ContextualHints;
|
|
23
|
+
/**
|
|
24
|
+
* Hints after getting a site.
|
|
25
|
+
*/
|
|
26
|
+
export declare function getSiteHints(serverId: string, siteId: string): ContextualHints;
|
|
27
|
+
/**
|
|
28
|
+
* Hints after getting a database.
|
|
29
|
+
*/
|
|
30
|
+
export declare function getDatabaseHints(serverId: string, databaseId: string): ContextualHints;
|
|
31
|
+
/**
|
|
32
|
+
* Hints after getting a database user.
|
|
33
|
+
*/
|
|
34
|
+
export declare function getDatabaseUserHints(serverId: string, userId: string): ContextualHints;
|
|
35
|
+
/**
|
|
36
|
+
* Hints after getting a daemon.
|
|
37
|
+
*/
|
|
38
|
+
export declare function getDaemonHints(serverId: string, daemonId: string): ContextualHints;
|
|
39
|
+
/**
|
|
40
|
+
* Hints after getting a certificate.
|
|
41
|
+
*/
|
|
42
|
+
export declare function getCertificateHints(serverId: string, siteId: string, certificateId: string): ContextualHints;
|
|
43
|
+
/**
|
|
44
|
+
* Hints after getting a firewall rule.
|
|
45
|
+
*/
|
|
46
|
+
export declare function getFirewallRuleHints(serverId: string, ruleId: string): ContextualHints;
|
|
47
|
+
/**
|
|
48
|
+
* Hints after getting an SSH key.
|
|
49
|
+
*/
|
|
50
|
+
export declare function getSshKeyHints(serverId: string, keyId: string): ContextualHints;
|
|
51
|
+
/**
|
|
52
|
+
* Hints after getting a recipe.
|
|
53
|
+
*/
|
|
54
|
+
export declare function getRecipeHints(recipeId: string): ContextualHints;
|
|
55
|
+
/**
|
|
56
|
+
* Hints after getting an nginx template.
|
|
57
|
+
*/
|
|
58
|
+
export declare function getNginxTemplateHints(serverId: string, templateId: string): ContextualHints;
|
|
59
|
+
export {};
|
|
60
|
+
//# sourceMappingURL=hints.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hints.d.ts","sourceRoot":"","sources":["../src/hints.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,UAAU,YAAY;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC;AAED,MAAM,WAAW,eAAe;IAC9B,iBAAiB,CAAC,EAAE,YAAY,EAAE,CAAC;IACnC,cAAc,CAAC,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,EAAE,CAAC;CACzE;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,eAAe,CAoChE;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,eAAe,CAoC9E;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,eAAe,CAgBtF;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,eAAe,CA0BtF;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,eAAe,CAoBlF;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,aAAa,EAAE,MAAM,GACpB,eAAe,CAqCjB;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,eAAe,CAqBtF;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,eAAe,CAgB/E;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,eAAe,CAoBhE;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,eAAe,CA+B3F"}
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import { a as INSTRUCTIONS, i as executeToolWithCredentials, r as TOOLS, t as VERSION } from "./version-Cw8OGt4r.js";
|
|
2
|
+
import { parseAuthHeader } from "./auth.js";
|
|
3
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
4
|
+
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
5
|
+
import { randomUUID } from "node:crypto";
|
|
6
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
7
|
+
import { createApp, defineEventHandler } from "h3";
|
|
8
|
+
var DEFAULT_TTL = 1800 * 1e3;
|
|
9
|
+
var DEFAULT_SWEEP_INTERVAL = 60 * 1e3;
|
|
10
|
+
var SessionManager = class {
|
|
11
|
+
sessions = /* @__PURE__ */ new Map();
|
|
12
|
+
sweepTimer;
|
|
13
|
+
ttl;
|
|
14
|
+
constructor(options) {
|
|
15
|
+
this.ttl = options?.ttl ?? DEFAULT_TTL;
|
|
16
|
+
if (this.ttl > 0) {
|
|
17
|
+
const interval = options?.sweepInterval ?? DEFAULT_SWEEP_INTERVAL;
|
|
18
|
+
this.sweepTimer = setInterval(() => {
|
|
19
|
+
this.sweep();
|
|
20
|
+
}, interval);
|
|
21
|
+
this.sweepTimer.unref();
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Register a session after its ID has been assigned by the transport.
|
|
26
|
+
*/
|
|
27
|
+
register(transport, server) {
|
|
28
|
+
const sessionId = transport.sessionId;
|
|
29
|
+
if (sessionId) {
|
|
30
|
+
const now = Date.now();
|
|
31
|
+
this.sessions.set(sessionId, {
|
|
32
|
+
transport,
|
|
33
|
+
server,
|
|
34
|
+
createdAt: now,
|
|
35
|
+
lastActiveAt: now
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Look up a session by its ID and refresh its activity timestamp.
|
|
41
|
+
*/
|
|
42
|
+
get(sessionId) {
|
|
43
|
+
const session = this.sessions.get(sessionId);
|
|
44
|
+
if (session) session.lastActiveAt = Date.now();
|
|
45
|
+
return session;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Remove a session and close its transport + server.
|
|
49
|
+
*/
|
|
50
|
+
async remove(sessionId) {
|
|
51
|
+
const session = this.sessions.get(sessionId);
|
|
52
|
+
if (session) {
|
|
53
|
+
this.sessions.delete(sessionId);
|
|
54
|
+
await session.transport.close();
|
|
55
|
+
await session.server.close();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Get the number of active sessions.
|
|
60
|
+
*/
|
|
61
|
+
get size() {
|
|
62
|
+
return this.sessions.size;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Sweep expired sessions. Called automatically by the sweep timer.
|
|
66
|
+
* Returns the number of sessions reaped.
|
|
67
|
+
*/
|
|
68
|
+
sweep() {
|
|
69
|
+
if (this.ttl <= 0) return 0;
|
|
70
|
+
const now = Date.now();
|
|
71
|
+
const expired = [];
|
|
72
|
+
for (const [id, session] of this.sessions) if (now - session.lastActiveAt > this.ttl) expired.push(id);
|
|
73
|
+
for (const id of expired)
|
|
74
|
+
/* v8 ignore start */
|
|
75
|
+
this.remove(id).catch(() => {});
|
|
76
|
+
return expired.length;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Close all sessions, stop the sweep timer, and clean up.
|
|
80
|
+
*/
|
|
81
|
+
async closeAll() {
|
|
82
|
+
if (this.sweepTimer) {
|
|
83
|
+
clearInterval(this.sweepTimer);
|
|
84
|
+
this.sweepTimer = void 0;
|
|
85
|
+
}
|
|
86
|
+
const promises = [];
|
|
87
|
+
for (const [, session] of this.sessions) {
|
|
88
|
+
promises.push(session.transport.close());
|
|
89
|
+
promises.push(session.server.close());
|
|
90
|
+
}
|
|
91
|
+
await Promise.all(promises);
|
|
92
|
+
this.sessions.clear();
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
/**
|
|
96
|
+
* Streamable HTTP transport for Forge MCP Server
|
|
97
|
+
*
|
|
98
|
+
* Implements the official MCP Streamable HTTP transport specification (2025-03-26)
|
|
99
|
+
* using the SDK's StreamableHTTPServerTransport.
|
|
100
|
+
*
|
|
101
|
+
* Architecture:
|
|
102
|
+
* - Stateful mode with per-session transport+server pairs (multi-tenant)
|
|
103
|
+
* - Auth via Bearer token → authInfo.token → handler extra.authInfo
|
|
104
|
+
* - Session manager (injected) maps session IDs to transport+server instances
|
|
105
|
+
* - Health/status endpoints handled by h3, MCP endpoint by the SDK transport
|
|
106
|
+
*/
|
|
107
|
+
/**
|
|
108
|
+
* Create a configured MCP Server instance for HTTP transport.
|
|
109
|
+
*
|
|
110
|
+
* Unlike stdio, HTTP mode does NOT include forge_configure/forge_get_config
|
|
111
|
+
* because credentials come from the Authorization header per-request.
|
|
112
|
+
*/
|
|
113
|
+
function createMcpServer() {
|
|
114
|
+
const server = new Server({
|
|
115
|
+
name: "forge-mcp",
|
|
116
|
+
version: VERSION
|
|
117
|
+
}, {
|
|
118
|
+
capabilities: { tools: {} },
|
|
119
|
+
instructions: INSTRUCTIONS
|
|
120
|
+
});
|
|
121
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
122
|
+
return { tools: TOOLS };
|
|
123
|
+
});
|
|
124
|
+
server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
|
|
125
|
+
const { name, arguments: args } = request.params;
|
|
126
|
+
const token = extra.authInfo?.token;
|
|
127
|
+
/* v8 ignore start */
|
|
128
|
+
if (!token) return {
|
|
129
|
+
content: [{
|
|
130
|
+
type: "text",
|
|
131
|
+
text: "Error: Authentication required. No token found in request."
|
|
132
|
+
}],
|
|
133
|
+
isError: true
|
|
134
|
+
};
|
|
135
|
+
/* v8 ignore stop */
|
|
136
|
+
try {
|
|
137
|
+
return await executeToolWithCredentials(
|
|
138
|
+
name,
|
|
139
|
+
/* v8 ignore next */
|
|
140
|
+
args ?? {},
|
|
141
|
+
{ apiToken: token }
|
|
142
|
+
);
|
|
143
|
+
} catch (error) {
|
|
144
|
+
/* v8 ignore stop */
|
|
145
|
+
return {
|
|
146
|
+
content: [{
|
|
147
|
+
type: "text",
|
|
148
|
+
text: `Error: ${error instanceof Error ? error.message : String(error)}`
|
|
149
|
+
}],
|
|
150
|
+
isError: true
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
return server;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Handle an MCP request using the Streamable HTTP transport.
|
|
158
|
+
*
|
|
159
|
+
* Routes requests based on whether they have a session ID:
|
|
160
|
+
* - No session ID + initialize request → create new session
|
|
161
|
+
* - Has session ID → route to existing session's transport
|
|
162
|
+
*
|
|
163
|
+
* @param req - Node.js IncomingMessage
|
|
164
|
+
* @param res - Node.js ServerResponse
|
|
165
|
+
* @param sessions - Session manager instance (injected)
|
|
166
|
+
*/
|
|
167
|
+
async function handleMcpRequest(req, res, sessions) {
|
|
168
|
+
const authHeader = req.headers.authorization;
|
|
169
|
+
const credentials = parseAuthHeader(authHeader);
|
|
170
|
+
if (!credentials) {
|
|
171
|
+
res.writeHead(401, { "Content-Type": "application/json" });
|
|
172
|
+
res.end(JSON.stringify({
|
|
173
|
+
jsonrpc: "2.0",
|
|
174
|
+
error: {
|
|
175
|
+
code: -32001,
|
|
176
|
+
message: "Authentication required. Provide a Bearer token with your Forge API token."
|
|
177
|
+
},
|
|
178
|
+
id: null
|
|
179
|
+
}));
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
const authenticatedReq = req;
|
|
183
|
+
authenticatedReq.auth = {
|
|
184
|
+
token: credentials.apiToken,
|
|
185
|
+
clientId: "forge-http-client",
|
|
186
|
+
scopes: []
|
|
187
|
+
};
|
|
188
|
+
const sessionId = req.headers["mcp-session-id"];
|
|
189
|
+
if (sessionId) {
|
|
190
|
+
const session = sessions.get(sessionId);
|
|
191
|
+
if (!session) {
|
|
192
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
193
|
+
res.end(JSON.stringify({
|
|
194
|
+
jsonrpc: "2.0",
|
|
195
|
+
error: {
|
|
196
|
+
code: -32e3,
|
|
197
|
+
message: "Session not found. The session may have expired or been terminated."
|
|
198
|
+
},
|
|
199
|
+
id: null
|
|
200
|
+
}));
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
await session.transport.handleRequest(authenticatedReq, res);
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: () => randomUUID() });
|
|
207
|
+
const server = createMcpServer();
|
|
208
|
+
await server.connect(transport);
|
|
209
|
+
transport.onclose = () => {
|
|
210
|
+
const sid = transport.sessionId;
|
|
211
|
+
/* v8 ignore start */
|
|
212
|
+
if (sid) sessions.remove(sid).catch(() => {});
|
|
213
|
+
/* v8 ignore stop */
|
|
214
|
+
};
|
|
215
|
+
await transport.handleRequest(authenticatedReq, res);
|
|
216
|
+
/* v8 ignore start */
|
|
217
|
+
if (transport.sessionId) sessions.register(transport, server);
|
|
218
|
+
else {
|
|
219
|
+
await transport.close();
|
|
220
|
+
await server.close();
|
|
221
|
+
}
|
|
222
|
+
/* v8 ignore stop */
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Create a request handler bound to a SessionManager instance.
|
|
226
|
+
* Convenience factory for server.ts.
|
|
227
|
+
*/
|
|
228
|
+
function createMcpRequestHandler(sessions) {
|
|
229
|
+
/* v8 ignore start */
|
|
230
|
+
return (req, res) => handleMcpRequest(req, res, sessions);
|
|
231
|
+
/* v8 ignore stop */
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Create h3 app for health check and service info endpoints.
|
|
235
|
+
* The MCP endpoint is handled separately by handleMcpRequest.
|
|
236
|
+
*/
|
|
237
|
+
function createHealthApp() {
|
|
238
|
+
const app = createApp();
|
|
239
|
+
app.get("/", defineEventHandler(() => {
|
|
240
|
+
return {
|
|
241
|
+
status: "ok",
|
|
242
|
+
service: "forge-mcp",
|
|
243
|
+
version: VERSION
|
|
244
|
+
};
|
|
245
|
+
}));
|
|
246
|
+
app.get("/health", defineEventHandler(() => {
|
|
247
|
+
return { status: "ok" };
|
|
248
|
+
}));
|
|
249
|
+
return app;
|
|
250
|
+
}
|
|
251
|
+
export { SessionManager as a, handleMcpRequest as i, createMcpRequestHandler as n, createMcpServer as r, createHealthApp as t };
|
|
252
|
+
|
|
253
|
+
//# sourceMappingURL=http-BJUKoZdb.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http-BJUKoZdb.js","names":[],"sources":["../src/sessions.ts","../src/http.ts"],"sourcesContent":["/**\n * Session manager for multi-tenant Streamable HTTP transport.\n *\n * Each MCP client session gets its own transport + server pair.\n * Sessions are identified by UUID and tracked in a Map.\n *\n * Supports automatic TTL-based cleanup of idle sessions to prevent\n * memory leaks from abandoned clients.\n */\n\nimport type { Server } from \"@modelcontextprotocol/sdk/server/index.js\";\nimport type { StreamableHTTPServerTransport } from \"@modelcontextprotocol/sdk/server/streamableHttp.js\";\n\n/**\n * A managed session: transport + MCP server pair.\n */\nexport interface ManagedSession {\n transport: StreamableHTTPServerTransport;\n server: Server;\n createdAt: number;\n lastActiveAt: number;\n}\n\nexport interface SessionManagerOptions {\n /**\n * Maximum idle time in milliseconds before a session is reaped.\n * Default: 30 minutes. Set to 0 to disable automatic cleanup.\n */\n ttl?: number;\n\n /**\n * How often to check for expired sessions, in milliseconds.\n * Default: 60 seconds.\n */\n sweepInterval?: number;\n}\n\nconst DEFAULT_TTL = 30 * 60 * 1000; // 30 minutes\nconst DEFAULT_SWEEP_INTERVAL = 60 * 1000; // 60 seconds\n\nexport class SessionManager {\n private sessions = new Map<string, ManagedSession>();\n private sweepTimer: ReturnType<typeof setInterval> | undefined;\n private readonly ttl: number;\n\n constructor(options?: SessionManagerOptions) {\n this.ttl = options?.ttl ?? DEFAULT_TTL;\n\n if (this.ttl > 0) {\n const interval = options?.sweepInterval ?? DEFAULT_SWEEP_INTERVAL;\n this.sweepTimer = setInterval(() => {\n this.sweep();\n }, interval);\n // Don't keep the process alive just for the sweep timer\n this.sweepTimer.unref();\n }\n }\n\n /**\n * Register a session after its ID has been assigned by the transport.\n */\n register(transport: StreamableHTTPServerTransport, server: Server): void {\n const sessionId = transport.sessionId;\n if (sessionId) {\n const now = Date.now();\n this.sessions.set(sessionId, {\n transport,\n server,\n createdAt: now,\n lastActiveAt: now,\n });\n }\n }\n\n /**\n * Look up a session by its ID and refresh its activity timestamp.\n */\n get(sessionId: string): ManagedSession | undefined {\n const session = this.sessions.get(sessionId);\n if (session) {\n session.lastActiveAt = Date.now();\n }\n return session;\n }\n\n /**\n * Remove a session and close its transport + server.\n */\n async remove(sessionId: string): Promise<void> {\n const session = this.sessions.get(sessionId);\n if (session) {\n this.sessions.delete(sessionId);\n await session.transport.close();\n await session.server.close();\n }\n }\n\n /**\n * Get the number of active sessions.\n */\n get size(): number {\n return this.sessions.size;\n }\n\n /**\n * Sweep expired sessions. Called automatically by the sweep timer.\n * Returns the number of sessions reaped.\n */\n sweep(): number {\n if (this.ttl <= 0) return 0;\n\n const now = Date.now();\n const expired: string[] = [];\n\n for (const [id, session] of this.sessions) {\n if (now - session.lastActiveAt > this.ttl) {\n expired.push(id);\n }\n }\n\n for (const id of expired) {\n // Fire-and-forget cleanup — don't block the sweep\n /* v8 ignore start */\n this.remove(id).catch(() => {});\n /* v8 ignore stop */\n }\n\n return expired.length;\n }\n\n /**\n * Close all sessions, stop the sweep timer, and clean up.\n */\n async closeAll(): Promise<void> {\n if (this.sweepTimer) {\n clearInterval(this.sweepTimer);\n this.sweepTimer = undefined;\n }\n\n const promises: Promise<void>[] = [];\n for (const [, session] of this.sessions) {\n promises.push(session.transport.close());\n promises.push(session.server.close());\n }\n await Promise.all(promises);\n this.sessions.clear();\n }\n}\n","/**\n * Streamable HTTP transport for Forge MCP Server\n *\n * Implements the official MCP Streamable HTTP transport specification (2025-03-26)\n * using the SDK's StreamableHTTPServerTransport.\n *\n * Architecture:\n * - Stateful mode with per-session transport+server pairs (multi-tenant)\n * - Auth via Bearer token → authInfo.token → handler extra.authInfo\n * - Session manager (injected) maps session IDs to transport+server instances\n * - Health/status endpoints handled by h3, MCP endpoint by the SDK transport\n */\n\nimport { randomUUID } from \"node:crypto\";\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\n\nimport { Server } from \"@modelcontextprotocol/sdk/server/index.js\";\nimport { StreamableHTTPServerTransport } from \"@modelcontextprotocol/sdk/server/streamableHttp.js\";\nimport { CallToolRequestSchema, ListToolsRequestSchema } from \"@modelcontextprotocol/sdk/types.js\";\nimport { createApp, defineEventHandler, type H3 } from \"h3\";\n\nimport { parseAuthHeader } from \"./auth.ts\";\nimport { executeToolWithCredentials } from \"./handlers/index.ts\";\nimport { INSTRUCTIONS } from \"./instructions.ts\";\nimport { SessionManager } from \"./sessions.ts\";\nimport { TOOLS } from \"./tools.ts\";\nimport { VERSION } from \"./version.ts\";\n\nexport { SessionManager } from \"./sessions.ts\";\n\n/**\n * Create a configured MCP Server instance for HTTP transport.\n *\n * Unlike stdio, HTTP mode does NOT include forge_configure/forge_get_config\n * because credentials come from the Authorization header per-request.\n */\nexport function createMcpServer(): Server {\n const server = new Server(\n {\n name: \"forge-mcp\",\n version: VERSION,\n },\n {\n capabilities: {\n tools: {},\n },\n instructions: INSTRUCTIONS,\n },\n );\n\n server.setRequestHandler(ListToolsRequestSchema, async () => {\n return { tools: TOOLS };\n });\n\n server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {\n const { name, arguments: args } = request.params;\n const token = extra.authInfo?.token;\n\n /* v8 ignore start */\n if (!token) {\n return {\n content: [\n {\n type: \"text\" as const,\n text: \"Error: Authentication required. No token found in request.\",\n },\n ],\n isError: true,\n };\n }\n /* v8 ignore stop */\n\n try {\n const result = await executeToolWithCredentials(\n name,\n /* v8 ignore next */ (args as Record<string, unknown>) ?? {},\n { apiToken: token },\n );\n return result as unknown as Record<string, unknown>;\n } catch (error) {\n /* v8 ignore start */\n const message = error instanceof Error ? error.message : String(error);\n /* v8 ignore stop */\n return {\n content: [{ type: \"text\" as const, text: `Error: ${message}` }],\n isError: true,\n };\n }\n });\n\n return server;\n}\n\n/**\n * Handle an MCP request using the Streamable HTTP transport.\n *\n * Routes requests based on whether they have a session ID:\n * - No session ID + initialize request → create new session\n * - Has session ID → route to existing session's transport\n *\n * @param req - Node.js IncomingMessage\n * @param res - Node.js ServerResponse\n * @param sessions - Session manager instance (injected)\n */\nexport async function handleMcpRequest(\n req: IncomingMessage,\n res: ServerResponse,\n sessions: SessionManager,\n): Promise<void> {\n // Extract and validate auth\n const authHeader = req.headers.authorization;\n const credentials = parseAuthHeader(authHeader);\n\n if (!credentials) {\n res.writeHead(401, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n jsonrpc: \"2.0\",\n error: {\n code: -32001,\n message: \"Authentication required. Provide a Bearer token with your Forge API token.\",\n },\n id: null,\n }),\n );\n return;\n }\n\n // Inject auth info for the SDK transport\n const authenticatedReq = req as IncomingMessage & {\n auth?: { token: string; clientId: string; scopes: string[] };\n };\n authenticatedReq.auth = {\n token: credentials.apiToken,\n clientId: \"forge-http-client\",\n scopes: [],\n };\n\n const sessionId = req.headers[\"mcp-session-id\"] as string | undefined;\n\n if (sessionId) {\n // Existing session — route to its transport\n const session = sessions.get(sessionId);\n if (!session) {\n res.writeHead(404, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n jsonrpc: \"2.0\",\n error: {\n code: -32000,\n message: \"Session not found. The session may have expired or been terminated.\",\n },\n id: null,\n }),\n );\n return;\n }\n\n await session.transport.handleRequest(authenticatedReq, res);\n return;\n }\n\n // No session ID — this should be an initialize request.\n // Create a new transport + server pair.\n const transport = new StreamableHTTPServerTransport({\n sessionIdGenerator: () => randomUUID(),\n });\n\n const server = createMcpServer();\n await server.connect(transport);\n\n // Set up cleanup on close\n transport.onclose = () => {\n const sid = transport.sessionId;\n /* v8 ignore start */\n if (sid) {\n sessions.remove(sid).catch(() => {\n // Ignore cleanup errors\n });\n }\n /* v8 ignore stop */\n };\n\n // Handle the request (this will set transport.sessionId during initialize)\n await transport.handleRequest(authenticatedReq, res);\n\n // After handling, register the session if the transport got a session ID\n /* v8 ignore start */\n if (transport.sessionId) {\n sessions.register(transport, server);\n } else {\n // No session was created (e.g., invalid request) — clean up\n await transport.close();\n await server.close();\n }\n /* v8 ignore stop */\n}\n\n/**\n * Create a request handler bound to a SessionManager instance.\n * Convenience factory for server.ts.\n */\nexport function createMcpRequestHandler(\n sessions: SessionManager,\n): (req: IncomingMessage, res: ServerResponse) => Promise<void> {\n /* v8 ignore start */\n return (req, res) => handleMcpRequest(req, res, sessions);\n /* v8 ignore stop */\n}\n\n/**\n * Create h3 app for health check and service info endpoints.\n * The MCP endpoint is handled separately by handleMcpRequest.\n */\nexport function createHealthApp(): H3 {\n const app = createApp();\n\n app.get(\n \"/\",\n defineEventHandler(() => {\n return { status: \"ok\", service: \"forge-mcp\", version: VERSION };\n }),\n );\n\n app.get(\n \"/health\",\n defineEventHandler(() => {\n return { status: \"ok\" };\n }),\n );\n\n return app;\n}\n"],"mappings":"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"}
|
package/dist/http.d.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Streamable HTTP transport for Forge MCP Server
|
|
3
|
+
*
|
|
4
|
+
* Implements the official MCP Streamable HTTP transport specification (2025-03-26)
|
|
5
|
+
* using the SDK's StreamableHTTPServerTransport.
|
|
6
|
+
*
|
|
7
|
+
* Architecture:
|
|
8
|
+
* - Stateful mode with per-session transport+server pairs (multi-tenant)
|
|
9
|
+
* - Auth via Bearer token → authInfo.token → handler extra.authInfo
|
|
10
|
+
* - Session manager (injected) maps session IDs to transport+server instances
|
|
11
|
+
* - Health/status endpoints handled by h3, MCP endpoint by the SDK transport
|
|
12
|
+
*/
|
|
13
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
14
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
15
|
+
import { type H3 } from "h3";
|
|
16
|
+
import { SessionManager } from "./sessions.ts";
|
|
17
|
+
export { SessionManager } from "./sessions.ts";
|
|
18
|
+
/**
|
|
19
|
+
* Create a configured MCP Server instance for HTTP transport.
|
|
20
|
+
*
|
|
21
|
+
* Unlike stdio, HTTP mode does NOT include forge_configure/forge_get_config
|
|
22
|
+
* because credentials come from the Authorization header per-request.
|
|
23
|
+
*/
|
|
24
|
+
export declare function createMcpServer(): Server;
|
|
25
|
+
/**
|
|
26
|
+
* Handle an MCP request using the Streamable HTTP transport.
|
|
27
|
+
*
|
|
28
|
+
* Routes requests based on whether they have a session ID:
|
|
29
|
+
* - No session ID + initialize request → create new session
|
|
30
|
+
* - Has session ID → route to existing session's transport
|
|
31
|
+
*
|
|
32
|
+
* @param req - Node.js IncomingMessage
|
|
33
|
+
* @param res - Node.js ServerResponse
|
|
34
|
+
* @param sessions - Session manager instance (injected)
|
|
35
|
+
*/
|
|
36
|
+
export declare function handleMcpRequest(req: IncomingMessage, res: ServerResponse, sessions: SessionManager): Promise<void>;
|
|
37
|
+
/**
|
|
38
|
+
* Create a request handler bound to a SessionManager instance.
|
|
39
|
+
* Convenience factory for server.ts.
|
|
40
|
+
*/
|
|
41
|
+
export declare function createMcpRequestHandler(sessions: SessionManager): (req: IncomingMessage, res: ServerResponse) => Promise<void>;
|
|
42
|
+
/**
|
|
43
|
+
* Create h3 app for health check and service info endpoints.
|
|
44
|
+
* The MCP endpoint is handled separately by handleMcpRequest.
|
|
45
|
+
*/
|
|
46
|
+
export declare function createHealthApp(): H3;
|
|
47
|
+
//# sourceMappingURL=http.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAEjE,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AAGnE,OAAO,EAAiC,KAAK,EAAE,EAAE,MAAM,IAAI,CAAC;AAK5D,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAI/C,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAE/C;;;;;GAKG;AACH,wBAAgB,eAAe,IAAI,MAAM,CAuDxC;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,cAAc,EACnB,QAAQ,EAAE,cAAc,GACvB,OAAO,CAAC,IAAI,CAAC,CAwFf;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,cAAc,GACvB,CAAC,GAAG,EAAE,eAAe,EAAE,GAAG,EAAE,cAAc,KAAK,OAAO,CAAC,IAAI,CAAC,CAI9D;AAED;;;GAGG;AACH,wBAAgB,eAAe,IAAI,EAAE,CAkBpC"}
|
package/dist/http.js
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import "./version-Cw8OGt4r.js";
|
|
2
|
+
import { a as SessionManager, i as handleMcpRequest, n as createMcpRequestHandler, r as createMcpServer, t as createHealthApp } from "./http-BJUKoZdb.js";
|
|
3
|
+
export { SessionManager, createHealthApp, createMcpRequestHandler, createMcpServer, handleMcpRequest };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Forge MCP Server — Stdio Transport
|
|
4
|
+
*
|
|
5
|
+
* This is the local execution mode using stdio transport.
|
|
6
|
+
* For remote HTTP deployment, use server.ts instead.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* npx @studiometa/forge-mcp
|
|
10
|
+
*
|
|
11
|
+
* Or in Claude Desktop config:
|
|
12
|
+
* {
|
|
13
|
+
* "mcpServers": {
|
|
14
|
+
* "forge": {
|
|
15
|
+
* "command": "forge-mcp",
|
|
16
|
+
* "env": { "FORGE_API_TOKEN": "your-token" }
|
|
17
|
+
* }
|
|
18
|
+
* }
|
|
19
|
+
* }
|
|
20
|
+
*/
|
|
21
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
22
|
+
/**
|
|
23
|
+
* Create and configure the MCP server.
|
|
24
|
+
*/
|
|
25
|
+
export declare function createStdioServer(): Server;
|
|
26
|
+
/**
|
|
27
|
+
* Start the stdio server.
|
|
28
|
+
*/
|
|
29
|
+
export declare function startStdioServer(): Promise<void>;
|
|
30
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AAQnE;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CAkC1C;AAED;;GAEG;AACH,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC,CAKtD"}
|