@socialproof/memory-mcp 0.1.1

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 ADDED
@@ -0,0 +1,189 @@
1
+ # Memory MCP
2
+
3
+ Secure stdio and hosted Streamable HTTP MCP server for `@socialproof/memory` and MySocial agent actions.
4
+
5
+ The MCP process resolves a local signer, keeps private key material inside the
6
+ process, and sends only public keys, signatures, and bounded session credentials
7
+ to upstream services. Raw `key`, `ownerCoSignKey`, `x-delegate-key`, and
8
+ `x-owner-delegate-key` transport is intentionally unsupported.
9
+
10
+ ## Install and run
11
+
12
+ ```bash
13
+ pnpm --filter @socialproof/memory-mcp build
14
+ pnpm --filter @socialproof/memory-mcp exec memory-mcp
15
+ ```
16
+
17
+ The package binary and runtime version are both sourced from `package.json`.
18
+ `node packages/mcp/dist/index.js` remains supported for existing local setups.
19
+
20
+ ### Localnet organization prerequisites
21
+
22
+ After every `myso start --force-regenesis`, bootstrap MySocial and create one
23
+ approved platform before starting the memory relayer:
24
+
25
+ ```bash
26
+ ./scripts/bootstrap.sh
27
+ ./scripts/proof-of-creativity-runnable.sh --create-platform
28
+ ```
29
+
30
+ For a relayer running against local GraphQL/fullnode, set
31
+ `SOCIAL_CHAIN_AUTO_DISCOVERY=true`, `SOCIAL_CHAIN_GRAPHQL_URL=http://127.0.0.1:9125/graphql`,
32
+ and `LOCAL_SPONSOR_ENABLED=true`. Discovery fills missing singleton IDs, verifies
33
+ their type and shared ownership against the fullnode, and replaces stale localnet
34
+ IDs after regenesis. Local sponsorship uses `SERVER_MYSO_PRIVATE_KEYS` only as gas
35
+ owners; the agent or owner wallet must still sign the transaction.
36
+
37
+ ## Credentials
38
+
39
+ The default configuration path is `~/.memory/credentials.json`. Override it with
40
+ `MEMORY_MCP_CREDENTIALS_FILE`.
41
+
42
+ Recommended macOS Keychain configuration:
43
+
44
+ ```json
45
+ {
46
+ "accountId": "<memory-account-object-id>",
47
+ "serverUrl": "http://127.0.0.1:8000",
48
+ "platformId": "<platform-object-id>",
49
+ "socialEnabled": false,
50
+ "signer": {
51
+ "type": "keychain",
52
+ "service": "network.mysocial.memory-mcp",
53
+ "account": "my-agent"
54
+ }
55
+ }
56
+ ```
57
+
58
+ `socialEnabled` defaults to `false`. When enabled, Tier 1A reactions and Tier 1B
59
+ publishing actions are available only when authenticated capabilities, approval
60
+ policy, and platform scope allow them. Network and shared-object IDs are discovered
61
+ from authenticated agent context; callers do not need to copy raw object IDs:
62
+
63
+ ```json
64
+ {
65
+ "accountId": "<memory-account-object-id>",
66
+ "serverUrl": "http://127.0.0.1:8000",
67
+ "socialEnabled": true,
68
+ "signer": {
69
+ "type": "keychain",
70
+ "service": "network.mysocial.memory-mcp",
71
+ "account": "my-agent"
72
+ }
73
+ }
74
+ ```
75
+
76
+ `mysoNetwork`, `mysoRpcUrl`, and `socialChain` remain optional deployment pins.
77
+ When supplied, they must exactly match authenticated context or execution fails closed.
78
+
79
+ Store a 32-byte Ed25519 key encoded as hex:
80
+
81
+ ```bash
82
+ security add-generic-password \
83
+ -U \
84
+ -s network.mysocial.memory-mcp \
85
+ -a my-agent \
86
+ -w '<agent-private-key-hex>'
87
+ ```
88
+
89
+ For local development only, a file-backed signer is available:
90
+
91
+ ```json
92
+ {
93
+ "accountId": "<memory-account-object-id>",
94
+ "serverUrl": "http://127.0.0.1:8000",
95
+ "socialEnabled": false,
96
+ "signer": {
97
+ "type": "development-file",
98
+ "path": "~/.memory/dev-agent.key"
99
+ }
100
+ }
101
+ ```
102
+
103
+ The key file must be a regular, non-symlink file with mode `0600` or stricter,
104
+ and the process must set `MEMORY_MCP_ALLOW_INSECURE_DEV_FILE=1`.
105
+
106
+ Hosted or wallet-backed runtimes should import `InjectedAgentSigner` and
107
+ `createMemoryMcpServer` instead of writing a key to disk. The current Memory SDK
108
+ still needs a local secret-backed client; callers using a non-exportable signer
109
+ must inject their own `MemoryClient` adapter.
110
+
111
+ ## Tools and result contract
112
+
113
+ Memory tools:
114
+
115
+ - `memory_remember`
116
+ - `memory_recall`
117
+ - `memory_health`
118
+
119
+ Secure social tools currently exposed when enabled:
120
+
121
+ - `social_react_post`
122
+ - `social_react_comment`
123
+ - `social_create_post`
124
+ - `social_create_comment`
125
+ - `social_create_repost`
126
+ - `social_remove_post_reaction`, `social_remove_comment_reaction`
127
+ - `social_edit_post`, `social_edit_comment`, `social_remove_repost`
128
+ - `social_follow_profile`, `social_unfollow_profile`
129
+ - `social_block_profile`, `social_unblock_profile`
130
+ - `messaging_send_message`
131
+ - `messaging_create_group`, `messaging_list_inbox`, `messaging_wait_for_message`
132
+ - `organization_get_control`
133
+ - `organization_create`, `organization_update_metadata`, `organization_update_category`
134
+ - `organization_ensure_memory_group`, `organization_define_role`
135
+ - `organization_assign_role`, `organization_revoke_role`, `organization_create_invitation`
136
+ - `organization_accept_invitation`, `organization_decline_invitation`
137
+ - `agent_provision_signer`, `agent_register_root`, `agent_register_child`
138
+ - `agent_update_child`, `agent_deactivate_child`, `agent_revoke_child`
139
+ - `chain_get_action_status`
140
+ - `chain_request_action_approval`
141
+ - `chain_approve_action`
142
+ - `chain_prepare_approved_action`
143
+ - `chain_submit_approved_action`
144
+
145
+ Every social write checks fresh authenticated agent context and sends only its
146
+ hard-coded registry ID, schema input, and required idempotency key to
147
+ `/api/chain/actions/prepare`. The trusted gateway builds and durably records the
148
+ registered action, then the local signer signs the sponsored transaction and
149
+ submits only the signature to `/api/chain/actions/submit`. These paths have no
150
+ direct-sign fallback and accept no model-supplied Move target or PTB. Reusing an
151
+ idempotency key with the same input returns the existing result; reusing it with
152
+ different input fails closed.
153
+
154
+ Every tool declares an output schema and MCP annotations and returns both a
155
+ short text summary and structured content:
156
+
157
+ ```json
158
+ {
159
+ "ok": false,
160
+ "error": {
161
+ "code": "CAPABILITY_DENIED",
162
+ "message": "The agent is not permitted to perform this action.",
163
+ "retryable": false,
164
+ "approvalRequired": false
165
+ }
166
+ }
167
+ ```
168
+
169
+ Owner-approved actions use an exact-input intent followed by wallet transaction signing. The approval binds account, agent, registry action/version, parameter hash, idempotency key, expiry, and one prepared action. No owner private key crosses HTTP.
170
+
171
+ Organization creation, root-agent registration, role changes, invitations, and organization deactivation use that owner-wallet flow. Delegated child-agent operations execute automatically only when the authenticated parent holds the matching capability; Move enforces ancestry, capability subset, platform scope, depth, and spend constraints. `agent_provision_signer` stores local secrets in macOS Keychain and returns only the public key, derived address, and signer reference. Hosted deployments inject a KMS-backed `AgentSignerProvisioner`.
172
+
173
+ `messaging_wait_for_message` is a bounded long-poll primitive for OpenClaw response loops. It returns encrypted payload digests and durable URIs; plaintext and group keys never cross the MCP transport.
174
+
175
+ ## Hosted MCP
176
+
177
+ `createHostedMcpHttpServer` serves stateless Streamable HTTP at `/mcp`. It verifies a Bearer token on every request, requires the exact RFC 8707 resource audience and `mcp:connect`, filters tools by OAuth scope, and enforces host/origin protections. Tokens must carry `accountId`, `agentObjectId`, and `signerSessionId` claims.
178
+
179
+ `OAuthIntrospectionVerifier` provides an RFC 7662 verifier that checks the authorization server on every request, so revoked tokens stop working without waiting for a local cache.
180
+
181
+ `KmsSessionAgentSigner` reauthorizes its session before every authentication or transaction signature. `HttpKmsSessionAuthorizer` and `RemoteKmsSigningProvider` implement the private HTTPS KMS/HSM service contract without exporting keys.
182
+
183
+ ## Verification
184
+
185
+ ```bash
186
+ pnpm --filter @socialproof/memory-mcp typecheck
187
+ pnpm --filter @socialproof/memory-mcp test
188
+ pnpm --filter @socialproof/memory-mcp pack:check
189
+ ```
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ import { runCliMain } from "../cli.js";
3
+ runCliMain();
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export declare function runCli(): Promise<void>;
2
+ export declare function runCliMain(): void;
package/dist/cli.js ADDED
@@ -0,0 +1,61 @@
1
+ import { Memory } from "@socialproof/memory";
2
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
+ import { loadCredentials } from "./credentials.js";
4
+ import { formatStartupError } from "./errors.js";
5
+ import { createMemoryMcpServer } from "./server.js";
6
+ import { SponsoredSocialGateway } from "./social-gateway.js";
7
+ import { createCliSigner } from "./signers.js";
8
+ import { LocalKeychainAgentProvisioner } from "./provisioning.js";
9
+ export async function runCli() {
10
+ const credentials = loadCredentials();
11
+ const signer = createCliSigner(credentials.signer);
12
+ const memory = await signer.withLocalSecret((secret) => Memory.create({
13
+ key: secret.slice(),
14
+ accountId: credentials.accountId,
15
+ serverUrl: credentials.serverUrl,
16
+ platformId: credentials.platformId,
17
+ }));
18
+ const social = credentials.socialEnabled && credentials.social
19
+ ? new SponsoredSocialGateway({
20
+ signer,
21
+ accountId: credentials.accountId,
22
+ serverUrl: credentials.socialGatewayUrl,
23
+ platformId: credentials.platformId,
24
+ network: credentials.social.network,
25
+ rpcUrl: credentials.social.rpcUrl,
26
+ chain: credentials.social.chain,
27
+ })
28
+ : undefined;
29
+ const server = createMemoryMcpServer({
30
+ memory,
31
+ social,
32
+ agentProvisioner: process.platform === "darwin"
33
+ ? new LocalKeychainAgentProvisioner()
34
+ : undefined,
35
+ });
36
+ const transport = new StdioServerTransport();
37
+ let closing = false;
38
+ const shutdown = async () => {
39
+ if (closing)
40
+ return;
41
+ closing = true;
42
+ memory.destroy();
43
+ signer.destroy();
44
+ await server.close();
45
+ };
46
+ process.once("SIGINT", () => void shutdown());
47
+ process.once("SIGTERM", () => void shutdown());
48
+ try {
49
+ await server.connect(transport);
50
+ }
51
+ catch (error) {
52
+ await shutdown();
53
+ throw error;
54
+ }
55
+ }
56
+ export function runCliMain() {
57
+ runCli().catch((error) => {
58
+ process.stderr.write(`memory-mcp: ${formatStartupError(error)}\n`);
59
+ process.exitCode = 1;
60
+ });
61
+ }
@@ -0,0 +1,21 @@
1
+ import type { SocialChainConfig } from "@socialproof/social";
2
+ import type { CliSignerReference } from "./signers.js";
3
+ export declare const DEFAULT_CREDENTIALS_PATH: string;
4
+ export interface McpCredentials {
5
+ accountId: string;
6
+ serverUrl: string;
7
+ platformId?: string;
8
+ socialEnabled: boolean;
9
+ socialGatewayUrl: string;
10
+ social?: {
11
+ /** Optional deployment pin. Authenticated agent context remains authoritative. */
12
+ network?: "mainnet" | "testnet" | "devnet" | "localnet";
13
+ /** Optional deployment pin. Authenticated agent context remains authoritative. */
14
+ rpcUrl?: string;
15
+ /** Optional object-id pins for deployments that previously configured them locally. */
16
+ chain?: Partial<SocialChainConfig>;
17
+ };
18
+ signer: CliSignerReference;
19
+ }
20
+ export declare function parseCredentials(value: unknown): McpCredentials;
21
+ export declare function loadCredentials(credentialsPath?: string): McpCredentials;
@@ -0,0 +1,157 @@
1
+ import fs from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { McpRuntimeError } from "./errors.js";
5
+ export const DEFAULT_CREDENTIALS_PATH = path.join(os.homedir(), ".memory", "credentials.json");
6
+ function requireObject(value, label) {
7
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
8
+ throw new McpRuntimeError("INVALID_CONFIGURATION", `${label} must be a JSON object.`);
9
+ }
10
+ return value;
11
+ }
12
+ function requireNonEmptyString(value, label) {
13
+ if (typeof value !== "string" || !value.trim()) {
14
+ throw new McpRuntimeError("INVALID_CONFIGURATION", `${label} must be a non-empty string.`);
15
+ }
16
+ return value.trim();
17
+ }
18
+ function optionalString(value, label) {
19
+ if (value === undefined)
20
+ return undefined;
21
+ return requireNonEmptyString(value, label);
22
+ }
23
+ function normalizeUrl(value, label, fallback) {
24
+ const raw = optionalString(value, label) ?? fallback;
25
+ let parsed;
26
+ try {
27
+ parsed = new URL(raw);
28
+ }
29
+ catch {
30
+ throw new McpRuntimeError("INVALID_CONFIGURATION", `${label} must be an absolute HTTP(S) URL.`);
31
+ }
32
+ if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
33
+ throw new McpRuntimeError("INVALID_CONFIGURATION", `${label} must use HTTP or HTTPS.`);
34
+ }
35
+ const localHostnames = new Set(["localhost", "127.0.0.1", "::1"]);
36
+ if (parsed.protocol === "http:" && !localHostnames.has(parsed.hostname)) {
37
+ throw new McpRuntimeError("INVALID_CONFIGURATION", `${label} must use HTTPS unless it targets localhost.`);
38
+ }
39
+ return parsed.toString().replace(/\/$/, "");
40
+ }
41
+ function parseSigner(value) {
42
+ const signer = requireObject(value, "signer");
43
+ if (signer.type === "keychain") {
44
+ const parsed = {
45
+ type: "keychain",
46
+ service: requireNonEmptyString(signer.service, "signer.service"),
47
+ account: requireNonEmptyString(signer.account, "signer.account"),
48
+ };
49
+ return parsed;
50
+ }
51
+ if (signer.type === "development-file") {
52
+ const parsed = {
53
+ type: "development-file",
54
+ path: requireNonEmptyString(signer.path, "signer.path"),
55
+ };
56
+ return parsed;
57
+ }
58
+ if (signer.type === "injected") {
59
+ throw new McpRuntimeError("INVALID_CONFIGURATION", "Injected signers must be supplied programmatically and cannot be configured in credentials.json.");
60
+ }
61
+ throw new McpRuntimeError("INVALID_CONFIGURATION", "signer.type must be keychain or development-file.");
62
+ }
63
+ function parseSocialChain(value) {
64
+ if (value === undefined)
65
+ return undefined;
66
+ const chain = requireObject(value, "socialChain");
67
+ const parsed = {};
68
+ const fields = [
69
+ "packageId",
70
+ "usernameRegistryId",
71
+ "platformRegistryId",
72
+ "platformObjectId",
73
+ "blockListRegistryId",
74
+ "postConfigId",
75
+ "memoryConfigId",
76
+ "mydataRegistryId",
77
+ "clockId",
78
+ "socialGraphId",
79
+ "messagingPackageId",
80
+ "messagingVersionId",
81
+ "messagingConfigId",
82
+ "messagingNamespaceId",
83
+ "messagingGroupManagerId",
84
+ "messagingGroupLeaverId",
85
+ ];
86
+ for (const field of fields) {
87
+ const configured = optionalString(chain[field], `socialChain.${field}`);
88
+ if (configured !== undefined)
89
+ parsed[field] = configured;
90
+ }
91
+ return Object.keys(parsed).length > 0 ? parsed : undefined;
92
+ }
93
+ function parseNetwork(value) {
94
+ if (value === undefined)
95
+ return undefined;
96
+ if (value === "mainnet" || value === "testnet" || value === "devnet" || value === "localnet") {
97
+ return value;
98
+ }
99
+ throw new McpRuntimeError("INVALID_CONFIGURATION", "mysoNetwork must be mainnet, testnet, devnet, or localnet when provided.");
100
+ }
101
+ export function parseCredentials(value) {
102
+ const input = requireObject(value, "credentials");
103
+ if ("key" in input || "ownerCoSignKey" in input) {
104
+ throw new McpRuntimeError("UNSAFE_LEGACY_CREDENTIALS", "Raw key and ownerCoSignKey fields are no longer accepted. Store the agent key in macOS Keychain and configure a signer reference; owner operations require the future approval flow.");
105
+ }
106
+ const accountId = requireNonEmptyString(input.accountId, "accountId");
107
+ if (!/^0x[0-9a-fA-F]{1,64}$/.test(accountId)) {
108
+ throw new McpRuntimeError("INVALID_CONFIGURATION", "accountId must be a valid MySo object ID.");
109
+ }
110
+ const serverUrl = normalizeUrl(input.serverUrl, "serverUrl", "https://memory.mysocial.network");
111
+ const socialGatewayUrl = normalizeUrl(input.socialGatewayUrl, "socialGatewayUrl", serverUrl);
112
+ if (input.socialEnabled !== undefined && typeof input.socialEnabled !== "boolean") {
113
+ throw new McpRuntimeError("INVALID_CONFIGURATION", "socialEnabled must be a boolean.");
114
+ }
115
+ const socialEnabled = input.socialEnabled === true;
116
+ const social = socialEnabled
117
+ ? {
118
+ network: parseNetwork(input.mysoNetwork),
119
+ rpcUrl: input.mysoRpcUrl === undefined
120
+ ? undefined
121
+ : normalizeUrl(input.mysoRpcUrl, "mysoRpcUrl", ""),
122
+ chain: parseSocialChain(input.socialChain),
123
+ }
124
+ : undefined;
125
+ return {
126
+ accountId,
127
+ serverUrl,
128
+ platformId: optionalString(input.platformId, "platformId"),
129
+ socialEnabled,
130
+ socialGatewayUrl,
131
+ social,
132
+ signer: parseSigner(input.signer),
133
+ };
134
+ }
135
+ export function loadCredentials(credentialsPath = process.env.MEMORY_MCP_CREDENTIALS_FILE ?? DEFAULT_CREDENTIALS_PATH) {
136
+ let raw;
137
+ try {
138
+ const stat = fs.lstatSync(credentialsPath);
139
+ if (!stat.isFile() || stat.isSymbolicLink() || (stat.mode & 0o022) !== 0) {
140
+ throw new McpRuntimeError("INVALID_CONFIGURATION", `MCP credentials at ${credentialsPath} must be a regular file that is not group- or world-writable.`);
141
+ }
142
+ raw = fs.readFileSync(credentialsPath, "utf8");
143
+ }
144
+ catch (error) {
145
+ if (error instanceof McpRuntimeError)
146
+ throw error;
147
+ throw new McpRuntimeError("INVALID_CONFIGURATION", `Unable to read MCP credentials from ${credentialsPath}.`, { cause: error });
148
+ }
149
+ try {
150
+ return parseCredentials(JSON.parse(raw));
151
+ }
152
+ catch (error) {
153
+ if (error instanceof McpRuntimeError)
154
+ throw error;
155
+ throw new McpRuntimeError("INVALID_CONFIGURATION", `MCP credentials at ${credentialsPath} are not valid JSON.`, { cause: error });
156
+ }
157
+ }
@@ -0,0 +1,26 @@
1
+ export type McpErrorCode = "INVALID_ARGUMENT" | "INVALID_CONFIGURATION" | "UNSAFE_LEGACY_CREDENTIALS" | "SIGNER_UNAVAILABLE" | "SOCIAL_GATEWAY_UNAVAILABLE" | "APPROVAL_FLOW_NOT_AVAILABLE" | "AUTHENTICATION_FAILED" | "CAPABILITY_DENIED" | "RATE_LIMITED" | "CONFLICT" | "UPSTREAM_UNAVAILABLE" | "UNKNOWN_TOOL" | "INTERNAL_ERROR";
2
+ export interface StructuredMcpError {
3
+ code: McpErrorCode;
4
+ message: string;
5
+ retryable: boolean;
6
+ approvalRequired: boolean;
7
+ actionId?: string;
8
+ digest?: string;
9
+ }
10
+ export declare class McpRuntimeError extends Error {
11
+ readonly code: McpErrorCode;
12
+ readonly retryable: boolean;
13
+ readonly approvalRequired: boolean;
14
+ readonly actionId?: string;
15
+ readonly digest?: string;
16
+ constructor(code: McpErrorCode, message: string, options?: {
17
+ retryable?: boolean;
18
+ approvalRequired?: boolean;
19
+ actionId?: string;
20
+ digest?: string;
21
+ cause?: unknown;
22
+ });
23
+ }
24
+ export declare function redactSensitiveText(value: string): string;
25
+ export declare function toStructuredMcpError(error: unknown): StructuredMcpError;
26
+ export declare function formatStartupError(error: unknown): string;
package/dist/errors.js ADDED
@@ -0,0 +1,57 @@
1
+ export class McpRuntimeError extends Error {
2
+ code;
3
+ retryable;
4
+ approvalRequired;
5
+ actionId;
6
+ digest;
7
+ constructor(code, message, options = {}) {
8
+ super(message, { cause: options.cause });
9
+ this.name = "McpRuntimeError";
10
+ this.code = code;
11
+ this.retryable = options.retryable ?? false;
12
+ this.approvalRequired = options.approvalRequired ?? false;
13
+ this.actionId = options.actionId;
14
+ this.digest = options.digest;
15
+ }
16
+ }
17
+ const SECRET_ASSIGNMENT = /\b(key|secret|token|authorization)\b\s*[:=]\s*[^\s,;}]+/gi;
18
+ const MYSO_PRIVATE_KEY = /mysoprivkey1[a-z0-9]+/gi;
19
+ export function redactSensitiveText(value) {
20
+ return value
21
+ .replace(SECRET_ASSIGNMENT, "$1=[REDACTED]")
22
+ .replace(MYSO_PRIVATE_KEY, "[REDACTED_PRIVATE_KEY]")
23
+ .slice(0, 400);
24
+ }
25
+ export function toStructuredMcpError(error) {
26
+ if (error instanceof McpRuntimeError) {
27
+ return {
28
+ code: error.code,
29
+ message: redactSensitiveText(error.message),
30
+ retryable: error.retryable,
31
+ approvalRequired: error.approvalRequired,
32
+ ...(error.actionId ? { actionId: error.actionId } : {}),
33
+ ...(error.digest ? { digest: error.digest } : {}),
34
+ };
35
+ }
36
+ if (error instanceof TypeError && error.message.toLowerCase().includes("fetch")) {
37
+ return {
38
+ code: "UPSTREAM_UNAVAILABLE",
39
+ message: "The configured upstream service is unavailable.",
40
+ retryable: true,
41
+ approvalRequired: false,
42
+ };
43
+ }
44
+ return {
45
+ code: "INTERNAL_ERROR",
46
+ message: "The MCP server could not complete the request.",
47
+ retryable: false,
48
+ approvalRequired: false,
49
+ };
50
+ }
51
+ export function formatStartupError(error) {
52
+ return toStructuredMcpError(error instanceof McpRuntimeError
53
+ ? error
54
+ : new McpRuntimeError("INVALID_CONFIGURATION", "The MCP server could not start.", {
55
+ cause: error,
56
+ })).message;
57
+ }
@@ -0,0 +1,15 @@
1
+ import { type Server } from "node:http";
2
+ import type { OAuthTokenVerifier } from "@modelcontextprotocol/sdk/server/auth/provider.js";
3
+ import { type HostedMcpPrincipal } from "./oauth.js";
4
+ import type { ToolDependencies } from "./tools.js";
5
+ export interface HostedMcpOptions {
6
+ verifier: OAuthTokenVerifier;
7
+ resourceUrl: string;
8
+ authorizationServerUrl: string;
9
+ allowedHosts: readonly string[];
10
+ allowedOrigins?: readonly string[];
11
+ maxContentLength?: number;
12
+ createDependencies(principal: HostedMcpPrincipal): Promise<ToolDependencies>;
13
+ destroyDependencies?(dependencies: ToolDependencies): void | Promise<void>;
14
+ }
15
+ export declare function createHostedMcpHttpServer(options: HostedMcpOptions): Server;
package/dist/hosted.js ADDED
@@ -0,0 +1,108 @@
1
+ import { createServer } from "node:http";
2
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
3
+ import { McpRuntimeError, toStructuredMcpError } from "./errors.js";
4
+ import { authenticateHostedRequest } from "./oauth.js";
5
+ import { createMemoryMcpServer } from "./server.js";
6
+ export function createHostedMcpHttpServer(options) {
7
+ const resource = new URL(options.resourceUrl);
8
+ if (resource.protocol !== "https:" && resource.hostname !== "localhost") {
9
+ throw new McpRuntimeError("INVALID_CONFIGURATION", "Hosted MCP resourceUrl must use HTTPS.");
10
+ }
11
+ if (options.allowedHosts.length === 0) {
12
+ throw new McpRuntimeError("INVALID_CONFIGURATION", "Hosted MCP allowedHosts must not be empty.");
13
+ }
14
+ return createServer((request, response) => {
15
+ void handleHostedRequest(request, response, options, resource);
16
+ });
17
+ }
18
+ async function handleHostedRequest(request, response, options, resource) {
19
+ try {
20
+ applySecurityHeaders(response);
21
+ const url = new URL(request.url ?? "/", resource);
22
+ if (url.pathname === "/.well-known/oauth-protected-resource") {
23
+ if (request.method !== "GET")
24
+ return methodNotAllowed(response, "GET");
25
+ return json(response, 200, {
26
+ resource: resource.toString().replace(/\/$/, ""),
27
+ authorization_servers: [options.authorizationServerUrl],
28
+ scopes_supported: [
29
+ "mcp:connect", "memory:read", "memory:write", "chain:read",
30
+ "social:write", "social:publish", "social:approve", "social:destructive", "ai:spend",
31
+ "messaging:read", "organization:read", "organization:admin", "agent:provision",
32
+ ],
33
+ });
34
+ }
35
+ if (url.pathname !== "/mcp")
36
+ return json(response, 404, { error: "not_found" });
37
+ assertHostAndOrigin(request, options);
38
+ const length = Number(request.headers["content-length"] ?? 0);
39
+ if (!Number.isFinite(length) || length < 0 || length > (options.maxContentLength ?? 1_048_576)) {
40
+ return json(response, 413, { error: "request_too_large" });
41
+ }
42
+ const principal = await authenticateHostedRequest(request, options.verifier, resource);
43
+ const dependencies = await options.createDependencies(principal);
44
+ const scopedDependencies = {
45
+ ...dependencies,
46
+ oauthScopes: new Set(principal.auth.scopes),
47
+ };
48
+ const server = createMemoryMcpServer(scopedDependencies);
49
+ const transport = new StreamableHTTPServerTransport({
50
+ sessionIdGenerator: undefined,
51
+ enableJsonResponse: true,
52
+ });
53
+ request.auth = principal.auth;
54
+ try {
55
+ await server.connect(transport);
56
+ await transport.handleRequest(request, response);
57
+ }
58
+ finally {
59
+ await server.close();
60
+ await options.destroyDependencies?.(dependencies);
61
+ }
62
+ }
63
+ catch (error) {
64
+ if (response.headersSent) {
65
+ response.destroy();
66
+ return;
67
+ }
68
+ const structured = toStructuredMcpError(error);
69
+ const status = structured.code === "AUTHENTICATION_FAILED" ? 401
70
+ : structured.code === "CAPABILITY_DENIED" ? 403 : 400;
71
+ if (status === 401) {
72
+ response.setHeader("WWW-Authenticate", `Bearer resource_metadata="${new URL("/.well-known/oauth-protected-resource", resource).toString()}"`);
73
+ }
74
+ json(response, status, { error: structured });
75
+ }
76
+ }
77
+ function assertHostAndOrigin(request, options) {
78
+ let host = "";
79
+ try {
80
+ host = new URL(`http://${request.headers.host ?? ""}`).hostname.toLowerCase();
81
+ }
82
+ catch {
83
+ throw new McpRuntimeError("AUTHENTICATION_FAILED", "Host is invalid.");
84
+ }
85
+ if (!options.allowedHosts.map((value) => value.toLowerCase()).includes(host)) {
86
+ throw new McpRuntimeError("AUTHENTICATION_FAILED", "Host is not allowed.");
87
+ }
88
+ const origin = request.headers.origin;
89
+ if (origin && !(options.allowedOrigins ?? []).includes(origin)) {
90
+ throw new McpRuntimeError("AUTHENTICATION_FAILED", "Origin is not allowed.");
91
+ }
92
+ }
93
+ function applySecurityHeaders(response) {
94
+ response.setHeader("Cache-Control", "no-store");
95
+ response.setHeader("Content-Security-Policy", "default-src 'none'; frame-ancestors 'none'");
96
+ response.setHeader("Referrer-Policy", "no-referrer");
97
+ response.setHeader("X-Content-Type-Options", "nosniff");
98
+ response.setHeader("X-Frame-Options", "DENY");
99
+ }
100
+ function methodNotAllowed(response, allow) {
101
+ response.setHeader("Allow", allow);
102
+ json(response, 405, { error: "method_not_allowed" });
103
+ }
104
+ function json(response, status, body) {
105
+ response.statusCode = status;
106
+ response.setHeader("content-type", "application/json; charset=utf-8");
107
+ response.end(JSON.stringify(body));
108
+ }
@@ -0,0 +1,11 @@
1
+ export { runCli, runCliMain } from "./cli.js";
2
+ export { DEFAULT_CREDENTIALS_PATH, loadCredentials, parseCredentials, type McpCredentials, } from "./credentials.js";
3
+ export { McpRuntimeError, formatStartupError, redactSensitiveText, toStructuredMcpError, type McpErrorCode, type StructuredMcpError, } from "./errors.js";
4
+ export { createMemoryMcpServer } from "./server.js";
5
+ export { createHostedMcpHttpServer, type HostedMcpOptions } from "./hosted.js";
6
+ export { authenticateHostedRequest, OAuthIntrospectionVerifier, type HostedMcpPrincipal, type OAuthIntrospectionVerifierOptions, } from "./oauth.js";
7
+ export { SponsoredSocialGateway, type SocialGateway, type SponsoredSocialGatewayOptions, } from "./social-gateway.js";
8
+ export { InjectedAgentSigner, KmsSessionAgentSigner, HttpKmsSessionAuthorizer, RemoteKmsSigningProvider, LocalEd25519Signer, createCliSigner, type AgentSigner, type CliSignerReference, type DevelopmentFileSignerReference, type InjectedSignerCallbacks, type KeychainSignerReference, type KmsSessionAuthorizer, type KmsSessionPolicy, type KmsSigningOperation, type KmsSigningProvider, type RemoteKmsClientOptions, type LocalSecretAgentSigner, } from "./signers.js";
9
+ export { LocalKeychainAgentProvisioner, type AgentSignerProvisioner, type ProvisionedAgentSigner, } from "./provisioning.js";
10
+ export { createToolCatalog, executeTool, type MemoryClient, type ToolDependencies, type ToolEnvelope, TOOL_OAUTH_SCOPES, } from "./tools.js";
11
+ export { MCP_PACKAGE_NAME, MCP_PACKAGE_VERSION, MCP_SERVER_NAME, } from "./version.js";