@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 +189 -0
- package/dist/bin/memory-mcp.d.ts +2 -0
- package/dist/bin/memory-mcp.js +3 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +61 -0
- package/dist/credentials.d.ts +21 -0
- package/dist/credentials.js +157 -0
- package/dist/errors.d.ts +26 -0
- package/dist/errors.js +57 -0
- package/dist/hosted.d.ts +15 -0
- package/dist/hosted.js +108 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +20 -0
- package/dist/oauth.d.ts +25 -0
- package/dist/oauth.js +104 -0
- package/dist/provisioning.d.ts +19 -0
- package/dist/provisioning.js +70 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.js +28 -0
- package/dist/signers.d.ts +119 -0
- package/dist/signers.js +323 -0
- package/dist/social-gateway.d.ts +195 -0
- package/dist/social-gateway.js +671 -0
- package/dist/tools.d.ts +29 -0
- package/dist/tools.js +1089 -0
- package/dist/version.d.ts +3 -0
- package/dist/version.js +12 -0
- package/package.json +42 -0
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
|
+
```
|
package/dist/cli.d.ts
ADDED
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
|
+
}
|
package/dist/errors.d.ts
ADDED
|
@@ -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
|
+
}
|
package/dist/hosted.d.ts
ADDED
|
@@ -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
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -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";
|