stringcup 3.30.0__tar.gz → 3.32.0__tar.gz
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.
- {stringcup-3.30.0/stringcup.egg-info → stringcup-3.32.0}/PKG-INFO +1 -1
- {stringcup-3.30.0 → stringcup-3.32.0/stringcup.egg-info}/PKG-INFO +1 -1
- {stringcup-3.30.0 → stringcup-3.32.0}/stringcup.py +52 -6
- {stringcup-3.30.0 → stringcup-3.32.0}/stringcup_mcp.py +32 -6
- {stringcup-3.30.0 → stringcup-3.32.0}/LICENSE +0 -0
- {stringcup-3.30.0 → stringcup-3.32.0}/NOTICE +0 -0
- {stringcup-3.30.0 → stringcup-3.32.0}/PYPI-README.md +0 -0
- {stringcup-3.30.0 → stringcup-3.32.0}/pyproject.toml +0 -0
- {stringcup-3.30.0 → stringcup-3.32.0}/setup.cfg +0 -0
- {stringcup-3.30.0 → stringcup-3.32.0}/stringcup.egg-info/SOURCES.txt +0 -0
- {stringcup-3.30.0 → stringcup-3.32.0}/stringcup.egg-info/dependency_links.txt +0 -0
- {stringcup-3.30.0 → stringcup-3.32.0}/stringcup.egg-info/entry_points.txt +0 -0
- {stringcup-3.30.0 → stringcup-3.32.0}/stringcup.egg-info/requires.txt +0 -0
- {stringcup-3.30.0 → stringcup-3.32.0}/stringcup.egg-info/top_level.txt +0 -0
|
@@ -75,10 +75,10 @@ except ImportError as _exc: # pragma: no cover
|
|
|
75
75
|
"On Python 3.7 pin it below 46 (see requirements.txt) — 46 drops 3.7."
|
|
76
76
|
) from _exc
|
|
77
77
|
|
|
78
|
-
__version__ = "3.
|
|
78
|
+
__version__ = "3.29.0"
|
|
79
79
|
|
|
80
80
|
#: Numeric form, for comparisons. Compare this, never `__version__`.
|
|
81
|
-
version_info = (3,
|
|
81
|
+
version_info = (3, 29, 0)
|
|
82
82
|
|
|
83
83
|
#: Version of the PyPI DISTRIBUTION, which ships this module and
|
|
84
84
|
#: `stringcup_mcp.py` together. **This is a third number and it is not
|
|
@@ -109,7 +109,7 @@ version_info = (3, 28, 0)
|
|
|
109
109
|
#: It must increase whenever either module's version does.
|
|
110
110
|
#: `clients/python/test_contract.py` snapshots all three and fails on any
|
|
111
111
|
#: change, so bumping a module forces a decision about this one.
|
|
112
|
-
__dist_version__ = "3.
|
|
112
|
+
__dist_version__ = "3.32.0"
|
|
113
113
|
|
|
114
114
|
__all__ = [
|
|
115
115
|
"Client",
|
|
@@ -247,6 +247,7 @@ FEATURES = {
|
|
|
247
247
|
"handoff_expiry": (3, 26, 0), # the handoff block carries the relay deadline
|
|
248
248
|
"handoff_guide_url": (3, 27, 0), # the block tells a responder where the guide is
|
|
249
249
|
"sync_barrier_returns_drained": (3, 28, 0), # the barrier no longer destroys what it reads
|
|
250
|
+
"handoff_objective": (3, 29, 0), # the brief and the operator setup ride the block
|
|
250
251
|
}
|
|
251
252
|
|
|
252
253
|
DEFAULT_BASE_URL = "https://stringcup.com/api/v2"
|
|
@@ -2041,16 +2042,44 @@ class Client:
|
|
|
2041
2042
|
|
|
2042
2043
|
return info
|
|
2043
2044
|
|
|
2044
|
-
def handoff_block(self, info: dict, role: str = "responder"
|
|
2045
|
+
def handoff_block(self, info: dict, role: str = "responder",
|
|
2046
|
+
objective: Optional[str] = None) -> str:
|
|
2045
2047
|
"""
|
|
2046
2048
|
The block an operator pastes to the other agent, secret included.
|
|
2047
2049
|
|
|
2048
2050
|
Exists so the secret cannot be forgotten. The handoff was already a
|
|
2049
2051
|
copy-paste; carrying one more line in it is the entire cost of
|
|
2050
2052
|
authenticating first contact.
|
|
2053
|
+
|
|
2054
|
+
`objective` is what the work IS. Pass the brief your own operator gave
|
|
2055
|
+
you. A responder handed a token and nothing else is an untasked agent,
|
|
2056
|
+
and this project already records what that costs: both sides assume
|
|
2057
|
+
the other was briefed, and neither says so.
|
|
2051
2058
|
"""
|
|
2052
|
-
lines = [
|
|
2053
|
-
|
|
2059
|
+
lines = ["STRINGCUP HANDOFF", ""]
|
|
2060
|
+
|
|
2061
|
+
# THE OPERATOR SECTION COMES FIRST BECAUSE IT IS READ BEFORE PASTING.
|
|
2062
|
+
#
|
|
2063
|
+
# Self-install is a bonus that usually does not arrive. Measured on one
|
|
2064
|
+
# machine on 2026-09-17: the identical command has been both permitted
|
|
2065
|
+
# and refused repeatedly, and four controlled runs in one sitting were
|
|
2066
|
+
# refused every time -- the agent's role, the operator's own phrasing
|
|
2067
|
+
# and the presence of a task brief each tested and each eliminated. A
|
|
2068
|
+
# string permitted earlier was refused later, so nothing about the
|
|
2069
|
+
# prompt is the cause. It is unpredictable, not uniformly blocked. Onboarding is two operator actions, and putting
|
|
2070
|
+
# the first one here means the operator reads it at the moment they are
|
|
2071
|
+
# already acting, rather than after a refused attempt costs a round
|
|
2072
|
+
# trip against a token that expires.
|
|
2073
|
+
lines += [
|
|
2074
|
+
" BEFORE YOU PASTE THIS -- for the operator, not the agent.",
|
|
2075
|
+
" If the receiving agent has no Stringcup tools yet, run this in",
|
|
2076
|
+
" its project and restart it first. An MCP config is read at",
|
|
2077
|
+
" startup, so the restart is required either way, and the agent",
|
|
2078
|
+
" is often refused when it tries this itself:",
|
|
2079
|
+
"",
|
|
2080
|
+
" claude mcp add stringcup -- uvx --from stringcup stringcup-mcp",
|
|
2081
|
+
"",
|
|
2082
|
+
" The token below is time-boxed, so a setup detour can outlive it.",
|
|
2054
2083
|
"",
|
|
2055
2084
|
" YOUR ROLE: %s" % role,
|
|
2056
2085
|
" TOKEN: %s" % info["token"],
|
|
@@ -2104,6 +2133,23 @@ class Client:
|
|
|
2104
2133
|
# prompt because it asks an agent to fetch a web page and do as it
|
|
2105
2134
|
# says. Naming where the guide is does not ask for obedience, and an
|
|
2106
2135
|
# agent declining is still a correct outcome.
|
|
2136
|
+
# THE WORK, NOT JUST THE CONNECTION. An agent that arrives holding a
|
|
2137
|
+
# token and no brief has to guess, or ask -- and its peer, which does
|
|
2138
|
+
# have a brief, reads the silence as the peer being slow rather than
|
|
2139
|
+
# uninformed. The initiator was required to prompt for an objective
|
|
2140
|
+
# before pairing, so it always has one to pass on; not passing it was
|
|
2141
|
+
# an omission rather than a design.
|
|
2142
|
+
if objective:
|
|
2143
|
+
lines += [
|
|
2144
|
+
"",
|
|
2145
|
+
" OBJECTIVE: %s" % objective,
|
|
2146
|
+
"",
|
|
2147
|
+
" That is what your operator asked of the agent that opened this",
|
|
2148
|
+
" pairing. Confirm it with your own operator rather than adopting",
|
|
2149
|
+
" it -- it reached you through them, and they may be handing you a",
|
|
2150
|
+
" different part of the work.",
|
|
2151
|
+
]
|
|
2152
|
+
|
|
2107
2153
|
lines += [
|
|
2108
2154
|
"",
|
|
2109
2155
|
" No Stringcup tools yet, or unsure what to do with this?",
|
|
@@ -90,7 +90,7 @@ stringcup.require_features("short_timeouts", "sent_seq", "inbox_quota_errors",
|
|
|
90
90
|
"verified_pairing_pins", "local_pairing_role",
|
|
91
91
|
"header_framed_verify", "undecryptable_visible", "structural_pin_rollback")
|
|
92
92
|
|
|
93
|
-
__version__ = "1.
|
|
93
|
+
__version__ = "1.26.0"
|
|
94
94
|
|
|
95
95
|
#: The MCP revision this server implements.
|
|
96
96
|
PROTOCOL_VERSION = "2025-06-18"
|
|
@@ -129,7 +129,7 @@ _IDENTITY_EXCLUSIVE = None
|
|
|
129
129
|
#:
|
|
130
130
|
#: A newer library is NOT an error: it is usually fine and blocking it would
|
|
131
131
|
#: break legitimate installs. It is reported, not refused.
|
|
132
|
-
BUILT_AGAINST = (3,
|
|
132
|
+
BUILT_AGAINST = (3, 29, 0)
|
|
133
133
|
|
|
134
134
|
|
|
135
135
|
def _version_note() -> Optional[str]:
|
|
@@ -509,13 +509,14 @@ def tool_whoami(arguments: Dict[str, Any]) -> Dict[str, Any]:
|
|
|
509
509
|
def tool_open_rendezvous(arguments: Dict[str, Any]) -> Dict[str, Any]:
|
|
510
510
|
me = client()
|
|
511
511
|
info = me.open_rendezvous()
|
|
512
|
+
objective = arguments.get("objective")
|
|
512
513
|
return {
|
|
513
514
|
"token": info["token"],
|
|
514
515
|
# Generated here and NEVER sent to the relay. The relay issues the
|
|
515
516
|
# token, so the token authenticates nothing about a key the relay
|
|
516
517
|
# served; this is the half it cannot know.
|
|
517
518
|
"secret": info.get("secret"),
|
|
518
|
-
"handoff": me.handoff_block(info),
|
|
519
|
+
"handoff": me.handoff_block(info, objective=objective),
|
|
519
520
|
# The relay derives and reports the role; echo it rather than assuming.
|
|
520
521
|
"role": info.get("role", "initiator"),
|
|
521
522
|
# SAME REASONING, ONE FIELD OVER, and it was missing: the relay returns
|
|
@@ -531,7 +532,15 @@ def tool_open_rendezvous(arguments: Dict[str, Any]) -> Dict[str, Any]:
|
|
|
531
532
|
"\u2014 the token AND the secret. The secret never reaches the relay, which "
|
|
532
533
|
"is what lets the pairing prove neither key was substituted; the token "
|
|
533
534
|
"alone cannot, because the relay issued it. Then call await_peer with both. "
|
|
534
|
-
"You are the initiator: you speak first once paired
|
|
535
|
+
"You are the initiator: you speak first once paired.\n\n"
|
|
536
|
+
"TELL YOUR OPERATOR TO CONFIGURE THE SECOND AGENT BEFORE PASTING THIS. "
|
|
537
|
+
"You were able to reach Stringcup; the responder frequently is not, and "
|
|
538
|
+
"cannot fix it from inside its own session \u2014 an MCP config is read at "
|
|
539
|
+
"startup, so it must be set up and then restarted before it can join. "
|
|
540
|
+
"Observed twice on one machine: the initiator registered the server fine "
|
|
541
|
+
"and the responder was refused. Setting the second agent up first turns "
|
|
542
|
+
"two round trips into none, and the rendezvous is time-boxed, so a setup "
|
|
543
|
+
"detour can outlive the token."
|
|
535
544
|
),
|
|
536
545
|
}
|
|
537
546
|
|
|
@@ -1074,9 +1083,26 @@ TOOLS: List[Dict[str, Any]] = [
|
|
|
1074
1083
|
"token proves nothing about a key the relay served, but a tag computed "
|
|
1075
1084
|
"over both public keys with the secret matches only if neither key was "
|
|
1076
1085
|
"substituted. It costs your operator nothing — the same single paste was "
|
|
1077
|
-
"already happening. Never put the secret in a message
|
|
1086
|
+
"already happening. Never put the secret in a message.\n\n"
|
|
1087
|
+
"PASS `objective` — the brief your own operator gave you, in one or "
|
|
1088
|
+
"two sentences. It rides the handoff so the other agent arrives knowing "
|
|
1089
|
+
"what the work is. Without it they get a token and nothing else, and an "
|
|
1090
|
+
"untasked agent and a briefed one each assume the other was told, which "
|
|
1091
|
+
"reads as the peer being slow rather than uninformed. You were required "
|
|
1092
|
+
"to ask your operator for an objective before pairing, so you have one."
|
|
1078
1093
|
),
|
|
1079
|
-
"inputSchema": {
|
|
1094
|
+
"inputSchema": {
|
|
1095
|
+
"type": "object",
|
|
1096
|
+
"properties": {
|
|
1097
|
+
"objective": {
|
|
1098
|
+
"type": "string",
|
|
1099
|
+
"description": (
|
|
1100
|
+
"What the work is, from your own operator's brief. Travels "
|
|
1101
|
+
"in the handoff block so the peer is not left guessing."
|
|
1102
|
+
),
|
|
1103
|
+
},
|
|
1104
|
+
},
|
|
1105
|
+
},
|
|
1080
1106
|
"handler": tool_open_rendezvous,
|
|
1081
1107
|
},
|
|
1082
1108
|
{
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|